kopia lustrzana https://github.com/sq2ips/m20-custom-firmware
181 wiersze
9.4 KiB
C
181 wiersze
9.4 KiB
C
/*
|
|
* afsk.c
|
|
* By SQ2IPS
|
|
* Base implemenatation of AFSK modulation with HDLC Bell 202 tones and bit stuffing, 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.
|
|
* Modulation documentation https://files.tapr.org/meetings/DCC_2014/DCC2014-Amateur-Bell-202-Modem-W6KWF-and-Bridget-Benson.pdf, https://notblackmagic.com/bitsnpieces/afsk/
|
|
* 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, https://notblackmagic.com/bitsnpieces/ax.25
|
|
*/
|
|
|
|
#include "afsk.h"
|
|
#include "adf.h"
|
|
#include "utils.h"
|
|
#include "main.h"
|
|
#include "config.h"
|
|
|
|
/*
|
|
* 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[] = {
|
|
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};
|
|
|
|
static const uint16_t SINE_TABLE_SIZE = sizeof(sine_table);
|
|
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
|
|
|
|
volatile static uint16_t phase_inc = PHASE_INC_MARC; // current phase increase value, fixed point 9.7
|
|
volatile static uint16_t phase = 0; // Current phase value, fixed point 9.7
|
|
volatile static uint16_t sample_in_baud = 0;
|
|
|
|
static uint16_t bit_pos = 0;
|
|
static uint8_t stuffing_cnt = 0;
|
|
static bool stuff = false;
|
|
|
|
static uint8_t QRGCounter = 0;
|
|
bool AFSK_Active = false; // Activity flag
|
|
|
|
static char *buff;
|
|
static uint8_t buff_len;
|
|
|
|
void AFSK_stop_TX()
|
|
{ // Disable TX
|
|
TIM21->CR1 &= ~(TIM_CR1_CEN); // Disable the PWM counter
|
|
TIM21->CNT = 0;
|
|
TIM21->DIER &= ~(TIM_DIER_UIE); // Disable the interrupt
|
|
TIM21->CCER &= ~(LL_TIM_CHANNEL_CH1); // Reset PWM channel
|
|
adf_RF_off(); // turn TX off
|
|
AFSK_Active = false; // turn off activty flag
|
|
}
|
|
|
|
// 0, N1-1 | N1, N1+N2-1 | N1+N2, N1+N2+buff_len-1 | N1+N2+buff_len, N1+N2+buff_len+N3-1
|
|
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;
|
|
}
|
|
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;
|
|
}
|
|
|
|
return 0; // Default return value for unexpected cases
|
|
}
|
|
|
|
void AFSK_timer_handler()
|
|
{ // sampling and PWM timer (TIM21) changing duty cycle acoording to phase (and increasing it according to current tone)
|
|
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
|
|
|
|
phase += phase_inc; // increase phase for generating wanted frequency
|
|
if (phase >= (SINE_TABLE_SIZE << 7))
|
|
phase -= (SINE_TABLE_SIZE << 7); // normalise phase value to be in bounds of array
|
|
|
|
if (sample_in_baud < (1 << 8))
|
|
{ // With the baudrate frequency process next bit of data
|
|
|
|
if (bit_pos >= (N1_SYNC_COUNT + N2_SYNC_COUNT + buff_len + N3_SYNC_COUNT) * 8)
|
|
{ // check for end of transmission
|
|
AFSK_stop_TX();
|
|
}
|
|
else
|
|
{
|
|
/*
|
|
* "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)
|
|
phase_inc ^= (PHASE_INC_MARC ^ PHASE_INC_SPACE); // When bit is 0, change the current frequency, when 1 dont change it
|
|
}
|
|
}
|
|
|
|
sample_in_baud += (1 << 8);
|
|
if (sample_in_baud >= SAMPLES_PER_BAUD)
|
|
{
|
|
sample_in_baud -= SAMPLES_PER_BAUD;
|
|
bit_pos++; // increase bit counter
|
|
}
|
|
}
|
|
|
|
void AFSK_start_TX(char *buffer, uint8_t buffer_len)
|
|
{
|
|
buff = buffer; // Set buffer pointer
|
|
buff_len = buffer_len; // Set buffer length
|
|
|
|
adf_RF_on(QRG_AFSK[QRGCounter++], AFSK_POWER); // turn on radio TX
|
|
if (QRGCounter >= sizeof(QRG_AFSK) / sizeof(QRG_AFSK[0])) QRGCounter = 0;
|
|
|
|
AFSK_Active = true; // turn on activity flag
|
|
//phase_inc = PHASE_INC_MARC; // first phase increase for marc tone
|
|
phase = 0; // reset phase
|
|
sample_in_baud = 0; // reset samples per baud
|
|
bit_pos = 0; // reset bit position counter
|
|
stuffing_cnt = 0;
|
|
|
|
// ADF module set deviation
|
|
adf_set_deviation(ADF_FSK_DEVIATION);
|
|
|
|
// TIM21 - PWM timer generating tones and sampling interrupts
|
|
TIM21->CR1 &= ~(TIM_CR1_CEN); // Disable the TIM Counter
|
|
TIM21->PSC = AFSK_PWM_TIM_PSC; // Set prescaler
|
|
TIM21->ARR = AFSK_PWM_TIM_ARR; // Set autoreload
|
|
|
|
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
|
|
|
|
AFSK_timer_handler(); // Tirgger first modulation iteration to set initial values before PWM will be turned on
|
|
}
|