Lora1278 drive v4 4.2 explanation 2: drive multiple SX1278 chips

Recently, there is a project to make a micro gateway, which has requirements for size, volume, power consumption, cost and development cycle. The scheme is based on the previous Lora gateway for rapid research and development. The only difficulty is to drive multiple SX1278 with one MCU. By comparing sx12xxdrivers-v2 of SX1278 1.0 and sx12xxdrivers-v4 4.2, found v4 4.2 the driver is better modified, so it is decided to modify it in v4 4.2 add the function of supporting multiple SX1278 on the basis of driving.

sx12xxDrivers-V4.4.2 driver transplantation see: Lora1278 drive v4 4.2 explanation 1: drive transplantation

1, Train of thought

The general idea of adding multiple pieces of SX1278 is to use the object-oriented idea, for example:

SX1278Drivers RF1,RF2RF3;

RF1.init();

RF2.init();

RF3.init();

Wait, it's easy to implement. How to implement it in embedded C development? The ideas are as follows:

Abstract out a set of functions with the same function, abstract out the functions with different functions and define the function pointer, define the global variable and function pointer as the structure, define the RF1/RF2/RF3 entity, and take the entity pointer as the input parameter of the set of abstract functions. Similar to the following:

    net_work_rf_process(&s_tNetWorkRF1);                       //Wireless thread
    net_work_protocol_analysis_process(&s_tNetWorkRF1);        //Protocol analysis
    father_station_routing_table_maintenance(&s_tNetWorkRF1);  //Parent node routing point maintenance
    net_work_logic_process(&s_tNetWorkRF1);                    //Networking logic
    net_work_check_sx1278_process(&s_tNetWorkRF1);             //SX1278 maintenance thread
    s_tNetWorkRF1.ptRadio->IrqProcess(&s_tNetWorkRF1.tSX1276); //DIO0 handler

2, Drive modification

sx12xxDrivers-V4.4.2 for details, please see the previous blog( Driving explanation I ), the general idea is as follows:

1,radio.h abstract the driver interface;

/*!
 * \brief Radio driver definition
 */
