Lora1278驅動V4.4.2講解一:驅動移植

注意,Lora1278驅動sx12xxDrivers-V2.1.0,原廠已經不更新和維護了,反饋的任何軟件問題,

原廠就是一句話升級新的驅動,新驅動下載地址:https://github.com/Lora-net/LoRaMac-node

1276的驅動可以用與1278:

頻段劃分:

 其中頻段1屬於HF,頻段2和3屬於LF,在看驅動時候會用到。

射頻輸出:

TCXO設置:

一、驅動架構講解

由於Lora芯片有好幾款,官方本意是通過Radio.h文件抽象出接口層,對上有統一的接口,對

用戶屏蔽掉硬件層差異,首先來看Radio的接口:

/*!
 * \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 );
};

以上是用戶可見的的接口,由於芯片還有一些事件,比如:接收完成數據,發送完成數據,

接收超時等等,再看對事件的抽象:

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;

官方的庫文件結合了DIO0和SPI(必須有NSS),來驅動Lora,其中SPI採用的查詢模式,

至於是硬SPI還是軟SPI無所謂。

DIO0則在接收時候映射爲接收完成輸出,在發送時候映射爲發送完成輸出,它們都是電平

變化輸出,不是脈衝變化,然後有用戶手動復位。

二、移植工作

1、打開官方文件,由於我用的是Lora1278,因此把畫框的幾個文件拷貝到工程裏面:
/

2、平臺相關的

我們需要把如下幾個文件加到工程:

其中:

fifo.c 和fifo.h在LoRaMac-node-develop\src\system文件夾,不要改動;

spi.c是我手動自己新建的文件,spi.h在LoRaMac-node-develop\src\system文件夾,需要改動;

gpio.c和gpio.h在LoRaMac-node-develop\src\system文件夾,需要改動;

timer.c和timer.h在LoRaMac-node-develop\src\system文件夾,需要改動;

sx1276-board.c在LoRaMac-node-develop\src\boards\B-L072Z-LRWAN1拷貝,

sx1276-board.h在LoRaMac-node-develop\src\boards拷貝

main.c參考LoRaMac-node-develop\src\apps\ping-pong\NucleoL476

board-config.h從LoRaMac-node-develop\src\boards\NucleoL476拷貝

還有一個文件:rtc-board.c和rtc-board.h,這個暫時不講。

3、移植工作

1)fifo文件

前面說了,無需改動,官方提供的;

2)spi

打開SPI.h文件,你會發現裏面有好多函數,其實底層的驅動只用到了一個函數

uint16_t SpiInOut( Spi_t *obj, uint16_t outData ),調用在sx1276.c裏面:

void SX1276WriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );

    SpiInOut( &SX1276.Spi, addr | 0x80 );
    for( i = 0; i < size; i++ )
    {
        SpiInOut( &SX1276.Spi, buffer[i] );
    }

    //NSS = 1;
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

void SX1276ReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );

    SpiInOut( &SX1276.Spi, addr & 0x7F );

    for( i = 0; i < size; i++ )
    {
        buffer[i] = SpiInOut( &SX1276.Spi, 0 );
    }

    //NSS = 1;
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

我的實現:雖然返回的是uint16_t類型,但是底層還是用的uint8_t,因此SPI使用的是一個字節模式

uint16_t SpiInOut( Spi_t *obj, uint16_t outData )
{
	uint8_t RxData=0;

    if(NULL == obj){
        return 0;
    }

    if(NULL == obj->SpiId){
        return 0;
    }

    while(!LL_SPI_IsActiveFlag_TXE(obj->SpiId));
    LL_SPI_TransmitData8(obj->SpiId,outData);
    //Soft_delay_us(100);
    while(!LL_SPI_IsActiveFlag_TXE(obj->SpiId));
    while(!LL_SPI_IsActiveFlag_RXNE(obj->SpiId));
    RxData = LL_SPI_ReceiveData8(obj->SpiId);
    return RxData;
}

說說我的改動,原來的文件是傳入了:

/*!
 * SPI peripheral ID
 */
typedef enum
{
    SPI_1,
    SPI_2,
}SpiId_t;

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

我改爲了:(你們可以按着官網自己做)

