m20-custom-firmware/m20/Core/Src/afsk.c

173 wiersze
9.2 KiB
C
Czysty Zwykły widok Historia

2025-10-14 13:16:23 +00:00
/*
* afsk.c
* By SQ2IPS
2025-10-17 13:11:50 +00:00
* Base implemenatation of AFSK modulation with HDLC Bell 202 tones, sent into ADF module in direct FSK mode. Tone generated by high frequency PWM with changed duty cycle according to a sine table. Phase increase value changed according to tone frequency, making a continous phase tone transition.
2025-10-17 13:41:15 +00:00
* Modulation documentation https://files.tapr.org/meetings/DCC_2014/DCC2014-Amateur-Bell-202-Modem-W6KWF-and-Bridget-Benson.pdf, https://notblackmagic.com/bitsnpieces/afsk/
2025-10-17 21:55:08 +00:00
* Implementation based on https://github.com/trackuino/trackuino/blob/1.52/trackuino/afsk.cpp, https://github.com/mikaelnousiainen/RS41ng?tab=readme-ov-file#si4032-bell-fsk-modulation-hack-for-aprs
2025-10-14 13:16:23 +00:00
*/
2025-10-12 18:24:56 +00:00
#include "afsk.h"
#include "adf.h"
#include "utils.h"
2025-10-12 18:24:56 +00:00
#include "main.h"
#include "config.h"
2025-10-17 21:55:08 +00:00
/*
* The sine lookup table is the carrier signal, its values are set as the PWM dutycycle,
* it's indexed by a phase value, continously increased in the modulation interrupt by
* phase_inc_marc for marc tone or phase_inc_space for space tone
* therefore creating one of each tones with a phase continous transition when changed.
* Also it means that the PWM autoreload value must be the max balue from the table
*/
static const uint8_t sine_table[] = {
2025-10-18 11:56:52 +00:00
127, 129, 130, 132, 133, 135, 136, 138, 139, 141, 143, 144, 146, 147, 149, 150, 152, 153, 155, 156, 158,
159, 161, 163, 164, 166, 167, 168, 170, 171, 173, 174, 176, 177, 179, 180, 182, 183, 184, 186, 187, 188,
190, 191, 193, 194, 195, 197, 198, 199, 200, 202, 203, 204, 205, 207, 208, 209, 210, 211, 213, 214, 215,
216, 217, 218, 219, 220, 221, 223, 224, 225, 226, 227, 228, 228, 229, 230, 231, 232, 233, 234, 235, 236,
236, 237, 238, 239, 239, 240, 241, 242, 242, 243, 244, 244, 245, 245, 246, 247, 247, 248, 248, 249, 249,
249, 250, 250, 251, 251, 251, 252, 252, 252, 253, 253, 253, 253, 254, 254, 254, 254, 254, 254, 254, 254,
254, 254, 255, 254, 254, 254, 254, 254, 254, 254, 254, 254, 254, 253, 253, 253, 253, 252, 252, 252, 251,
251, 251, 250, 250, 249, 249, 249, 248, 248, 247, 247, 246, 245, 245, 244, 244, 243, 242, 242, 241, 240,
239, 239, 238, 237, 236, 236, 235, 234, 233, 232, 231, 230, 229, 228, 228, 227, 226, 225, 224, 223, 221,
220, 219, 218, 217, 216, 215, 214, 213, 211, 210, 209, 208, 207, 205, 204, 203, 202, 200, 199, 198, 197,
195, 194, 193, 191, 190, 188, 187, 186, 184, 183, 182, 180, 179, 177, 176, 174, 173, 171, 170, 168, 167,
166, 164, 163, 161, 159, 158, 156, 155, 153, 152, 150, 149, 147, 146, 144, 143, 141, 139, 138, 136, 135,
133, 132, 130, 129, 127, 125, 124, 122, 121, 119, 118, 116, 115, 113, 111, 110, 108, 107, 105, 104, 102,
101, 99, 98, 96, 95, 93, 91, 90, 88, 87, 86, 84, 83, 81, 80, 78, 77, 75, 74, 72, 71,
70, 68, 67, 66, 64, 63, 61, 60, 59, 57, 56, 55, 54, 52, 51, 50, 49, 47, 46, 45, 44,
43, 41, 40, 39, 38, 37, 36, 35, 34, 33, 31, 30, 29, 28, 27, 26, 26, 25, 24, 23, 22,
21, 20, 19, 18, 18, 17, 16, 15, 15, 14, 13, 12, 12, 11, 10, 10, 9, 9, 8, 7, 7,
6, 6, 5, 5, 5, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1,
2, 2, 2, 3, 3, 3, 4, 4, 5, 5, 5, 6, 6, 7, 7, 8, 9, 9, 10, 10, 11,
12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23, 24, 25, 26, 26, 27, 28,
29, 30, 31, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 44, 45, 46, 47, 49, 50, 51, 52,
54, 55, 56, 57, 59, 60, 61, 63, 64, 66, 67, 68, 70, 71, 72, 74, 75, 77, 78, 80, 81,
83, 84, 86, 87, 88, 90, 91, 93, 95, 96, 98, 99, 101, 102, 104, 105, 107, 108, 110, 111, 113,
115, 116, 118, 119, 121, 122, 124, 125};
2025-10-17 21:55:08 +00:00
static const uint16_t SINE_TABLE_SIZE = sizeof(sine_table);
2025-10-18 11:56:52 +00:00
static const uint16_t AFSK_SAMPLE_RATE = MODEM_CLOCK_RATE / (AFSK_PWM_TIM_ARR + 1); // Frequency of PWM and rate of the sampling interrupt
static const uint16_t PHASE_INC_MARC = ((SINE_TABLE_SIZE * BELL202_MARK) << 7) / AFSK_SAMPLE_RATE; // Phase increase for generating marc Bell 202 tone. Fixed point 9.7
static const uint16_t PHASE_INC_SPACE = ((SINE_TABLE_SIZE * BELL202_SPACE) << 7) / AFSK_SAMPLE_RATE; // Phase increase for generating space Bell 202 tone. Fixed point 9.7
static const uint16_t SAMPLES_PER_BAUD = (AFSK_SAMPLE_RATE << 8) / AFSK_BAUDRATE; // Number of samples after with the next next bit will be sent. Fixed point 8.8
2025-10-17 21:55:08 +00:00
volatile static uint16_t phase_inc = PHASE_INC_MARC; // current phase increase value, fixed point 9.7
2025-10-18 11:56:52 +00:00
volatile static uint16_t phase = 0; // Current phase value, fixed point 9.7
2025-10-17 21:55:08 +00:00
volatile static uint16_t sample_in_baud = 0;
2025-10-17 21:55:08 +00:00
volatile static uint16_t bit_pos = 0;
volatile static uint8_t stuffing_cnt = 0;
volatile static bool stuff = false;
2025-10-18 12:27:02 +00:00
bool AFSK_Active = false; // Activity flag
static uint8_t *buff;
static uint16_t buff_len;
2025-10-12 18:24:56 +00:00
2025-10-18 11:56:52 +00:00
void AFSK_stop_TX()
{ // Disable TX
TIM21->DIER &= ~(TIM_DIER_UIE); // Disable the interrupt
TIM21->CR1 &= (uint16_t)(~((uint16_t)TIM_CR1_CEN)); // Disable the PWM counter
adf_RF_off(); // turn TX off
AFSK_Active = false; // turn off activty flag
2025-10-12 18:24:56 +00:00
}
// 0, N1-1 | N1, N1+N2-1 | N1+N2, N1+N2+buff_len-1 | N1+N2+buff_len, N1+N2+buff_len+N3-1
2025-10-18 11:56:52 +00:00
static bool get_next_bit()
{
if (bit_pos < (N1_SYNC_COUNT) * 8)
{
return 0; // N1 sync octet is 0x00
}
else if (bit_pos >= (N1_SYNC_COUNT) * 8 && bit_pos < (N1_SYNC_COUNT + N2_SYNC_COUNT) * 8)
{ // N2 octet sync section
return (AFSK_SYNC_FLAG >> (7 - (bit_pos % 8))) & 1;
}
else if (bit_pos >= N1_SYNC_COUNT + N2_SYNC_COUNT * 8 && bit_pos < (N1_SYNC_COUNT + N2_SYNC_COUNT + buff_len) * 8)
{ // DATA section
bool bit = (buff[(bit_pos / 8) - (N1_SYNC_COUNT + N2_SYNC_COUNT)] >> (bit_pos % 8)) & 1;
if(bit){
stuffing_cnt++;
if(stuffing_cnt>=5){
stuffing_cnt = 0;
stuff = true;
}
}else{
stuffing_cnt = 0;
}
return bit;
2025-10-18 11:56:52 +00:00
}
else if (bit_pos >= (N1_SYNC_COUNT + N2_SYNC_COUNT + buff_len) * 8 && bit_pos < (N1_SYNC_COUNT + N2_SYNC_COUNT + buff_len + N3_SYNC_COUNT) * 8)
{ // N3 octet sync section
return (AFSK_SYNC_FLAG >> (7 - (bit_pos % 8))) & 1;
2025-10-14 13:16:23 +00:00
}
}
2025-10-18 11:56:52 +00:00
void AFSK_timer_handler()
{ // sampling and PWM timer (TIM21) changing duty cycle acoording to phase (and increasing it according to current tone)
// TIM21->CNT = 0; // Reset timer counter, not needed, PWM reloads it
2025-10-18 11:56:52 +00:00
TIM21->CCR1 = sine_table[(phase >> 7) + ((phase & (1 << 6)) >> 6)]; // Set the duty cycle to index from phase, rounding fixed point 9.7 to int
2025-10-17 13:11:50 +00:00
phase += phase_inc; // increase phase for generating wanted frequency
2025-10-18 11:56:52 +00:00
if (phase >= (SINE_TABLE_SIZE << 7))
phase -= (SINE_TABLE_SIZE << 7); // normalise phase value to be in bounds of array
2025-10-18 11:56:52 +00:00
if (sample_in_baud < (1 << 8))
{ // With the baudrate frequency process next bit of data
2025-10-18 11:56:52 +00:00
if (bit_pos >= (N1_SYNC_COUNT + N2_SYNC_COUNT + buff_len + N3_SYNC_COUNT) * 8)
{ // check for end of transmission
2025-10-17 21:55:08 +00:00
AFSK_stop_TX();
2025-10-18 11:56:52 +00:00
}
else
{
/*
2025-10-17 21:55:08 +00:00
* "One implication of using HDLC is that frames are not encoded using the 1200Hz mark and 2200Hz
* space symbols of traditional Bell 202, but instead use an inverted non-return to zero (NRZI) encoding.
* NRZI calls for zeros in the original bit stream to be encoded as a continuous-phase frequency
* transition between consecutive symbols, while ones are encoded as the lack of a frequency change between two symbols."
*/
if(stuff){
phase_inc ^= (PHASE_INC_MARC ^ PHASE_INC_SPACE);
bit_pos--;
stuff = false;
}else if (get_next_bit() == 0)
2025-10-18 11:56:52 +00:00
phase_inc ^= (PHASE_INC_MARC ^ PHASE_INC_SPACE); // When bit is 0, change the current frequency, when 1 dont change it
2025-10-17 22:30:27 +00:00
}
2025-10-17 21:55:08 +00:00
}
2025-10-14 13:16:23 +00:00
2025-10-18 11:56:52 +00:00
sample_in_baud += (1 << 8);
if (sample_in_baud >= SAMPLES_PER_BAUD)
{
sample_in_baud -= SAMPLES_PER_BAUD;
2025-10-17 13:11:50 +00:00
bit_pos++; // increase bit counter
}
2025-10-12 18:24:56 +00:00
}
2025-10-18 11:56:52 +00:00
void AFSK_start_TX(uint8_t *buffer, uint16_t buffer_len)
{
buff = buffer; // Set buffer pointer
buff_len = buffer_len; // Set buffer length
2025-10-18 11:56:52 +00:00
adf_RF_on(QRG_AFSK, PA_FSK4); // turn on radio TX
AFSK_Active = true; // turn on activity flag
//phase_inc = PHASE_INC_MARC; // first phase increase for marc tone
2025-10-18 11:56:52 +00:00
phase = 0; // reset phase
sample_in_baud = 0; // reset samples per baud
bit_pos = 0; // reset bit position counter
stuffing_cnt = 0;
2025-10-18 11:56:52 +00:00
// TIM21 - PWM timer generating tones and sampling interrupts
TIM21->CR1 &= (uint16_t)(~((uint16_t)TIM_CR1_CEN)); // Disable the TIM Counter
TIM21->PSC = AFSK_PWM_TIM_PSC; // Set prescaler
TIM21->ARR = AFSK_PWM_TIM_ARR; // Set autoreload
2025-10-18 11:56:52 +00:00
TIM21->CCER |= LL_TIM_CHANNEL_CH1; // Set PWM channel
TIM21->CR1 |= TIM_CR1_CEN; // enable timer again
TIM21->DIER |= TIM_DIER_UIE; // Enable the interrupt
2025-10-18 11:56:52 +00:00
AFSK_timer_handler(); // Tirgger first modulation iteration to set initial values before PWM will be turned on
2025-10-12 18:24:56 +00:00
}