模块使用教程(基于STM32)——NRF24L01无线通信模块
本文介绍了NRF24L01无线通信模块的工作原理及其驱动代码实现。NRF24L01是一款2.4GHz无线收发器,支持1-32字节有效载荷、自动应答和重传等功能。文章详细讲解了模块的寄存器配置、状态转移、数据包格式以及SPI通信协议。在驱动实现部分,提供了GPIO初始化、SPI数据交换、模式控制等关键函数的代码实现,并给出了发送和接收的完整流程。最后通过主程序示例展示了如何使用该模块进行双向通信,包
参考教程:https://www.bilibili.com/video/BV16bN1zZES6/?spm_id_from=333.1387.favlist.content.click
一、NRF24L01无线通信模块工作原理
1、NRF24L01简介
(1)NRF24L01是Nordic Semiconductor公司开发的一款单芯片2.4GHz无线收发器。
(2)可配置工作频率:2.400GHz~2.525GHz(2.400~2.4835GHz为全球ISM频段,该频段内无需授权即可大范围使用)。
(3)可配置数据传输速率:250kbps(仅+版本支持),1Mbps,2Mbps。
(4)可配置3~5字节地址宽度,可配置1~32字节有效载荷宽度。
(5)可配置发射功率:0dBm,-6dBm,-12dBm,-18dBm。
(6)带有1个发送通道、6个接收通道,支持一对多通信。
(7)运用Enhanced ShockBurst™技术,支持自动数据包组装和定时、自动应答、自动重传。
(8)支持其它高级功能,如动态包长、应答附加载荷、动态应答。
2、芯片框图
(1)NRF24L01+的框图如下图所示。

(2)NRF24L01+主要分为三部分:
①Baseband负责基带信号处理,基带信号可简单理解为二进制数据流。
通过SPI可以实现模块与单片机通信,收到单片机的信号后,通过内部的总线线路,SPI可以将单片机传来的信息传递到各子模块
通过SPI,单片机可以读写Register map(寄存器表,内含电路参数、发送地址、接收地址等重要数据)
TX FIFOs(发送队列)和RX FIFOs(接收队列)各有3个数据包空间,也即可以有3个数据包在队列中排队,等待被发送或被接收,FIFO可通过数据总线与SPI交互,进而实现FIFO与单片机的数据交互
在TX FIFOs中排队的数据会依次送入RF Transmitter模块,由RF Transmitter模块处理后直接发送
RF Receiver接收到的有效数据会依次送入RX FIFOs,待STM32有读命令请求时,RX FIFOs中的数据会通过SPI发送给单片机
Enhanced ShockBurst Baseband Engine和Radio Control负责控制整个数据发送、接收等功能的执行
②RF Transmitter负责射频发射,即把基带的二进制数据流调制成适合天线发送的高频信号,这样就能将信号通过天线发射。
收到TX FIFOs的二进制数据流后,先对其进行GFSK调制,将数据流调制到2.4GHz的工作频率下,再通过发送滤波器处理,再经过PA功率放大器处理,然后将信号输出至天线
③RF Receiver负责射频接收,即从天线接收高频信号,并解调为二进制数据流,将其传入Baseband部分进行处理。
从天线接收到信号后,先使其经过LNA低噪声放大器处理,再经过接收滤波器处理,再经GFSK解调,即可将天线信号还原为基带信号,将其送入RX FIFOs中
(3)模块引脚定义:
①单片机交互引脚:
[1]CSN是SPI的片选,低电平有效。
[2]SCK、MISO和MOSI是SPI的通信引脚,该外设与单片机使用SPI协议进行通信。
[3]IRQ是中断信号输出,当收到数据、发送成功或发送失败等事件发生时,IRQ会输出低电平跳变,可配合单片机的外部中断来实现中断式收发数据,也可供单片机监测IRQ引脚电平以查询是否有事件发生。
[4]CE引脚负责控制新芯片的工作模式,置为高电平时进入收发模式,置低电平时不工作。
②电源相关引脚即框图下方的五个引脚,从左到右依次为电源负极、电源正极、参考电流、内部数字电源输出和功率放大器的电源输出,根据数据手册中的说明连接电路即可。
③XC1和XC2是连接晶振的引脚,ANT1和ANT2是连接天线的引脚。
3、硬件外围电路设计
(1)NRF24L01+的封装如下图所示。

(2)NRF24L01+数据手册提供的参考电路如下图所示,手册中还提供了元器件选型表和PCB布局参考,绘制PCB时可直接参考数据手册。

4、主要工作流程
(1)发送数据流程:
①单片机通过SPI写入待发送数据及发送地址,芯片会根据单片机指定的发送数据和发送地址进行数据包的组装,再增加一些校验位、前导码(用于发送方和接收方同步,确认数据流边界)等信息,然后存至TX FIFOs。
②单片机控制芯片进入发送模式。
③芯片对TX FIFOs中的二进制数据流进行GFSK调制,然后通过天线发送。
(2)接收数据流程:
①天线收到信号后对其进行GFSK解调,得到二进制数据流。
②拆解数据包,比对其中的接收地址和寄存器表中的接收地址是否一致,如果一致,有效数据内容会存至RX FIFOs,同时置标志位表示有数据接收。
③单片机根据标志位判断是否有数据接收,如有,可通过SPI读取RX FIFOs中的数据。
④增强型ShockBurst支持自动应答,接收数据后,接收方和发送发角色互换,接收方发送一个空数据包进行应答,这个过程由芯片自主完成。

5、数据包格式
(1)数据包的组装由芯片自主完成,一个数据包中包含了数据、地址等信息。
(2)ShockBurst数据包格式(旧版):
①从左到右依次为1字节的前导码,3~5字节的地址,1~32字节的有效载荷数据,1~2字节的CRC校验码。
②字节数浮动的段,需要收发双方事先约定好使用的位数,否则协议冲突,无法正常通信。

(3)Enhanced ShockBurst数据包格式:
①从左到右依次为1字节的前导码,3~5字节的地址,6bit的有效载荷长度(可理解为数据段长度,用于服务动态包长,这样数据段的长度就可以不固定了),2bit的包标识符(用于支持自动应答和自动重发),1bit的不要应答标志位,0~32字节的有效载荷数据,1~2字节的CRC校验码。
②字节数浮动的段,除有效载荷数据段外,需要收发双方事先约定好使用的位数,否则协议冲突,无法正常通信。
③通过配置寄存器,可以使Enhanced ShockBurst数据包格式兼容ShockBurst数据包格式。

