楼主: alicedodo

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

[复制链接]

该用户从未签到

 楼主| 发表于 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)

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



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

该用户从未签到

 楼主| 发表于 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-5-19 10:55 | 显示全部楼层
持续关注,最近被这玩意搞得头晕脑胀的,我用的是mega2560  由于tft屏幕占用了PTX端的spi  只能用模拟spi,网上找了一个库 调通了一次 ,但是不稳定,头文件都翻烂了,都没搞懂.可能是头文件过于简洁并没有设计多对一的函数,连 接收模式和发送模式函数都没有,可能只是一对一的一个实验nrf24l01的库...但是也调通了二对一.两个PRX端不定时的发送失败.头疼.  
   想请教下楼主!不胜感激!
我用了三块 mega2560  一块主控驱动 TFT 电阻屏幕 一个作用是显示两块副板收集的数据, 另外一个是通过对这些数据的处理 操作两块副板控制几个继电器.
三个模块都用了nrf24l01, 主控的频率太慢,刷新TFT都很慢 所以主控没用中断的方式用24l01 因为那样会造成屏幕刷新了一半卡顿了! 所以方式是 两个副板全是监听状态,主控需要刷新数据的时候 先发送一个指令给副板,然后转入监听,接受副板发来的数据,这段时间正好是屏幕清屏的时间,能使屏幕看上去正常一点.
现在的问题就是没找到可用的稳定的适用于mega2560的模拟spi的nrf24l01的驱动. 请楼主帮帮忙 提供一份能用的驱动 不胜感激 有偿帮助! qq:174802980,微信:doyouremeber

该用户从未签到

 楼主| 发表于 2019-5-20 09:42 | 显示全部楼层
mekiss123 发表于 2019-5-19 10:55
持续关注,最近被这玩意搞得头晕脑胀的,我用的是mega2560  由于tft屏幕占用了PTX端的spi  只能用模拟spi,网 ...

1."由于tft屏幕占用了PTX端的spi  只能用模拟spi"
这句话不正确,SPI是可以一主多从的,你研究一下这篇文章:
http://www.emtronix.com/article/article20181111.html
如果现成的硬件SPI的nrf库对一主多从有支持的话,你的项目是可以不使用软件SPI的

2. "由于tft屏幕占用了PTX端的spi" / "两个PRX端不定时的发送失败"
从这两句描述可以看出,你的多对一方案是 2个PRX [vs] 1个PTX, 我觉得为了方便实现,应该对调一下: 2个PTX [vs] 1个PRX, 然后PTX即你的副板采用定时上报的方式启动通信。
nrf2401最多可支持 6个PTX [vs] 1个PRX,即6对1

3. "所以主控没用中断的方式用24l01 因为那样会造成屏幕刷新了一半卡顿了"
这句话我没理解,我认为中断恰恰是解决这种问题的好方式:
(1)硬件SPI一主多从
(2)24l01 IRQ使用外部中断模式处理
(3)TFT代码写SPI的接口加中断屏蔽保护,进去之前关中断 出来后开中断

如果你的SPI CLK是8MHz,那么访问nrf2401单次最大耗时绝对不超过3/400微秒,就这么点阻断TFT刷新的时间,肯定不会对刷新造成卡顿


4. 比较抱歉,我这里确实没有软件SPI相关的驱动程序

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

该用户从未签到

发表于 2019-5-20 11:41 | 显示全部楼层
楼主,非常感谢您的讲解,受益匪浅,最近在用这个东西。我想问几个问题
1.这个编码标准是什么呢?
2.可否讲下内部配置寄存器的SPI时序的工作原理?
3.我用的RF24库,不太清楚如何更改寄存器配置,比如配置发射功率为-18DBM,可否举个例子介绍一下?
谢谢楼主!

该用户从未签到

 楼主| 发表于 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中断
  等等
码字不易,如果觉得学到了真家伙,请我喝碗咸豆花

2人打赏

该用户从未签到

发表于 2019-5-21 09:13 | 显示全部楼层
alicedodo 发表于 2019-5-20 13:53
1. "这个编码标准是什么呢?"
这句话我没理解是什么意思,"编码"是说的什么编码?

好的,谢谢楼主,非常感谢您,可否加个企鹅 727409529 ,常请教下,哈哈

该用户从未签到

