1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
|
/*
* 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);
//'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;
}
|