查看: 19272|回复: 90

[经验] 开贴讲讲NRF24L01P,让你彻底搞懂它的工作原理,持续更...

[复制链接]

该用户从未签到

发表于 2019-5-7 17:16 | 显示全部楼层 |阅读模式
本帖最后由 alicedodo 于 2019-5-23 09:50 编辑

*****************************************                                            
更贴码字很多,很容易触发论坛人工审核机制(我今天的更贴无一例外啊),导致最新内容一直刷新不出来。
今天赶上有些时间,所以多更了两次,人工审核太慢了,跟不上更贴的节奏了。
无奈在码云上专门开了一个项目,当帖子写。
TIM截图20190508190118.png
大家想尽早看最新内容的话,可以点开我个人主页找到项目地址,上图中的这个项目,查看wiki文档。
哥们儿说话算话,既然说了不弃坑,那论坛这边会一直更新,如果提问题尽量在论坛这边提.


*****************************************

【1】为什么使用2.4GHz频段https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=466799

【2】聊一聊nrf2401的功率
https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=466801

【3】nrf2401数据传输原理第1讲:FSK原理
https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=466802

【4】nrf2401数据传输原理第2讲:半双工通信
https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=467621

【5】nrf2401数据传输原理第3讲:地址和数据通道
https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=469710

【6】nrf2401数据传输原理第4讲(小结):数据包帧格式
https://www.arduino.cn/forum.php?mod=redirect&goto=findpost&ptid=86275&pid=471576

【7】待添加
送福利: https://gitee.com/alicedodo/xaobao_cheap_bus_servo_hack_record

*****************************************



开场白:
在讨论区里已经不止一次看到有同学求助有关使用nrf24l01+通信的问题,也看到不少高手发过相关库的使用教程或demo。
但事实证明效果不怎么好,还是时不时有人发帖问这个问题,甚至直接照抄高手的代码也通不了。
即使抄代码搞通了,但按自己的需求稍微修改代码之后又不通了,有的同学甚至都怀疑自己的硬件有问题。

这个无线模块真的这么难用么?并不是。
一切问题的根本原因在于你没真正的搞懂这个模块,没彻底明白它的工作原理。遇到问题的,有仔细阅读过官方的datasheet么?

开这个帖子的目的,就是想彻底终结有关nrf24l01+各种【入门问题】,我会用通俗易懂的方式将这个芯片的工作原理表达出来。
只要你有基本的电磁学常识,对arduino已经入门,认真看帖,我保证你也能彻底搞懂这个【难用】的无线模块。

帖子应该会比较长,我业余时间有限,所以会循序渐进,分期更新,保证不弃坑。
https://gitee.com/alicedodo/arduino-nrf2401-bootloader
这是我在码云上开源的arduino无线下载bootloader,使用nrf24l01+作为无线模块,希望它可以让大家对这个帖子的质量保持期待。


开场白结束,下面加几个声明:
1. 帖子里不会就某个具体的nrf24的库进行讲解或展示demo,至少在模块工作原理彻底写完之前不会,因为这不重要,如果你搞明白了原理,还怕不会使用封装接口库么?
2. 大家在帖子里不要问"两个模块一对一传输某某格式的数据,代码该怎么写"之类的问题,如果你对工作原理不理解,我说了你也只是一知半解,当然我更没时间给每个类似问题写代码了。
3. 抛开具体代码,讨论基本工作原理的问题可以随便问,知道的我会回答,不知道的我会明确告知,不会乱说。
4. 新来的同学有问题先翻翻帖子,看看之前有没有已经被解答的类似问题,尽量不要伸手就要。解决问题时保持一定的独立性,这是提高自己的好习惯。












该用户从未签到

 楼主| 发表于 2019-5-8 15:00 | 显示全部楼层
lonelyman 发表于 2019-5-8 14:06
持续关注ing,支持楼主

谢谢关注!
第一次在论坛开贴,不懂规矩,更贴有些费劲。
经常码了一段字,也不知道是哪个词有问题,提交时就会提示触发论坛审核,需要转入人工审核。
问过版主,人工审核一天之内会放行,有些慢(中午更的一段这会儿还没刷出来呢)
影响更贴兴致啊

该用户从未签到

 楼主| 发表于 2019-6-19 15:37 | 显示全部楼层