发表于 2019-5-21 10:36 | 显示全部楼层
本帖最后由 mekiss123 于 2019-5-21 13:56 编辑

首先非常感谢你的回复,.
1:一主多从的的文章我看了,我还是先用当前的方案继续尝试下,如果解决不了,我就用gpio模拟片选信号试试硬spi驱动nrf24l01的方案,我的数据量不是很多 只是十几路经过处理的传感器的信号.也就20多个三位的整数,.
附:由于 nrf24只能传输char,因此高于125的整数 需要分解传输,懵逼了一上午才解决,解决方式是 在发送端将 整数的高低位 分解,传输完成后 在接收端以同样的方式组合起来, 大家如果碰到可以试试这个思路.
2: 当前用的确实就是这个方式 (一个PTX 两个PRX), 的确要更改,   其实我的设想是, 两个PTX端 平时只在采集数据和接收状态 ,当收到主机发过来的请求信号以后再转为发送状态,将需要发送的数据发送出去 ,这样的话就需要互相通信, 在PTX端用0号外部中断,只采集数据.当中断发生时 将数据整合 发送,然后继续采集数据,不需要PTX端定时发送,    而在PRX端 不用中断方式,是给1号PTX发送一个请求(其实就是一个数字,PTX收到这个数字就动作),然后确定发送成功以后,立即进入接收模式,等待数据的到来. 接收完毕以后 再给2号PTX发送请求...
3:mega2560的主频只有16m,刷新TFT的屏幕很吃力,所以大部分只是静态图像,只有数字部分需要刷新新的数据,还不能整屏刷新刷新一次整屏要好几秒....无语 我的方式是用背景颜色的和当前数值一样的数字覆盖, 所以在覆盖数字的时候中断发生了,就会出现覆盖了一半的情况.... 你说的在刷新时候关中断,刷新完再开我想可以的,只是会不会影响到 数据的接收呢?所以我想的方式就是上面的采用请求的方式,在刷新完以后我再去请求新的数据,这样两不耽误.
我把我找到的软spi的驱动发出来你看看.能不能给尝试修改一下.以我目前的水平还做不到.不胜感激!!!

该用户从未签到

发表于 2019-5-21 10:37 | 显示全部楼层
本帖最后由 mekiss123 于 2019-5-21 10:40 编辑

这是 h文件
#ifndef __nrf24l01_H__
#define __nrf24l01_H__


#include <Arduino.h>


//*****************************************************************************
/* SPI(nRF24L01) 命令 */
#define READ_REG        0x00  // 读寄存器
#define WRITE_REG       0x20  // 写寄存器
#define RD_RX_PLOAD     0x61  // 读RX有效数据1-32字节。读操作全部从字节0开始。当读RX有效数据完成后FIFO寄存器中有效


数据被清除。 应用于接收模式下
#define WR_TX_PLOAD     0xA0  // 写TX有效数据1-32字节。写操作从字节0开始。 应用于发射模式
#define FLUSH_TX        0xE1  // 清除TX FIFO寄存器应用于发射模式下。
#define FLUSH_RX        0xE2  // 清除RX FIFO寄存器应用于接收模式下。 在传输应答信号过程中不应执行此指令。也就是说若传


输应答信号过程中执行此指令的话将使得应答信号不能被完整的传输
#define REUSE_TX_PL     0xE3  // 重新使用上一包有效数据。当CE为高过程中数据包被不断的重新发射,在发射数据包过程//中必


须禁止数据包重利用功能
#define NOP             0xFF  // 空操作。可以用来读状态寄存器


/* Bit Mnemonics */
#define MASK_RX_DR  6
#define MASK_TX_DS  5
#define MASK_MAX_RT 4
#define EN_CRC      3
#define CRCO        2
#define PWR_UP      1
#define PRIM_RX     0
#define ENAA_P5     5
#define ENAA_P4     4
#define ENAA_P3     3
#define ENAA_P2     2
#define ENAA_P1     1
#define ENAA_P0     0
#define ERX_P5      5
#define ERX_P4      4
#define ERX_P3      3
#define ERX_P2      2
#define ERX_P1      1
#define ERX_P0      0
#define AW          0
#define ARD         4
#define ARC         0
#define PLL_LOCK    4
#define RF_DR       3
#define RF_PWR      1
#define LNA_HCURR   0        
#define RX_DR       6
#define TX_DS       5
#define MAX_RT      4
#define RX_P_NO     1
#define TX_FULL     0
#define PLOS_CNT    4
#define ARC_CNT     0
#define TX_REUSE    6
#define FIFO_FULL   5
#define TX_EMPTY    4
#define RX_FULL     1
#define RX_EMPTY    0       


