NRF24L01 调试笔记

前几天想要做一个开源航模接收机,于是去淘宝看了下无线模块,以前有了解过NRF24L01,于是就下单了三片,一片才8块钱,真的划算。

初体验

到手后才知道这玩意是SPI协议,以前也只是听说过,没有实际写过有关SPI的东西,都是串口用得多,我寻思着太莽了不合适,正好卖家有一个这玩意的调试器(说是调试器,其实就是用AT命令来控制),于是又下单了两个。

到手后插上,灯闪烁,说明检测到模块了,于是打开串口工具,乱码。与卖家PY一番后得知这玩意传输的是中文字符串,还是GBK编码,真是裂开,以前知道硬件工程师一般比较落后,没想到这么落后。

基本配置

通过那个调试器我了解了这模块的一些基本操作。首先模块需要配置发送地址和接收地址(5字节),大概类似于配对,比如A模块的发送地址是0x11 0x22 0x33 0x44 0x55,接收地址是0x11 0x22 0x33 0x44 0x56,那么如果有一个B模块想要与A模块通信,就需要把B模块的发送地址配置为0x11 0x22 0x33 0x44 0x56,接收地址配置为0x11 0x22 0x33 0x44 0x55,还要设置同样的CRC校验以及同样的频段就可以相互通信了。

之前看的教程提到地址都不明确说,搞得我一头雾水。

SPI

有关SPI的介绍网上很多,这里不再赘述。但是要提一点,这玩意虽然硬件上是全双工,但是它和UART不一样,它并不是对等协议,也就是说从机不能主动发起通信,主机在拉低从机的片选信号后会控制SCLK信号,这时候从机才能发送给主机,也就是说这玩意在软件层面上还是属于半双工。

我使用的单片机是STM32,之前被坑了好久,与串口类似,SPI也有一系列HAL_SPI函数,比如HAL_SPI_TransmitHAL_SPI_Receive,以及对应的_IT_DMA,但是我们前面提到了SPI与串口的不同之处,也就是说,SPI的发送和接收是同时进行的,而且发送的长度和接收的长度相等,我在写串口的时候习惯于一直调用HAL_UART_Receive_IT来保证串口数据不会被丢失,因为串口可以抽象成一般的字节流,如果没有数据当然不会触发中断,但是SPI它不一样就在这里了,一旦调用HAL_SPI_Receive_IT就一定会触发中断,这时候得到的数据由MISO引脚的电平决定,也就是说无论有没有接设备,都一定能收到“数据”,这导致我之前调试的时候根本没接设备还能不停收到0或者255这一情况是懵逼的,后来才专门补了SPI的相关知识。

NRF24L01

Datasheet是很重要的资料,如果你还没有,可以从链接里下载。

命令

总的来说,NRF24L01使用SPI发送命令来控制,具体可以看Datasheet的8.3.1节。

每一组命令由片选信号下降沿开始,上升沿结束。具我自己的测试,STM32的硬件SPI片选好像不太行,得手动操作GPIO,每次发送命令前拉低片选引脚,发送和接收完毕后再拉高。

总共有11个命令,长度都是固定的1字节。操作寄存器可以用R_REGISTER和W_REGISTER,携带1~5字节的数据,前面提到过,SPI的发送和接收同时进行同时结束,拿读取寄存器来说,如果要读取5个字节的话,就得发送6个字节(有一个字节是命令本身),除了命令那个字节以为后面跟的是啥并不重要,可以设置为0xFF,同时也会收到6个字节,第一个字节固定是状态寄存器的值,后面5个字节才是实际读取的字节。写入寄存器的操作同理,比如对一个寄存器写入5个字节,就会发送6个字节,同时收到6个字节,但是收到的后面5个字节都没有意义(经过测试基本都是0)。

模式

主要有四种模式,可以通过设置寄存器和控制引脚来切换模式,具体可以看Datasheet 6.1.1的状态图。

掉电模式(Power Down Mode)

注意这并不是没有电的情况,这是模块上电后的默认模式,此时CONFIG寄存器的PWR_UP = 0,可以读取和设置寄存器,正如其名,功耗很低。

待机模式(Standby Modes)

待机模式有两个

待机模式1(Standby-I Mode)

在掉电模式下设置PWR_UP = 1即可进入待机模式1,这是我们使用模块收发的时候主要使用的模式。

待机模式2(Standby-II Mode)