/*!
 * 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;

然後我在SPI裏面再添加一個函數:

void SpiInit(Spi_t *obj,SPI_TypeDef *tSpiX)
{
    if(NULL == obj || NULL == tSpiX){
        return;
    }

    obj->SpiId = tSpiX;
}

 SPI的硬件初始化自己在main裏面實現。

3)gpio這個就要實現多個函數了:
IO定義在board-config.h

由於1278只有一個輸出控制開關,其它的全部刪掉

#define RADIO_RESET                                 PC_6

#define RADIO_NSS                                   PA_4

#define RADIO_DIO_0                                 PA_8
#define RADIO_DIO_1                                 PB_11
#define RADIO_DIO_2                                 PB_10
#define RADIO_DIO_3                                 PB_2
#define RADIO_DIO_4                                 PB_1
#define RADIO_DIO_5                                 PB_0

#define RADIO_ANT_SWITCH_POWER                      PA_2

有人會感到奇怪,PC_6按着以前的寫法不應該是兩個宏嗎,一個定義端口號,一個定義IO口號,

其實你看下它的IO口定義宏就知道了:

/*!
 * STM32 Pin Names
 */
#define MCU_PINS \
    PA_0 = 0, PA_1, PA_2, PA_3, PA_4, PA_5, PA_6, PA_7, PA_8, PA_9, PA_10, PA_11, PA_12, PA_13, PA_14, PA_15, \
    PB_0,     PB_1, PB_2, PB_3, PB_4, PB_5, PB_6, PB_7, PB_8, PB_9, PB_10, PB_11, PB_12, PB_13, PB_14, PB_15, \
    PC_0,     PC_1, PC_2, PC_3, PC_4, PC_5, PC_6, PC_7, PC_8, PC_9, PC_10, PC_11, PC_12, PC_13, PC_14, PC_15, \
    PD_0,     PD_1, PD_2, PD_3, PD_4, PD_5, PD_6, PD_7, PD_8, PD_9, PD_10, PD_11, PD_12, PD_13, PD_14, PD_15, \
    PE_0,     PE_1, PE_2, PE_3, PE_4, PE_5, PE_6, PE_7, PE_8, PE_9, PE_10, PE_11, PE_12, PE_13, PE_14, PE_15, \
    PF_0,     PF_1, PF_2, PF_3, PF_4, PF_5, PF_6, PF_7, PF_8, PF_9, PF_10, PF_11, PF_12, PF_13, PF_14, PF_15, \
    PH_0,     PH_1, PH_2, PH_3, PH_4, PH_5, PH_6, PH_7, PH_8, PH_9, PH_10, PH_11, PH_12, PH_13, PH_14, PH_15


// SX1509 Pin Names
#define IOE_PINS \
    IOE_0, IOE_1, IOE_2, IOE_3, IOE_4, IOE_5, IOE_6, IOE_7, \
    IOE_8, IOE_9, IOE_10, IOE_11, IOE_12, IOE_13, IOE_14, IOE_15

/*!
 * Board GPIO pin names
 */
typedef enum
{
    MCU_PINS,
    IOE_PINS,

    // Not connected
    NC = (int)0xFFFFFFFF
}PinNames;

IOE是一個驅動芯片,不管它,這裏用不到。

再看實現:

