aboutsummaryrefslogtreecommitdiffstats
path: root/src/rds_mod.c
diff options
context:
space:
mode:
authorSteve Markgraf <steve@steve-m.de>2018-04-18 23:07:54 +0200
committerSteve Markgraf <steve@steve-m.de>2018-04-18 23:07:54 +0200
commit632482623d4821ac5748b91e9cadda557fac3e3f (patch)
tree9b4489a2d3285984f563f46b27f59796bf183ba2 /src/rds_mod.c
parent12ec69362e758b5f4f7f563b07b70f7126204122 (diff)
downloadosmo-fl2k-632482623d4821ac5748b91e9cadda557fac3e3f.tar.gz
osmo-fl2k-632482623d4821ac5748b91e9cadda557fac3e3f.tar.bz2
osmo-fl2k-632482623d4821ac5748b91e9cadda557fac3e3f.zip
fl2k_fm: add stereo and RDS support
Signed-off-by: Steve Markgraf <steve@steve-m.de>
Diffstat (limited to 'src/rds_mod.c')
-rw-r--r--src/rds_mod.c296
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;
+}