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

admin 2020-5-23 1873


开场白:

在讨论区里已经不止一次看到有同学求助有关使用nrf24l01+通信的问题,也看到不少高手发过相关库的使用教程或demo。

但事实证明效果不怎么好,还是时不时有人发帖问这个问题,甚至直接照抄高手的代码也通不了。

即使抄代码搞通了,但按自己的需求稍微修改代码之后又不通了,有的同学甚至都怀疑自己的硬件有问题。

这个无线模块真的这么难用么?并不是。

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

开这个帖子的目的,就是想彻底终结有关nrf24l01+各种【入门问题】,我会用通俗易懂的方式将这个芯片的工作原理表达出来。

只要你有基本的电磁学常识,对arduino已经入门,认真看帖,我保证你也能彻底搞懂这个【难用】的无线模块。

帖子应该会比较长,我业余时间有限,所以会循序渐进,分期更新,保证不弃坑。

https://gitee.com/alicedodo/arduino-nrf2401-bootloader

这是我在码云上开源的arduino无线下载bootloader,使用nrf24l01+作为无线模块,希望它可以让大家对这个帖子的质量保持期待。

开场白结束,下面加几个声明:

1. 帖子里不会就某个具体的nrf24的库进行讲解或展示demo,至少在模块工作原理彻底写完之前不会,因为这不重要,如果你搞明白了原理,还怕不会使用封装接口库么?

2. 大家在帖子里不要问"两个模块一对一传输某某格式的数据,代码该怎么写"之类的问题,如果你对工作原理不理解,我说了你也只是一知半解,当然我更没时间给每个类似问题写代码了。

3. 抛开具体代码,讨论基本工作原理的问题可以随便问,知道的我会回答,不知道的我会明确告知,不会乱说。

4. 新来的同学有问题先翻翻帖子,看看之前有没有已经被解答的类似问题,尽量不要伸手就要。解决问题时保持一定的独立性,这是提高自己的好习惯。


同样是无线模块,从技术实现上说,蓝牙模块要比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毫安左右,换算成功率也就十几毫瓦,自然传不远,即使那种加了功率增强芯片的模块,发射电流也就一百几十毫安。


接着上文继续说说功率。
十万块对王思聪来说是毛毛雨,但对我等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)都是要大幅耗电的,但上一段提到的射频功率控制只能控制对外发送数据,接收数据的功率是没办法控制的,这个一定要记住。


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


我们的数据其实就是一长串0和1的组合,无线传输就是想办法将0/1信号通过电磁波发送出去,专业的词汇叫信号调制,怎么做呢?
nrf2401使用的技术叫FSK(Frequency Shift Keying 直译过来叫频移键控,我认为翻译成键控频移更合适)。
说人话就是:以某个固定频率为基准(也叫载波),通过改变电磁波的频率来传递0或1,即在载波频率的基础上,发0时让频率高一点,发1时让频率低一点,接收端通过持续监测这种频率变化,从而识别出0或1,接收端的这个识别过程叫检波。
从度娘那里搞来一张图:

上面这张图就是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信息的频率离载波频率太近,就无法保证可靠的识别,那只有加宽频带才能解决这个问题了。
以频带换时间。


【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.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同时有效。


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


【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上截取的流程图,很直观:



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





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


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


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


