【STM32外设系列】NRF24L01无线收发模块

🎀 文章作者:二土电子

🌸 关注公众号获取更多资料!

🐸 期待大家一起学习交流!


文章目录

  • 一、NRF24L01简介
    • [1.1 什么是NRF24L01](#1.1 什么是NRF24L01)
    • [1.2 NRF24L01引脚介绍](#1.2 NRF24L01引脚介绍)
    • [1.3 NRF24L01工作模式](#1.3 NRF24L01工作模式)
    • [1.4 NRF24L01的SPI时序](#1.4 NRF24L01的SPI时序)
    • [1.5 Enhanced ShockBurstTM收发模式介绍](#1.5 Enhanced ShockBurstTM收发模式介绍)
      • [1.5.1 Enhanced ShockBurstTM发送流程](#1.5.1 Enhanced ShockBurstTM发送流程)
      • [1.5.2 Enhanced ShockBurstTM接收流程](#1.5.2 Enhanced ShockBurstTM接收流程)
    • [1.6 NRF24L01用途](#1.6 NRF24L01用途)
  • 二、程序设计
    • [2.1 NRF24L01初始化](#2.1 NRF24L01初始化)
    • [2.2 NRF24L01连接检测](#2.2 NRF24L01连接检测)
    • [2.3 设置为发送模式](#2.3 设置为发送模式)
    • [2.4 设置为接收模式](#2.4 设置为接收模式)
    • [2.5 发送一次数据](#2.5 发送一次数据)
    • [2.6 接收一次数据](#2.6 接收一次数据)
  • 三、无线摇杆测试
    • [3.1 NRF24L01发送端初始化程序](#3.1 NRF24L01发送端初始化程序)
    • [3.2 NRF24L01接收端初始化程序](#3.2 NRF24L01接收端初始化程序)
    • [3.3 效果展示](#3.3 效果展示)

一、NRF24L01简介

1.1 什么是NRF24L01

NRF24L01是NORDIC公司生产的一款工作在2.4GHz的无线收发模块,采用FSK调制,通常由频率发生器、增强型SchockBurstTM模式控制器、功率放大器、晶体放大器、调制器、解调器等组成,可以实现点对点或者1对6的无线通信,无线通信速度最高可达2Mbps,在空旷地带通信距离可达180~240m(未实测)。

NRF24L01采用SPI通信,关于SPI通信的知识,这里就不再详细介绍了,具体可查看STM32速成笔记专栏中关于SPI的介绍。

1.2 NRF24L01引脚介绍

不太友好的是,买来的NRF24L01后发现实物上没有对应的引脚标注,这里贴一下引脚图

我们简单地介绍一下它各个引脚的功能

NRF24L01引脚 功能描述
CE 模式控制线,在CSN为低电平时,CE协同Config寄存器共同决定NRF24L01的状态(关于NRF24L01的状态后续会有介绍)
CSN SPI片选线
SCK SPI时钟线
MOSI 主机输出从机输入
MISO 主机输入从机输出
IRQ 中断信号线,中断时变为低电平,有三种中断,分别是TxFIFO发送完并收到ACK(使能ACK的情况)、RxFIFO收到数据、达到最大重发次数

1.3 NRF24L01工作模式

NRF24L01的工作模式由CE引脚电平和Config寄存器的PWR_UP和PRIM_RX为共同控制。

NRF24L01工作状态 PWR_UP PRIM_RX CE FIFO寄存器状态
接收模式 1 1 -
发送模式 1 0 数据在TxFIFO中
发送模式 1 0 高--->低 停留在发送模式,直至发送完成
待机模式II 1 0 TxFIFO为空
待机模式I 1 - 无数据传输

其中收发模式有Enhanced ShockBurstTM收发模式和ShockBurstTM收发模式,只有前者支持自动ACK和自动重发。开启了自动ACK,实际也就默认选择了Enhanced ShockBurstTM收发模式。
  Enhanced ShockBurstTM收发模式使用片内先入先出堆栈区,数据可以低速从微控制器送入,然后以高速发射。在Enhanced ShockBurstTM收发模式下,NRF24L01自动处理字头和CRC校验码。在接收数据时,自动把字头和CRC校验码去掉。在发送数据时,自动加上字头和CRC校验码。在发送模式下,置CE为高,至少10us,将使能发送过程。

1.4 NRF24L01的SPI时序

NRF24L01需要设置时钟极性为0,也就是空闲状态时SCK时钟线为低电平;时钟相位设置为0,也就是在时钟线SCK的第一个跳变沿采样数据。

1.5 Enhanced ShockBurstTM收发模式介绍

Enhanced ShockBurstTM收发模式的发送方要求终端设备在接收到数据后有应答信号,以便发送方检测有无数据丢失,一旦检测到丢失则重发数据。在接收模式下,最多可以接收6路不同的数据,每一个数据通道使用不同的地址。也就是说6个不同的NRF24L01设置为发送模式后,可以与同一个设置为接收模式的NRF24L01进行通讯,接收端的NRF24L01可以对这6个发射端进行识别。

上面说了,可以1收6发,接收端可以识别6个不同的发送端,这是怎么做到的呢?其实上面也说了,通过不同的地址。我们举个简单的例子来说明一下。

在开始介绍例子之前我们要知道一个前提条件,无论是接收端还是发送端,他们的寄存器都是一样的,无非就是一个是接收模式一个是发送模式,知道了这个前提之后我们开始介绍。

假设NRF24L01的6个接收数据的地址分别为0x0、0x1、0x2、0x3、0x4、0x5。当第一个NRF24L01发送数据时,通过数据通道0x0发送数据,接收端的0x0寄存器收到数据之后就知道是第一个NRF24L01发送的,在给0x0地址返回一个应答信号,其他5个也是相同的原理。

简单总结一下

  • 在接收端,确认收到数据后记录地址,并一次地址为目的地址返回应答信号。
  • 在发送端,发送地址和接收地址要保持一致,以确保接收到正确的应答信号。

1.5.1 Enhanced ShockBurstTM发送流程

  • 把地址和要发送的数据按时序送入NRF24L01;
  • 配置Config寄存器,使NRF24L01进入发送模式;
  • 微控制器将CE置高(至少10us),触发Enhanced ShockBurstTM发射;

1.5.2 Enhanced ShockBurstTM接收流程

  • 配置接收地址和要接收的数据包大小(接收地址要确保和发送时配置的地址是一致的);
  • 配置Config寄存器,使NRF24L01进入接收模式,把CE置高;
  • 130us后,NRF24L01进入监听状态,等待数据包的到来;
  • 但接收到正确的数据包(正确的地址和CRC校验码)后,NRF24L01自动把字头、地址和CRC校验码去掉;
  • NRF24L01通过把STATUS寄存器的RX_DR置位,通知控制器接收完成;
  • 微控制器利用指令把数据从FIFO中读出;
  • 所有数据读取完毕后,清除STATUS寄存器;

关于NRF24L01的指令,在后续的程序设计中会有详细介绍。

1.6 NRF24L01用途

简单了解了之后,我们用NRF24L01可以做什么呢?这里博主打算是拿NRF2401来做一辆遥控的麦克纳姆轮小车,NRF24L01充当无线遥控的功能。当然,除了做遥控手柄外,它也可以做其他的远距离无线控制,比如无线开关等。

二、程序设计

下面我们开始设计一下NRF24L01的程序,我们使用的是两个STM32F103C8T6核心板和两个NRF24L01,一个作为发送端,一个作为接收端,在开始程序设计之前我们先确定一下引脚分配。

NRF24L01引脚 主控GPIO
GND GND
VCC 3.3V
CE PB11
CSN PB12
IRQ PB10
SCK PB13
MISO PB14
MOSI PB15

我们使用的是SPI2来与NRF24L01进行通信,关于SPI2的程序这就就不再展示了,可以到STM32速成笔记专栏中查看,这里着重介绍一下NRF24L01的程序。

2.1 NRF24L01初始化

初始化包括两部分,一部分是初始化GPIO,另一部分是初始化SPI,程序设计如下

c 复制代码
/*
 *==============================================================================
 *函数名称:Drv_Nrf24l01_Gpio_Init
 *函数功能:NRF24L01引脚初始化
 *输入参数:无
 *返回值:无
 *备  注:用来初始化NRF24L01的CS、IRQ和CE的引脚
 *==============================================================================
*/
void Drv_Nrf24l01_Gpio_Init (void)
{
	// 结构体定义
	GPIO_InitTypeDef GPIO_InitStructure;
	
	// 开启时钟
	RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
	
	// 配置结构体 CSN CE
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11 | GPIO_Pin_12;
 	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;   // 推挽输出
 	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 	GPIO_Init(GPIOB, &GPIO_InitStructure);				
  
	GPIO_InitStructure.GPIO_Pin  = GPIO_Pin_10;   
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD;   // 下拉输入
	GPIO_Init(GPIOB, &GPIO_InitStructure);

	GPIO_SetBits(GPIOB,GPIO_Pin_10 | GPIO_Pin_11 | GPIO_Pin_12);   // 上拉	
}
c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_Init
 *函数功能:初始化NRF24L01
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
*/
void Med_Nrf24l01_Init (void)
{
	// 结构体定义
	SPI_InitTypeDef  SPI_InitStructure;
	
	Drv_Nrf24l01_Gpio_Init();   // 初始化NRF24L01引脚
	
	SPI2_Init();   // 初始化SPI2
	SPI_Cmd(SPI2, DISABLE);   // 不使能SPI2,因为要针对NRF24L01重新配置结构体
	
	// 配置SPI结构体
	SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex;   // SPI设置为双线双向全双工
	SPI_InitStructure.SPI_Mode = SPI_Mode_Master;   // SPI主机
  SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b;   // 发送接收8位帧结构
	SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low;   // 时钟悬空低
	SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge;   // 数据捕获于第1个时钟沿
	SPI_InitStructure.SPI_NSS = SPI_NSS_Soft;   // NSS信号由软件控制
	SPI_InitStructure.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_16;   // 定义波特率预分频的值:波特率预分频值为16
	SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB;   // 数据传输从MSB位开始
	SPI_InitStructure.SPI_CRCPolynomial = 7;   // CRC值计算的多项式
	SPI_Init(SPI2,&SPI_InitStructure);   // 根据SPI_InitStruct中指定的参数初始化外设SPIx寄存器
 
	SPI_Cmd(SPI2, ENABLE);   // 使能SPI外设
			 
	NRF24L01_CE = 0;   // 使能24L01
	NRF24L01_CSN = 1;   // SPI片选取消  
}

2.2 NRF24L01连接检测

连接检测的原理比较简单,我们往NRF24L01的特定寄存器写入特定值之后再读取,如果读取到的值和写入的相同,那么认为NRF24L01连接正常,程序设计如下

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_Connect_Check
 *函数功能:检查NRF24L01的连接状态
 *输入参数:无
 *返回值:0:连接正常;1:连接异常
 *备  注:无
 *==============================================================================
*/
u8 Med_Nrf24l01_Connect_Check (void)
{
	u8 buf[5] = {0XA5,0XA5,0XA5,0XA5,0XA5};
	u8 i;
	SPI2_SetSpeed(SPI_BaudRatePrescaler_4);   // spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)   	 
	Med_Nrf24l01_Write_Buf(NRF_WRITE_REG + TX_ADDR,buf,5);   // 写入5个字节的地址.	
	Med_Nrf24l01_Write_Buf(TX_ADDR,buf,5);   // 读出写入的地址
	
	for(i = 0;i < 5;i ++)
	{
		if(buf[i] != 0XA5)
		{
			break;
		}
	}
	if(i != 5)
	{
		return 1;   // 检测24L01错误
	}
	return 0;   // 检测到24L01
}

在初始化时可以进行连接检测,但是最好是加入一个超时检测,防止在NRF24L01连接异常时程序卡死。

2.3 设置为发送模式

设置NRF24L01为发送模式程序设计如下

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_TX_Mode
 *函数功能:初始化NRF24L01到TX模式
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
*/
void Med_Nrf24l01_TX_Mode (void)
{														 
	NRF24L01_CE = 0;	    
	Med_Nrf24l01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);   // 写TX节点地址 
	Med_Nrf24l01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);   // 设置TX节点地址,主要为了使能ACK	  

	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);   // 使能通道0的自动应答    
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);   // 使能通道0的接收地址  
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);   // 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+RF_CH,40);   // 设置RF通道为40
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);   // 设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);   // 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
	NRF24L01_CE = 1;   // CE为高,10us后启动发送
}

2.4 设置为接收模式

设置NRF24L01为接收模式程序设计如下

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_RX_Mode
 *函数功能:初始化NRF24L01到RX模式
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
*/
void Med_Nrf24l01_RX_Mode (void)
{
	NRF24L01_CE = 0;	  
	Med_Nrf24l01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);   // 写RX节点地址
	
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);   // 使能通道0的自动应答    
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01);   // 使能通道0的接收地址  	 
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+RF_CH,40);   // 设置RF通信频率		  
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);   // 选择通道0的有效数据宽度 	    
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);   // 设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+CONFIG, 0x0f);   // 配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式 
	NRF24L01_CE = 1;   // CE为高,进入接收模式 
}

2.5 发送一次数据

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_TxPacket
 *函数功能:NRF24L01发送一次数据
 *输入参数:txbuf;要发送的数据首地址指针
 *返回值:发送完成情况
 *备  注:无
 *==============================================================================
*/
u8 Med_Nrf24l01_TxPacket (u8 *txbuf)
{
	u8 sta;
 	SPI2_SetSpeed(SPI_BaudRatePrescaler_8);   // spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)   
	NRF24L01_CE = 0;
	Med_Nrf24l01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);   // 写数据到TX BUF  32个字节
 	NRF24L01_CE = 1;   // 启动发送	   
	while(NRF24L01_IRQ != 0);   // 等待发送完成
	sta = Med_Nrf24l01_Read_Reg(STATUS);   // 读取状态寄存器的值	   
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG + STATUS,sta);   // 清除TX_DS或MAX_RT中断标志
	if(sta & MAX_TX)   // 达到最大重发次数
	{
		Med_Nrf24l01_Write_Reg(FLUSH_TX,0xff);   // 清除TX FIFO寄存器 
		return MAX_TX; 
	}
	if(sta & TX_OK)   // 发送完成
	{
		return TX_OK;
	}
	return 0xff;   // 其他原因发送失败
}

2.6 接收一次数据

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Nrf24l01_RxPacket
 *函数功能:NRF24L01接收一次数据
 *输入参数:rxbuf;存储接收数据的首地址指针
 *返回值:0:接收成功;1:接收失败
 *备  注:无
 *==============================================================================
*/
u8 Med_Nrf24l01_RxPacket (u8 *rxbuf)
{
	u8 sta;		    							   
	SPI2_SetSpeed(SPI_BaudRatePrescaler_8);   // spi速度为9Mhz(24L01的最大SPI时钟为10Mhz)   
	sta = Med_Nrf24l01_Read_Reg(STATUS);   // 读取状态寄存器的值    	 
	Med_Nrf24l01_Write_Reg(NRF_WRITE_REG+STATUS,sta);   // 清除TX_DS或MAX_RT中断标志
	if(sta & RX_OK)   // 接收到数据
	{
		Med_Nrf24l01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);   // 读取数据
		Med_Nrf24l01_Write_Reg(FLUSH_RX,0xff);   // 清除RX FIFO寄存器 
		return 0;
	}	   
	return 1;   // 没收到任何数据
}

三、无线摇杆测试

下面我们结合之前我们介绍的外设双轴按键PS2来简单实现无线摇杆,关于双轴按键PS2摇杆的内容,可以移步至STM32外设系列专栏查看,这里就不再指路了。

3.1 NRF24L01发送端初始化程序

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Mcu_Iint
 *函数功能:初始化系统
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
*/
void Med_Mcu_Iint (void)
{
	u8 outTimeCheck = 0;   // 超时检测变量
	
	delay_init();   //延时函数初始化
	uart_init(115200);   // 初始化串口
	ADC1_Init();   // ADC初始化
	Drv_Ps2_Gpio_Init();   // 初始化PS2摇杆引脚
	Med_Nrf24l01_Init();   // 初始化NRF24L01
	
	// NRF24L01连接检测
	printf ("Ready to check NRF24L01 connect...\r\n");
	while(Med_Nrf24l01_Connect_Check())
	{
		outTimeCheck = outTimeCheck + 1;
		delay_ms(200);
		
		// 超时退出
		if (outTimeCheck >= 100)
		{
			printf ("NRF24L01 connect error!\r\n");
			break;
		}
	}
	printf ("NRF24L01 connect ok!\r\n");
	
	Med_Nrf24l01_TX_Mode();   // 初始化为发送模式
	printf ("NRF24L01 set send mode ok!\r\n");
}

3.2 NRF24L01接收端初始化程序

c 复制代码
/*
 *==============================================================================
 *函数名称:Med_Mcu_Iint
 *函数功能:初始化系统
 *输入参数:无
 *返回值:无
 *备  注:无
 *==============================================================================
*/
void Med_Mcu_Iint (void)
{
	u8 outTimeCheck = 0;   // 超时检测变量
	
	delay_init();   //延时函数初始化
	uart_init(115200);   // 初始化串口
	ADC1_Init();   // ADC初始化
	Med_Nrf24l01_Init();   // 初始化NRF24L01
	
	// NRF24L01连接检测
	printf ("Ready to check NRF24L01 connect...\r\n");
	while(Med_Nrf24l01_Connect_Check())
	{
		outTimeCheck = outTimeCheck + 1;
		delay_ms(200);
		
		// 超时退出
		if (outTimeCheck >= 100)
		{
			printf ("NRF24L01 connect error!\r\n");
			break;
		}
	}
	printf ("NRF24L01 connect ok!\r\n");
	
	Med_Nrf24l01_RX_Mode();   // 初始化为接收模式
	printf ("NRF24L01 set receive mode ok!\r\n");
}

3.3 效果展示

最后我们来看一下实现效果

相关推荐
智者知已应修善业1 小时前
【51单片机用数码管显示流水灯的种类是按钮控制数码管加一和流水灯】2022-6-14
c语言·经验分享·笔记·单片机·嵌入式硬件·51单片机
智商偏低8 小时前
单片机之helloworld
单片机·嵌入式硬件
青牛科技-Allen9 小时前
GC3910S:一款高性能双通道直流电机驱动芯片
stm32·单片机·嵌入式硬件·机器人·医疗器械·水泵、
森焱森11 小时前
无人机三轴稳定控制(2)____根据目标俯仰角,实现俯仰稳定化控制,计算出升降舵输出
c语言·单片机·算法·架构·无人机
白鱼不小白11 小时前
stm32 USART串口协议与外设(程序)——江协教程踩坑经验分享
stm32·单片机·嵌入式硬件
S,D11 小时前
MCU引脚的漏电流、灌电流、拉电流区别是什么
驱动开发·stm32·单片机·嵌入式硬件·mcu·物联网·硬件工程
芯岭技术14 小时前
PY32F002A单片机 低成本控制器解决方案,提供多种封装
单片机·嵌入式硬件
youmdt15 小时前
Arduino IDE ESP8266连接0.96寸SSD1306 IIC单色屏显示北京时间
单片机·嵌入式硬件
嘿·嘘15 小时前
第七章 STM32内部FLASH读写
stm32·单片机·嵌入式硬件
Meraki.Zhang15 小时前
【STM32实践篇】:I2C驱动编写
stm32·单片机·iic·驱动·i2c