6、状态转移图与模式控制
(1)下图所示的是芯片各个工作模式的切换示意图。

(2)各状态释义:
|
状态 |
释义 |
|
Undefined(未定义状态) |
初始化状态 |
|
Power Down(掉电模式) |
芯片不工作,处于最低功耗状态 |
|
Standby-1(待机模式1) |
芯片成功起振,准备就绪,比掉电模式功耗高 |
|
Standby-2(待机模式2) |
芯片欲进入发送模式,但无数据可发,暂时待机 |
|
RX Mode(接收模式) |
芯片处于接收数据模式 |
|
TX Mode(发送模式) |
芯片处于发送数据模式 |
(3)状态转移条件:
|
状态转移 |
条件 |
|
Undefined(未定义状态)→Power on reset 100ms→Power Down(掉电模式) |
VDD电压大于1.9V后,经过100ms的上电复位,即芯片供电电压大于1.9V后,芯片才开始工作 |
|
Undefined(未定义状态)→Crystal oscillator start up Tpd2stby→Standby-1(待机模式1) |
通过SPI置寄存器标志位PWR_UP为1,芯片经过Tpd2stby这段时间(这段时间用于晶振起振)后,进入待机模式1 |
|
Standby-1(待机模式1)→Power Down(掉电模式) |
通过SPI置寄存器标志位PWR_UP为0,马上回到掉电模式 |
|
Standby-1(待机模式1)→Rx Setting 130us→RX Mode(接收模式) |
通过SPI置寄存器标志位PRIM_RX为1,并置CE引脚为高电平,芯片经过130us的RX设置后,进入接收模式 |
|
RX Mode(接收模式)→Standby-1(待机模式1) |
置CE引脚为低电平,进入待机模式1 |
|
Standby-1(待机模式1)→Tx Setting 130us→TX Mode(发送模式) |
通过SPI置寄存器标志位PRIM_RX为0,并置CE引脚为高电平持续至少10us,且发送队列非空,则芯片经过130us的RX设置后,进入发送模式 |
|
TX Mode(发送模式)→Standby-1(待机模式1) |
置CE引脚为低电平,且一个数据包发送完毕(这意味着即使数据发送过程中CE跳0,也会发送一个完整的数据包),进入待机模式1 |
|
Standby-1(待机模式1)→Standby-2(待机模式2) |
通过SPI置寄存器标志位PRIM_RX为0,并置CE引脚为高电平,且发送队列为空,芯片进入待机模式2 |
|
Standby-2(待机模式2)→Tx Setting 130us→TX Mode(发送模式) |
置CE引脚为高电平,且发送队列非空,则芯片经过130us的RX设置后,进入发送模式 |
|
TX Mode(发送模式)→TX Mode(发送模式) |
置CE引脚为高电平,且发送队列非空,芯片不退出发送模式 |
|
TX Mode(发送模式)→Standby-2(待机模式2) |
置CE引脚为高电平,且发送队列为空,芯片进入待机模式2 |
|
…… |
其余转移线较易理解,此处不再过多赘述,具体可见数据手册…… |
(4)模式控制方式总结:
掉电模式: PWR_UP = 0
待机模式I: PWR_UP = 1,CE = 0
接收模式: PWR_UP = 1,CE = 1,PRIM_RX = 1
发送模式: PWR_UP = 1,CE = 1,PRIM_RX = 0
待机模式II: PWR_UP = 1,CE = 1,PRIM_RX = 0,TX FIFO为空

7、一对多通信
(1)每个设备配置有1个发送通道和6个接收通道,这意味着它可以实现一对多通信。
(2)带有应答的传输过程:
①PTX(主发送)设备发出数据包,PRX(主接收)设备地址匹配的接收通道收到数据包。
②PRX(主接收)设备以相同地址发出应答包,PTX(主发送)设备接收通道0收到应答包。

(3)6个接收通道分别是通道0~通道5,每个通道的地址长度是5个字节,其中只有通道0和通道1的地址可任意配置,通道2~通道5的高4字节与通道1保持一致,所以通道0~通道1的地址寄存器长度为5字节,而通道2~通道5的地址寄存器长度为1字节。

8、自动应答和自动重传
(1)常规的传输和应答过程:
①MCU PTX进行UL,即发送方单片机通过SPI写入要发送的数据。
②PTX设备开始生成发送波形,包的PID为1,其作用为避免自动重传时接收方重复收到数据包;同时,另一个PRX设备开始接收这个波形。
③一个数据包传输完成,PRX设备的IRQ引脚输出低电平跳变,同时产生RX_DR中断,表示有新数据接收完成。
④MCU PRX进行DL,即接收方单片机通过SPI读取接收到的数据。
⑤一个数据包传输完成后等待130us,PRX设备进入发送模式,PTX设备进入接收模式,PRX设备会自动向PTX设备再发送一个应答包,格式与数据包类似,包含有地址信息;同时PTX设备在接收通道0接收这个应答包,收到应答包后产生TX_DS中断,表示数据发送成功。

(2)传输过程中数据包丢包的处理过程:
①PTX设备第一次发送PID=1的数据包,如果该数据包在传输过程中丢失,接收方收不到数据,也就不会回复应答信号。
②PTX设备转入接收模式后,如果收不到应答信号,接收状态会自动结束以保证能耗。
③在ARD时段(自动重传时间可配置)过去后,PTX设备开始重传数据包,PID仍为1,表示是上一个数据包的重传。
④如果发送的数据包持续丢失,PTX设备不会一直重传,可配置最大重传次数,超过此次数后,PTX设备会回到待机状态,并置MAT_RT为1。

(3)传输过程中应答包丢包的处理过程:
①一个数据包传输完成后等待130us,PRX设备进入发送模式,PTX设备进入接收模式,PRX设备会自动向PTX设备发送一个应答包,如果应答包在传输过程中丢失,PTX设备没收到应答信号,会认为发送失败。
②在ARD时段(自动重传时间可配置)过去后,PTX设备开始重传数据包,PID仍为1,表示是上一个数据包的重传;同时PRX设备开始接收数据包,检测到这个数据包PID仍为1,说明是上一个数据包的重复(如果是新的数据包,PTX设备在发送前会置PID自增1),直接丢弃该数据包,但还是要回复应答信号。

9、NRF24L01指令传输和读写操作
(1)SPI协议中四根通信线的作用:SS/CS选中从机,在SCK时钟的同步下,MOSI传输主机至从机的数据流,MISO传输从机至主机的数据流,一次传输会发送同时接收一个字节。

