LoRa协议在Arduino上的应用——原理及代码分析(二)
目錄
- `LoRa`調制與解調模式
- 代碼分析
- 主程序
- 開始發包
- `Packet Structure`
- 數據傳輸時間
- 重置FIFO地址與payload長度
- 結束發包
- 中斷源
- `Receiver`
- `parsePacket`
- 是否存在數據包
- 數據讀取
- `RSSI`
前述文章鏈接在此~~
LoRa協議在Arduino上的應用——原理及代碼分析(一).
LoRa調制與解調模式
擴頻與循環糾錯編碼相結合
可以看到,LoRa調制模式下,有一個獨立的雙端口數據緩沖區FIFO,可通過所有模式共有的SPI接口進行訪問。
可針對特定的應用優化LoRa調制:擴頻因子、調制帶寬、錯誤編碼率
先說一下擴頻:其實通俗來講,原來我發送一個信號,這個信號中只包含兩個bit10,現在我發送一個信號,這個信號中包含16個bit,這樣我就相當于將信號拓寬,擴頻因子就是16/2=8。那么我們就可以理解為什么擴頻技術是擴展信號的帶寬:把信道想象成一個通道,原來一次只能通過兩個bit,現在一次性可以通過16個bit,信道是不是變寬了?
但有一點需要注意,就是擴頻前后信號的能量是不變的
至于擴頻的好處:可以根據香農公式,在相同的信息速率下,帶寬和信噪比可以互換,擴頻就是用大帶寬,換接收端信噪比的低要求
再來看下循環糾錯編碼:
其實就是使用糾錯編碼中的循環碼:網上資料很多,不贅述了
代碼分析
主程序
int counter = 0; ... void loop() {...// send packetLoRa.beginPacket();LoRa.print("hello ");LoRa.print(counter);LoRa.endPacket();}開始發包
int LoRaClass::beginPacket(int implicitHeader) {if (isTransmitting()) {return 0;}// put in standby modeidle();if (implicitHeader) {implicitHeaderMode();} else {explicitHeaderMode();}// reset FIFO address and paload lengthwriteRegister(REG_FIFO_ADDR_PTR, 0);writeRegister(REG_PAYLOAD_LENGTH, 0);return 1; }在頭文件中,已經設置implicitHeader默認為false,因此會進入explicitHeaderMode()函數
void LoRaClass::explicitHeaderMode() {_implicitHeaderMode = 0;writeRegister(REG_MODEM_CONFIG_1, readRegister(REG_MODEM_CONFIG_1) & 0xfe); }可以看到這個函數就是把原來調制模式配置寄存器中的數值讀出來,將最后一位設置為0,表示Explicit Header mode,然后寫回。
Packet Structure
LoRa的數據包包含三個部分:
用于保持接收機與輸入的數據流同步,作用是提醒接收芯片,即將發送的是有效信號。默認size是12個符號長度,有時為了縮短接收機占空比,可以縮短前導碼長度。接收機會定期檢測前導碼,因此接收和發射前導碼長度需要一致。
可以選擇顯示(explicit)或隱式(implicit)兩種類型。
顯式報頭:包括Payload長度,前向糾錯編碼率,是否使用CRC(16bit)
隱式報頭:需要手動設置無線鏈路兩端的Payload長度、錯誤編碼率、CRC(如果擴頻因子SF=6,只能使用隱式報頭模式)
長度不固定,在FIFO中讀寫
數據傳輸時間
LoRa數據包總傳輸時間 = 前導碼傳輸時間Tpre + 數據包傳輸時間Tpay
前導碼傳輸時間為:Tpre = (Npre + 4.25)*Tpay
Npre表示已設定的前導碼長度(讀取RegPreambleMsb和RegPreambleLsb寄存器得到)
計算Payload符號數的公式為:
PL表示Payload字節數,H=0表示header使能,DE=1表示LowDataRateOptimize=1,CR表示編碼速率
重置FIFO地址與payload長度
結束發包
int LoRaClass::endPacket(bool async) {if ((async) && (_onTxDone))writeRegister(REG_DIO_MAPPING_1, 0x40); // DIO0 => TXDONE// put in TX modewriteRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);if (!async) {// wait for TX donewhile ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0) {yield();}// clear IRQ'swriteRegister(REG_IRQ_FLAGS, IRQ_TX_DONE_MASK);}return 1; }默認async = false,_onTxDone(NULL)
SX112系列的6個通用DIO引腳在LoRa模式下均可用,其映射關系取決于RegDioMapping1和RegDioMapping2這兩個寄存器
第一句代碼的意思就是如果處于異步通信狀態,且在發射完成狀態(此處有疑義),就將DIO0引腳映射為發射完成指示符
設置為發射模式,如果不處于異步通信狀態,就等待發送完成,然后清除中斷。
這里講的不是很清楚,我們再來具體看一下:
同步、異步、阻塞、非阻塞這四個是進程間通信的概念,進程間通信通過send()和receive()兩種基本操作完成:
阻塞式發送:發送方進程會一直被阻塞,直到消息被接收方進程收到;
非阻塞式發送:發送方調用send()后,可以一直進行其他操作;
阻塞式接收:接收方進程會一直被阻塞,直到消息到達可用;
非阻塞式接收:接收方調用receive()后,要么得到一個有效的效果,要么得到一個空值
(此處參考文章)
可以看到,這是一個類內私有的函數指針,_onTxDone是一個指針,該指針指向的函數的返回值是void
初始化時,該指針指向的確實是一個NULL,但是如果執行了這個函數:
那么這個指針就不為空,也就是滿足這個判斷條件:
if ((async) && (_onTxDone)) writeRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_TX);這一句對應的就是上圖的Mode Request Tx
while ((readRegister(REG_IRQ_FLAGS) & IRQ_TX_DONE_MASK) == 0)這一句就是在等待中斷
中斷源
只把TxDoneMask置為1,其余全部置為0。而如果要滿足while中的循環條件,則REG_IRQ_FLAGS這個寄存器中的第3bit位TxDone必須為0,如果不滿足就yield()就是一直執行這個空循環
跳出這個循環之后,就清除中斷標志,并且設置TX完成標志位
Receiver
void loop() {// try to parse packetint packetSize = LoRa.parsePacket();if (packetSize) {// received a packetSerial.print("Received packet '");// read packetwhile (LoRa.available()) {Serial.print((char)LoRa.read());}// print RSSI of packetSerial.print("' with RSSI ");Serial.println(LoRa.packetRssi());} }parsePacket
int LoRaClass::parsePacket(int size) {int packetLength = 0;int irqFlags = readRegister(REG_IRQ_FLAGS);if (size > 0) {implicitHeaderMode();writeRegister(REG_PAYLOAD_LENGTH, size & 0xff);} else {explicitHeaderMode();}// clear IRQ'swriteRegister(REG_IRQ_FLAGS, irqFlags);if ((irqFlags & IRQ_RX_DONE_MASK) && (irqFlags & IRQ_PAYLOAD_CRC_ERROR_MASK) == 0) {// received a packet_packetIndex = 0;// read packet lengthif (_implicitHeaderMode) {packetLength = readRegister(REG_PAYLOAD_LENGTH);} else {packetLength = readRegister(REG_RX_NB_BYTES);}// set FIFO address to current RX addresswriteRegister(REG_FIFO_ADDR_PTR, readRegister(REG_FIFO_RX_CURRENT_ADDR));// put in standby modeidle();} else if (readRegister(REG_OP_MODE) != (MODE_LONG_RANGE_MODE | MODE_RX_SINGLE)) {// not currently in RX mode// reset FIFO addresswriteRegister(REG_FIFO_ADDR_PTR, 0);// put in single RX modewriteRegister(REG_OP_MODE, MODE_LONG_RANGE_MODE | MODE_RX_SINGLE);}return packetLength; }這個函數很長,我們逐步分析。
首先觀察返回值,可以知道這個函數是計算數據包的長度
在頭文件中默認定義int parsePacket(int size = 0);
也就是說會進入顯式數據包模式
接收一個數據包的條件是:從中斷寄存器中的讀取的數據第6位為0或者第5位為0
讀取完數據包長度之后,還需要設置FIFO的地址
具體就是跟著這張圖來
是否存在數據包
int LoRaClass::available() {return (readRegister(REG_RX_NB_BYTES) - _packetIndex); }這個看起來有點像串口檢測字符的那個函數。。。。。
如果存在數據包,REG_RX_NB_BYTES這個寄存器的值就不為0,就會開始讀取數據
數據讀取
int LoRaClass::read() {if (!available()) {return -1;}_packetIndex++;return readRegister(REG_FIFO); }返回從FIFO中讀取的數據
RSSI
received signal strength indicator:接收信號強度指示符
常規:
RSSI (dBm) = -157 + Rssi, (高頻口)
RSSI (dBm) = -164 + Rssi, (低頻口)
總結
以上是生活随笔為你收集整理的LoRa协议在Arduino上的应用——原理及代码分析(二)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: React开发(232):传参可以转变思
- 下一篇: Golang 微服务系列 go-kit(