NRF24L01-2.4G模块的使用

开篇小叙

说到单片机里常用的无线通信方式,无非也就是2.4G无线技术,蓝牙以及WiFi,为什么会选用NRF24L01-2.4G模块来作为无线通信的的方式呢,无非是NRF24L01简单便宜好用,当然我们也可以使用esp8266以及他的升级版esp32进行蓝牙和WiFi的通信,但是考虑到有的时候并不需要很复杂的功能,两个NRF24L01模块就能很好的完成我们的需求。

事实上NRF24L01在单片机特别是stm32上还是使用非常频繁的。而esp系列虽然也很好用,但是大多的应用场景是在物联网项目上。

NRF24L01-2.4G模块的介绍

  1. 2.4Ghz全球开放ISM频段免许可证使用
  2. 最高工作速率2Mbps,高效GFSK调制,抗干扰能力强,特别适合工业控制场合
  3. 126频道,满足多点通信和跳频通信需要
  4. 内置硬件CRC检错和点对多点通信地址控制
  5. 低功耗1.9-3.6V工作,待机模式下状态为22uA;掉电模式下为900nA
  6. 内置2.4Ghz天线,体积小巧15mm X29mm
  7. 模块可软件设地址,只有收到本机地址时才会输出数据(提供中断指示),可直接接各种单片机使用,软件编程非常方便
  8. 内置专门稳压电路,使用各种电源包括DC/DC开关电源均有很好的通信效果
  9. 2.54MM间距接口, DIP封装

NRF24L01 状态机

· 对于 NRF24L01 的固件编程工作主要是参照NRF24L01 的状态机。主要有以下几个状态

  • Power Down Mode:掉电模式
  • Tx Mode:发射模式
  • Rx Mode:接收模式
  • Standby-1Mode:待机 1 模式
  • Standby-2 Mode:待机 2 模式

上面五种模式之间的相互切换方法以及切换所需要的时间参照下图

SPI通信

在运用NRF24L01进行收发信号之前,先简单的来看一下NRF24L01和单片机之间的通信方式,NRF24L01采用标准的SPI通信,最大频率不超过10HMz,所以想要使用NRF24L01模块,首先我们要来实现主机和外设的通信,也就是单片机如何从NRF24L01中读写数据。

按照惯例我们首先看下模块上面的引脚,然后将他们一一对应到单片机的引脚上面,进行引脚的初始化操作。

如下图所示,NRF24L01一共有8个引脚,分别是6根通信线和两根电源线

我们在.h文件里面进行一下管脚定义:

确定了管脚的对应关系,我们就很方便的去给每个管脚进行初始化操作,

cpp 复制代码
void NRF24L01_Configuration(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	SPI_InitTypeDef SPI_InitStructure;
	
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB | RCC_APB2Periph_SPI1, ENABLE);
	
	//PB0-CE, PB1-CSN
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0 | GPIO_Pin_1;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;
	GPIO_Init(GPIOB, &GPIO_InitStructure);
	
	//IRQ-PA4
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_4;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU; 
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	//PA5:SCK  //PA7:MOSI
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5 | GPIO_Pin_7;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	//PA6:MISO
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6;
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;
	GPIO_Init(GPIOA, &GPIO_InitStructure);
	
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;//SPI 设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;		//设置为主 SPI
	SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;	//SPI 发送接收 8 位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;			//时钟悬空低
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;		//数据捕获于第一个时钟沿
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;			//内部 NSS 信号有 SSI 位控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_8;//波特率预分频值为 8
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;  //数据传输从 MSB 位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;			//用于CRC校验,默认值是7
	SPI_Init(SPI1, &SPI_InitStructure);
	SPI_Cmd(SPI1, ENABLE);
}

使用stm32内部硬件操作SIP需要我们设置SPI相关的时钟配置,就要去看手册里面的时序图,时序图规定了通信双方之间的时序规则

通过查看手册当中的时序图我们可以知道,读操作时,CSN信号线要处于低电平,SCL时钟线号线处于上升沿,数据位从高位开始,一次读写8位。所以在配置SPI时我们设置SPI 发送接收 8 位帧结构,初始时钟悬空低,数据捕获于第一个时钟沿,数据传输从 MSB 位开始。