void GpioInit( Gpio_t *obj, PinNames pin, uint32_t mode, uint32_t OutputType,  uint32_t pull, uint32_t Alternate, uint32_t value )
{
    if( pin < IOE_0 )
    {
        LL_GPIO_InitTypeDef     tGPIO_InitStruct = {0};

        if(NULL == obj){
            return;
        }

        obj->pin = pin;

        if(NC == obj->pin){
            return;
        }

        obj->pinIndex = ( 0x01 << ( obj->pin & 0x0F ) );

        if(obj->pinIndex > LL_GPIO_PIN_15){
            //error
            while(1){
                TRACE_ERROR("gpio_get_struct is error \r\n");
                DelayMs(1000);
            }
        }

        if( ( obj->pin & 0xF0 ) == 0x00 )
        {
            obj->port       = GPIOA;
            obj->portIndex  = 0;
            LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOA);
        }
        else if( ( obj->pin & 0xF0 ) == 0x10 )
        {
            obj->port       = GPIOB;
            obj->portIndex  = 1;
            LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOB);
        }
        else if( ( obj->pin & 0xF0 ) == 0x20 )
        {
            obj->port       = GPIOC;
            obj->portIndex  = 2;
            LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOC);
        }
        else if( ( obj->pin & 0xF0 ) == 0x30 )
        {
            obj->port       = GPIOD;
            obj->portIndex  = 3;
            LL_IOP_GRP1_EnableClock(LL_IOP_GRP1_PERIPH_GPIOD);
        }
        else
        {
            //error
            while(1){
                TRACE_ERROR("gpio_get_struct is error \r\n");
                DelayMs(1000);
            }
        }

        tGPIO_InitStruct.Pin        = obj->pinIndex;
        tGPIO_InitStruct.Mode       = mode;
        tGPIO_InitStruct.Speed      = LL_GPIO_SPEED_FREQ_HIGH;
        tGPIO_InitStruct.OutputType = OutputType;
        tGPIO_InitStruct.Pull       = pull;
        tGPIO_InitStruct.Alternate  = Alternate;

        LL_GPIO_Init(obj->port,&tGPIO_InitStruct);
        if(LL_GPIO_MODE_OUTPUT == mode){
            GpioWrite( obj, value );
        }
    }
    else
    {
#if defined( BOARD_IOE_EXT )
        // IOExt Pin
        GpioIoeInit( obj, pin, mode, config, type, value );
#endif
    }
}

是不是很簡單,具體算法如下:

PinNames & 0x0F得到低四位,範圍爲0~15,然後1<<(0~15)就得到了PIN的序號,看MCU頭文件定義:

#define LL_GPIO_PIN_8                      GPIO_BSRR_BS8 /*!< Select pin 8 */

#define GPIO_BSRR_BS8_Pos              (8U)
#define GPIO_BSRR_BS8_Msk              (0x1UL << GPIO_BSRR_BS8_Pos)             /*!< 0x00000100 */
#define GPIO_BSRR_BS8                  GPIO_BSRR_BS8_Msk

PinNames & 0xF0得到高四位,0x10對應A,0x20對應B等等。

 

//實現IO口中斷設置(同時設置static Gpio_t *GpioIrq[16];)

void GpioSetInterrupt( Gpio_t *obj, uint8_t irqMode,uint8_t Trigger, IrqPriorities irqPriority, GpioIrqHandler *irqHandler )

//禁止IO口中斷

void GpioRemoveInterrupt( Gpio_t *obj )

//IO口中斷函數,根據輸入的PIN,去執行相應函數,對應static Gpio_t *GpioIrq[16];

void gpio_exit_irq(uint32_t gpioPin);

//設置IO輸出電平

void GpioWrite( Gpio_t *obj, uint32_t value )

//IO口取反(未使用)

void GpioToggle( Gpio_t *obj )

//讀IO口值

uint32_t GpioRead( Gpio_t *obj )

以上函數全部需要實現,有點複雜,那是因爲它做的是通用型驅動,兼容STM32的16個IO口。

有兩種簡單方案:

(1)你可以只設置連接到DIO0的PIN中斷(驅動裏面用上升沿中斷,當然配置Lora中斷取反,也可以設置爲下降沿);

(2)可以不用IO口中斷,用查詢方式,這樣IO口中斷可以不用做,實現如下:

     在sx1278.c添加函數,然後賦值給sx1276-board.c的倒數第三個參數:

IrqProcess函數需要實現如下功能:

void IrqProcess( void )
{
    if(DHIO是否是高電平){
        SX1276OnDio0Irq(NULL);
    }
}

(其它IO口中斷函數,類似;但是經過測試,DIO1~DIO5未使用)

4)sx1276-board.c修改

這個文件和具體平臺有關,其它函數根據自己的平臺自己移植,1278的芯片下面三個函數這樣改:

SX1276AntSwInit()和SX1276AntSwDeInit()保留空函數,SX1276SetAntSw()函數是控制天線的switch開關。

