diff --git a/Libraries/Si446x/si446x.c b/Libraries/Si446x/si446x.c new file mode 100644 --- /dev/null +++ b/Libraries/Si446x/si446x.c @@ -0,0 +1,427 @@ +// +// Si446x: Initializes and configures a Si446x transceiver over SPI +// + +#include "stm32f0xx_hal.h" + +#include "si446x.h" +#include "config.h" +#include "error.h" +#include "system/gpio.h" +#include "system/sysclk.h" + + +// Private variables +static SPI_HandleTypeDef hspi1; +static uint8_t si446x_cw_status = 0; + + +// Private function prototypes +static void __init_spi1(void); + + +// Initialize Si446x in 2FSK transmit mode +void si446x_init(void) +{ + // init spi port + __init_spi1(); + + GPIO_InitTypeDef GPIO_InitStruct; + + // GPIO: TCXO control + GPIO_InitStruct.Pin = SI446x_TCXO_EN_PIN; + GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP; + GPIO_InitStruct.Pull = GPIO_NOPULL; + HAL_GPIO_Init(SI446x_TCXO_EN_PORT, &GPIO_InitStruct); + HAL_GPIO_WritePin(SI446x_TCXO_EN_PORT, SI446x_TCXO_EN_PIN, 1); + + // 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); + + // Perform PoR (takes 20ms) and turn device on + si446x_reset(); + + // 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}; + si446x_sendcmd(7, init_command, SI446x_CHECK_ACK); + + HAL_Delay(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); + HAL_Delay(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); + + HAL_Delay(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); + + HAL_Delay(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); + + HAL_Delay(10); + + // Tune to frequency specified + si446x_setchannel(TUNE_FREQUENCY); + + HAL_Delay(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); + + HAL_Delay(10); + + // Set Si446x initial output power, input to power amp (0-0x7F, 0mW - 40mw?) + uint8_t basepower = 0x02; + // 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); + + + HAL_Delay(10); + + // Set air data rate + si446x_setdatarate(); + HAL_Delay(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); + HAL_Delay(10); + + si446x_cw_status = 0; + + si446x_cw_on(); +} + + +// 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) +{ + uint8_t dummy[128]; + SI446x_SELECT; + HAL_SPI_TransmitReceive(&hspi1, data, dummy, len, SI446x_TIMEOUT); + SI446x_DESELECT; +} + + +// 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) +{ + SI446x_SELECT; + + delay_cycles(); + + uint8_t dummyrx[25]; + if(tx_len >=25) + { + SI446x_DESELECT; + 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(&hspi1, data, dummyrx, tx_len+1, SI446x_TIMEOUT); + + if(res != HAL_OK) + { + SI446x_DESELECT; + 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(&hspi1, 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(); +} + + +// Set transmit frequency of Si446x +void si446x_setchannel(uint32_t frequency) +{ + + // 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, 0x03, 0x00}; + si446x_sendcmd(7, set_frequency_separation, 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; +} + + +// 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; + + // 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; + + // SPI pins + __SPI1_CLK_ENABLE(); + GPIO_InitStruct.Pin = SI446x_SCK_PIN|SI446x_MOSI_PIN|SI446x_MOSI_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_SCK_PORT, &GPIO_InitStruct); + + // SPI peripheral + 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; // Double-check, this is usually high, but might be low for this chip + hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // was 1edge before (rising edge of clock) + hspi1.Init.NSS = SPI_NSS_SOFT; + hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16; + hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB; + hspi1.Init.TIMode = SPI_TIMODE_DISABLED; + hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLED; + hspi1.Init.NSSPMode = SPI_NSS_PULSE_DISABLED; + HAL_SPI_Init(&hspi1); +} + + +// 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 handle +SPI_HandleTypeDef* spi1_get(void) +{ + return &hspi1; +}