前言:本文为手把手教学NRF24L01 2.4G通讯模块的驱动实验,本教程的 MCU 采用STM32F103ZET6与STM32F103C8T6,彼此进行互相通讯。通过 CubeMX 软件配置 SPI 协议驱动NRF24L01 2.4G通讯模块(HAL库)。NRF24L01 2.4G是嵌入式较为常见的模块,希望这篇博文能给读者朋友的工程项目给予些许帮助。(文末代码开源!)
硬件设备:STM32F103ZET6;STM32F103C8T6;NRF24L01 2.4G(2个);DHT11;OLED
硬件实物图:
效果图:
引脚连接:
NRF24L01 2.4G引脚(ZET6):
CSN --> PB3
CE --> PB4
IRQ --> PB5
SCK --> PA5
MISO --> PA6
MOSI --> PA7
Vcc --> 3.3v
GND --> GND
DHT11引脚(ZET6):
PA1 --> DATA
Vcc --> 3.3v
GND --> GND
0.96寸OLED引脚(C8T6):
SDA --> PB7
SCL --> PB6
Vcc --> 3.3v
GND --> GND
一、NRF24L01 2.4G模块简介
NRF24L01 是北欧芯片巨头 Nordic 公司生产的一款无线通信芯片,可以工作在免费开放的 2.4GHz 频段,通信速率可以达到最高 2Mbps。采用 FSK 调制,内部集成 Nordic 自己的 Enhanced Short Burst 协议,可以实现 点对点 或者 1对6 的无线通信。
NRF24L01 2.4G 通讯模块采用 SPI 通信,引脚如下:
1、CSN:芯片的片选线,低电平芯片工作;
2、SCK:芯片控制的时钟线(SPI的时钟);
3、MISO:芯片控制数据线(SPI的MISO);
4、MOSI:芯片控制数据线(SPI的MOSI);
5、IRQ:中断信号,NRF24L01芯片收到数据、或者发送完数据等等一些情况会产生下降沿中断;
6、CE:芯片的模式控制线,决定了芯片的工作状态。
NRF24L01 的引脚情况如下图所示,上图仅供参考,实际情况以手中模块为准。
NRF24L01 的涉及领域:
无线鼠标、键盘、游戏机操纵杆;无线门禁;无线数据通讯;无线数据通讯;遥控装置;遥感勘测;智能运动设备;工业传感器;玩具。
二、模块详解
2.1 工作模式
NRF24L01 共有 6 种工作模式,工作模式由 PWR_UP 寄存器、PRIM_RX 寄存器 和 CE 决定,详见下表:
收发模式:
其中,收发模式又有: Enhanced ShockBurstTM 收发模式和 ShockBurstTM 收发模式,只有 Enhanced ShockBurstTM 收发模式支持自动 ACK 和自动重发。开启自动 ACK,则默认选择 Enhanced ShockBurstTM 模式
待机模式:
待机模式 I 在保证快速启动的同时减少系统平均消耗电流。在待机模式 I 下,晶振正常工作。在待机模式 II 下部分时钟缓冲器处在工作模式。当发送端 TX FIFO 寄存器为空并且 CE 为高电平时进入待机模式 II。在待机模式期间,寄存器配置字内容保持不变。
掉电模式:
在掉电模式下,nRF20L01 各功能关闭,保持电流消耗最小。进入掉电模式后,nRF24L01 停止工作,但寄存器内容保持不变。掉电模式由寄存器 PWR_UP 位来控制。
★在 Enhanced ShockBurstTM 收发模式下:
1、NRF24L01 自动处理字头和 CRC 校验码。在接收数据时,自动把字头和 CRC 校验码移去。在发送数据时,自动加上字头和 CRC 校验码,在发送模式下,置 CE 为高,至少 10us,将使能发送过程。
2、在接收模式下:最多可以接收 6 路不通的数据。每一个数据通道使用不同的地址,但是共用相同的频道。也就是说 6 个不同的 NRF24L01 置为发送模式后可以与同一个设置为接收模式的 NRF24L01 进行通讯,而设置为接收模式的 NRF24L01 可以对这 6 个发射端进行识别。
2.2 NRF24L01固件编程
NRF24L01 2.4G 的固件编程的基本思路如下:- 置 CSN 为低,使能芯片,配置芯片各个参数(关键RX和TX配置),配置参数在PowerDown状态中完成。
- 如果是Tx模式,装载接收端地址,填充TxFIFO。
- 配置完成以后,通过CONFIG中的PWR_UP、PRIM_RX与CE参数确定24L01要切换到的状态。
- Tx Mode:PWR_UP=1;PRIM_RX=0;CE=1 (保持超过10us就可以);
- Rx Mode:PWR_UP=1;PRIM_RX=1;CE=1;
- 将IRQ 接到外部中断输入引脚,通过中断程序进行处理。IRQ引脚会在以下三种情况变低:
- Tx FIFO发完并且收到ACK(使能 ACK 情况下)
- Rx FIFO收到数据
- 达到最大重发次数
★Tx 与Rx 的配置过程:
本节只是叙述了采用 Enhanced ShockBurstTM 通信方式的Tx 与Rx 的配置及通信过程,熟悉了NRF24L01 以后可以采用别的通信方式。
2.2.1 Tx 模式初始化过程
(1)写Tx 节点的地址 TX_ADDR
(2)写Rx 节点的地址(主要是为了使能Auto Ack) RX_ADDR_P0
(3)使能AUTO ACK EN_AA
(4)使能PIPE 0 EN_RXADDR
(5)配置自动重发次数 SETUP_RETR
(6)选择通信频率 RF_CH
(7)配置发射参数(低噪放大器增益、发射功率、无线速率) RF_SETUP
(8 ) 选择通道0 有效数据宽度 Rx_Pw_P0
(9)配置24L01 的基本参数以及切换工作模式 CONFIG。
2.2.2 Rx 模式初始化过程
(1)写Rx 节点的地址 RX_ADDR_P0
(2)使能AUTO ACK EN_AA
(3)使能PIPE 0 EN_RXADDR
(4)选择通信频率 RF_CH
(5) 选择通道0 有效数据宽度 Rx_Pw_P0
(6)配置发射参数(低噪放大器增益、发射功率、无线速率) RF_SETUP
(7)配置24L01 的基本参数以及切换工作模式 CONFIG。
三、项目详解
3.1 项目概述
实验目标:实时进行 STM32F103ZET6 驱动 DHT11 进行环境测温,然后借助 NRF24L01 2.4G 模块将温度信息传输给 STM32F103C8T6,并在0.96寸OLED上进行显示。具体工程图如下:
3.2 项目模块
本项目除了 NRF24L01 2.4G 模块外,其余模块均为过往博客项目使用的模块,这里仅给读者朋友提供参考博客。
博客地址:http://t.csdn.cn/suwlV
四、CubeMX配置
★CubeMX 项目工程给出 NRF24L01 2.4G 的配置过程,2个板套件的 NRF24L01 2.4G 驱动方式一致(以STM32F103ZET6的CubeMX配置为例)。
1、RCC配置外部高速晶振(精度更高)——HSE;
2、SYS配置:Debug设置成Serial Wire(否则可能导致芯片自锁);
3、TIM2配置:由上面可知DHT11的使用需要us级的延迟函数,HAL库自带只有ms的,所以需要自己设计一个定时器;
4、SPI1配置:NRF24L01 2.4G模块采用SPI通讯;Mode设置:Full Duplex Master;Baud Rate:不可超10MBits/s速度;
5、UART1配置:通过串口1将DHT11测得温度实时打印,与NRF24L01通讯后的数据进行验证;
6、GPIO配置:PB3和PB4设置为output与PB5设置为input;PA1设置为output,即DHT11数据输出引脚;
7、时钟树配置
8、工程配置
五、代码与解析
5.1 STM32F103ZET6程序
STM32F103ZET6 通过 DHT11 模块测量温湿度,之后将测得的温度通过 NRF24L01 2.4G 发送给同 “地址” 并处于 “接受” 状态的 NRF24L01 2.4G 模块,之后再将接收到的温湿度数据显示在 OLED 上。
特别强调:
根据无线通讯原理,NRF24L01 发送数据给对端模块的时候要发射电磁波,这本质上是一个像四周空间 广播 的过程。只要是有效距离范围内的任意一个处于接收状态的 NRF24L01 模块,都能收到这个信号。辨别数据的前提就是 “地址”。
总结:一定要保证接收端与发送端的地址一致并处于各自应该处于的模式下。
5.1.1 NRF24L01代码
根据上述解析可以得知,需要将程序中的 TX_ADDRESS[TX_ADR_WIDTH] 和 RX_ADDRESS[RX_ADR_WIDTH] 变量保持一致。同时根据 NRF24L01 2.4G 模块的 Tx 和 Rx 的配置要求,编写寄存器的配置代码。代码框架为HAL库的API接口函数编写,具体参考如下:
nrf24l01.h:
#ifndef __nrf24L01_H #define __nrf24L01_H #include "stdint.h" /* 宏定义 --------------------------------------------------------------------*/ #define NRF24L01_SPI_CS_ENABLE() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3,GPIO_PIN_RESET) //PB3 #define NRF24L01_SPI_CS_DISABLE() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_3,GPIO_PIN_SET) #define NRF24L01_CE_LOW() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_RESET) //PB4 #define NRF24L01_CE_HIGH() HAL_GPIO_WritePin(GPIOB,GPIO_PIN_4,GPIO_PIN_SET) #define NRF24L01_IRQ_PIN_READ() HAL_GPIO_ReadPin(GPIOB,GPIO_PIN_5) //PB5 // NRF24L01发送接收数据宽度定义 #define TX_ADR_WIDTH 5 //5字节的地址宽度 #define RX_ADR_WIDTH 5 //5字节的地址宽度 #define TX_PLOAD_WIDTH 32 //32字节的用户数据宽度 #define RX_PLOAD_WIDTH 32 //32字节的用户数据宽度 //NRF24L01寄存器操作命令 #define NRF_READ_REG 0x00 //读配置寄存器,低5位为寄存器地址 #define NRF_WRITE_REG 0x20 //写配置寄存器,低5位为寄存器地址 #define RD_RX_PLOAD 0x61 //读RX有效数据,1~32字节 #define WR_TX_PLOAD 0xA0 //写TX有效数据,1~32字节 #define FLUSH_TX 0xE1 //清除TX FIFO寄存器.发射模式下用 #define FLUSH_RX 0xE2 //清除RX FIFO寄存器.接收模式下用 #define REUSE_TX_PL 0xE3 //重新使用上一包数据,CE为高,数据包被不断发送. #define NOP 0xFF //空操作,可以用来读状态寄存器 //SPI(NRF24L01)寄存器地址 #define CONFIG 0x00 //配置寄存器地址;bit0:1接收模式,0发射模式;bit1:电选择;bit2:CRC模式;bit3:CRC使能; //bit4:中断MAX_RT(达到最大重发次数中断)使能;bit5:中断TX_DS使能;bit6:中断RX_DR使能 #define EN_AA 0x01 //使能自动应答功能 bit0~5,对应通道0~5 #define EN_RXADDR 0x02 //接收地址允许,bit0~5,对应通道0~5 #define SETUP_AW 0x03 //设置地址宽度(所有数据通道):bit1,0:00,3字节;01,4字节;02,5字节; #define SETUP_RETR 0x04 //建立自动重发;bit3:0,自动重发计数器;bit7:4,自动重发延时 250*x+86us #define RF_CH 0x05 //RF通道,bit6:0,工作通道频率; #define RF_SETUP 0x06 //RF寄存器;bit3:传输速率(0:1Mbps,1:2Mbps);bit2:1,发射功率;bit0:低噪声放大器增益 #define STATUS 0x07 //状态寄存器;bit0:TX FIFO满标志;bit3:1,接收数据通道号(最大:6);bit4,达到最多次重发 //bit5:数据发送完成中断;bit6:接收数据中断; #define MAX_TX 0x10 //达到最大发送次数中断 #define TX_OK 0x20 //TX发送完成中断 #define RX_OK 0x40 //接收到数据中断 #define OBSERVE_TX 0x08 //发送检测寄存器,bit7:4,数据包丢失计数器;bit3:0,重发计数器 #define CD 0x09 //载波检测寄存器,bit0,载波检测; #define RX_ADDR_P0 0x0A //数据通道0接收地址,最大长度5个字节,低字节在前 #define RX_ADDR_P1 0x0B //数据通道1接收地址,最大长度5个字节,低字节在前 #define RX_ADDR_P2 0x0C //数据通道2接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; #define RX_ADDR_P3 0x0D //数据通道3接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; #define RX_ADDR_P4 0x0E //数据通道4接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; #define RX_ADDR_P5 0x0F //数据通道5接收地址,最低字节可设置,高字节,必须同RX_ADDR_P1[39:8]相等; #define TX_ADDR 0x10 //发送地址(低字节在前),ShockBurstTM模式下,RX_ADDR_P0与此地址相等 #define RX_PW_P0 0x11 //接收数据通道0有效数据宽度(1~32字节),设置为0则非法 #define RX_PW_P1 0x12 //接收数据通道1有效数据宽度(1~32字节),设置为0则非法 #define RX_PW_P2 0x13 //接收数据通道2有效数据宽度(1~32字节),设置为0则非法 #define RX_PW_P3 0x14 //接收数据通道3有效数据宽度(1~32字节),设置为0则非法 #define RX_PW_P4 0x15 //接收数据通道4有效数据宽度(1~32字节),设置为0则非法 #define RX_PW_P5 0x16 //接收数据通道5有效数据宽度(1~32字节),设置为0则非法 #define NRF_FIFO_STATUS 0x17 //FIFO状态寄存器;bit0,RX FIFO寄存器空标志;bit1,RX FIFO满标志;bit2,3,保留 //bit4,TX FIFO空标志;bit5,TX FIFO满标志;bit6,1,循环发送上一数据包.0,不循环; /* 函数声明 ------------------------------------------------------------------*/ void NRF24L01_RX_Mode(void); //配置为接收模式 void NRF24L01_TX_Mode(void); //配置为发送模式 uint8_t NRF24L01_Write_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uint8_ts);//写数据区 uint8_t NRF24L01_Read_Buf(uint8_t reg, uint8_t *pBuf, uint8_t uint8_ts); //读数据区 uint8_t NRF24L01_Read_Reg(uint8_t reg); //读寄存器 uint8_t NRF24L01_Write_Reg(uint8_t reg, uint8_t value); //写寄存器 uint8_t NRF24L01_Check(void); //检查24L01是否存在 uint8_t NRF24L01_TxPacket(uint8_t *txbuf); //发送一个包的数据 uint8_t NRF24L01_RxPacket(uint8_t *rxbuf); //接收一个包的数据 void NRF_LowPower_Mode(void); #endif
nrf24l01.c:
#include "nrf24L01.h" #include "spi.h" const uint8_t TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址 const uint8_t RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //接收地址 /** * 函数功能: 往串行Flash读取写入一个字节数据并接收一个字节数据 * 输入参数: byte:待发送数据 * 返 回 值: uint8_t:接收到的数据 * 说 明:无 */ uint8_t SPIx_ReadWriteByte(SPI_HandleTypeDef* hspi,uint8_t byte) { uint8_t d_read,d_send=byte; if(HAL_SPI_TransmitReceive(hspi,&d_send,&d_read,1,0xFF)!=HAL_OK) { d_read=0xFF; } return d_read; } /** * 函数功能: 检测24L01是否存在 * 输入参数: 无 * 返 回 值: 0,成功;1,失败 * 说 明:无 */ uint8_t NRF24L01_Check(void) { uint8_t buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5}; uint8_t i; NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);//写入5个字节的地址. NRF24L01_Read_Buf(TX_ADDR,buf,5); //读出写入的地址 for(i=0;i