/* SPI(nRF24L01) 寄存器地址*/
#define CONFIG          0x00  // 配置寄存器
#define EN_AA           0x01  // 自动应答
#define EN_RXADDR       0x02  // 接收地址
#define SETUP_AW        0x03  // 地址宽度
#define SETUP_RETR      0x04  // 自动重发
#define RF_CH           0x05  // 射频通道
#define RF_SETUP        0x06  // 射频配置
#define STATUS          0x07  // 寄存器状态
#define OBSERVE_TX      0x08  // 发送检测
#define CD              0x09  // 载波检测
#define RX_ADDR_P0      0x0A  // 数据接收通道0接收地址。最大长度:5个字节先写低字节
#define RX_ADDR_P1      0x0B  // 数据接收通道1接收地址。最大长度:5个字节先写低字节
#define RX_ADDR_P2      0x0C  // 数据接收通道2接收地址。最大长度:5个字节先写低字节
#define RX_ADDR_P3      0x0D  // 数据接收通道3接收地址。最大长度:5个字节先写低字节
#define RX_ADDR_P4      0x0E  // 数据接收通道4接收地址。最大长度:5个字节先写低字节
#define RX_ADDR_P5      0x0F  // 数据接收通道5接收地址。最大长度:5个字节先写低字节
#define TX_ADDR         0x10  // 发送地址先写低字节在增强型ShockBurstTM模式下RX_ADDR_P0与此地址相等
#define RX_PW_P0        0x11  // 接收数据通道0有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define RX_PW_P1        0x12  // 接收数据通道1有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define RX_PW_P2        0x13  // 接收数据通道2有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define RX_PW_P3        0x14  // 接收数据通道3有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define RX_PW_P4        0x15  // 接收数据通道4有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define RX_PW_P5        0x16  // 接收数据通道5有效数据宽度(1到32字节) 0: 设置不合法 1:  1字节有效数据宽度 …… 32:  32


字节有效数据宽度
#define FIFO_STATUS     0x17  // FIFO 状态寄存器


/* 数据长度 */
#define ADR_WIDTH    5        // 地址长度
#define TX_PLOAD_WIDTH  32    // 发送数据长度


/* 中断状态 */
#define RX_IRQ                11
#define TX_IRQ                22
#define MAX_RT_IRQ        33


/* 默认端口号
#define CE       33 //5 33  CE_BIT:   Digital Input     Chip Enable Activates RX or TX mode
#define CSN      32 //4 32  CSN BIT:  Digital Input     SPI Chip Select
#define SCK      35 //7 35  SCK BIT:  Digital Input     SPI Clock
#define MOSI     34 //6 34  MOSI BIT: Digital Input     SPI Slave Data Input
#define MISO     37 //9 37  MISO BIT: Digital Output    SPI Slave Data Output, with tri-state option*/
//以上为主控端口


#define CE       27 //5 33  CE_BIT:   Digital Input     Chip Enable Activates RX or TX mode
#define CSN      26 //4 32  CSN BIT:  Digital Input     SPI Chip Select
#define SCK      24 //7 35  SCK BIT:  Digital Input     SPI Clock
#define MOSI     29 //6 34  MOSI BIT: Digital Input     SPI Slave Data Input
#define MISO     25 //9 37  MISO BIT: Digital Output    SPI Slave Data Output, with tri-state option
//以上为挂车端口
#define DEFAULT_CONFIG ((1 << EN_CRC) | (0 << CRCO))


//*****************************************************************************
class nRF24L01 {
public:
        unsigned char cePin;
        unsigned char csnPin;
        unsigned char sckPin;
        unsigned char mosiPin;
        unsigned char misoPin;
        unsigned char channel;
        unsigned char payload;


        nRF24L01();
        void init(void);
        void powerDown(void);
        unsigned char getStatus(void);
        void setTADDR(char * adr);
        void setRADDR(char * adr);
        void flushRx(void);
        void powerUpRx(void);
        void powerUpTx(void);
        void config(void);
        void send(char * data);
        bool isSending(void);
        bool rxFifoEmpty(void);
        bool dataReady(void);
        void getData(char * data);
        int irqStatus(void);
        bool verifySend(void);
private:
        unsigned char PTX;


