// // Si446x: Initializes and configures a Si446x transceiver over SPI // #include "stm32f0xx_hal.h" #include "si446x.h" #include "buoy.h" #include "adc.h" #include "vhf.h" #include "system/spi.h" #include "config.h" #include "error.h" #include "gpio.h" #include "sysclk.h" // Private variables static SPI_HandleTypeDef hspi1; SPI_HandleTypeDef* hspi2; static DMA_HandleTypeDef hspi1_dma_tx; static uint8_t si446x_cw_status = 0; static uint8_t current_channel = EFS_DEFAULT_VHF_CHANNEL; // RF channel lookup table static uint32_t rf_channels[] = { 162250000, 163000000, 163750000, 164500000, 165250000, 166000000, 166750000, 167500000, 168250000, 169000000, 169750000, 170500000, 171250000, 172000000, 172750000, 173500000, 162625000, 163375000, 164125000, 164875000, 165625000, 166375000, 167125000, 167875000, 168625000, 169375000, 170125000, 170875000, 171625000, 172375000, 173125000, 136000000, 136375000, 136750000, 137125000, 137500000, 137875000, 138250000, 138625000, 139000000, 139375000, 139750000, 140125000, 140500000, 140875000, 141250000, 141625000, 142000000, 142375000, 142750000, 143125000, 143500000, 143875000, 144250000, 144625000, 145000000, 145375000, 145750000, 146125000, 146500000, 146875000, 147250000, 147625000, 148000000, 148375000, 148750000, 149125000, 149500000, 149875000, 150250000, 150625000, 151000000, 151375000, 151750000, 152125000, 152500000, 152875000, 153250000, 153625000, 154000000, 154375000, 154750000, 155125000, 155500000, 155875000, 156250000, 156625000, 157000000, 157375000, 157750000, 158125000, 158500000, 158875000, 159250000, 159625000, 160000000, 160375000, 160750000, 161125000 }; // Private function prototypes static void __set_poweramp_dac(uint16_t power_word); static void __init_spi1(void); // Initialize Si446x in 2FSK transmit mode void si446x_init(void) { GPIO_InitTypeDef GPIO_InitStruct; // GPIO: VHF chip select GPIO_InitStruct.Pin = SI446x_CS_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_LOW; HAL_GPIO_Init(SI446x_CS_PORT, &GPIO_InitStruct); SI446x_DESELECT; // GPIO: VHF radio shutdown control GPIO_InitStruct.Pin = SI446x_SHUTDOWN_PIN; GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; HAL_GPIO_Init(SI446x_SHUTDOWN_PORT, &GPIO_InitStruct); // Initialize modulator pseudo-SPI port __init_spi1(); // Perform PoR (takes 20ms) and turn device on si446x_reset(); // Save SPI port reference hspi2 = spi2_get(); // Divide SI446x_VCXO_FREQ into its bytes; MSB first uint16_t x3 = SI446x_VCXO_FREQ / 0x1000000; uint16_t x2 = (SI446x_VCXO_FREQ - x3 * 0x1000000) / 0x10000; uint16_t x1 = (SI446x_VCXO_FREQ - x3 * 0x1000000 - x2 * 0x10000) / 0x100; uint16_t x0 = (SI446x_VCXO_FREQ - x3 * 0x1000000 - x2 * 0x10000 - x1 * 0x100); // Power up radio module boot xtal _XO_frequency_ //TCXO // const char init_command[] = {SI446x_CMD_POWER_UP, 0x01, 0x01, x3, x2, x1, x0}; // Crystal uint8_t init_command[] = {SI446x_CMD_POWER_UP, 0x01, 0x00, x3, x2, x1, x0}; si446x_sendcmd(7, init_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); // Change to SPI Ready state EMZ added for re-init, might help on startup uint8_t change_state_commanda[] = {SI446x_CMD_CHANGE_STATE, 0x02}; // Change to spi ready si446x_sendcmd(2, change_state_commanda, SI446x_CHECK_ACK); sysclk_dogdelay(10); // Radio ready: clear all pending interrupts and get the interrupt status back uint8_t get_int_status_command[] = {SI446x_CMD_GET_INT_STATUS, 0x00, 0x00, 0x00}; si446x_sendcmd(4, get_int_status_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); // GPIO config: Set all GPIOs LOW; Link NIRQ to CTS; Link SDO to MISO; Max drive strength uint8_t gpio_pin_cfg_command[] = { SI446x_CMD_GPIO_PIN_CFG, // Command SI446x_GPIO_LOW, // GPIO0 - Power amp control DAC AD5611 Sync Pin SI446x_GPIO_INPUT, // GPIO1 - Input for modulation SI446x_GPIO_LOW, // GPIO2 - Blue LED SI446x_GPIO_NOCHANGE, // GPIO3 - Unused SI446x_GPIO_NOCHANGE, // NIRQ SI446x_GPIO_NOCHANGE, // 0x11, // SDO SI446x_GPIO_NOCHANGE, // Gencfg }; si446x_sendcmd(8, gpio_pin_cfg_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); uint8_t tune_xo_cmd[] = { SI446x_CMD_SET_PROPERTY, SI446x_XO_TUNE_REGISTER_GROUP, 0x1, // num data SI446x_XO_TUNE_REGISTER_PROP, SI446x_CRYSTAL_LOAD_TUNING, }; si446x_sendcmd(5, tune_xo_cmd, SI446x_CHECK_ACK); sysclk_dogdelay(10); // Tune to frequency specified si446x_setchannel(buoy_getconfig()->values.vhf_channel); sysclk_dogdelay(10); // Set to 2FSK mode uint8_t modemconfig = SI446x_MOD_TYPE_2FSK | SI446x_MOD_TYPE_SOURCE_DIRECTMODE | SI446x_MOD_TYPE_DIRECT_ASYNCH | SI446x_MOD_TYPE_DIRECT_SOURCE_GPIO1; //SI446x_MOD_TYPE_SOURCE_PACKETHANDLER uint8_t set_modem_mod_type_command[] = { SI446x_CMD_SET_PROPERTY, SI446x_MOD_TYPE_REGISTER_GROUP, 0x01, // num data SI446x_MOD_TYPE_REGISTER_PROP, modemconfig }; si446x_sendcmd(5, set_modem_mod_type_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); #ifdef SI446X_CONFIG_FIFO // Disable insertion of preamble into FIFO message uint8_t preamble_prop_command[] = { SI446x_CMD_SET_PROPERTY, SI446x_PREAMBLE_TX_LENGTH_GROUP, 0x01, // num data SI446x_PREAMBLE_TX_LENGTH_PROP, 0, // no preamble bytes }; si446x_sendcmd(5, preamble_prop_command, SI446x_CHECK_ACK); sysclk_dogdelay(50); // Disable sync uint8_t sync_prop_command[] = { SI446x_CMD_SET_PROPERTY, SI446x_SYNC_LENGTH_GROUP, 0x01, // num data SI446x_SYNC_LENGTH_PROP, 0b10000000, // no preamble bytes }; si446x_sendcmd(5, sync_prop_command, SI446x_CHECK_ACK); sysclk_dogdelay(50); // Disable manchester? uint8_t manch_prop_command[] = { SI446x_CMD_SET_PROPERTY, SI446x_MODEM_MAP_GROUP, 0x01, // num data SI446x_MODEM_MAP_PROP, 0x00, // no manchester }; si446x_sendcmd(5, manch_prop_command, SI446x_CHECK_ACK); sysclk_dogdelay(50); // Set global config (big FIFO, bond both 128byte registers) uint8_t set_fifo_big[] = { SI446x_CMD_SET_PROPERTY, 0x00, // group 0x01, // numprops 0x03, // startproperty 0b00010000 // data: set FIFO to 128-byte shared buffer }; si446x_sendcmd(5, set_fifo_big, SI446x_CHECK_ACK); sysclk_dogdelay(50); // Reset FIFO uint8_t fifo_reset[] = { SI446x_CMD_FIFO_INFO, // set prop 0b11 // Reset rx and tx fifos }; si446x_sendcmd(2, fifo_reset, SI446x_CHECK_ACK); sysclk_dogdelay(50); #endif // Set Si446x initial output power, input to power amp (0-0x7F, 0mW - 40mw?) uint8_t basepower = 0x10; // FIXME: basepower should be 0x10 for underperforming units and 0x04 for normal units uint8_t set_power_level_command[] = {SI446x_CMD_SET_PROPERTY, 0x22, 0x01, 0x01, basepower}; si446x_sendcmd(5, set_power_level_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); // Set output amplifier power if(buoy_iswet()) { si446x_setpower(buoy_getconfig()->values.rf_power); } else { si446x_setpower(VHF_TXPOWER_0W); } sysclk_dogdelay(10); // Set air data rate si446x_setdatarate(); sysclk_dogdelay(10); // Tune TX uint8_t change_state_command[] = {SI446x_CMD_CHANGE_STATE, 0x05}; // Change to TX tune state si446x_sendcmd(2, change_state_command, SI446x_CHECK_ACK); sysclk_dogdelay(10); si446x_cw_status = 0; } // Perform power-on-reset of Si446x. Takes 20ms. void si446x_reset(void) { si446x_shutdown(); HAL_Delay(10); si446x_wakeup(); HAL_Delay(10); } // Set GPIO pin state on Si446x void si446x_gpio(uint8_t gpio, uint8_t state, uint8_t doack) { // GPIO invalid if(gpio > 7) return; // Default to not changing any GPIO uint8_t gpio_pin_cfg_command[] = { SI446x_CMD_GPIO_PIN_CFG, // Command SI446x_GPIO_NOCHANGE, // GPIO0 - Power amp control DAC AD5611 Sync Pin SI446x_GPIO_NOCHANGE, // GPIO1 - Input for modulation SI446x_GPIO_NOCHANGE, // GPIO2 - Blue SI446x_GPIO_NOCHANGE, // GPIO3 - Unused SI446x_GPIO_NOCHANGE, // NIRQ SI446x_GPIO_NOCHANGE, // 0x11, // SDO SI446x_GPIO_NOCHANGE, // Gencfg }; // Set requested GPIO to requested state gpio_pin_cfg_command[gpio+1] = state; si446x_sendcmd(8, gpio_pin_cfg_command, doack); } // Set over-air data rate void si446x_setdatarate(void) { // Set data rate (unsure if this actually affects direct modulation) // set prop group numprops startprop data uint8_t set_data_rate_command[] = {SI446x_CMD_SET_PROPERTY, 0x20, 0x03, 0x03, 0x0F, 0x42, 0x40}; si446x_sendcmd(7, set_data_rate_command, SI446x_CHECK_ACK); } // Block write data to the Si446x SPI interface, up to 128 byte length void si446x_senddata(uint8_t* data, uint8_t len) { adc_pause(); uint8_t dummy[128]; SI446x_SELECT; HAL_SPI_TransmitReceive(hspi2, data, dummy, len, SI446x_TIMEOUT); SI446x_DESELECT; adc_resume(); } // Delay approximately 20us static void delay_cycles(void) { uint32_t delay_cycles = 180; while(delay_cycles>0) { asm("NOP"); delay_cycles--; } } // Send a command to the radio (Blocking) // Avoid calling this during code runtime as it will block for a significant period of time due to delays void si446x_sendcmd(uint8_t tx_len, uint8_t* data, uint8_t doack) { // EMZ TODO/FUTURE: change blocking SPI tx/rx to interrupts or DMA-based adc_pause(); SI446x_SELECT; delay_cycles(); uint8_t dummyrx[25]; if(tx_len >=25) { error_assert_info(ERR_VHF_TIMEOUT, "Packet len too long"); SI446x_DESELECT; adc_resume(); return; } // using transmit receive to transmit data because it actually blocks until the data is sent // an additional byte is added on to the transmission so we can receive the CTS byte HAL_StatusTypeDef res = HAL_SPI_TransmitReceive(hspi2, data, dummyrx, tx_len+1, SI446x_TIMEOUT); if(res != HAL_OK) { error_assert_silent(ERR_VHF_SPIBUSY); SI446x_DESELECT; adc_resume(); return; } SI446x_DESELECT; // If checking for the ACK, perform a SPI read and see if the command was acknowledged if(doack) { delay_cycles(); SI446x_SELECT; int reply = 0x00; uint8_t tx_requestack[2]; tx_requestack[0] = SI446x_CMD_READ_CMD_BUFF; tx_requestack[1] = 0x00; uint16_t attempts = 0; // Keep trying receive until it returns 0xFF (successful ACK) while (reply != 0xFF) { // Attempt to receive two bytes from the Si446x which should be an ACK uint8_t tmprx[2] = {0,0}; res = HAL_SPI_TransmitReceive(hspi2, tx_requestack, tmprx, 2, SI446x_TIMEOUT); if(res != HAL_OK) { error_assert_silent(ERR_VHF_SPIBUSY); break; // Break out, deinit, and exit } reply = tmprx[1]; // Cycle chip select line on and off if (reply != 0xFF) { delay_cycles(); SI446x_DESELECT; delay_cycles(); SI446x_SELECT; delay_cycles(); HAL_GPIO_TogglePin(LED_ACT); } // Maximum number of attempts exceeded if(attempts > 1024) { error_assert_silent(ERR_VHF_TIMEOUT); break; // Break out, deinit and exit } attempts++; } } // Turn off activity LED HAL_GPIO_WritePin(LED_ACT, GPIO_PIN_RESET); SI446x_DESELECT; delay_cycles(); adc_resume(); } // Set transmit frequency of Si446x void si446x_setchannel(uint8_t channel) { if(channel < 1 || channel > 99) return; // Invalid channel current_channel = channel; uint32_t frequency = rf_channels[channel - 1] + SI446x_TUNE_OFFSET; // Set the output divider according to recommended ranges given in si446x datasheet uint32_t outdiv = 4; uint32_t band = 0; if (frequency < 705000000UL) { outdiv = 6; band = 1;}; if (frequency < 525000000UL) { outdiv = 8; band = 2;}; if (frequency < 353000000UL) { outdiv = 12; band = 3;}; if (frequency < 239000000UL) { outdiv = 16; band = 4;}; if (frequency < 177000000UL) { outdiv = 24; band = 5;}; uint32_t f_pfd = 2 * SI446x_VCXO_FREQ / outdiv; uint32_t n = ((uint32_t)(frequency / f_pfd)) - 1; float ratio = (float)frequency / (float)f_pfd; float rest = ratio - (float)n; uint32_t m = (uint32_t)(rest * 524288UL); // Set the band parameter uint32_t sy_sel = 8; uint8_t set_band_property_command[] = {SI446x_CMD_SET_PROPERTY, 0x20, 0x01, 0x51, (band + sy_sel)}; si446x_sendcmd(5, set_band_property_command, SI446x_CHECK_ACK); // Set the pll parameters uint32_t m2 = m / 0x10000; uint32_t m1 = (m - m2 * 0x10000) / 0x100; uint32_t m0 = (m - m2 * 0x10000 - m1 * 0x100); // Assemble parameter string uint8_t set_frequency_property_command[] = {SI446x_CMD_SET_PROPERTY, 0x40, 0x04, 0x00, n, m2, m1, m0}; si446x_sendcmd(8, set_frequency_property_command, SI446x_CHECK_ACK); // Set frequency deviation // ...empirically 0xF9 looks like about 5khz. Sketchy. // set prop group numprops startprop data // Was 0x0F 00 for ~40kHz dev, switched to 56khzish? dev //2DF5 is correct for 56khz uint8_t set_frequency_separation[] = {SI446x_CMD_SET_PROPERTY, 0x20, 0x03, 0x0a, 0x00, 0x2D, 0xE0}; si446x_sendcmd(7, set_frequency_separation, SI446x_CHECK_ACK); } // Set transmit power of Si446x void si446x_setpower(uint8_t powerindex) { uint16_t powerlevel = 0; switch(powerindex) { case 0: powerlevel = VHF_TXPOWER_0W; break; case 1: powerlevel = VHF_TXPOWER_0W2; break; case 2: powerlevel = VHF_TXPOWER_0W5; break; case 3: powerlevel = VHF_TXPOWER_1W; break; default: powerlevel = VHF_TXPOWER_0W2; break; } __set_poweramp_dac(powerlevel); } // Set the power amplifier control DAC based on the desired power static void __set_poweramp_dac(uint16_t power_word) { // 10-bit DAC: check upper bound and limit if(power_word > 1023) power_word = 1023; sysclk_dogdelay(10); // Assert sync line; deselect Si446x si446x_gpio(SI446x_GPIO0, SI446x_GPIO_HIGH, SI446x_IGNORE_ACK); sysclk_dogdelay(10); // Disable SPI port hspi2->Instance->CR1 &= ~(SPI_CR1_SPE); // Switch SPI port to falling edge clock phase hspi2->Instance->CR1 |= SPI_PHASE_2EDGE; // Enable SPI port hspi2->Instance->CR1 |= SPI_CR1_SPE; sysclk_dogdelay(10); // Send SPI command to DAC uint8_t dummyrx[2]; uint8_t daccmd[2] = { AD56XX_NORMAL_OPERATION | ((power_word >> 4) & 0b00111111), // Top 6 bits go into the MSB (power_word << 4) & 0xF0 // Lower 4 bits go into the LSB, the 4 least significant bits are don't-cares }; HAL_SPI_TransmitReceive(hspi2, daccmd, dummyrx, 2, SI446x_TIMEOUT); // Disable SPI port hspi2->Instance->CR1 &= ~(SPI_CR1_SPE); // Switch SPI port back to rising edge clock phase hspi2->Instance->CR1 &= ~(SPI_PHASE_2EDGE); // Enable SPI port hspi2->Instance->CR1 |= SPI_CR1_SPE; sysclk_dogdelay(10); // Select Si446x; Deassert sync line si446x_gpio(SI446x_GPIO0, SI446x_GPIO_LOW, SI446x_IGNORE_ACK); sysclk_dogdelay(10); } // Start FIFO TX void si446x_fifo_txstart(uint16_t txlen) { // Change to TX state uint8_t change_state_command[] = { SI446x_CMD_START_TX, // command ID: START_TX 0, // Transmit channel 0,//b01110000, // txcomplete state, retransmit, and start immediately, txlen>>8, // txlen high byte txlen & 0xff, // txlen low byte }; si446x_sendcmd(5, change_state_command, SI446x_CHECK_ACK); } // Turn CW transmit on void si446x_cw_on(void) { // Change to TX state uint8_t change_state_command[] = {SI446x_CMD_CHANGE_STATE, 0x07}; si446x_sendcmd(2, change_state_command, SI446x_CHECK_ACK); si446x_cw_status = 1; } // Turn CW transmit off void si446x_cw_off(void) { // Change to ready state uint8_t change_state_command[] = {SI446x_CMD_CHANGE_STATE, 0x03}; si446x_sendcmd(2, change_state_command, SI446x_CHECK_ACK); si446x_cw_status = 0; // Turn off power amplifier si446x_setpower(VHF_TXPOWER_0W); } // Returns 1 if CW is on or 0 if CW is off inline uint8_t si446x_tx_status(void) { return si446x_cw_status; } // Initialize SPI port for generation of direct modulation for Si446x static void __init_spi1(void) { GPIO_InitTypeDef GPIO_InitStruct; // Radio Modulator SPI __SPI1_CLK_ENABLE(); GPIO_InitStruct.Pin = SI446x_GPIO_PIN; GPIO_InitStruct.Mode = GPIO_MODE_AF_PP; GPIO_InitStruct.Pull = GPIO_NOPULL; GPIO_InitStruct.Speed = GPIO_SPEED_HIGH; GPIO_InitStruct.Alternate = GPIO_AF0_SPI1; HAL_GPIO_Init(SI446x_GPIO_PORT, &GPIO_InitStruct); hspi1.Instance = SPI1; hspi1.Init.Mode = SPI_MODE_MASTER; hspi1.Init.Direction = SPI_DIRECTION_2LINES; hspi1.Init.DataSize = SPI_DATASIZE_8BIT; hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; hspi1.Init.NSS = SPI_NSS_SOFT; hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_256; hspi1.Init.FirstBit = SPI_FIRSTBIT_LSB; hspi1.Init.TIMode = SPI_TIMODE_DISABLED; hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED; hspi1.Init.CRCPolynomial = 10; hspi1.Init.CRCLength = SPI_CRC_LENGTH_DATASIZE; hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLED; HAL_SPI_Init(&hspi1); __DMA1_CLK_ENABLE(); hspi1_dma_tx.Instance = DMA1_Channel3; hspi1_dma_tx.Init.Direction = DMA_MEMORY_TO_PERIPH; hspi1_dma_tx.Init.PeriphInc = DMA_PINC_DISABLE; hspi1_dma_tx.Init.MemInc = DMA_MINC_ENABLE; hspi1_dma_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE; hspi1_dma_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE; hspi1_dma_tx.Init.Mode = DMA_CIRCULAR; hspi1_dma_tx.Init.Priority = DMA_PRIORITY_HIGH; HAL_DMA_Init(&hspi1_dma_tx); __HAL_LINKDMA(&hspi1,hdmatx,hspi1_dma_tx); // DMA interrupt init HAL_NVIC_SetPriority(DMA1_Channel2_3_IRQn, 0, 0); HAL_NVIC_EnableIRQ(DMA1_Channel2_3_IRQn); } // Place the Si446x into shutdown void si446x_shutdown(void) { HAL_GPIO_WritePin(SI446x_SHUTDOWN, GPIO_PIN_SET); } // Wake up the Si446x from shutdown void si446x_wakeup(void) { HAL_GPIO_WritePin(SI446x_SHUTDOWN, GPIO_PIN_RESET); } // Accessor for SPI1 (modulator spi) handle SPI_HandleTypeDef* spi1_get(void) { return &hspi1; } // Accessor for SPI1 (modulator spi) DMA handle DMA_HandleTypeDef* spi1_get_txdma_handle(void) { return &hspi1_dma_tx; }