10. PTX的超时重发机制
在【4. 数据传输原理第二讲】中,描述过PTX和PRX之间双向通信的基本过程。
里面说过,PTX掌管着通信过程的主动权,其中一项就是等待PRX端回复时,如果超过限定时间,则PTX会将刚刚发过的数据包再重新发送一次。
这个超时重发还是有不少道道可说的,这一节单拎出来讲讲超时重发机制。
再强调一下:超时和重发只跟PTX有关系,没PRX什么事儿,所以相关寄存器的配置操作,只在PTX端的nrf24l01上配置即可,PRX端不用配置。
nrf_tx_bad.png
nrf_ack_bad.png
上面这两张图(datasheet第43页)描述了两种导致超时重发的意外场景。
图上的内容很多,一时看不明白也不要紧,很多和本节内容无关。
只关心我红框标出的位置即可,跟着下文的描述走就行了。
红圈叉号表示数据包传输时意外丢失(电磁波干扰、信号太弱、PRX没准备好、PRX解析数据出错等等各种意外情况);
矩形红框圈出的是程序给PTX设定好的超时时间,简称ARD(Auto-Retransmit-Delay)
任何一次PTX和PRX之间的通信过程,由PTX负责启动:
在【Enhanced ShockBurst】工作模式下,我们的程序只需要在上图T0时刻按照前面章节描述的方法启动PTX的数据发送,上图中后续的所有过程完全由PTX自主完成。
PTX首先进入TX Mode,将一个PID为1的数据包发送出去。
发送完毕后,PTX立刻开始计时,同时准备进入RX Mode(RX Mode下才能接收数据),这个切换过程的耗时是固定的、精确的、130uS。
130uS一过,PTX正式进入了RX Mode,就可以接收来自对方的数据了。
由于PTX发出去的包PRX没收到,或者PRX发过来的ACK包传输时意外丢失,所以PTX在RX Mode下根本不会收到正确的回复数据。
刚才说过,PTX是一直开着计时的,在上述意外情况下,计时时间是肯定会达到ARD预设的时间值的。当超时的那一刻,PTX就判断出需要执行重发操作了(也可能没有重发流程,这个要看ARC的配置,此细节后面再细说)。
PTX开始执行重发流程,由RX Mode转入TX Mode,中间同样需要130uS的转换时间。
PTX入定TX Mode之后,将刚才PID为1的数据包再给PRX发一次。
上图中这次的发送是成功的,后面一切顺利,正常完成了数据的发送,回复的接收等过程。
当然,上图的示例仅仅展示了重发一次就成功的场景,但我们在实际使用时,有可能遇到更坏的情况。
如果重发的那次再失败呢?那就二次重发!
二次重发也失败呢?三次重发!!
再失败呢? 四次重发!!!
没完没了了是吧?!
PTX当然不会允许自己陷入这种死循环,PTX存在一个重发次数的上限,这个上限数值简称ARC(Auto-Retransmit-Count)。
如果【同一个数据包】,【连续重发次数】累积达到了限定值,PTX会自动终止这种重发循环。
终止之后,PTX会立即触发MAX-RT IRQ,拉低nrf24l01的IRQ脚,告知程序:【重发次数达到上限了,我已经尽力了,你自己看着办吧!】
上面提到的 ARD 和 ARC这两项参数,nrf24l01允许我们的程序自由配置,只不过是N选1的自由。
这两个参数集中放置在一个寄存器中:
nrf_ard_arc.png
如上图:
ARD 占用 bit4-bit7, 0000表示超时上限为250uS / 0001 为 500uS / 以此类推,最大 1111表示4000uS。
ARC 占用 bit0-bit3, 0001表示只重发一次,之后再失败就触发 MAX-RT IRQ , 以此类推,最多重发15次。
特别说一下 ARC=0000的情况,0000表示没有重发流程,如果首次发送数据包失败,就立即触发MAX-RT。
说到这里,再提个小细节:
细心的同学会发现,在PTX在转入RX Mode等待PRX回复的这个时间段,我的描述和实际图示在细节上有出入。
我的描述貌似在说PTX一直在RX Mode状态下等待PRX的回复,直到等待超时;
而图示中PTX在转入RX Mode之后,在里面只呆了很短的时间就退出了RX Mode,ARD期间大部分时间是不在RX Mode中的。
其实两种说法都是对的,看怎么理解^_^
【在ARD限定的时间之内没收到PRX的回复,则判定为超时,触发重发流程】
这句话只是口语化的粗略描述,PTX对于回复超时的判定有一套精确的规则:
规则1: 进入RX Mode那一刻起,250uS内没检测到和自己匹配的地址数据
规则2: 250uS内收到匹配地址,但回复数据太长了,直到ARD限定的时刻,完整的数据包依旧没收完
规则3: 地址匹配,回复数据没等到ARD超时就早早地收完了,但数据被污染,校验出错
以上3种场景任意一种都会触发PTX的超时重发机制。
显然,图示中描述的情况符合条件1,PTX早早就退出了RX Mode(为了省电^_^)。
而我描述的情况符合条件2,ARD设定的不合理,时间太短。
那么ARD和ARC如何选取才【安全/合理/高效】呢?
最安全的,ARD和ARC全部选取最大配置值,即ARD=4000uS ARC=15
安全倒是安全,但是效率太低了:
4毫秒*15=60毫秒,也就是说,4毫秒之后才会重发一次,60毫秒之后,才会触发MAX-RT IRQ。
冗余等待的时间太长了,一旦偶现通信环境短暂干扰,会严重拉低持续通信的平均传输速率。
为了快速重发,ARD的时间长度要尽量短,但太短也不行:
250kBps波特率下,仅仅传输32字节有效数据,就需要花费 (32*8)/250kBps=1024微秒。
再加上数据包里的前导码/地址/CRC等,至少1300多微秒才能传输完毕,所以此时ARD设定值不能小于1500微秒,不然就会触发规则2。
所以,ARD在不同波特率下,配置值是有个下限的,即必须保证【最长ACK包也能安全地完整地传输完毕】。
数据包传输花费时间有一个精确的计算公式:
nrf_air.png
上图位于datasheet第40页。
【air data rate】: 波特率,250K/1M/2M
【package length】:数据包中全部信息的bit数总和
ARD的下限值,大家写程序时根据自己的情况套用公式自行计算。
【package length】有个理论上的上限: 8*(1+5+32+2) + 9 = 329 bit。
根据 [(329)/air-data-rate] 计算出的值可以满足各种包长情况下的完整安全传输。
至于ARC的选取,就没有什么严格标准了,取1也可,取15也可。
我认为一般取5-6就可以了。
【此节完】

该用户从未签到

 楼主| 发表于 2019-6-5 20:45 | 显示全部楼层
本帖最后由 alicedodo 于 2019-6-5 20:48 编辑

8. nrf24l01的工作模式详解

之前在讲nrf24l01功耗的时候,提到过【工作模式】,为了避免过早的牵扯到很细节的内容,在那一节只是粗略归纳了一下:掉电模式/热待机模式/火力全开模式,说的其实很模糊,这一节正式的讲解一下。
先上一张nrf24l01的状态转换图:
174759_3648d234_409556.png


这张图截取自官方datasheet第6章第1节(第21页),如果觉得截图不清楚,可以直接去下载PDF文档,放大了看。
  • 圆环圈起来的是模块的工作状态
    虚线环表示临时状态,模块在此状态下仅做短暂的停留,实线环(不管粗细)表示稳定的工作状态
  • 带箭头的线(无论粗细)表示模块在两个工作状态之间进行【迁移】
  • 箭头线上的文字表示【迁移条件】,即旧状态下只有满足特定条件,模块才会进行状态迁移,没有文字表示无条件迁移
  • 最上面的【undefined】表示模块上电之前的状态