void SX1276AntSwInit( void )
{
    //GpioInit( &AntSwitchRx, RADIO_ANT_SWITCH_RX, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    //GpioInit( &AntSwitchRx, RADIO_ANT_SWITCH_POWER, LL_GPIO_MODE_OUTPUT,LL_GPIO_OUTPUT_PUSHPULL, LL_GPIO_PULL_UP,LL_GPIO_AF_1,  0 );
    //GpioInit( &AntSwitchTxBoost, RADIO_ANT_SWITCH_TX_BOOST, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
    //GpioInit( &AntSwitchTxRfo, RADIO_ANT_SWITCH_TX_RFO, PIN_OUTPUT, PIN_PUSH_PULL, PIN_NO_PULL, 0 );
}

void SX1276AntSwDeInit( void )
{
    //GpioInit( &AntSwitchRx, RADIO_ANT_SWITCH_RX, PIN_ANALOGIC, PIN_OPEN_DRAIN, PIN_NO_PULL, 0 );
    //GpioInit( &AntSwitchRx, RADIO_ANT_SWITCH_POWER, LL_GPIO_MODE_ANALOG,LL_GPIO_OUTPUT_OPENDRAIN, LL_GPIO_PULL_NO,LL_GPIO_AF_1,  0 );
    //GpioInit( &AntSwitchTxBoost, RADIO_ANT_SWITCH_TX_BOOST, PIN_ANALOGIC, PIN_OPEN_DRAIN, PIN_NO_PULL, 0 );
    //GpioInit( &AntSwitchTxRfo, RADIO_ANT_SWITCH_TX_RFO, PIN_ANALOGIC, PIN_OPEN_DRAIN, PIN_NO_PULL, 0 );
}

void SX1276SetAntSw( uint8_t opMode )
{
    uint8_t paConfig =  SX1276Read( REG_PACONFIG );
    switch( opMode )
    {
    case RFLR_OPMODE_TRANSMITTER:
//        if( ( paConfig & RF_PACONFIG_PASELECT_PABOOST ) == RF_PACONFIG_PASELECT_PABOOST )
//        {
//            GpioWrite( &AntSwitchTxBoost, 1 );
//        }
//        else
//        {
//            GpioWrite( &AntSwitchTxRfo, 1 );
//        }
        GpioWrite( &AntSwitchRx, 0 );
        break;
    case RFLR_OPMODE_RECEIVER:
    case RFLR_OPMODE_RECEIVER_SINGLE:
    case RFLR_OPMODE_CAD:
    default:
        GpioWrite( &AntSwitchRx, 1 );
        break;
    }
}

還有一個最重要的一點就是對radio抽象接口的實例化:

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,
    NULL, // void ( *IrqProcess )( void )
    NULL, // void ( *RxBoosted )( uint32_t timeout ) - SX126x Only
    NULL, // void ( *SetRxDutyCycle )( uint32_t rxTime, uint32_t sleepTime ) - SX126x Only
};

 

5)timer.c

由於我不用休眠低功耗模式,這個文件裏面所有的函數全部爲空函數即可(回頭會講解低功耗模式下的驅動移植)。

6)上層調用(參考LoRaMac-node-develop\src\apps\ping-pong\NucleoL476\main.c )

用戶首先實現5個函數:

void OnTxDone( void )
{
    SET_EVENT(&tRfTxDone);
}

void OnRxDone( uint8_t *payload, uint16_t size, int16_t rssi, int8_t snr )
{
    //Radio.Sleep( );
    BufferSize = size;
    memcpy( Buffer, payload, BufferSize );
    RssiValue = rssi;
    SnrValue = snr;
    SET_EVENT(&tRfRxDone);
}

void OnTxTimeout( void )
{
    //Radio.Sleep( );
}

void OnRxTimeout( void )
{
    //Radio.Sleep( );
}

void OnRxError( void )
{
    //Radio.Sleep( );
}

只需要實現OnTxDone和OnRxDone即可,其中OnTxDone告知程序發送完成(如果用戶沒有

實現超時功能,這和OnTxTimeout一樣空函數即可),OnRxDone主要是接收底層數據,並告知

應用程序,現有有數據需要處理。

