楼主: alicedodo

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

[复制链接]

该用户从未签到

发表于 2019-6-10 10:30 | 显示全部楼层
楼主,最近刚过完节,今天又来学习了一下,看到您5号发的这个图25 SPI NOP timing diagram里面的Tcc Tcch,这些不知道代表什么,这个图不是很理解,特地过来请教一下。

该用户从未签到

发表于 2019-6-10 10:31 | 显示全部楼层
a727409529 发表于 2019-6-10 10:30
楼主,最近刚过完节,今天又来学习了一下,看到您5号发的这个图25 SPI NOP timing diagram里面的Tcc Tcch, ...

5月30日发的那篇

该用户从未签到

 楼主| 发表于 2019-6-10 16:46 | 显示全部楼层
a727409529 发表于 2019-6-10 10:30
楼主,最近刚过完节,今天又来学习了一下,看到您5号发的这个图25 SPI NOP timing diagram里面的Tcc Tcch, ...

你的问题有点儿偏门啊

你提到的这张图 名字叫 “SPI NOP timing diagram”【主控端向nrf2401发送NOP指令时,SPI的4根线上的时序图】
关于NOP指令的作用,可以参见官方datasheet第48页的Table 19

紧挨着这个图,第50页有这么个表:
111111111111.png

这个表详细解释了每个时间参数的含义以及参数值
以Tcc为例子,文档上解释的意思是: 从【CSN下降沿开始】到【SCK第一个上升沿】之间的时间间隔。
从上表可以看出,nrf2401要求这个时间间隔必须不能短于2纳秒(Min:2 ns),而Max没填,意思是说只要大于2纳秒,那么你想抻多长时间都可以,不做限制。   
为什么要有个最短时间要求?  
这个很好理解,nrf2401内部的电路对输入信号的反应速度不是无限快的,CSN拉低使能SPI接口之后,电路不可能瞬间就进入工作状态,肯定需要做些准备才能正确识别SCK上的信号变化,所以SCK上的时钟信号必须多等一点时间才能输出。

其他时间参数都类似,就不一一介绍了,可以看下文档。

其实关于这张表里的参数,除了我在帖子正文里提到的那两项,arduino编程的时候,其他参数不需要关心即可,如果arduino通过硬件SPI和nrf2401通信,配置好SPI时钟之后,这些时序上的细节,硬件完全替你代劳了。
码字不易,如果觉得学到了真家伙,请我喝碗咸豆花

该用户从未签到

 楼主| 发表于 2019-6-13 14:07 | 显示全部楼层
9. nrf24l01的数据缓冲区(TX FIFO、RX FIFO)
【温馨提示:】
从本节开始,关于nrf24l01使用,我只会讲和【Enhanced ShockBurst(增强型短时猝发工作模式)】有关的内容,【Enhanced ShockBurst】就是前面屡次提到的【自动回复】工作模式的官方称呼。
所以那种使用PTX/PRX身份互换而实现双向通信的方式就不会做太多涉及了,因为这两种模式对模块的使用差别非常大,交叉写的话可能会对初学的同学造成困扰,对期待这部分内容的同学表示抱歉。
而其实,对于PTX/PRX身份互换双向通信的方式,当你对通信可靠性(丢包/确认/超时/重发)有很高要求时,仔细考虑一下,你会豁然发现,这些要求不早就在【Enhanced ShockBurst】中得到满足了嘛!
上一节在描述状态迁移条件时,简单提过nrf24l01内部数据缓冲区的问题,这节正式讲解一下:
111111111111.png
上面这个图就是缓冲区的框图。
可以看到,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 来读出寄存器的值,从而获取这些状态;
111111111111.png

上面这张表是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的时序应该是:
111111111111.png

特别说一下命令结束时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 来获取数据的长度信息。
111111111111.png
长度拿到之后,就可以使用 R_RX_PAYLOAD 命令将数据读出来了:
111111111111.png
这张图是和上面那张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寄存器中。
111111111111.png
想要拿到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 的寄存器中:
111111111111.png
限于篇幅,每一个位段的含义就不展开了,图上的注释栏写的很清楚,大家自己阅读。
任何一个可以导致TX/RX FIFO发生变化的事件:
比如读写FIFO、清空FIFO、数据已发送成功、收到新数据等等,都会触发 FIFO_STATUS 的刷新。
我们的程序只要在需要的时候读取一下这个寄存器,就能全程实时掌握TX/RX FIFO的最新状态。
写程序的时候要根据需要灵活运用。



码字不易,如果觉得学到了真家伙,请我喝碗咸豆花

该用户从未签到

发表于 2019-6-13 15:46 | 显示全部楼层
alicedodo 发表于 2019-6-10 16:46
你的问题有点儿偏门啊

你提到的这张图 名字叫 “SPI NOP timing diagram”【主控端向nrf2401发送NOP ...

谢谢楼主,认真拜读了。等着继续更新学习。

该用户从未签到

 楼主| 发表于 2019-6-13 16:04 | 显示全部楼层
a727409529 发表于 2019-6-13 15:46
谢谢楼主,认真拜读了。等着继续更新学习。

中午的时候更了一节,触发人工审核了,明天应该可以看到
码字不易,如果觉得学到了真家伙,请我喝碗咸豆花

该用户从未签到

发表于 2019-6-14 10:07 | 显示全部楼层
楼主大人。协议拜读了,程序调通了。谢谢!
我想问参考设计上的问题, datasheet写的负载时15+j88,而参加设计匹配电路50怎么计算的。ANT1天线端的8.2nh 的电感是充当扼流圈吗,我太明白这个2.4g的电平信号怎么输出的。我自己按pi或者L 型都算不出来50偶。 是要把板参数放在ADS 仿真吗。
2电阻电容之间的走线也要设置50偶。叠加起来不就100了吗。

该用户从未签到

 楼主| 发表于 2019-6-14 13:11 | 显示全部楼层
SCYGO 发表于 2019-6-14 10:07
楼主大人。协议拜读了,程序调通了。谢谢!
我想问参考设计上的问题, datasheet写的负载时15+j88,而参加设 ...

咳咳 很是惭愧您的问题我回答不了我的硬件知识只是业余水平 高频电子方面更是纯粹的小白

非常抱歉
码字不易,如果觉得学到了真家伙,请我喝碗咸豆花
  • TA的每日心情
    开心
    2019-6-11 16:29
  • 签到天数: 1 天

    [LV.1]初来乍到

    发表于 2019-6-14 16:36 | 显示全部楼层
    大牛,写的很好,慢慢拜读

    该用户从未签到

    发表于 2019-6-18 13:05 | 显示全部楼层
    @alicedodo  大牛,您好,我这两天在用NRF24L01,遇到这样的问题,重连几次后,就连接不上了,IRQ一直是高电平,测量MCU这边的SPI通信数据,一直有数据,会是哪里的问题?有可能是电源干扰,造成芯片的寄存器被复位了吗?
    您需要登录后才可以回帖 登录 | 立即注册  

    本版积分规则

    Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   ( 蜀ICP备14017632号-3 )
    快速回复 返回顶部 返回列表