kopia lustrzana https://github.com/DL7AD/pecanpico10
1798 wiersze
59 KiB
C
1798 wiersze
59 KiB
C
/**
|
|
* Si446x driver specialized for APRS transmissions. The driver supports APRS
|
|
* transmission and reception.
|
|
* There can be either used the SLabs Si4463 or Si4464.
|
|
*/
|
|
|
|
#include "pktconf.h"
|
|
#include "debug.h"
|
|
#include "radio.h"
|
|
#include "geofence.h"
|
|
#include "si4463_patch.h"
|
|
|
|
|
|
/*===========================================================================*/
|
|
/* Module local variables. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module constants. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module exported variables. */
|
|
/*===========================================================================*/
|
|
|
|
/*===========================================================================*/
|
|
/* Module local definitions. */
|
|
/*===========================================================================*/
|
|
|
|
static const uint8_t Si4463_Patch_Data_Array[] = {
|
|
SI4463_PATCH_CMDS,
|
|
0x00
|
|
};
|
|
|
|
/*
|
|
* @brief The SPI configuration.
|
|
* @note the CS line is set dynamically per radio.
|
|
*/
|
|
static SPIConfig ls_spicfg = {
|
|
.cr1 = SPI_CR1_MSTR
|
|
};
|
|
|
|
/**
|
|
* Get pointer to the radio specific configuration.
|
|
*/
|
|
static const si446x_mcucfg_t *Si446x_getConfig(const radio_unit_t radio) {
|
|
const radio_config_t *data = pktGetRadioData(radio);
|
|
return (si446x_mcucfg_t *)data->cfg;
|
|
}
|
|
|
|
/**
|
|
* Get pointer to the radio specific volatile data.
|
|
*/
|
|
static si446x_data_t *Si446x_getData(const radio_unit_t radio) {
|
|
const radio_config_t *data = pktGetRadioData(radio);
|
|
return (si446x_data_t *)data->dat;
|
|
}
|
|
|
|
/**
|
|
* Acquire bus and set the select line in SPI configuration.
|
|
*/
|
|
static SPIDriver *Si446x_spiSetupBus(const radio_unit_t radio,
|
|
SPIConfig *cfg) {
|
|
SPIDriver *spip = Si446x_getConfig(radio)->spi;
|
|
spiAcquireBus(spip);
|
|
cfg->ssport = PAL_PORT(Si446x_getConfig(radio)->cs);
|
|
cfg->sspad = PAL_PAD(Si446x_getConfig(radio)->cs);
|
|
return spip;
|
|
}
|
|
|
|
/**
|
|
* SPI write which uses CTS presented on radio GPIO1.
|
|
* Used when starting the radio up from shutdown state.
|
|
* @pre The MCU GPIO pin connected to 446x GPIO1 must be configured as input.
|
|
*/
|
|
static bool Si446x_writeBoot(const radio_unit_t radio,
|
|
const uint8_t* txData, uint32_t len) {
|
|
/* Write data via SPI with CTS checked via GPIO1. */
|
|
|
|
/* Acquire bus and then start SPI. */
|
|
SPIDriver *spip = Si446x_spiSetupBus(radio, &ls_spicfg);
|
|
spiStart(spip, &ls_spicfg);
|
|
|
|
/* Poll for CTS with timeout of 100mS. */
|
|
ioline_t cts = Si446x_getConfig(radio)->gpio1;
|
|
uint8_t timeout = 100;
|
|
do {
|
|
if(timeout != 100)
|
|
chThdSleep(TIME_MS2I(1));
|
|
} while(palReadLine(cts) != PAL_HIGH && timeout--);
|
|
|
|
if(!timeout) {
|
|
TRACE_ERROR("SI > CTS not received");
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
return false;
|
|
}
|
|
|
|
/* Transfer data now there is CTS.*/
|
|
spiSelect(spip);
|
|
spiSend(spip, len, txData);
|
|
spiUnselect(spip);
|
|
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static bool Si446x_write(const radio_unit_t radio,
|
|
const uint8_t* txData, uint32_t len) {
|
|
/* Transmit data by SPI with CTS polling by command. */
|
|
uint8_t null_spi[len];
|
|
|
|
/* Acquire bus, get SPI Driver object and then start SPI. */
|
|
SPIDriver *spip = Si446x_spiSetupBus(radio, &ls_spicfg);
|
|
spiStart(spip, &ls_spicfg);
|
|
|
|
/* Poll for CTS with timeout of 100mS. */
|
|
uint8_t timeout = 100;
|
|
uint8_t rx_ready[] = {Si446x_READ_CMD_BUFF, 0x00};
|
|
do {
|
|
spiSelect(spip);
|
|
spiExchange(spip, 1, rx_ready, &rx_ready[1]);
|
|
spiUnselect(spip);
|
|
if(timeout != 100)
|
|
chThdSleep(TIME_MS2I(1));
|
|
} while(rx_ready[1] != Si446x_COMMAND_CTS && timeout--);
|
|
|
|
if(!timeout) {
|
|
TRACE_ERROR("SI > CTS not received");
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
return false;
|
|
}
|
|
|
|
/* Transfer data. */
|
|
spiSelect(spip);
|
|
spiExchange(spip, len, txData, null_spi);
|
|
spiUnselect(spip);
|
|
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* SPI read which uses CTS on GPIO1.
|
|
* Use this when first taking radio out of shutdown.
|
|
* The MCU GPIO pin connected to 446x GPIO1 must be already configured.
|
|
*/
|
|
static bool Si446x_readBoot(const radio_unit_t radio,
|
|
const uint8_t* txData, uint32_t txlen,
|
|
uint8_t* rxData, uint32_t rxlen) {
|
|
|
|
/* Acquire bus and get SPI Driver object. */
|
|
SPIDriver *spip = Si446x_spiSetupBus(radio, &ls_spicfg);
|
|
|
|
/* Poll for CTS with timeout of 100mS. */
|
|
ioline_t cts = Si446x_getConfig(radio)->gpio1;
|
|
uint8_t timeout = 100;
|
|
while(palReadLine(cts) != PAL_HIGH && timeout--) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
|
|
if(!timeout) {
|
|
/* Relinquish bus. */
|
|
spiReleaseBus(spip);
|
|
TRACE_ERROR("SI > CTS not received");
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now write command and any data.
|
|
*/
|
|
spiStart(spip, &ls_spicfg);
|
|
spiSelect(spip);
|
|
spiSend(spip, txlen, txData);
|
|
spiUnselect(spip);
|
|
|
|
/* Poll for CTS from command. */
|
|
timeout = 100;
|
|
while(palReadLine(cts) != PAL_HIGH && timeout--) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
|
|
if(!timeout) {
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
TRACE_ERROR("SI > CTS not received");
|
|
return false;
|
|
}
|
|
|
|
/* Read the response. */
|
|
uint8_t rx_ready[] = {Si446x_READ_CMD_BUFF, 0x00};
|
|
spiSelect(spip);
|
|
spiExchange(spip, rxlen, rx_ready, rxData);
|
|
spiUnselect(spip);
|
|
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Read data from Si446x.
|
|
*/
|
|
static bool Si446x_read(const radio_unit_t radio,
|
|
const uint8_t* txData, uint32_t txlen,
|
|
uint8_t* rxData, uint32_t rxlen) {
|
|
|
|
/* Acquire bus and then start SPI. */
|
|
SPIDriver *spip = Si446x_spiSetupBus(radio, &ls_spicfg);
|
|
spiStart(spip, &ls_spicfg);
|
|
|
|
/*
|
|
* Poll command buffer waiting for CTS from the READ_CMD_BUFF command.
|
|
* This command does not itself cause CTS to report busy.
|
|
* Allocate a buffer to use for CTS check.
|
|
* Timeout after 100mS waiting for CTS.
|
|
*/
|
|
uint8_t timeout = 100;
|
|
uint8_t rx_ready[] = {Si446x_READ_CMD_BUFF, 0x00};
|
|
do {
|
|
if(timeout != 100)
|
|
chThdSleep(TIME_MS2I(1));
|
|
spiSelect(spip);
|
|
spiExchange(spip, 1, rx_ready, &rx_ready[1]);
|
|
spiUnselect(spip);
|
|
|
|
} while(rx_ready[1] != Si446x_COMMAND_CTS && timeout--);
|
|
|
|
if(!timeout) {
|
|
TRACE_ERROR("SI > CTS not received");
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now write command and data.
|
|
*/
|
|
spiSelect(spip);
|
|
spiSend(spip, txlen, txData);
|
|
spiUnselect(spip);
|
|
/*
|
|
* Poll waiting for CTS again using the READ_CMD_BUFF command.
|
|
* Once CTS is received the response data is ready in the rx data buffer.
|
|
* The buffer contains the command, CTS and 0 - 16 bytes of response.
|
|
* Timeout after 100mS waiting for CTS.
|
|
*/
|
|
timeout = 100;
|
|
do {
|
|
if(timeout != 100)
|
|
chThdSleep(TIME_MS2I(1));
|
|
spiSelect(spip);
|
|
spiExchange(spip, rxlen, rx_ready, rxData);
|
|
spiUnselect(spip);
|
|
} while(rxData[1] != Si446x_COMMAND_CTS && timeout--);
|
|
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(spip);
|
|
spiReleaseBus(spip);
|
|
|
|
if(!timeout) {
|
|
TRACE_ERROR("SI > CTS not received");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/* TODO: Make set property a single func with size parameter. */
|
|
static void Si446x_setProperty8(const radio_unit_t radio,
|
|
uint16_t reg, uint8_t val) {
|
|
uint8_t msg[] = {Si446x_SET_PROPERTY,
|
|
(reg >> 8) & 0xFF, 0x01, reg & 0xFF, val};
|
|
Si446x_write(radio, msg, sizeof(msg));
|
|
}
|
|
|
|
static void Si446x_setProperty16(const radio_unit_t radio,
|
|
uint16_t reg, uint8_t val1, uint8_t val2) {
|
|
uint8_t msg[] = {Si446x_SET_PROPERTY,
|
|
(reg >> 8) & 0xFF, 0x02, reg & 0xFF, val1, val2};
|
|
Si446x_write(radio, msg, sizeof(msg));
|
|
}
|
|
|
|
static void Si446x_setProperty24(const radio_unit_t radio,
|
|
uint16_t reg, uint8_t val1,
|
|
uint8_t val2, uint8_t val3) {
|
|
uint8_t msg[] = {Si446x_SET_PROPERTY,
|
|
(reg >> 8) & 0xFF, 0x03, reg & 0xFF, val1, val2, val3};
|
|
Si446x_write(radio, msg, sizeof(msg));
|
|
}
|
|
|
|
static void Si446x_setProperty32(const radio_unit_t radio,
|
|
uint16_t reg, uint8_t val1,
|
|
uint8_t val2, uint8_t val3, uint8_t val4) {
|
|
uint8_t msg[] = {Si446x_SET_PROPERTY,
|
|
(reg >> 8) & 0xFF, 0x04, reg & 0xFF,
|
|
val1, val2, val3, val4};
|
|
Si446x_write(radio, msg, sizeof(msg));
|
|
}
|
|
|
|
/**
|
|
* Get temperature of chip.
|
|
*/
|
|
void Si446x_getTemperature(const radio_unit_t radio) {
|
|
const uint8_t txData[] = {Si446x_GET_ADC_READING, 0x10};
|
|
uint8_t rxData[8];
|
|
Si446x_read(radio, txData, sizeof(txData), rxData, sizeof(rxData));
|
|
uint16_t adc = rxData[7] | ((rxData[6] & 0x7) << 8);
|
|
int16_t temp = (89900 * adc) / 4096 - 29300;
|
|
Si446x_getData(radio)->lastTemp = temp;
|
|
}
|
|
|
|
/**
|
|
* Initializes Si446x transceiver chip.
|
|
*/
|
|
static bool Si446x_init(const radio_unit_t radio) {
|
|
|
|
TRACE_INFO("SI > Start up and initialize radio %d", radio);
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
/*
|
|
* Set MCU GPIO for radio GPIO1 (CTS).
|
|
* Execute radio startup sequence.
|
|
*/
|
|
if(!Si446x_radioStartup(radio)) {
|
|
TRACE_ERROR("SI > Start up of radio %d failed", radio);
|
|
return false;
|
|
}
|
|
|
|
/* Calculate clock source parameters. */
|
|
const uint8_t x3 = (Si446x_CCLK >> 24) & 0x0FF;
|
|
const uint8_t x2 = (Si446x_CCLK >> 16) & 0x0FF;
|
|
const uint8_t x1 = (Si446x_CCLK >> 8) & 0x0FF;
|
|
const uint8_t x0 = (Si446x_CCLK >> 0) & 0x0FF;
|
|
|
|
/*
|
|
* Start the chip API with the POWER_UP command.
|
|
* A second POWER_UP will take place if a patch needs to be applied.
|
|
* The PART_INFO command is used to determine if this is a 4464 or 4463.
|
|
*/
|
|
|
|
const uint8_t init_command[] = {Si446x_POWER_UP, 0x01,
|
|
(Si446x_CLK_TCXO_EN & 0x1),
|
|
x3, x2, x1, x0};
|
|
/*
|
|
* Use of writeBoot() enables SPI write without using OS delays.
|
|
* The Si446x GPIO1 is set to CTS at start up.
|
|
*
|
|
* The Si446x SDO pin is configured to SDO data by the POWER_UP command.
|
|
*/
|
|
Si446x_writeBoot(radio, init_command, sizeof(init_command));
|
|
|
|
/*
|
|
* Next get the PART_INFO.
|
|
* Store details for reference.
|
|
* If the part requires a patch then reset and delay (TBD).
|
|
* Output the patch and re-execute the POWER_UP command.
|
|
*/
|
|
si446x_part_t part_info;
|
|
const uint8_t get_part[] = {Si446x_GET_PART_INFO};
|
|
Si446x_readBoot(radio, get_part, sizeof(get_part), (uint8_t *)&part_info,
|
|
sizeof(part_info));
|
|
|
|
/* Save the part number and ROM revision. */
|
|
handler->radio_part = (part_info.info[3] << 8) + part_info.info[4];
|
|
handler->radio_rom_rev = part_info.info[9];
|
|
|
|
/*
|
|
* Check this radio requires a patch installed.
|
|
* TODO: Probably should be in a table...
|
|
*/
|
|
if(is_Si4463_patch_required(handler->radio_part, handler->radio_rom_rev)) {
|
|
/* Power cycle radio and apply patch. */
|
|
Si446x_radioShutdown(radio);
|
|
chThdSleep(TIME_MS2I(10));
|
|
Si446x_radioStartup(radio);
|
|
uint16_t i = 0;
|
|
while(Si4463_Patch_Data_Array[i] != 0) {
|
|
Si446x_writeBoot(radio, &Si4463_Patch_Data_Array[i + 1],
|
|
Si4463_Patch_Data_Array[i]);
|
|
i += Si4463_Patch_Data_Array[i] + 1;
|
|
}
|
|
const uint8_t init_command[] = {Si446x_POWER_UP, 0x81,
|
|
(Si446x_CLK_TCXO_EN & 0x1),
|
|
x3, x2, x1, x0};
|
|
Si446x_writeBoot(radio, init_command, sizeof(init_command));
|
|
}
|
|
|
|
/* Get and save the patch ID from FUNC_INFO for reference. */
|
|
si446x_func_t func_info;
|
|
const uint8_t get_func[] = {Si446x_GET_FUNC_INFO};
|
|
Si446x_readBoot(radio, get_func, sizeof(get_func), (uint8_t *)&func_info,
|
|
sizeof(func_info));
|
|
|
|
handler->radio_patch = (func_info.info[5] << 8) + func_info.info[6];
|
|
|
|
/*
|
|
* Set transceiver GPIOs.
|
|
* GPIO0, 1 and NIRQ can now be reconfigured as required by TX or RX modes.
|
|
* In that case each needs to setup GPIOs as required.
|
|
*/
|
|
uint8_t gpio_pin_cfg_command2[] = {
|
|
Si446x_GPIO_PIN_CFG, // Command type = GPIO settings
|
|
0x00, // GPIO0 GPIO_MODE = DONOTHING
|
|
0x15, // GPIO1 GPIO_MODE = RAW_RX_DATA
|
|
0x21, // GPIO2 GPIO_MODE = RX_STATE
|
|
0x20, // GPIO3 GPIO_MODE = TX_STATE
|
|
0x1B, // NIRQ NIRQ_MODE = CCA
|
|
0x0B, // SDO SDO_MODE = SDO
|
|
0x00 // GEN_CONFIG
|
|
};
|
|
|
|
Si446x_write(radio, gpio_pin_cfg_command2, sizeof(gpio_pin_cfg_command2));
|
|
|
|
/* TODO: We should clear interrupts here with a GET_INT_STATUS. */
|
|
|
|
/* If Si446x is using its own xtal set the trim capacitor value. */
|
|
#if !Si446x_CLK_TCXO_EN
|
|
Si446x_setProperty8(radio, Si446x_GLOBAL_XO_TUNE, 0x40);
|
|
#endif
|
|
|
|
/* Fast response registers - not used at this time. */
|
|
Si446x_setProperty8(radio, Si446x_FRR_CTL_A_MODE, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_FRR_CTL_B_MODE, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_FRR_CTL_C_MODE, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_FRR_CTL_D_MODE, 0x00);
|
|
|
|
/* Disable interrupts globally. NIRQ pin is used for CCA. */
|
|
Si446x_setProperty8(radio, Si446x_INT_CTL_ENABLE, 0x00);
|
|
|
|
/* Set combined FIFO mode = 0x70. */
|
|
Si446x_setProperty8(radio, Si446x_GLOBAL_CONFIG, 0x70);
|
|
|
|
/* Clear TX & RX FIFO. */
|
|
const uint8_t reset_fifo[] = {Si446x_FIFO_INFO, 0x03};
|
|
Si446x_write(radio, reset_fifo, sizeof(reset_fifo));
|
|
/* No need to unset bits... see si docs. */
|
|
|
|
/*
|
|
* TODO: Move the TX and RX settings out into the respective functions.
|
|
* This would split up into AFSK and FSK for RX & TX.
|
|
* Leave only common setup and init here.
|
|
*/
|
|
Si446x_setProperty8(radio, Si446x_PREAMBLE_TX_LENGTH, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_SYNC_CONFIG, 0x80);
|
|
|
|
/* 32K clock disabled. Divided clock disabled. */
|
|
Si446x_setProperty8(radio, Si446x_GLOBAL_CLK_CFG, 0x00);
|
|
|
|
/* TODO: This setting would move to 2FSK RX. */
|
|
Si446x_setProperty8(radio, Si446x_PREAMBLE_CONFIG_STD_1, 0x14);
|
|
|
|
/* Bit polarity and mapping. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_MAP_CONTROL, 0x00);
|
|
|
|
/* Delta Sigma modulation control for PLL synthesizer. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSM_CTRL, 0x07);
|
|
|
|
/* PLL synthesizer settings. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CLKGEN_BAND, 0x0D);
|
|
|
|
/* Deviation set to +-0.5KHz. */
|
|
Si446x_setProperty24(radio, Si446x_MODEM_FREQ_DEV, 0x00, 0x00, 0x79);
|
|
|
|
/* Ramp down after TX final symbol. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_TX_RAMP_DELAY, 0x01);
|
|
|
|
/* PA ramp timing and modulation delay. */
|
|
Si446x_setProperty8(radio, Si446x_PA_TC, 0x3D);
|
|
|
|
/* Synthesizer PLL settings. */
|
|
Si446x_setProperty8(radio, Si446x_FREQ_CONTROL_INTE, 0x41);
|
|
Si446x_setProperty24(radio, Si446x_FREQ_CONTROL_FRAC, 0x0B, 0xB1, 0x3B);
|
|
Si446x_setProperty16(radio, Si446x_FREQ_CONTROL_CHANNEL_STEP_SIZE, 0x0B, 0xD1);
|
|
Si446x_setProperty8(radio, Si446x_FREQ_CONTROL_W_SIZE, 0x20);
|
|
Si446x_setProperty8(radio, Si446x_FREQ_CONTROL_VCOCNT_RX_ADJ, 0xFA);
|
|
|
|
/* Antenna settings. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_ANT_DIV_MODE, 0x01);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_ANT_DIV_CONTROL, 0x80);
|
|
|
|
/* RSSI value compensation. */
|
|
if(is_part_Si4463(handler->radio_part))
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_COMP, 0x44);
|
|
else
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_COMP, 0x40);
|
|
|
|
/*
|
|
* TODO: Preamble configuration should be set in each mode.
|
|
* Will be needed for RX FSK mode.
|
|
* For now it is not relevant since:
|
|
* - we don't have RX FSK implemented yet.
|
|
* - RX AFSK preamble is decoded in the MCU DSP chain.
|
|
* - TX AFSK encodes its own preamble and then upsamples the entire packet.
|
|
* - TX 2FSK also encodes its own preamble which is sent as data by the PH.
|
|
*/
|
|
Si446x_setProperty8(radio, Si446x_PREAMBLE_CONFIG, 0x21);
|
|
|
|
/* Measure the chip temperature and save initial measurement. */
|
|
Si446x_getTemperature(radio);
|
|
handler->radio_init = true;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Intialize radio only if it has been shutdown.
|
|
*/
|
|
bool Si446x_conditional_init(const radio_unit_t radio) {
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
if(!handler->radio_init)
|
|
return Si446x_init(radio);
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Set radio NCO registers for frequency.
|
|
* This function also collects the chip temperature data at the moment.
|
|
* TODO: Move temperature reading to???
|
|
*/
|
|
bool Si446x_setBandParameters(const radio_unit_t radio,
|
|
radio_freq_t freq,
|
|
channel_hz_t step) {
|
|
|
|
/* Check frequency is in range of chip. */
|
|
if(freq < 144000000UL || freq > 900000000UL)
|
|
return false;
|
|
|
|
|
|
/* Set the output divider as recommended in Si446x data sheet. */
|
|
uint32_t outdiv = 0;
|
|
uint32_t band = 0;
|
|
if(freq < 705000000UL) {outdiv = 6; band = 1;}
|
|
if(freq < 525000000UL) {outdiv = 8; band = 2;}
|
|
if(freq < 353000000UL) {outdiv = 12; band = 3;}
|
|
if(freq < 239000000UL) {outdiv = 16; band = 4;}
|
|
if(freq < 177000000UL) {outdiv = 24; band = 5;}
|
|
|
|
/*
|
|
* Initialize radio.
|
|
*/
|
|
//Si446x_conditional_init(radio);
|
|
|
|
/* Set the band parameter. */
|
|
uint32_t sy_sel = 8;
|
|
uint8_t set_band_property_command[] = {Si446x_SET_PROPERTY,
|
|
0x20, 0x01, 0x51, (band + sy_sel)};
|
|
Si446x_write(radio, set_band_property_command,
|
|
sizeof(set_band_property_command));
|
|
|
|
/* Set the PLL parameters. */
|
|
uint32_t f_pfd = 2 * Si446x_CCLK / outdiv;
|
|
uint32_t n = ((uint32_t)(freq / f_pfd)) - 1;
|
|
float ratio = (float)freq / (float)f_pfd;
|
|
float rest = ratio - (float)n;
|
|
|
|
uint32_t m = (uint32_t)(rest * 524288UL);
|
|
uint32_t m2 = m >> 16;
|
|
uint32_t m1 = (m - m2 * 0x10000) >> 8;
|
|
uint32_t m0 = (m - m2 * 0x10000 - (m1 << 8));
|
|
|
|
uint32_t channel_increment = 524288 * outdiv * step / (2 * Si446x_CCLK);
|
|
uint8_t c1 = channel_increment / 0x100;
|
|
uint8_t c0 = channel_increment - (0x100 * c1);
|
|
|
|
uint8_t set_frequency_property_command[] = {Si446x_SET_PROPERTY,
|
|
0x40, 0x04, 0x00, n,
|
|
m2, m1, m0, c1, c0};
|
|
Si446x_write(radio, set_frequency_property_command,
|
|
sizeof(set_frequency_property_command));
|
|
|
|
uint32_t x = ((((uint32_t)1 << 19) * outdiv * 1300.0)/(2*Si446x_CCLK))*2;
|
|
uint8_t x2 = (x >> 16) & 0xFF;
|
|
uint8_t x1 = (x >> 8) & 0xFF;
|
|
uint8_t x0 = (x >> 0) & 0xFF;
|
|
uint8_t set_deviation[] = {Si446x_SET_PROPERTY, 0x20, 0x03, 0x0a, x2, x1, x0};
|
|
Si446x_write(radio, set_deviation, sizeof(set_deviation));
|
|
|
|
/* Measure the chip temperature and update saved value. */
|
|
Si446x_getTemperature(radio);
|
|
return true;
|
|
}
|
|
|
|
/*static void Si446x_setShift(uint16_t shift)
|
|
{
|
|
if(!shift)
|
|
return;
|
|
|
|
float units_per_hz = (( 0x40000 * outdiv ) / (float)Si446x_CCLK);
|
|
|
|
// Set deviation for 2FSK
|
|
uint32_t modem_freq_dev = (uint32_t)(units_per_hz * shift / 2.0 );
|
|
uint8_t modem_freq_dev_0 = 0xFF & modem_freq_dev;
|
|
uint8_t modem_freq_dev_1 = 0xFF & (modem_freq_dev >> 8);
|
|
uint8_t modem_freq_dev_2 = 0xFF & (modem_freq_dev >> 16);
|
|
|
|
uint8_t set_modem_freq_dev_command[] = {0x11, 0x20, 0x03, 0x0A, modem_freq_dev_2, modem_freq_dev_1, modem_freq_dev_0};
|
|
Si446x_write(set_modem_freq_dev_command, 7);
|
|
}*/
|
|
|
|
static void Si446x_setPowerLevel(const radio_unit_t radio,
|
|
const radio_pwr_t level) {
|
|
// Set the Power
|
|
uint8_t set_pa_pwr_lvl_property_command[] = {Si446x_SET_PROPERTY,
|
|
0x22, 0x01, 0x01, level};
|
|
Si446x_write(radio, set_pa_pwr_lvl_property_command,
|
|
sizeof(set_pa_pwr_lvl_property_command));
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* Radio modulation settings
|
|
*/
|
|
|
|
static void Si446x_setModemAFSK_TX(const radio_unit_t radio) {
|
|
// Setup the NCO modulo and oversampling mode
|
|
uint32_t s = Si446x_CCLK / 10;
|
|
uint8_t f3 = (s >> 24) & 0xFF;
|
|
uint8_t f2 = (s >> 16) & 0xFF;
|
|
uint8_t f1 = (s >> 8) & 0xFF;
|
|
uint8_t f0 = (s >> 0) & 0xFF;
|
|
Si446x_setProperty32(radio, Si446x_MODEM_TX_NCO_MODE, f3, f2, f1, f0);
|
|
|
|
// Setup the NCO data rate for APRS
|
|
Si446x_setProperty24(radio, Si446x_MODEM_DATA_RATE, 0x00, 0x33, 0x90);
|
|
|
|
// Use up-sampled AFSK from FIFO (PH)
|
|
Si446x_setProperty8(radio, Si446x_MODEM_MOD_TYPE, 0x02);
|
|
|
|
/* Set PH bit order for AFSK. */
|
|
Si446x_setProperty8(radio, Si446x_PKT_CONFIG1, 0x01);
|
|
|
|
// Set AFSK filter
|
|
const uint8_t coeff[] = {0x81, 0x9f, 0xc4, 0xee, 0x18, 0x3e, 0x5c, 0x70, 0x76};
|
|
uint8_t i;
|
|
for(i = 0; i < sizeof(coeff); i++) {
|
|
uint8_t msg[] = {0x11, 0x20, 0x01, 0x17-i, coeff[i]};
|
|
Si446x_write(radio, msg, 5);
|
|
}
|
|
}
|
|
|
|
static void Si446x_setModemAFSK_RX(const radio_unit_t radio) {
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
/*
|
|
# BatchName Si4464
|
|
# Crys_freq(Hz): 26000000 Crys_tol(ppm): 20 IF_mode: 2
|
|
# High_perf_Ch_Fil: 1 OSRtune: 0 Ch_Fil_Bw_AFC: 0
|
|
# ANT_DIV: 0 PM_pattern: 15
|
|
# MOD_type: 2 Rsymb(sps): 1200 Fdev(Hz): 500 RXBW(Hz): 150000
|
|
# Manchester: 0 AFC_en: 0 Rsymb_error: 0.0 Chip-Version: 3
|
|
# RF Freq.(MHz): 144 API_TC: 29 fhst: 12500 inputBW: 0
|
|
# BERT: 0 RAW_dout: 0 D_source: 1 Hi_pfm_div: 1
|
|
#
|
|
# RX IF frequency is -406250 Hz
|
|
# WB filter 2 (BW = 14.89 kHz); NB-filter 2 (BW = 14.89 kHz)
|
|
#
|
|
# Modulation index: 0.833
|
|
*/
|
|
|
|
/* Set DIRECT_MODE (asynchronous mode as 2FSK). */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_MOD_TYPE, 0x0A);
|
|
|
|
/* Packet handler disabled in RX. */
|
|
Si446x_setProperty8(radio, Si446x_PKT_CONFIG1, 0x41);
|
|
|
|
if(is_part_Si4463(handler->radio_part)) {
|
|
/* Run 4463 in 4464 compatibility mode (set SEARCH2 to zero). */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RAW_SEARCH2, 0x00);
|
|
}
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RAW_CONTROL, 0x8F);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RAW_SEARCH, 0xD6);
|
|
Si446x_setProperty16(radio, Si446x_MODEM_RAW_EYE, 0x00, 0x3B);
|
|
|
|
/*
|
|
* OOK_MISC settings include parameters related to asynchronous mode.
|
|
* Asynchronous mode is used for AFSK reception passed to DSP decode.
|
|
*/
|
|
Si446x_setProperty8(radio, Si446x_MODEM_OOK_PDTC, 0x2A);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_OOK_CNT1, 0x85);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_OOK_MISC, 0x23);
|
|
|
|
/* RX AFC control. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AFC_GEAR, 0x54);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AFC_WAIT, 0x36);
|
|
Si446x_setProperty16(radio, Si446x_MODEM_AFC_GAIN, 0x80, 0xAB);
|
|
Si446x_setProperty16(radio, Si446x_MODEM_AFC_LIMITER, 0x02, 0x50);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AFC_MISC, 0xC0); // 0x80
|
|
|
|
/* RX AGC control. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AGC_CONTROL, 0xE0); // 0xE2 (bit 1 not used in 4464. It is used in 4463.)
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AGC_WINDOW_SIZE, 0x11);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AGC_RFPD_DECAY, 0x63);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_AGC_IFPD_DECAY, 0x63);
|
|
|
|
/* RX Bit clock recovery control. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_MDM_CTRL, 0x80);
|
|
Si446x_setProperty16(radio, Si446x_MODEM_BCR_OSR, 0x01, 0xC3);
|
|
Si446x_setProperty24(radio, Si446x_MODEM_BCR_NCO_OFFSET, 0x01, 0x22, 0x60);
|
|
Si446x_setProperty16(radio, Si446x_MODEM_BCR_GAIN, 0x00, 0x91);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_BCR_GEAR, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_BCR_MISC1, 0xC2);
|
|
|
|
/* RX IF controls. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_IF_CONTROL, 0x08);
|
|
Si446x_setProperty24(radio, Si446x_MODEM_IF_FREQ, 0x02, 0x80, 0x00);
|
|
|
|
/* RX IF filter decimation controls. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DECIMATION_CFG1, 0x70);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DECIMATION_CFG0, 0x10);
|
|
if(is_part_Si4463(handler->radio_part)) {
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DECIMATION_CFG2, 0x0C);
|
|
}
|
|
|
|
/* RSSI latching disabled. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_CONTROL, 0x00);
|
|
|
|
/* RX IF filter coefficients. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE13_7_0, 0xFF);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE12_7_0, 0xC4);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE11_7_0, 0x30);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE10_7_0, 0x7F);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE9_7_0, 0x5F);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE8_7_0, 0xB5);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE7_7_0, 0xB8);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE6_7_0, 0xDE);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE5_7_0, 0x05);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE4_7_0, 0x17);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE3_7_0, 0x16);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE2_7_0, 0x0C);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE1_7_0, 0x03);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COE0_7_0, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COEM0, 0x15);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COEM1, 0xFF);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COEM2, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX1_CHFLT_COEM3, 0x00);
|
|
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE13_7_0, 0xFF);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE12_7_0, 0xC4);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE11_7_0, 0x30);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE10_7_0, 0x7F);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE9_7_0, 0x5F);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE8_7_0, 0xB5);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE7_7_0, 0xB8);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE6_7_0, 0xDE);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE5_7_0, 0x05);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE4_7_0, 0x17);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE3_7_0, 0x16);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE2_7_0, 0x0C);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE1_7_0, 0x03);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COE0_7_0, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COEM0, 0x15);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COEM1, 0xFF);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COEM2, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_CHFLT_RX2_CHFLT_COEM3, 0x00);
|
|
|
|
Si446x_setProperty8(radio, Si446x_PREAMBLE_CONFIG, 0x21);
|
|
|
|
/* Unused Si4463 features for AFSK RX. */
|
|
if(is_part_Si4463(handler->radio_part)) {
|
|
/* DSA is not enabled. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSA_CTRL1, 0x00); // 0xA0
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSA_CTRL2, 0x00); // 0x04
|
|
Si446x_setProperty8(radio, Si446x_MODEM_SPIKE_DET, 0x00); // 0x03
|
|
Si446x_setProperty8(radio, Si446x_MODEM_ONE_SHOT_AFC, 0x00); // 0x07
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSA_QUAL, 0x00); // 0x06
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSA_RSSI, 0x00); // 0x78
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_MUTE, 0x00);
|
|
Si446x_setProperty8(radio, Si446x_MODEM_DSA_MISC, 0x00); // 0x20
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void Si446x_setModem2FSK_TX(const radio_unit_t radio,
|
|
const uint32_t speed) {
|
|
// Setup the NCO modulo and oversampling mode
|
|
uint32_t s = Si446x_CCLK / 10;
|
|
uint8_t f3 = (s >> 24) & 0xFF;
|
|
uint8_t f2 = (s >> 16) & 0xFF;
|
|
uint8_t f1 = (s >> 8) & 0xFF;
|
|
uint8_t f0 = (s >> 0) & 0xFF;
|
|
Si446x_setProperty32(radio, Si446x_MODEM_TX_NCO_MODE, f3, f2, f1, f0);
|
|
|
|
// Setup the NCO data rate for 2GFSK
|
|
Si446x_setProperty24(radio, Si446x_MODEM_DATA_RATE,
|
|
(uint8_t)(speed >> 16),
|
|
(uint8_t)(speed >> 8), (uint8_t)speed);
|
|
|
|
// Use 2GFSK from FIFO (PH)
|
|
Si446x_setProperty8(radio, Si446x_MODEM_MOD_TYPE, 0x03);
|
|
|
|
/* Set PH bit order for 2FSK. */
|
|
Si446x_setProperty8(radio, Si446x_PKT_CONFIG1, 0x01);
|
|
|
|
// Set 2GFSK filter (default per Si).
|
|
const uint8_t coeff[] = {0x01, 0x03, 0x08, 0x11, 0x21, 0x36, 0x4d, 0x60, 0x67};
|
|
uint8_t i;
|
|
for(i = 0; i < sizeof(coeff); i++) {
|
|
uint8_t msg[] = {0x11, 0x20, 0x01, 0x17-i, coeff[i]};
|
|
Si446x_write(radio, msg, sizeof(msg));
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* Radio Settings
|
|
*/
|
|
static uint8_t __attribute__((unused)) Si446x_getChannel(const radio_unit_t radio) {
|
|
const uint8_t state_info[] = {Si446x_REQUEST_DEVICE_STATE};
|
|
uint8_t rxData[4];
|
|
Si446x_read(radio, state_info, sizeof(state_info), rxData, sizeof(rxData));
|
|
return rxData[3];
|
|
}
|
|
|
|
/*
|
|
* Radio FIFO
|
|
*/
|
|
|
|
static void Si446x_writeFIFO(const radio_unit_t radio,
|
|
uint8_t *msg, uint8_t size) {
|
|
uint8_t write_fifo[size+1];
|
|
write_fifo[0] = Si446x_WRITE_TX_FIFO;
|
|
memcpy(&write_fifo[1], msg, size);
|
|
Si446x_write(radio, write_fifo, size+1);
|
|
}
|
|
|
|
static uint8_t Si446x_getTXfreeFIFO(const radio_unit_t radio) {
|
|
const uint8_t fifo_info[] = {Si446x_FIFO_INFO, 0x00};
|
|
uint8_t rxData[4];
|
|
Si446x_read(radio, fifo_info, sizeof(fifo_info), rxData, sizeof(rxData));
|
|
return rxData[3];
|
|
}
|
|
|
|
/*
|
|
* Radio States
|
|
*/
|
|
|
|
radio_signal_t Si446x_getCurrentRSSI(const radio_unit_t radio) {
|
|
/* Get status. Leave any pending interrupts intact. */
|
|
const uint8_t status_info[] = {Si446x_GET_MODEM_STATUS, 0xEF};
|
|
uint8_t rxData[11];
|
|
Si446x_read(radio, status_info, sizeof(status_info), rxData, sizeof(rxData));
|
|
return rxData[4];
|
|
}
|
|
|
|
static uint8_t Si446x_getState(const radio_unit_t radio) {
|
|
const uint8_t state_info[] = {Si446x_REQUEST_DEVICE_STATE};
|
|
uint8_t rxData[4];
|
|
Si446x_read(radio, state_info, sizeof(state_info), rxData, sizeof(rxData));
|
|
return rxData[2] & 0xF;
|
|
}
|
|
|
|
static void Si446x_setTXState(const radio_unit_t radio, uint8_t chan, uint16_t size){
|
|
uint8_t change_state_command[] = {Si446x_START_TX, chan,
|
|
(Si446x_STATE_READY << 4),
|
|
(size >> 8) & 0x1F, size & 0xFF};
|
|
Si446x_write(radio, change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
static void Si446x_setReadyState(const radio_unit_t radio) {
|
|
const uint8_t change_state_command[] = {Si446x_CHANGE_STATE,
|
|
Si446x_STATE_READY};
|
|
Si446x_write(radio, change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
static void Si446x_setRXState(const radio_unit_t radio, uint8_t chan){
|
|
const uint8_t change_state_command[] = {Si446x_START_RX, chan, 0x00, 0x00,
|
|
0x00, 0x00, 0x08, 0x08};
|
|
Si446x_write(radio, change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
static void Si446x_setStandbyState(const radio_unit_t radio) {
|
|
const uint8_t change_state_command[] = {Si446x_CHANGE_STATE,
|
|
Si446x_STATE_STANDBY};
|
|
Si446x_write(radio, change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void Si446x_radioStandby(const radio_unit_t radio) {
|
|
Si446x_setStandbyState(radio);
|
|
}
|
|
|
|
/**
|
|
* The GPIO connected to radio SDN is set high in board initialization.
|
|
* Thus the radio is in shutdown following board initialization.
|
|
* Si446x GPIO1 is configured to output CTS (option 8) during POR.
|
|
* We use the MCU GPIO connected to radio GPIO1 to check CTS here.
|
|
*
|
|
* Radio init is performed in the radio manager thread init stage.
|
|
* The radio GPIOs can be reconfigured after radio init is complete.
|
|
*/
|
|
bool Si446x_radioStartup(const radio_unit_t radio) {
|
|
|
|
TRACE_INFO("SI > Enable radio %i", radio);
|
|
|
|
/* Assert SDN low to perform radio POR wakeup. */
|
|
palClearLine(Si446x_getConfig(radio)->sdn);
|
|
|
|
/*
|
|
* Set MCU GPIO input for POR and CTS of radio from GPIO0 and GPIO1.
|
|
*/
|
|
palSetLineMode(Si446x_getConfig(radio)->gpio0, PAL_MODE_INPUT_PULLDOWN);
|
|
palSetLineMode(Si446x_getConfig(radio)->gpio1, PAL_MODE_INPUT_PULLDOWN);
|
|
|
|
/* Wait for transceiver to wake up (maximum wakeup time is 6mS).
|
|
* During start up the POR state is on GPIO0.
|
|
* This goes from zero to one when POR completes.
|
|
* We could test this but for now just use a delay.
|
|
*/
|
|
chThdSleep(TIME_MS2I(10));
|
|
/* Return state of CTS after delay. */
|
|
return pktReadGPIOline(Si446x_getConfig(radio)->gpio1) == PAL_HIGH;
|
|
}
|
|
|
|
/**
|
|
* The radio is shutdown by setting SDN high.
|
|
*/
|
|
void Si446x_radioShutdown(const radio_unit_t radio) {
|
|
TRACE_INFO("SI > Disable radio %i", radio);
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
palSetLine(Si446x_getConfig(radio)->sdn);
|
|
handler->radio_init = false;
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
|
|
/*
|
|
* Radio TX/RX
|
|
*/
|
|
|
|
|
|
/**
|
|
* Get CCA over measurement interval.
|
|
* Algorithm counts CCA pulses per millisecond (in systick time slices).
|
|
* If more than one pulse per millisecond is counted then CCA is not true.
|
|
*/
|
|
static bool Si446x_checkCCAthreshold(const radio_unit_t radio, uint8_t ms) {
|
|
/* Get the CCA line. */
|
|
ioline_t cca_line = Si446x_getConfig(radio)->nirq;
|
|
uint16_t cca = 0;
|
|
/* Measure sliced CCA instances in period. */
|
|
for(uint16_t i = 0; i < (ms * TIME_MS2I(1)); i++) {
|
|
cca += Si446x_getCCA(cca_line);
|
|
/* Sleep one tick. */
|
|
chThdSleep(1);
|
|
}
|
|
/* Return result. */
|
|
return cca > ms;
|
|
}
|
|
|
|
/**
|
|
* Wait for a clear time slot and initiate packet transmission.
|
|
*/
|
|
static bool Si446x_transmit(const radio_unit_t radio,
|
|
const radio_freq_t freq,
|
|
const channel_hz_t step,
|
|
const radio_ch_t chan,
|
|
const radio_pwr_t power,
|
|
const uint16_t size,
|
|
const radio_squelch_t rssi,
|
|
sysinterval_t cca_timeout) {
|
|
|
|
/* Get an absolute operating frequency in Hz. */
|
|
radio_freq_t op_freq = pktComputeOperatingFrequency(radio, freq,
|
|
step, chan, RADIO_TX);
|
|
|
|
if(op_freq == FREQ_INVALID) {
|
|
TRACE_ERROR("SI > Frequency out of range");
|
|
TRACE_ERROR("SI > abort transmission");
|
|
return false;
|
|
}
|
|
|
|
/* Switch to ready state if receive is active. */
|
|
if(Si446x_getState(radio) == Si446x_STATE_RX) {
|
|
TRACE_INFO("SI > Switch Si446x to ready state");
|
|
Si446x_setReadyState(radio);
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
|
|
/* Frequency is an absolute frequency in Hz. */
|
|
Si446x_setBandParameters(radio, op_freq, step);
|
|
|
|
/* Check for blind send request. */
|
|
if(rssi != PKT_SI446X_NO_CCA_RSSI) {
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_THRESH, rssi);
|
|
|
|
/* Listen on the TX frequency. */
|
|
Si446x_setRXState(radio, chan);
|
|
/* Wait for RX state. */
|
|
while(Si446x_getState(radio) != Si446x_STATE_RX) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
/* Minimum timeout for CCA is 1 second. */
|
|
if(cca_timeout < TIME_S2I(1)) {
|
|
TRACE_WARN("SI > Minimum CCA wait time forced to 1 second,"
|
|
" %d ms was specified", chTimeI2MS(cca_timeout));
|
|
cca_timeout = TIME_S2I(1);
|
|
}
|
|
|
|
/* Try to get clear channel. */
|
|
TRACE_INFO( "SI > Wait up to %.1f seconds for CCA on"
|
|
" %d.%03d MHz",
|
|
(float32_t)(TIME_I2MS(cca_timeout) / 1000),
|
|
op_freq/1000000, (op_freq%1000000)/1000);
|
|
#define CCA_VALID_TIME_MS 50
|
|
sysinterval_t t0 = chVTGetSystemTime();
|
|
while((Si446x_getState(radio) != Si446x_STATE_RX
|
|
|| Si446x_checkCCAthreshold(radio, CCA_VALID_TIME_MS))
|
|
&& chVTIsSystemTimeWithinX(t0, t0 + cca_timeout)) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
/* Clear channel timing. */
|
|
TRACE_INFO( "SI > CCA attained in %d milliseconds",
|
|
chTimeI2MS(chVTTimeElapsedSinceX(t0)));
|
|
}
|
|
|
|
// Transmit
|
|
TRACE_INFO("SI > Tune Si446x to %d.%03d MHz (TX)",
|
|
op_freq/1000000, (op_freq%1000000)/1000);
|
|
Si446x_setReadyState(radio);
|
|
while(Si446x_getState(radio) != Si446x_STATE_READY) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
/* Set power level and start transmit. */
|
|
Si446x_setPowerLevel(radio, power);
|
|
Si446x_setTXState(radio, chan, size);
|
|
|
|
// Wait until transceiver enters transmit state
|
|
/* TODO: Make a function to handle timeout on fail to reach state. */
|
|
while(Si446x_getState(radio) != Si446x_STATE_TX) {
|
|
chThdSleep(TIME_MS2I(1));
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool Si446x_receiveNoLock(const radio_unit_t radio,
|
|
radio_freq_t freq,
|
|
channel_hz_t step,
|
|
radio_ch_t channel,
|
|
radio_squelch_t rssi,
|
|
mod_t mod) {
|
|
|
|
radio_freq_t op_freq = pktComputeOperatingFrequency(radio, freq,
|
|
step, channel,
|
|
RADIO_RX);
|
|
if(op_freq == FREQ_INVALID) {
|
|
TRACE_ERROR("SI > Frequency out of range");
|
|
TRACE_ERROR("SI > abort transmission");
|
|
return false;
|
|
}
|
|
|
|
uint16_t tot = 0;
|
|
// Wait until transceiver finishes transmission (if there is any)
|
|
while(Si446x_getState(radio) == Si446x_STATE_TX) {
|
|
chThdSleep(TIME_MS2I(10));
|
|
if(tot++ < 500)
|
|
continue;
|
|
|
|
/* Remove TX state. */
|
|
Si446x_setReadyState(radio);
|
|
TRACE_ERROR("SI > Timeout waiting for TX state end");
|
|
TRACE_ERROR("SI > Attempt start of receive");
|
|
break;
|
|
}
|
|
|
|
/* Configure radio for modulation type. */
|
|
if(mod == MOD_AFSK) {
|
|
Si446x_setModemAFSK_RX(radio);
|
|
} else {
|
|
TRACE_ERROR("SI > Modulation type not supported in receive");
|
|
TRACE_ERROR("SI > abort reception");
|
|
return false;
|
|
}
|
|
|
|
TRACE_INFO("SI > Tune Si446x to %d.%03d MHz (RX)",
|
|
op_freq/1000000, (op_freq%1000000)/1000);
|
|
|
|
/* Set squelch level. */
|
|
Si446x_setProperty8(radio, Si446x_MODEM_RSSI_THRESH, rssi);
|
|
|
|
/* Start the receiver. */
|
|
Si446x_setRXState(radio, channel);
|
|
|
|
/* Wait for the receiver to start. */
|
|
while(Si446x_getState(radio) != Si446x_STATE_RX)
|
|
chThdSleep(TIME_MS2I(1));
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* Start or restore reception.
|
|
*
|
|
* return true if RX was enabled and/or resumed OK.
|
|
* return false if RX was not enabled.
|
|
*/
|
|
bool Si4464_enableReceive(const radio_unit_t radio,
|
|
const radio_freq_t rx_frequency,
|
|
const channel_hz_t rx_step,
|
|
const radio_ch_t rx_chan,
|
|
const radio_squelch_t rx_rssi,
|
|
const mod_t rx_mod) {
|
|
|
|
/* Get an absolute operating frequency in Hz. */
|
|
radio_freq_t op_freq = pktComputeOperatingFrequency(radio,
|
|
rx_frequency,
|
|
rx_step,
|
|
rx_chan,
|
|
RADIO_RX);
|
|
|
|
|
|
TRACE_INFO( "SI > Enable reception %d.%03d MHz (ch %d),"
|
|
" RSSI %d, %s",
|
|
op_freq/1000000, (op_freq % 1000000)/1000,
|
|
rx_chan,
|
|
rx_rssi, getModulation(rx_mod));
|
|
|
|
/* Initialize radio before any commands as it may have been powered down. */
|
|
Si446x_conditional_init(radio);
|
|
|
|
/* Frequency must be an absolute frequency in Hz. */
|
|
if(!Si446x_setBandParameters(radio, op_freq, rx_step))
|
|
return false;
|
|
|
|
return Si446x_receiveNoLock(radio, op_freq, rx_step,
|
|
rx_chan, rx_rssi, rx_mod);
|
|
}
|
|
|
|
/*
|
|
* Called when a packet RX channel is closed.
|
|
* If the receiver is active put it into standby.
|
|
*/
|
|
void Si446x_disableReceive(const radio_unit_t radio) {
|
|
/* FIXME: Should have timeout. */
|
|
if(Si446x_getState(radio) == Si446x_STATE_RX) {
|
|
Si446x_radioStandby(radio);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void Si446x_terminateReceive(const radio_unit_t radio) {
|
|
/* FIXME: Should provide status. */
|
|
if(Si446x_getState(radio) == Si446x_STATE_RX) {
|
|
Si446x_setReadyState(radio);
|
|
while(Si446x_getState(radio) == Si446x_STATE_RX);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void Si446x_waitTransmitEnd(const radio_unit_t radio) {
|
|
/* FIXME: Should have timeout. */
|
|
while(Si446x_getState(radio) == Si446x_STATE_TX);
|
|
}
|
|
|
|
/*
|
|
* AFSK Transmitter functions
|
|
*/
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static uint8_t Si446x_getUpsampledNRZIbits(up_sampler_t *upsampler,
|
|
uint8_t *buf) {
|
|
uint8_t b = 0;
|
|
for(uint8_t i = 0; i < 8; i++) {
|
|
if(upsampler->current_sample_in_baud == 0) {
|
|
if((upsampler->packet_pos & 7) == 0) { // Load up next byte
|
|
upsampler->current_byte = buf[upsampler->packet_pos >> 3];
|
|
} else { // Load up next bit
|
|
upsampler->current_byte >>= 1;
|
|
}
|
|
}
|
|
|
|
// Toggle tone (1200 <> 2200)
|
|
upsampler->phase_delta = (upsampler->current_byte & 1)
|
|
? PHASE_DELTA_1200 : PHASE_DELTA_2200;
|
|
/* Add delta-phase (position within SAMPLES_PER_BAUD). */
|
|
upsampler->phase += upsampler->phase_delta;
|
|
b |= ((upsampler->phase >> 16) & 1) << i; // Set modulation bit
|
|
|
|
if(++upsampler->current_sample_in_baud == SAMPLES_PER_BAUD) {
|
|
upsampler->current_sample_in_baud = 0;
|
|
upsampler->packet_pos++;
|
|
}
|
|
}
|
|
return b;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
static void Si446x_transmitTimeoutI(thread_t *tp) {
|
|
/* Tell the thread to terminate. */
|
|
chSysLockFromISR();
|
|
chEvtSignalI(tp, SI446X_EVT_TX_TIMEOUT);
|
|
chSysUnlockFromISR();
|
|
}
|
|
|
|
/*
|
|
* Simple AFSK send thread with minimized buffering and burst send capability.
|
|
* Uses an iterator to size NRZI output and allocate suitable size buffer.
|
|
*
|
|
*/
|
|
THD_FUNCTION(bloc_si_fifo_feeder_afsk, arg) {
|
|
radio_task_object_t *rto = arg;
|
|
|
|
radio_unit_t radio = rto->handler->radio;
|
|
|
|
packet_t pp = rto->packet_out;
|
|
|
|
chDbgAssert(pp != NULL, "no packet in radio task");
|
|
|
|
if(pktLockRadioTransmit(radio, TIME_INFINITE) == MSG_RESET) {
|
|
TRACE_ERROR("SI > AFSK TX reset from radio acquisition");
|
|
/* Free packet object memory. */
|
|
pktReleaseBufferChain(pp);
|
|
|
|
/* Schedule thread and task object memory release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_RESET);
|
|
/* We never arrive here. */
|
|
chSysHalt("TX AFSK exit");
|
|
}
|
|
|
|
/* Initialize radio before any commands as it may have been powered down. */
|
|
Si446x_conditional_init(radio);
|
|
|
|
/* Base frequency is an absolute frequency in Hz. */
|
|
Si446x_setBandParameters(radio, rto->base_frequency,
|
|
rto->step_hz);
|
|
|
|
/* Set 446x back to READY. */
|
|
Si446x_terminateReceive(radio);
|
|
|
|
/* Set the radio for AFSK upsampled mode. */
|
|
Si446x_setModemAFSK_TX(radio);
|
|
|
|
/* Initialize variables for AFSK encoder. */
|
|
virtual_timer_t send_timer;
|
|
|
|
chVTObjectInit(&send_timer);
|
|
msg_t exit_msg;
|
|
tx_iterator_t iterator;
|
|
|
|
/*
|
|
* Use the specified CCA RSSI level.
|
|
* CCA level will be set to blind send after first packet.
|
|
*/
|
|
radio_squelch_t rssi = rto->squelch;
|
|
|
|
do {
|
|
|
|
/*
|
|
* Set NRZI encoding format.
|
|
* Iterator object.
|
|
* Packet reference.
|
|
* Preamble length (HDLC flags)
|
|
* Postamble length (HDLC flags)
|
|
* Tail length (HDLC zeros)
|
|
* Scramble off
|
|
*/
|
|
pktStreamIteratorInit(&iterator, pp, 30, 10, 10, false);
|
|
|
|
uint16_t all = pktStreamEncodingIterator(&iterator, NULL, 0);
|
|
|
|
if(all == 0) {
|
|
/* Nothing encoded. Release packet send object. */
|
|
|
|
TRACE_ERROR("SI > AFSK TX no NRZI data encoded");
|
|
|
|
/* Free packet object memory. */
|
|
pktReleaseBufferChain(pp);
|
|
|
|
/* Schedule thread and task object memory release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktUnlockRadioTransmit(radio);
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_ERROR);
|
|
/* We never arrive here. */
|
|
}
|
|
/* Allocate buffer and perform NRZI encoding. */
|
|
uint8_t layer0[all];
|
|
pktStreamEncodingIterator(&iterator, layer0, all);
|
|
|
|
all *= SAMPLES_PER_BAUD;
|
|
/* Reset TX FIFO in case some remnant unsent data is left there. */
|
|
const uint8_t reset_fifo[] = {0x15, 0x01};
|
|
Si446x_write(radio, reset_fifo, 2);
|
|
|
|
up_sampler_t upsampler = {0};
|
|
upsampler.phase_delta = PHASE_DELTA_1200;
|
|
|
|
/* Maximum amount of FIFO data when using combined TX+RX (safe size). */
|
|
uint8_t localBuffer[Si446x_FIFO_COMBINED_SIZE];
|
|
|
|
/* Get the FIFO buffer amount currently available. */
|
|
uint8_t free = Si446x_getTXfreeFIFO(radio);
|
|
|
|
/* Calculate initial FIFO fill. */
|
|
uint16_t c = (all > free) ? free : all;
|
|
|
|
/*
|
|
* Start transmission timeout timer.
|
|
* If the 446x gets locked up we'll exit TX and release packet object.
|
|
*/
|
|
chVTSet(&send_timer, TIME_S2I(10),
|
|
(vtfunc_t)Si446x_transmitTimeoutI, chThdGetSelfX());
|
|
|
|
/* The exit message if all goes well. */
|
|
exit_msg = MSG_OK;
|
|
|
|
/* Initial FIFO load. */
|
|
for(uint16_t i = 0; i < c; i++)
|
|
localBuffer[i] = Si446x_getUpsampledNRZIbits(&upsampler, layer0);
|
|
Si446x_writeFIFO(radio, localBuffer, c);
|
|
|
|
uint8_t lower = 0;
|
|
|
|
/* Request start of transmission. */
|
|
if(Si446x_transmit(radio,
|
|
rto->base_frequency,
|
|
rto->step_hz,
|
|
rto->channel,
|
|
rto->tx_power,
|
|
all,
|
|
rssi,
|
|
TIME_S2I(10))) {
|
|
|
|
/* Feed the FIFO while data remains to be sent. */
|
|
while((all - c) > 0) {
|
|
/* Get TX FIFO free count. */
|
|
uint8_t more = Si446x_getTXfreeFIFO(radio);
|
|
/* Update the FIFO free low water mark. */
|
|
lower = (more > lower) ? more : lower;
|
|
|
|
/* If there is more free than we need use remainder only. */
|
|
more = (more > (all - c)) ? (all - c) : more;
|
|
|
|
/* Load the FIFO. */
|
|
for(uint16_t i = 0; i < more; i++)
|
|
localBuffer[i] = Si446x_getUpsampledNRZIbits(&upsampler, layer0);
|
|
Si446x_writeFIFO(radio, localBuffer, more); // Write into FIFO
|
|
c += more;
|
|
|
|
/*
|
|
* Wait for a timeout event during up-sampled NRZI send.
|
|
* Time delay allows ~SAMPLES_PER_BAUD bytes to be consumed from FIFO.
|
|
* If no timeout event go back and load more data to FIFO.
|
|
*/
|
|
eventmask_t evt = chEvtWaitAnyTimeout(SI446X_EVT_TX_TIMEOUT,
|
|
chTimeUS2I(833 * 8));
|
|
if(evt) {
|
|
/* Force 446x out of TX state. */
|
|
Si446x_setReadyState(radio);
|
|
exit_msg = MSG_TIMEOUT;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Transmit start failed. */
|
|
TRACE_ERROR("SI > Transmit start failed");
|
|
exit_msg = MSG_ERROR;
|
|
} /* End transmit. */
|
|
chVTReset(&send_timer);
|
|
|
|
/*
|
|
* If nothing went wrong wait for TX to finish.
|
|
* Else don't wait.
|
|
*/
|
|
while(Si446x_getState(radio) == Si446x_STATE_TX && exit_msg == MSG_OK) {
|
|
/* TODO: Add an absolute timeout on this. */
|
|
/* Sleep for an AFSK byte time. */
|
|
chThdSleep(chTimeUS2I(833 * 8));
|
|
continue;
|
|
}
|
|
|
|
/* No CCA on subsequent packet sends. */
|
|
rssi = PKT_SI446X_NO_CCA_RSSI;
|
|
|
|
if(lower > (free / 2)) {
|
|
/*
|
|
* Warn when free level is more than 50% of FIFO size.
|
|
* This means the FIFO is not being filled fast enough.
|
|
*/
|
|
TRACE_WARN("SI > AFSK TX FIFO dropped below safe threshold %i", lower);
|
|
}
|
|
/* Get the next linked packet to send. */
|
|
packet_t np = pp->nextp;
|
|
if(exit_msg == MSG_OK) {
|
|
/* Send was OK. Release the just completed packet. */
|
|
pktReleaseBufferObject(pp);
|
|
} else {
|
|
/* Send failed so release any queue and terminate. */
|
|
pktReleaseBufferChain(pp);
|
|
np = NULL;
|
|
}
|
|
|
|
/* Process next packet. */
|
|
pp = np;
|
|
} while(pp != NULL);
|
|
|
|
/* Save status in case a callback requires it. */
|
|
rto->result = exit_msg;
|
|
|
|
/* Finished send so schedule thread memory and task object release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktUnlockRadioTransmit(radio);
|
|
|
|
/* Exit thread. */
|
|
chThdExit(exit_msg);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool Si446x_blocSendAFSK(radio_task_object_t *rt) {
|
|
|
|
thread_t *afsk_feeder_thd = NULL;
|
|
|
|
/* Create a send thread name which includes the sequence number. */
|
|
char tx_thd_name[16];
|
|
chsnprintf(tx_thd_name, sizeof(tx_thd_name),
|
|
"tx_afsk_%03i", rt->tx_seq_num);
|
|
|
|
afsk_feeder_thd = chThdCreateFromHeap(NULL,
|
|
THD_WORKING_AREA_SIZE(SI_AFSK_FIFO_MIN_FEEDER_WA_SIZE),
|
|
tx_thd_name,
|
|
NORMALPRIO - 10,
|
|
bloc_si_fifo_feeder_afsk,
|
|
rt);
|
|
|
|
if(afsk_feeder_thd == NULL) {
|
|
TRACE_ERROR("SI > Unable to create AFSK transmit thread");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/*
|
|
* AFSK Receiver
|
|
*/
|
|
|
|
void Si446x_stopDecoder(void) {
|
|
// TODO: Nothing yet here
|
|
}
|
|
|
|
/*
|
|
* 2FSK
|
|
*/
|
|
|
|
/*
|
|
* New 2FSK send thread using minimized buffer space and burst send.
|
|
*/
|
|
THD_FUNCTION(bloc_si_fifo_feeder_fsk, arg) {
|
|
radio_task_object_t *rto = arg;
|
|
|
|
radio_unit_t radio = rto->handler->radio;
|
|
|
|
packet_t pp = rto->packet_out;
|
|
|
|
chDbgAssert(pp != NULL, "no packet in radio task");
|
|
|
|
/* Check for MSG_RESET which means system has forced radio release. */
|
|
if(pktLockRadioTransmit(radio, TIME_INFINITE) == MSG_RESET) {
|
|
TRACE_ERROR("SI > 2FSK TX reset from radio acquisition");
|
|
/* Free packet object memory. */
|
|
pktReleaseBufferChain(pp);
|
|
|
|
/* Schedule thread and task object memory release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_RESET);
|
|
/* We never arrive here. */
|
|
}
|
|
|
|
/* Initialize radio before any commands as it may have been powered down. */
|
|
Si446x_conditional_init(radio);
|
|
|
|
/* Set 446x back to READY from RX (if active). */
|
|
Si446x_terminateReceive(radio);
|
|
|
|
/* Base frequency must be an absolute frequency in Hz. */
|
|
Si446x_setBandParameters(radio, rto->base_frequency, rto->step_hz);
|
|
|
|
/* Set parameters for 2FSK transmission. */
|
|
Si446x_setModem2FSK_TX(radio, rto->tx_speed);
|
|
|
|
/* Initialize variables for 2FSK encoder. */
|
|
|
|
virtual_timer_t send_timer;
|
|
|
|
chVTObjectInit(&send_timer);
|
|
|
|
tx_iterator_t iterator;
|
|
|
|
/* The exit message. */
|
|
msg_t exit_msg;
|
|
|
|
/*
|
|
* Use the specified CCA RSSI level.
|
|
* CCA will be set to blind send after first packet.
|
|
*/
|
|
radio_squelch_t rssi = rto->squelch;
|
|
|
|
do {
|
|
/*
|
|
* Set NRZI encoding format.
|
|
* Iterator object.
|
|
* Packet reference.
|
|
* Preamble length (HDLC flags)
|
|
* Postamble length (HDLC flags)
|
|
* Tail length (HDLC zeros)
|
|
* Scramble on
|
|
*/
|
|
pktStreamIteratorInit(&iterator, pp, 30, 10, 10, true);
|
|
|
|
/* Compute size of NRZI stream. */
|
|
uint16_t all = pktStreamEncodingIterator(&iterator, NULL, 0);
|
|
|
|
if(all == 0) {
|
|
/* Nothing encoded. Release packet send object. */
|
|
TRACE_ERROR("SI > 2FSK TX no NRZI data encoded");
|
|
|
|
/* Free packet object memory. */
|
|
pktReleaseBufferChain(pp);
|
|
|
|
rto->result = MSG_ERROR;
|
|
|
|
/* Schedule thread and task object memory release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktUnlockRadioTransmit(radio);
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_ERROR);
|
|
/* We never arrive here. */
|
|
}
|
|
/* Allocate buffer and perform NRZI encoding. */
|
|
uint8_t layer0[all];
|
|
|
|
pktStreamEncodingIterator(&iterator, layer0, all);
|
|
|
|
/* Reset TX FIFO in case some remnant unsent data is left there. */
|
|
const uint8_t reset_fifo[] = {0x15, 0x01};
|
|
Si446x_write(radio, reset_fifo, 2);
|
|
|
|
/* Get the FIFO buffer amount currently available. */
|
|
uint8_t free = Si446x_getTXfreeFIFO(radio);
|
|
|
|
/* Calculate initial FIFO fill. */
|
|
uint16_t c = (all > free) ? free : all;
|
|
|
|
/*
|
|
* Start/re-start transmission timeout timer for this packet.
|
|
* If the 446x gets locked up we'll exit TX and release packet object(s).
|
|
*/
|
|
chVTSet(&send_timer, TIME_S2I(10),
|
|
(vtfunc_t)Si446x_transmitTimeoutI, chThdGetSelfX());
|
|
|
|
/* The exit message if all goes well. */
|
|
exit_msg = MSG_OK;
|
|
|
|
uint8_t *bufp = layer0;
|
|
|
|
/* Initial FIFO load. */
|
|
Si446x_writeFIFO(radio, bufp, c);
|
|
bufp += c;
|
|
uint8_t lower = 0;
|
|
|
|
/* Request start of transmission. */
|
|
if(Si446x_transmit(radio,
|
|
rto->base_frequency,
|
|
rto->step_hz,
|
|
rto->channel,
|
|
rto->tx_power,
|
|
all,
|
|
rssi,
|
|
TIME_S2I(10))) {
|
|
/* Feed the FIFO while data remains to be sent. */
|
|
while((all - c) > 0) {
|
|
/* Get TX FIFO free count. */
|
|
uint8_t more = Si446x_getTXfreeFIFO(radio);
|
|
/* Update the FIFO free low water mark. */
|
|
lower = (more > lower) ? more : lower;
|
|
|
|
/* If there is more free than we need for send use remainder only. */
|
|
more = (more > (all - c)) ? (all - c) : more;
|
|
|
|
/* Load the FIFO. */
|
|
Si446x_writeFIFO(radio, bufp, more); // Write into FIFO
|
|
bufp += more;
|
|
c += more;
|
|
|
|
/*
|
|
* Wait for a timeout event during up-sampled NRZI send.
|
|
* Time delay allows ~10 bytes to be consumed from FIFO.
|
|
* If no timeout event go back and load more data to FIFO.
|
|
*/
|
|
eventmask_t evt = chEvtWaitAnyTimeout(SI446X_EVT_TX_TIMEOUT,
|
|
chTimeUS2I(104 * 8 * 10));
|
|
if(evt) {
|
|
/* Force 446x out of TX state. */
|
|
Si446x_setReadyState(radio);
|
|
exit_msg = MSG_TIMEOUT;
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Transmit start failed. */
|
|
TRACE_ERROR("SI > 2FSK transmit start failed");
|
|
exit_msg = MSG_ERROR;
|
|
}
|
|
chVTReset(&send_timer);
|
|
|
|
/*
|
|
* If nothing went wrong wait for TX to finish.
|
|
* Else don't wait.
|
|
*/
|
|
while(Si446x_getState(radio) == Si446x_STATE_TX && exit_msg == MSG_OK) {
|
|
/* TODO: Add an absolute timeout on this. */
|
|
/* Sleep for a 2FSK byte time. */
|
|
chThdSleep(chTimeUS2I(104 * 8 * 10));
|
|
continue;
|
|
}
|
|
|
|
/* No CCA on subsequent packet sends. */
|
|
rssi = PKT_SI446X_NO_CCA_RSSI;
|
|
|
|
if(lower > (free / 2)) {
|
|
/* Warn when free level is > 50% of FIFO size. */
|
|
TRACE_WARN("SI > AFSK TX FIFO dropped below safe threshold %i", lower);
|
|
}
|
|
/* Get the next linked packet to send. */
|
|
packet_t np = pp->nextp;
|
|
if(exit_msg == MSG_OK) {
|
|
|
|
/* Send was OK. Release the just completed packet. */
|
|
pktReleaseBufferObject(pp);
|
|
} else {
|
|
/* Send failed so release any queue and terminate. */
|
|
pktReleaseBufferChain(pp);
|
|
np = NULL;
|
|
}
|
|
|
|
/* Process next packet. */
|
|
pp = np;
|
|
} while(pp != NULL);
|
|
|
|
/* Save status in case a callback requires it. */
|
|
rto->result = exit_msg;
|
|
|
|
/* Finished send so schedule thread memory and task object release. */
|
|
pktLLDradioSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktUnlockRadioTransmit(radio);
|
|
|
|
/* Exit thread. */
|
|
chThdExit(exit_msg);
|
|
}
|
|
|
|
/*
|
|
* Return true on send successfully enqueued.
|
|
* Task object will be returned
|
|
* Return false on failure
|
|
*/
|
|
bool Si446x_blocSend2FSK(radio_task_object_t *rt) {
|
|
|
|
thread_t *fsk_feeder_thd = NULL;
|
|
|
|
/* Create a send thread name which includes the sequence number. */
|
|
char tx_thd_name[16];
|
|
chsnprintf(tx_thd_name, sizeof(tx_thd_name),
|
|
"tx_2fsk_%03i", rt->tx_seq_num);
|
|
|
|
fsk_feeder_thd = chThdCreateFromHeap(NULL,
|
|
THD_WORKING_AREA_SIZE(SI_FSK_FIFO_FEEDER_WA_SIZE),
|
|
tx_thd_name,
|
|
NORMALPRIO - 10,
|
|
bloc_si_fifo_feeder_fsk,
|
|
rt);
|
|
|
|
if(fsk_feeder_thd == NULL) {
|
|
TRACE_ERROR("SI > Unable to create FSK transmit thread");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Used by collector. At the moment it collects for PKT_RADIO_1 only.
|
|
* There should be an LLD API selecting the radio type via VMT etc.
|
|
*/
|
|
si446x_temp_t Si446x_getLastTemperature(const radio_unit_t radio) {
|
|
return Si446x_getData(radio)->lastTemp;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
ICUDriver *Si446x_attachPWM(const radio_unit_t radio) {
|
|
/* The RX_RAW_DATA input is routed to ICU timer channel.
|
|
* TODO: The STM32 Alternate mode should be in the radio config data.
|
|
* Then the ICU GPIO setting can be generalized.
|
|
*/
|
|
//(void)pktSetLineModeICU(Si446x_getConfig(radio)->gpio1);
|
|
pktSetGPIOlineMode(Si446x_getConfig(radio)->gpio1,
|
|
Si446x_getConfig(radio)->alt);
|
|
|
|
/*
|
|
* Set up GPIO port where the NIRQ from the radio is connected.
|
|
* The NIRQ line is configured in the radio to output the CCA condition.
|
|
*/
|
|
pktSetGPIOlineMode(Si446x_getConfig(radio)->nirq, PAL_MODE_INPUT_PULLUP);
|
|
|
|
/*
|
|
* Return the ICU this radio is assigned to.
|
|
* TODO: Check that the ICU is not already taken?
|
|
* This would only be a config error.
|
|
* Packet channel control enforces single use of decoder PWM for AFSK.
|
|
*/
|
|
return Si446x_getConfig(radio)->icu;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
bool Si446x_detachPWM(const radio_unit_t radio) {
|
|
(void)radio;
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
const ICUConfig *Si446x_enablePWMevents(const radio_unit_t radio,
|
|
palcallback_t cb) {
|
|
/* Set callback for squelch events. */
|
|
palSetLineCallback(Si446x_getConfig(radio)->nirq, cb,
|
|
Si446x_getConfig(radio)->icu);
|
|
|
|
/* Enabling events on both edges of CCA.*/
|
|
palEnableLineEvent(Si446x_getConfig(radio)->nirq,
|
|
PAL_EVENT_MODE_BOTH_EDGES);
|
|
|
|
return &Si446x_getConfig(radio)->cfg;
|
|
}
|
|
|
|
/**
|
|
*
|
|
*/
|
|
void Si446x_disablePWMevents(radio_unit_t radio) {
|
|
palDisableLineEvent(Si446x_getConfig(radio)->nirq);
|
|
}
|
|
/**
|
|
*
|
|
*/
|
|
uint8_t Si446x_readCCA(const radio_unit_t radio) {
|
|
return palReadLine(Si446x_getConfig(radio)->nirq);
|
|
}
|