LoRa網關項目——SX1278開發(二)




#前言

​ 最近在做一個LoRa物聯網網關的項目,網關的作用主要是管理連接的LoRa傳感器終端,將傳感數據通過協議轉換向上轉發到Internet,當然,也要處理下行的數據。

​ 使用到的LoRa射頻芯片是SX1278,MCU為STM32F103RCT6,連接Internet用的是ESP8266+AT,且移植了FreeRTOS(單純是為了學習),開發環境是STM32CubeMX+Keil 5。由於之前沒負責過整個系統的開發,所以開此貼記錄一下開發過程,由於本人上學以來語文一直不好,所以文筆正在努力進步中,如果此文章有您覺得我說的不明白的地方,可以發送郵件到wanglu082@yeah.net,或者在文章下方評論,我看到會盡快回復您,多謝諒解!


LoRa網關項目——SX1278開發(二)

​ 上一章介紹了整個工程的架構和選用的LoRa模塊,本章自然就來到了SX1278的初始化環節。

一. SX1278初始化

​ 遇到有多個復雜的結構體的項目時,我個人不喜歡先去看一堆結構體的定義,而是先看代碼的邏輯,遇到一個不會的再去到Defination去研究,所以咱們直接看代碼。

1.1 注冊相關回調函數

/* 注冊相關的回調函數 */
RadioEvents.TxDone = OnTxDone;
RadioEvents.RxDone = OnRxDone;
RadioEvents.TxTimeout = OnTxTimeout;
RadioEvents.RxTimeout = OnRxTimeout;
RadioEvents.RxError = OnRxError;

​ 什么?第一步居然不是 xxxInit ,這是因為下一步對SX1278初始化函數要傳入 RadioEvents ,所以要先對這個 RadioEvents 進行初始化,去到它的 Definantion 發現它的類型是 RadioEvents_t ,看一下它的定義:

/*!
 * \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     Raw SNR value given by the radio hardware
     *                     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;

​ RadioEvents_t的參數全部是函數指針,使用函數指針我認為是C語言中進階高手的必經之路。到這可以推斷出SX1278的驅動是基於事件觸發回調函數的方式設計的,它的各類事件是通過DIO引腳通知的(后面會細說),在引腳的中斷函數里調用這些函數指針指向的回調函數,最終做出相應的操作,這種用硬件通知事件的方式還是第一次見到,學習了。

​ 回調函數的實現后面再說,先對整個驅動做框架上的認知,這才是重點。

1.2 硬件初始化

​ 其實,我將SX1278的初始化分為硬件初始化和軟件的初始化。硬件初始化就是有關GPIO引腳、SPI的初始化過程,說白了就是MCU能與SX1278進行通訊前期做的所有准備工作;而軟件的初始化就要建立能與SX1278通過SPI進行通訊的基礎上,它的任務是對其內部寄存器進行讀寫,使芯片處於默認的狀態。

​ 軟硬件初始化過程都位於下面一行代碼中:

Radio.Init( &RadioEvents );

​ 不要小看這區區一行代碼,里邊其實有非常復雜的實現過程,只是因為漂亮的封裝讓我們覺得它很簡單而已。

​ 首先就是這個 Radio ,這個東西是個全局變量,在 sx1276-board.c 中定義,它本身是 Radio_s 類型的,與上面的 RadioEvents_t 類似,它也是一個成員全是函數指針的結構體,這兩個結構體都是在radio.h 中定義的,由於篇幅原因我把一些參數的注釋給刪了:

struct Radio_s
{
    void    ( *Init )( RadioEvents_t *events );
    RadioState_t ( *GetStatus )( void );
    void    ( *SetModem )( RadioModems_t modem );
    void    ( *SetChannel )( uint32_t freq );
    bool    ( *IsChannelFree )( RadioModems_t modem, uint32_t freq, int16_t rssiThresh );
    uint32_t ( *Random )( void );
    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 );
    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 );
    bool    ( *CheckRfFrequency )( uint32_t frequency );
    uint32_t  ( *TimeOnAir )( RadioModems_t modem, uint8_t pktLen );
    void    ( *Send )( uint8_t *buffer, uint8_t size );
    void    ( *Sleep )( void );
    void    ( *Standby )( void );
    void    ( *Rx )( uint32_t timeout );
    void    ( *StartCad )( void );
    int16_t ( *Rssi )( RadioModems_t modem );
    void    ( *Write )( uint8_t addr, uint8_t data );
    uint8_t ( *Read )( uint8_t addr );
    void    ( *WriteBuffer )( uint8_t addr, uint8_t *buffer, uint8_t size );
    void    ( *ReadBuffer )( uint8_t addr, uint8_t *buffer, uint8_t size );
    void ( *SetMaxPayloadLength )( RadioModems_t modem, uint8_t max );

};

​ 這個結構體其實就是將一些中間件層的函數封裝到一個結構體中,更加直觀和方便調用。上面說了,Radio 中函數指針的賦值在 sx1276-board.c 中實現:

const struct Radio_s Radio =
{
    SX1276Init,
    SX1276GetStatus,
    SX1276SetModem,
    SX1276SetChannel,
    SX1276IsChannelFree,
    SX1276Random,
    SX1276SetRxConfig,
    SX1276SetTxConfig,
    SX1276CheckRfFrequency,
    SX1276GetTimeOnAir,
    SX1276Send,
    SX1276SetSleep,
    SX1276SetStby, 
    SX1276SetRx,
    SX1276StartCad,
    SX1276ReadRssi,
    SX1276Write,
    SX1276Read,
    SX1276WriteBuffer,
    SX1276ReadBuffer,
    SX1276SetMaxPayloadLength
};

​ 這里其實我覺得與我的想法產生分歧,sx1276-board 本身是板級支持包的文件,而這些函數都是中間件層的函數,這樣就需要再引入 sx1276.h ,那么為什么不把 Radio 的定義放在sx1276.c 中呢?

​ 拋去這些,反正這下知道了,我們當前調用的 Radio.Init( &RadioEvents ) 其實最終是調用的 SX1276Init( &RadioEvents )

void SX1276Init( RadioEvents_t *events )
{
    uint8_t i;

    RadioEvents = events;

	SX1276TimerInit();         /* 初始化定時器 */
	SX1276IoInit();            /* 初始化GPIO, SPI */
    SX1276Reset();             /* 初始化Reset引腳且進行硬件復位*/
											         
    RxChainCalibration( );     /* 執行LF和HF波段的接收鏈校准 */

    SX1276SetOpMode( RF_OPMODE_SLEEP );

    SX1276IoIrqInit( DioIrq ); /* 初始化使用到的DIO引腳 */

		/* 給必要的寄存器賦初值 */
    for( i = 0; i < sizeof( RadioRegsInit ) / sizeof( RadioRegisters_t ); i++ )
    {
        SX1276SetModem( RadioRegsInit[i].Modem );
        SX1276Write( RadioRegsInit[i].Addr, RadioRegsInit[i].Value );
    }

    SX1276SetModem( MODEM_FSK );        /* 初始先是FSK模式, 后面會修改為LoRa模式 */

    SX1276.Settings.State = RF_IDLE;	/* 設置狀態為空閑態 */
}