这个模式需要从待机模式1进入。这个有点待发送那味,当发送缓冲区为空且PRIM_RX = 0,CE拉高的时候会进入这个模式,在这个模式下如果有数据填入发送缓冲区则会进入到发送模式,发送完毕后发送缓冲区清空,如果没有拉低CE则又会回到这个模式,否则回到待机模式1。

也就是说在这个模式下只要填入发送缓冲区,数据就会被发送出去。

接收模式(RX Mode)

用于接收数据。

在待机模式1下,CE拉高,PRIM_RX = 1即可进入这个模式。

一般来说这个模式下只能接收数据,但是Datasheet中提到

However, if the automatic protocol features (Enhanced ShockBurstTM) in the baseband protocol engine are enabled, the nRF24L01+ can enter other modes in order to execute the protocol.

说实话不太理解它的意思,如果有大佬能翻译一下实在感激不尽。

发送模式(TX Mode)

用于发送数据。

在待机模式1下,CE拉高,PRIM_RX = 0,发送缓冲区存在数据时即可进入这个模式。

编程指北

检测模块是否存在

一般的操作是写入5个字节的指定到TX_ADDR寄存器,然后再读取出来看是否一致。没什么难的,干就完事了。

初始化

首先要拉低CE,然后主要就是设置寄存器

CONFIG - 0x00

设置IRQ引脚行为,建议启用所有中断源,启用CRC校验,1字节,PWR_UP = 1,也就是0x0A

EN_AA - 0x01

用于配置Enhanced ShockBurst,需要哪个通道就开哪个,默认全开,但是如果只是一对一通信的话只要开两个就好了,比如只开通道0和1,设置为0x03即可。

EN_RXADDR - 0x02

启用接收的通道,默认只开了通道1和通道0,即0x03

SETUP_AW - 0x03

设置地址长度,默认是5字节(0x03)。

SETUP_RETR - 0x04

设置自动重发次数(3次)和重发延迟时间(500微秒)(0x13)。

Datasheet的注释中提到如果要使用250kbps速率,则需要把重发延迟时间设置为500微秒或更高,具体可以看7.4.2节。

RF_CH - 0x05

射频的频段,默认是0x02,也就是2.402GHZ。

RF_SETUP - 0x06

射频的速率和功率,具体去看Datasheet吧,我这里使用250kbps速率,0dbm功率,0x26

RX_ADDR_P0 - 0x0A

接收通道0的接收地址,最长5字节,如果要使用Enhanced ShockBurst就得设置成和发送地址一样。

RX_ADDR_P1 - 0x0B

接收通道1的地址在开启Enhanced ShockBurst的情况下一般用这个通道来接收数据。

TX_ADDR - 0x10

发送地址,同样是自己设置。

DYNPD - 0x1C

接收通道动态载荷长度(Dynamic Payload Length),我们开启通道1和通道0,0x03

FEATURE - 0x1D

三个设置项

  • EN_DPL,启用动态载荷长度(Dynamic Payload Length),Datasheet中这里写错了,bit应该是1,而不是2。
  • EN_ACK_PAY,启用Payload with ACK。
  • EN_DYN_ACK,启用W_TX_PAYLOAD_NOACK命令。

默认是0x00,我们开启EN_DPL和EN_ACK_PAY,0x06

发送和接收

Enhanced ShockBurst

最好先了解Enhanced ShockBurst是个什么玩意,怎么操作。后面的内容以Enhanced ShockBurst为基本,因为这个很好用。

大多数的无线通信在硬件上是半双工,当然我们可以通过软件方法抽象成全双工。Enhanced ShockBurst是ShockBurst的增强版,它自动帮我们完成应答(ACK)过程,同时接收端还可以设置应答的载荷(通过命令W_ACK_PAYLOAD来写入,发送方收到应答之后从通道0读取载荷)。要使用Enhanced ShockBurst,需要接收和发送两方都进行设置,

  • 发送方配置ARC>0(默认为3),同时将通道0的接收地址设置为与发送地址相同,通过W_TX_PAYLOAD命令写入待发送数据,而不是W_TX_PAYLOAD_NOACK。
  • 接收方配置EN_AA。
动态长度(Dynamic Payload Length)

一般单词发送的包可以携带32字节的载荷,但有时候我们并不需要这么长的载荷,所以可以通过这个功能来减少损耗。

需要发送和接收双方都要开启EN_DPL,接收方还要配置DYNPD寄存器开启各个通道的动态长度。

接收方可以用命令R_RX_PL_WID来读取FIFO中数据的实际长度。