(2)NRF24L01的SPI通信过程:CSN(SS/CS)下降沿通信开始,第一个发送的字节表示指令,根据指令的不同,后续可以跟写操作、读操作或者不执行操作,最后CSN(SS/CS)上升沿通信结束。
①读操作SPI时序:

②写操作SPI时序:

(3)NRF24L01指令集:
①读寄存器指令的英文名为R_REGISTER,其指令码为“0b000x xxxx”(二进制),高3位表示读命令,低5位指定读取的寄存器地址。芯片中有几个寄存器比较特殊,如发送地址寄存器、接收通道0/1地址寄存器各自有5个字节,如果需要读它们,该指令后面可以跟多个SPI交换数据的操作。
②写寄存器指令的英文名为W_REGISTER,其指令码为“0b001x xxxx”(二进制),高3位表示写命令,低5位指定写入的寄存器地址,该指令只能在掉电或待机模式下执行。芯片中有几个寄存器比较特殊,如发送地址寄存器、接收通道0/1地址寄存器各自有5个字节,如果需要写它们,该指令后面可以跟多个SPI交换数据的操作。
③读接收有效载荷指令(即读RX FIFOs)的英文名为R_RX_PAYLOAD,其指令码为“0x61”(十六进制),该指令后可以跟1~32字节的SPI交换数据操作,RX FIFOs中的有效载荷被读取后将直接删除,该指令只能在接收模式下使用。
④写发送有效载荷指令(即写TX FIFOs)的英文名为W_TX_PAYLOAD,其指令码为“0xA0”(十六进制),该指令后可以跟1~32字节的SPI交换数据操作,写入的数据会进入TX FIFOs中,该指令在发送时使用。
⑤FLUSH_TX指令用于清空TX FIFOs中的所有数据,其指令码为“0xE1”(十六进制),这是单独指令,后面不跟任何写/读操作。
⑥FLUSH_RX指令用于清空RX FIFOs中的所有数据,其指令码为“0xE2”(十六进制),这是单独指令,后面不跟任何写/读操作,该指令在传输应答时不应被执行。
⑦REUSE_TX_PL指令用于重新发送上次传输的有效载荷,其指令码为“0xE3”(十六进制),这是单独指令,后面不跟任何写/读操作,该指令用于PTX设备。
⑧NOP指令是空指令,从机不执行任何操作,其指令码为“0xFF”(十六进制),可以使用这个指令读取状态寄存器,主机可以从发空指令交换回的数据得到状态寄存器的内容(实际上,当主机发送任何SPI命令时,SPI都会将从机状态寄存器的值换回给主机)。

