Lora1278驱动V4.4.2讲解一:驱动移植

注意,Lora1278驱动sx12xxDrivers-V2.1.0,原厂已经不更新和维护了,反馈的任何软件问题,

原厂就是一句话升级新的驱动,新驱动下载地址:https://github.com/Lora-net/LoRaMac-node

1276的驱动可以用与1278:

Lora1278驱动V4.4.2讲解一:驱动移植_第1张图片

频段划分:

Lora1278驱动V4.4.2讲解一:驱动移植_第2张图片

 其中频段1属于HF,频段2和3属于LF,在看驱动时候会用到。

射频输出:

Lora1278驱动V4.4.2讲解一:驱动移植_第3张图片

TCXO设置:

Lora1278驱动V4.4.2讲解一:驱动移植_第4张图片

Lora1278驱动V4.4.2讲解一:驱动移植_第5张图片

一、驱动架构讲解

由于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则在接收时候映射为接收完成输出,在发送时候映射为发送完成输出,它们都是电平

变化输出,不是脉冲变化,然后有用户手动复位。

Lora1278驱动V4.4.2讲解一:驱动移植_第6张图片

二、移植工作

1、打开官方文件,由于我用的是Lora1278,因此把画框的几个文件拷贝到工程里面:
Lora1278驱动V4.4.2讲解一:驱动移植_第7张图片/

2、平台相关的

我们需要把如下几个文件加到工程:

Lora1278驱动V4.4.2讲解一:驱动移植_第8张图片

其中:

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时序

Lora1278驱动V4.4.2讲解一:驱动移植_第9张图片

查看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 );
}

一切正常。

你可能感兴趣的:(Lora)