arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)

 

 

从本节开始,关于nrf24l01使用,我只会讲和【Enhanced ShockBurst( 增强型短时猝发工作模式 )】有关的内容
【Enhanced ShockBurst】就是前面屡次提到的【自动回复】工作模式的官方称呼。

所以那种使用PTX/PRX身份互换而实现双向通信的方式就不会做太多涉及了,因为这两种模式对模块的使用差别非常大,交叉写的话可能会对初学的同学造成困扰,对期待这部分内容的同学表示抱歉。

而其实,对于PTX/PRX身份互换双向通信的方式,当你对通信可靠性(丢包/确认/超时/重发)有很高要求时,仔细考虑一下,你会豁然发现,这些要求不早就在【Enhanced ShockBurst】中得到满足了嘛!
 
上一节在描述状态迁移条件时,简单提过nrf24l01内部数据缓冲区的问题,这节正式讲解一下:

 

arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第1张图片

 

上面这个图就是缓冲区的框图。

可以看到,nrf24l01既有 发送缓冲区 TX FIFO ,也有 接收缓冲区 RX FIFO

FIFO意思是先入先出队列,一个数据结构的概念,不多说,不了解的可以搜索引擎之。
 
首先需要说明的是:
仅就数据无线传输这个功能来说,数据缓冲区并不是必要的,这个东西仅仅是为了缓解SPI接口和射频模块之间数据传输速度差距巨大的问题而存在的。

就算不给nrf24l01设计这两组缓冲区,无线传输照样可以实现。

实际上,我相信绝大多数人在使用nrf24l01进行双向通信的时候,根本没把【 数据缓冲 】的这个功能利用上:
发一包 --> 收一包 --> 再发一包 --> 再收一包 【你中枪了没?】
 
不管是发数据还是收数据,我们的程序其实只和T X/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 来读出寄存器的值,从而获取这些状态;
 
 
arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第2张图片

 

上面这张表是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的时序应该是:
 
 
arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第3张图片

 

特别说一下命令结束时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 来获取数据的长度信息。
 
 

 

arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第4张图片

长度拿到之后,就可以使用 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寄存器中。

 

arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第5张图片

想要拿到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 的寄存器中:
 
 

arduino笔记37:nrf24l01的数据缓冲区(TX FIFO、RX FIFO)_第6张图片

 

限于篇幅,每一个位段的含义就不展开了,图上的注释栏写的很清楚,大家自己阅读。
任何一个可以导致TX/RX FIFO发生变化的事件:
比如读写FIFO、清空FIFO、数据已发送成功、收到新数据等等,都会触发 FIFO_STATUS 的刷新。
我们的程序只要在需要的时候读取一下这个寄存器,就能全程实时掌握TX/RX FIFO的最新状态。
写程序的时候要根据需要灵活运用。

 

 

你可能感兴趣的:(arduino学习笔记)