管脚顺利定义完成,接着我们要进行主从设备通信,只需要严格按照时序图所给定标准即可

cpp 复制代码
unsigned char SPI1_ReadWriteByte(unsigned char Data)
{
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE) == RESET);
	SPI_I2S_SendData(SPI1, Data);
	
	while(SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE) == RESET);
	return SPI_I2S_ReceiveData(SPI1);
}

void NRF24L01_Write_Reg(unsigned char reg, unsigned char value)
{
	NRF24L01_CSN_L;
	SPI1_ReadWriteByte(reg);
	SPI1_ReadWriteByte(value);
	NRF24L01_CSN_H;
}

unsigned char NRF24L01_Read_Reg(unsigned char reg)
{
	unsigned char reg_val;
	
	NRF24L01_CSN_L;
	SPI1_ReadWriteByte(reg);
	reg_val = SPI1_ReadWriteByte(0x00);
	NRF24L01_CSN_H;
	
	return reg_val;
}

相信大家也发现了,在SPI读操作时,先要进行MOSI,然后才是MISO。然而的,在SPI写操作时,是先进行MOSI,后面再跟小段MISO。通俗的来讲就是我们要从NRF24L01模块中读取数据前,先要向NRF24L01里面发点什么,我们要向NRF24L01模块中写入数据后,也要从里面读点什么。所以我们需要知道的是SPI里面有读就有写,他们是成对出现的。

这里可能就有些人要感到疑惑了,我只是想单纯的读或者写,为什么非得先写或读呢,这里我简单进行一点说明,这里的这种迷惑操作如果我们结合IIC协议去看,可能就比较容易理解了,在IIC协议中,只有一根线进行数据传输,所以数据传输时需要有ACK(应答)来确保通信双方是否能够正确传输数据。而SPI中数据传输有两根线,实际上进行MOSI前先MISO,就是SPI中的应答操作,只不过是从一根线上的数据操作变成了两根线,导致我们感觉像是有点不符合常理。但是你如果把MOSI对应的MISO理解成为我从NRF24L01里面读取数据前,先给NRF24L01发一个信号,告诉NRF24L01我要开始读数据了,让NRF24L01有所准备,这样一切都梳理成章了起来。

接着我要首先测试一下主机与NRF24L01的读写是否正常,以便后面进行无线通信做准备,我们可以编写一个检查的函数如下所示

cpp 复制代码
unsigned int NRF24L01_Check(void)
{
	unsigned char ch;
	
	NRF24L01_Write_Reg(NRF_WRITE_REG + TX_ADDR, 'A');
	ch = NRF24L01_Read_Reg(NRF_READ_REG + TX_ADDR);
	
	if(ch == 'A')
		return 0;
	else
		return 1;
}

如果NRF24L01_Check函数返回值为0,那么证明我们的通信是正常的,利用串口我们可以打印一下函数的返回值:

可以看串口助手里面成功打印出0000,证明我们配置SPI和通信是没有问题的,接下来我们就可以安心的进行设置NRF24L01 状态机和发送数据以及接受数据函数的编写了。

设置NRF24L01状态

众所周知,NRF24L01常用的有5中状态,这里我们只讨论能够实现我们通信功能的发送模式和接收模式 ,实际上要设置这两种模式也很简单,无非就是下面的固定的几步:

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。

Rx 模式初始化过程:

  1. 初始化步骤 24L01 相关寄存器
  2. 写 Rx 节点的地址 RX_ADDR_P0
  3. 使能 AUTO ACK EN_AA
  4. 使能 PIPE 0 EN_RXADDR
  5. 选择通信频率 RF_CH
  6. 选择通道 0 有效数据宽度 Rx_Pw_P0
  7. 配置发射参数(低噪放大器增益、发射功率、无线速率) RF_SETUP
  8. 配置 24L01 的基本参数以及切换工作模式 CONFIG。

手册当中给的配置发送接收模式的过程十分详细,甚至可以说详细过头了。实际上我们在编写配置Tx / Rx 模式初始化的时候根本用不着完全按照手册当中的过程,里面的有好多过程是可以省略的。