在各处的【迁移条件】上,我们一共看到了这么几个关键词:CE/PWR_UP/PRIM_RX/TX FIFO
  • CE就是上一节提到的CE管脚;
  • PWR_UP和PRIM_RX是nrf24l01内部名为CONFIG的寄存器(地址0x00)的两个bit位;

11111.png

           PWR_UP由0变1,则模块从掉电模式PowerDown切换为待机模式Standby-I,但需要至少1.5毫秒的转换过程;
              PWR_UP由1变0,则模块不论当前正处于什么工作状态,立即转入PowerDown模式,无需等待;
              PRIM_RX则控制模块在工作时是一个什么身份,即PTX还是PRX;
              我们的程序可以使用上一节提到的写寄存区命令 W_REGISTER 来修改这两个bit的值,从而控制模块的工作状态/工作模式
  • TX FIFO是nrf24l01内部的发送缓冲区;
    我们的程序通过SPI接口给模块写入发送数据是很快的,但模块将数据完全发送出去却比较慢,为了缓解双方速度不对等的问题,模块内部设置了一个最大容纳96字节数据的缓冲区,每32字节为一组,共3组,以组为单位构成先入先出(FIFO)缓冲区,即是TX FIFO。
    当我们需要发送很多数据的时候,先一次性将数据写满这3组缓冲区,操作CE管脚启动发送之后,模块再慢悠悠的将缓冲区里的数据以组为基本单元发送出去,发完一组后再取下一组,直到将缓冲区的数据全部发送。
    关于缓冲区的内容这里只简单说一下,后面章节会细聊。

那么状态图中粗环粗线/细环细线是个什么意思呢? 这是官方推荐的模块工作流程。
结合上面的图,从前面描述中我们知道,通过操作【CE/PWR_UP/PRIM_RX/TX FIFO】这4项,我们可以随意控制nrf24l01的状态转换。

以最底下的【TX Mode】状态为例子,根据不同的操作条件,模块在切换工作状态的时候有4条路可走:

【路线1】TX Mode --> Standby-I (条件是一包数据发送完毕且CE管脚为低电平)
【路线2】TX Mode --> PowerDown (不管模块在TX Mode下正在干什么,只要把PWR_UP的bit值改为0,那么模块立即进入掉电模式)
【路线3】TX Mode --> TX Mode (一包数据发完之后,如果缓冲区里还有未发送的且CE管脚为高电平,那么继续呆在TX Mode发送下一包)
【路线4】TX Mode --> Standby-II (缓冲区里的数据已经全部发送完毕,但CE管脚依然是高电平,那么立即转入Standby-II状态)

从写程序的角度上说,这4条路都是可以的,nrf24l01对此并不做限制,但为什么官方偏偏推荐【路线1】?

【路线2】明显过于暴力,不多说。
【路线1】和【路线3/4】唯一的区别就是对CE管脚的操作方式不一样:
【路线1】时,触发发送仅仅是在CE管脚上制造一个10微秒宽度的高电平脉冲,之后CE要回归低电平;
【路线3/4】时,触发发送要简单,只要将CE管脚置为高电平就不管了;

这么看起来,PTX模式下CE管脚一直保持高电平在程序实现上反而更方便,因为使用【路线1】工作流程的时候,每发完一包,我们的程序还必须要主动地为CE管脚制造一个高电平脉冲才能再次触发数据发送。

既然这样为啥官方还是推荐【路线1】呢?

我认为是因为功耗的问题。(真的只是"我认为",datasheet上并没有专门解释,功耗论只是我觉得可信度比较高的理由)
200747_87939c2d_409556.png
从上面这个表可知,虽然同样是待机模式,但Standby-I的功耗要比Standby-II的功耗小一个数量级还多。

当然【路线1】官方只是推荐,我们在写程序的时候不一定非要按照【路线1】来搞,一个写程序更方便,一个更省电,两者各有利弊,自己权衡即可。

那么Standby-II怎么才能转入Standby-I呢?
前面那张状态图上并没有画,其实datasheet在后面章节有提到过: 直接把CE置为低电平即可。

最后,总结一下我们写程序的时候,能从这张状态图中可以得到哪些注意事项:
  • nrf24l01上电之后,我们的程序至少要延时100毫秒,等它稳定进入PowerDown之后才能开始进行寄存器配置
  • 通过修改bit位PWR_UP,让nrf24l01从PowerDown转入Standby-I
  • 通过修改bit位PRIM_RX,可以使nrf24l01进入PTX模式或PRX模式
  • PWR_UP和PRIM_RX在同一个寄存器里,所以可以一次性配置
  • bit位PWR_UP写入1之后,程序必须至少延时1.5毫秒(建议2毫秒),模块才能稳定进入Standby-I,之后程序才能触发数据发送或数据接收
  • CE管脚脉冲式触发的时候,要保证高电平至少持续10微秒(建议15微秒)
  • 程序操作模块的时候,让nrf24l01尽量按照官方推荐的状态转换路线走


该用户从未签到

 楼主| 发表于 2019-5-22 11:48 | 显示全部楼层
【6.nrf2401数据传输原理小结】
【为了和讨论楼层区分,后面帖子正文统一加序号标题,大家看的时候注意区分】
书接上文。
上回说到"地址"可以让到两个nrf2401排除其他节点干扰进行定向的数据传输,但是地址解决不了数据准确性检查的问题,即PRX/PTX收到了对方发来的这一串0101,但不确定信号传输过程中有没有没被干扰过,哪怕只有1个bit被干扰出错了,整份数据就是不可靠的。


串行通信典型如串口通信,为了防止对方接收到错误的数据,一般会在正式数据末尾添加一些用来校验的冗余数据,这些冗余数据被叫做校验码。根据协议不同,校验码长度一般会取1/2/4字节长度。校验码是【对要发送的数据进行某种数学运算而得到的结果】,运算方法各式各样,奇偶校验/累加和校验/CRC校验等。


