text: /*!
   * \file      radio.c
   *
   * \brief     SX126xRadio driver API definition
   *
   * \copyright Revised BSD License, see section \ref LICENSE.
   *
   * \code
   *                ______                              _
   *               / _____)             _              | |
   *              ( (____  _____ ____ _| |_ _____  ____| |__
   *               \____ \| ___ |    (_   _) ___ |/ ___)  _ \
   *               _____) ) ____| | | || |_| ____( (___| | | |
   *              (______/|_____)_|_|_| \__)_____)\____)_| |_|
   *              (C)2013-2017 Semtech
   *
   * \endcode
   *
   * \author    Miguel Luis ( Semtech )
   *
   * \author    Gregory Cristian ( Semtech )
   */
  #include <math.h>
  #include <string.h>
  #include "sx126x.h"
  #include "sx126x-board.h"
  #include "../board.h"
  
  /*!
   * \brief Initializes the radio
   *
   * \param [IN] events Structure containing the driver callback functions
   */
  void SX126xRadioInit( RadioEvents_t *events );
  
  /*!
   * Return current radio status
   *
   * \param status SX126xRadio status.[RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING]
   */
  RadioState_t SX126xRadioGetStatus( void );
  
  /*!
   * \brief Configures the radio with the given modem
   *
   * \param [IN] modem Modem to be used [0: FSK, 1: LoRa]
   */
  void SX126xRadioSetModem( RadioModems_t modem );
  
  /*!
   * \brief Sets the channel frequency
   *
   * \param [IN] freq         Channel RF frequency
   */
  void SX126xRadioSetChannel( uint32_t freq );
  
  /*!
   * \brief Checks if the channel is free for the given time
   *
   * \param [IN] modem      SX126xRadio modem to be used [0: FSK, 1: LoRa]
   * \param [IN] freq       Channel RF frequency
   * \param [IN] rssiThresh RSSI threshold
   * \param [IN] maxCarrierSenseTime Max time while the RSSI is measured
   *
   * \retval isFree         [true: Channel is free, false: Channel is not free]
   */
  bool SX126xRadioIsChannelFree( RadioModems_t modem, uint32_t freq, int16_t rssiThresh, uint32_t maxCarrierSenseTime );
  
  /*!
   * \brief Generates a 32 bits random value based on the RSSI readings
   *
   * \remark This function sets the radio in LoRa modem mode and disables
   *         all interrupts.
   *         After calling this function either Radio.SetRxConfig or
   *         Radio.SetTxConfig functions must be called.
   *
   * \retval randomValue    32 bits random value
   */
  uint32_t SX126xRadioRandom( void );
  
  /*!
   * \brief Sets the reception parameters
   *
   * \param [IN] modem        Radio modem to be used [0: FSK, 1: LoRa]
   * \param [IN] bandwidth    Sets the bandwidth
   *                          FSK : >= 2600 and <= 250000 Hz
   *                          LoRa: [0: 125 kHz, 1: 250 kHz,
   *                                 2: 500 kHz, 3: Reserved]
   * \param [IN] datarate     Sets the Datarate
   *                          FSK : 600..300000 bits/s
   *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
   *                                10: 1024, 11: 2048, 12: 4096  chips]
   * \param [IN] coderate     Sets the coding rate (LoRa only)
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
   * \param [IN] bandwidthAfc Sets the AFC Bandwidth (FSK only)
   *                          FSK : >= 2600 and <= 250000 Hz
   *                          LoRa: N/A ( set to 0 )
   * \param [IN] preambleLen  Sets the Preamble length
   *                          FSK : Number of bytes
   *                          LoRa: Length in symbols (the hardware adds 4 more symbols)
   * \param [IN] symbTimeout  Sets the RxSingle timeout value
   *                          FSK : timeout in number of bytes
   *                          LoRa: timeout in symbols
   * \param [IN] fixLen       Fixed length packets [0: variable, 1: fixed]
   * \param [IN] payloadLen   Sets payload length when fixed length is used
   * \param [IN] crcOn        Enables/Disables the CRC [0: OFF, 1: ON]
   * \param [IN] FreqHopOn    Enables disables the intra-packet frequency hopping
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [0: OFF, 1: ON]
   * \param [IN] HopPeriod    Number of symbols between each hop
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: Number of symbols
   * \param [IN] iqInverted   Inverts IQ signals (LoRa only)
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [0: not inverted, 1: inverted]
   * \param [IN] rxContinuous Sets the reception in continuous mode
   *                          [false: single mode, true: continuous mode]
   */
  void SX126xRadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
                            uint32_t datarate, uint8_t coderate,
                            uint32_t bandwidthAfc, uint16_t preambleLen,
                            uint16_t symbTimeout, bool fixLen,
                            uint8_t payloadLen,
                            bool crcOn, bool FreqHopOn, uint8_t HopPeriod,
                            bool iqInverted, bool rxContinuous );
  
  /*!
   * \brief Sets the transmission parameters
   *
   * \param [IN] modem        Radio modem to be used [0: FSK, 1: LoRa]
   * \param [IN] power        Sets the output power [dBm]
   * \param [IN] fdev         Sets the frequency deviation (FSK only)
   *                          FSK : [Hz]
   *                          LoRa: 0
   * \param [IN] bandwidth    Sets the bandwidth (LoRa only)
   *                          FSK : 0
   *                          LoRa: [0: 125 kHz, 1: 250 kHz,
   *                                 2: 500 kHz, 3: Reserved]
   * \param [IN] datarate     Sets the Datarate
   *                          FSK : 600..300000 bits/s
   *                          LoRa: [6: 64, 7: 128, 8: 256, 9: 512,
   *                                10: 1024, 11: 2048, 12: 4096  chips]
   * \param [IN] coderate     Sets the coding rate (LoRa only)
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [1: 4/5, 2: 4/6, 3: 4/7, 4: 4/8]
   * \param [IN] preambleLen  Sets the preamble length
   *                          FSK : Number of bytes
   *                          LoRa: Length in symbols (the hardware adds 4 more symbols)
   * \param [IN] fixLen       Fixed length packets [0: variable, 1: fixed]
   * \param [IN] crcOn        Enables disables the CRC [0: OFF, 1: ON]
   * \param [IN] FreqHopOn    Enables disables the intra-packet frequency hopping
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [0: OFF, 1: ON]
   * \param [IN] HopPeriod    Number of symbols between each hop
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: Number of symbols
   * \param [IN] iqInverted   Inverts IQ signals (LoRa only)
   *                          FSK : N/A ( set to 0 )
   *                          LoRa: [0: not inverted, 1: inverted]
   * \param [IN] timeout      Transmission timeout [ms]
   */
  void SX126xRadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
                            uint32_t bandwidth, uint32_t datarate,
                            uint8_t coderate, uint16_t preambleLen,
                            bool fixLen, bool crcOn, bool FreqHopOn,
                            uint8_t HopPeriod, bool iqInverted, uint32_t timeout );
  
  /*!
   * \brief Checks if the given RF frequency is supported by the hardware
   *
   * \param [IN] frequency RF frequency to be checked
   * \retval isSupported [true: supported, false: unsupported]
   */
  bool SX126xRadioCheckRfFrequency( uint32_t frequency );
  
  /*!
   * \brief Computes the packet time on air in ms for the given payload
   *
   * \Remark Can only be called once SetRxConfig or SetTxConfig have been called
   *
   * \param [IN] modem      Radio modem to be used [0: FSK, 1: LoRa]
   * \param [IN] pktLen     Packet payload length
   *
   * \retval airTime        Computed airTime (ms) for the given packet payload length
   */
  uint32_t SX126xRadioTimeOnAir( RadioModems_t modem, uint8_t pktLen );
  
  /*!
   * \brief Sends the buffer of size. Prepares the packet to be sent and sets
   *        the radio in transmission
   *
   * \param [IN]: buffer     Buffer pointer
   * \param [IN]: size       Buffer size
   */
  void SX126xRadioSend( uint8_t *buffer, uint8_t size );
  
  /*!
   * \brief Sets the radio in sleep mode
   */
  void SX126xRadioSleep( void );
  
  /*!
   * \brief Sets the radio in standby mode
   */
  void SX126xRadioStandby( void );
  
  /*!
   * \brief Sets the radio in reception mode for the given time
   * \param [IN] timeout Reception timeout [ms]
   *                     [0: continuous, others timeout]
   */
  void SX126xRadioRx( uint32_t timeout );
  
  /*!
   * \brief Start a Channel Activity Detection
   */
  void SX126xRadioStartCad( void );
  
  /*!
   * \brief Sets the radio in continuous wave transmission mode
   *
   * \param [IN]: freq       Channel RF frequency
   * \param [IN]: power      Sets the output power [dBm]
   * \param [IN]: time       Transmission mode timeout [s]
   */
  void SX126xRadioSetTxContinuousWave( uint32_t freq, int8_t power, uint16_t time );
  
  /*!
   * \brief Reads the current RSSI value
   *
   * \retval rssiValue Current RSSI value in [dBm]
   */
  int16_t SX126xRadioRssi( RadioModems_t modem );
  
  /*!
   * \brief Writes the radio register at the specified address
   *
   * \param [IN]: addr Register address
   * \param [IN]: data New register value
   */
  void SX126xRadioWrite( uint16_t addr, uint8_t data );
  
  /*!
   * \brief Reads the radio register at the specified address
   *
   * \param [IN]: addr Register address
   * \retval data Register value
   */
  uint8_t SX126xRadioRead( uint16_t addr );
  
  /*!
   * \brief Writes multiple radio registers starting at address
   *
   * \param [IN] addr   First SX126xRadio register address
   * \param [IN] buffer Buffer containing the new register's values
   * \param [IN] size   Number of registers to be written
   */
  void SX126xRadioWriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size );
  
  /*!
   * \brief Reads multiple radio registers starting at address
   *
   * \param [IN] addr First Radio register address
   * \param [OUT] buffer Buffer where to copy the registers data
   * \param [IN] size Number of registers to be read
   */
  void SX126xRadioReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size );
  
  /*!
   * \brief Sets the maximum payload length.
   *
   * \param [IN] modem      Radio modem to be used [0: FSK, 1: LoRa]
   * \param [IN] max        Maximum payload length in bytes
   */
  void SX126xRadioSetMaxPayloadLength( RadioModems_t modem, uint8_t max );
  
  /*!
   * \brief Sets the network to public or private. Updates the sync byte.
   *
   * \remark Applies to LoRa modem only
   *
   * \param [IN] enable if true, it enables a public network
   */
  void SX126xRadioSetPublicNetwork( bool enable );
  
  /*!
   * \brief Gets the time required for the board plus radio to get out of sleep.[ms]
   *
   * \retval time Radio plus board wakeup time in ms.
   */
  uint32_t SX126xRadioGetWakeupTime( void );
  
  /*!
   * \brief Process radio irq
   */
  void SX126xRadioIrqProcess( void );
  
  /*!
   * \brief Sets the radio in reception mode with Max LNA gain for the given time
   * \param [IN] timeout Reception timeout [ms]
   *                     [0: continuous, others timeout]
   */
  void SX126xRadioRxBoosted( uint32_t timeout );
  
  /*!
   * \brief Sets the Rx duty cycle management parameters
   *
   * \param [in]  rxTime        Structure describing reception timeout value
   * \param [in]  sleepTime     Structure describing sleep timeout value
   */
  void SX126xRadioSetRxDutyCycle( uint32_t rxTime, uint32_t sleepTime );
  
  /*!
   * Radio driver structure initialization
   */
  
  Radio_t SX126xRadioNew(){
      Radio_t radio = {
          SX126xRadioInit,
          SX126xRadioGetStatus,
          SX126xRadioSetModem,
          SX126xRadioSetChannel,
          SX126xRadioIsChannelFree,
          SX126xRadioRandom,
          SX126xRadioSetRxConfig,
          SX126xRadioSetTxConfig,
          SX126xRadioCheckRfFrequency,
          SX126xRadioTimeOnAir,
          SX126xRadioSend,
          SX126xRadioSleep,
          SX126xRadioStandby,
          SX126xRadioRx,
          SX126xRadioStartCad,
          SX126xRadioSetTxContinuousWave,
          SX126xRadioRssi,
          SX126xRadioWrite,
          SX126xRadioRead,
          SX126xRadioWriteBuffer,
          SX126xRadioReadBuffer,
          SX126xRadioSetMaxPayloadLength,
          SX126xRadioSetPublicNetwork,
          SX126xRadioGetWakeupTime,
          SX126xRadioIrqProcess,
          // Available on SX126x only
          SX126xRadioRxBoosted,
          SX126xRadioSetRxDutyCycle,
          NULL, // void (*EnableTcxo)( void );
  
      };
      return radio;
  }
  
  /*
   * Local types definition
   */
  
  
   /*!
   * FSK bandwidth definition
   */
  typedef struct
  {
      uint32_t bandwidth;
      uint8_t  RegValue;
  }Sx126x_FskBandwidth_t;
  
  /*!
   * Precomputed FSK bandwidth registers values
   */
  const Sx126x_FskBandwidth_t Sx126x_FskBandwidths[] =
  {
      { 4800  , 0x1F },
      { 5800  , 0x17 },
      { 7300  , 0x0F },
      { 9700  , 0x1E },
      { 11700 , 0x16 },
      { 14600 , 0x0E },
      { 19500 , 0x1D },
      { 23400 , 0x15 },
      { 29300 , 0x0D },
      { 39000 , 0x1C },
      { 46900 , 0x14 },
      { 58600 , 0x0C },
      { 78200 , 0x1B },
      { 93800 , 0x13 },
      { 117300, 0x0B },
      { 156200, 0x1A },
      { 187200, 0x12 },
      { 234300, 0x0A },
      { 312000, 0x19 },
      { 373600, 0x11 },
      { 467000, 0x09 },
      { 500000, 0x00 }, // Invalid Bandwidth
  };
  
  const RadioLoRaBandwidths_t Sx126x_Bandwidths[] = { LORA_BW_125, LORA_BW_250, LORA_BW_500 };
  
  //                                          SF12    SF11    SF10    SF9    SF8    SF7
  static double SX126xRadioLoRaSymbTime[3][6] = {{ 32.768, 16.384, 8.192, 4.096, 2.048, 1.024 },  // 125 KHz
                                           { 16.384, 8.192,  4.096, 2.048, 1.024, 0.512 },  // 250 KHz
                                           { 8.192,  4.096,  2.048, 1.024, 0.512, 0.256 }}; // 500 KHz
  
  uint8_t MaxPayloadLength = 0xFF;
  
  uint32_t TxTimeout = 0;
  uint32_t RxTimeout = 0;
  
  bool RxContinuous = false;
  
  
  PacketStatus_t SX126xRadioPktStatus;
  uint8_t SX126xRadioRxPayload[255];
  
  bool IrqFired = false;
  
  /*
   * SX126x DIO IRQ callback functions prototype
   */
  
  /*!
   * \brief DIO 0 IRQ callback
   */
  void SX126xRadioOnDioIrq( void* context );
  
  /*!
   * \brief Tx timeout timer callback
   */
  void SX126xRadioOnTxTimeoutIrq( void* context );
  
  /*!
   * \brief Rx timeout timer callback
   */
  void SX126xRadioOnRxTimeoutIrq( void* context );
  
  /*
   * Private global variables
   */
  
  
  /*!
   * Holds the current network type for the radio
   */
  typedef struct
  {
      bool Previous;
      bool Current;
  }SX126xRadioPublicNetwork_t;
  
  static SX126xRadioPublicNetwork_t SX126xRadioPublicNetwork = { false };
  
  /*!
   * SX126xRadio callbacks variable
   */
  static RadioEvents_t* SX126xRadioEvents;
  
  /*
   * Public global variables
   */
  
  /*!
   * SX126xRadio hardware and global parameters
   */
  SX126x_t SX126x;
  
  /*!
   * Tx and Rx timers
   */
  TimerEvent_t SX126xTxTimeoutTimer;
  TimerEvent_t SX126xRxTimeoutTimer;
  
  /*!
   * Returns the known FSK bandwidth registers value
   *
   * \param [IN] bandwidth Bandwidth value in Hz
   * \retval regValue Bandwidth register value.
   */
  static uint8_t SX126xRadioGetFskBandwidthRegValue( uint32_t bandwidth )
  {
      uint8_t i;
  
      if( bandwidth == 0 )
      {
          return( 0x1F );
      }
  
      for( i = 0; i < ( sizeof( Sx126x_FskBandwidths ) / sizeof( Sx126x_FskBandwidth_t ) ) - 1; i++ )
      {
          if( ( bandwidth >= Sx126x_FskBandwidths[i].bandwidth ) && ( bandwidth < Sx126x_FskBandwidths[i + 1].bandwidth ) )
          {
              return Sx126x_FskBandwidths[i+1].RegValue;
          }
      }
      // ERROR: Value not found
      while( 1 );
  }
  
  void SX126xRadioInit( RadioEvents_t *events )
  {
      SX126xRadioEvents = events;
  
      SX126xInit( SX126xRadioOnDioIrq );
      SX126xSetStandby( STDBY_RC );
      SX126xSetRegulatorMode( USE_DCDC );
  
      SX126xSetBufferBaseAddress( 0x00, 0x00 );
      SX126xSetTxParams( 0, RADIO_RAMP_200_US );
      SX126xSetDioIrqParams( IRQ_RADIO_ALL, IRQ_RADIO_ALL, IRQ_RADIO_NONE, IRQ_RADIO_NONE );
  
      // Initialize driver timeout timers
      TimerInit( &SX126xTxTimeoutTimer, SX126xRadioOnTxTimeoutIrq );
      TimerInit( &SX126xRxTimeoutTimer, SX126xRadioOnRxTimeoutIrq );
  
      IrqFired = false;
  }
  
  RadioState_t SX126xRadioGetStatus( void )
  {
      switch( SX126xGetOperatingMode( ) )
      {
          case MODE_TX:
              return RF_TX_RUNNING;
          case MODE_RX:
              return RF_RX_RUNNING;
          case MODE_CAD:
              return RF_CAD;
          default:
              return RF_IDLE;
      }
  }
  
  void SX126xRadioSetModem( RadioModems_t modem )
  {
      switch( modem )
      {
      default:
      case MODEM_FSK:
          SX126xSetPacketType( PACKET_TYPE_GFSK );
          // When switching to GFSK mode the LoRa SyncWord register value is reset
          // Thus, we also reset the SX126xRadioPublicNetwork variable
          SX126xRadioPublicNetwork.Current = false;
          break;
      case MODEM_LORA:
          SX126xSetPacketType( PACKET_TYPE_LORA );
          // Public/Private network register is reset when switching modems
          if( SX126xRadioPublicNetwork.Current != SX126xRadioPublicNetwork.Previous )
          {
              SX126xRadioPublicNetwork.Current = SX126xRadioPublicNetwork.Previous;
              SX126xRadioSetPublicNetwork( SX126xRadioPublicNetwork.Current );
          }
          break;
      }
  }
  
  void SX126xRadioSetChannel( uint32_t freq )
  {
      SX126xSetRfFrequency( freq );
  }
  
  bool SX126xRadioIsChannelFree( RadioModems_t modem, uint32_t freq, int16_t rssiThresh, uint32_t maxCarrierSenseTime )
  {
      bool status = true;
      int16_t rssi = 0;
      uint32_t carrierSenseTime = 0;
  
      if( SX126xRadioGetStatus( ) != RF_IDLE )
      {
          return false;
      }
  
      SX126xRadioSetModem( modem );
  
      SX126xRadioSetChannel( freq );
  
      SX126xRadioRx( 0 );
  
      DelayMs( 1 );
  
      carrierSenseTime = TimerGetCurrentTime( );
  
      // Perform carrier sense for maxCarrierSenseTime
      while( TimerGetElapsedTime( carrierSenseTime ) < maxCarrierSenseTime )
      {
          rssi = SX126xRadioRssi( modem );
  
          if( rssi > rssiThresh )
          {
              status = false;
              break;
          }
      }
      SX126xRadioSleep( );
      return status;
  }
  
  uint32_t SX126xRadioRandom( void )
  {
      uint8_t i;
      uint32_t rnd = 0;
  
      /*
       * SX126xRadio setup for random number generation
       */
      // Set LoRa modem ON
      SX126xRadioSetModem( MODEM_LORA );
  
      // Set radio in continuous reception
      SX126xSetRx( 0 );
  
      for( i = 0; i < 32; i++ )
      {
          DelayMs( 1 );
          // Unfiltered RSSI value reading. Only takes the LSB value
          rnd |= ( ( uint32_t )SX126xGetRssiInst( ) & 0x01 ) << i;
      }
  
      SX126xRadioSleep( );
  
      return rnd;
  }
  
  void SX126xRadioSetRxConfig( RadioModems_t modem, uint32_t bandwidth,
                           uint32_t datarate, uint8_t coderate,
                           uint32_t bandwidthAfc, uint16_t preambleLen,
                           uint16_t symbTimeout, bool fixLen,
                           uint8_t payloadLen,
                           bool crcOn, bool freqHopOn, uint8_t hopPeriod,
                           bool iqInverted, bool rxContinuous )
  {
  
      RxContinuous = rxContinuous;
      if( rxContinuous == true )
      {
          symbTimeout = 0;
      }
      if( fixLen == true )
      {
          MaxPayloadLength = payloadLen;
      }
      else
      {
          MaxPayloadLength = 0xFF;
      }
  
      switch( modem )
      {
          case MODEM_FSK:
              SX126xSetStopRxTimerOnPreambleDetect( false );
              SX126x.ModulationParams.PacketType = PACKET_TYPE_GFSK;
  
              SX126x.ModulationParams.Params.Gfsk.BitRate = datarate;
              SX126x.ModulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
              SX126x.ModulationParams.Params.Gfsk.Bandwidth = SX126xRadioGetFskBandwidthRegValue( bandwidth );
  
              SX126x.PacketParams.PacketType = PACKET_TYPE_GFSK;
              SX126x.PacketParams.Params.Gfsk.PreambleLength = ( preambleLen << 3 ); // convert byte into bit
              SX126x.PacketParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
              SX126x.PacketParams.Params.Gfsk.SyncWordLength = 3 << 3; // convert byte into bit
              SX126x.PacketParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
              SX126x.PacketParams.Params.Gfsk.HeaderType = ( fixLen == true ) ? RADIO_PACKET_FIXED_LENGTH : RADIO_PACKET_VARIABLE_LENGTH;
              SX126x.PacketParams.Params.Gfsk.PayloadLength = MaxPayloadLength;
              if( crcOn == true )
              {
                  SX126x.PacketParams.Params.Gfsk.CrcLength = SX126x_RADIO_CRC_2_BYTES_CCIT;
              }
              else
              {
                  SX126x.PacketParams.Params.Gfsk.CrcLength = SX126x_RADIO_CRC_OFF;
              }
              SX126x.PacketParams.Params.Gfsk.DcFree = RADIO_DC_FREEWHITENING;
  
              SX126xRadioStandby( );
              SX126xRadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
              SX126xSetModulationParams( &SX126x.ModulationParams );
              SX126xSetPacketParams( &SX126x.PacketParams );
              SX126xSetSyncWord( ( uint8_t[] ){ 0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00 } );
              SX126xSetWhiteningSeed( 0x01FF );
  
              RxTimeout = ( uint32_t )( symbTimeout * ( ( 1.0 / ( double )datarate ) * 8.0 ) * 1000 );
              break;
  
          case MODEM_LORA:
              SX126xSetStopRxTimerOnPreambleDetect( false );
              SX126xSetLoRaSymbNumTimeout( symbTimeout );
              SX126x.ModulationParams.PacketType = PACKET_TYPE_LORA;
              SX126x.ModulationParams.Params.LoRa.SpreadingFactor = ( RadioLoRaSpreadingFactors_t )datarate;
              SX126x.ModulationParams.Params.LoRa.Bandwidth = Sx126x_Bandwidths[bandwidth];
              SX126x.ModulationParams.Params.LoRa.CodingRate = ( RadioLoRaCodingRates_t )coderate;
  
              if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
              ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
              {
                  SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
              }
              else
              {
                  SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
              }
  
              SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
  
              if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
                  ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) )
              {
                  if( preambleLen < 12 )
                  {
                      SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
                  }
                  else
                  {
                      SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
                  }
              }
              else
              {
                  SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
              }
  
              SX126x.PacketParams.Params.LoRa.HeaderType = ( RadioLoRaPacketLengthsMode_t )fixLen;
  
              SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength;
              SX126x.PacketParams.Params.LoRa.CrcMode = ( RadioLoRaCrcModes_t )crcOn;
              SX126x.PacketParams.Params.LoRa.InvertIQ = ( RadioLoRaIQModes_t )iqInverted;
  
              SX126xRadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
              SX126xSetModulationParams( &SX126x.ModulationParams );
              SX126xSetPacketParams( &SX126x.PacketParams );
  
              // WORKAROUND - Optimizing the Inverted IQ Operation, see DS_SX1261-2_V1.2 datasheet chapter 15.4
              if( SX126x.PacketParams.Params.LoRa.InvertIQ == LORA_IQ_INVERTED )
              {
                  // RegIqPolaritySetup = @address 0x0736
                  SX126xWriteRegister( 0x0736, SX126xReadRegister( 0x0736 ) & ~( 1 << 2 ) );
              }
              else
              {
                  // RegIqPolaritySetup @address 0x0736
                  SX126xWriteRegister( 0x0736, SX126xReadRegister( 0x0736 ) | ( 1 << 2 ) );
              }
              // WORKAROUND END
  
              // Timeout Max, Timeout handled directly in SetRx function
              RxTimeout = 0xFFFF;
  
              break;
          default:
          break;
      }
  }
  
  void SX126xRadioSetTxConfig( RadioModems_t modem, int8_t power, uint32_t fdev,
                          uint32_t bandwidth, uint32_t datarate,
                          uint8_t coderate, uint16_t preambleLen,
                          bool fixLen, bool crcOn, bool freqHopOn,
                          uint8_t hopPeriod, bool iqInverted, uint32_t timeout )
  {
  
      switch( modem )
      {
          case MODEM_FSK:
              SX126x.ModulationParams.PacketType = PACKET_TYPE_GFSK;
              SX126x.ModulationParams.Params.Gfsk.BitRate = datarate;
  
              SX126x.ModulationParams.Params.Gfsk.ModulationShaping = MOD_SHAPING_G_BT_1;
              SX126x.ModulationParams.Params.Gfsk.Bandwidth = SX126xRadioGetFskBandwidthRegValue( bandwidth );
              SX126x.ModulationParams.Params.Gfsk.Fdev = fdev;
  
              SX126x.PacketParams.PacketType = PACKET_TYPE_GFSK;
              SX126x.PacketParams.Params.Gfsk.PreambleLength = ( preambleLen << 3 ); // convert byte into bit
              SX126x.PacketParams.Params.Gfsk.PreambleMinDetect = RADIO_PREAMBLE_DETECTOR_08_BITS;
              SX126x.PacketParams.Params.Gfsk.SyncWordLength = 3 << 3 ; // convert byte into bit
              SX126x.PacketParams.Params.Gfsk.AddrComp = RADIO_ADDRESSCOMP_FILT_OFF;
              SX126x.PacketParams.Params.Gfsk.HeaderType = ( fixLen == true ) ? RADIO_PACKET_FIXED_LENGTH : RADIO_PACKET_VARIABLE_LENGTH;
  
              if( crcOn == true )
              {
                  SX126x.PacketParams.Params.Gfsk.CrcLength = SX126x_RADIO_CRC_2_BYTES_CCIT;
              }
              else
              {
                  SX126x.PacketParams.Params.Gfsk.CrcLength = SX126x_RADIO_CRC_OFF;
              }
              SX126x.PacketParams.Params.Gfsk.DcFree = RADIO_DC_FREEWHITENING;
  
              SX126xRadioStandby( );
              SX126xRadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
              SX126xSetModulationParams( &SX126x.ModulationParams );
              SX126xSetPacketParams( &SX126x.PacketParams );
              SX126xSetSyncWord( ( uint8_t[] ){ 0xC1, 0x94, 0xC1, 0x00, 0x00, 0x00, 0x00, 0x00 } );
              SX126xSetWhiteningSeed( 0x01FF );
              break;
  
          case MODEM_LORA:
              SX126x.ModulationParams.PacketType = PACKET_TYPE_LORA;
              SX126x.ModulationParams.Params.LoRa.SpreadingFactor = ( RadioLoRaSpreadingFactors_t ) datarate;
              SX126x.ModulationParams.Params.LoRa.Bandwidth =  Sx126x_Bandwidths[bandwidth];
              SX126x.ModulationParams.Params.LoRa.CodingRate= ( RadioLoRaCodingRates_t )coderate;
  
              if( ( ( bandwidth == 0 ) && ( ( datarate == 11 ) || ( datarate == 12 ) ) ) ||
              ( ( bandwidth == 1 ) && ( datarate == 12 ) ) )
              {
                  SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x01;
              }
              else
              {
                  SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize = 0x00;
              }
  
              SX126x.PacketParams.PacketType = PACKET_TYPE_LORA;
  
              if( ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF5 ) ||
                  ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor == LORA_SF6 ) )
              {
                  if( preambleLen < 12 )
                  {
                      SX126x.PacketParams.Params.LoRa.PreambleLength = 12;
                  }
                  else
                  {
                      SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
                  }
              }
              else
              {
                  SX126x.PacketParams.Params.LoRa.PreambleLength = preambleLen;
              }
  
              SX126x.PacketParams.Params.LoRa.HeaderType = ( RadioLoRaPacketLengthsMode_t )fixLen;
              SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength;
              SX126x.PacketParams.Params.LoRa.CrcMode = ( RadioLoRaCrcModes_t )crcOn;
              SX126x.PacketParams.Params.LoRa.InvertIQ = ( RadioLoRaIQModes_t )iqInverted;
  
              SX126xRadioStandby( );
              SX126xRadioSetModem( ( SX126x.ModulationParams.PacketType == PACKET_TYPE_GFSK ) ? MODEM_FSK : MODEM_LORA );
              SX126xSetModulationParams( &SX126x.ModulationParams );
              SX126xSetPacketParams( &SX126x.PacketParams );
              break;
          default:
              break;
      }
  
      // WORKAROUND - Modulation Quality with 500 kHz LoRa� Bandwidth, see DS_SX1261-2_V1.2 datasheet chapter 15.1
      if( ( modem == MODEM_LORA ) && ( SX126x.ModulationParams.Params.LoRa.Bandwidth == LORA_BW_500 ) )
      {
          // RegTxModulation = @address 0x0889
          SX126xWriteRegister( 0x0889, SX126xReadRegister( 0x0889 ) & ~( 1 << 2 ) );
      }
      else
      {
          // RegTxModulation = @address 0x0889
          SX126xWriteRegister( 0x0889, SX126xReadRegister( 0x0889 ) | ( 1 << 2 ) );
      }
      // WORKAROUND END
  
      SX126xSetRfTxPower( power );
      TxTimeout = timeout;
  }
  
  bool SX126xRadioCheckRfFrequency( uint32_t frequency )
  {
      return true;
  }
  
  uint32_t SX126xRadioTimeOnAir( RadioModems_t modem, uint8_t pktLen )
  {
      uint32_t airTime = 0;
  
      // switch( modem )
      // {
      // case MODEM_FSK:
      //     {
      //        airTime = rint( ( 8 * ( SX126x.PacketParams.Params.Gfsk.PreambleLength +
      //                                  ( SX126x.PacketParams.Params.Gfsk.SyncWordLength >> 3 ) +
      //                                  ( ( SX126x.PacketParams.Params.Gfsk.HeaderType == RADIO_PACKET_FIXED_LENGTH ) ? 0.0 : 1.0 ) +
      //                                  pktLen +
      //                                  ( ( SX126x.PacketParams.Params.Gfsk.CrcLength == SX126x_RADIO_CRC_2_BYTES ) ? 2.0 : 0 ) ) /
      //                                  SX126x.ModulationParams.Params.Gfsk.BitRate ) * 1e3 );
      //     }
      //     break;
      // case MODEM_LORA:
      //     {
      //         double ts = SX126xRadioLoRaSymbTime[SX126x.ModulationParams.Params.LoRa.Bandwidth - 4][12 - SX126x.ModulationParams.Params.LoRa.SpreadingFactor];
      //         // time of preamble
      //         double tPreamble = ( SX126x.PacketParams.Params.LoRa.PreambleLength + 4.25 ) * ts;
      //         // Symbol length of payload and time
      //         double tmp = ceil( ( 8 * pktLen - 4 * SX126x.ModulationParams.Params.LoRa.SpreadingFactor +
      //                              28 + 16 * SX126x.PacketParams.Params.LoRa.CrcMode -
      //                              ( ( SX126x.PacketParams.Params.LoRa.HeaderType == LORA_PACKET_FIXED_LENGTH ) ? 20 : 0 ) ) /
      //                              ( double )( 4 * ( SX126x.ModulationParams.Params.LoRa.SpreadingFactor -
      //                              ( ( SX126x.ModulationParams.Params.LoRa.LowDatarateOptimize > 0 ) ? 2 : 0 ) ) ) ) *
      //                              ( ( SX126x.ModulationParams.Params.LoRa.CodingRate % 4 ) + 4 );
      //         double nPayload = 8 + ( ( tmp > 0 ) ? tmp : 0 );
      //         double tPayload = nPayload * ts;
      //         // Time on air
      //         double tOnAir = tPreamble + tPayload;
      //         // return milli seconds
      //         airTime = floor( tOnAir + 0.999 );
      //     }
      //     break;
      // default:
      // break;
      // }
      return airTime;
  
  }
  
  void SX126xRadioSend( uint8_t *buffer, uint8_t size )
  {
      SX126xSetDioIrqParams( IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_TX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_RADIO_NONE,
                             IRQ_RADIO_NONE );
  
      if( SX126xGetPacketType( ) == PACKET_TYPE_LORA )
      {
          SX126x.PacketParams.Params.LoRa.PayloadLength = size;
      }
      else
      {
          SX126x.PacketParams.Params.Gfsk.PayloadLength = size;
      }
      SX126xSetPacketParams( &SX126x.PacketParams );
  
      SX126xSendPayload( buffer, size, 0 );
      TimerSetValue( &SX126xTxTimeoutTimer, TxTimeout );
      TimerStart( &SX126xTxTimeoutTimer );
  }
  
  void SX126xRadioSleep( void )
  {
      SleepParams_t params = { .Value = 0 };
  
      params.Fields.WarmStart = 1;
      SX126xSetSleep( params );
  
      DelayMs( 2 );
  }
  
  void SX126xRadioStandby( void )
  {
      SX126xSetStandby( STDBY_RC );
  }
  
  void SX126xRadioRx( uint32_t timeout )
  {
      SX126xSetDioIrqParams( IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_RADIO_NONE,
                             IRQ_RADIO_NONE );
  
      if( timeout != 0 )
      {
          TimerSetValue( &SX126xRxTimeoutTimer, timeout );
          TimerStart( &SX126xRxTimeoutTimer );
      }
  
      if( RxContinuous == true )
      {
          SX126xSetRx( 0xFFFFFF ); // Rx Continuous
      }
      else
      {
          SX126xSetRx( RxTimeout << 6 );
      }
  }
  
  void SX126xRadioRxBoosted( uint32_t timeout )
  {
      SX126xSetDioIrqParams( IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_RADIO_ALL, //IRQ_RX_DONE | IRQ_RX_TX_TIMEOUT,
                             IRQ_RADIO_NONE,
                             IRQ_RADIO_NONE );
  
      if( timeout != 0 )
      {
          TimerSetValue( &SX126xRxTimeoutTimer, timeout );
          TimerStart( &SX126xRxTimeoutTimer );
      }
  
      if( RxContinuous == true )
      {
          SX126xSetRxBoosted( 0xFFFFFF ); // Rx Continuous
      }
      else
      {
          SX126xSetRxBoosted( RxTimeout << 6 );
      }
  }
  
  void SX126xRadioSetRxDutyCycle( uint32_t rxTime, uint32_t sleepTime )
  {
      SX126xSetRxDutyCycle( rxTime, sleepTime );
  }
  
  void SX126xRadioStartCad( void )
  {
      SX126xSetCad( );
  }
  
  void SX126xRadioTx( uint32_t timeout )
  {
      SX126xSetTx( timeout << 6 );
  }
  
  void SX126xRadioSetTxContinuousWave( uint32_t freq, int8_t power, uint16_t time )
  {
      SX126xSetRfFrequency( freq );
      SX126xSetRfTxPower( power );
      SX126xSetTxContinuousWave( );
  
      TimerSetValue( &SX126xTxTimeoutTimer, time  * 1e3 );
      TimerStart( &SX126xTxTimeoutTimer );
  }
  
  int16_t SX126xRadioRssi( RadioModems_t modem )
  {
      return SX126xGetRssiInst( );
  }
  
  void SX126xRadioWrite( uint16_t addr, uint8_t data )
  {
      SX126xWriteRegister( addr, data );
  }
  
  uint8_t SX126xRadioRead( uint16_t addr )
  {
      return SX126xReadRegister( addr );
  }
  
  void SX126xRadioWriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
  {
      SX126xWriteRegisters( addr, buffer, size );
  }
  
  void SX126xRadioReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
  {
      SX126xReadRegisters( addr, buffer, size );
  }
  
  void SX126xRadioWriteFifo( uint8_t *buffer, uint8_t size )
  {
      SX126xWriteBuffer( 0, buffer, size );
  }
  
  void SX126xRadioReadFifo( uint8_t *buffer, uint8_t size )
  {
      SX126xReadBuffer( 0, buffer, size );
  }
  
  void SX126xRadioSetMaxPayloadLength( RadioModems_t modem, uint8_t max )
  {
      if( modem == MODEM_LORA )
      {
          SX126x.PacketParams.Params.LoRa.PayloadLength = MaxPayloadLength = max;
          SX126xSetPacketParams( &SX126x.PacketParams );
      }
      else
      {
          if( SX126x.PacketParams.Params.Gfsk.HeaderType == RADIO_PACKET_VARIABLE_LENGTH )
          {
              SX126x.PacketParams.Params.Gfsk.PayloadLength = MaxPayloadLength = max;
              SX126xSetPacketParams( &SX126x.PacketParams );
          }
      }
  }
  
  void SX126xRadioSetPublicNetwork( bool enable )
  {
      SX126xRadioPublicNetwork.Current = SX126xRadioPublicNetwork.Previous = enable;
  
      SX126xRadioSetModem( MODEM_LORA );
      if( enable == true )
      {
          // Change LoRa modem SyncWord
          SX126xWriteRegister( SX126x_REG_LR_SYNCWORD, ( SX126x_LORA_MAC_PUBLIC_SYNCWORD >> 8 ) & 0xFF );
          SX126xWriteRegister( SX126x_REG_LR_SYNCWORD + 1, SX126x_LORA_MAC_PUBLIC_SYNCWORD & 0xFF );
      }
      else
      {
          // Change LoRa modem SyncWord
          SX126xWriteRegister( SX126x_REG_LR_SYNCWORD, ( SX126x_LORA_MAC_PRIVATE_SYNCWORD >> 8 ) & 0xFF );
          SX126xWriteRegister( SX126x_REG_LR_SYNCWORD + 1, SX126x_LORA_MAC_PRIVATE_SYNCWORD & 0xFF );
      }
  }
  
  uint32_t SX126xRadioGetWakeupTime( void )
  {
      return SX126xGetBoardTcxoWakeupTime( ) + SX126x_RADIO_WAKEUP_TIME;
  }
  
  void SX126xRadioOnTxTimeoutIrq( void* context )
  {
      if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->TxTimeout != NULL ) )
      {
          SX126xRadioEvents->TxTimeout( );
      }
  }
  
  void SX126xRadioOnRxTimeoutIrq( void* context )
  {
      if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->RxTimeout != NULL ) )
      {
          SX126xRadioEvents->RxTimeout( );
      }
  }
  
  void SX126xRadioOnDioIrq( void* context )
  {
      IrqFired = true;
      SX126xRadioIrqProcess();
  }
  
  void SX126xRadioIrqProcess( void )
  {
      if( IrqFired == true )
      {
          //CRITICAL_SECTION_BEGIN( );
          // Clear IRQ flag
          IrqFired = false;
          //CRITICAL_SECTION_END( );
  
          uint16_t irqRegs = SX126xGetIrqStatus( );
          SX126xClearIrqStatus( IRQ_RADIO_ALL );
  
          if( ( irqRegs & IRQ_TX_DONE ) == IRQ_TX_DONE )
          {
              TimerStop( &SX126xTxTimeoutTimer );
              //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
              SX126xSetOperatingMode( MODE_STDBY_RC );
              if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->TxDone != NULL ) )
              {
                  SX126xRadioEvents->TxDone( );
              }
          }
  
          if( ( irqRegs & IRQ_RX_DONE ) == IRQ_RX_DONE )
          {
              uint8_t size;
  
              TimerStop( &SX126xRxTimeoutTimer );
              if( RxContinuous == false )
              {
                  //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
                  SX126xSetOperatingMode( MODE_STDBY_RC );
  
                  // WORKAROUND - Implicit Header Mode Timeout Behavior, see DS_SX1261-2_V1.2 datasheet chapter 15.3
                  // RegRtcControl = @address 0x0902
                  SX126xWriteRegister( 0x0902, 0x00 );
                  // RegEventMask = @address 0x0944
                  SX126xWriteRegister( 0x0944, SX126xReadRegister( 0x0944 ) | ( 1 << 1 ) );
                  // WORKAROUND END
              }
              SX126xGetPayload( SX126xRadioRxPayload, &size , 255 );
              SX126xGetPacketStatus( &SX126xRadioPktStatus );
              if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->RxDone != NULL ) )
              {
                  SX126xRadioEvents->RxDone( SX126xRadioRxPayload, size, SX126xRadioPktStatus.Params.LoRa.RssiPkt, SX126xRadioPktStatus.Params.LoRa.SnrPkt );
              }
          }
  
          if( ( irqRegs & IRQ_CRC_ERROR ) == IRQ_CRC_ERROR )
          {
              if( RxContinuous == false )
              {
                  //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
                  SX126xSetOperatingMode( MODE_STDBY_RC );
              }
              if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->RxError ) )
              {
                  SX126xRadioEvents->RxError( );
              }
          }
  
          if( ( irqRegs & IRQ_CAD_DONE ) == IRQ_CAD_DONE )
          {
              //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
              SX126xSetOperatingMode( MODE_STDBY_RC );
              if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->CadDone != NULL ) )
              {
                  SX126xRadioEvents->CadDone( ( ( irqRegs & IRQ_CAD_ACTIVITY_DETECTED ) == IRQ_CAD_ACTIVITY_DETECTED ) );
              }
          }
  
          if( ( irqRegs & IRQ_RX_TX_TIMEOUT ) == IRQ_RX_TX_TIMEOUT )
          {
              if( SX126xGetOperatingMode( ) == MODE_TX )
              {
                  TimerStop( &SX126xTxTimeoutTimer );
                  //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
                  SX126xSetOperatingMode( MODE_STDBY_RC );
                  if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->TxTimeout != NULL ) )
                  {
                      SX126xRadioEvents->TxTimeout( );
                  }
              }
              else if( SX126xGetOperatingMode( ) == MODE_RX )
              {
                  TimerStop( &SX126xRxTimeoutTimer );
                  //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
                  SX126xSetOperatingMode( MODE_STDBY_RC );
                  if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->RxTimeout != NULL ) )
                  {
                      SX126xRadioEvents->RxTimeout( );
                  }
              }
          }
  
          if( ( irqRegs & IRQ_PREAMBLE_DETECTED ) == IRQ_PREAMBLE_DETECTED )
          {
              //__NOP( );
          }
  
          if( ( irqRegs & IRQ_SYNCWORD_VALID ) == IRQ_SYNCWORD_VALID )
          {
              //__NOP( );
          }
  
          if( ( irqRegs & IRQ_HEADER_VALID ) == IRQ_HEADER_VALID )
          {
              //__NOP( );
          }
  
          if( ( irqRegs & IRQ_HEADER_ERROR ) == IRQ_HEADER_ERROR )
          {
              TimerStop( &SX126xRxTimeoutTimer );
              if( RxContinuous == false )
              {
                  //!< Update operating mode state to a value lower than \ref MODE_STDBY_XOSC
                  SX126xSetOperatingMode( MODE_STDBY_RC );
              }
              if( ( SX126xRadioEvents != NULL ) && ( SX126xRadioEvents->RxTimeout != NULL ) )
              {
                  SX126xRadioEvents->RxTimeout( );
              }
          }
      }
  }
  