        unsigned char SPI_RW(unsigned char byte);
        unsigned char SPI_RW_Reg(unsigned char reg, unsigned char value);
        unsigned char SPI_Read(unsigned char reg);
        unsigned char SPI_Read_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes);
        unsigned char SPI_Write_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes);


        void csnHi(void);
        void csnLow(void);


        void ceHi(void);
        void ceLow(void);




};


#endif /* __nrf24l01_H__ */

该用户从未签到

发表于 2019-5-21 10:37 | 显示全部楼层
本帖最后由 mekiss123 于 2019-5-21 11:05 编辑

这是 cpp  能不能帮忙解读一下,底子太薄 实在看不懂
#include "NRF24L01.h"

nRF24L01::nRF24L01() {
        cePin = CE;
        csnPin = CSN;
        sckPin = SCK;
        mosiPin = MOSI;
        misoPin = MISO;
        channel = 40;
        payload = TX_PLOAD_WIDTH;
}

unsigned char nRF24L01::SPI_RW(unsigned char byte) {
        for(int i = 0; i < 8; i++){
                if(byte & 0x80){
                        digitalWrite(mosiPin, HIGH);
                } else {
                        digitalWrite(mosiPin, LOW);
                }
                digitalWrite(sckPin, HIGH);
                byte <<= 1;
                if(digitalRead(misoPin) == 1){
                        byte |= 1;
                }
                digitalWrite(sckPin, LOW);
        }
        return byte;
}

unsigned char nRF24L01::SPI_RW_Reg(unsigned char reg, unsigned char value) {
        unsigned char status;
        csnLow();
        status = SPI_RW(reg);
        SPI_RW(value);
        csnHi();
        return status;
}

unsigned char nRF24L01::SPI_Read(unsigned char reg) {
        unsigned char reg_val;
        csnLow();
        SPI_RW(reg);
        reg_val = SPI_RW(0);
        csnHi();
        return reg_val;
}

unsigned char nRF24L01::SPI_Read_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes) {
        unsigned char status;
        csnLow();
        status = SPI_RW(reg);
        for(int i = 0; i < bytes; i++) {
                pBuf = SPI_RW(0);
        }
        csnHi();
        return status;
}

unsigned char nRF24L01::SPI_Write_Buf(unsigned char reg, unsigned char *pBuf, unsigned char bytes) {
        unsigned char status;
        csnLow();
        status = SPI_RW(reg);
        for(int i = 0; i < bytes; i++){
                SPI_RW(*pBuf++);
        }
        csnHi();
        return status;
}

void nRF24L01::init(void) {
        pinMode(cePin,  OUTPUT);
        pinMode(csnPin, OUTPUT);
        pinMode(sckPin, OUTPUT);
        pinMode(mosiPin,  OUTPUT);
        pinMode(misoPin, INPUT);

        ceLow();
        csnHi();
}

void nRF24L01::csnHi(void) {
        digitalWrite(csnPin, HIGH);
}

void nRF24L01::csnLow(void) {
        digitalWrite(csnPin, LOW);
}

void nRF24L01::ceHi(void) {
        digitalWrite(cePin, HIGH);
}

void nRF24L01::ceLow(void) {
        digitalWrite(cePin,LOW);
}

void nRF24L01::powerDown(void) {
        ceLow();
        SPI_RW_Reg(WRITE_REG + CONFIG, DEFAULT_CONFIG);
}

unsigned char nRF24L01::getStatus(void) {
        return SPI_Read(STATUS);
}

void nRF24L01::setTADDR(char * adr) {
        SPI_Write_Buf(WRITE_REG + TX_ADDR, (unsigned char *)adr, ADR_WIDTH);
        SPI_Write_Buf(WRITE_REG + RX_ADDR_P0, (unsigned char *)adr, ADR_WIDTH);
        /* 0号读通道必须和发送地址相同,使自动回复生效 */
}

void nRF24L01::setRADDR(char * adr) {
        ceLow();
        SPI_Write_Buf(WRITE_REG + RX_ADDR_P1, (unsigned char *)adr, ADR_WIDTH);
        ceHi();
}

void nRF24L01::flushRx(void) {
        SPI_RW_Reg(FLUSH_RX,0);
}