发送方发送数据之前,先对数据进行校验码计算,然后将检验码附在数据尾部,一并发送出去;
接收方收到数据之后,根据约定格式取出数据部分,然后使用相同的计算方法自己再算一遍校验码;
算出校验码之后,接收方再把数据附带的校验码和自己算出的校验码相比较,值相同则数据是正确无误的,不同则认为数据被污染了,不可信。


nrf2401使用的就是CRC校验,CRC校验有根据校验码长度分为CRC8/CRC16/CRC32,校验码长度分别对应1/2/4字节。nrf2401支持CRC8/CRC16,通过配置寄存器的方式,我们可以让nrf2401二选一。


除此之外,为了让接收方能更可靠的识别出数据流,nrf2401在每份数据之前还加了8bit长度的前导码,类似于串口通信协议中的帧头,前导码的值是精心设计的,定死的。


再除此之外,还记得【4.nrf2401数据传输原理第二讲】中提到的每次发送有32字节最大长度限制么,这并不是说每次发送数据必须发送32字节。如果这次我只有1字节要发送,非得凑够32字节是纯粹的浪费。


nrf2401是可以支持动态数据长度的(当然需要配置寄存器),每次发送的数据的长度,只要不超过32字节都是可以的。既然有了动态包长,为了让接收方知道这份数据有多长,当然需要把数据长度的相关信息也要一并附在数据里啦。


好了,到现在为止,我们在一个数据包里发现了【前导码/地址/数据长度/实际数据/校验码】,这个格式很好很强大,是不是很完美?


骚年,要是你认为它很完美,说明你还是太天真了。


我们来假设一个场景(下文看着有些晕的同学,请再认真看一遍【4.nrf2401数据传输原理第二讲】):


某时,我作为PRX有64字节的数据要回传给PTX,很明显,至少要分两次才能传输完毕,为了方便描述,我在这里用数据包A和数据包B代指,A先B后;
现在我收到了PTX的数据,记作PTX甲包,假设校验通过,那么我会将A包作为回复发送给PTX,然后准备等下次通信的时候回复B包;
过了一会儿,PTX又发来了一个数据包,正常来说我应该回复B包给对方了。
但是等等!直接回复B包可以么?
我怎么知道我第二次收到的这个包是【一个新的PTX乙包】还是【刚才PTX甲包的副本】?要知道第二种情况是可能发生的,如果我回复的A包传输过程中除了问题,导致PTX没收到,PTX等待超时之后会重新发送PTX甲包的。
很明显,如果PRX在收副本包的情况下依然回复B包,对于PTX来说,A包的数据实际上已经丢了,不可恢复。
所以,如果PRX检测出新收到的是PTX甲包的副本,必须重新发送一次A包才可以。
那PRX如何检测新收到的包是不是PTX重发的副本呢?只能是再在数据包里添加字段了,我们把这个新字段称为PID(package ID)。


PID是一个2bit的字段,数值范围 0/1/2/3.
当PTX首次发送数据包给PRX时,PTX将PID设置为0,附带在数据中发送出去;
PRX收到数据之后,取出PID和CRC校验值,拿出小账本记一下这俩值,然后正常回复PTX;
PTX收到PRX的回复之后,将PID加1,附带在第二份新数据中发送出去,而如果没收到PRX回复导致超时,重发旧数据的时候,PID保持不变,发送数据副本;
PRX再次收到了PTX的数据,取出其中的PID和校验值,然后打开自己的小账本,将本子上写的值和它们相比较;
如果PID相同而且CRC校验值也相同,那么PRX就知道这是一个重发的数据副本,就会将刚才回复过的数据重新回复一次;
如果但凡有一个不同,则PRX认为这是一个新的数据包,然后正常回复新数据给PTX。


datasheet上截取的流程图,很直观:
nnnnnnnn3755.png


至此,更新过后的数据格式为:【前导码/地址/数据长度/PID/实际数据/校验码】,这下就很完美了。
下图是从datasheet中截取的数据包格式示意图:


nrf24_pkg_fmt.png


里面有个没提到的 NO_ACK bit,这个没多大用处,讲起来又很费劲,略过。


前面说了这么多操作,包括地址检查、数据校验、重发控制、长度检测、副本判断等等一些列操作,都是nrf2401自主完成的,我们的程序只需要配置好相关的寄存器,给nrf2401写入要发送的数据,剩下的这一些列操作都是由它自己来完成的。


【至此,数据传输原理算是写完了,期间我一直避免牵扯到具体的寄存器或操作时序之类的细节,好让大家从感性上理解nrf2401的工作原理,但我知道,这样的写法只能让你理解工作原理,对写程序仅有理论上的支持,没有直接的代码级别的帮助,所以,从下一节开始,我会贴近官方datasheet,写一些对写代码有直接帮助的内容。】


该用户从未签到

 楼主| 发表于 2019-5-16 14:41 | 显示全部楼层
【5.nrf2401数据传输原理第三讲】
【这两天比较忙,今天终于抽出了一点时间,抓紧更一段】
由无线通信的基本原理可知,当nrf2401发送数据给对端模块的时候要发射电磁波,这本质上是一个像四周空间广播的过程。
既然是广播,只要是有效距离范围内的任意一个处于接收状态的nrf2401模块,都能收到这个信号,那我咋知道我收到的这波数据是不是给我的?


于是"地址"这个概念就出现了:
PTX是主发送端,通信过程由PTX主动发起,发送数据的时候,PTX将表示某个地址的数据附带在要发送的这段数据上,而同时,所有的主接收端即PRX,都在其内部预先存好了一个唯一的地址。这个地址的长度是3到5字节,具体长度是可以通过我们的程序来配置的,3/4/5字节3选1。


PTX发送数据的时候会先将目标地址数据发出去,然后再发送实际的数据;
当PRX识别出有效信号时,会首先从这一长串0101中找出表示地址的那段数据,然后将这个地址和自己设定的地址进行比较,如果相符,则认为数据是发给自己的,继续接收数据,如果不相符,则数据不是发给自己的,直接丢掉,继续等待下一波数据。


