From 7b4706cf79bb97d74fafdf49000483b343d41d87 Mon Sep 17 00:00:00 2001 From: KubaPro010 Date: Sat, 5 Apr 2025 13:48:51 +0200 Subject: [PATCH] add rt timeout, clean up code, add error checking for rdsencoder save, add rds eon af and remove stale values --- .vscode/.server-controller-port.log | 2 +- CMakeLists.txt | 2 +- gen_wave.py | 139 ++++++++++++++-------------- src/ascii_cmd.c | 9 +- src/common.h | 6 +- src/lib.c | 2 +- src/modulator.c | 13 +-- src/rds.c | 73 ++++++++++++--- src/rds.h | 13 ++- src/rds95.c | 9 +- 10 files changed, 159 insertions(+), 109 deletions(-) diff --git a/.vscode/.server-controller-port.log b/.vscode/.server-controller-port.log index e804d2a..c0d6b0c 100644 --- a/.vscode/.server-controller-port.log +++ b/.vscode/.server-controller-port.log @@ -1,5 +1,5 @@ { "port": 13452, - "time": 1743622887495, + "time": 1743850931238, "version": "0.0.3" } \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 62d16ae..65959c2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.10) -project(rds95 VERSION 1.2) +project(rds95 VERSION 1.3) add_compile_options(-Wall -Werror -Wextra -pedantic -O2 -std=c18 -march=native -DVERSION=\"${PROJECT_VERSION}\") diff --git a/gen_wave.py b/gen_wave.py index 33c7e86..54087ad 100644 --- a/gen_wave.py +++ b/gen_wave.py @@ -4,7 +4,7 @@ FFT = PLOT and True import math import io, os if PLOT: import matplotlib.pyplot as plt -if FFT: import numpy as np # Import numpy for FFT +if FFT: import numpy as np DATA_RATE = 1187.5 SIZE_RATIO = 2 @@ -16,33 +16,33 @@ if not sample_rate.is_integer(): raise ValueError("Need a even value") # this is modified from ChristopheJacquet's pydemod def rrcosfilter(NumSamples): - T_delta = 1/float(sample_rate) - sample_num = list(range(NumSamples)) - h_rrc = [0.0] * NumSamples - SymbolPeriod = 1/(2*DATA_RATE) + T_delta = 1/float(sample_rate) + sample_num = list(range(NumSamples)) + h_rrc = [0.0] * NumSamples + SymbolPeriod = 1/(2*DATA_RATE) - for x in sample_num: - t = (x-NumSamples/2)*T_delta - if t == 0.0: - h_rrc[x] = 1.0 - 1 + (4/math.pi) - elif t == SymbolPeriod/4: - h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \ - (math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4)))) - elif t == -SymbolPeriod/4: - h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \ - (math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4)))) - else: - h_rrc[x] = (4*(t/SymbolPeriod)*math.cos(math.pi*t*2/SymbolPeriod))/ \ - (math.pi*t*(1-(4*t/SymbolPeriod)*(4*t/SymbolPeriod))/SymbolPeriod) + for x in sample_num: + t = (x-NumSamples/2)*T_delta + if t == 0.0: + h_rrc[x] = 1.0 - 1 + (4/math.pi) + elif t == SymbolPeriod/4: + h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \ + (math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4)))) + elif t == -SymbolPeriod/4: + h_rrc[x] = (1/math.sqrt(2))*(((1+2/math.pi)* \ + (math.sin(math.pi/4))) + ((1-2/math.pi)*(math.cos(math.pi/4)))) + else: + h_rrc[x] = (4*(t/SymbolPeriod)*math.cos(math.pi*t*2/SymbolPeriod))/ \ + (math.pi*t*(1-(4*t/SymbolPeriod)*(4*t/SymbolPeriod))/SymbolPeriod) - return h_rrc + return h_rrc def convolve(a, b): - out = [0] * (len(a) + len(b) - 1) - for i in range(len(a)): - for j in range(len(b)): - out[i+j] += a[i] * b[j] - return out + out = [0] * (len(a) + len(b) - 1) + for i in range(len(a)): + for j in range(len(b)): + out[i+j] += a[i] * b[j] + return out PATH = os.path.dirname(os.path.abspath(__file__)) @@ -63,61 +63,58 @@ outc.write(header) outh.write(header) def generate(): - l = ratio // 2 + l = ratio // 2 - sample = [0.0] * (16*l) - sample[l] = 1 - sample[2*l] = -1 + sample = [0.0] * (16*l) + sample[l] = 1 + sample[2*l] = -1 - sf = rrcosfilter(l*16) - shapedSamples = convolve(sample, sf) - - lowest = 0 - lowest_idx = 0 - highest = 0 - highest_idx = 0 - for i,j in enumerate(shapedSamples): - if j < lowest: - lowest = j - lowest_idx = i - if j > highest: - highest = j - highest_idx = i - middle = int((lowest_idx+highest_idx)/2) + sf = rrcosfilter(l*16) + shapedSamples = convolve(sample, sf) - out = shapedSamples[middle-int(ratio*SIZE_RATIO):middle+int(ratio*SIZE_RATIO)] - out = [2 * (i - min(out)) / (max(out) - min(out)) - 1 for i in out] - if max(out) > 1 or min(out) < -1: raise Exception("Clipped") - print(f"{len(out)=} {len(out)/sample_rate=} {(len(out)/sample_rate)/(1/DATA_RATE)=} {1/DATA_RATE=}") + lowest = 0 + lowest_idx = 0 + highest = 0 + highest_idx = 0 + for i,j in enumerate(shapedSamples): + if j < lowest: + lowest = j + lowest_idx = i + if j > highest: + highest = j + highest_idx = i + middle = int((lowest_idx+highest_idx)/2) - if PLOT: - # Plot the waveform - plt.plot(out, label="out") - plt.legend() - plt.grid(True) - plt.show() + out = shapedSamples[middle-int(ratio*SIZE_RATIO):middle+int(ratio*SIZE_RATIO)] + out = [2 * (i - min(out)) / (max(out) - min(out)) - 1 for i in out] + if max(out) > 1 or min(out) < -1: raise Exception("Clipped") + print(f"{len(out)=} {len(out)/sample_rate=} {(len(out)/sample_rate)/(1/DATA_RATE)=} {1/DATA_RATE=}") - if FFT: - # Compute the FFT of the waveform - N = len(out) - fft_out = np.fft.fft(out) - fft_freqs = np.fft.fftfreq(N, d=1/sample_rate) + if PLOT: + plt.plot(out, label="out") + plt.legend() + plt.grid(True) + plt.show() - # Plot the magnitude of the FFT - plt.figure(figsize=(10, 6)) - plt.plot(fft_freqs[:N//2], np.abs(fft_out)[:N//2]) # Plot only the positive frequencies - plt.xlim(0,DATA_RATE*3) - plt.title("FFT of the waveform") - plt.xlabel("Frequency (Hz)") - plt.ylabel("Magnitude") - plt.grid(True) - plt.show() + if FFT: + N = len(out) + fft_out = np.fft.fft(out) + fft_freqs = np.fft.fftfreq(N, d=1/sample_rate) - outc.write(u"float waveform_biphase[{size}] = {{{values}}};\n\n".format( - values = u", ".join(map(str, out)), - size = len(out))) + plt.figure(figsize=(10, 6)) + plt.plot(fft_freqs[:N//2], np.abs(fft_out)[:N//2]) + plt.xlim(0,DATA_RATE*3) + plt.title("FFT of the waveform") + plt.xlabel("Frequency (Hz)") + plt.ylabel("Magnitude") + plt.grid(True) + plt.show() - outh.write(u"extern float waveform_biphase[{size}];\n".format(size=len(out))) + outc.write(u"float waveform_biphase[{size}] = {{{values}}};\n\n".format( + values = u", ".join(map(str, out)), + size = len(out))) + + outh.write(u"extern float waveform_biphase[{size}];\n".format(size=len(out))) generate() diff --git a/src/ascii_cmd.c b/src/ascii_cmd.c index f962895..3cba3cf 100644 --- a/src/ascii_cmd.c +++ b/src/ascii_cmd.c @@ -236,6 +236,12 @@ static void handle_grpseq2(char *arg, RDSModulator* mod, char* output) { strcpy(output, "+\0"); } +static void handle_dttmout(char *arg, RDSModulator* mod, char* output) { + mod->enc->data[mod->enc->program].original_rt_text_timeout = atoi(arg); + mod->enc->data[mod->enc->program].rt_text_timeout = mod->enc->data[mod->enc->program].original_rt_text_timeout; + strcpy(output, "+\0"); +} + static void handle_level(char *arg, RDSModulator* mod, char* output) { mod->params.level = atoi(arg)/255.0f; strcpy(output, "+\0"); @@ -313,7 +319,7 @@ static void handle_init(char *arg, RDSModulator* mod, char* output) { static void handle_ver(char *arg, RDSModulator* mod, char* output) { (void)arg; (void)mod; - sprintf(output, "Firmware v. 1.2 - (C) 2025 radio95"); + sprintf(output, "Encoder v. %s - (C) 2025 radio95", VERSION); } static void handle_eonen(char *arg, char *pattern, RDSModulator* mod, char* output) { @@ -453,6 +459,7 @@ static const command_handler_t commands_eq8[] = { {"PROGRAM", handle_program, 7}, {"RDS2MOD", handle_rds2mod, 7}, {"GRPSEQ2", handle_grpseq2, 7}, + {"DTTMOUT", handle_dttmout, 7}, }; static const command_handler_t commands_exact[] = { {"INIT", handle_init, 4}, diff --git a/src/common.h b/src/common.h index 5faa727..58a27dc 100644 --- a/src/common.h +++ b/src/common.h @@ -10,4 +10,8 @@ #define M_PI 3.14159265358979323846 #endif -#define M_2PI (M_PI * 2.0) \ No newline at end of file +#define M_2PI (M_PI * 2.0) + +#ifndef VERSION +#define VERSION "-.-" +#endif \ No newline at end of file diff --git a/src/lib.c b/src/lib.c index 76da4dc..257b974 100644 --- a/src/lib.c +++ b/src/lib.c @@ -17,7 +17,7 @@ int _strnlen(const char *s, int maxlen) { return len; } -// For RDS2 RFT, and UECP +// For RDS2 RFT, file error checking, and UECP uint16_t crc16_ccitt(char* data, uint16_t len) { uint16_t i, crc=0xFFFF; for (i=0; i < len; i++ ) { diff --git a/src/modulator.c b/src/modulator.c index b9900a5..3694773 100644 --- a/src/modulator.c +++ b/src/modulator.c @@ -83,9 +83,7 @@ void init_rds_modulator(RDSModulator* rdsMod, RDSEncoder* enc) { } float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) { - uint16_t idx; float *cur_waveform; - float sample; if (rdsMod->data[stream].sample_count == SAMPLES_PER_BIT) { if (rdsMod->data[stream].bit_pos == BITS_PER_GROUP) { get_rds_bits(rdsMod->enc, rdsMod->data[stream].bit_buffer, stream); @@ -96,7 +94,7 @@ float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) { rdsMod->data[stream].prev_output = rdsMod->data[stream].cur_output; rdsMod->data[stream].cur_output = rdsMod->data[stream].prev_output ^ rdsMod->data[stream].cur_bit; - idx = rdsMod->data[stream].in_sample_index; + uint16_t idx = rdsMod->data[stream].in_sample_index; cur_waveform = waveform[rdsMod->data[stream].cur_output]; for (uint16_t i = 0; i < FILTER_SIZE; i++) { @@ -111,19 +109,18 @@ float get_rds_sample(RDSModulator* rdsMod, uint8_t stream) { } rdsMod->data[stream].sample_count++; - if(rdsMod->data[stream].symbol_shifting.symbol_shift != 0) { + float sample = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index]; + if(stream != 0 && rdsMod->data[stream].symbol_shifting.symbol_shift != 0) { rdsMod->data[stream].symbol_shifting.sample_buffer[rdsMod->data[stream].symbol_shifting.sample_buffer_idx++] = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index]; if (rdsMod->data[stream].symbol_shifting.sample_buffer_idx == rdsMod->data[stream].symbol_shifting.symbol_shift) rdsMod->data[stream].symbol_shifting.sample_buffer_idx = 0; sample = rdsMod->data[stream].symbol_shifting.sample_buffer[rdsMod->data[stream].symbol_shifting.sample_buffer_idx]; - } else { - sample = rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index]; } rdsMod->data[stream].sample_buffer[rdsMod->data[stream].out_sample_index++] = 0; - if (rdsMod->data[stream].out_sample_index == SAMPLE_BUFFER_SIZE) - rdsMod->data[stream].out_sample_index = 0; + if (rdsMod->data[stream].out_sample_index == SAMPLE_BUFFER_SIZE) rdsMod->data[stream].out_sample_index = 0; + uint8_t tooutput = 1; if (rdsMod->params.rdsgen == 0) { tooutput = 0; diff --git a/src/rds.c b/src/rds.c index ce81e66..24f3cfd 100644 --- a/src/rds.c +++ b/src/rds.c @@ -40,7 +40,7 @@ void saveToFile(RDSEncoder *emp, const char *option) { } else if (strcmp(option, "PTYN") == 0) { memcpy(tempEncoder.data[emp->program].ptyn, emp->data[emp->program].ptyn, PTYN_LENGTH); tempEncoder.data[emp->program].ptyn_enabled = emp->data[emp->program].ptyn_enabled; - } else if (strcmp(option, "AF") == 0 || strcmp(option, "AFCH") == 0) { + } else if (strcmp(option, "AF") == 0) { memcpy(&(tempEncoder.data[emp->program].af), &(emp->data[emp->program].af), sizeof(emp->data[emp->program].af)); } else if (strcmp(option, "ECC") == 0) { tempEncoder.data[emp->program].ecc = emp->data[emp->program].ecc; @@ -97,6 +97,7 @@ void saveToFile(RDSEncoder *emp, const char *option) { memcpy(&(rdsEncoderfile.rtpData[emp->program]), &(tempEncoder.rtpData[emp->program]), sizeof(RDSRTPlusData)); memcpy(&(rdsEncoderfile.encoder_data), &(tempEncoder.encoder_data), sizeof(RDSEncoderData)); rdsEncoderfile.program = tempEncoder.program; + rdsEncoderfile.crc = crc16_ccitt((char*)&rdsEncoderfile, sizeof(RDSEncoderFile) - sizeof(uint16_t)); file = fopen(encoderPath, "wb"); if (file == NULL) { @@ -121,10 +122,17 @@ void loadFromFile(RDSEncoder *enc) { fclose(file); if (rdsEncoderfile.file_starter != 225 || rdsEncoderfile.file_ender != 95 || rdsEncoderfile.file_middle != 160) { - fprintf(stderr, "Invalid file format\n"); + fprintf(stderr, "[RDSENCODER-FILE] Invalid file format\n"); return; } + uint16_t calculated_crc = crc16_ccitt((char*)&rdsEncoderfile, sizeof(RDSEncoderFile) - sizeof(uint16_t)); + + if (calculated_crc != rdsEncoderfile.crc) { + fprintf(stderr, "[RDSENCODER-FILE] CRC mismatch! Data may be corrupted\n"); + return; + } + for (int i = 0; i < PROGRAMS; i++) { memcpy(&(enc->data[i]), &(rdsEncoderfile.data[i]), sizeof(RDSData)); memcpy(&(enc->rtpData[i]), &(rdsEncoderfile.rtpData[i]), sizeof(RDSRTPlusData)); @@ -145,25 +153,48 @@ int rdssaved() { } static uint16_t get_next_af(RDSEncoder* enc) { - static uint8_t af_state; uint16_t out; if (enc->data[enc->program].af.num_afs) { - if (af_state == 0) { + if (enc->state[enc->program].af_state == 0) { out = (AF_CODE_NUM_AFS_BASE + enc->data[enc->program].af.num_afs) << 8; out |= enc->data[enc->program].af.afs[0]; - af_state += 1; + enc->state[enc->program].af_state += 1; } else { - out = enc->data[enc->program].af.afs[af_state] << 8; - if (enc->data[enc->program].af.afs[af_state + 1]) - out |= enc->data[enc->program].af.afs[af_state + 1]; + out = enc->data[enc->program].af.afs[enc->state[enc->program].af_state] << 8; + if (enc->data[enc->program].af.afs[enc->state[enc->program].af_state + 1]) + out |= enc->data[enc->program].af.afs[enc->state[enc->program].af_state + 1]; else out |= AF_CODE_FILLER; - af_state += 2; + enc->state[enc->program].af_state += 2; } - if (af_state >= enc->data[enc->program].af.num_entries) af_state = 0; + if (enc->state[enc->program].af_state >= enc->data[enc->program].af.num_entries) enc->state[enc->program].af_state = 0; } else { - out = AF_CODE_NO_AF << 8 | AF_CODE_FILLER; + out = AF_CODE_NUM_AFS_BASE << 8 | AF_CODE_FILLER; + } + + return out; +} + +static uint16_t get_next_af_eon(RDSEncoder* enc, uint8_t eon_index) { + uint16_t out; + + if (enc->data[enc->program].eon[eon_index].af.num_afs) { + if (enc->state[enc->program].eon_states[eon_index].af_state == 0) { + out = (AF_CODE_NUM_AFS_BASE + enc->data[enc->program].af.num_afs) << 8; + out |= enc->data[enc->program].eon[eon_index].af.afs[0]; + enc->state[enc->program].eon_states[eon_index].af_state += 1; + } else { + out = enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state] << 8; + if (enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state + 1]) + out |= enc->data[enc->program].eon[eon_index].af.afs[enc->state[enc->program].eon_states[eon_index].af_state + 1]; + else + out |= AF_CODE_FILLER; + enc->state[enc->program].eon_states[eon_index].af_state += 2; + } + if (enc->state[enc->program].eon_states[eon_index].af_state >= enc->data[enc->program].eon[eon_index].af.num_entries) enc->state[enc->program].eon_states[eon_index].af_state = 0; + } else { + out = AF_CODE_NUM_AFS_BASE << 8 | AF_CODE_FILLER; } return out; @@ -355,7 +386,10 @@ get_eon: blocks[2] |= eon.ps[enc->state[enc->program].eon_state*2 + 1]; blocks[1] |= enc->state[enc->program].eon_state; break; - case 4: // 13 + case 4: + blocks[2] = get_next_af_eon(enc, enc->state[enc->program].eon_index); + break; + case 5: // 13 if(eon.pty == 0 && eon.tp == 0) { break; } @@ -363,12 +397,11 @@ get_eon: if(eon.tp) blocks[2] |= eon.ta; blocks[1] |= 13; break; - // TODO: Add AF } blocks[3] = eon.pi; - if(enc->state[enc->program].eon_state == 4) { + if(enc->state[enc->program].eon_state == 5) { enc->state[enc->program].eon_index++; uint8_t i = 0; @@ -556,6 +589,14 @@ static void get_rds_group(RDSEncoder* enc, uint16_t *blocks, uint8_t stream) { } } + if(enc->data[enc->program].rt1_enabled && enc->data[enc->program].rt_text_timeout != 0) { + enc->data[enc->program].rt_text_timeout--; + if(enc->data[enc->program].rt_text_timeout == 0) { + enc->state[enc->program].rt_update = 1; + memccpy(enc->state[enc->program].rt_text, enc->data[enc->program].default_rt, 0, RT_LENGTH); + } + } + if(enc->data[enc->program].ct && stream == 0) { get_rds_ct_group(enc, blocks); goto group_coded; @@ -742,6 +783,8 @@ void init_rds_encoder(RDSEncoder* enc) { void set_rds_rt1(RDSEncoder* enc, char *rt1) { uint8_t i = 0, len = 0; + enc->data[enc->program].rt_text_timeout = enc->data[enc->program].original_rt_text_timeout; + enc->state[enc->program].rt_update = 1; memset(enc->data[enc->program].rt1, ' ', RT_LENGTH); @@ -767,7 +810,7 @@ void set_rds_rt1(RDSEncoder* enc, char *rt1) { void set_rds_rt2(RDSEncoder* enc, char *rt2) { uint8_t i = 0, len = 0; - + enc->state[enc->program].rt2_update = 1; memset(enc->data[enc->program].rt2, ' ', RT_LENGTH); diff --git a/src/rds.h b/src/rds.h index 56327e6..ee4807c 100644 --- a/src/rds.h +++ b/src/rds.h @@ -26,8 +26,7 @@ #define MAX_AFS 25 #define AF_CODE_FILLER 205 -#define AF_CODE_NO_AF 224 -#define AF_CODE_NUM_AFS_BASE AF_CODE_NO_AF +#define AF_CODE_NUM_AFS_BASE 224 #define AF_CODE_LFMF_FOLLOWS 250 #define PROGRAMS 2 @@ -56,9 +55,6 @@ typedef struct { char ps[PS_LENGTH]; char rt1[RT_LENGTH]; - uint8_t dsn; - uint8_t psn; - uint8_t ecc; uint8_t ta : 1; @@ -103,6 +99,9 @@ typedef struct { RDSEON eon[4]; } RDSData; +typedef struct { + uint8_t af_state : 6; +} RDSEONState; typedef struct { uint8_t ps_update : 1; uint8_t tps_update : 1; @@ -148,6 +147,9 @@ typedef struct { uint8_t eon_index : 3; uint8_t eon_state : 4; + RDSEONState eon_states[4]; + + uint8_t af_state : 6; uint16_t last_stream0_group[3]; } RDSState; @@ -197,6 +199,7 @@ typedef struct { RDSEncoderData encoder_data; uint8_t program : 3; uint8_t file_ender; // Always is 95 my freq + uint16_t crc; } RDSEncoderFile; #define GROUP_TYPE_0 ( 0 << 4) diff --git a/src/rds95.c b/src/rds95.c index 12a7e4e..dd51689 100644 --- a/src/rds95.c +++ b/src/rds95.c @@ -13,7 +13,7 @@ #define RDS_DEVICE "RDS" -#define NUM_MPX_FRAMES 256 +#define NUM_MPX_FRAMES 128 static uint8_t stop_rds; @@ -34,7 +34,7 @@ static void *control_pipe_worker(void* modulator) { } static void show_version() { - printf("rds95 (a RDS encoder by radio95) version 1.2\n"); + printf("rds95 (a RDS encoder by radio95) version %s\n", VERSION); } static void show_help(char *name) { @@ -43,7 +43,6 @@ static void show_help(char *name) { "Usage: %s [options]\n" "\n" " -C,--ctl FIFO control pipe\n" - " -h,--help Show this help text and exit\n" "\n", name ); @@ -95,8 +94,8 @@ int main(int argc, char **argv) { format.rate = RDS_SAMPLE_RATE; buffer.prebuf = 0; - buffer.tlength = 12228; - buffer.maxlength = 12228; + buffer.tlength = NUM_MPX_FRAMES*4; + buffer.maxlength = NUM_MPX_FRAMES*4; rds_device = pa_simple_new( NULL,