diff --git a/Libraries/Si446x/si446x.c.orig b/Libraries/Si446x/si446x.c.orig new file mode 100644 --- /dev/null +++ b/Libraries/Si446x/si446x.c.orig @@ -0,0 +1,659 @@ +// +// 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; +}