sforkowany z mirror/RS41ng
765 wiersze
24 KiB
C
765 wiersze
24 KiB
C
#include <stdio.h>
|
|
|
|
#include "config.h"
|
|
#include "payload.h"
|
|
#include "telemetry.h"
|
|
#include "log.h"
|
|
#include "hal/system.h"
|
|
#include "hal/delay.h"
|
|
#include "hal/spi.h"
|
|
#include "hal/pwm.h"
|
|
#include "hal/usart_gps.h"
|
|
#include "codecs/fsk/fsk.h"
|
|
#include "codecs/bell/bell.h"
|
|
#include "codecs/aprs/aprs.h"
|
|
#include "codecs/ax25/ax25.h"
|
|
#include "codecs/jtencode/jtencode.h"
|
|
#include "drivers/si4032/si4032.h"
|
|
#include "si5351_handler.h"
|
|
|
|
typedef enum _radio_type {
|
|
RADIO_TYPE_SI4032 = 1,
|
|
RADIO_TYPE_SI5351,
|
|
} radio_type;
|
|
|
|
typedef enum _radio_data_mode {
|
|
RADIO_DATA_MODE_CW = 1,
|
|
RADIO_DATA_MODE_RTTY,
|
|
RADIO_DATA_MODE_APRS,
|
|
RADIO_DATA_MODE_WSPR,
|
|
RADIO_DATA_MODE_FT8,
|
|
RADIO_DATA_MODE_JT65,
|
|
RADIO_DATA_MODE_JT4,
|
|
RADIO_DATA_MODE_JT9,
|
|
RADIO_DATA_MODE_FSQ_2,
|
|
RADIO_DATA_MODE_FSQ_3,
|
|
RADIO_DATA_MODE_FSQ_4_5,
|
|
RADIO_DATA_MODE_FSQ_6,
|
|
} radio_data_mode;
|
|
|
|
typedef struct _radio_transmit_entry {
|
|
radio_type radio_type;
|
|
radio_data_mode data_mode;
|
|
|
|
uint32_t frequency;
|
|
uint8_t tx_power;
|
|
uint32_t symbol_rate;
|
|
|
|
payload_encoder *payload_encoder;
|
|
fsk_encoder_api *fsk_encoder_api;
|
|
|
|
jtencode_mode_type jtencode_mode_type;
|
|
|
|
fsk_encoder fsk_encoder;
|
|
} radio_transmit_entry;
|
|
|
|
static bool si4032_use_dma = false;
|
|
|
|
static volatile bool radio_transmission_active = false;
|
|
static volatile bool radio_transmission_finished = false;
|
|
static volatile radio_transmit_entry *radio_current_transmit_entry = NULL;
|
|
static volatile int radio_current_transmit_entry_index = 0;
|
|
static volatile uint32_t radio_post_transmit_delay_counter = 0;
|
|
static volatile uint32_t radio_next_symbol_counter = 0;
|
|
|
|
static volatile bool radio_si5351_state_change = false;
|
|
static volatile uint64_t radio_si5351_freq = 0;
|
|
|
|
static volatile bool radio_si4032_state_change = false;
|
|
static volatile uint32_t radio_si4032_freq = 0;
|
|
|
|
static volatile radio_transmit_entry *radio_start_transmit_entry = NULL;
|
|
static volatile radio_transmit_entry *radio_stop_transmit_entry = NULL;
|
|
static volatile bool radio_transmit_next_symbol_flag = false;
|
|
|
|
static volatile uint32_t radio_symbol_count_interrupt = 0;
|
|
static volatile uint32_t radio_symbol_count_loop = 0;
|
|
|
|
static volatile bool radio_dma_transfer_active = false;
|
|
static volatile int8_t radio_dma_transfer_stop_after_counter = -1;
|
|
|
|
static volatile bool radio_manual_transmit_active = false;
|
|
|
|
uint8_t radio_current_payload[RADIO_PAYLOAD_MAX_LENGTH];
|
|
size_t radio_current_payload_length = 0;
|
|
|
|
fsk_tone *radio_current_fsk_tones = NULL;
|
|
int8_t radio_current_fsk_tone_count = 0;
|
|
uint32_t radio_current_tone_spacing_hz_100 = 0;
|
|
|
|
uint32_t radio_current_symbol_rate = 0;
|
|
uint32_t radio_current_symbol_delay_ms_100 = 0;
|
|
|
|
telemetry_data current_telemetry_data;
|
|
|
|
uint8_t aprs_packet[RADIO_PAYLOAD_MAX_LENGTH];
|
|
|
|
static volatile uint32_t start_tick = 0, end_tick = 0;
|
|
|
|
static size_t radio_fill_pwm_buffer(size_t offset, size_t length, uint16_t *buffer);
|
|
|
|
size_t radio_aprs_encode(uint8_t *payload, size_t length, telemetry_data *telemetry_data)
|
|
{
|
|
aprs_generate_position_without_timestamp(
|
|
aprs_packet, sizeof(aprs_packet), telemetry_data, APRS_SYMBOL_TABLE, APRS_SYMBOL, APRS_COMMENT);
|
|
|
|
log_debug("aprs: %s\n", aprs_packet);
|
|
|
|
return ax25_encode_packet_aprs(APRS_CALLSIGN, APRS_SSID, APRS_DESTINATION, APRS_DESTINATION_SSID, APRS_RELAYS,
|
|
(char *) aprs_packet, length, payload);
|
|
}
|
|
|
|
payload_encoder radio_aprs_payload_encoder = {
|
|
.encode = radio_aprs_encode,
|
|
};
|
|
|
|
size_t radio_ft8_encode(uint8_t *payload, size_t length, telemetry_data *telemetry_data)
|
|
{
|
|
// TODO: Encode locator for FT8
|
|
return snprintf((char *) payload, length, "%s %s", FT8_CALLSIGN, FT8_LOCATOR);
|
|
}
|
|
|
|
payload_encoder radio_ft8_payload_encoder = {
|
|
.encode = radio_ft8_encode,
|
|
};
|
|
|
|
size_t radio_wspr_encode(uint8_t *payload, size_t length, telemetry_data *telemetry_data)
|
|
{
|
|
// TODO: Encode locator for WSPR
|
|
return snprintf((char *) payload, length, "");
|
|
}
|
|
|
|
payload_encoder radio_wspr_payload_encoder = {
|
|
.encode = radio_wspr_encode,
|
|
};
|
|
|
|
#define RADIO_TRANSMIT_ENTRY_COUNT 1
|
|
|
|
static radio_transmit_entry radio_transmit_schedule[] = {
|
|
{
|
|
.radio_type = RADIO_TYPE_SI4032,
|
|
.data_mode = RADIO_DATA_MODE_APRS,
|
|
.frequency = RADIO_SI4032_TX_FREQUENCY_APRS,
|
|
.tx_power = RADIO_SI4032_TX_POWER,
|
|
.symbol_rate = 1200,
|
|
.payload_encoder = &radio_aprs_payload_encoder,
|
|
.fsk_encoder_api = &bell_fsk_encoder_api,
|
|
},
|
|
/* {
|
|
.radio_type = RADIO_TYPE_SI5351,
|
|
.data_mode = RADIO_DATA_MODE_FT8,
|
|
.frequency = RADIO_SI5351_TX_FREQUENCY_FT8,
|
|
.tx_power = RADIO_SI5351_TX_POWER,
|
|
.payload_encoder = &radio_ft8_payload_encoder,
|
|
.fsk_encoder_api = &jtencode_fsk_encoder_api,
|
|
.jtencode_mode_type = JTENCODE_MODE_FT8,
|
|
},
|
|
{
|
|
.radio_type = RADIO_TYPE_SI5351,
|
|
.data_mode = RADIO_DATA_MODE_WSPR,
|
|
.frequency = RADIO_SI5351_TX_FREQUENCY_WSPR,
|
|
.tx_power = RADIO_SI5351_TX_POWER,
|
|
.payload_encoder = &radio_wspr_payload_encoder,
|
|
.fsk_encoder_api = &jtencode_fsk_encoder_api,
|
|
.jtencode_mode_type = JTENCODE_MODE_WSPR,
|
|
},*/
|
|
};
|
|
|
|
static bool radio_start_transmit_si4032(radio_transmit_entry *entry)
|
|
{
|
|
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;
|
|
use_direct_mode = true;
|
|
break;
|
|
case RADIO_DATA_MODE_RTTY:
|
|
frequency_offset = 0;
|
|
modulation_type = SI4032_MODULATION_TYPE_NONE;
|
|
use_direct_mode = false;
|
|
break;
|
|
case RADIO_DATA_MODE_APRS:
|
|
frequency_offset = 0;
|
|
modulation_type = SI4032_MODULATION_TYPE_FSK;
|
|
use_direct_mode = true;
|
|
if (si4032_use_dma) {
|
|
radio_fill_pwm_buffer(0, PWM_TIMER_DMA_BUFFER_SIZE, pwm_timer_dma_buffer);
|
|
}
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
si4032_set_tx_frequency(((float) entry->frequency) / 1000000.0f);
|
|
si4032_set_tx_power(entry->tx_power * 7 / 100);
|
|
si4032_set_frequency_offset(frequency_offset);
|
|
si4032_set_modulation_type(modulation_type);
|
|
|
|
si4032_enable_tx();
|
|
|
|
if (use_direct_mode) {
|
|
//delay_us(100);
|
|
spi_uninit();
|
|
pwm_timer_use(true);
|
|
pwm_timer_pwm_enable(true);
|
|
si4032_use_direct_mode(true);
|
|
}
|
|
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_APRS:
|
|
if (si4032_use_dma) {
|
|
radio_dma_transfer_active = true;
|
|
radio_dma_transfer_stop_after_counter = -1;
|
|
system_disable_tick();
|
|
pwm_dma_start();
|
|
} else {
|
|
log_info("Si4032 manual TX\n");
|
|
radio_manual_transmit_active = true;
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool radio_start_transmit_si5351(radio_transmit_entry *entry)
|
|
{
|
|
si5351_set_drive_strength(SI5351_CLOCK_CLK0, entry->tx_power * 3 / 100);
|
|
si5351_set_frequency(SI5351_CLOCK_CLK0, ((uint64_t) entry->frequency) * 100ULL);
|
|
si5351_output_enable(SI5351_CLOCK_CLK0, true);
|
|
return true;
|
|
}
|
|
|
|
static inline void radio_reset_next_symbol_counter()
|
|
{
|
|
if (radio_current_symbol_rate > 0) {
|
|
radio_next_symbol_counter = (uint32_t) (((float) SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND) /
|
|
(float) radio_current_symbol_rate);
|
|
} else {
|
|
radio_next_symbol_counter = (uint32_t) (((float) radio_current_symbol_delay_ms_100) *
|
|
(float) SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND / 100000.0f);
|
|
}
|
|
}
|
|
|
|
static inline bool radio_should_transmit_next_symbol()
|
|
{
|
|
return radio_next_symbol_counter == 0;
|
|
}
|
|
|
|
static bool radio_start_transmit(radio_transmit_entry *entry)
|
|
{
|
|
bool success;
|
|
|
|
radio_symbol_count_interrupt = 0;
|
|
radio_symbol_count_loop = 0;
|
|
|
|
telemetry_collect(¤t_telemetry_data);
|
|
|
|
radio_current_payload_length = entry->payload_encoder->encode(
|
|
radio_current_payload, sizeof(radio_current_payload), ¤t_telemetry_data);
|
|
|
|
log_info("Full payload length: %d\n", radio_current_payload_length);
|
|
|
|
for (int i = 0; i < radio_current_payload_length; i++) {
|
|
char c = radio_current_payload[i];
|
|
if (c >= 0x20 && c <= 0x7e) {
|
|
log_info("%c", c);
|
|
} else {
|
|
log_info(" [%02X] ", c);
|
|
}
|
|
}
|
|
|
|
log_info("\n");
|
|
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_CW:
|
|
break;
|
|
case RADIO_DATA_MODE_RTTY:
|
|
break;
|
|
case RADIO_DATA_MODE_APRS:
|
|
// TODO: make bell tones and flag field count configurable
|
|
bell_encoder_new(&entry->fsk_encoder, entry->symbol_rate, BELL_FLAG_FIELD_COUNT_1200, bell202_tones);
|
|
radio_current_symbol_rate = entry->fsk_encoder_api->get_symbol_rate(&entry->fsk_encoder);
|
|
entry->fsk_encoder_api->get_tones(&entry->fsk_encoder, &radio_current_fsk_tone_count,
|
|
&radio_current_fsk_tones);
|
|
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
|
|
break;
|
|
case RADIO_DATA_MODE_WSPR:
|
|
case RADIO_DATA_MODE_FT8:
|
|
case RADIO_DATA_MODE_JT65:
|
|
case RADIO_DATA_MODE_JT4:
|
|
case RADIO_DATA_MODE_JT9:
|
|
case RADIO_DATA_MODE_FSQ_2:
|
|
case RADIO_DATA_MODE_FSQ_3:
|
|
case RADIO_DATA_MODE_FSQ_4_5:
|
|
case RADIO_DATA_MODE_FSQ_6:
|
|
// TODO: Encode WSPR locator
|
|
jtencode_encoder_new(&entry->fsk_encoder, entry->jtencode_mode_type, WSPR_CALLSIGN, WSPR_LOCATOR, WSPR_DBM,
|
|
FSQ_CALLSIGN_FROM, FSQ_CALLSIGN_TO, FSQ_COMMAND);
|
|
radio_current_symbol_delay_ms_100 = entry->fsk_encoder_api->get_symbol_delay(&entry->fsk_encoder);
|
|
radio_current_tone_spacing_hz_100 = entry->fsk_encoder_api->get_tone_spacing(&entry->fsk_encoder);
|
|
entry->fsk_encoder_api->set_data(&entry->fsk_encoder, radio_current_payload_length, radio_current_payload);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
// USART interrupts may interfere with transmission timing
|
|
usart_gps_enable(false);
|
|
|
|
switch (entry->radio_type) {
|
|
case RADIO_TYPE_SI4032:
|
|
success = radio_start_transmit_si4032(entry);
|
|
break;
|
|
case RADIO_TYPE_SI5351:
|
|
success = radio_start_transmit_si5351(entry);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (!success) {
|
|
usart_gps_enable(true);
|
|
// TODO: stop transmit here
|
|
return false;
|
|
}
|
|
|
|
log_info("TX enabled\n");
|
|
|
|
system_set_red_led(true);
|
|
|
|
radio_transmission_active = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool radio_stop_transmit_si4032(radio_transmit_entry *entry)
|
|
{
|
|
bool use_direct_mode;
|
|
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_CW:
|
|
use_direct_mode = true;
|
|
break;
|
|
case RADIO_DATA_MODE_RTTY:
|
|
use_direct_mode = false;
|
|
break;
|
|
case RADIO_DATA_MODE_APRS:
|
|
use_direct_mode = true;
|
|
if (si4032_use_dma) {
|
|
system_enable_tick();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (use_direct_mode) {
|
|
si4032_use_direct_mode(false);
|
|
pwm_timer_pwm_enable(false);
|
|
//delay_us(100);
|
|
pwm_timer_use(false);
|
|
//delay_us(100);
|
|
spi_init();
|
|
//delay_us(100);
|
|
}
|
|
|
|
si4032_inhibit_tx();
|
|
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_APRS:
|
|
if (si4032_use_dma) {
|
|
system_enable_tick();
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool radio_stop_transmit_si5351(radio_transmit_entry *entry)
|
|
{
|
|
si5351_output_enable(SI5351_CLOCK_CLK0, false);
|
|
return true;
|
|
}
|
|
|
|
static inline void radio_reset_transmit_state()
|
|
{
|
|
radio_transmission_active = false;
|
|
radio_next_symbol_counter = 0;
|
|
|
|
radio_current_payload_length = 0;
|
|
|
|
radio_current_fsk_tones = NULL;
|
|
radio_current_fsk_tone_count = 0;
|
|
radio_current_tone_spacing_hz_100 = 0;
|
|
|
|
radio_current_symbol_rate = 0;
|
|
radio_current_symbol_delay_ms_100 = 0;
|
|
}
|
|
|
|
static bool radio_stop_transmit(radio_transmit_entry *entry)
|
|
{
|
|
bool success;
|
|
|
|
switch (entry->radio_type) {
|
|
case RADIO_TYPE_SI4032:
|
|
success = radio_stop_transmit_si4032(entry);
|
|
break;
|
|
case RADIO_TYPE_SI5351:
|
|
success = radio_stop_transmit_si5351(entry);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
radio_reset_transmit_state();
|
|
|
|
radio_manual_transmit_active = false;
|
|
radio_dma_transfer_active = false;
|
|
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_CW:
|
|
break;
|
|
case RADIO_DATA_MODE_RTTY:
|
|
break;
|
|
case RADIO_DATA_MODE_APRS:
|
|
bell_encoder_destroy(&entry->fsk_encoder);
|
|
break;
|
|
case RADIO_DATA_MODE_WSPR:
|
|
case RADIO_DATA_MODE_FT8:
|
|
case RADIO_DATA_MODE_JT65:
|
|
case RADIO_DATA_MODE_JT4:
|
|
case RADIO_DATA_MODE_JT9:
|
|
case RADIO_DATA_MODE_FSQ_2:
|
|
case RADIO_DATA_MODE_FSQ_3:
|
|
case RADIO_DATA_MODE_FSQ_4_5:
|
|
case RADIO_DATA_MODE_FSQ_6:
|
|
jtencode_encoder_destroy(&entry->fsk_encoder);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
usart_gps_enable(true);
|
|
system_set_red_led(false);
|
|
|
|
return success;
|
|
}
|
|
|
|
static uint32_t radio_next_symbol_si4032(radio_transmit_entry *entry)
|
|
{
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_CW:
|
|
return 0;
|
|
case RADIO_DATA_MODE_RTTY:
|
|
return 0;
|
|
case RADIO_DATA_MODE_APRS: {
|
|
int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder);
|
|
if (next_tone_index < 0) {
|
|
return 0;
|
|
}
|
|
|
|
return radio_current_fsk_tones[next_tone_index].frequency_hz_100;
|
|
}
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static bool radio_transmit_symbol_si4032(radio_transmit_entry *entry)
|
|
{
|
|
uint32_t frequency = radio_next_symbol_si4032(entry);
|
|
|
|
if (frequency == 0) {
|
|
return false;
|
|
}
|
|
|
|
radio_si4032_freq = frequency;
|
|
radio_si4032_state_change = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool radio_transmit_symbol_si5351(radio_transmit_entry *entry)
|
|
{
|
|
switch (entry->data_mode) {
|
|
case RADIO_DATA_MODE_CW:
|
|
return false;
|
|
default: {
|
|
int8_t next_tone_index = entry->fsk_encoder_api->next_tone(&entry->fsk_encoder);
|
|
if (next_tone_index < 0) {
|
|
return false;
|
|
}
|
|
|
|
log_trace("Tone: %d\n", next_tone_index);
|
|
|
|
uint64_t frequency =
|
|
((uint64_t) entry->frequency) * 100UL + (next_tone_index * radio_current_tone_spacing_hz_100);
|
|
radio_si5351_freq = frequency;
|
|
radio_si5351_state_change = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool radio_transmit_symbol(radio_transmit_entry *entry)
|
|
{
|
|
bool success;
|
|
|
|
switch (entry->radio_type) {
|
|
case RADIO_TYPE_SI4032:
|
|
success = radio_transmit_symbol_si4032(entry);
|
|
break;
|
|
case RADIO_TYPE_SI5351:
|
|
success = radio_transmit_symbol_si5351(entry);
|
|
break;
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
if (success) {
|
|
radio_symbol_count_interrupt++;
|
|
}
|
|
|
|
return success;
|
|
}
|
|
|
|
static void radio_next_transmit_entry()
|
|
{
|
|
radio_current_transmit_entry_index = (radio_current_transmit_entry_index + 1) % RADIO_TRANSMIT_ENTRY_COUNT;
|
|
radio_current_transmit_entry = &radio_transmit_schedule[radio_current_transmit_entry_index];
|
|
radio_post_transmit_delay_counter = RADIO_POST_TRANSMIT_DELAY * SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND / 1000;
|
|
}
|
|
|
|
void radio_handle_timer_tick()
|
|
{
|
|
if (radio_dma_transfer_active || radio_manual_transmit_active) {
|
|
return;
|
|
}
|
|
|
|
if (radio_next_symbol_counter > 0) {
|
|
radio_next_symbol_counter--;
|
|
}
|
|
|
|
if (radio_transmission_active && radio_should_transmit_next_symbol()) {
|
|
radio_transmit_next_symbol_flag = true;
|
|
radio_reset_next_symbol_counter();
|
|
}
|
|
|
|
if (!radio_transmission_active && radio_post_transmit_delay_counter > 0) {
|
|
radio_post_transmit_delay_counter--;
|
|
}
|
|
|
|
// TODO: specify which modes need time synchronization from GPS
|
|
// TODO: implement time sync
|
|
}
|
|
|
|
uint16_t symbol_delay = 823; // -> good around ~823 for a tight loop
|
|
|
|
void radio_handle_main_loop()
|
|
{
|
|
if (radio_post_transmit_delay_counter == 0) {
|
|
telemetry_collect(¤t_telemetry_data);
|
|
log_info("Battery: %d mV\n", current_telemetry_data.battery_voltage_millivolts);
|
|
log_info("Internal temperature: %ld C*100\n", current_telemetry_data.internal_temperature_celsius_100);
|
|
log_info("Time: %02d:%02d:%02d\n",
|
|
current_telemetry_data.gps.hours, current_telemetry_data.gps.minutes,
|
|
current_telemetry_data.gps.seconds);
|
|
log_info("Fix: %d, Sats: %d, OK packets: %d, Bad packets: %d\n",
|
|
current_telemetry_data.gps.fix, current_telemetry_data.gps.sats_raw,
|
|
current_telemetry_data.gps.ok_packets, current_telemetry_data.gps.bad_packets);
|
|
log_info("Lat: %ld *1M, Lon: %ld *1M, Alt: %ld m\n",
|
|
current_telemetry_data.gps.lat_raw / 10, current_telemetry_data.gps.lon_raw / 10,
|
|
(current_telemetry_data.gps.alt_raw / 1000) * 3280 / 1000);
|
|
|
|
radio_post_transmit_delay_counter = RADIO_POST_TRANSMIT_DELAY * SYSTEM_SCHEDULER_TIMER_TICKS_PER_SECOND / 1000;
|
|
radio_start_transmit_entry = radio_current_transmit_entry;
|
|
}
|
|
|
|
if (radio_si4032_state_change) {
|
|
if (radio_manual_transmit_active) {
|
|
// TODO: Refactor this code for proper handling of APRS
|
|
fsk_encoder_api *fsk_encoder_api = radio_current_transmit_entry->fsk_encoder_api;
|
|
fsk_encoder *fsk_enc = &radio_current_transmit_entry->fsk_encoder;
|
|
log_info("Si4032 manual TX start: %d\n", symbol_delay);
|
|
system_disable_tick();
|
|
int8_t tone_index = 0;
|
|
uint32_t pwm_periods[2];
|
|
pwm_periods[0] = pwm_calculate_period(radio_current_fsk_tones[0].frequency_hz_100);
|
|
pwm_periods[1] = pwm_calculate_period(radio_current_fsk_tones[1].frequency_hz_100);
|
|
switch (radio_current_transmit_entry->data_mode) {
|
|
case RADIO_DATA_MODE_APRS:
|
|
do {
|
|
// radio_si4032_state_change = false;
|
|
pwm_timer_set_frequency(pwm_periods[tone_index]);
|
|
// radio_symbol_count_loop++;
|
|
delay_us(symbol_delay);
|
|
tone_index = fsk_encoder_api->next_tone(fsk_enc);
|
|
} while (tone_index >= 0);
|
|
//} while (radio_transmit_symbol(radio_current_transmit_entry));
|
|
|
|
radio_si4032_state_change = false;
|
|
radio_transmission_finished = true;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
system_enable_tick();
|
|
//symbol_delay += 1;
|
|
} else {
|
|
radio_si4032_state_change = false;
|
|
pwm_timer_set_frequency(radio_si4032_freq);
|
|
radio_symbol_count_loop++;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (radio_si5351_state_change) {
|
|
radio_si5351_state_change = false;
|
|
si5351_set_frequency(SI5351_CLOCK_CLK0, radio_si5351_freq);
|
|
return;
|
|
}
|
|
|
|
bool first_symbol = false;
|
|
if (radio_start_transmit_entry != NULL) {
|
|
log_info("Start transmit\n");
|
|
bool success = radio_start_transmit(radio_start_transmit_entry);
|
|
start_tick = system_get_tick();
|
|
|
|
radio_start_transmit_entry = NULL;
|
|
if (!success) {
|
|
radio_next_transmit_entry();
|
|
return;
|
|
}
|
|
|
|
if (!radio_dma_transfer_active) {
|
|
first_symbol = true;
|
|
radio_transmit_next_symbol_flag = true;
|
|
}
|
|
}
|
|
|
|
if (radio_transmission_active && radio_transmit_next_symbol_flag) {
|
|
radio_transmit_next_symbol_flag = false;
|
|
bool success = radio_transmit_symbol(radio_current_transmit_entry);
|
|
if (!success) {
|
|
radio_transmission_finished = true;
|
|
}
|
|
if (first_symbol) {
|
|
radio_reset_next_symbol_counter();
|
|
}
|
|
}
|
|
|
|
if (radio_transmission_finished) {
|
|
radio_stop_transmit_entry = radio_current_transmit_entry;
|
|
end_tick = system_get_tick();
|
|
radio_transmission_finished = false;
|
|
}
|
|
|
|
if (radio_stop_transmit_entry != NULL) {
|
|
radio_stop_transmit(radio_stop_transmit_entry);
|
|
radio_stop_transmit_entry = NULL;
|
|
|
|
radio_next_transmit_entry();
|
|
|
|
log_info("Transmit stopped\n");
|
|
log_info("Symbol count (interrupt): %ld\n", radio_symbol_count_interrupt);
|
|
log_info("Symbol count (loop): %ld\n", radio_symbol_count_loop);
|
|
log_info("Total ticks: %ld\n", end_tick - start_tick);
|
|
log_info("Next symbol counter: %ld\n", radio_next_symbol_counter);
|
|
log_info("Symbol rate: %ld\n", radio_current_symbol_rate);
|
|
log_info("Symbol delay: %ld\n", radio_current_symbol_delay_ms_100);
|
|
log_info("Tone spacing: %ld\n", radio_current_tone_spacing_hz_100);
|
|
}
|
|
}
|
|
|
|
static size_t radio_fill_pwm_buffer(size_t offset, size_t length, uint16_t *buffer)
|
|
{
|
|
size_t count = 0;
|
|
for (size_t i = offset; i < (offset + length); i++, count++) {
|
|
uint32_t frequency = radio_next_symbol_si4032(radio_current_transmit_entry);
|
|
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;
|
|
}
|
|
|
|
static bool radio_stop_dma_transfer_if_requested()
|
|
{
|
|
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;
|
|
radio_transmission_finished = true;
|
|
radio_dma_transfer_active = false;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
size_t radio_handle_pwm_transfer_half(size_t buffer_size, uint16_t *buffer)
|
|
{
|
|
if (radio_stop_dma_transfer_if_requested()) {
|
|
return 0;
|
|
}
|
|
if (radio_transmission_finished) {
|
|
log_info("Should not be here, half-transfer!\n");
|
|
}
|
|
|
|
size_t length = radio_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;
|
|
}
|
|
|
|
size_t radio_handle_pwm_transfer_full(size_t buffer_size, uint16_t *buffer)
|
|
{
|
|
if (radio_stop_dma_transfer_if_requested()) {
|
|
return 0;
|
|
}
|
|
if (radio_transmission_finished) {
|
|
log_info("Should not be here, transfer complete!\n");
|
|
}
|
|
|
|
size_t length = radio_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()
|
|
{
|
|
pwm_handle_dma_transfer_half = radio_handle_pwm_transfer_half;
|
|
pwm_handle_dma_transfer_full = radio_handle_pwm_transfer_full;
|
|
|
|
radio_current_transmit_entry = &radio_transmit_schedule[radio_current_transmit_entry_index];
|
|
|
|
pwm_timer_init(100 * 100);
|
|
|
|
if (si4032_use_dma) {
|
|
pwm_data_timer_init();
|
|
pwm_dma_init();
|
|
}
|
|
}
|