目标PRX收到数据只是第一步,如果PRX有数据要回传给PTX,那么它会按照同样的方式,将自己的地址(注意是PRX端的地址,不要搞错)附带在数据前面一并发送出去。由于PTX本来就知道这个地址,所以PTX端遵照同样的接收规则接收回应数据,至此就是一个完整的通信过程。即使PRX没有实际数据要发送给PTX,按照约定规则,它也会回复一份不带有效数据的回应给PTX端,这份回应同样附带地址。


不管是作为PTX还是PRX,我们的程序可以通过nrf2401的SPI接口,按照nrf2401规定的方式,随意修改这个"地址"。但关于地址有几个非常重要的细节要说明:


nrf2401作为PRX的时候,内部【最多】可以同时存在6个接收地址,可以通过配置【分别启用】或【分别禁用】一个或多个接收地址。如果启用了多个地址,那么PRX接收数据信号的时候,数据中的目标地址会同时和这些地址依次比较,只要有一个地址匹配上了,那么PRX就认为这份数据是给自己的。哪个地址匹配上了,PRX回复时就把这个地址附带到回应数据上。


nrf2401把这6个接收地址相关的东西叫做数据通道(data pipe),也就是大家在使用库的时候必定要接触的pipe,编号是pipe0~pipe5,故而这个地址也叫pipe address.


前面说过,pipe地址长度可以随意配置成3/4/5字节,但这6个pipe只能共用一种长度配置,不能分别使用不同的地址长度;
pipe0 地址最长可5字节,地址值随意配置,当取3/4字节地址长度时,5字节中使用最低的那3/4字节,高处字节忽略;
pipe1 特点同pipe0;
pipe2~pipe5 地址最长可5字节,但地址值仅有最低字节可以随意配置,剩余的高位字节只能共用/跟随pipe1中配置的值。


PRX虽然有多个数据通道,但需要注意的是,nrf2401只有一个射频模块,任意时候只能接收一份数据。
如果你在多对一通信时使用【多PTX<-->单PRX的多pipe】方案,需要注意错开各自发送时间。
如果同一时刻两个PTX同时向一个PRX发送数据,即使地址不同,两个信号也会相互干扰,导致PRX谁的信号也解不出来。


再来说说PTX端,前面说过,PTX接收回应数据时也要检测地址,PTX使用数据通道pipe0来接收数据(定死的,不能改),所以我们必须【启用】pipe0通道,并为其【配置好地址和地址宽度】,再强调一下,这个地址严格讲不是PTX的地址,而是目标PRX的地址!实际上PTX是没有自己的地址的。


别忘了PTX刚开始发送数据的时候也需要一个地址,按照常理说,pipe0的地址都配置好了,PTX发送数据时就应该知道要发到哪个地址上,但并不是这样:
不知处于什么设计方面的考虑,nrf2401发送数据前必须要给它单独配置一个发送地址(TX_ADDR),也就是说,想要让PTX正确的发送和接收数据,必须将目标地址配置给PTX配置两次(即将一个地址分别写到两个不同的地方),一次是给pipe0的,一次是给TX_ADDR的,这两个地方的值必须一样。


配置地址宽度对PTX来说也是通用的,即对pipe0和TX_ADDR同时有效。


先写这些,字数不多但是里面的细节很多,且都是对写程序非常重要的细节,大家仔细看看。

该用户从未签到

 楼主| 发表于 2019-9-14 09:19 | 显示全部楼层
rouse520 发表于 2019-9-12 12:00
首先非常感谢楼主的无私奉献精神,自己掌握的知识无私分享给大家,还不厌其烦的回答大家提出的疑问,而且楼 ...

感谢关注

很抱歉 这几天在出远差 基本上没时间兼顾业余方面的事情了
跳频相关的东西其实我也没有什么丰富的实践经验
很多都是停留在"想"的这一层面,有的甚至这一层面也不是很清晰

等出差结束,我整理下思路,把一些跳频相关的想法发到这个帖子里

再次感谢!

该用户从未签到

 楼主| 发表于 2019-5-20 13:53 | 显示全部楼层
a727409529 发表于 2019-5-20 11:41
楼主,非常感谢您的讲解,受益匪浅,最近在用这个东西。我想问几个问题
1.这个编码标准是什么呢?
2.可否讲 ...

1. "这个编码标准是什么呢?"
这句话我没理解是什么意思,"编码"是说的什么编码?

2. 我计划在写完了双向通信原理之后会开始写SPI时序的部分,这里只是简单回答一下吧
   既然你关心的是寄存器的配置,那就只说一下写寄存器的过程:
写寄存器既是通过SPI给nrf2401发送一条命令(格式:命令字节+N个数据字节)
   (1)CSN拉低 (两个作用,即是使能nrf2401的SPI接口,也是让nrf2401知道接下来要开始接收一条命令)
   (2)MOSI输出命令字节,高bit先出,写寄存器命令字节是一个拼接出来的值: 0x20+目标寄存器地址(5bit)
   (3)MOSI依次输出数据字节(即你想给对应寄存器配置的值),多字节时低字节先出,字节内部高bit先出
   (4)CSN拉高,即表示关闭nrf2401的SPI接口,也让nrf2401知道命令结束
注意,写寄存器只能在nrf2401处于PowerDown/Standby-I/Standby-II 这3种工作状态下才有效
如果你还想了解更多又等不及帖子更新的话,可以去找一份官方NRF24L01+的datasheet,看第8章(不长,就五六页吧)

3. 实际操作例程的话,我计划在写完工作原理之后会挑一个库分期写几个demo
   比如第一节只讲如何正确写寄存器,然后再读出来验证我们写入的结果
  第二节在进一步讲如何写入payload,然后触发一下MAX-RT中断
  等等

该用户从未签到

 楼主| 发表于 2019-5-7 17:16 | 显示全部楼层
本帖最后由 alicedodo 于 2019-5-8 13:10 编辑