void nRF24L01::powerUpRx(void) {
        PTX = 0;
        ceLow();
        SPI_RW_Reg(WRITE_REG + CONFIG, DEFAULT_CONFIG | ((1 << PWR_UP) | (1 << PRIM_RX)));
        ceHi();
        SPI_RW_Reg(WRITE_REG + STATUS, (1 << TX_DS) | (1 << MAX_RT));
}

void nRF24L01::powerUpTx(void) {
        PTX = 1;
        SPI_RW_Reg(WRITE_REG + CONFIG, DEFAULT_CONFIG | ((1 << PWR_UP) | (0 << PRIM_RX)));
}

void nRF24L01::config(void) {
        SPI_RW_Reg(WRITE_REG + RF_CH, channel);

        SPI_RW_Reg(WRITE_REG + RX_PW_P0, payload);
        SPI_RW_Reg(WRITE_REG + RX_PW_P1, payload);

        powerUpRx();
        flushRx();
}

void nRF24L01::send(char * data) {
        unsigned char status;
        status = getStatus();

        while(PTX) {
                status = getStatus();

                if((status & ((1 << TX_DS) | (1 << MAX_RT)))) {
                        PTX = 0;
                        break;
                }
        }
        ceLow();
        powerUpTx();
        SPI_RW_Reg(FLUSH_TX,0);
        SPI_Write_Buf(WR_TX_PLOAD,(unsigned char *)data,payload);
        ceHi();
}

bool nRF24L01::isSending(void) {
        unsigned char status;
        if(PTX) {
                status = getStatus();
                if((status & ((1 << TX_DS) | (1 << MAX_RT)))) {
                        powerUpRx();
                        return false;
                }
                return true;
        }
        return false;
}

bool nRF24L01::rxFifoEmpty(void) {
        unsigned char fifoStatus;
        fifoStatus = SPI_Read(FIFO_STATUS);
        return(fifoStatus & (1 << RX_EMPTY));
}

bool nRF24L01::dataReady(void) {
        unsigned char status;
        status = getStatus();
        if(status & (1 << RX_DR)) {
                return 1;
        }
        return !rxFifoEmpty();
}

void nRF24L01::getData(char * data) {
        SPI_Read_Buf(RD_RX_PLOAD,(unsigned char *) data, payload);
        SPI_RW_Reg(WRITE_REG + STATUS, (1 << RX_DR));
}

int nRF24L01::irqStatus(void) {
        unsigned char status;
        status = getStatus();
        if (status & (1 << RX_DR)) {
                return RX_IRQ;
        }
        if (status & (1 << TX_DS)) {
                return TX_IRQ;
        }
        if (status & (1 << MAX_RT)) {
                return MAX_RT_IRQ;
        }
}


bool nRF24L01::verifySend(void) {
        unsigned char status;
        if(PTX) {
                delay(10);
                status = getStatus();
                if (status & (1 << TX_DS)) {
                        powerUpRx();
                        return true;
                }
                if (status & (1 << MAX_RT)) {
                        powerUpRx();
                        return false;
                }
        }
        return false;
}
您需要登录后才可以回帖 登录 | 立即注册  

本版积分规则

热门推荐

【原创】全球最小口袋3D打印机mini one直播教程贴
【原创】全球最小口袋3D打
最近闲得蛋疼,没事搞个掌上3D打印机,先放效果图吧。 搞了半天,终于能正常打印,
SpiderRobot 蜘蛛
SpiderRobot 蜘蛛
关于SpiderRobot 项目实行方案 之前有人推荐我做这个项目,于是乎就有了这个 h
Arduino MEGA 与UNO 通过nRF24L模块通讯
Arduino MEGA 与UNO 通过n
之前在深水宝很“实惠”的店铺买了一些原件,随手砍了esp8266以及nRF24L*3 因为缺
求助!GM65二维码扫描模块怎么用?
求助!GM65二维码扫描模块
求助!GM65二维码扫描模块怎么用? 卖家给的资料真的看不懂该怎么弄。。( ̄▽ ̄)~*
KPM大比拼
KPM大比拼
前言:没别的就是比手快! 方案描述: 做这个的初衷其实是受到一些网红玩具的启发—
Copyright   ©2015-2016  Arduino中文社区  Powered by©Discuz!   ( 蜀ICP备14017632号-3 )
快速回复 返回顶部 返回列表