初始化:

    // Radio initialization
    RadioEvents.TxDone = OnTxDone;
    RadioEvents.RxDone = OnRxDone;
    RadioEvents.TxTimeout = OnTxTimeout;
    RadioEvents.RxTimeout = OnRxTimeout;
    RadioEvents.RxError = OnRxError;

    Radio.Init( &RadioEvents );

    Radio.SetChannel( RF_FREQUENCY );

#if defined( USE_MODEM_LORA )

    Radio.SetTxConfig( MODEM_LORA, TX_OUTPUT_POWER, 0, LORA_BANDWIDTH,
                                   LORA_SPREADING_FACTOR, LORA_CODINGRATE,
                                   LORA_PREAMBLE_LENGTH, LORA_FIX_LENGTH_PAYLOAD_ON,
                                   true, 0, 0, LORA_IQ_INVERSION_ON, 3000 );

    Radio.SetRxConfig( MODEM_LORA, LORA_BANDWIDTH, LORA_SPREADING_FACTOR,
                                   LORA_CODINGRATE, 0, LORA_PREAMBLE_LENGTH,
                                   LORA_SYMBOL_TIMEOUT, LORA_FIX_LENGTH_PAYLOAD_ON,
                                   0, true, 0, 0, LORA_IQ_INVERSION_ON, true );

#elif defined( USE_MODEM_FSK )

    Radio.SetTxConfig( MODEM_FSK, TX_OUTPUT_POWER, FSK_FDEV, 0,
                                  FSK_DATARATE, 0,
                                  FSK_PREAMBLE_LENGTH, FSK_FIX_LENGTH_PAYLOAD_ON,
                                  true, 0, 0, 0, 3000 );

    Radio.SetRxConfig( MODEM_FSK, FSK_BANDWIDTH, FSK_DATARATE,
                                  0, FSK_AFC_BANDWIDTH, FSK_PREAMBLE_LENGTH,
                                  0, FSK_FIX_LENGTH_PAYLOAD_ON, 0, true,
                                  0, 0,false, true );

#else
    #error "Please define a frequency band in the compiler options."
#endif

設置爲接收:

Radio.Rx( RX_TIMEOUT_VALUE );接收完成後,如果是連續接收模式,不需要再次設置Radio.Rx( RX_TIMEOUT_VALUE )

如果要發送數據:

Radio.Send( Buffer, BufferSize );發送完成記得Radio.Rx( RX_TIMEOUT_VALUE );可以寫到OnTxDone裏面

至此,新驅動可以簡單的收到了。

三、sx1276.c講解

這個主要是sx1278的驅動文件,它有SX1276OnDio0Irq~SX1276OnDio5Irq分別對應DIO0~DIO5留個IO口,

由於IO口是電平變化,即低變高(可以配置爲高變低),軟件復位電平,因此我們可以採用中斷或者查詢模式

(因爲不是脈衝方式,脈衝的話,只能用中斷方式),新驅動是按着中斷方式寫的。

內部有三個超時事件:
/*!
 * Tx and Rx timers
 */
TimerEvent_t TxTimeoutTimer;
TimerEvent_t RxTimeoutTimer;
TimerEvent_t RxTimeoutSyncWord;

但是實際是一個處理函數:

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