Tx 模式初始化

首先是发送模式的配置,第一条要我们配置Tx 节点的地址 TX_ADDR,第二条让我们配置Rx 节点的地址,其实这第二条配置我们可以先不着急,等到我们编写发送数据包的时候再进行接收端地址的设置也不迟。第三条第四条告诉我们要是能EN_AA和EN_RXADDR,这里实际上NRF24L01默认就已经配置好了,所以我们也可以跳过。第五条配置自动重发次数SETUP_RETR,这个需要我们自己手动配置,第七条和第八条也是可以直接使用NRF24L01里面的默认配置,当然你也可以选择自己再手动更改一下通信频率和发射参数,当然我认为没有这个必要,毕竟我们编程当然是越简单越好。最后就是配置 24L01 的基本参数以及切换工作模式CONFIG了。完成后代码如下:

cpp 复制代码
void Set_NRF24L01_TX_Mode(void)
{
	NRF24L01_CE_L;
	
	NRF24L01_Write_Buf(NRF_WRITE_REG + TX_ADDR, TX_ADDRESS, 5);
	NRF24L01_Write_Reg(NRF_WRITE_REG + SETUP_RETR, 0x1a);
	NRF24L01_Write_Reg(NRF_WRITE_REG + CONFIG, 0x0E);
	
	NRF24L01_CE_H;	
}

这时候有人可能发现了,再配置前后我分别又加上了两条语句NRF24L01_CE_L和NRF24L01_CE_H,这两条语句是干什么的呢,实际上通读手册我们可以得知,NRF24L01的工作模式是由CE和CNS两条信号线共同控制的,在CSN为低的情况下,CE协同CONFIG寄存器共同决定NRF24L01的状态,在发送模式下,置CE为高,至少10us, 将使能发送过程。

Rx 模式初始化过程

有了配置Tx模式初始化的经验,Rx模式初始化和它高度一直,我也就不在啰嗦,直接上配置完成的代码,大家可以结合上面的Rx 模式初始化过程观看。

cpp 复制代码
void Set_NRF24L01_RX_Mode(void)
{
	NRF24L01_CE_L;
	
	NRF24L01_Write_Buf(NRF_WRITE_REG + RX_ADDR_P0, RX_ADDRESS, 5);
	NRF24L01_Write_Reg(NRF_WRITE_REG + RX_PW_P0, 32);
	NRF24L01_Write_Reg(NRF_WRITE_REG + CONFIG, 0x0F);
	
	NRF24L01_CE_H;	
}

发送和接收数据包

发送流程

1.配置接收地址和要接收的数据包大小;

2.NRF24L01通过把STATUS寄存器置位(STATUS-般引起微控制器中断)通知微控制器;

3.读取中断标志位

4.清除中断标志位以便下一次中断的正常进行

5.判断发生了哪种中断,若发生超时中断则清除发送超时的发送缓冲区

cpp 复制代码
unsigned int NRF24L01_TxPacket(unsigned char *txbuf)
{
	unsigned char stat;
	
	NRF24L01_Write_Buf(NRF_WRITE_REG + RX_ADDR_P0, RX_ADDRESS, 5);
	NRF24L01_Write_Buf(WR_TX_PLOAD, txbuf, 32);
	
	//判断是否进入中断,若进入了中断,说明达到最大重发次数或者发送完成,跳过循环
	while(NRF24L01_IRQ != 0);
	
	stat = NRF24L01_Read_Reg(NRF_READ_REG + STATUS);//读取中断标志位
	NRF24L01_Write_Reg(NRF_WRITE_REG + STATUS, stat);//清除中断标志位
	
	if(stat & MAX_RT)//判断发生了哪种中断
	{
		NRF24L01_Write_Reg(FLUSH_TX, 0xff);//清除发送超时的发送缓冲区
		return 1;
	}
	if(stat & TX_DS)
			return 0;
	
	return 1;
}

接收流程

1.当接收到正确的数据包(正确的地址和CRC校验码),NRF2401 自动把字头、地址和CRC校验位移去;