(4)NRF24L01寄存器定义表(概览,具体可参考芯片手册):
|
地址 |
名称 |
释义 |
地址 |
名称 |
释义 |
|
00 |
CONFIG |
配置寄存器 |
0D |
RX_ADDR_P3 |
接收通道3地址 |
|
01 |
EN_AA |
使能自动应答 |
0E |
RX_ADDR_P4 |
接收通道4地址 |
|
02 |
EN_RXADDR |
使能接收通道 |
0F |
RX_ADDR_P5 |
接收通道5地址 |
|
03 |
SETUP_AW |
设置地址宽度 |
10 |
TX_ADDR |
发送地址(5字节) |
|
04 |
SETUP_RETR |
设置自动重传 |
11 |
RX_PW_P0 |
接收通道0宽度 |
|
05 |
RF_CH |
射频通道 |
12 |
RX_PW_P1 |
接收通道1宽度 |
|
06 |
RF_SETUP |
射频相关参数设置 |
13 |
RX_PW_P2 |
接收通道2宽度 |
|
07 |
STATUS |
状态寄存器 |
14 |
RX_PW_P3 |
接收通道3宽度 |
|
08 |
OBSERVE_TX |
发送观察寄存器 |
15 |
RX_PW_P4 |
接收通道4宽度 |
|
09 |
RPD |
接收功率检测 |
16 |
RX_PW_P5 |
接收通道5宽度 |
|
0A |
RX_ADDR_P0 |
接收通道0地址(5字节) |
17 |
FIFO_STATUS |
FIFO状态 |
|
0B |
RX_ADDR_P1 |
接收通道1地址(5字节) |
1C |
DYNPD |
使能动态包长 |
|
0C |
RX_ADDR_P2 |
接收通道2地址 |
1D |
FEATURE |
使能高级特征 |
(5)状态寄存器定义:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7 |
0b0 |
R/W |
预留 |
|
RX_DR |
6 |
0b0 |
R/W |
RX FIFO数据就绪中断,当新数据到达RX FIFO时该位置1 写1表示清除此状态位 |
|
TX_DS |
5 |
0b0 |
R/W |
数据已发送TX FIFO中断,当数据包成功发送时该位置1,若激活自动应答,则需收到应答信号才置1 写1表示清除此状态位 |
|
MAX_RT |
4 |
0b0 |
R/W |
最大TX重传中断,事件触发时置1 写1表示清除此状态位 如果MAX_RT为1,则必须清除状态位,否则无法通信 |
|
RX_P_NO |
3:1 |
0b111 |
R |
RX FIFO读取有效负载的通道号 000-101:通道号 110:不定义 111:RX FIFO为空 |
|
TX_FULL |
0 |
0b0 |
R |
1:TX FIFO满 0:TX FIFO有空位 |
(6)其它常用寄存器定义:
配置寄存器CONFIG:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7 |
0b0 |
R/W |
预留 |
|
MASK_RX_DR |
6 |
0b0 |
R/W |
收到数据中断配置 1:不开启此中断 0:事件触发时IRQ输出低电平 |
|
MASK_TX_DS |
5 |
0b0 |
R/W |
成功发出数据中断配置 1:不开启此中断 0:事件触发时IRQ输出低电平 |
|
MASK_MAX_RT |
4 |
0b0 |
R/W |
达到最大重传次数中断配置 1:不开启此中断 0:事件触发时IRQ输出低电平 |
|
EN_CRC |
3 |
0b1 |
R/W |
使能CRC功能,如果开启了自动应答功能,此位必须为1 |
|
CRCO |
2 |
0b0 |
R/W |
控制CRC校验使用的字节数 0:1字节 1:2字节 |
|
PWM_UP |
1 |
0b0 |
R/W |
1:POWER UP,上电 0:POWER DOWN,掉电 |
|
PRIM_RX |
0 |
0b0 |
R/W |
用于RX/TX的控制 1:PRX,主接收模式 0:PTX,主发送模式 |
使能自动应答功能寄存器EN_AA:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7:6 |
0b00 |
R/W |
预留 |
|
ENAA_P5 |
5 |
0b1 |
R/W |
1:使能接收通道5的自动应答 0:失能接收通道5的自动应答 |
|
ENAA_P4 |
4 |
0b1 |
R/W |
1:使能接收通道4的自动应答 0:失能接收通道4的自动应答 |
|
ENAA_P3 |
3 |
0b1 |
R/W |
1:使能接收通道3的自动应答 0:失能接收通道3的自动应答 |
|
ENAA_P2 |
2 |
0b1 |
R/W |
1:使能接收通道2的自动应答 0:失能接收通道2的自动应答 |
|
ENAA_P1 |
1 |
0b1 |
R/W |
1:使能接收通道1的自动应答 0:失能接收通道1的自动应答 |
|
ENAA_P0 |
0 |
0b1 |
R/W |
1:使能接收通道0的自动应答 0:失能接收通道0的自动应答 |
使能接收地址寄存器EN_RXADDR:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7:6 |
0b00 |
R/W |
预留 |
|
EPX_P5 |
5 |
0b0 |
R/W |
1:使能接收通道5 0:失能接收通道5 |
|
EPX_P4 |
4 |
0b0 |
R/W |
1:使能接收通道4 0:失能接收通道4 |
|
EPX_P3 |
3 |
0b0 |
R/W |
1:使能接收通道3 0:失能接收通道3 |
|
EPX_P2 |
2 |
0b0 |
R/W |
1:使能接收通道2 0:失能接收通道2 |
|
EPX_P1 |
1 |
0b1 |
R/W |
1:使能接收通道1 0:失能接收通道1 |
|
EPX_P0 |
0 |
0b1 |
R/W |
1:使能接收通道0 0:失能接收通道0 |
地址宽度设置寄存器SETUP_AW:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7:2 |
0b000000 |
R/W |
预留 |
|
AW |
1:0 |
0b11 |
R/W |
00:非法值 01:地址长度为3字节 10:地址长度为4字节 11:地址长度为5字节 |
自动重传设置寄存器SETUP_RETR:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
ARDa |
7:4 |
0b0000 |
R/W |
设置自动重传的延时 0000:延时设置为250us 0001:延时设置为500us ……(等差数列关系,公差为250us) 1111:延时设置为4000us |
|
ARC |
3:0 |
0b0011 |
R/W |
设置重传次数,ARC的值转换为十进制即为最大允许重传次数 |
射频通道寄存器RF_CH:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7 |
0b0 |
R/W |
预留 |
|
RF_CH |
6:0 |
0b0000010 |
R/W |
配置射频的工作频率为(2400+RF_CH)MHz,默认值为2.402GHz |
射频参数配置寄存器RF_SETUP:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
CONT_WAVE |
7 |
0b0 |
R/W |
可不关注 |
|
Reserved |
6 |
0b0 |
R/W |
预留 |
|
RF_DR_LOW |
5 |
0b0 |
R/W |
配置数据传输速率,与RF_DR_HIGH共同作用,具体见RF_DR_HIGH备注 |
|
PLL_LOCK |
4 |
0b0 |
R/W |
可不关注 |
|
RF_DR_HIGH |
3 |
0b1 |
R/W |
配置数据传输速率,与RF_DR_LOW共同作用 [RF_DR_LOW, RF_DR_HIGH]: 00:配置传输速率为1Mbps 01:配置传输速率为2Mbps 10:配置传输速率为250kbps 11:预留 |
|
RF_PWR |
2:1 |
0b11 |
R/W |
配置发送功率 00:配置发送功率为-18dBm 01:配置发送功率为-12dBm 10:配置发送功率为-6dBm 11:配置发送功率为0dBm |
|
Obsolete |
0 |
/ |
/ |
不必关注 |
发送观察寄存器OBSERVE_TX:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
PLOS_CNT |
7:4 |
0b0000 |
R |
记录在最大重传数之后没有通过的报文数 |
|
ARC_CNT |
3:0 |
0b0011 |
R |
记录自动重传成功时重传的数据包个数,也就是重传了多少次才成功 |
FIFO_STATUS队列状态寄存器:
|
助记符 |
位 |
重置值 |
类型 |
备注 |
|
Reserved |
7 |
0b0 |
R/W |
可不关注 |
|
TX_REUSE |
6 |
0b0 |
R |
1:TX FIFO为空 0:TX FIFO非空 |
|
TX_FULL |
5 |
0b0 |
R |
1:TX FIFO已满 0:TX FIFO未满 |
|
RX_EMPTY |
4 |
0b0 |
R |
可不关注 |
|
Reserved |
3:2 |
0b00 |
R/W |
预留 |
|
RX_FULL |
1 |
0b0 |
R |
1:RX FIFO已满 0:RX FIFO未满 |
|
RX_EMPTY |
0 |
0b1 |
R |
1:RX FIFO为空 0:RX FIFO非空 |
10、读写操作示例
(1)写寄存器,给SETUP_RETR寄存器写入0x55。

(2)读寄存器,读出RF_CH寄存器的值。

(3)写寄存器,给TX_ADDR寄存器写入0x11, 0x22, 0x33, 0x44, 0x55。

(4)读寄存器,读出TX_ADDR寄存器的值,读5字节。

(5)写发送有效载荷,给TX_PAYLOAD写入0x01, 0x02, 0x03, 0x04。

(6)读接收有效载荷,读出RX_PAYLOAD的值,读4字节。

(7)清空发送FIFO。

(8)快速读状态寄存器。

二、NRF24L01驱动代码实现
1、准备工作
(1)按下图所示接好电路,同样的电路需准备两份,设计两个单片机通过NRF24L01进行通信(说明:本实验程序适用于NRF24L01、NRF24L01+和Si24R1)。

(2)拷贝一份STM32教程中“使用OLED屏进行显示”的工程文件夹,并更名为“NRF24L01驱动代码实现”,然后新建三个文件并添加至工程中,分别命名为NRF24L01.c、NRF24L01.h和NRF24L01_Define.h,将它们存放在Hardware文件夹中。