void SX1276OnTimeoutIrq( void* context )
{
    switch( SX1276.Settings.State )
    {
    case RF_RX_RUNNING:
        if( SX1276.Settings.Modem == MODEM_FSK )
        {
            SX1276.Settings.FskPacketHandler.PreambleDetected = false;
            SX1276.Settings.FskPacketHandler.SyncWordDetected = false;
            SX1276.Settings.FskPacketHandler.NbBytes = 0;
            SX1276.Settings.FskPacketHandler.Size = 0;

            // Clear Irqs
            SX1276Write( REG_IRQFLAGS1, RF_IRQFLAGS1_RSSI |
                                        RF_IRQFLAGS1_PREAMBLEDETECT |
                                        RF_IRQFLAGS1_SYNCADDRESSMATCH );
            SX1276Write( REG_IRQFLAGS2, RF_IRQFLAGS2_FIFOOVERRUN );

            if( SX1276.Settings.Fsk.RxContinuous == true )
            {
                // Continuous mode restart Rx chain
                SX1276Write( REG_RXCONFIG, SX1276Read( REG_RXCONFIG ) | RF_RXCONFIG_RESTARTRXWITHOUTPLLLOCK );
                TimerStart( &RxTimeoutSyncWord );
            }
            else
            {
                SX1276.Settings.State = RF_IDLE;
                TimerStop( &RxTimeoutSyncWord );
            }
        }
        if( ( RadioEvents != NULL ) && ( RadioEvents->RxTimeout != NULL ) )
        {
            RadioEvents->RxTimeout( );
        }
        break;
    case RF_TX_RUNNING:
        // Tx timeout shouldn't happen.
        // Reported issue of SPI data corruption resulting in TX TIMEOUT 
        // is NOT related to a bug in radio transceiver.
        // It is mainly caused by improper PCB routing of SPI lines and/or
        // violation of SPI specifications.
        // To mitigate redesign, Semtech offers a workaround which resets
        // the radio transceiver and putting it into a known state.

        // BEGIN WORKAROUND

        // Reset the radio
        SX1276Reset( );

        // Calibrate Rx chain
        RxChainCalibration( );

        // Initialize radio default values
        SX1276SetOpMode( RF_OPMODE_SLEEP );

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

        // Restore previous network type setting.
        SX1276SetPublicNetwork( SX1276.Settings.LoRa.PublicNetwork );
        // END WORKAROUND

        SX1276.Settings.State = RF_IDLE;
        if( ( RadioEvents != NULL ) && ( RadioEvents->TxTimeout != NULL ) )
        {
            RadioEvents->TxTimeout( );
        }
        break;
    default:
        break;
    }
}

Lora模式沒有接收同步字超時,在接收超時時候,底層僅僅是調用了上層的OnRxTimeout()。

發送超時之後,對芯片進行了一系列初始化操作後,調用上層的OnTxTimeout(),注意這裏把

芯片初始化爲sleep模式了。(由於沒有實現timer,因此這個函數沒有調用)

關於sx1276的其他的函數,用戶自己去看實現就好,不再講解。

四、注意事項

1、操作flash,導致HardFault_Handler

      原因是初始化時候操作了未初始化的指針。

2、Lora不能連續接收

通過示波器查看DIO0波形,發現接收完成後,並沒有使IO口變低,一直維持高電平,導致

下個接收中斷不能產生。

查看官方代碼有清除IRQ標誌:

                    // Clear Irq
                    SX1276Write( REG_LR_IRQFLAGS, RFLR_IRQFLAGS_RXDONE );

打印Log發現,執行這句,表示位還是1,所以我懷疑SPI驅動,但是讀沒有問題,查看Lora的SPI時序

查看sx1276.c文件:

void SX1276WriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );

    SpiInOut( &SX1276.Spi, addr | 0x80 );
    for( i = 0; i < size; i++ )
    {
        SpiInOut( &SX1276.Spi, buffer[i] );
    }

    //NSS = 1;
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

void SX1276ReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );

    SpiInOut( &SX1276.Spi, addr & 0x7F );

    for( i = 0; i < size; i++ )
    {
        buffer[i] = SpiInOut( &SX1276.Spi, 0 );
    }

    //NSS = 1;
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

修改爲:


void SX1276WriteBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );
    Soft_delay_us(10);
    SpiInOut( &SX1276.Spi, addr | 0x80 );
    for( i = 0; i < size; i++ )
    {
        SpiInOut( &SX1276.Spi, buffer[i] );
    }

    //NSS = 1;
    Soft_delay_us(5);
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

void SX1276ReadBuffer( uint16_t addr, uint8_t *buffer, uint8_t size )
{
    uint8_t i;

    //NSS = 0;
    GpioWrite( &SX1276.Spi.Nss, 0 );
    Soft_delay_us(5);
    SpiInOut( &SX1276.Spi, addr & 0x7F );

    for( i = 0; i < size; i++ )
    {
        buffer[i] = SpiInOut( &SX1276.Spi, 0 );
    }

    //NSS = 1;
    Soft_delay_us(5);
    GpioWrite( &SX1276.Spi.Nss, 1 );
}

一切正常。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章