最新回复 [2]
  • admin 2020-5-24
    0 2


    【7. nrf2401模块的接口】【今天发现直接引用码云的图挂了,重新搞一下吧】
    以TB上最常见最便宜的模块为例。

    左边的这个型号最常见,最便宜,大约3/4块钱一个,双排8针,2.54mm间距,模块尺寸比右边那个大;
    右边这个也不少,稍贵,大约6/7块钱一个,单排8针,1.27mm间距,带邮票孔,体积非常小巧,做工我感觉要比前一个好些;
    除此之外,这两种功能上没任何区别。
    都是板载nrf24l01+单芯片,没有PA(射频功率放大)芯片,功率不大,空旷通信距离百米左右。
    根据你自己的实际情况选择:
    如果你【对成本不敏感】且【打算自己做板】且【希望板子做的小巧漂亮】,推荐右边这个;
    除此之外一律选择左边这个;
    根据我自己的经验,列出一些关于硬件使用上的问题(对上面两种型号均适用):
    • 模块供电【一定/必须/不能】超过3.6V,如果你不小心接了5V供电,哪怕就一会儿,请节哀,不要问我是怎么知道的(╥╯^╰╥)。
    • 除VCC和GND之外,其余6个pin却是兼容5V电平的,这也是为什么arduino可以导线直连的方式驱动它的原因。
    • 除【买了便宜到超越底线的模块】之外,不要随便怀疑模块硬件有问题,一个模块能持续多年大量出货且不用升级换代,足以说明模块的稳定性。
    • 所有管脚直连arduino的对应管脚即可,无需【串联】电阻。
    • 用稳压芯片引出的3.3V供电可以直接给模块使用,除供电端极度不稳定之外,几乎不需要并联电容。
    • 如果你用的是带PA的模块,工作电流峰值能达到一百几十毫安,要特别注意供电端的带载能力是否足够。
    下面来说说除VCC/GND之外其余6个管脚的作用,可以先看一下上图中的那个管脚功能表,再看下文。
    我们的程序要使用nrf24l01+,包括配置工作模式/查询工作状态/发送数据/接收数据,全部是通过这6个管脚来和模块进行交互的。
    这6个管脚可以分为3组:
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    CSN/SCK/MOSI/MISO算一组,是SPI接口,往模块里送数据/从模块里读数据都是使用SPI来进行的。
    这里所说的数据有2个含义,即可以是我们打算向外界真实发送的数据,也可以是给这个模块自身的配置数据,也就是它内部各个寄存器的值。
    从底层来说,这些数据就是一串0101,一打眼没什么区别,直接丢给你你也看不出来。
    那nrf24l01+如何知道这一串0101代表什么含义呢? 当然是约定好协议格式啦:
    不管这一串bit流有多长,一律按8bit即单字节为基本单元进行格式划分。
    nrf24l01+规定,双方在传输字节时,必须高位bit优先输出,即输出顺序是: bit7-bit6-bit5-bit4-bit3-bit2-bit1-bit0。
    CSN一旦拉低,模块的SPI接口被使能,同时也代表数据交换【或者叫“一次通信过程”吧】的开始;
    主机从MOSI输出的第一个字节被叫做【命令字】,这个字节的含义是让模块知道主机想要做什么,例如读寄存器/写寄存器/写入待发送数据/读入已接收数据等等;
    以写寄存器为例子,按照一般逻辑,不能只告诉我你想写寄存器,总还得告诉我你想写哪个吧,即必须还要传给我寄存器的地址。
    nrf24l01+内部一共30个寄存器,编址0x00-0x1D,不多不少;
    主机能做的事情也不多,读写寄存器,读写数据等等,总共十多项;
    一个字节总共可以表示256种情况,不算少;
    以上3者一结合,为了提高传输效率,于是砖家在设计nrf24l01+命令字的时候取了个巧,将代表【操作类型】的信息和代表【地址属性】的信息整合到一个字节中,既是【命令字】。
    于是【命令字】从【编码格式】上分,一共有两大类:【只带操作信息不需要地址信息的】/【操作信息地址信息都要有的】。
    下图中的3条命令就是这类复合命令字的典型。
    以上图中读寄存器命令 R_REGISTER 为例,
    操作码是0x00;
    寄存器地址用bit4-bit0来表示,前面说过寄存器地址的最大值不过是0x1D, 5个bit足够容纳了。
    于是修改0x1D寄存器的命令字应该是: 0x00+0x1D = 0x1D。
    主机端在MOSI脚输出命令字的同时, nrf24l01+也没闲着,通过MISO脚也输出了从机端的第1个字节。
    这个字节是有意义的,叫做【状态字】,是模块内部名为STATUS(地址0x07)寄存器数据的副本。
    为什么偏偏选这个这个寄存器的值输出呢? 看一下这个寄存器的位值说明:
    上面的位值表示了模块在工作过程中的一些关键性的状态数据,包括:
    数据是否已经发送成功/是否发送失败/是否收到了外界的新数据/新数据来自哪个pipe/数据缓冲区是否已被写满
    我们的程序在运行时一定会非常频繁的要获知这些状态,【状态字】给我们提供了某种福利:
    不需要专门使用【命令字(0x00+0x07=0x07)】去读取STATUS,任何一次与模块的交互过程都能立即得到STATUS的值。
    当然你非要使用读寄存器的方式获取STATUS也是可以的,就是效率低一点儿。
    【命令字】和【状态字】过后,从第二个字节开始,后面都是数据字节。而数据字节的流向就只是单向的了。
    【命令字】按照【数据流向】划分的话,可以分为3类:
    一类是给nrf2401送数据,比如写寄存器/写入待发送数据;
    一类是从nrf2401里面拿数据,比如读寄存区/读出其他模块发来的数据;
    最后一类是没有数据,只有命令,比如FLUSH_TX/FLUSH_RX只是通知模块清空内部数据缓冲区,明显不需要提供数据。
    不存在既要给模块送数据同时也要从模块里读出数据的情况,所以说数据流向是单向的。
    对于R_REGISTER命令,数据从模块流出,于是有效数据在MISO这根线上,MOSI上的数据就无所谓了;
    同理,对于W_REGISTER命令,数据从主机流入模块,有效数据在MOSI这根线上,MISO上有啥无所谓;
    绝大部分命令只带一个字节的数据就行了,个别情况需要附带多个字节的数据,以W_REGISTER为例:
    数据通道pipe0的地址最长5个字节,在nrf2401内部只给这个pipe0_adr寄存器分配了1个寄存器地址而不是5个。
    这个寄存器地址是0x0A,所以我们可以认为0x0A是一个长度为5字节的寄存器。
    假设我们要给pipe0设置的地址为: 0xA4A3A2A1A0,拆开看从高到低5字节:0xA4 0xA3 0xA2 0xA1 0xA0
    前面说过,SPI传输时字节内部高位bit优先输出,而协议规定传输多字节数据时,【低字节优先】输出。
    于是MOSI信号线上字节流的顺序是这样的: CMD-A0-A1-A2-A3-A4
    最后,数据传输完毕,CSN要拉高,即代表模块的SPI断开了主机的连接,也让nrf2401知晓一条命令结束了。
    这个地方必须提醒,CSN拉高是必须的,哪怕你有很多命令排队等着操作模块,每条命令的写入都要遵守:【CSN拉低-数据传输-CSN拉高】这条规定。
    啰嗦了一大堆话,一张图足以代表一切:
    此外关于SPI时序还有一张比较重要的图:
    解读一下这张图对我们写程序有什么用:
    • 模块要求MOSI/MISO在SCK上升沿的时候输出bit,不管是软件模拟SPI还是硬件SPI要特别注意这一点,尤其是stm32这类SOC当主控时,它的SPI功能非常繁多,配置SPI工作模式的时候要特别注意
    • 两条操作命令之间要在CSN高电平时添加适当延时
      上图红框标出的Tcwh,这个时间代表上一条命令结束后,下条命令开始前,必须要有个恢复时间,防止误动作。
      这个时间不要少于【100纳秒】,保守起见,建议至少延时【500纳秒】。

    关于SPI接口,最后再说一下SCK时钟频率的问题:
    nrf2401模块的时钟频率最高可以到10MHz,但保守起见,我们实际使用是建议时钟速度不要超过8M,留一些余量总是好的。
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    CE 可以单独算一组,这个管脚和nrf2401射频模块的工作状态有关。
    当nrf2401被配置成PTX时,我们通过SPI写入要对外发送的数据之后,模块并不会立即启动发送,
    我们必须再拉高CE管脚并使其高电平保持至少10微秒(10微秒过后,可以拉低也可以继续保持高电平),
    模块才会开启射频模块,进入【发送状态】,开始发送数据。
    当nrf2401被配置成PRX时,同样模块也不会立即启动射频模块进入【接收状态】,
    我们必须拉高CE并使其一直保持高电平,模块才会进入【接收状态】开始监听数据。
    任意时刻,只要CE被拉低了,PRX会立即从【接收状态】退出到待机状态。
    这只是CE管脚的基本用处,更多的细节我们后面在讲完【发送/接收缓冲区】以及【工作状态图】之后再说。
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    IRQ 也单独算一组,这个管脚如其名,是模块用来通知主机【数据发送/数据接收相关的紧急事件】的快捷通道。
    相对于单片机的运行速度来说,nrf2401是比较慢的,8M的SPI时钟下,主机要对外发送32字节数据,
    只需要不到40微秒就能将数据全部塞给模块,而模块想要将这些数据发送完毕,需要的时间却是要多得多。
    主机当然可以在写完数据之后,一遍遍的询问模块数据是否发送完成,但同时主机也不可能去干别的事情了,所以必须要使用中断通知的方式来减轻主机的压力。
    主机写完数据之后就可以去忙别的了,后续发送过程/成功确认/发送失败确认的一系列工作交给模块自主完成,一旦结果出来之后,模块立即将IRQ管脚拉低。
    主机端一般会将自己的外部中断管脚和模块的IRQ管脚连在一起,一旦IRQ管脚被模块置为有效,主机端的外部中断会立即触发,然后就可以在中断函数中访问模块,查看发生了什么。
    这中紧急事件只有3种: 【数据发送成功】/【数据发送失败且已经多次失败】/【收到其他模块发来的新数据】
    当中任意一个单独发生或任意多个同时发生,都会触发IRQ,所以主机程序在获知中断发生之后,必须在读取一遍模块内部的STATUS寄存器才能准确知道是哪一种事件触发了IRQ。
    关于IRQ更细致的问题,后面章节中在讨论。

    8. nrf24l01的工作模式详解

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



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



               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上并没有专门解释,功耗论只是我觉得可信度比较高的理由)
    从上面这个表可知,虽然同样是待机模式,但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尽量按照官方推荐的状态转换路线走


    9. nrf24l01的数据缓冲区(TX FIFO、RX FIFO)

    【温馨提示:】
    从本节开始,关于nrf24l01使用,我只会讲和【Enhanced ShockBurst(增强型短时猝发工作模式)】有关的内容,【Enhanced ShockBurst】就是前面屡次提到的【自动回复】工作模式的官方称呼。
    所以那种使用PTX/PRX身份互换而实现双向通信的方式就不会做太多涉及了,因为这两种模式对模块的使用差别非常大,交叉写的话可能会对初学的同学造成困扰,对期待这部分内容的同学表示抱歉。
    而其实,对于PTX/PRX身份互换双向通信的方式,当你对通信可靠性(丢包/确认/超时/重发)有很高要求时,仔细考虑一下,你会豁然发现,这些要求不早就在【Enhanced ShockBurst】中得到满足了嘛!
    上一节在描述状态迁移条件时,简单提过nrf24l01内部数据缓冲区的问题,这节正式讲解一下:
    上面这个图就是缓冲区的框图。
    可以看到,nrf24l01既有发送缓冲区 TX FIFO,也有接收缓冲区 RX FIFO。
    FIFO意思是先入先出队列,一个数据结构的概念,不多说,不了解的可以搜索引擎之。
    首先需要说明的是:
    仅就数据无线传输这个功能来说,数据缓冲区并不是必要的,这个东西仅仅是为了缓解SPI接口和射频模块之间数据传输速度差距巨大的问题而存在的。
    就算不给nrf24l01设计这两组缓冲区,无线传输照样可以实现。
    实际上,我相信绝大多数人在使用nrf24l01进行双向通信的时候,根本没把【数据缓冲】的这个功能利用上:
    发一包 --> 收一包 --> 再发一包 --> 再收一包 【你中枪了没?】
    不管是发数据还是收数据,我们的程序其实只和TX/RX FIFO打交道:
    发送数据时,程序通过SPI指令将数据写入TX FIFO,启动发送后,nrf24l01再从TX FIFO中取出数据发送出去;
    接收数据时,nrf24l01先将收取到的数据存入RX FIFO,程序通过IRQ获知有数据之后,通过SPI指令将RX FIFO中的数据读取出来;
    从上面的框图中可以看出:
    每32字节组成一个缓冲区单元;
    TX/RX FIFO 各有3个缓冲区单元,理论上一次性可以最多存储96字节的待发送/待接收数据;
    TX FIFO 和 RX FIFO是各自独立的,你存你的,我存我的,互不影响;
    TX/RX FIFO是一个环形先入先出队列,3个单元没有编号,地位完全相同;
    那么,我们在写程序的时候,如何才能操作TX/RX FIFO呢?
    所谓对TX/RX FIFO的操作,可以划分为两大类:
    • 读写类操作,包括如何给TX FIFO写入数据,如何从RX FIFO读出数据,如果数据不想要了如何清空FIFO中的数据等等
    • 查询类操作,包括FIFO中是否存在有效数据,FIFO是否已经写满,是否还有空余位置,RX FIFO某单元中有效数据来自哪个pipe,32字节里有多少是有效数据等等
    为了实现以上各类操作,nrf24l01给我们提供了丰富的手段:
    为读写类操作提供了好几条专用的SPI指令;
    专门设计了几个寄存器,用来记录FIFO的相关状态,通过读寄存器指令 R_REGISTER 来读出寄存器的值,从而获取这些状态;


    上面这张表是nrf24l01提供的所有的SPI指令,我把但凡在FIFO操作中能用到的指令全部标了出来。
    可以看到,SPI指令表中绝大部分都是和FIFO有关的。
    下面逐个讲解一下,会穿插提到一些相关寄存器:


    【------------W_TX_PAYLOAD / W_ACK_PAYLOAD-----------】
    这两条指令是TX FIFO写入数据的专用指令,W_TX_PAYLOAD是PTX专用的,W_ACK_PAYLOAD是PRX专用的,不要用错。
    指令后面附带要写入TX FIFO的数据,数据长度1字节到32字节都可以。
    程序无需特意告诉nrf24l01写入的数据有多长,指令结束之后(指CSN拉高之后),模块自然就知道了数据长度。
    每执行一条写入指令,不管你每次写入1字节还是32字节,都会消耗一个FIFO单元,所以最多执行3次写入指令,TX FIFO就写满了,极端情况下,你每次写入1字节,执行3次,那么整个96字节的TX FIFO实际只包含3字节有效数据。
    TX FIFO写满之后,再执行写入指令,操作无效。
    由于FIFO的特性,写满FIFO之后,发送数据时,哪份数据先被写入,哪份数据先被发送。
    当nrf24l01确认某个FIFO单元的数据发送完成之后,会删除对应的FIFO内的数据,此单元被清空,可以再次执行指令写入新数据。(在自动回复模式下,这里的"发送完成"不是单单指数据从射频模块发出去了,必须收到对端的确认信息之后才算发送完成,这个后面章节会提到)
    注意 W_ACK_PAYLOAD 指令里面还有个PPP的参数,在前面章节【5. 地址和数据通道】中提过PRX总共有6个data pipe,PPP就是用来告诉PRX【这份数据是发给哪个通道的】,PPP取值范围是000-101,分别对应pipe0-pipe5,PPP必须填对才能保证数据被发送到正确的对端上,哪怕是简单的一对一通信。
    假设我们要写入6字节的数据,那么SPI的时序应该是:


    特别说一下命令结束时CSN拉高的问题:
    CSN拉高的时机是我们的程序主动控制的,有多少数据要写入,程序肯定是知道的;
    命令字节输出完毕,开始输出数据字节时,程序自己要数数统计;
    每输出一个数据字节就要加1,当数量够了之后,程序就要主动拉高CSN;
    nrf24l01根据CSN拉高的时机,就能知道此次一共写入了多少数据。


    【------------FLUSH_TX-----------】
    FLUSH_TX负责将TX FIFO中的数据"一键清空"。PTX和PRX均可使用。
    这个指令不需要任何参数,所以附带任何数据字节,只给nrf24l01发送一个命令字即可。
    这条指令一般在【模块初始化】或【重置通信】的场景中使用。


    【------------REUSE_TX_PL-----------】
    这条指令为PTX专用指令,指示模块复用一下上次成功发送出去的数据包(包=数据+地址+校验+...)。单命令字,无需任何数据。
    设想这样一种场景:我们有一个无线手柄和一辆无线遥控小车,手柄作为PTX,小车作为PRX。手柄发送一些固定格式的数据指示小车做一些动作。
    某时候手柄需要小车多次重复做一个动作,程序细节上就是让手柄上的nrf24l01(PTX)反复的发送同一份数据给小车(PRX)。
    要知道,当TX FIFO中的数据成功发送给对端之后,FIFO中的数据就会被移除,所以程序想再发送一次同样数据的话,有2种做法:
    • 重新通过 W_TX_PAYLOAD 再写一次同样的数据进去,然后操作CE启动发送。
    • 使用 REUSE_TX_PL 将刚才发送过的数据包再用一下,然后操作CE启动发送。
    由于 REUSE_TX_PL 不需要附带额外数据,只需一字节的命令字即可,所以方法2的效率大大高于方法1,发送的数据越长(当然最长不能超过32字节),差距越大。
    但是如果 FLUSH_TX 和 W_ACK_PAYLOAD 中有任意一个被执行过,那么 REUSE_TX_PL 就立即失效了,除非再次发送数据成功。


    【------------FLUSH_RX-----------】
    类似FLUSH_TX,只不过清空的是RX FIFO,不多说。PTX和PRX均可使用。


    【------------R_RX_PAYLOAD / R_RX_PL_WID-----------】
    这两条指令也是【PTX和PRX均可使用】,互有关联,所以放在一起讲。
    R_RX_PAYLOAD 是从nrf24l01的RX FIFO中读取有效数据, R_RX_PL_WID 是查询当前这份数据的有效长度。
    不管是PTX还是PRX,当nrf24l01收到对端发来的数据,检查无误后,会将数据存储到RX FIFO中,然后通过IRQ通知我们的程序。
    由于一包数据里有效数据最长就是32字节,所以只会消耗RX FIFO中的一个存储单元,如果这包数据只有1字节,也会占用一整个FIFO单元。
    这里需要注意:
    如果我们的程序反应比较慢,没有及时将RX FIFO中的数据取走的话,RX FIFO有可能被后续新来的数据塞满。
    如果RX FIFO没有可用的空闲单元了,那么nrf24l01不会再收取新的数据包,直到RX FIFO再次出现空闲的存储单元。
    IRQ只是告诉程序有数据到了,但我们的程序此时并不知道这份数据有多少字节,所以真正发 R_RX_PAYLOAD 命令读取数据之前,还要必须首先发送 R_RX_PL_WID 来获取数据的长度信息。
    长度拿到之后,就可以使用 R_RX_PAYLOAD 命令将数据读出来了:
    这张图是和上面那张TX FIFO写入时序图是对应的。
    对方写TX FIFO时数据字节顺序是什么样的,我方读RX FIFO时数据字节顺序就是什么样的。
    CSN拉高的时机和前面类似,由程序自己统计读进来的字节数,数够了之后,主动控拉高CSN。
    对于PTX来说,它只能和一个PRX通信,所以PTX的RX FIFO中的数据确定无误就是来源于那个唯一的PRX。
    但是对于PRX来说却不是这样,PRX最多可以有6个pipe来监听数据,每个pipe对应一个PTX节点。
    只知道数据长度的话,还是不能确定这份数据到底来自哪个PTX节点,所以还必须要知道这份数据来自哪个pipe。
    当然,如果你的应用场景就只是PTX/PRX一对一通信,那么pipe编号没没必要知道了。
    通过前面章节可知,PRX在收取数据的时候是知道来自哪个pipe的,所以PRX将数据存入RX FIFO后,会将这个pipe的编号存入STATUS寄存器中。
    想要拿到STATUS的值,一共3种方法,任选其一:
    • 使用常规的读寄存器命令 R_REGISTER ,读取 STATUS 寄存器的值
    • 使用专用的 NOP 命令获取 STATUS, 比方法1快
    • 由【7. nrf2401模块的接口】可知,无需刻意获取 STATUS,当发送 R_RX_PL_WID 命令的时候,STATUS就顺便拿到了
    总结一下RX FIFO的读取过程:
    PTX读取数据: 获取数据长度 ---> 实际读取数据
    PRX读取数据: 获取数据长度 ---> 获取数据来自哪个pipe ---> 实际读取数据


    【------------R_REGISTER-----------】
    状态查询类的操作,除了 STATUS 寄存器的获取方式多样化之外,其余状态的获取就只能通过 R_REGISTER 来实现了。
    nrf24l01把TX/RX FIFO的当前状态集中存储在了名为 FIFO_STATUS 的寄存器中:
    限于篇幅,每一个位段的含义就不展开了,图上的注释栏写的很清楚,大家自己阅读。
    任何一个可以导致TX/RX FIFO发生变化的事件:
    比如读写FIFO、清空FIFO、数据已发送成功、收到新数据等等,都会触发 FIFO_STATUS 的刷新。
    我们的程序只要在需要的时候读取一下这个寄存器,就能全程实时掌握TX/RX FIFO的最新状态。
    写程序的时候要根据需要灵活运用。

    10. PTX的超时重发机制

    在【4. 数据传输原理第二讲】中,描述过PTX和PRX之间双向通信的基本过程。
    里面说过,PTX掌管着通信过程的主动权,其中一项就是等待PRX端回复时,如果超过限定时间,则PTX会将刚刚发过的数据包再重新发送一次。
    这个超时重发还是有不少道道可说的,这一节单拎出来讲讲超时重发机制。
    再强调一下:超时和重发只跟PTX有关系,没PRX什么事儿,所以相关寄存器的配置操作,只在PTX端的nrf24l01上配置即可,PRX端不用配置。
    上面这两张图(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的自由。
    这两个参数集中放置在一个寄存器中:
    如上图:
    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包也能安全地完整地传输完毕】。
    数据包传输花费时间有一个精确的计算公式:
    上图位于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就可以了。
    【此节完】


  • admin 2020-5-24
    0 3


    11. nrf24l01的中断详解

    前面章节中,中断相关的内容时不时提到,这节详细说一下。
    因为前面有过铺垫,所以就不按部就班的啰嗦前戏了,直接进入正题吧。

    nrf24l01一共3种中断: TX_DS / RX_DR / MAX_RT
    • TX_DS : (TX data sent)通知本端主控,我方的数据已经发出去了。
    • RX_DR : (RX data received/ready)通知本端主控,我收到了新的数据,你要不要看看?
    • MAX_RT: (max for re-transmition)上节详细提过,不赘述了。

    当任意一个或多个中断发生时,IRQ管脚都会被【nrf24l01主动】拉低,即IRQ管脚低电平触发。
    程序知道了IRQ脚变成低电平,只能知道有中断发生了,但并不知道到底是哪个或哪些中断,所以nrf24l01通过STATUS寄存器记录了具体的中断源:


    如上图,标出的每个bit表示对应的中断,bit=0表示当前没有此中断,bit=1表示当前此中断已经触发。
    当IRQ拉低之后,程序读取这个寄存器,就能知道到底是哪个或哪些中断被触发了。
    IRQ管脚被拉低之后,即使我们程序读取了STATUS,管脚【也不会自动地】回归高电平,即nrf24l01不会主动清理中断。

    清理中断的工作必须由我们的程序亲自来操作,做法是:
    【往STATUS寄存器对应bit位上写1】(不要觉得奇怪,这里我没写错,就是写1而不是写0)
    只有当前【所有】被触发的中断【都】被清理了,IRQ管脚才会回归高电平。
    除此之外,程序可以通过配置寄存器的方式来【分别禁用/屏蔽】这些中断:


    如上图,nrf24l01将中断屏蔽位整合到了CONFIG寄存器中,给bit位写1则屏蔽对应中断,写0保持开启。
    个人认为,这个屏蔽设计有些轻微脑残,我实在想不出需要把中断屏蔽掉的理由。
    之所以在这儿添上这些内容,是为了提醒大家不要【误操作】:
    在修改CONFIG其他bit位的时候,不要将中断屏蔽位给误写了,不然程序就再也收不到对应的中断通知了。

    本节上面的描述中,暗含了一个意思:
    IRQ管脚拉低时,可能是某中断单独发生,也可能是多个中断同时发生。
    在【Enhanced ShockBurst】工作模式下:
    PTX 存在TX_DS/RX_DR/MAX_RT这全部的3种类型的中断。
    PRX 只存在TX_DS/RX_DR这2种类型的中断,MAX_RT永远不会出现在PRX端。

    列一下中断可能出现的【所有的】组合情况:

    如果上面表格中"说明"一栏你看了之后觉得更"说不明",那么继续往下看。
    MAX_RT的含义以及什么时候出现,前面章节已经详细说过了,这里略过。
    只说 TX_DS 和 RX_DR,nrf24l01具体满足什么条件时,这两个中断才会触发?


    上面这张图原型来自datasheet的第7章第8节(第39页)。

    描述的是在【Enhanced ShockBurst】工作模式下,PTX和PRX之间一次通信过程的详细时序。

    通信的最终结果是数据传输完毕,两端分别触发了各自的IRQ。
    我加了很多标注,非常详细,这里就不再解说过程了,我相信看这个标注图要比看大段文字更直观。

    先去看图,确保看明白之后再往下看,不然也会很晕^_^

    OK,标注图看明白了,我们继续。

    现在从这张图中提炼一些有用的信息:


    - 针对PTX发送的数据,PRX端的RX_DR在时间上要早于PTX端的TX_DS
    这个结论也许让你有点意外,但确实是这样。
    因为PTX端对通信管控负有更多的责任,必须收到对方的ACK之后才能确认自己发的数据被PRX收到了,才能放心的触发TX_DS。
    而PRX不管这些,只要收到数据就触发RX_DR。

    - PRX端的程序要好好利用回复ACK之前的这130微秒时间段
    这句话里的【130微秒】指的是: 从【PRX触发IRQ】那一刻 到 【开始发送ACK包】那一刻之间的【PLL Lock】时间段。
    我把这个时间段叫做【PRX的黄金时刻】。

    这个时间段有什么特殊的地方?

    如果PRX在这个时间段内,能把一些数据及时地塞进自己的TX FIFO里,那么Auto-ACK时,ACK包里就能带上这些数据!
    这意味着,就在PTX端触发自己这边TX_DS的时候,就会收到这些数据,RX_DR必定会触发。
    而如果PRX错过了机会,例如当ACK开始发送的时候,SPI上的数据还没写完,那么这次Auto-ACK就没机会捎上这些数据了。
    这样的话,PTX想收到这些数据,必须等到下次通信了,时间被大大延后了。

    假设这样一种应用场景:
    PTX端是遥控设备(比如无线手柄),PRX端是受控设备(比如遥控小车/机械臂)。
    遥控器时常发送一些指令(本质是一些约定好协议格式的数据),用来 查询/修改/操作 受控设备;
    受控设备收到指令后,读取/解析/执行 指令,而且必须回复遥控器,例如回传查询结果/确认动作执行完成/参数修改成功等。
    注意受控设备的工作逻辑,它是收到指令之后才去执行的,而且要及时回复数据给遥控器。
    我们假设这些指令都很短,最长也不超过32字节,所以nrf24l01单包传送就能搞定。

    这样的场景很常见吧?
    受控设备(PRX)的工作逻辑决定了: 只有本端RX_DR IRQ触发以后,PRX才能知道该回复什么数据给PTX。
    由此可知,想要最高效的回传数据,必须好好利用上文的【130微秒黄金时段】。

    我们先看一下在上述场景中程序对RX_DR IRQ的一般化的处理过程:
    (1)IRQ触发中断处理程序
    (2)SPI访问STATUS寄存器,确认中断源为RX_DR (其实这步可以省略,PRX端IRQ响了,里面必定会有RX_DR的)
    (3)SPI读取RX FIFO得到指令数据,读入RAM
    (4)SPI写STATUS寄存器,清理中断源
    (5)识别/解析/执行指令,然后生成回复数据
    (6)SPI将回复数据写入TX FIFO
    (7)处理结束

    只要以上(1)-(6)步骤能在130微秒内全部完成,程序就能做到理论上的最快回复。
    在回复数据长达32字节(当然要讨论最坏的情况)的情况下,能实现130微秒全部搞定么?

    假设步骤1非常快,时间忽略。
    步骤2/3/4/6的耗时和SPI的时钟速度有直接关系。
    步骤5的耗时和nrf24l01无关,只和具体的程序逻辑有关。
    步骤2使用NOP指令最快,SPI上要走2个字节。
    步骤3根据【9. nrf24l01的数据缓冲区】这节中的内容可知,一对一通信SPI上【最多】要走大约2+33=35字节。
    步骤4,SPI上要走2个字节。
    步骤6,最长回复时,SPI上要走33字节。
    加起来是2+35+2+33=72字节。

    如果SPI的时钟速度是8MHz(对nrf24l01来说基本就是安全上限了,不要再高了),那么耗时72微秒。
    还有各条命令中间CSN电平保持间隔以及杂七杂八的开销,估算10微秒应该够了。
    这样就已经消耗了82微秒了。
    这里提醒一下,就算你用的不是arduino,而是CPU时钟高达72M的stm32f103,这80多微秒是无法再减少的。
    你的单片机再快(往往其片上SPI也会很快)也得适应nrf24l01上SPI接口的要求不是?

    留给步骤5的时间:130-82=48微秒,约50微秒。
    如果程序设计合理的话,我认为这些时间足够可以计算出回复数据了。
    除此之外,还要考虑整个过程被其他中断打断的情况,这会增加额外的耗时。
    如果SPI的时钟为6M甚至更低,130微秒几乎就不够了。

    - PRX端TX_DS IRQ对分包传输的影响
    再假设另外一个应用场景:
    PRX作为环境数据采集终端,有一堆传感器,实时监测温度/湿度/光照等一些物理参数,每隔一分钟采集一次传感器数据;
    PTX作为这些数据的接收终端,每隔十分钟从PRX取走它积攒的数据,后续用于存储/显示/统计/分析等用途。

    在这里我们先不纠结【采集终端作为PTX还是作为PRX哪个更合理】以及【为啥不每隔一分钟就取走一次数据】的问题,
    只关注在上面已设定好的方案中【数据传输】方面的问题。

    十多次采集积攒的数据量肯定不少。
    把这个场景提炼一下,其实就是: 怎样把大量的数据(大于32字节)从PRX端完整传输到PTX端?

    结合本节前面的内容,方案就很明显了:
    PRX端将数据拆包,然后通过多次Auto-ACK的方式,把数据传给PTX,PTX端再把分包数据还原。
    假设要传输70字节有效数据,则需要拆分成3个数据包,列一下基本流程:
    (1)PTX向PRX查询有多少数据要传送
    (2)PRX通过查询包的Auto-ACK将长度信息回复给PTX
    (3)分包依次传输这些数据,总共3次 (长度拿到了,PTX自然知道会有几包数据)
    (4)PTX收到最后一包有效数据后,还必须给PRX发一个确认包,通知PRX:你的数据我已全部收取。

    步骤4是必要的:
    PRX【必须】要知道它的最后一包数据到底有没有被PTX收到,这样才能确认数据全部发完了。
    PRX【只能】通过本端 TX_DS 中断才能确认某次数据传输(附带在Auto-ACK中的有效数据)OK。
    PRX端的 TX_DS 触发依赖于 PTX端 【再多启动一次】通信,这就是上面所说的【确认包】。

    我把完整的过程做了一张时序图:


    - 【PRX对PTX的分包传输机制】对PTX端【应用数据】传输效率的影响
    先定义一下什么是【应用数据】: 双方传输的那些数据中,和具体通信手段无关的那些数据被称为【应用数据】。
    这里先声明一下,"应用数据"这个词是我为了描述方面而自造的词,不是标准的术语。
    有一个更简单的辨别方式:就算通信模块被替换了,双方依然要传输的那些数据就是应用数据。

    比如我们用串口或者蓝牙替代nrf24l01来通信,通信双方依然需要传输某些数据,且数据格式是不变的。
    在【遥控器和小车】的场景里,控制指令就是应用数据,小车响应查询指令回复的查询结果也是应用数据。
    在【环境数据采集】的场景里,采集到的温度湿度光照等也是应用数据。

    再回到PTX/PRX,上面那个时序图中,【查询数据长度】【取数据】【确认结束】这些包不是应用数据。它们仅仅是【为了分包传输应用数据】而衍生出来的,这里我给他们起个自造的名字:【传输控制数据】。如果你用串口线把无线模块替换掉,那就没有这些东西了。

    也就是说,在nrf24l01眼中的,【有效数据】其实分为两种:【应用数据】和【传输控制数据】。
    【传输控制数据】是属于【传输层】(这个是标准术语^^)层面的东西,和具体的应用无关。
    【应用数据】是属于【应用层】(这个也是标准术语^
    ^)层面的东西,和具体的通信媒介/通信手段无关。

    好了,现在铺垫内容介绍完毕,开始说正题:
    对传输效率的影响第1项: 一些冗余数据(【传输控制数据】) 的加入会降低【应用数据】的平均传输速度。
    对传输效率的影响第2项: PTX在发送应用数据时,TX FIFO的32字节不能都用掉,必须留一部分空间给协议控制使用。

    解释一下第2项:

    PTX端程序在写入TX FIFO时,当然知道写入的数据是应用数据,PRX收到后通知程序,PRX端的程序读出数据。
    但PRX端的程序只知道这是有效数据,却不知道这到底是应用数据还是传输控制数据。
    数据的属性是必须区分的,应用数据要送去上层处理,而传输控制数据不能往上层送,要内部处理掉。

    因此,一包最长32字节的有效数据中,要至少留出一个字节的位置来标识数据的属性:即到底是【应用数据】还是仅仅是【传输控制数据】。
    例如PTX在写TX FIFO时,如果写的是应用数据,TX FIFO第1个字节写0x00,表示后面31字节数据均是应用数据;
    而如果写入的是传输控制数据,那么TX FIFO的第1个字节要写0x01,表示后面的31字节数据是传输控制数据。

    PRX端的程序在读出这份数据后,发现第1字节为0x00,那么就知道是应用数据,取出后送往上层,否则当传输控制数据去解析处理。
    当然,这个额外要求只是针对PTX的,PRX在回复数据的时候不需要这样做,32字节空间随便用就好。
    因为PRX是【主接收端】,只是被动响应,主动权在PTX手里,PRX回复什么完全是应PTX的要求。

    所以PTX对于收到的Auto-ACK数据是什么非常清楚。



    温馨提示:
    最后关于多包传输的这部分内容,仅仅是我自己根据对nrf24l01 IRQ的了解推演出的一些关于编程方面的东西,只是一个尽量通用的方案。这部分不是官方datasheet的内容。大家编程时仅参考即可,不必拘泥。对于你自己的项目,可以在这些内容的基础上进行简化/改造/升级。
    适合自己的才是最好的!

    【把和IRQ有关的,能想到的东西都写了,此节完^_^】


返回