文章目录
一、NRF24L01简介
1.1 什么是NRF24L01
NRF24L01是NORDIC公司生产的一款工作在2.4GHz的无线收发模块,采用FSK调制,通常由频率发生器、增强型SchockBurstTM模式控制器、功率放大器、晶体放大器、调制器、解调器等组成,可以实现点对点或者1对6的无线通信,无线通信速度最高可达2Mbps,在空旷地带通信距离可达180~240m(未实测)。
1.2 NRF24L01引脚介绍
不太友好的是,买来的NRF24L01后发现实物上没有对应的引脚标注,这里贴一下引脚图
我们简单地介绍一下它各个引脚的功能
1.3 NRF24L01工作模式
NRF24L01的工作模式由CE引脚电平和Config寄存器的PWR_UP和PRIM_RX为共同控制。
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寄存器;
1.6 NRF24L01用途
简单了解了之后,我们用NRF24L01可以做什么呢?这里博主打算是拿NRF2401来做一辆遥控的麦克纳姆轮小车,NRF24L01充当无线遥控的功能。当然,除了做遥控手柄外,它也可以做其他的远距离无线控制,比如无线开关等。
二、程序设计
下面我们开始设计一下NRF24L01的程序,我们使用的是两个STM32F103C8T6核心板和两个NRF24L01,一个作为发送端,一个作为接收端,在开始程序设计之前我们先确定一下引脚分配。
我们使用的是SPI2来与NRF24L01进行通信,关于SPI2的程序这就就不再展示了,可以到STM32速成笔记专栏中查看,这里着重介绍一下NRF24L01的程序。
2.1 NRF24L01初始化
初始化包括两部分,一部分是初始化GPIO,另一部分是初始化SPI,程序设计如下
/*
*==============================================================================
*函数名称: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); // 上拉
}
/*
*==============================================================================
*函数名称: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连接正常,程序设计如下
/*
*==============================================================================
*函数名称: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为发送模式程序设计如下
/*
*==============================================================================
*函数名称: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为接收模式程序设计如下
/*
*==============================================================================
*函数名称: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 发送一次数据
/*
*==============================================================================
*函数名称: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 接收一次数据
/*
*==============================================================================
*函数名称: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发送端初始化程序
/*
*==============================================================================
*函数名称: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接收端初始化程序
/*
*==============================================================================
*函数名称: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 效果展示
最后我们来看一下实现效果