303 lines
7.0 KiB
C
303 lines
7.0 KiB
C
/*
|
|
* 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+1];
|
|
char rt[RT_LENGTH+1];
|
|
} 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);
|
|
|
|
//'struct tm' has no member named 'tm_gmtoff' on Windows+MinGW
|
|
#if defined(__APPLE__) || defined(__FreeBSD__)
|
|
offset = utc->tm_gmtoff / (30 * 60);
|
|
#else
|
|
offset = time(NULL) / (30 * 60);
|
|
#endif
|
|
|
|
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;
|
|
}
|