diff options
Diffstat (limited to 'src/rds_mod.c')
-rw-r--r-- | src/rds_mod.c | 296 |
1 files changed, 296 insertions, 0 deletions
diff --git a/src/rds_mod.c b/src/rds_mod.c new file mode 100644 index 0000000..865ca11 --- /dev/null +++ b/src/rds_mod.c @@ -0,0 +1,296 @@ +/* + * RDS Modulator from: + * PiFmRds - FM/RDS transmitter for the Raspberry Pi + * https://github.com/ChristopheJacquet/PiFmRds + * + * Copyright (C) 2014 by Christophe Jacquet, F8FTK + * + * adapted for use with fl2k_fm: + * Copyright (C) 2018 by Steve Markgraf <steve@steve-m.de> + * + * SPDX-License-Identifier: GPL-3.0+ + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see <http://www.gnu.org/licenses/>. +*/ + +#include <stdint.h> +#include <string.h> +#include <time.h> +#include <stdlib.h> + +#define RT_LENGTH 64 +#define PS_LENGTH 8 +#define GROUP_LENGTH 4 + +extern double waveform_biphase[576]; + +struct { + uint16_t pi; + int ta; + char ps[PS_LENGTH]; + char rt[RT_LENGTH]; +} rds_params = { 0 }; + +/* The RDS error-detection code generator polynomial is + x^10 + x^8 + x^7 + x^5 + x^4 + x^3 + x^0 +*/ +#define POLY 0x1B9 +#define POLY_DEG 10 +#define MSB_BIT (1 << 15) +#define BLOCK_SIZE 16 + +#define BITS_PER_GROUP (GROUP_LENGTH * (BLOCK_SIZE+POLY_DEG)) +#define SAMPLES_PER_BIT 192 +#define FILTER_SIZE (sizeof(waveform_biphase)/sizeof(double)) +#define SAMPLE_BUFFER_SIZE (SAMPLES_PER_BIT + FILTER_SIZE) + + +uint16_t offset_words[] = { 0x0FC, 0x198, 0x168, 0x1B4 }; +// We don't handle offset word C' here for the sake of simplicity + +/* Classical CRC computation */ +uint16_t crc(uint16_t block) +{ + uint16_t crc = 0; + int i, bit, msb; + + for (i = 0; i < BLOCK_SIZE; i++) { + bit = (block & MSB_BIT) != 0; + block <<= 1; + + msb = (crc >> (POLY_DEG-1)) & 1; + crc <<= 1; + + if ((msb ^ bit) != 0) + crc = crc ^ POLY; + } + + return crc; +} + +/* Possibly generates a CT (clock time) group if the minute has just changed + Returns 1 if the CT group was generated, 0 otherwise +*/ +int get_rds_ct_group(uint16_t *blocks) +{ + static int latest_minutes = -1; + int l, mjd, offset; + + // Check time + time_t now; + struct tm *utc; + + now = time(NULL); + utc = gmtime(&now); + + if(utc->tm_min != latest_minutes) { + // Generate CT group + latest_minutes = utc->tm_min; + + l = utc->tm_mon <= 1 ? 1 : 0; + mjd = 14956 + utc->tm_mday + + (int)((utc->tm_year - l) * 365.25) + + (int)((utc->tm_mon + 2 + l*12) * 30.6001); + + blocks[1] = 0x4400 | (mjd>>15); + blocks[2] = (mjd<<1) | (utc->tm_hour>>4); + blocks[3] = (utc->tm_hour & 0xF)<<12 | utc->tm_min<<6; + + utc = localtime(&now); + + offset = utc->tm_gmtoff / (30 * 60); + blocks[3] |= abs(offset); + if (offset < 0) + blocks[3] |= 0x20; + + return 1; + } else { + return 0; + } +} + +/* Creates an RDS group. This generates sequences of the form 0A, 0A, 0A, 0A, 2A, etc. + The pattern is of length 5, the variable 'state' keeps track of where we are in the + pattern. 'ps_state' and 'rt_state' keep track of where we are in the PS (0A) sequence + or RT (2A) sequence, respectively. +*/ +void get_rds_group(int *buffer) +{ + static int state = 0; + static int ps_state = 0; + static int rt_state = 0; + uint16_t blocks[GROUP_LENGTH] = { rds_params.pi, 0, 0, 0 }; + uint16_t block, check; + int i, j; + + // Generate block content + if (!get_rds_ct_group(blocks)) { // CT (clock time) has priority on other group types + if (state < 4) { + blocks[1] = 0x0400 | ps_state; + + if (rds_params.ta) + blocks[1] |= 0x0010; + + blocks[2] = 0xCDCD; // no AF + blocks[3] = rds_params.ps[ps_state*2] << 8 | rds_params.ps[ps_state*2+1]; + ps_state++; + + if (ps_state >= 4) + ps_state = 0; + } else { // state == 5 + blocks[1] = 0x2400 | rt_state; + blocks[2] = rds_params.rt[rt_state*4+0] << 8 | rds_params.rt[rt_state*4+1]; + blocks[3] = rds_params.rt[rt_state*4+2] << 8 | rds_params.rt[rt_state*4+3]; + rt_state++; + if (rt_state >= 16) + rt_state = 0; + } + + state++; + if (state >= 5) + state = 0; + } + + // Calculate the checkword for each block and emit the bits + for (i = 0; i < GROUP_LENGTH; i++) { + block = blocks[i]; + check = crc(block) ^ offset_words[i]; + for (j = 0; j < BLOCK_SIZE; j++) { + *buffer++ = ((block & (1 << (BLOCK_SIZE-1))) != 0); + block <<= 1; + } + for (j = 0; j < POLY_DEG; j++) { + *buffer++ = ((check & (1 << (POLY_DEG-1))) != 0); + check <<= 1; + } + } +} + +/* Get a number of RDS samples. This generates the envelope of the waveform using + pre-generated elementary waveform samples, and then it amplitude-modulates the + envelope with a 57 kHz carrier, which is very efficient as 57 kHz is 4 times the + sample frequency we are working at (228 kHz). + */ +void get_rds_samples(double *buffer, uint32_t count) +{ + static int bit_buffer[BITS_PER_GROUP]; + static int bit_pos = BITS_PER_GROUP; + static double sample_buffer[SAMPLE_BUFFER_SIZE] = {0}; + + static int prev_output = 0; + static int cur_output = 0; + static int cur_bit = 0; + static int sample_count = SAMPLES_PER_BIT; + static int inverting = 0; + static int phase = 0; + + static unsigned int in_sample_index = 0; + static unsigned int out_sample_index = SAMPLE_BUFFER_SIZE-1; + + unsigned int i, j, idx; + double val, sample; + double *src; + + for (i = 0; i < count; i++) { + if (sample_count >= SAMPLES_PER_BIT) { + if (bit_pos >= BITS_PER_GROUP) { + get_rds_group(bit_buffer); + bit_pos = 0; + } + + // do differential encoding + cur_bit = bit_buffer[bit_pos]; + prev_output = cur_output; + cur_output = prev_output ^ cur_bit; + + inverting = (cur_output == 1); + + src = waveform_biphase; + idx = in_sample_index; + + for (j = 0; j < FILTER_SIZE; j++) { + val = *src++; + if (inverting) + val = -val; + + sample_buffer[idx++] += val; + + if (idx >= SAMPLE_BUFFER_SIZE) + idx = 0; + } + + in_sample_index += SAMPLES_PER_BIT; + if (in_sample_index >= SAMPLE_BUFFER_SIZE) + in_sample_index -= SAMPLE_BUFFER_SIZE; + + bit_pos++; + sample_count = 0; + } + + sample = sample_buffer[out_sample_index]; + sample_buffer[out_sample_index] = 0; + out_sample_index++; + if (out_sample_index >= SAMPLE_BUFFER_SIZE) + out_sample_index = 0; + + // modulate at 57 kHz + // use phase for this + switch (phase) { + case 0: + case 2: sample = 0; break; + case 1: break; + case 3: sample = -sample; break; + } + phase++; + if (phase >= 4) + phase = 0; + + *buffer++ = sample; + sample_count++; + } +} + +void set_rds_pi(uint16_t pi_code) +{ + rds_params.pi = pi_code; +} + +void set_rds_rt(char *rt) +{ + int i; + + strncpy(rds_params.rt, rt, 64); + + for (i = 0; i < 64; i++) { + if (rds_params.rt[i] == 0) + rds_params.rt[i] = 32; + } +} + +void set_rds_ps(char *ps) +{ + int i; + + strncpy(rds_params.ps, ps, 8); + + for (i = 0; i < 8; i++) { + if (rds_params.ps[i] == 0) + rds_params.ps[i] = 32; + } +} + +void set_rds_ta(int ta) +{ + rds_params.ta = ta; +} |