2、模块编写
(1)NRF24L01.c文件负责实现模块的主要功能。
①宏定义和全局变量部分:
#include "stm32f10x.h" // Device header
#include "NRF24L01_Define.h"
/* 发送部分全局变量 */
uint8_t NRF24L01_TxAddress[5] = {0x11, 0x22, 0x33, 0x44, 0x55}; //发送地址,固定5字节
#define NRF24L01_TX_PACKET_WIDTH 4 //发送数据包宽度,范围:1~32字节
uint8_t NRF24L01_TxPacket[NRF24L01_TX_PACKET_WIDTH]; //发送数据包
/* 接收部分全局变量 */
uint8_t NRF24L01_RxAddress[5] = {0x11, 0x22, 0x33, 0x44, 0x55}; //接收通道0地址,固定5字节
#define NRF24L01_RX_PACKET_WIDTH 4 //接收通道0数据包宽度,范围:1~32字节
uint8_t NRF24L01_RxPacket[NRF24L01_RX_PACKET_WIDTH]; //接收数据包
②引脚配置部分(对照电路图和状态转移图编写):
/* 引脚配置部分 */
void NRF24L01_W_CE(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_0,(BitAction)BitValue);
//置PA0引脚(连接CE)为高/低电平,高or低取决于函数参数值
}
void NRF24L01_W_CSN(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_1,(BitAction)BitValue);
//置PA1引脚(连接CSN)为高/低电平,高or低取决于函数参数值
}
void NRF24L01_W_SCK(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_2,(BitAction)BitValue);
//置PA2引脚(连接SCK)为高/低电平,高or低取决于函数参数值
}
void NRF24L01_W_MOSI(uint8_t BitValue)
{
GPIO_WriteBit(GPIOA, GPIO_Pin_3,(BitAction)BitValue);
//置PA3引脚(连接MOSI)为高/低电平,高or低取决于函数参数值
}
uint8_t NRF24L01_R_MISO(void)
{
return GPIO_ReadInputDataBit(GPIOA, GPIO_Pin_4);
//读取PA4引脚(连接MISO)的电平并返回
}
void NRF24L01_GPIO_Init(void) //初始化NRF24L01模块使用的GPIO口
{
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1 | GPIO_Pin_2 | GPIO_Pin_3;
GPIO_Init(GPIOB, &GPIO_InitStructure); //CE、CSN、SCK、MOSI引脚初始化为推挽输出模式
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
GPIO_Init(GPIOB, &GPIO_InitStructure); //MISO引脚初始化为上拉输入模式
NRF24L01_W_CE(0); //CE默认输出低电平(默认退出收发模式)
NRF24L01_W_CSN(1); //CSN默认输出高电平(不选中从机)
NRF24L01_W_SCK(0); //SCK默认输出低电平(对应SPI模式0)
NRF24L01_W_MOSI(0); //MOSI默认输出低电平(也可默认输出高电平)
}
③SPI通信实现部分(对照读/写操作时序图编写):
/* SPI通信实现部分 */
uint8_t NRF24L01_SPI_SwapByte(uint8_t Byte) //一次字节数据交换
{
//主机把要发送的Byte从最高位开始逐位移到MOSI引脚输出
//同时将从机在MISO引脚返回的数据从最高位开始逐位从Byte的低位移入
for(int i = 0; i < 8; i++)
{
//从高位移出数据
if(Byte & 0x80)
{
NRF24L01_W_MOSI(1);
}
else
{
NRF24L01_W_MOSI(0);
}
Byte <<= 1; //Byte左移,高位移出,低位补0
NRF24L01_W_SCK(1); //SCK置高电平
//移入数据至低位
if(NRF24L01_R_MISO())
{
Byte |= 0x01;
}
else
{
Byte &= ~0x01;
}
NRF24L01_W_SCK(0); //SCK置低电平
}
return Byte;
}
④指令时序实现部分(对照指令定义编写):
/* 指令时序实现部分 */
void NRF24L01_WriteReg(uint8_t RegAddress, uint8_t Data)
{
//在指定(单字节)寄存器地址RegAddress中写入Data
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_W_REGISTER | RegAddress); //发送写指令码,同时发送寄存器地址
NRF24L01_SPI_SwapByte(Data); //发送写入寄存器的数据
NRF24L01_W_CSN(1); //SPI通信结束
}
uint8_t NRF24L01_ReadReg(uint8_t RegAddress)
{
//读取指定地址RegAddress(单字节)寄存器中的数据
uint8_t Data;
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_R_REGISTER | RegAddress); //发送读指令码,同时发送寄存器地址
Data = NRF24L01_SPI_SwapByte(NRF24L01_NOP); //接收寄存器中的数据,用空指令做交换
NRF24L01_W_CSN(1); //SPI通信结束
return Data;
}
void NRF24L01_WriteRegs(uint8_t RegAddress, uint8_t* DataArray, uint8_t Count)
{
//在指定(多字节)寄存器地址RegAddress中写入Data
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_W_REGISTER | RegAddress); //发送写指令码,同时发送寄存器地址
for(int i = 0; i < Count; i++)
{
NRF24L01_SPI_SwapByte(DataArray[i]); //发送写入寄存器的1字节数据,寄存器有几个字节就发几次
}
NRF24L01_W_CSN(1); //SPI通信结束
}
void NRF24L01_ReadRegs(uint8_t RegAddress, uint8_t* DataArray, uint8_t Count)
{
//读取指定地址RegAddress(多字节)寄存器中的数据
uint8_t Data;
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_R_REGISTER | RegAddress); //发送读指令码,同时发送寄存器地址
for(int i = 0; i < Count; i++)
{
DataArray[i] = NRF24L01_SPI_SwapByte(NRF24L01_NOP); //接收寄存器中的1字节数据,寄存器有几个字节就收几次
}
NRF24L01_W_CSN(1); //SPI通信结束
}
void NRF24L01_WriteTxPayload(uint8_t* DataArray, uint8_t Count)
{
//写发送有效载荷至TX FIFO
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_W_TX_PAYLOAD); //发送写FIFO指令码
for(int i = 0; i < Count; i++)
{
NRF24L01_SPI_SwapByte(DataArray[i]); //发送写入TX FIFO的1字节数据,数据段有几个字节就发几次
}
NRF24L01_W_CSN(1); //SPI通信结束
}
void NRF24L01_ReadRxPayload(uint8_t* DataArray, uint8_t Count)
{
//从RX FIFO中读取有效载荷
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_R_RX_PAYLOAD); //发送读FIFO指令码
for(int i = 0; i < Count; i++)
{
DataArray[i] = NRF24L01_SPI_SwapByte(NRF24L01_NOP); //接收RX FIFO中的1字节数据,数据段有几个字节就收几次
}
NRF24L01_W_CSN(1); //SPI通信结束
}
void NRF24L01_FlushTx(void)
{
//清空TX FIFO
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_FLUSH_TX); //发送清空TX FIFO指令码
NRF24L01_W_CSN(1); //SPI通信结束
}
void NRF24L01_FlushRx(void)
{
//清空RX FIFO
NRF24L01_W_CSN(0); //SPI通信起始
NRF24L01_SPI_SwapByte(NRF24L01_FLUSH_RX); //发送清空RX FIFO指令码
NRF24L01_W_CSN(1); //SPI通信结束
}
uint8_t NRF24L01_ReadStatus(void)
{
//读取状态寄存器
uint8_t Status;
NRF24L01_W_CSN(0); //SPI通信起始
Status = NRF24L01_SPI_SwapByte(NRF24L01_NOP); //发送空指令,接收状态寄存器的值
NRF24L01_W_CSN(1); //SPI通信结束
return Status;
}
⑤功能实现部分(根据状态转移图和寄存器定义表编写):
[1]模式控制函数:
void NRF24L01_PowerDown(void) //控制NRF24L01进入掉电模式
{
uint8_t Config;
/* CE置为0 */
NRF24L01_W_CE(0);
/* PWR_UP置为0 */
Config = NRF24L01_ReadReg(NRF24L01_CONFIG); //获取配置寄存器的值
if (Config == 0xFF) {return;} //配置寄存器全为1,出错,退出函数
Config &= ~0x02;
NRF24L01_WriteReg(NRF24L01_CONFIG, Config); //置配置寄存器第1位为0,其它位保持原值
}
void NRF24L01_StandbyI(void) //控制NRF24L01进入待机模式1
{
uint8_t Config;
/* CE置为0 */
NRF24L01_W_CE(0);
/* PWR_UP置为1 */
Config = NRF24L01_ReadReg(NRF24L01_CONFIG); //获取配置寄存器的值
if (Config == 0xFF) {return;} //配置寄存器全为1,出错,退出函数
Config |= 0x02;
NRF24L01_WriteReg(NRF24L01_CONFIG, Config); //置配置寄存器第1位为1,其它位保持原值
}
void NRF24L01_RxMode(void) //控制NRF24L01进入接收模式
{
uint8_t Config;
/* CE置为0 */
NRF24L01_W_CE(0);
/* PWR_UP置为1,PRIM_RX置为1 */
Config = NRF24L01_ReadReg(NRF24L01_CONFIG); //获取配置寄存器的值
if (Config == 0xFF) {return;} //配置寄存器全为1,出错,退出函数
Config |= 0x03;
NRF24L01_WriteReg(NRF24L01_CONFIG, Config); //置配置寄存器第0位和第1位为1,其它位保持原值
/* CE置为1 */
NRF24L01_W_CE(1);
}
void NRF24L01_TxMode(void) //控制NRF24L01进入发送模式
{
uint8_t Config;
/* CE置为0 */
NRF24L01_W_CE(0);
/* PWR_UP置为1,PRIM_RX置为0 */
Config = NRF24L01_ReadReg(NRF24L01_CONFIG); //获取配置寄存器的值
if (Config == 0xFF) {return;} //配置寄存器全为1,出错,退出函数
Config |= 0x02;
Config &= ~0x01;
NRF24L01_WriteReg(NRF24L01_CONFIG, Config); //置配置寄存器第0位为0、第1位为1,其它位保持原值
/* CE置为1 */
NRF24L01_W_CE(1);
}
[2]模块初始化函数:
void NRF24L01_Init(void)
{
/* 模块引脚初始化 */
NRF24L01_GPIO_Init();
/* 寄存器初始化,配置初始参数 */
NRF24L01_WriteReg(NRF24L01_CONFIG, 0x08); //CONFIG寄存器初始化
NRF24L01_WriteReg(NRF24L01_EN_AA, 0x3F); //EN_AA寄存器初始化
NRF24L01_WriteReg(NRF24L01_EN_RXADDR, 0x01); //EN_RXADDR寄存器初始化,只开启接收通道0
NRF24L01_WriteReg(NRF24L01_SETUP_AW, 0x03); //SETUP_AW寄存器初始化,配置地址宽度为5
NRF24L01_WriteReg(NRF24L01_SETUP_RETR, 0x03); //SETUP_RETR寄存器初始化,配置地址宽度为5
NRF24L01_WriteReg(NRF24L01_RF_CH, 0x02); //RF_CH寄存器初始化,配置工作频率
NRF24L01_WriteReg(NRF24L01_RF_SETUP, 0x0E); //RF_SETUP寄存器初始化
NRF24L01_WriteRegs(NRF24L01_RX_ADDR_P0, NRF24L01_RxAddress, 5); //接收通道0地址配置
NRF24L01_WriteReg(NRF24L01_RX_PW_P0, NRF24L01_RX_PACKET_WIDTH); //NRF24L01_RX_PW_P0寄存器初始化,配置接收通道0接收数据包宽度
/* 默认进入接收模式 */
NRF24L01_RxMode();
}
[3]发送函数:
uint8_t NRF24L01_Send(void)
{
uint8_t Status;
uint8_t SendFlag;
uint32_t Timeout;
/* 写入发送通道地址 */
NRF24L01_WriteRegs(NRF24L01_TX_ADDR, NRF24L01_TxAddress, 5);
/* 写入发送数据 */
NRF24L01_WriteTxPayload(NRF24L01_TxPacket, NRF24L01_TX_PACKET_WIDTH);
/* 写入接收通道0的地址(开启自动应答后,需与发送通道地址一致,以接收应答包) */
NRF24L01_WriteRegs(NRF24L01_RX_ADDR_P0, NRF24L01_TxAddress, 5);
/* 进入发送模式 */
NRF24L01_TxMode();
/* 指定超时时间,即循环读取状态寄存器的次数 */
Timeout = 10000;
while(1)
{
/* 读取状态寄存器 */
Status = NRF24L01_ReadStatus();
Timeout --; //超时计次
if(Timeout == 0)
{
SendFlag = 4; //认为发送超时,置标志位为4
NRF24L01_Init(); //重新初始化设备
break; //跳出循环
}
/* 根据状态寄存器的值,判断发送状态 */
if((Status & 0x30) == 0x30) //MAX_RT和TX_DS同时为1
{
SendFlag = 3; //认为状态寄存器的值不合法,置标志位为3
NRF24L01_Init(); //重新初始化设备
break; //跳出循环
}
else if((Status & 0x10) == 0x10) //MAX_RT为1
{
SendFlag = 2; //达到了最大重发次数仍未收到应答,置标志位为2
NRF24L01_Init(); //重新初始化设备
break; //跳出循环
}
else if((Status & 0x20) == 0x20) //TX_DS为1
{
SendFlag = 1; //发送成功,无错误,置标志位为1
break; //跳出循环
}
}
/* 给状态寄存器的位4(MAX_RT)和位5(TX_DS)写1,清标志位 */
NRF24L01_WriteReg(NRF24L01_STATUS, 0x30);
/* 清空Tx FIFO的所有数据 */
NRF24L01_FlushTx();
/* 发送完成后,恢复接收通道0原来的地址 */
//如果发送地址和接收通道0地址设置相同,则可不执行这一句
NRF24L01_WriteRegs(NRF24L01_RX_ADDR_P0, NRF24L01_RxAddress, 5);
/* 发送完成,芯片恢复为接收模式 */
NRF24L01_RxMode();
/* 返回发送标志位 */
return SendFlag;
}
[4]接收函数:
uint8_t NRF24L01_Receive(void)
{
uint8_t Status, Config;
uint8_t ReceiveFlag;
/* 读取状态寄存器 */
Status = NRF24L01_ReadStatus();
/* 读取配置寄存器 */
Config = NRF24L01_ReadReg(NRF24L01_CONFIG);
/* 根据配置寄存器和状态寄存器的值,判断接收状态 */
if ((Config & 0x02) == 0x00) //PWR_UP为0
{
ReceiveFlag = 3; //设备仍处于掉电模式,置标志位为3
NRF24L01_Init(); //重新初始化设备
}
else if ((Status & 0x30) == 0x30) //MAX_RT和TX_DS同时为1
{
ReceiveFlag = 2; //认为状态寄存器的值不合法,置标志位为2
NRF24L01_Init(); //重新初始化设备
}
else if ((Status & 0x40) == 0x40) //RX_DR为1
{
ReceiveFlag = 1; //接收到数据,置标志位为1
/* 读接收有效载荷 */
NRF24L01_ReadRxPayload(NRF24L01_RxPacket, NRF24L01_RX_PACKET_WIDTH);
/* 给状态寄存器的位6(RX_DR)写1,清标志位 */
NRF24L01_WriteReg(NRF24L01_STATUS, 0x40);
/* 清空Rx FIFO的所有数据 */
NRF24L01_FlushRx();
}
else
{
ReceiveFlag = 0; //未接收到数据,置标志位为0
}
/* 返回接收标志位 */
return ReceiveFlag;
}
(2)NRF24L01.h文件负责声明模块新增的函数和外部可能需要调用的全局变量。
#ifndef __NRF24L01_H
#define __NRF24L01_H
#include "NRF24L01_Define.h"
extern uint8_t NRF24L01_TxAddress[];
extern uint8_t NRF24L01_TxPacket[];
extern uint8_t NRF24L01_RxAddress[];
extern uint8_t NRF24L01_RxPacket[];
uint8_t NRF24L01_ReadReg(uint8_t RegAddress);
void NRF24L01_ReadRegs(uint8_t RegAddress, uint8_t *DataArray, uint8_t Count);
void NRF24L01_WriteReg(uint8_t RegAddress, uint8_t Data);
void NRF24L01_WriteRegs(uint8_t RegAddress, uint8_t *DataArray, uint8_t Count);
void NRF24L01_ReadRxPayload(uint8_t *DataArray, uint8_t Count);
void NRF24L01_WriteTxPayload(uint8_t *DataArray, uint8_t Count);
void NRF24L01_FlushTx(void);
void NRF24L01_FlushRx(void);
uint8_t NRF24L01_ReadStatus(void);
void NRF24L01_PowerDown(void);
void NRF24L01_StandbyI(void);
void NRF24L01_RxMode(void);
void NRF24L01_TxMode(void);
void NRF24L01_Init(void);
uint8_t NRF24L01_Send(void);
uint8_t NRF24L01_Receive(void);
#endif
(3)NRF24L01_Define.h文件负责将指令码和寄存器地址用宏替换。
#ifndef __NRF24L01_DEFINE_H
#define __NRF24L01_DEFINE_H
/*NRF24L01指令宏定义*/
#define NRF24L01_R_REGISTER 0x00 //读寄存器,高3位为指令码,低5位为寄存器地址,后续跟1~5字节读数据
#define NRF24L01_W_REGISTER 0x20 //写寄存器,高3位为指令码,低5位为寄存器地址,后续跟1~5字节写数据
#define NRF24L01_R_RX_PAYLOAD 0x61 //读Rx有效载荷,后续跟1~32字节读数据
#define NRF24L01_W_TX_PAYLOAD 0xA0 //写Tx有效载荷,后续跟1~32字节写数据
#define NRF24L01_FLUSH_TX 0xE1 //清空Tx FIFO所有数据,单独指令
#define NRF24L01_FLUSH_RX 0xE2 //清空Rx FIFO所有数据,单独指令
#define NRF24L01_REUSE_TX_PL 0xE3 //重新使用最后一次发送的有效载荷,单独指令
#define NRF24L01_R_RX_PL_WID 0x60 //读取Rx FIFO最前面一个数据包的宽度,后续跟1字节读数据,仅适用于动态包长模式
#define NRF24L01_W_ACK_PAYLOAD 0xA8 //写应答附带的有效载荷,高5位为指令码,低3位为通道号,后续跟1~32字节写数据,仅适用于应答附带载荷模式
#define NRF24L01_W_TX_PAYLOAD_NOACK 0xB0 //写Tx有效载荷,不要求应答,后续跟1~32字节写数据,仅适用于不要求应答模式
#define NRF24L01_NOP 0xFF //空操作,单独指令,可以用读取状态寄存器
/*NRF24L01寄存器地址宏定义*/
#define NRF24L01_CONFIG 0x00 //配置寄存器,1字节
#define NRF24L01_EN_AA 0x01 //使能自动应答,1字节
#define NRF24L01_EN_RXADDR 0x02 //使能接收通道,1字节
#define NRF24L01_SETUP_AW 0x03 //设置地址宽度,1字节
#define NRF24L01_SETUP_RETR 0x04 //设置自动重传,1字节
#define NRF24L01_RF_CH 0x05 //射频通道,1字节
#define NRF24L01_RF_SETUP 0x06 //射频相关参数设置,1字节
#define NRF24L01_STATUS 0x07 //状态寄存器,1字节
#define NRF24L01_OBSERVE_TX 0x08 //发送观察寄存器,1字节
#define NRF24L01_RPD 0x09 //接收功率检测,1字节
#define NRF24L01_RX_ADDR_P0 0x0A //接收通道0地址,5字节
#define NRF24L01_RX_ADDR_P1 0x0B //接收通道1地址,5字节
#define NRF24L01_RX_ADDR_P2 0x0C //接收通道2地址,1字节,高位地址与接收通道1相同
#define NRF24L01_RX_ADDR_P3 0x0D //接收通道3地址,1字节,高位地址与接收通道1相同
#define NRF24L01_RX_ADDR_P4 0x0E //接收通道4地址,1字节,高位地址与接收通道1相同
#define NRF24L01_RX_ADDR_P5 0x0F //接收通道5地址,1字节,高位地址与接收通道1相同
#define NRF24L01_TX_ADDR 0x10 //发送地址,5字节
#define NRF24L01_RX_PW_P0 0x11 //接收通道0有效载荷数据宽度,1字节
#define NRF24L01_RX_PW_P1 0x12 //接收通道1有效载荷的数据宽度,1字节
#define NRF24L01_RX_PW_P2 0x13 //接收通道2有效载荷的数据宽度,1字节
#define NRF24L01_RX_PW_P3 0x14 //接收通道3有效载荷的数据宽度,1字节
#define NRF24L01_RX_PW_P4 0x15 //接收通道4有效载荷的数据宽度,1字节
#define NRF24L01_RX_PW_P5 0x16 //接收通道5有效载荷的数据宽度,1字节
#define NRF24L01_FIFO_STATUS 0x17 //发送和接收FIFO状态,1字节
#define NRF24L01_DYNPD 0x1C //使能接收通道的动态包长模式,1字节
#define NRF24L01_FEATURE 0x1D //使能高级功能,1字节
#endif
3、main.c文件编写与程序调试
(1)包含相关模块头文件,并定义全局变量。
#include "stm32f10x.h" // Device header
#include "Key.h"
#include "OLED.h"
#include "NRF24L01.h"
#include "Delay.h"
uint8_t KeyNum;
uint8_t SendFlag; //发送标志位
uint8_t SendSuccessCount, SendFailedCount; //发送成功计次,发送失败计次
uint8_t ReceiveFlag; //接收标志位
uint8_t ReceiveSuccessCount, ReceiveFailedCount; //接收成功计次,接收失败计次
(2)主函数中初始化各模块后,编写测试代码。
int main(void)
{
/* 模块初始化 */
OLED_Init(); //OLED初始化
Key_Init(); //按键初始化
NRF24L01_Init(); //NRF24L01初始化
/* OLED显示静态字符串 */
OLED_ShowString(1, 1, "T:000-000-0"); //格式为:T:发送成功计次-发送失败计次-发送标志位
OLED_ShowString(3, 1, "R:000-000-0"); //格式为:R:接收成功计次-接收失败计次-接收标志位
/* 初始化发送数据 */
NRF24L01_TxPacket[0] = 0x00;
NRF24L01_TxPacket[1] = 0x01;
NRF24L01_TxPacket[2] = 0x02;
NRF24L01_TxPacket[3] = 0x03;
while (1)
{
KeyNum = Key_GetNum(); //读取按键,获取键码
if (KeyNum == 1) //按键按下
{
/* 变换测试数据,便于观察实验现象 */
NRF24L01_TxPacket[0]++;
NRF24L01_TxPacket[1]++;
NRF24L01_TxPacket[2]++;
NRF24L01_TxPacket[3]++;
/* 调用NRF24L01_Send函数,发送数据,同时返回发送标志位 */
SendFlag = NRF24L01_Send();
if (SendFlag == 1){ //发送标志位为1,表示发送成功
SendSuccessCount++; //发送成功计次变量自增
}
else{ //发送标志位不为1,即2/3/4,表示发送不成功
SendFailedCount++; //发送失败计次变量自增
}
OLED_ShowNum(1, 3, SendSuccessCount, 3); //显示发送成功次数
OLED_ShowNum(1, 7, SendFailedCount, 3); //显示发送失败次数
OLED_ShowNum(1, 11, SendFlag, 1); //显示最近一次的发送标志位
/* OLED显示发送数据 */
OLED_ShowHexNum(2, 1, NRF24L01_TxPacket[0], 2);
OLED_ShowHexNum(2, 4, NRF24L01_TxPacket[1], 2);
OLED_ShowHexNum(2, 7, NRF24L01_TxPacket[2], 2);
OLED_ShowHexNum(2, 10, NRF24L01_TxPacket[3], 2);
/* TX字符串闪烁一次,表明发送了一次数据 */
OLED_ShowString(1, 15, "TX");
Delay_ms(100);
OLED_ShowString(1, 15, " ");
}
/* 调用NRF24L01_Receive函数,接收数据,同时返回接收标志位 */
ReceiveFlag = NRF24L01_Receive();
if (ReceiveFlag) //接收标志位不为0,表示收到了一个数据包
{
if (ReceiveFlag == 1){ //接收标志位为1,表示接收成功
ReceiveSuccessCount++; //接收成功计次变量自增
}
else{ //接收标志位不为0也不为1,即2/3,表示此次接收产生了错误
ReceiveFailedCount++; //接收失败计次变量自增
}
OLED_ShowNum(3, 3, ReceiveSuccessCount, 3); //显示接收成功次数
OLED_ShowNum(3, 7, ReceiveFailedCount, 3); //显示接收失败次数
OLED_ShowNum(3, 11, ReceiveFlag, 1); //显示最近一次的接收标志位
/* OLED显示接收数据 */
OLED_ShowHexNum(4, 1, NRF24L01_RxPacket[0], 2);
OLED_ShowHexNum(4, 4, NRF24L01_RxPacket[1], 2);
OLED_ShowHexNum(4, 7, NRF24L01_RxPacket[2], 2);
OLED_ShowHexNum(4, 10, NRF24L01_RxPacket[3], 2);
/* RX字符串闪烁一次,表明接收到了一次数据 */
OLED_ShowString(3, 15, "RX");
Delay_ms(100);
OLED_ShowString(3, 15, " ");
}
}
}
(3)将程序编译,然后分别下载到两个单片机中,按照注释进行调试,测试双方能否互相正常收发数据。
更多推荐



所有评论(0)