struct Radio_s
{
    /*!
     * \brief Initializes the radio
     *
     * \param [IN] events Structure containing the driver callback functions
     */
    void    ( *Init )( RadioEvents_t *events );
    /*!
     * Return current radio status
     *
     * \param status Radio status.[RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING]
     */
    RadioState_t ( *GetStatus )( void );
    /*!
     * \brief Configures the radio with the given modem
     *
     * \param [IN] modem Modem to be used [0: FSK, 1: LoRa]
     */
    void    ( *SetModem )( RadioModems_t modem );
    /*!
     * \brief Sets the channel frequency
     *
     * \param [IN] freq         Channel RF frequency
     */
    void    ( *SetChannel )( uint32_t freq );
    /*!
     * \brief Checks if the channel is free for the given time
     *
     * \param [IN] modem      Radio 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    ( *IsChannelFree )( 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 ( *Random )( 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    ( *SetRxConfig )( 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    ( *SetTxConfig )( 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    ( *CheckRfFrequency )( 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  ( *TimeOnAir )( 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    ( *Send )( uint8_t *buffer, uint8_t size );
    /*!
     * \brief Sets the radio in sleep mode
     */
    void    ( *Sleep )( void );
    /*!
     * \brief Sets the radio in standby mode
     */
    void    ( *Standby )( void );
    /*!
     * \brief Sets the radio in reception mode for the given time
     * \param [IN] timeout Reception timeout [ms]
     *                     [0: continuous, others timeout]
     */
    void    ( *Rx )( uint32_t timeout );
    /*!
     * \brief Start a Channel Activity Detection
     */
    void    ( *StartCad )( 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    ( *SetTxContinuousWave )( uint32_t freq, int8_t power, uint16_t time );
    /*!
     * \brief Reads the current RSSI value
     *
     * \retval rssiValue Current RSSI value in [dBm]
     */
    int16_t ( *Rssi )( RadioModems_t modem );
    /*!
     * \brief Writes the radio register at the specified address
     *
     * \param [IN]: addr Register address
     * \param [IN]: data New register value
     */
    void    ( *Write )( 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 ( *Read )( uint16_t addr );
    /*!
     * \brief Writes multiple radio registers starting at address
     *
     * \param [IN] addr   First Radio register address
     * \param [IN] buffer Buffer containing the new register's values
     * \param [IN] size   Number of registers to be written
     */
    void    ( *WriteBuffer )( 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    ( *ReadBuffer )( 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    ( *SetMaxPayloadLength )( 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    ( *SetPublicNetwork )( 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  ( *GetWakeupTime )( void );
    /*!
     * \brief Process radio irq
     */
    void ( *IrqProcess )( void );
    /*
     * The next functions are available only on SX126x radios.
     */
    /*!
     * \brief Sets the radio in reception mode with Max LNA gain for the given time
     *
     * \remark Available on SX126x radios only.
     *
     * \param [IN] timeout Reception timeout [ms]
     *                     [0: continuous, others timeout]
     */
    void    ( *RxBoosted )( uint32_t timeout );
    /*!
     * \brief Sets the Rx duty cycle management parameters
     *
     * \remark Available on SX126x radios only.
     *
     * \param [in]  rxTime        Structure describing reception timeout value
     * \param [in]  sleepTime     Structure describing sleep timeout value
     */
    void ( *SetRxDutyCycle ) ( uint32_t rxTime, uint32_t sleepTime );
};

2,sx1276.c) realize the interface;

3. The user interacts with the bottom layer through five events, which are realized by the user and called by the bottom driver;

/*!
 * \brief Radio driver callback functions
 */
typedef struct
{
    /*!
     * \brief  Tx Done callback prototype.
     */
    void    ( *TxDone )( void );
    /*!
     * \brief  Tx Timeout callback prototype.
     */
    void    ( *TxTimeout )( void );
    /*!
     * \brief Rx Done callback prototype.
     *
     * \param [IN] payload Received buffer pointer
     * \param [IN] size    Received buffer size
     * \param [IN] rssi    RSSI value computed while receiving the frame [dBm]
     * \param [IN] snr     SNR value computed while receiving the frame [dB]
     *                     FSK : N/A ( set to 0 )
     *                     LoRa: SNR value in dB
     */
    void    ( *RxDone )(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr );
    /*!
     * \brief  Rx Timeout callback prototype.
     */
    void    ( *RxTimeout )( void );
    /*!
     * \brief Rx Error callback prototype.
     */
    void    ( *RxError )( void );
    /*!
     * \brief  FHSS Change Channel callback prototype.
     *
     * \param [IN] currentChannel   Index number of the current channel
     */
    void ( *FhssChangeChannel )( uint8_t currentChannel );

    /*!
     * \brief CAD Done callback prototype.
     *
     * \param [IN] channelDetected    Channel Activity detected during the CAD
     */
    void ( *CadDone ) ( bool channelActivityDetected );
}RadioEvents_t;

4. DIO interrupt handling

Lora has DIO0-DIO5 to indicate the current chip status (faster than SPI query). The underlying driver has the corresponding processing function:

SX1276OnDio0Irq,

SX1276OnDio1Irq,

SX1276OnDio2Irq,

SX1276OnDio3Irq,

SX1276OnDio4Irq

DIO5 no,

These five DIO processing functions are put into an array in the driver,

/*!
 * Hardware DIO IRQ callback initialization
 */
static DioIrqHandler *DioIrq[] = { SX1276OnDio0Irq, SX1276OnDio1Irq,
                                   SX1276OnDio2Irq, SX1276OnDio3Irq,
                                   SX1276OnDio4Irq, NULL };

Then you need an external implementation: void SX1276IoIrqInit(DioIrqHandler **irqHandlers)

View examples:

void SX1276IoIrqInit( DioIrqHandler **irqHandlers )
{
    GpioSetInterrupt( &SX1276.DIO0, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[0] );
    GpioSetInterrupt( &SX1276.DIO1, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[1] );
    GpioSetInterrupt( &SX1276.DIO2, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[2] );
    GpioSetInterrupt( &SX1276.DIO3, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[3] );
    GpioSetInterrupt( &SX1276.DIO4, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[4] );
    GpioSetInterrupt( &SX1276.DIO5, LL_EXTI_MODE_IT, LL_EXTI_TRIGGER_RISING, IRQ_HIGH_PRIORITY, irqHandlers[5] );
}

The function is to put the interrupt processing function into the corresponding IO port interrupt processing function. The implementation of the official routine is a little complicated, because it writes a general architecture (I personally don't think it's necessary. In short, remember one sentence: put the IO port interrupt into the corresponding IO port interrupt processing function). The above ideas can also be driven by v2 The query idea of 1.0 is very simple: query the IO port status. If it is valid, call the corresponding function. The implementation is as follows:

Implement the function IrqProcess (the input parameter void* ptRFStruct is to support multi chip, you can directly implement it with entities in the function):

#if (0 == LORA_IRQ_IS_ENABLE)
void IrqProcess( void* ptRFStruct )
{
	SX1276_t* ptSX1276 = (SX1276_t*)ptRFStruct;
    DioIrqHandler*  ptIrqHandler = NULL;
    if(NULL == ptSX1276){
        return;
    }

    if(GpioRead(&ptSX1276->DIO0)){
        ptIrqHandler=ptSX1276->DioIrq[0];
        if(NULL != ptIrqHandler){
            ptIrqHandler(ptSX1276,NULL);
        }
    }
//    if(GpioRead(&ptSX1276->DIO1)){
//        ptIrqHandler=ptSX1276->DioIrq[1];
//        if(NULL != ptIrqHandler){
//            ptIrqHandler(ptSX1276,NULL);
//        }
//    }
//    if(GpioRead(&ptSX1276->DIO2)){
//        ptIrqHandler=ptSX1276->DioIrq[2];
//        if(NULL != ptIrqHandler){
//            ptIrqHandler(ptSX1276,NULL);
//        }
//    }
//    if(GpioRead(&ptSX1276->DIO3)){
//        ptIrqHandler=ptSX1276->DioIrq[3];
//        if(NULL != ptIrqHandler){
//            ptIrqHandler(ptSX1276,NULL);
//        }
//    }
//    if(GpioRead(&ptSX1276->DIO4)){
//        ptIrqHandler=ptSX1276->DioIrq[4];
//        if(NULL != ptIrqHandler){
//            ptIrqHandler(ptSX1276,NULL);
//        }
//    }
//    if(GpioRead(&ptSX1276->DIO5)){
//        ptIrqHandler=ptSX1276->DioIrq[5];
//        if(NULL != ptIrqHandler){
//            ptIrqHandler(ptSX1276,NULL);
//        }
//    }
}
#endif

Because I test the driver and only use DIO0, other processing functions are shielded.

5. Timeout handler

The driver provides the timeout processing function sx127ontimeoutirq, which defines three timeout events:

/*!
 * Tx and Rx timers
 */
TimerEvent_t TxTimeoutTimer;
TimerEvent_t RxTimeoutTimer;
TimerEvent_t RxTimeoutSyncWord;

Driver initialization:

    // Initialize driver timeout timers
    TimerInit( &TxTimeoutTimer, SX1276OnTimeoutIrq );
    TimerInit( &RxTimeoutTimer, SX1276OnTimeoutIrq );
    TimerInit( &RxTimeoutSyncWord, SX1276OnTimeoutIrq );

It is mainly processed when receiving, sending and receiving synchronization words timeout. If low-power mode (MCU sleep) is not used here, it can be ignored directly. At the same time, make corresponding logic in the application layer, or directly call this timeout processing function when the application layer timeout.

3, Implement driver and support multi chip

1. View sx1276 Global variables of C:

/*
 * Private global variables
 */

/*!
 * Radio callbacks variable
 */
static RadioEvents_t *RadioEvents;

/*!
 * Reception buffer
 */
static uint8_t RxTxBuffer[RX_BUFFER_SIZE];

/*
 * Public global variables
 */

/*!
 * Radio hardware and global parameters
 */
SX1276_t SX1276;

/*!
 * Hardware DIO IRQ callback initialization
 */
DioIrqHandler *DioIrq[] = { SX1276OnDio0Irq, SX1276OnDio1Irq,
                            SX1276OnDio2Irq, SX1276OnDio3Irq,
                            SX1276OnDio4Irq, NULL };

/*!
 * Tx and Rx timers
 */
TimerEvent_t TxTimeoutTimer;
TimerEvent_t RxTimeoutTimer;
TimerEvent_t RxTimeoutSyncWord;

The analysis is as follows:

1. The RadioEvents variable pointer is event processing, which needs to be implemented by the user, and then passed in and called by the underlying driver. The event processing of each chip must be different, so RadioEvents is placed in struct SX1276_s in the structure;

2,static uint8_t RxTxBuffer[RX_BUFFER_SIZE]; Receive and send cache, put it into struct SX1276_s in the structure;

3,SX1276_t SX1276; Removed, defined by the user layer, as the input parameter of the driver layer API;

4. DioIrq remains unchanged, the of DIO interrupt processing is modified, and sx1276 is added_ T * pointer input parameter;

5. Three timeout event variables are processed and put into struct SX1276_s in the structure;

6,struct SX1276_s structure is added with power control pin and RF switch control pin, which are modified as follows:

/*!
 * Radio hardware and global parameters
 */
typedef struct SX1276_s
{
    //GPIO
    Gpio_t        Reset;
    Gpio_t        Power;
    Gpio_t        RFswitch;
    Gpio_t        DIO0;
    Gpio_t        DIO1;
    Gpio_t        DIO2;
    Gpio_t        DIO3;
    Gpio_t        DIO4;
    Gpio_t        DIO5;
    Spi_t         Spi;
    RadioSettings_t Settings;

    //event
    RadioEvents_t *RadioEvents;
    //cache
    uint8_t RxTxBuffer[RX_BUFFER_SIZE];
    //Timeout event
    TimerEvent_t TxTimeoutTimer;
    TimerEvent_t RxTimeoutTimer;
    TimerEvent_t RxTimeoutSyncWord;
    //IO interrupt
#if (0 == LORA_IRQ_IS_ENABLE)
    DioIrqHandler **DioIrq;
#endif
}SX1276_t;

7,Spi_ Add SpiId variable in T and modify it as follows;

/*!
 * SPI object type definition
 */
typedef struct Spi_s
{
    SPI_TypeDef* SpiId;
    Gpio_t Mosi;
    Gpio_t Miso;
    Gpio_t Sclk;
    Gpio_t Nss;
}Spi_t;

8. Interface modification

Add void * input parameter to the corresponding interface because of radio H is the external interface document, sx1276 H includes radio h. So struct SX1276_s type pair radio H is invisible,

Modified radio H abstract interface:

/*!
 * \brief Radio driver definition
 */
struct Radio_s
{
    /*!
     * \brief Initializes the radio
     *
     * \param [IN] events Structure containing the driver callback functions
     */
    void    ( *Init )(void* ptSX1276, RadioEvents_t *events );
    /*!
     * Return current radio status
     *
     * \param status Radio status.[RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING]
     */
    RadioState_t ( *GetStatus )( void* ptSX1276 );
    /*!
     * \brief Configures the radio with the given modem
     *
     * \param [IN] modem Modem to be used [0: FSK, 1: LoRa]
     */
    void    ( *SetModem )(void* ptSX1276, RadioModems_t modem );
    /*!
     * \brief Sets the channel frequency
     *
     * \param [IN] freq         Channel RF frequency
     */
    void    ( *SetChannel )(void* ptSX1276, uint32_t freq );
    /*!
     * \brief Checks if the channel is free for the given time
     *
     * \param [IN] modem      Radio 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    ( *IsChannelFree )(void* ptSX1276, 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 ( *Random )( void* ptSX1276 );
    /*!
     * \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    ( *SetRxConfig )(void* ptSX1276, 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    ( *SetTxConfig )(void* ptSX1276, 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    ( *CheckRfFrequency )(void* ptSX1276, 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  ( *TimeOnAir )(void* ptSX1276, 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    ( *Send )(void* ptSX1276, uint8_t *buffer, uint8_t size );
    /*!
     * \brief Sets the radio in sleep mode
     */
    void    ( *Sleep )( void* ptSX1276 );
    /*!
     * \brief Sets the radio in standby mode
     */
    void    ( *Standby )( void* ptSX1276 );
    /*!
     * \brief Sets the radio in reception mode for the given time
     * \param [IN] timeout Reception timeout [ms]
     *                     [0: continuous, others timeout]
     */
    void    ( *Rx )(void* ptSX1276, uint32_t timeout );
    /*!
     * \brief Start a Channel Activity Detection
     */
    void    ( *StartCad )( void* ptSX1276 );
    /*!
     * \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    ( *SetTxContinuousWave )(void* ptSX1276, uint32_t freq, int8_t power, uint16_t time );
    /*!
     * \brief Reads the current RSSI value
     *
     * \retval rssiValue Current RSSI value in [dBm]
     */
    int16_t ( *Rssi )(void* ptSX1276, RadioModems_t modem );
    /*!
     * \brief Writes the radio register at the specified address
     *
     * \param [IN]: addr Register address
     * \param [IN]: data New register value
     */
    void    ( *Write )(void* ptSX1276, 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 ( *Read )(void* ptSX1276, uint16_t addr );
    /*!
     * \brief Writes multiple radio registers starting at address
     *
     * \param [IN] addr   First Radio register address
     * \param [IN] buffer Buffer containing the new register's values
     * \param [IN] size   Number of registers to be written
     */
    void    ( *WriteBuffer )(void* ptSX1276, 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    ( *ReadBuffer )(void* ptSX1276, 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    ( *SetMaxPayloadLength )(void* ptSX1276, 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    ( *SetPublicNetwork )(void* ptSX1276, 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  ( *GetWakeupTime )(void* ptSX1276 );
    /*!
     * \brief Process radio irq
     */
    void ( *IrqProcess )( void* ptSX1276 );
    /*
     * The next functions are available only on SX126x radios.
     */
    /*!
     * \brief Sets the radio in reception mode with Max LNA gain for the given time
     *
     * \remark Available on SX126x radios only.
     *
     * \param [IN] timeout Reception timeout [ms]
     *                     [0: continuous, others timeout]
     */
    void    ( *RxBoosted )(void* ptSX1276, uint32_t timeout );
    /*!
     * \brief Sets the Rx duty cycle management parameters
     *
     * \remark Available on SX126x radios only.
     *
     * \param [in]  rxTime        Structure describing reception timeout value
     * \param [in]  sleepTime     Structure describing sleep timeout value
     */
    void ( *SetRxDutyCycle ) (void* ptSX1276, uint32_t rxTime, uint32_t sleepTime );
};

Defined in sx1276 board c:

/*!
 * Radio driver structure initialization
 */
const struct Radio_s Radio =
{
    SX1276Init,
    SX1276GetStatus,
    SX1276SetModem,
    SX1276SetChannel,
    SX1276IsChannelFree,
    SX1276Random,
    SX1276SetRxConfig,
    SX1276SetTxConfig,
    SX1276CheckRfFrequency,
    SX1276GetTimeOnAir,
    SX1276Send,
    SX1276SetSleep,
    SX1276SetStby,
    SX1276SetRx,
    SX1276StartCad,
    SX1276SetTxContinuousWave,
    SX1276ReadRssi,
    SX1276Write,
    SX1276Read,
    SX1276WriteBuffer,
    SX1276ReadBuffer,
    SX1276SetMaxPayloadLength,
    SX1276SetPublicNetwork,
    SX1276GetWakeupTime,
#if (0 == LORA_IRQ_IS_ENABLE)
    IrqProcess,
#else
    NULL, // void ( *IrqProcess )( void )
#endif
    NULL, // void ( *RxBoosted )( uint32_t timeout ) - SX126x Only
    NULL, // void ( *SetRxDutyCycle )( uint32_t rxTime, uint32_t sleepTime ) - SX126x Only
};

sx1276.c interface implementation modification. Take sx127init as an example, other function modifications are similar:

void SX1276Init(void* ptRFStruct, RadioEvents_t *events )
{
    uint8_t i;

    SX1276_t* ptSX1276 = (SX1276_t*)ptRFStruct;
    if(NULL == ptSX1276){
        return;
    }

    ptSX1276->RadioEvents = events;

    // Initialize driver timeout timers
    TimerInit( &ptSX1276->TxTimeoutTimer, SX1276OnTimeoutIrq );
    TimerInit( &ptSX1276->RxTimeoutTimer, SX1276OnTimeoutIrq );
    TimerInit( &ptSX1276->RxTimeoutSyncWord, SX1276OnTimeoutIrq );

    //TRACE_DEBUG("SX1276Init=%d\r\n",1);
    SX1276Reset( ptSX1276 );
    //TRACE_DEBUG("SX1276Init=%d\r\n",2);
    while(0x6C != SX1276Read(ptSX1276,0x06)){
        TRACE_ERROR("Hard SPI Err!\r\n");
        my_delay_ms(500);
    }
    //TRACE_DEBUG("SX1276Init=%d\r\n",3);

    RxChainCalibration(ptSX1276 );

    SX1276SetOpMode(ptSX1276, RF_OPMODE_SLEEP );

#if (LORA_IRQ_IS_ENABLE)
    SX1276IoIrqInit(ptSX1276, DioIrq );
#else
    ptSX1276->DioIrq = DioIrq;
#endif

    for( i = 0; i < sizeof( RadioRegsInit ) / sizeof( RadioRegisters_t ); i++ )
    {
        SX1276SetModem(ptSX1276, RadioRegsInit[i].Modem );
        SX1276Write(ptSX1276, RadioRegsInit[i].Addr, RadioRegsInit[i].Value );
    }

    SX1276SetModem(ptSX1276, MODEM_FSK );

    ptSX1276->Settings.State = RF_IDLE;

    //TRACE_DEBUG("SX1276Init irq= %x\r\n",SX1276Read(REG_LR_IRQFLAGS));
}

4, Application layer modification

1. First define different entities:

static net_work_t   s_tNetWorkRF1;
static net_work_t   s_tNetWorkRF2;
static net_work_t   s_tNetWorkRF3;

2. Implement different event handling functions:

static void OnTxDoneRF1( void )
{
    SET_EVENT(&s_tNetWorkRF1.tRfTxDone);
}

static void OnRxDoneRF1(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
     if(WAIT_EVENT(&s_tNetWorkRF1.tRfRxDone) || size > NET_WOEK_RF_BUFFER_SIZE || NULL == payload){
        return;
    }
    memcpy(s_tNetWorkRF1.chRadioRxBuffer,payload,size);
    s_tNetWorkRF1.hwRadioRxSize = size;
    s_tNetWorkRF1.chPacketRssiValue = rssi;
    s_tNetWorkRF1.chSnrValue = snr;
    SET_EVENT(&s_tNetWorkRF1.tRfRxDone);
}

static void OnTxTimeoutRF1( void )
{
    //Radio.Sleep( );
}

static void OnRxTimeoutRF1( void )
{
    //Radio.Sleep( );
}

static void OnRxErrorRF1( void )
{
    //Radio.Sleep( );
}

static void OnTxDoneRF2( void )
{
    SET_EVENT(&s_tNetWorkRF2.tRfTxDone);
}

static void OnRxDoneRF2(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
     if(WAIT_EVENT(&s_tNetWorkRF2.tRfRxDone) || size > NET_WOEK_RF_BUFFER_SIZE || NULL == payload){
        return;
    }
    memcpy(s_tNetWorkRF2.chRadioRxBuffer,payload,size);
    s_tNetWorkRF2.hwRadioRxSize = size;
    s_tNetWorkRF2.chPacketRssiValue = rssi;
    s_tNetWorkRF2.chSnrValue = snr;
    SET_EVENT(&s_tNetWorkRF2.tRfRxDone);
}

static void OnTxTimeoutRF2( void )
{
    //Radio.Sleep( );
}

static void OnRxTimeoutRF2( void )
{
    //Radio.Sleep( );
}

static void OnRxErrorRF2( void )
{
    //Radio.Sleep( );
}

static void OnTxDoneRF3( void )
{
    SET_EVENT(&s_tNetWorkRF3.tRfTxDone);
}

static void OnRxDoneRF3(uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
     if(WAIT_EVENT(&s_tNetWorkRF3.tRfRxDone) || size > NET_WOEK_RF_BUFFER_SIZE || NULL == payload){
        return;
    }
    memcpy(s_tNetWorkRF3.chRadioRxBuffer,payload,size);
    s_tNetWorkRF3.hwRadioRxSize = size;
    s_tNetWorkRF3.chPacketRssiValue = rssi;
    s_tNetWorkRF3.chSnrValue = snr;
    SET_EVENT(&s_tNetWorkRF3.tRfRxDone);
}

static void OnTxTimeoutRF3( void )
{
    //Radio.Sleep( );
}

static void OnRxTimeoutRF3( void )
{
    //Radio.Sleep( );
}

static void OnRxErrorRF3( void )
{
    //Radio.Sleep( );
}
/*!
 * Radio events function pointer
 */
static const RadioEvents_t s_RadioEventsRF1 = {
    .TxDone             = OnTxDoneRF1,
    .TxTimeout          = OnTxTimeoutRF1,
    .RxDone             = OnRxDoneRF1,
    .RxTimeout          = OnRxTimeoutRF1,
    .RxError            = OnRxErrorRF1,
    .FhssChangeChannel  = NULL,
    .CadDone            = NULL,
};
static const RadioEvents_t s_RadioEventsRF2 = {
    .TxDone             = OnTxDoneRF2,
    .TxTimeout          = OnTxTimeoutRF2,
    .RxDone             = OnRxDoneRF2,
    .RxTimeout          = OnRxTimeoutRF2,
    .RxError            = OnRxErrorRF2,
    .FhssChangeChannel  = NULL,
    .CadDone            = NULL,
};
static const RadioEvents_t s_RadioEventsRF3 = {
    .TxDone             = OnTxDoneRF3,
    .TxTimeout          = OnTxTimeoutRF3,
    .RxDone             = OnRxDoneRF3,
    .RxTimeout          = OnRxTimeoutRF3,
    .RxError            = OnRxErrorRF3,
    .FhssChangeChannel  = NULL,
    .CadDone            = NULL,
};

3. Initialization is as follows:

void user_net_work_init(net_work_app_api_t *ptAppApiRF1,net_work_app_api_t *ptAppApiRF2,net_work_app_api_t *ptAppApiRF3)
{
    srand(s_chRandomNumber[0]%s_chRandomNumber[1]+s_chRandomNumber[2]%s_chRandomNumber[3]);     //Random number seed
    TRACE_DEBUG("srand=%d\r\n",(s_chRandomNumber[0]%s_chRandomNumber[1]+s_chRandomNumber[2]%s_chRandomNumber[3]));

    memset((uint8_t*)&s_tNetWorkRF1,0x00,sizeof(net_work_t));
    memset((uint8_t*)&s_tNetWorkRF2,0x00,sizeof(net_work_t));
    memset((uint8_t*)&s_tNetWorkRF3,0x00,sizeof(net_work_t));

    s_tNetWorkRF1.tChannel = RF_DATA_CHANNEL_BASE_STATION;
    s_tNetWorkRF2.tChannel = RF_DATA_CHANNEL_HANDHELD_DEVICE;
    s_tNetWorkRF3.tChannel = RF_DATA_CHANNEL_SENSOR_1;

    SX1276IoInit(&s_tNetWorkRF1.tSX1276,s_tNetWorkRF1.tChannel);
    SX1276IoInit(&s_tNetWorkRF2.tSX1276,s_tNetWorkRF2.tChannel);
    SX1276IoInit(&s_tNetWorkRF3.tSX1276,s_tNetWorkRF3.tChannel);

    SpiInit(&s_tNetWorkRF1.tSX1276.Spi,RF1_SPI);
    SpiInit(&s_tNetWorkRF2.tSX1276.Spi,RF2_SPI);
    SpiInit(&s_tNetWorkRF3.tSX1276.Spi,RF3_SPI);

    net_work_init(&s_tNetWorkRF1,s_chRfBufferRF1,sizeof(s_chRfBufferRF1),(RadioEvents_t*)&s_RadioEventsRF1,ptAppApiRF1);
    TRACE_DEBUG("RF1 init success\r\n");
    net_work_init(&s_tNetWorkRF2,s_chRfBufferRF2,sizeof(s_chRfBufferRF2),(RadioEvents_t*)&s_RadioEventsRF2,ptAppApiRF2);
    TRACE_DEBUG("RF2 init success\r\n");
    net_work_init(&s_tNetWorkRF3,s_chRfBufferRF3,sizeof(s_chRfBufferRF3),(RadioEvents_t*)&s_RadioEventsRF3,ptAppApiRF3);
    TRACE_DEBUG("RF3 init success\r\n");


}

4. The idea of application interface is the same as that of driver

Example:

/*************************************  APP  **********************************/
bool net_work_service_process(void)
{
    net_work_rf_process(&s_tNetWorkRF1);                       //Wireless thread
    net_work_protocol_analysis_process(&s_tNetWorkRF1);        //Protocol analysis
    father_station_routing_table_maintenance(&s_tNetWorkRF1);  //Parent node routing point maintenance
    net_work_logic_process(&s_tNetWorkRF1);                    //Networking logic
    net_work_check_sx1278_process(&s_tNetWorkRF1);             //SX1278 maintenance thread
    s_tNetWorkRF1.ptRadio->IrqProcess(&s_tNetWorkRF1.tSX1276); //DIO0 handler

    net_work_rf_process(&s_tNetWorkRF2);                       //Wireless thread
    net_work_protocol_analysis_process(&s_tNetWorkRF2);        //Protocol analysis
    father_station_routing_table_maintenance(&s_tNetWorkRF2);  //Parent node routing point maintenance
    net_work_logic_process(&s_tNetWorkRF2);                    //Networking logic
    net_work_check_sx1278_process(&s_tNetWorkRF2);             //SX1278 maintenance thread
    s_tNetWorkRF2.ptRadio->IrqProcess(&s_tNetWorkRF2.tSX1276); //DIO0 handler

    net_work_rf_process(&s_tNetWorkRF3);                       //Wireless thread
    net_work_protocol_analysis_process(&s_tNetWorkRF3);        //Protocol analysis
    father_station_routing_table_maintenance(&s_tNetWorkRF3);  //Parent node routing point maintenance
    net_work_logic_process(&s_tNetWorkRF3);                    //Networking logic
    net_work_check_sx1278_process(&s_tNetWorkRF3);             //SX1278 maintenance thread
    s_tNetWorkRF3.ptRadio->IrqProcess(&s_tNetWorkRF3.tSX1276); //DIO0 handler

    return true;
}

Read RSSI application layer API:

/********************************  SX1278 Maintenance thread****************************/
static bool  net_work_check_rf_chip_state(net_work_t *ptNetWork)
{
    int16_t             iTemp   = 0;
    static  uint8_t     chNum   = 0;

    if(RF_IDLE == ptNetWork->ptRadio->GetStatus(&ptNetWork->tSX1276)){
        chNum = 0;
        return true;
    }

    iTemp = ptNetWork->ptRadio->Rssi(&ptNetWork->tSX1276,MODEM_LORA);
    TRACE_DEBUG("RF rx rssi = %d\r\n",iTemp);

    if(iTemp <= (-160)){
        chNum++;
        TRACE_ERROR("RF rx rssi error = %d\r\n",iTemp);
        if(chNum >= 5){
            chNum = 0;
            return false;
        }
    }else{
        chNum=0;
    }
    return true;
}

Read RSSI driver layer API:

int16_t SX1276ReadRssi(void* ptRFStruct, RadioModems_t modem )
{
    int16_t rssi = 0;
	SX1276_t* ptSX1276 = (SX1276_t*)ptRFStruct;
    if(NULL == ptSX1276){
        return 0;
    }
    switch( modem )
    {
    case MODEM_FSK:
        rssi = -( SX1276Read(ptSX1276, REG_RSSIVALUE ) >> 1 );
        break;
    case MODEM_LORA:
        if( ptSX1276->Settings.Channel > RF_MID_BAND_THRESH )
        {
            rssi = RSSI_OFFSET_HF + SX1276Read(ptSX1276, REG_LR_RSSIVALUE );
        }
        else
        {
            rssi = RSSI_OFFSET_LF + SX1276Read(ptSX1276, REG_LR_RSSIVALUE );
        }
        break;
    default:
        rssi = -1;
        break;
    }
    return rssi;
}

The operation effect is as follows:

Keywords: IoT lora

Added by twm on Thu, 17 Feb 2022 05:27:36 +0200