kopia lustrzana https://github.com/DL7AD/pecanpico10
1365 wiersze
46 KiB
C
1365 wiersze
46 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"
|
|
#ifndef PKT_IS_TEST_PROJECT
|
|
#include "debug.h"
|
|
#endif
|
|
|
|
#ifndef PKT_IS_TEST_PROJECT
|
|
#include "radio.h"
|
|
#endif
|
|
|
|
#include "geofence.h"
|
|
|
|
// Si446x variables
|
|
static int16_t lastTemp = 0x7FFF;
|
|
|
|
|
|
/* =================================================================== SPI communication ==================================================================== */
|
|
|
|
static const SPIConfig ls_spicfg = {
|
|
.ssport = PAL_PORT(LINE_RADIO_CS),
|
|
.sspad = PAL_PAD(LINE_RADIO_CS),
|
|
.cr1 = SPI_CR1_MSTR
|
|
};
|
|
|
|
static bool Si446x_write(const uint8_t* txData, uint32_t len) {
|
|
// Transmit data by SPI
|
|
/* TODO: Add radio unit ID and get specific radio SPI driver. */
|
|
uint8_t null_spi[len];
|
|
|
|
/* Acquire bus and then start SPI. */
|
|
spiAcquireBus(PKT_RADIO_SPI);
|
|
spiStart(PKT_RADIO_SPI, &ls_spicfg);
|
|
|
|
/* Poll for CTS. */
|
|
uint8_t timeout = 100;
|
|
uint8_t rx_ready[] = {Si446x_READ_CMD_BUFF, 0x00};
|
|
do {
|
|
spiSelect(PKT_RADIO_SPI);
|
|
spiExchange(PKT_RADIO_SPI, 1, rx_ready, &rx_ready[1]);
|
|
spiUnselect(PKT_RADIO_SPI);
|
|
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(PKT_RADIO_SPI);
|
|
spiReleaseBus(PKT_RADIO_SPI);
|
|
return false;
|
|
}
|
|
|
|
/* Transfer data. Discard read back. */
|
|
spiSelect(PKT_RADIO_SPI);
|
|
spiExchange(PKT_RADIO_SPI, len, txData, null_spi);
|
|
spiUnselect(PKT_RADIO_SPI);
|
|
|
|
/* Stop SPI and relinquish bus. */
|
|
spiStop(PKT_RADIO_SPI);
|
|
spiReleaseBus(PKT_RADIO_SPI);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Read data from Si446x.
|
|
*/
|
|
static bool Si446x_read(const uint8_t* txData, uint32_t txlen,
|
|
uint8_t* rxData, uint32_t rxlen) {
|
|
// Transmit data by SPI
|
|
/* TODO: Add radio unit ID and get SPI configuration accordingly. */
|
|
/* Command readback to be discarded here. */
|
|
uint8_t null_spi[txlen];
|
|
|
|
/* Acquire bus and then start SPI. */
|
|
spiAcquireBus(PKT_RADIO_SPI);
|
|
spiStart(PKT_RADIO_SPI, &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.
|
|
*/
|
|
uint8_t timeout = 100;
|
|
uint8_t rx_ready[] = {Si446x_READ_CMD_BUFF, 0x00};
|
|
do {
|
|
spiSelect(PKT_RADIO_SPI);
|
|
spiExchange(PKT_RADIO_SPI, 1, rx_ready, &rx_ready[1]);
|
|
spiUnselect(PKT_RADIO_SPI);
|
|
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 (de-asserts select as well) and relinquish bus. */
|
|
spiStop(PKT_RADIO_SPI);
|
|
spiReleaseBus(PKT_RADIO_SPI);
|
|
return false;
|
|
}
|
|
|
|
/*
|
|
* Now write command and data. Discard the read back.
|
|
*/
|
|
spiSelect(PKT_RADIO_SPI);
|
|
spiExchange(PKT_RADIO_SPI, txlen, txData, null_spi);
|
|
|
|
/*
|
|
* 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 = 100;
|
|
do {
|
|
spiUnselect(PKT_RADIO_SPI);
|
|
if(timeout != 100)
|
|
chThdSleep(TIME_MS2I(1));
|
|
spiSelect(PKT_RADIO_SPI);
|
|
spiExchange(PKT_RADIO_SPI, rxlen, rx_ready, rxData);
|
|
} while(rxData[1] != Si446x_COMMAND_CTS && timeout--);
|
|
|
|
/* Stop SPI (de-asserts select as well) and relinquish bus. */
|
|
spiStop(PKT_RADIO_SPI);
|
|
spiReleaseBus(PKT_RADIO_SPI);
|
|
|
|
if(!timeout) {
|
|
TRACE_ERROR("SI > CTS not received");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
static void Si446x_setProperty8(uint16_t reg, uint8_t val) {
|
|
uint8_t msg[] = {0x11, (reg >> 8) & 0xFF, 0x01, reg & 0xFF, val};
|
|
Si446x_write(msg, 5);
|
|
}
|
|
|
|
static void Si446x_setProperty16(uint16_t reg, uint8_t val1, uint8_t val2) {
|
|
uint8_t msg[] = {0x11, (reg >> 8) & 0xFF, 0x02, reg & 0xFF, val1, val2};
|
|
Si446x_write(msg, 6);
|
|
}
|
|
|
|
static void Si446x_setProperty24(uint16_t reg, uint8_t val1, uint8_t val2, uint8_t val3) {
|
|
uint8_t msg[] = {0x11, (reg >> 8) & 0xFF, 0x03, reg & 0xFF, val1, val2, val3};
|
|
Si446x_write(msg, 7);
|
|
}
|
|
|
|
static void Si446x_setProperty32(uint16_t reg, uint8_t val1, uint8_t val2, uint8_t val3, uint8_t val4) {
|
|
uint8_t msg[] = {0x11, (reg >> 8) & 0xFF, 0x04, reg & 0xFF, val1, val2, val3, val4};
|
|
Si446x_write(msg, 8);
|
|
}
|
|
|
|
/**
|
|
* Initializes Si446x transceiver chip. Adjusts the frequency which is shifted by variable
|
|
* oscillator voltage.
|
|
* @param mv Oscillator voltage in mv
|
|
*/
|
|
static void Si446x_init(const radio_unit_t radio) {
|
|
|
|
TRACE_INFO("SI > Init radio");
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
pktPowerUpRadio(radio);
|
|
|
|
// Power up (send oscillator type)
|
|
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;
|
|
const uint8_t init_command[] = {0x02, 0x01, (Si446x_CLK_TCXO_EN & 0x1), x3, x2, x1, x0};
|
|
Si446x_write(init_command, 7);
|
|
chThdSleep(TIME_MS2I(25));
|
|
|
|
// Set transceiver GPIOs
|
|
uint8_t gpio_pin_cfg_command[] = {
|
|
0x13, // 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(gpio_pin_cfg_command, 8);
|
|
chThdSleep(TIME_MS2I(25));
|
|
|
|
#if !Si446x_CLK_TCXO_EN
|
|
Si446x_setProperty8(Si446x_GLOBAL_XO_TUNE, 0x00);
|
|
#endif
|
|
|
|
Si446x_setProperty8(Si446x_FRR_CTL_A_MODE, 0x00);
|
|
Si446x_setProperty8(Si446x_FRR_CTL_B_MODE, 0x00);
|
|
Si446x_setProperty8(Si446x_FRR_CTL_C_MODE, 0x00);
|
|
Si446x_setProperty8(Si446x_FRR_CTL_D_MODE, 0x00);
|
|
Si446x_setProperty8(Si446x_INT_CTL_ENABLE, 0x00);
|
|
/* Set combined FIFO mode = 0x70. */
|
|
//Si446x_setProperty8(Si446x_GLOBAL_CONFIG, 0x60);
|
|
Si446x_setProperty8(Si446x_GLOBAL_CONFIG, 0x70);
|
|
|
|
/* Clear FIFO. */
|
|
const uint8_t reset_fifo[] = {0x15, 0x01};
|
|
Si446x_write(reset_fifo, 2);
|
|
/* 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 in here for selected base band frequency.
|
|
*/
|
|
Si446x_setProperty8(Si446x_PREAMBLE_TX_LENGTH, 0x00);
|
|
/* TODO: Use PREAMBLE_CONFIG_NSTD, etc. to send flags?
|
|
* To do this with AFSK up-sampling requires a preamble pattern of 88 bits.
|
|
* The 446x only has up to 32 pattern bits.
|
|
* Why 88 bits? Due to the oversampling used to create AFSK at 13.2ksps.
|
|
* Each HDLC bit takes 11 TX bit times.
|
|
*
|
|
* The alternative is to use TX_FIELDS.
|
|
* Send preamble (HDLC flags) using FIELD_1 in a loop with fixed data 0x7E.
|
|
* Field length can be 4096 bytes so up to 372 flags could be sent.
|
|
* The flag bit stream uses 11 bytes per flag.
|
|
* Using 200 flags would be 11 * 200 = 2200 bytes (17,600 stream bits).
|
|
* Set FIELD_1 as 2,200 bytes and feed 200 x the bit pattern to the FIFO.
|
|
* The transition to FIELD_2 is handled in the 446x packet handler.
|
|
* Then FIELD_2 FIFO data is fed from the layer0 (bit stream) data buffer.
|
|
*/
|
|
Si446x_setProperty8(Si446x_SYNC_CONFIG, 0x80);
|
|
|
|
Si446x_setProperty8(Si446x_GLOBAL_CLK_CFG, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_RSSI_CONTROL, 0x00);
|
|
/* TODO: Don't need this setting? */
|
|
Si446x_setProperty8(Si446x_PREAMBLE_CONFIG_STD_1, 0x14);
|
|
Si446x_setProperty8(Si446x_PKT_CONFIG1, 0x41);
|
|
Si446x_setProperty8(Si446x_MODEM_MAP_CONTROL, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_DSM_CTRL, 0x07);
|
|
Si446x_setProperty8(Si446x_MODEM_CLKGEN_BAND, 0x0D);
|
|
|
|
Si446x_setProperty24(Si446x_MODEM_FREQ_DEV, 0x00, 0x00, 0x79);
|
|
Si446x_setProperty8(Si446x_MODEM_TX_RAMP_DELAY, 0x01);
|
|
Si446x_setProperty8(Si446x_PA_TC, 0x3D);
|
|
Si446x_setProperty8(Si446x_FREQ_CONTROL_INTE, 0x41);
|
|
Si446x_setProperty24(Si446x_FREQ_CONTROL_FRAC, 0x0B, 0xB1, 0x3B);
|
|
Si446x_setProperty16(Si446x_FREQ_CONTROL_CHANNEL_STEP_SIZE, 0x0B, 0xD1);
|
|
Si446x_setProperty8(Si446x_FREQ_CONTROL_W_SIZE, 0x20);
|
|
Si446x_setProperty8(Si446x_FREQ_CONTROL_VCOCNT_RX_ADJ, 0xFA);
|
|
Si446x_setProperty8(Si446x_MODEM_MDM_CTRL, 0x80);
|
|
Si446x_setProperty8(Si446x_MODEM_IF_CONTROL, 0x08);
|
|
Si446x_setProperty24(Si446x_MODEM_IF_FREQ, 0x02, 0x80, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_DECIMATION_CFG1, 0x70);
|
|
Si446x_setProperty8(Si446x_MODEM_DECIMATION_CFG0, 0x10);
|
|
Si446x_setProperty16(Si446x_MODEM_BCR_OSR, 0x01, 0xC3);
|
|
Si446x_setProperty24(Si446x_MODEM_BCR_NCO_OFFSET, 0x01, 0x22, 0x60);
|
|
Si446x_setProperty16(Si446x_MODEM_BCR_GAIN, 0x00, 0x91);
|
|
Si446x_setProperty8(Si446x_MODEM_BCR_GEAR, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_BCR_MISC1, 0xC2);
|
|
Si446x_setProperty8(Si446x_MODEM_AFC_GEAR, 0x54);
|
|
Si446x_setProperty8(Si446x_MODEM_AFC_WAIT, 0x36);
|
|
Si446x_setProperty16(Si446x_MODEM_AFC_GAIN, 0x80, 0xAB);
|
|
Si446x_setProperty16(Si446x_MODEM_AFC_LIMITER, 0x02, 0x50);
|
|
Si446x_setProperty8(Si446x_MODEM_AFC_MISC, 0x80);
|
|
Si446x_setProperty8(Si446x_MODEM_AGC_CONTROL, 0xE2);
|
|
Si446x_setProperty8(Si446x_MODEM_AGC_WINDOW_SIZE, 0x11);
|
|
Si446x_setProperty8(Si446x_MODEM_AGC_RFPD_DECAY, 0x63);
|
|
Si446x_setProperty8(Si446x_MODEM_AGC_IFPD_DECAY, 0x63);
|
|
Si446x_setProperty8(Si446x_MODEM_FSK4_GAIN1, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_FSK4_GAIN0, 0x02);
|
|
Si446x_setProperty16(Si446x_MODEM_FSK4_TH, 0x35, 0x55);
|
|
Si446x_setProperty8(Si446x_MODEM_FSK4_MAP, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_OOK_PDTC, 0x2A);
|
|
Si446x_setProperty8(Si446x_MODEM_OOK_CNT1, 0x85);
|
|
Si446x_setProperty8(Si446x_MODEM_OOK_MISC, 0x23);
|
|
Si446x_setProperty8(Si446x_MODEM_RAW_SEARCH, 0xD6);
|
|
Si446x_setProperty8(Si446x_MODEM_RAW_CONTROL, 0x8F);
|
|
Si446x_setProperty16(Si446x_MODEM_RAW_EYE, 0x00, 0x3B);
|
|
Si446x_setProperty8(Si446x_MODEM_ANT_DIV_MODE, 0x01);
|
|
Si446x_setProperty8(Si446x_MODEM_ANT_DIV_CONTROL, 0x80);
|
|
Si446x_setProperty8(Si446x_MODEM_RSSI_COMP, 0x40);
|
|
|
|
handler->radio_init = true;
|
|
}
|
|
|
|
void Si446x_conditional_init(const radio_unit_t radio) {
|
|
// Initialize radio
|
|
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
if(!handler->radio_init)
|
|
Si446x_init(radio);
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
bool Si446x_setBandParameters(const radio_unit_t radio,
|
|
radio_freq_t freq,
|
|
channel_hz_t step) {
|
|
|
|
if(freq == FREQ_APRS_DYNAMIC) {
|
|
freq = getAPRSRegionFrequency(); // Get transmission frequency by geofencing
|
|
/* If using geofence ignore channel and step for now. */
|
|
step = 0;
|
|
}
|
|
/* 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;}
|
|
|
|
Si446x_conditional_init(radio);
|
|
|
|
/* Set the band parameter. */
|
|
uint32_t sy_sel = 8;
|
|
uint8_t set_band_property_command[] = {0x11, 0x20, 0x01, 0x51, (band + sy_sel)};
|
|
Si446x_write(set_band_property_command, 5);
|
|
|
|
/* 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[] = {0x11, 0x40, 0x04, 0x00, n, m2, m1, m0, c1, c0};
|
|
Si446x_write(set_frequency_property_command, 10);
|
|
|
|
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[] = {0x11, 0x20, 0x03, 0x0a, x2, x1, x0};
|
|
Si446x_write(set_deviation, 7);
|
|
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(radio_pwr_t level)
|
|
{
|
|
// Set the Power
|
|
uint8_t set_pa_pwr_lvl_property_command[] = {0x11, 0x22, 0x01, 0x01, level};
|
|
Si446x_write(set_pa_pwr_lvl_property_command, 5);
|
|
}
|
|
|
|
|
|
|
|
/* =========================================================== Radio specific modulation settings =========================================================== */
|
|
|
|
static void Si446x_setModemAFSK_TX(const radio_unit_t radio) {
|
|
/* TODO: Hardware mapping. */
|
|
(void)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(Si446x_MODEM_TX_NCO_MODE, f3, f2, f1, f0);
|
|
|
|
// Setup the NCO data rate for APRS
|
|
Si446x_setProperty24(Si446x_MODEM_DATA_RATE, 0x00, 0x33, 0x90);
|
|
|
|
// Use upsampled AFSK from FIFO (PH)
|
|
Si446x_setProperty8(Si446x_MODEM_MOD_TYPE, 0x02);
|
|
|
|
// 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(msg, 5);
|
|
}
|
|
}
|
|
|
|
static void Si446x_setModemAFSK_RX(const radio_unit_t radio) {
|
|
/* TODO: Hardware mapping. */
|
|
(void)radio;
|
|
// Setup the NCO modulo and oversampling mode
|
|
/* uint32_t s = Si446x_CCLK;
|
|
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(Si446x_MODEM_TX_NCO_MODE, f3, f2, f1, f0);*/
|
|
|
|
// Setup the NCO data rate for APRS
|
|
//Si446x_setProperty24(Si446x_MODEM_DATA_RATE, 0x04, 0x07, 0x40);
|
|
|
|
// Use 2FSK in DIRECT_MODE
|
|
Si446x_setProperty8(Si446x_MODEM_MOD_TYPE, 0x0A);
|
|
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE13_7_0, 0xFF);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE12_7_0, 0xC4);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE11_7_0, 0x30);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE10_7_0, 0x7F);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE9_7_0, 0x5F);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE8_7_0, 0xB5);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE7_7_0, 0xB8);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE6_7_0, 0xDE);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE5_7_0, 0x05);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE4_7_0, 0x17);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE3_7_0, 0x16);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE2_7_0, 0x0C);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE1_7_0, 0x03);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COE0_7_0, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COEM0, 0x15);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COEM1, 0xFF);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COEM2, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX1_CHFLT_COEM3, 0x00);
|
|
|
|
/* Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE13_7_0, 0xFF);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE12_7_0, 0xC4);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE11_7_0, 0x30);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE10_7_0, 0x7F);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE9_7_0, 0xF5);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE8_7_0, 0xB5);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE7_7_0, 0xB8);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE6_7_0, 0xDE);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE5_7_0, 0x05);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE4_7_0, 0x17);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE3_7_0, 0x16);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE2_7_0, 0x0C);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE1_7_0, 0x03);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COE0_7_0, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COEM0, 0x15);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COEM1, 0xFF);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COEM2, 0x00);
|
|
Si446x_setProperty8(Si446x_MODEM_CHFLT_RX2_CHFLT_COEM3, 0x00);*/
|
|
}
|
|
|
|
static void Si446x_setModem2FSK_TX(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(Si446x_MODEM_TX_NCO_MODE, f3, f2, f1, f0);
|
|
|
|
// Setup the NCO data rate for 2GFSK
|
|
Si446x_setProperty24(Si446x_MODEM_DATA_RATE, (uint8_t)(speed >> 16), (uint8_t)(speed >> 8), (uint8_t)speed);
|
|
|
|
// Use 2GFSK from FIFO (PH)
|
|
Si446x_setProperty8(Si446x_MODEM_MOD_TYPE, 0x03);
|
|
|
|
// 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(msg, sizeof(msg));
|
|
}
|
|
}
|
|
|
|
|
|
/* ====================================================================== Radio Settings ====================================================================== */
|
|
|
|
static uint8_t __attribute__((unused)) Si446x_getChannel(void) {
|
|
/* TODO: add hardware mapping. */
|
|
const uint8_t state_info[] = {Si446x_REQUEST_DEVICE_STATE};
|
|
uint8_t rxData[4];
|
|
Si446x_read(state_info, sizeof(state_info), rxData, sizeof(rxData));
|
|
return rxData[3];
|
|
}
|
|
|
|
/* ======================================================================= Radio FIFO ======================================================================= */
|
|
|
|
static void Si446x_writeFIFO(uint8_t *msg, uint8_t size) {
|
|
/* TODO: add hardware mapping. */
|
|
uint8_t write_fifo[size+1];
|
|
write_fifo[0] = Si446x_WRITE_TX_FIFO;
|
|
memcpy(&write_fifo[1], msg, size);
|
|
Si446x_write(write_fifo, size+1);
|
|
}
|
|
|
|
static uint8_t Si446x_getTXfreeFIFO(void) {
|
|
/* TODO: add hardware mapping. */
|
|
const uint8_t fifo_info[] = {Si446x_FIFO_INFO, 0x00};
|
|
uint8_t rxData[4];
|
|
Si446x_read(fifo_info, sizeof(fifo_info), rxData, sizeof(rxData));
|
|
return rxData[3];
|
|
}
|
|
|
|
/* ====================================================================== Radio States ====================================================================== */
|
|
|
|
radio_signal_t Si446x_getCurrentRSSI(const radio_unit_t radio) {
|
|
/* TODO: add hardware mapping. */
|
|
(void)radio;
|
|
/* Get status. Leave any pending interrupts intact. */
|
|
const uint8_t status_info[] = {Si446x_GET_MODEM_STATUS, 0xEF};
|
|
uint8_t rxData[11];
|
|
Si446x_read(status_info, sizeof(status_info), rxData, sizeof(rxData));
|
|
return rxData[4];
|
|
}
|
|
|
|
static uint8_t Si446x_getState(const radio_unit_t radio) {
|
|
/* TODO: add hardware mapping. */
|
|
(void)radio;
|
|
const uint8_t state_info[] = {Si446x_REQUEST_DEVICE_STATE};
|
|
uint8_t rxData[4];
|
|
Si446x_read(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){
|
|
/* TODO: add hardware mapping. */
|
|
(void)radio;
|
|
uint8_t change_state_command[] = {0x31, chan,
|
|
(Si446x_STATE_READY << 4),
|
|
(size >> 8) & 0x1F, size & 0xFF};
|
|
Si446x_write(change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
static void Si446x_setReadyState(const radio_unit_t radio) {
|
|
/* TODO: add hardware mapping. */
|
|
(void)radio;
|
|
const uint8_t change_state_command[] = {0x34, 0x03};
|
|
Si446x_write(change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
static void Si446x_setRXState(const radio_unit_t radio, uint8_t chan){
|
|
/* TODO: add hardware mapping. */
|
|
(void)radio;
|
|
const uint8_t change_state_command[] = {0x32, chan, 0x00, 0x00,
|
|
0x00, 0x00, 0x08, 0x08};
|
|
Si446x_write(change_state_command, sizeof(change_state_command));
|
|
}
|
|
|
|
|
|
void Si446x_shutdown(radio_unit_t radio) {
|
|
TRACE_INFO("SI > Shutdown radio %i", radio);
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
pktPowerDownRadio(radio);
|
|
handler->radio_init = false;
|
|
}
|
|
|
|
/* ====================================================================== 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(radio_unit_t radio, uint8_t ms) {
|
|
/* TODO: Hardware mapping of radio. */
|
|
(void)radio;
|
|
uint16_t cca = 0;
|
|
/* Tick per millisecond. */
|
|
//sysinterval_t uslice = TIME_MS2I(1);
|
|
/* Measure sliced CCA instances in period. */
|
|
for(uint16_t i = 0; i < (ms * TIME_MS2I(1)); i++) {
|
|
cca += Si446x_getCCA();
|
|
/* Sleep one tick. */
|
|
chThdSleep(1);
|
|
}
|
|
/* Return result. */
|
|
return cca > ms;
|
|
}
|
|
|
|
/*
|
|
* Wait for a clear time slot and initiate packet transmission.
|
|
*/
|
|
static bool Si446x_transmit(radio_unit_t radio,
|
|
radio_freq_t freq,
|
|
channel_hz_t step,
|
|
radio_ch_t chan,
|
|
radio_pwr_t power,
|
|
uint16_t size,
|
|
radio_squelch_t rssi,
|
|
sysinterval_t cca_timeout) {
|
|
|
|
radio_freq_t op_freq = pktComputeOperatingFrequency(radio, freq,
|
|
step, chan, RADIO_TX);
|
|
|
|
if(op_freq == FREQ_RADIO_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));
|
|
}
|
|
|
|
/* Set band parameters. */
|
|
Si446x_setBandParameters(radio, freq, step);
|
|
|
|
/* Check for blind send request. */
|
|
if(rssi != PKT_SI446X_NO_CCA_RSSI) {
|
|
Si446x_setProperty8(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(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(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_RADIO_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;
|
|
}
|
|
|
|
// Initialize radio
|
|
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);
|
|
Si446x_setProperty8(Si446x_MODEM_RSSI_THRESH, rssi);
|
|
|
|
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 if it was paused for TX.
|
|
* return true if RX was enabled and/or resumed OK.
|
|
* return false if RX was not enabled.
|
|
*/
|
|
bool Si4464_resumeReceive(radio_unit_t radio,
|
|
radio_freq_t rx_frequency,
|
|
channel_hz_t rx_step,
|
|
radio_ch_t rx_chan,
|
|
radio_squelch_t rx_rssi,
|
|
mod_t rx_mod) {
|
|
(void)radio;
|
|
bool ret = true;
|
|
|
|
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));
|
|
|
|
/* Resume reception. */
|
|
Si446x_setBandParameters(radio, rx_frequency, rx_step);
|
|
ret = Si446x_receiveNoLock(radio, rx_frequency, rx_step,
|
|
rx_chan, rx_rssi, rx_mod);
|
|
return ret;
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void Si446x_disableReceive(radio_unit_t radio) {
|
|
/* FIXME: */
|
|
if(Si446x_getState(radio) == Si446x_STATE_RX) {
|
|
//rx_frequency = 0;
|
|
Si446x_shutdown(radio);
|
|
}
|
|
}
|
|
|
|
/*
|
|
*
|
|
*/
|
|
void Si446x_pauseReceive(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);
|
|
}
|
|
}
|
|
|
|
/* ==================================================================== AFSK Transmitter ==================================================================== */
|
|
|
|
/*
|
|
*
|
|
*/
|
|
static uint8_t Si446x_getUpsampledNRZIbits(up_iterator_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;
|
|
}
|
|
|
|
#define SI446X_EVT_TX_TIMEOUT EVENT_MASK(0)
|
|
|
|
static void Si446x_transmitTimeoutI(thread_t *tp) {
|
|
/* The tell the thread to terminate. */
|
|
chEvtSignal(tp, SI446X_EVT_TX_TIMEOUT);
|
|
}
|
|
|
|
/*
|
|
* 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(pktAcquireRadio(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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_RESET);
|
|
/* We never arrive here. */
|
|
}
|
|
|
|
/* Initialize radio. */
|
|
Si446x_conditional_init(radio);
|
|
|
|
Si446x_setBandParameters(radio, rto->base_frequency,
|
|
rto->step_hz);
|
|
|
|
/* Set 446x back to READY. */
|
|
Si446x_pauseReceive(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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktReleaseRadio(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(reset_fifo, 2);
|
|
|
|
up_iterator_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();
|
|
|
|
/* 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(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();
|
|
/* 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(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;
|
|
}
|
|
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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktReleaseRadio(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. */
|
|
chsnprintf(rt->tx_thd_name, sizeof(rt->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),
|
|
rt->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(pktAcquireRadio(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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Exit thread. */
|
|
chThdExit(MSG_RESET);
|
|
/* We never arrive here. */
|
|
}
|
|
|
|
/* Initialize radio. */
|
|
Si446x_conditional_init(radio);
|
|
|
|
/* Set 446x back to READY from RX (if active). */
|
|
Si446x_pauseReceive(radio);
|
|
|
|
Si446x_setBandParameters(radio, rto->base_frequency, rto->step_hz);
|
|
|
|
/* Set parameters for 2FSK transmission. */
|
|
Si446x_setModem2FSK_TX(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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktReleaseRadio(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(reset_fifo, 2);
|
|
|
|
/* Get the FIFO buffer amount currently available. */
|
|
uint8_t free = Si446x_getTXfreeFIFO();
|
|
|
|
/* 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.
|
|
*/
|
|
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(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();
|
|
/* 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(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. */
|
|
pktScheduleSendComplete(rto, chThdGetSelfX());
|
|
|
|
/* Unlock radio. */
|
|
pktReleaseRadio(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;
|
|
|
|
/* TODO: Don't need to put the thread name in the packet. Just use local var. */
|
|
/* Create a send thread name which includes the sequence number. */
|
|
chsnprintf(rt->tx_thd_name, sizeof(rt->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),
|
|
rt->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;
|
|
}
|
|
|
|
/* ========================================================================== Misc ========================================================================== */
|
|
|
|
static int16_t Si446x_getTemperature(const radio_unit_t radio) {
|
|
/* TODO: Add hardware selection. */
|
|
(void)radio;
|
|
const uint8_t txData[] = {Si446x_GET_ADC_READING, 0x10};
|
|
uint8_t rxData[8];
|
|
Si446x_read(txData, sizeof(txData), rxData, sizeof(rxData));
|
|
uint16_t adc = rxData[7] | ((rxData[6] & 0x7) << 8);
|
|
return (89900 * adc) / 4096 - 29300;
|
|
}
|
|
|
|
/* TODO: Abstract this by radio ID. */
|
|
int16_t Si446x_getLastTemperature(const radio_unit_t radio) {
|
|
if(lastTemp == 0x7FFF) { // Temperature was never measured => measure it now
|
|
packet_svc_t *handler = pktGetServiceObject(radio);
|
|
|
|
chDbgAssert(handler != NULL, "invalid radio ID");
|
|
|
|
if(handler->radio_init) {
|
|
pktAcquireRadio(radio, TIME_INFINITE);
|
|
// Temperature readout
|
|
lastTemp = Si446x_getTemperature(radio);
|
|
TRACE_INFO("SI > Transmitter temperature %d degC", lastTemp/100);
|
|
pktReleaseRadio(radio);
|
|
} else {
|
|
TRACE_INFO("SI > Transmitter temperature not available");
|
|
return 0;
|
|
}
|
|
}
|
|
return lastTemp;
|
|
}
|
|
|
|
//#endif
|