接着上文继续说说功率。
十万块对王思聪来说是毛毛雨,但对我等DS来说就是巨款了。同理,nrf2401号称是超低功率,发射信号时最大电流也就十几毫安,貌似是很小,但那要看使用条件。
如果你的arduino通过充电器接在了电网上,有海一样的能源供应,那无线模块这点儿消耗绝对是小意思,但是如果你只有两节南孚电池,那这十几毫安的消耗就很可观了,几天就给你搞没电了。而同时呢,我们并不是时时刻刻都有数据要传送,为了省电,多数情况下可以让模块关机嘛,等有数据要发的时候再开机就好了。英雄所见略同,nrf2401的设计者和我们想到一块儿去了,他们给nrf2401设计了一个掉电模式(专业词 PowerDown mode),这个模式下nrf2401消耗多少电流呢? 900nA,整整比火力全开的状态低4个数量级,勤俭节约的典范啊。


可是理想很丰满,现实很骨感,nrf2401在掉电模式为什么这么省电?因为它把包括射频电路在内的绝大部分子模块都关闭了,只有SPI接口的相关电路还能工作(这个不难理解,SPI部分要是关闭了,我们就没办法操作它了)。如果我们要让它从掉电模式进入火力全开模式,nrf2401有很多工作要做,依次开启各个关闭的模块,等待进入稳定状态,这个过程比较耽误时间,大约几个毫秒,当我们对数据传输有较高实时性要求时,这个过程明显太慢了。
于是贴心的设计者又帮我们想到了好办法:在掉电模式和火力全开之间再增加【热待机模式】(Standby)(其实有两种待机模式 Standby-I和 Standby-II,此等细节我们后面再详细讨论),这个模式下只有射频模块的电路是关闭了,其他部分保持激活状态,一旦我们要发送数据,nrf2401只需要很短的准备时间就可以火力全开。但这样做也不是没代价的,由于更多的模块处于激活状态,热待机模式的耗电量和掉电模式相比显著增加,大约在30-300uA这个范围,但是也算很省电了。


除此之外,本着好人做到底的原则,nrf2401设计者在火力全开模式下也为我们增加了功耗控制的手段。在这个模式下射频部分的功耗占整个芯片功耗的绝对大头,自然要拿它开刀了,射频部分的功耗被划分为4档次,由低到高代号分别为0/1/2/3,一般射频功率用dBm这个单位来表示,0/1/2/3分别对应-18/-12/-6/0dBm(是不是有点儿眼熟?大家在使用模块是配置发射功率其实就是这么回事儿),自然功率越好耗电越少啦,对应的,其他方面同等条件下,功率越小传输距离越小,大家在实际应用的时候可以根据自己的情况自行选择使用什么功率配置。


提醒一句,nrf2401模块不管是对外发送数据(TX mode)还是接收数据(RX mode)都是要大幅耗电的,但上一段提到的射频功率控制只能控制对外发送数据,接收数据的功率是没办法控制的,这个一定要记住。

该用户从未签到

 楼主| 发表于 2019-5-7 17:16 | 显示全部楼层
本帖最后由 alicedodo 于 2019-5-7 19:49 编辑

同样是无线模块,从技术实现上说,蓝牙模块要比nrf2401复杂的多得多,但为什么蓝牙模块用起来这么简单?
原因很简单,蓝牙协议是国际标准,数不清的公司为其添砖加瓦,趟平任何技术难题,将复杂的协议包了一层又一层,最后提供了一个非常傻瓜化的使用接口,简单到当串口用就可以了。
而nrf2401只是Nordic这个公司自己的产品,不是通用标准,虽然简单许多,但我们普通使用者需要直接面对模块,最多只有Mirf这样的简单封装库可以使用,而且这些库也只是让我们操作模块的时候少撸几行代码,里面并没有封装什么通用型的通信协议,所以本质上还是在直接操作nrf2401模块,如果你对这个模块不熟悉,自然也就对封装库提供的接口一知半解,似是而非。


好了,闲言少叙,开始正题:
道生一 一生二 三生万物
nrf2401是一个无线模块,通过电磁波传输数据。
不要认为这是一句废话。这是这个帖子后续所有内容的起点,或者说所有的讨论都是对这句话的丰富和细化。慢慢往后看你会明白的。


既然是无线传输,那肯定要占用电磁波频段了。对模块稍微有了解的都知道,这个模块使用的是2.4GHz附近的频率,可是为什么偏偏要用这个波段?
答案是: 这是一个技术问题,更是一个法律问题.
距离相近的两个电台不能使用同一波段,因为会相互干扰,结果就是谁也没法正常通信,所以你占了这个波段我就不能用了,因此电磁波频段是稀缺资源。这种好东西肯定要归国家所有啊,于是全世界所有国家无一例外都纷纷立法,非政府批准,不得随意占用无线电频段。
但总归该留点儿汤吧,于是2.4G附近的频段就成了民间自由使用的极少数频段资源之一了,这也是为什么WiFi/ZigBee/蓝牙/nrf2401都使用这个频段的重要原因。
由此可以推导出第一个问题:
为什么nrf2401有时候会莫名其妙的出现很高的丢包率、时好时坏?
答案是很可能是受WiFi的干扰,2.4G WiFi的频段和nrf2401有很大一块频带是重叠的,如果你家有wifi信号,那很容易会受干扰而导致丢包严重,将nrf2401的频段设置为高于2490MHz可以显著降低丢包率。