​ 這里面既包括硬件部分的初始化也包括軟件部分的初始化,下面會介紹軟件相關的,這里先說硬件。

  1. SX1276TimerInit(); 首先是Timer的初始化,sx1278的發送和接收可以設置一個超時時間,超過這個時間需要產生相應發送/接收超時的中斷,這就需要定時器的參與,這里的定時器可以用硬件也可以用軟件,我最終是采用了FreeRTOS提供的軟件定時器,所以這部分在后面也會說。

  2. SX1276IoInit(); 初始化片選引腳、SPI等對應的GPIO,配置SPI的模式,都很常規,沒什么好說的。

  3. SX1276IoIrqInit( DioIrq ); 這里可以說一下,這個函數實現了將MCU與sx1278的DIO0~DIO5連接的引腳初始化。這些引腳被配置成輸入+外部中斷模式,當對應的事件發生時,就會向通過對應DIOx向MCU報告,MCU檢測到中斷信號就會執行外部中斷的中斷服務函數。一個DIO可以被配置成不同的事件,這個可以在sx1278的官方手冊中找到:
    image-20210429212523550

    DIO的映射關系可以通過配置 RegDioMapping1 和 RegDioMapping2 寄存器來實現,后續的配置Rx和Tx的代碼中也會提到,到時候再說。

    這個函數的傳入參數也有值得我學習的地方

    DioIrqHandler *DioIrq[] = { SX1276OnDio0Irq, SX1276OnDio1Irq, SX1276OnDio2Irq, SX1276OnDio3Irq, SX1276OnDio4Irq, NULL };

    DioIrq 是一個指向 void 類型的指針數組( DioIrqHandle 就是 void ),數組的內容是一些中斷服務函數的函數指針,這樣就可以通過 DioIrq[0]( ) 來訪問 SX1276OnDio0Irq( ) 了,在MCU對應DIOx引腳的中斷服務函數中可以看到這種訪問方式,不可不謂精彩!


1.3軟件初始化

​ 說完了硬件,軟件部分其實就非常簡單了,我們只需要看懂官方給的驅動文件是在干嘛就行了,有時候它為什么要這么做我也不明白,手冊里寫的也不清楚。

  1. RxChainCalibration( ); 攤牌了,完全不懂,但是也不用修改什么。
  2. SX1276SetOpMode( RF_OPMODE_SLEEP ); 設置芯片為休眠模式,只有休眠模式才能對寄存器進行配置。
  3. SX1276SetModem( MODEM_FSK ); 配置調制方式,這里驅動先使用FSK模式,后面會修改為LoRa。
  4. SX1276.Settings.State = RF_IDLE; State 也是一個重要的參數,這個驅動也引入了狀態機的思想,共有4種狀態:RF_IDLE, RF_RX_RUNNING, RF_TX_RUNNING, RF_CAD

關於對寄存器配置的操作中,我也學習到了新的東西,就拿改變opMode為例,通過查手冊很容易知道是通過修改 RegOpMode 的 2-0 bit 來實現:

image-20210429222019245

在驅動中是這樣實現的:

SX1276Write( REG_OPMODE, ( SX1276Read( REG_OPMODE ) & RF_OPMODE_MASK ) | opMode );

他首先讀出了這個寄存器的原始內容,然后將它與上 RF_OPMODE_MASK ,RF_OPMODE_MASK 為 0xF8,這樣在與操作之后該寄存器的2-0bit全為0了,最終在或上要修改的opMode就能實現 2-0bit 安全的修改。學到了學到了!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM