TinyModem
Header-only software audio modem library
Loading...
Searching...
No Matches
AX25Decoder.h
Go to the documentation of this file.
1#pragma once
2
3#include <math.h>
4#include <string.h>
5
6#include "AX25.h"
7#include "AX25FEC.h"
8#include "AudioTools.h"
9#include "ModemBase.h"
10
11namespace tinymodem {
12
30class AX25Decoder : public ModemDecoder {
31 public:
32 AX25Decoder() = default;
33 AX25Decoder(Print& out) { setOutput(out); }
34
37 AX25Config result;
38 result.setMode("aprs");
39 return result;
40 }
41
43 void setAudioInfo(audio_tools::AudioInfo info) override {
44 if (info.sample_rate == cfg.sample_rate && info.channels == cfg.channels &&
45 info.bits_per_sample == cfg.bits_per_sample)
46 return;
47 cfg.sample_rate = info.sample_rate;
48 cfg.channels = info.channels;
49 cfg.bits_per_sample = info.bits_per_sample;
50 if (is_active) begin();
51 }
52
53 audio_tools::AudioInfo audioInfo() override { return cfg; }
54
55 using audio_tools::AudioDecoder::begin;
56
59 cfg = config;
60 return begin();
61 }
62
63 bool begin() override {
64 is_active = false;
65 if (!cfg.isValid()) return false;
66 if (!input.begin(cfg.bits_per_sample, cfg.channels, cfg.channel))
67 return false;
68 window = lroundf(cfg.sample_rate / cfg.baud_rate);
69 products.resize(window * 4);
70 mark.begin(cfg.mark_freq, cfg.sample_rate);
71 space.begin(cfg.space_freq, cfg.sample_rate);
72 pll_step = cfg.baud_rate / cfg.sample_rate;
73 resetState();
74 frames = 0;
75 errors = 0;
76 is_active = true;
77 return true;
78 }
79
81 void end() override {
82 if (!is_active) return;
83 flush();
84 status(
85 "### AX25 frames=%u fcs_errors=%u fx25=%u il2p=%u corrected=%u ###\n",
86 (unsigned)frames, (unsigned)errors, (unsigned)fx25_frames,
87 (unsigned)il2p_frames, (unsigned)corrected_bytes);
88 is_active = false;
89 }
90
92 void flush() override {
93 if (!is_active) return;
95 }
96
98 size_t write(const uint8_t* data, size_t len) override {
99 if (!is_active) return 0;
100 input.write(data, len, [this](float sample) { addSample(sample); });
101 return len;
102 }
103
105 bool isCarrier() override {
106 return (in_frame && frame_len > 0) || fx_tag != 0 || il_state != 0;
107 }
108
110 bool isFinished() override { return false; }
111
113 size_t frameCount() { return frames; }
114
116 size_t errorCount() { return errors; }
117
119 size_t fx25Count() { return fx25_frames; }
120
122 size_t il2pCount() { return il2p_frames; }
123
125 size_t correctedCount() { return corrected_bytes; }
126
128 AX25Config& config() { return cfg; }
129
130 operator bool() override { return is_active; }
131
132 protected:
134 struct Phasor {
135 float re = 1, im = 0, c = 1, s = 0;
136 void begin(float freq, float sample_rate) {
137 double w = 2.0 * M_PI * freq / sample_rate;
138 c = cos(w);
139 s = sin(w);
140 re = 1;
141 im = 0;
142 }
143 void next() {
144 float r = re * c - im * s;
145 im = re * s + im * c;
146 re = r;
147 }
148 void normalize() {
149 float mag = sqrtf(re * re + im * im);
150 re /= mag;
151 im /= mag;
152 }
153 };
154
156 bool is_active = false;
158 uint32_t noise_seed = 1;
159 size_t frames = 0;
160 size_t errors = 0;
161 size_t fx25_frames = 0;
162 size_t il2p_frames = 0;
163 size_t corrected_bytes = 0;
164
165 // correlators: products of the last window samples with the phasors
166 int window = 40;
167 audio_tools::Vector<float> products; // mark re/im, space re/im
168 int pos = 0;
169 float sums[4] = {0, 0, 0, 0};
171 uint32_t sample_count = 0;
172
173 // bit clock
174 float pll_step = 0.025f;
175 float pll = 0;
176 bool last_tone = false;
177 bool bit_tone = false;
178
179 // HDLC
180 uint8_t history = 0;
181 int ones = 0;
182 bool in_frame = false;
184 int frame_len = 0;
185 uint8_t current = 0;
188 // frame which was received during an FX.25 block
190 int pending_len = 0;
191
192 // FX.25
193 int fx_tag = 0; // 0 = searching the correlation tag
194 uint64_t fx_accum = 0;
196 int fx_len = 0, fx_bits = 0, fx_total = 0;
197
198 // IL2P
199 static constexpr int IL2P_MAX_ENCODED = AX25Frame::MAX_FRAME + 2 * 16;
200 int il_state = 0; // 0 = searching, 1 = header, 2 = payload
201 uint32_t il_accum = 0;
202 bool il_inverted = false;
203 int il_len = 0, il_bits = 0, il_need = 0;
208
209 void resetState() {
210 for (int j = 0; j < window * 4; j++) products[j] = 0;
211 for (int j = 0; j < 4; j++) sums[j] = 0;
212 pos = 0;
213 pll = 0;
214 in_frame = false;
215 history = 0;
216 ones = 0;
217 pending_len = 0;
218 fx_tag = 0;
219 fx_accum = 0;
220 il_state = 0;
221 il_accum = 0;
222 input.reset();
223 }
224
226 float noise() {
227 noise_seed = noise_seed * 1103515245u + 12345u;
228 return ((noise_seed >> 8) & 0xFFFF) / 65535.0f - 0.5f;
229 }
230
231 void addSample(float x) {
232 if (cfg.rx_noise != 0.0f) x += noise() * cfg.rx_noise * 2.0f;
233 // sliding correlation over one bit
234 float values[4] = {x * mark.re, x * mark.im, x * space.re, x * space.im};
235 float* old = products.data() + pos * 4;
236 for (int j = 0; j < 4; j++) {
237 sums[j] += values[j] - old[j];
238 old[j] = values[j];
239 }
240 if (++pos == window) pos = 0;
241 mark.next();
242 space.next();
243 if (++sample_count % 4096 == 0) recalculate();
244
245 float mark_energy = sums[0] * sums[0] + sums[1] * sums[1];
246 float space_energy = sums[2] * sums[2] + sums[3] * sums[3];
247 bool tone = mark_energy > space_energy;
248
249 // digital PLL: a tone change should be in the middle between two bit
250 // samples
251 pll += pll_step;
252 if (tone != last_tone) {
253 pll -= PLL_GAIN * (pll - 0.5f);
254 last_tone = tone;
255 }
256 if (pll >= 1.0f) {
257 pll -= 1.0f;
258 // NRZI: no tone change = 1
259 int bit = tone == bit_tone ? 1 : 0;
260 bit_tone = tone;
261 hdlc(bit);
262 fx25(bit);
263 il2p(tone); // IL2P does not use NRZI
264 }
265 }
266
268 void recalculate() {
269 for (int j = 0; j < 4; j++) sums[j] = 0;
270 for (int k = 0; k < window; k++)
271 for (int j = 0; j < 4; j++) sums[j] += products[k * 4 + j];
272 mark.normalize();
273 space.normalize();
274 }
275
276 void hdlc(int bit) {
277 history = (history << 1) | bit;
278 if (history == 0x7E) {
279 // flag: the last 7 bits (0111111) belong to the flag
280 if (in_frame && current_bits == 7) processFrame();
281 in_frame = true;
282 frame_len = 0;
283 current = 0;
284 current_bits = 0;
285 ones = 0;
286 return;
287 }
288 if (!in_frame) return;
289 if (bit) {
290 if (++ones > 6) { // abort
291 in_frame = false;
292 return;
293 }
294 } else {
295 if (ones == 5) { // stuffed bit
296 ones = 0;
297 return;
298 }
299 ones = 0;
300 }
301 // least significant bit first
302 current |= bit << current_bits;
303 if (++current_bits == 8) {
304 if (frame_len < (int)sizeof(frame)) {
306 } else {
307 in_frame = false;
308 }
309 current = 0;
310 current_bits = 0;
311 }
312 }
313
315 if (frame_len < AX25Frame::MIN_FRAME + 2) return;
316 int len = frame_len - 2;
317 uint16_t fcs = frame[len] | (frame[len + 1] << 8);
318 if (AX25Frame::fcs(frame, len) != fcs) {
319 errors++;
320 return;
321 }
322 if (fx_tag != 0) {
323 // probably the content of the FX.25 block: we wait for its result
325 memcpy(pending, frame, len);
326 pending_len = len;
327 return;
328 }
329 deliver(frame, len);
330 }
331
334 pending_len = 0;
335 }
336
338 void fx25(int bit) {
339 if (fx_tag == 0) {
340 fx_accum = (fx_accum >> 1) | ((uint64_t)bit << 63);
341 int tag = FX25::findTag(fx_accum);
342 if (tag == 0) return;
343 fx_tag = tag;
344 fx_total = FX25::tag(tag).data + FX25::tag(tag).check;
345 fx_len = fx_bits = 0;
346 memset(fx_block, 0, sizeof(fx_block));
347 return;
348 }
349 fx_block[fx_len] |= bit << fx_bits;
350 if (++fx_bits < 8) return;
351 fx_bits = 0;
352 if (++fx_len < fx_total) return;
353 int tag = fx_tag;
354 fx_tag = 0;
355 fx_accum = 0;
356 int corrected = FX25::correct(tag, fx_block);
357 uint8_t result[AX25Frame::MAX_FRAME + 2];
358 int len = corrected < 0 ? 0
359 : FX25::extract(fx_block, FX25::tag(tag).data,
360 result, sizeof(result));
361 if (len <= 0) {
362 deliverPending(); // the standard frame (if any)
363 return;
364 }
365 pending_len = 0; // duplicate
366 fx25_frames++;
367 corrected_bytes += corrected;
368 deliver(result, len);
369 }
370
372 void il2p(int raw) {
373 il_accum = ((il_accum << 1) | (raw & 1)) & 0xFFFFFF;
374 if (il_state == 0) {
375 if (popcount(il_accum ^ IL2P::SYNC_WORD) <= 1) {
376 il_inverted = false;
377 } else if (popcount((~il_accum & 0xFFFFFF) ^ IL2P::SYNC_WORD) <= 1) {
378 il_inverted = true;
379 } else {
380 return;
381 }
382 il_state = 1;
383 il_len = il_bits = 0;
384 return;
385 }
386 if (++il_bits < 8) return;
387 il_bits = 0;
388 uint8_t value = il_inverted ? ~il_accum & 0xFF : il_accum & 0xFF;
389 if (il_state == 1) {
390 il_raw_header[il_len++] = value;
391 if (il_len < (int)sizeof(il_raw_header)) return;
392 il_corrected = 0;
395 il_state = 0;
396 return;
397 }
398 il_len = 0;
399 il_state = 2;
400 if (il_need > 0) return;
401 } else {
402 il_payload[il_len++] = value;
403 if (il_len < il_need) return;
404 }
405 il_state = 0;
406 uint8_t result[AX25Frame::MAX_FRAME];
407 int len = IL2P::decodeFrame(il_header, il_payload, result, sizeof(result),
408 &il_corrected);
409 if (len <= 0) return;
410 il2p_frames++;
412 deliver(result, len);
413 }
414
415 static int popcount(uint32_t v) {
416 int n = 0;
417 for (; v; v &= v - 1) n++;
418 return n;
419 }
420
422 void deliver(const uint8_t* frame, int len) {
423 frames++;
424 if (p_print == nullptr) return;
425 if (cfg.format == AX25Format::KISS) {
426 AX25Frame::writeKiss(*p_print, frame, len);
427 } else {
428 int n = AX25Frame::toText(frame, len, text, sizeof(text) - 1);
429 if (n > 0) {
430 text[n++] = '\n';
431 p_print->write((const uint8_t*)text, n);
432 }
433 }
434 }
435
436 static constexpr float PLL_GAIN = 0.15f;
437};
438
439} // namespace tinymodem
uint8_t il_payload[IL2P_MAX_ENCODED]
Definition AX25Decoder.h:206
Phasor mark
Definition AX25Decoder.h:170
void deliver(const uint8_t *frame, int len)
Writes a received frame (without FCS) to the output.
Definition AX25Decoder.h:422
size_t frameCount()
Number of received frames with a correct frame check sequence.
Definition AX25Decoder.h:113
size_t corrected_bytes
Definition AX25Decoder.h:163
int fx_tag
Definition AX25Decoder.h:193
uint8_t frame[AX25Frame::MAX_FRAME+2]
Definition AX25Decoder.h:183
float pll
Definition AX25Decoder.h:175
uint8_t fx_block[FX25::BLOCK_SIZE]
Definition AX25Decoder.h:195
uint32_t sample_count
Definition AX25Decoder.h:171
char text[AX25Frame::MAX_FRAME *2]
Definition AX25Decoder.h:187
void setAudioInfo(audio_tools::AudioInfo info) override
Defines the format of the input audio data.
Definition AX25Decoder.h:43
void fx25(int bit)
FX.25: correlation tag and Reed-Solomon block.
Definition AX25Decoder.h:338
int current_bits
Definition AX25Decoder.h:186
uint32_t il_accum
Definition AX25Decoder.h:201
int il_bits
Definition AX25Decoder.h:203
size_t write(const uint8_t *data, size_t len) override
Provide PCM data to the decoder.
Definition AX25Decoder.h:98
size_t il2pCount()
Number of received IL2P frames.
Definition AX25Decoder.h:122
size_t errorCount()
Number of frames with a wrong frame check sequence.
Definition AX25Decoder.h:116
void deliverPending()
Definition AX25Decoder.h:332
void flush() override
Provides a frame which is held back during an FX.25 block.
Definition AX25Decoder.h:92
void hdlc(int bit)
Definition AX25Decoder.h:276
int pending_len
Definition AX25Decoder.h:190
size_t errors
Definition AX25Decoder.h:160
bool begin(AX25Config config)
Starts the processing with the indicated configuration.
Definition AX25Decoder.h:58
float noise()
uniform random value in the range -0.5 to 0.5
Definition AX25Decoder.h:226
size_t il2p_frames
Definition AX25Decoder.h:162
size_t frames
Definition AX25Decoder.h:159
int il_state
Definition AX25Decoder.h:200
void il2p(int raw)
IL2P: sync word, header and payload.
Definition AX25Decoder.h:372
size_t fx25_frames
Definition AX25Decoder.h:161
Phasor space
Definition AX25Decoder.h:170
AX25Decoder(Print &out)
Definition AX25Decoder.h:33
static constexpr float PLL_GAIN
Definition AX25Decoder.h:436
int il_need
Definition AX25Decoder.h:203
size_t correctedCount()
Number of bytes which were corrected by FX.25 and IL2P.
Definition AX25Decoder.h:125
audio_tools::AudioInfo audioInfo() override
Definition AX25Decoder.h:53
uint32_t noise_seed
Definition AX25Decoder.h:158
int fx_total
Definition AX25Decoder.h:196
int pos
Definition AX25Decoder.h:168
int ones
Definition AX25Decoder.h:181
void resetState()
Definition AX25Decoder.h:209
AX25Config & config()
The configuration which was provided in begin().
Definition AX25Decoder.h:128
void processFrame()
Definition AX25Decoder.h:314
AX25Config cfg
Definition AX25Decoder.h:155
bool is_active
Definition AX25Decoder.h:156
static constexpr int IL2P_MAX_ENCODED
Definition AX25Decoder.h:199
int fx_bits
Definition AX25Decoder.h:196
PcmInput input
Definition AX25Decoder.h:157
bool last_tone
Definition AX25Decoder.h:176
bool isFinished() override
The decoder never stops.
Definition AX25Decoder.h:110
void recalculate()
Avoids the accumulation of rounding errors.
Definition AX25Decoder.h:268
int il_len
Definition AX25Decoder.h:203
bool il_inverted
Definition AX25Decoder.h:202
uint8_t current
Definition AX25Decoder.h:185
uint8_t il_header[IL2P::HEADER_SIZE]
Definition AX25Decoder.h:205
int frame_len
Definition AX25Decoder.h:184
uint64_t fx_accum
Definition AX25Decoder.h:194
void addSample(float x)
Definition AX25Decoder.h:231
float sums[4]
Definition AX25Decoder.h:169
uint8_t history
Definition AX25Decoder.h:180
uint8_t pending[AX25Frame::MAX_FRAME]
Definition AX25Decoder.h:189
int fx_len
Definition AX25Decoder.h:196
bool in_frame
Definition AX25Decoder.h:182
int il_corrected
Definition AX25Decoder.h:207
bool bit_tone
Definition AX25Decoder.h:177
audio_tools::Vector< float > products
Definition AX25Decoder.h:167
bool isCarrier() override
true while a frame is received
Definition AX25Decoder.h:105
size_t fx25Count()
Number of received FX.25 frames.
Definition AX25Decoder.h:119
float pll_step
Definition AX25Decoder.h:174
int window
Definition AX25Decoder.h:166
AX25Config defaultConfig()
Provides a default configuration: APRS (1200 baud AFSK).
Definition AX25Decoder.h:36
static int popcount(uint32_t v)
Definition AX25Decoder.h:415
void end() override
Reports the statistics.
Definition AX25Decoder.h:81
uint8_t il_raw_header[IL2P::HEADER_SIZE+IL2P::HEADER_PARITY]
Definition AX25Decoder.h:204
bool begin() override
Definition AX25Decoder.h:63
static size_t writeKiss(Print &out, const uint8_t *frame, size_t len)
Writes the frame as KISS data frame for port 0.
Definition AX25.h:242
static int toText(const uint8_t *frame, size_t len, char *text, size_t text_size)
Definition AX25.h:198
static uint16_t fcs(const uint8_t *data, size_t len)
AX.25 frame check sequence: CRC-16-CCITT with reflected bits.
Definition AX25.h:151
static constexpr int MAX_FRAME
Definition AX25.h:143
static constexpr int MIN_FRAME
Definition AX25.h:144
static int correct(int t, uint8_t *received)
Definition AX25FEC.h:101
static int extract(const uint8_t *data, int len, uint8_t *frame, int frame_size)
Definition AX25FEC.h:117
static const Tag & tag(int index)
The tag 1 - 11.
Definition AX25FEC.h:39
static constexpr int BLOCK_SIZE
Definition AX25FEC.h:31
static int findTag(uint64_t bits)
Finds the tag which matches the received 64 bits: returns 0 if none.
Definition AX25FEC.h:58
static int decodeHeader(const uint8_t *received, uint8_t *hdr, int *corrected=nullptr)
Definition AX25FEC.h:252
static int decodeFrame(const uint8_t *hdr, const uint8_t *encoded, uint8_t *frame, int frame_size, int *corrected=nullptr)
Definition AX25FEC.h:267
static constexpr int HEADER_PARITY
Definition AX25FEC.h:219
static constexpr uint32_t SYNC_WORD
Definition AX25FEC.h:217
static constexpr int HEADER_SIZE
Definition AX25FEC.h:218
Common API of the demodulators (FSKDecoder, MFSKDecoder): PCM audio data is written to the decoder an...
Definition ModemBase.h:344
void status(const char *fmt,...)
Definition ModemBase.h:367
Assembles the PCM frames from the written bytes and provides the samples of the selected channel as f...
Definition ModemBase.h:170
TinyModem: software audio modems and packets with error correction.
Definition AX25.h:11
@ KISS
Definition AX25.h:20
Configuration of the AX.25 packet radio modem (AX25Encoder, AX25Decoder): Bell 202 AFSK with 1200 bau...
Definition AX25.h:44
bool setMode(const char *mode)
Definition AX25.h:78
Rotating phasor of a tone: avoids sin() and cos() per sample.
Definition AX25Decoder.h:134
float s
Definition AX25Decoder.h:135
float im
Definition AX25Decoder.h:135
float c
Definition AX25Decoder.h:135
void next()
Definition AX25Decoder.h:143
float re
Definition AX25Decoder.h:135
void begin(float freq, float sample_rate)
Definition AX25Decoder.h:136
void normalize()
Definition AX25Decoder.h:148
int check
check bytes
Definition AX25FEC.h:28
int data
transmitted data bytes
Definition AX25FEC.h:26