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