2.NRF24L01通过把STATUS寄存器的RX_ DR置位(STATUS-般引起微控制器中断)通知微控制器;

3.微控制器把数据从FIFO读出(0X61指令);

4.所有数据读取完毕后,可以清除STATUS寄存器。NRF2401可以进入四种主要的模式之一;

cpp 复制代码
unsigned int NRF24L01_RxPacket(unsigned char *rxbuf)
{
	unsigned char stat;
	
	while(NRF24L01_IRQ != 0);
	stat = NRF24L01_Read_Reg(NRF_READ_REG + STATUS);
	
	if(stat & RX_DR)
	{
		NRF24L01_Read_Buf(RD_RX_PLOAD, rxbuf, 32);
        NRF24L01_Write_Reg(NRF_WRITE_REG + STATUS, stat);
		return 0;
	}
		
	return 1;
}

小结

到此为止关于NRF24L01相关使用的说明就结束了,学到了如何在单片机之间进行无线通信,而且对之前的SPI知识进行了一个加强巩固的作用。希望能对大家有所帮助,谢谢大家了。至于如何使用这些功能函数真正实现通信就需要大家自己整理自己所需的业务逻辑,调用上述功能函数来进行实际调整了最后我放出我的主函实例供大家参考:

Send

cpp 复制代码
int main(void)
{	
	unsigned char Control = 0;
	
	SysTick_Configuration();
	Uart1_Configuration();
	Key_Configuration();
			
	NRF24L01_Configuration();  
	
	while(NRF24L01_Check())
	{
		printf("Connect to NRF24L01 error\n");
		Delay_us(1000000);
	}
	printf("NRF24L01 OK\n");
	
	Set_NRF24L01_TX_Mode();
	
	while(1)
	{
		if(key_scan(GPIOA, GPIO_Pin_0) == KEY_ON)
		{
			Control = 1;
			while(NRF24L01_TxPacket(&Control) != 0)
			{
				printf("Send Failed\n");
			}
			Control = 0;
		}
	}
}

Recevie

cpp 复制代码
int main(void)
{	
	unsigned char Rec;
	
	SysTick_Configuration();
	Uart1_Configuration();
	Led_Configuration();
		
	NRF24L01_Configuration();  
	
	while(NRF24L01_Check())
	{
		printf("Connect to NRF24L01 error\n");
		Delay_us(1000000);
	}
	printf("NRF24L01 OK\n");
	
	Set_NRF24L01_RX_Mode();
	
	while(1)
	{
		while(NRF24L01_RxPacket(&Rec) != 0);
		if(Rec == 1)
			LED_TOGGLE;
	}
}
相关推荐
代码敲不对.2 小时前
江科大笔记—软件安装
笔记·stm32·单片机·嵌入式硬件
&AtTiTuDe;4 小时前
如何使用IIC外设(硬件IIC)
经验分享·笔记·stm32·单片机·嵌入式硬件·硬件工程
LightningJie5 小时前
STM32(十五):I2C通信
stm32·单片机·嵌入式硬件
辰哥单片机设计5 小时前
4×4矩阵键盘详解(STM32)
stm32·单片机·嵌入式硬件·矩阵·计算机外设·传感器
xuhc_zd5 小时前
STM32外设-0.96寸OLED显示屏
stm32·单片机
安科瑞刘鸿鹏5 小时前
分布式光伏发电系统如何确保电能质量达到并网要求?
服务器·网络·分布式·嵌入式硬件·能源
侥幸哥f5 小时前
GD32F103单片机-EXTI外部中断
单片机·gd32·exti
HeiLongMada7 小时前
从边缘设备到云端平台,合宙DTU&RTU打造无缝物联网解决方案
嵌入式硬件·物联网·硬件工程·pcb工艺
QQ19284999068 小时前
基于STM32无刷直流电机调速蓝牙APP无线监测控制系统
stm32·嵌入式硬件·mongodb
OH五星上将9 小时前
OpenHarmony(鸿蒙南向开发)——标准系统方案之瑞芯微RK3568移植案例(下)
linux·驱动开发·嵌入式硬件·harmonyos·openharmony·鸿蒙开发·系统移植