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的数据包包含三个部分:
A preamble
:前导码
用于保持接收机与输入的数据流同步,作用是提醒接收芯片,即将发送的是有效信号。默认size是12个符号长度,有时为了缩短接收机占空比,可以缩短前导码长度。接收机会定期检测前导码,因此接收和发射前导码长度需要一致。An optional header
可以选择显示(explicit
)或隐式(implicit
)两种类型。
显式报头:包括Payload长度,前向纠错编码率,是否使用CRC(16bit)
隐式报头:需要手动设置无线链路两端的Payload长度、错误编码率、CRC(如果扩频因子SF=6,只能使用隐式报头模式)The data payload
长度不固定,在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引脚映射为发射完成指示符
设置为发射模式,如果不处于异步通信状态,就等待发送完成,然后清除中断。
这里讲的不是很清楚,我们再来具体看一下:
async
:意思是异步,是在non-blocking mode
下使用的一个变量。由于网关可能不会向后发送任何数据,因此我们在尝试接收之前使套接字成为非阻塞状态,以防止卡在等待永远不会到达的数据包中
同步、异步、阻塞、非阻塞这四个是进程间通信的概念,进程间通信通过send()
和receive()
两种基本操作完成:
阻塞式发送:发送方进程会一直被阻塞,直到消息被接收方进程收到;
非阻塞式发送:发送方调用send()
后,可以一直进行其他操作;
阻塞式接收:接收方进程会一直被阻塞,直到消息到达可用;
非阻塞式接收:接收方调用receive()
后,要么得到一个有效的效果,要么得到一个空值
(此处参考文章)void (*_onTxDone)();
可以看到,这是一个类内私有的函数指针,_onTxDone
是一个指针,该指针指向的函数的返回值是void
初始化时,该指针指向的确实是一个NULL
,但是如果执行了这个函数:
void LoRaClass::onTxDone(void(*callback)())
{_onTxDone = callback;.......
}
那么这个指针就不为空,也就是满足这个判断条件:
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)
这一句就是在等待中断
中断源
FifoEmpty
:当整个FIFO为空,中断源为高,从FIFO检索数据的时候,FifoEmpty
在NSS下降沿更新。也就是说每次读取操作之后检查FifoEmpty
状态,以决定是否读取下一个字节,FifoEmpty
=1表示不需要读取更多字节FifoFull
FifoOverrunFlag
:is set when a new byte is written by the user (in Tx or Standby modes) or the SR (in Rx mode) while the FIFO is already full,在FIFO已满的情况下,有一个新的字节被写入的时候。数据丢失,并且标志应通过写1清除,同时FIFO也将被清除。PacketSent
:when the SR’s last bit has been sentFifoLevel
#define IRQ_TX_DONE_MASK 0x08
只把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);
也就是说会进入显式数据包模式
#define IRQ_PAYLOAD_CRC_ERROR_MASK 0x20
#define IRQ_RX_DONE_MASK 0x40
接收一个数据包的条件是:从中断寄存器中的读取的数据第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
, (低频口)
int LoRaClass::packetRssi()
{return (readRegister(REG_PKT_RSSI_VALUE) - (_frequency < 868E6 ? 164 : 157));
}
LoRa协议在Arduino上的应用——原理及代码分析(二)相关推荐
- 对dpdk的rte_ring实现原理和代码分析
对dpdk的rte_ring实现原理和代码分析 前言 dpdk的rte_ring是借鉴了linux内核的kfifo实现原理,这里统称为无锁环形缓冲队列. 环形缓冲区通常有一个读指针和一个写指针.读指针 ...
- TrueCrypt 6.2a原理及代码分析
TrueCrypt 6.2a原理及代码分析 3 comments 25th Apr 10 rafa 1 项目物理布局 Project |____ Boot /* MBR部分的代码 */ ...
- 免费的Lucene 原理与代码分析完整版下载
Lucene是一个基于Java的高效的全文检索库. 那么什么是全文检索,为什么需要全文检索? 目前人们生活中出现的数据总的来说分为两类:结构化数据和非结构化数据.很容易理解,结构化数据是有固定格式和结 ...
- Lucene原理与代码分析(高手博客备忘)
2019独角兽企业重金招聘Python工程师标准>>> 随笔 - 69 文章 - 77 评论 - 687 随笔分类 - Lucene原理与代码分析 Lucene 4.X 倒排索引 ...
- Lucene 原理与代码分析完整版
原文地址为: Lucene 原理与代码分析完整版 Lucene 原理与代码分析系列文章已经基本告一段落,可能问题篇还会有新的更新. 完整版pdf可由以下链接下载. Lucene 原理与代码分析完整版 ...
- OpenStack 虚拟机冷/热迁移的实现原理与代码分析
目录 文章目录 目录 前文列表 冷迁移代码分析(基于 Newton) Nova 冷迁移实现原理 热迁移代码分析 Nova 热迁移实现原理 向 libvirtd 发出 Live Migration 指令 ...
- 海思AI芯片(Hi3519A/3559A)方案学习(十七)开发板上运行yolo3模型的代码分析
前言 前面的博客系列 已经介绍了如何将caffemodel转换成wk文件,如何将jpg文件转成bgr格式数据以及如何在PC上仿真模型推理等,基于这些基础,本文来结合代码分析如何在板子上推理yolov3 ...
- stm32-通用定时器原理及代码分析
目录 定时器:基本,通用 一,基本定时器: 作用: 结构图: 二.通用定时器: 作用: 结构图: 三.代码分析: 1.选择时钟 2.配置时基单元 3.产生中断 4.使用定时器 定时器:基本,通用 一, ...
- LR_scheduler及warmup底层原理和代码分析
LR_scheduler LR_scheduler是用于调节学习率lr的,在代码中,我们经常看到这样的一行代码 scheduler.step() 通过这行代码来实现lr的更新的,那么其中的底层原理是什 ...
- 单点登录(SSO)原理以及代码分析
什么是单点登录: 简单概括就是,对于一个系统有多个应用(淘宝有淘宝,天猫)的情况,用户只需登陆一次,就可以访问所有该系统下的其它应用(登录了淘宝后,跳转到天猫也不用登陆了). 登录的问题: 首先要知道 ...
最新文章
- Python程序打包
- 在GridView里做单选按钮,总结了三种方法
- db2v9/9.5高级应用开发_Spark v2.4.3应用程序开发入门-基于IDEA/Maven 构建简单应用
- python将列表横着输出来
- iOS开发笔记[13/50]:解决SenTestingKit/SenTestingKit.h: No such file or directory问题
- B端产品思维全解析,提升产品经理核心竞争力
- 基础网络和关键基础设施
- 快速掌握MATLAB应用,从这一步开始
- 0037 Java学习笔记-多线程-同步代码块、同步方法、同步锁
- 计算机硬件基础大纲,计算机硬件基础教学大纲..docx
- Apache Flume 简介
- 【高斯模糊算法的理解】简单易懂
- git base cli
- 2017年北京共享单车数据(订单数据)
- WTL for MFC Programmers, Part V - Advanced Dialog UI Classes
- SEO网站内容优化的6点干货分享分享-飞鱼SEO
- 2. Mac 命令行走代理服务器
- 非对称加密之公钥密码体系 【五】
- Pandas-DataFrame使用
- ARM编译中的RO、RW和ZI DATA区段
热门文章
- ,PLCSIM、SIMIT、Amesim、NX MCD、 Process Simulate、Plant Simulation,一文带你了解西门子整个虚拟调试与仿真软硬件体系
- 蚂蚁课堂视频笔记思维导图-3期 七、互联网高并发解决方案
- 红米k30pro工程测试代码_红米K30Pro-MIUI12测试版|全局小窗|隐秘面具|拦截网|光锥动效|超多功能|步数修改-刷机之家...
- 设计模式之模板模式(模板方法)
- VMware许可证过期解决方案
- vue中动态加载图片路径
- libreoffice word转pdf时中文乱码问题解决
- NB-iot的M5310A的学习
- python ui自动化_python-UI自动化
- 常用身份证识别 OCR 技术 手机拍照识别身份证