接着再引出第二个问题:为什么这个无线模块有效通信距离这么近?
TB上最便宜、最常见的nrf2401模块,上面只有一个nrf2401芯片而且天线还是附着在PCB上的铜线的那种。
空旷无遮挡的地方大约也就100米左右,即使是那种加了功率放大芯片的模块,有效距离也就一两千米。为啥会这么短、而且穿墙能力这么弱?
有两方面原因:(1)2.4G高频电磁波的物理特性导致(2)发射功率太小,这是主要原因
先说(1),电磁波频率越高越容易被物体遮挡,复杂地形位置传输距离越短
再说(2),任何向周围空间均匀传播的能量形式,能量密度都会随着距离的增加而显著的衰减,典型的如声波,好比两个人隔空讲话,对方嗓门不变时,你离得越远听得越不清楚,远到一定程度就什么也听不见了。电磁波也不例外,频率不变的前提下,想要增大传输距离,只能增大发射功率。而nrf2401的功率发射信号时,最大电流也不过11毫安左右,换算成功率也就十几毫瓦,自然传不远,即使那种加了功率增强芯片的模块,发射电流也就一百几十毫安。

该用户从未签到

 楼主| 发表于 2019-5-7 17:16 | 显示全部楼层
本帖最后由 alicedodo 于 2019-5-8 19:35 编辑



功率的事儿聊完了,转过头来说说频率,nrf2401是怎样使用电磁波来传输数据的呢?


我们的数据其实就是一长串0和1的组合,无线传输就是想办法将0/1信号通过电磁波发送出去,专业的词汇叫信号调制,怎么做呢?
nrf2401使用的技术叫FSK(Frequency Shift Keying 直译过来叫频移键控,我认为翻译成键控频移更合适)。
说人话就是:以某个固定频率为基准(也叫载波),通过改变电磁波的频率来传递0或1,即在载波频率的基础上,发0时让频率高一点,发1时让频率低一点,接收端通过持续监测这种频率变化,从而识别出0或1,接收端的这个识别过程叫检波。
从度娘那里搞来一张图:
FSK调制原理简图.png
上面这张图就是FSK的基本原理简图,不过大家不要被这张图的某个细节所误导,即传递1的时候并不是一个正弦波就完事儿了,同样0也不是两三个正弦波就完事了,要准确地传递一个bit,对应的频率信号必须要稳定地持续一段时间,这样接收端才能准确的判断出这是一个有效的bit。


我们假设这个时间是T,(1/T)是啥呢?波特率!是不是很眼熟啊.


其实对于nrf2401来说,上文中的波特率准确的名字应该叫空中波特率(air-data-rate),就FSK的原理上来说,波特率可以是任意值,但nrf2401做了大幅简化,只允许使用3种固定的波特率,分别是 250Kbps/1Mbps/2Mbps,nrf2401允许我们在使用时给它指定一个波特率,3选1。需要注意的是,当nrf2401正处于接收数据或发送数据的工作状态时,不要修改波特率,你修改了,就破坏了当前这份数据的接收/发送规律,必然导致通信失败。


科普完了FSK的原理,有些问题自然而然就明了了:
(1)记得刚才说的"载波频率"吧,nrf2401把它叫做啥呢?"RF Channel frequency"!在nrf2401这里,载波频率是可调的,通过修改nrf2401内部相关寄存器的配置进行调节,既然是由寄存器来决定载波频率,那频率取值肯定是不连续了,以2400MHz为起始值,每隔1MHz取一个频率点,最大一直到2525MHz(设定频率的时候记得避开WiFi干扰),一共126个取值,nrf2401把这些值叫做 RF Channel。 和波特率一样的问题,收发数据的时候不要修改。


(2)由FSK的原理可知,两个模块一收一发,想要正常通信,双方必须使用同样的载波频率,也就是两边RF Channel的值必须相同


(3)由FSK的原理可知,当nrf2401在某载波频率上发送数据时,会占用一个窄窄的、以载波频率为为中心左右延伸的频带。承载bit信息的频率离载波频率越远,接收端越容易识别出这个bit,但坏处是会占用更宽的频带(这个道理应该不难理解吧?)。前面说了,RF Channel的频率间隔是1MHz,如果频带宽度超过了1MHz,那么相邻的两个RF Channel在频带上就会重叠,会相互干扰,这俩Channel就一起废了。


当nrf2401的波特率是250Kbps或1Mbps时,可以做到频带宽度小于1MHz,这样就能保证126个RF Channel互不影响,也就是说,我们共有126个Channel可用,很完美。
但是当波特率是2Mbps的时候,情况出现了变化,频带宽度小于1MHz搞不定,只能做到让频带宽度小于2MHz,也就是说我们只有一半的Channel可以用了。


