2020-09-03 19:12:43 +00:00
|
|
|
#include <stdint.h>
|
|
|
|
#include <string.h>
|
|
|
|
|
|
|
|
#include "hal/system.h"
|
|
|
|
#include "hal/spi.h"
|
|
|
|
#include "hal/pwm.h"
|
|
|
|
#include "hal/delay.h"
|
2021-08-11 20:24:30 +00:00
|
|
|
#include "hal/datatimer.h"
|
2020-09-03 19:12:43 +00:00
|
|
|
#include "drivers/si4032/si4032.h"
|
|
|
|
#include "log.h"
|
|
|
|
|
|
|
|
#include "radio_si4032.h"
|
2021-08-11 20:24:30 +00:00
|
|
|
#include "codecs/mfsk/mfsk.h"
|
2020-09-03 19:12:43 +00:00
|
|
|
|
2021-08-12 21:42:50 +00:00
|
|
|
#define CW_SYMBOL_RATE_MULTIPLIER 4
|
|
|
|
|
2020-09-10 19:57:41 +00:00
|
|
|
/**
|
|
|
|
* I have attempted to implement Bell 202 frequency generation using hardware DMA and PWM, but have failed to generate
|
|
|
|
* correct symbol rate that other APRS equipment are able to decode. I have tried to decode the DMA-based modulation with
|
|
|
|
* some tools intended for debugging APRS and while some bytes are decoded correctly every once in a while,
|
|
|
|
* the timings are mostly off for some unknown reason.
|
|
|
|
*
|
|
|
|
* The Bell 202 modulation implementation uses hardware PWM to generate the individual tone frequencies,
|
|
|
|
* but when si4032_use_dma is false, the symbol timing is created in a loop with delay that was chosen
|
|
|
|
* carefully via experiments.
|
|
|
|
*/
|
2020-09-03 19:12:43 +00:00
|
|
|
static bool si4032_use_dma = false;
|
|
|
|
|
|
|
|
// TODO: Add support for multiple APRS baud rates
|
|
|
|
uint16_t symbol_delay_bell_202_1200bps_us = 823;
|
|
|
|
|
|
|
|
static volatile bool radio_si4032_state_change = false;
|
|
|
|
static volatile uint32_t radio_si4032_freq = 0;
|
|
|
|
static volatile int8_t radio_dma_transfer_stop_after_counter = -1;
|
|
|
|
|
|
|
|
uint32_t precalculated_pwm_periods[FSK_TONE_COUNT_MAX];
|
|
|
|
|
|
|
|
uint16_t radio_si4032_fill_pwm_buffer(uint16_t offset, uint16_t length, uint16_t *buffer);
|
|
|
|
|
|
|
|
bool radio_start_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
uint16_t frequency_offset;
|
|
|
|
si4032_modulation_type modulation_type;
|
|
|
|
bool use_direct_mode;
|
|
|
|
|
|
|
|
switch (entry->data_mode) {
|
|
|
|
case RADIO_DATA_MODE_CW:
|
|
|
|
frequency_offset = 1;
|
|
|
|
modulation_type = SI4032_MODULATION_TYPE_OOK;
|
2021-08-11 20:24:30 +00:00
|
|
|
use_direct_mode = false;
|
2021-08-12 21:42:50 +00:00
|
|
|
|
|
|
|
data_timer_init(entry->symbol_rate * CW_SYMBOL_RATE_MULTIPLIER);
|
2020-09-03 19:12:43 +00:00
|
|
|
break;
|
|
|
|
case RADIO_DATA_MODE_RTTY:
|
|
|
|
frequency_offset = 0;
|
|
|
|
modulation_type = SI4032_MODULATION_TYPE_NONE;
|
|
|
|
use_direct_mode = false;
|
|
|
|
break;
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200:
|
2020-09-03 19:12:43 +00:00
|
|
|
frequency_offset = 0;
|
|
|
|
modulation_type = SI4032_MODULATION_TYPE_FSK;
|
|
|
|
use_direct_mode = true;
|
|
|
|
if (si4032_use_dma) {
|
2021-08-11 20:24:30 +00:00
|
|
|
pwm_data_timer_init();
|
2020-09-03 19:12:43 +00:00
|
|
|
radio_si4032_fill_pwm_buffer(0, PWM_TIMER_DMA_BUFFER_SIZE, pwm_timer_dma_buffer);
|
|
|
|
}
|
|
|
|
break;
|
2021-08-11 20:24:30 +00:00
|
|
|
case RADIO_DATA_MODE_HORUS_V1: {
|
|
|
|
fsk_tone *idle_tone = mfsk_get_idle_tone(&entry->fsk_encoder);
|
|
|
|
frequency_offset = (uint16_t) idle_tone->index + HORUS_V1_FREQUENCY_OFFSET;
|
|
|
|
modulation_type = SI4032_MODULATION_TYPE_OOK;
|
|
|
|
use_direct_mode = false;
|
|
|
|
|
|
|
|
data_timer_init(entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder));
|
|
|
|
break;
|
|
|
|
}
|
2020-09-03 19:12:43 +00:00
|
|
|
default:
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
si4032_set_tx_frequency(((float) entry->frequency) / 1000000.0f);
|
2020-09-10 19:57:41 +00:00
|
|
|
si4032_set_tx_power(entry->tx_power);
|
2020-09-03 19:12:43 +00:00
|
|
|
si4032_set_frequency_offset(frequency_offset);
|
|
|
|
si4032_set_modulation_type(modulation_type);
|
|
|
|
|
|
|
|
si4032_enable_tx();
|
|
|
|
|
|
|
|
if (use_direct_mode) {
|
|
|
|
spi_uninit();
|
2021-08-12 21:42:50 +00:00
|
|
|
pwm_timer_init(100 * 100); // TODO: Idle tone
|
2020-09-03 19:12:43 +00:00
|
|
|
pwm_timer_use(true);
|
|
|
|
pwm_timer_pwm_enable(true);
|
|
|
|
si4032_use_direct_mode(true);
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (entry->data_mode) {
|
2021-08-12 21:42:50 +00:00
|
|
|
case RADIO_DATA_MODE_CW:
|
|
|
|
spi_uninit();
|
|
|
|
system_disable_tick();
|
|
|
|
si4032_use_sdi_pin(true);
|
|
|
|
shared_state->radio_interrupt_transmit_active = true;
|
|
|
|
break;
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200:
|
2020-09-03 19:12:43 +00:00
|
|
|
if (si4032_use_dma) {
|
|
|
|
shared_state->radio_dma_transfer_active = true;
|
|
|
|
radio_dma_transfer_stop_after_counter = -1;
|
|
|
|
system_disable_tick();
|
|
|
|
pwm_dma_start();
|
|
|
|
} else {
|
|
|
|
shared_state->radio_manual_transmit_active = true;
|
|
|
|
}
|
|
|
|
break;
|
2021-08-11 20:24:30 +00:00
|
|
|
case RADIO_DATA_MODE_HORUS_V1:
|
|
|
|
system_disable_tick();
|
|
|
|
shared_state->radio_interrupt_transmit_active = true;
|
|
|
|
break;
|
2020-09-03 19:12:43 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static uint32_t radio_next_symbol_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
switch (entry->data_mode) {
|
|
|
|
case RADIO_DATA_MODE_CW:
|
|
|
|
return 0;
|
|
|
|
case RADIO_DATA_MODE_RTTY:
|
|
|
|
return 0;
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200: {
|
2020-09-03 19:12:43 +00:00
|
|
|
int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder);
|
|
|
|
if (next_tone_index < 0) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
return shared_state->radio_current_fsk_tones[next_tone_index].frequency_hz_100;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
bool radio_transmit_symbol_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
uint32_t frequency = radio_next_symbol_si4032(entry, shared_state);
|
|
|
|
|
|
|
|
if (frequency == 0) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
radio_si4032_freq = frequency;
|
|
|
|
radio_si4032_state_change = true;
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void radio_handle_main_loop_manual_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
fsk_encoder_api *fsk_encoder_api = entry->fsk_encoder_api;
|
|
|
|
fsk_encoder *fsk_enc = &entry->fsk_encoder;
|
|
|
|
|
|
|
|
for (uint8_t i = 0; i < shared_state->radio_current_fsk_tone_count; i++) {
|
|
|
|
precalculated_pwm_periods[i] = pwm_calculate_period(shared_state->radio_current_fsk_tones[i].frequency_hz_100);
|
|
|
|
}
|
|
|
|
|
|
|
|
system_disable_tick();
|
|
|
|
|
|
|
|
switch (entry->data_mode) {
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200: {
|
2020-09-03 19:12:43 +00:00
|
|
|
int8_t tone_index;
|
|
|
|
|
|
|
|
while ((tone_index = fsk_encoder_api->next_tone(fsk_enc)) >= 0) {
|
|
|
|
pwm_timer_set_frequency(precalculated_pwm_periods[tone_index]);
|
|
|
|
shared_state->radio_symbol_count_loop++;
|
|
|
|
delay_us(symbol_delay_bell_202_1200bps_us);
|
2021-08-11 20:24:30 +00:00
|
|
|
}
|
2020-09-03 19:12:43 +00:00
|
|
|
|
|
|
|
radio_si4032_state_change = false;
|
|
|
|
shared_state->radio_transmission_finished = true;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
system_enable_tick();
|
|
|
|
}
|
|
|
|
|
|
|
|
void radio_handle_main_loop_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
2021-08-11 20:24:30 +00:00
|
|
|
if (entry->radio_type != RADIO_TYPE_SI4032 || shared_state->radio_interrupt_transmit_active) {
|
2020-09-03 19:12:43 +00:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (shared_state->radio_manual_transmit_active) {
|
|
|
|
radio_handle_main_loop_manual_si4032(entry, shared_state);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (radio_si4032_state_change) {
|
|
|
|
radio_si4032_state_change = false;
|
|
|
|
pwm_timer_set_frequency(radio_si4032_freq);
|
|
|
|
shared_state->radio_symbol_count_loop++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-11 20:24:30 +00:00
|
|
|
inline void radio_handle_data_timer_si4032()
|
|
|
|
{
|
2021-08-12 21:42:50 +00:00
|
|
|
static int cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER;
|
|
|
|
|
2021-08-11 20:24:30 +00:00
|
|
|
if (radio_current_transmit_entry->radio_type != RADIO_TYPE_SI4032 || !radio_shared_state.radio_interrupt_transmit_active) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (radio_current_transmit_entry->data_mode) {
|
2021-08-12 21:42:50 +00:00
|
|
|
case RADIO_DATA_MODE_CW: {
|
|
|
|
cw_symbol_rate_multiplier--;
|
|
|
|
if (cw_symbol_rate_multiplier > 0) {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
cw_symbol_rate_multiplier = CW_SYMBOL_RATE_MULTIPLIER;
|
|
|
|
|
|
|
|
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
|
|
|
|
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
|
|
|
|
int8_t tone_index;
|
|
|
|
|
|
|
|
tone_index = fsk_encoder_api->next_tone(fsk_enc);
|
|
|
|
if (tone_index < 0) {
|
|
|
|
si4032_set_sdi_pin(false);
|
|
|
|
log_info("CW TX finished\n");
|
|
|
|
radio_shared_state.radio_interrupt_transmit_active = false;
|
|
|
|
radio_shared_state.radio_transmission_finished = true;
|
|
|
|
system_enable_tick();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
si4032_set_sdi_pin(tone_index == 0 ? false : true);
|
|
|
|
|
|
|
|
radio_shared_state.radio_symbol_count_interrupt++;
|
|
|
|
break;
|
|
|
|
}
|
2021-08-11 20:24:30 +00:00
|
|
|
case RADIO_DATA_MODE_HORUS_V1: {
|
|
|
|
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
|
|
|
|
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
|
|
|
|
int8_t tone_index;
|
|
|
|
|
|
|
|
tone_index = fsk_encoder_api->next_tone(fsk_enc);
|
|
|
|
if (tone_index < 0) {
|
|
|
|
log_info("Horus V1 TX finished\n");
|
|
|
|
radio_shared_state.radio_interrupt_transmit_active = false;
|
|
|
|
radio_shared_state.radio_transmission_finished = true;
|
|
|
|
system_enable_tick();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
si4032_set_frequency_offset_small(tone_index + HORUS_V1_FREQUENCY_OFFSET);
|
2021-08-12 21:42:50 +00:00
|
|
|
|
2021-08-11 20:24:30 +00:00
|
|
|
radio_shared_state.radio_symbol_count_interrupt++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-09-03 19:12:43 +00:00
|
|
|
bool radio_stop_transmit_si4032(radio_transmit_entry *entry, radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
bool use_direct_mode = false;
|
|
|
|
|
|
|
|
switch (entry->data_mode) {
|
|
|
|
case RADIO_DATA_MODE_CW:
|
2021-08-12 21:42:50 +00:00
|
|
|
si4032_use_sdi_pin(false);
|
|
|
|
data_timer_uninit();
|
|
|
|
spi_init();
|
|
|
|
break;
|
2020-09-03 19:12:43 +00:00
|
|
|
case RADIO_DATA_MODE_RTTY:
|
2021-08-11 20:24:30 +00:00
|
|
|
case RADIO_DATA_MODE_HORUS_V1:
|
2021-08-12 21:42:50 +00:00
|
|
|
data_timer_uninit();
|
2020-09-03 19:12:43 +00:00
|
|
|
break;
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200:
|
2020-09-03 19:12:43 +00:00
|
|
|
use_direct_mode = true;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (use_direct_mode) {
|
|
|
|
si4032_use_direct_mode(false);
|
|
|
|
pwm_timer_pwm_enable(false);
|
|
|
|
pwm_timer_use(false);
|
2021-08-12 21:42:50 +00:00
|
|
|
pwm_timer_uninit();
|
2020-09-03 19:12:43 +00:00
|
|
|
spi_init();
|
|
|
|
}
|
|
|
|
|
|
|
|
si4032_inhibit_tx();
|
|
|
|
|
|
|
|
switch (entry->data_mode) {
|
2021-08-12 21:42:50 +00:00
|
|
|
case RADIO_DATA_MODE_CW:
|
|
|
|
system_enable_tick();
|
|
|
|
break;
|
2020-09-10 19:57:41 +00:00
|
|
|
case RADIO_DATA_MODE_APRS_1200:
|
2020-09-03 19:12:43 +00:00
|
|
|
if (si4032_use_dma) {
|
2021-08-11 20:24:30 +00:00
|
|
|
pwm_data_timer_uninit();
|
2020-09-03 19:12:43 +00:00
|
|
|
system_enable_tick();
|
|
|
|
}
|
|
|
|
break;
|
2021-08-11 20:24:30 +00:00
|
|
|
case RADIO_DATA_MODE_HORUS_V1:
|
|
|
|
system_enable_tick();
|
|
|
|
break;
|
2020-09-03 19:12:43 +00:00
|
|
|
default:
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t radio_si4032_fill_pwm_buffer(uint16_t offset, uint16_t length, uint16_t *buffer)
|
|
|
|
{
|
|
|
|
uint16_t count = 0;
|
|
|
|
for (uint16_t i = offset; i < (offset + length); i++, count++) {
|
|
|
|
uint32_t frequency = radio_next_symbol_si4032(radio_current_transmit_entry, &radio_shared_state);
|
|
|
|
if (frequency == 0) {
|
|
|
|
// TODO: fill the other side of the buffer with zeroes too?
|
|
|
|
memset(buffer + offset, 0, (length - i) * sizeof(uint16_t));
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
buffer[i] = pwm_calculate_period(frequency);
|
|
|
|
}
|
|
|
|
|
|
|
|
return count;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool radio_si4032_stop_dma_transfer_if_requested(radio_module_state *shared_state)
|
|
|
|
{
|
|
|
|
if (radio_dma_transfer_stop_after_counter > 0) {
|
|
|
|
radio_dma_transfer_stop_after_counter--;
|
|
|
|
} else if (radio_dma_transfer_stop_after_counter == 0) {
|
|
|
|
pwm_dma_stop();
|
|
|
|
radio_dma_transfer_stop_after_counter = -1;
|
|
|
|
shared_state->radio_transmission_finished = true;
|
|
|
|
shared_state->radio_dma_transfer_active = false;
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t radio_si4032_handle_pwm_transfer_half(uint16_t buffer_size, uint16_t *buffer)
|
|
|
|
{
|
|
|
|
if (radio_si4032_stop_dma_transfer_if_requested(&radio_shared_state)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (radio_shared_state.radio_transmission_finished) {
|
|
|
|
log_info("Should not be here, half-transfer!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t length = radio_si4032_fill_pwm_buffer(0, buffer_size / 2, buffer);
|
|
|
|
if (radio_dma_transfer_stop_after_counter < 0 && length < buffer_size / 2) {
|
|
|
|
radio_dma_transfer_stop_after_counter = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t radio_si4032_handle_pwm_transfer_full(uint16_t buffer_size, uint16_t *buffer)
|
|
|
|
{
|
|
|
|
if (radio_si4032_stop_dma_transfer_if_requested(&radio_shared_state)) {
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
if (radio_shared_state.radio_transmission_finished) {
|
|
|
|
log_info("Should not be here, transfer complete!\n");
|
|
|
|
}
|
|
|
|
|
|
|
|
uint16_t length = radio_si4032_fill_pwm_buffer(buffer_size / 2, buffer_size / 2, buffer);
|
|
|
|
if (radio_dma_transfer_stop_after_counter < 0 && length < buffer_size / 2) {
|
|
|
|
radio_dma_transfer_stop_after_counter = 2;
|
|
|
|
}
|
|
|
|
|
|
|
|
return length;
|
|
|
|
}
|
|
|
|
|
|
|
|
void radio_init_si4032()
|
|
|
|
{
|
|
|
|
pwm_handle_dma_transfer_half = radio_si4032_handle_pwm_transfer_half;
|
|
|
|
pwm_handle_dma_transfer_full = radio_si4032_handle_pwm_transfer_full;
|
|
|
|
|
|
|
|
if (si4032_use_dma) {
|
|
|
|
pwm_data_timer_init();
|
|
|
|
pwm_dma_init();
|
|
|
|
}
|
|
|
|
}
|