这是啥原理呢?
还记得前面说的那个时间T么?2Mbps时这个T只有500纳秒,即接收端在接收数据时只有500纳秒的时间来识别每个bit,如果承载bit信息的频率离载波频率太近,就无法保证可靠的识别,那只有加宽频带才能解决这个问题了。
以频带换时间。
  • TA的每日心情
    奋斗
    2020-1-8 08:53
  • 签到天数: 13 天

    [LV.3]偶尔看看II

    发表于 2019-5-8 14:06 | 显示全部楼层
    持续关注ing,支持楼主

    该用户从未签到

    发表于 2019-5-10 08:44 | 显示全部楼层
    很棒,最近接触到了这个模块,不懂原理真的是一头雾水。谢谢!

    该用户从未签到

     楼主| 发表于 2019-5-10 13:25 | 显示全部楼层
    本帖最后由 alicedodo 于 2019-5-10 14:21 编辑

    【4.nrf2401数据传输原理第二讲】
    之前的讨论中,我们已经了解了nrf2401传输bit信息的原理,这一节再延伸一下。
    根据FSK的实现原理可以很明显的看出,这是串行传输模式。和串口传输几乎是一样的,唯一不同的是串口有TX RX两根线,通信两端可以同时接收/发送,互不影响。而反观nrf2401,它内部只有一个射频模块,任意时刻,射频模块只能在【关机/发射信号/接收信号】这3种工作状态中3选1,也就是nrf2401要么只能对外发送数据,要么只能接收数据。


    来点儿专业词汇:
    两个通信节点,数据只能从本端传输到对端而不能反着来,这叫单工通信;
    本端可以发数据给对端,对端也可以发数据给本端,这叫双工通信;
    本端随时可以发数据给对端,对端也可以随时发数据给本端,互不影响,这叫全双工通信;
    虽然两端可以互传数据,但我发的时候你只能收不能发,同样你发的时候我也只能收不能发,这叫半双工通信。
    综上可知,串口属于全双工通信,nrf2401属于半双工通信。


    当我们使用nrf2401进行双向通信的时候,根据项目的不同应该会遇到各种各样的通信场景,我们讨论一下最复杂的情况:
    两个无线节点互相通信,某一时刻,两边都有大量的数据想要尽快传送给对方。半双工的特性决定了肯定无法同时互相传输,应该怎么制定传输方案呢?
    最简单的方法,我先发你收着,等我这边的数据发完了你再给我发。
    这个方法原理上可行,但不合适:
    1. 实时性差。 波特率不变时,数据量越大,传输耗时越长,我这边发给你的数据越多,开始收取你那边数据的时间就越靠后。
    2. 不可靠。相比有线传输,无线传输从原理上就天然的不可靠,很容易受周围空间中其他电磁波的干扰。
    巴拉巴拉传了一长串,万一中间某个地方干扰了一下,哪怕只导致一个bit解析出错,那对方收到的整份数据就是错误的。


    现在换一种方法:
    (1)我们双方约定好,公平使用传输通道,我让让你,你可以先发数据。但有个条件,不管你有多少数据要发给我,发数据的时候,每次发的字节数量不能超过某个固定长度,我们这里假设是32字节。发少了可以,发多了不行。
    (2)你那边发完了这32字节数据之后,要立即转入接收状态。
    此外,我对你还有个要求,一旦你进入了接收状态,不要一直死等,如果过了一段时间你没收到我发给你的数据,请切换到发送状态,把刚才发我的那段数据再发一遍。
    (3)我收到你的数据之后,如果检查没错误,我会立即转入发送状态,把我这边的数据发给你,同样我也会遵守单次发送最大长度32字节的约定。
    (4)如果我收到数据后检查发现有错误,那不好意思,就算我有数据要发给你,但因为你给我的数据是错误的,所以我不搭理你,等着你再给我发一遍。
    (5)还有一种特殊情况:经检查你发的数据没问题,但我没什么数据要发给你,这时候,为了不让你在接收状态死等下去,我会简单发你一个"恩,收到了"之类的无意义回复。
    (6)给你发完回复之后,我不关心你有没有收到,我会接着退出发送状态,进入接收状态继续等待。
    (7)你在接收状态收到了我发给你的数据,知道了"我已收到你的数据",然后你就可以再次转入发送状态,将第二段数据发给我,继续重复前面的过程。
    (8)如果你在接收状态下一直没收到我的回复,导致等待超时了,那说明刚才的交流某个环节出了问题。也许是我根本没收到你的数据,也许你的数据被我检查出错误来了,也许是我发你的回复你没收到。所以,你需要重新发送一次刚才发过的数据。
    (9)某些极端情况下,你可能一遍遍的重发同样的数据,我一直没搭理你。然后,你怒了,能咋办?回家告状去呗!


    以上的描述就是nrf2401实现可靠双向通信的基本过程。
    可以看出,上文中的"你"主动性更大,每次通信的时候,都是由"你"发起的,"我"只是被动的接收,
    然后"我"通过"回复(ACK)"的方式变相的把"我"这边的数据回传给"你".
    通信过程的异常控制也是有"你"这一端来掌控的,包括 超时控制 / 重发控制 / 重发失败足够次数之后终止通信并报错。

    nrf2401把行为如"你"的这一端叫做"主发送端"(Primary Transmitter,简称PTX),
    把行为如"我"的这一端叫做"主接收端"(Primary Receiver,简称PRX),
    以后讨论时我们只使用 PTX / PRX 这两个简写,大家记一下。


    还有一点需要特别强调:
    PTX虽然叫PTX,但PTX可以进入"发送模式"发送数据,也可以进入"接收模式"接收数据;
    PRX同样既可以进入"发送模式"发送数据, 也可以进入"接收模式"接收数据;
    PTX/PRX指的是控制逻辑;"发送/接收模式"指的是射频部分的工作状态,切记两者不要混淆。

    我们计算一下,单次发送32字节数据,需要多长时间:
    2Mbps  波特率: 32*8/2000000 = 128  微秒
    1Mbps  波特率: 32*8/1000000 = 256  微秒
    250Kbps波特率:  32*8/250000  = 1024 微秒 1毫秒多一点点
    可以看出这个时间是很短的,在此基础上,把时间轴拉长,从宏观上看,可以近似看做两端是在同时、双向通信。
    这种方案本质上是将时间划分为多个很小的片段,随着时间片的向前推进,通信双方交替使用唯一的信号通道向对端发送数据。
    专业的词汇叫: 时分复用(Time Division Multiplexing,TDM)

    【关于上面那一长段你我交互的过程,大家仔细看看,确保弄清各个环节,对于后面理解更细致的通信过程有很大帮助。】



    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    热门推荐

    5分钟带你快速了解新一代开发板:M5STACK
    5分钟带你快速了解新一代
    一、什么是M5Stack M5Stack是一种模块化、可堆叠扩展的开发板,每个模块
    创客火首发无人机编队套装,开启不一样的人工智能教育
    创客火首发无人机编队套装
    2017年国务院发布《新一代人工智能发展规划》,提出要广泛开展人工智能科普活动,在中
    【Arduino】108种传感器模块系列实验(129)---BH1750光照传感器
    【Arduino】108种传感器模
    37款传感器与模块的提法,在网络上广泛流传,其实Arduino能够兼容的传感器模块肯定是
    智能插座-blinker-天猫精灵-开源分享
    智能插座-blinker-天猫精
    1。之前学blinker做的一个项目,开源分享一下。视频上传B站,PCB在立创开源。
    在Arduino Nano 33 BLE Sense上用机器学习通过声音分辨不同的昆虫
    在Arduino Nano 33 BLE Se
    用机器学习识别植物的应用有很多,拿出手机,打开APP,拍张照片就可以一键识别眼前盛
    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   
    快速回复 返回顶部 返回列表