NRF24L01的模块资料,网上已很详尽了,在这不再重复描述知识点了。
这篇文章的目的,旨在把主要知识点胶接起来,梳理成一套完整的步骤,使器件快速上手汇入工作使用。
将按操作顺序,拆分成7个步骤,注解重点、暗坑。以方便自己进行知识管理,亦方便同行兄弟查阅。
代码方面,尽量做到一行代码一行解释。(如有订正和疑问,可留言交流,将迅速回复。)
目录
一、思维导图
二、 引脚连接,解释
三、SPI初始化、函数封装
四、NRF24L01参数写入
五、中断处理函数
六、发送数据
七、接收数据
八、代码下载
3种主要工作模式、8个命令字、每包32字节有效数据,等等这些,直接思维导图更有效,不用文字啰嗦。
对主要点进行了组织,包括了工作模式、命令字等图例,认真过一遍,心里马上就能有个框框。
点击一下图片能变成清晰的大图。当然,右击保存就更方便查看了。
明确一个,NRF24L01和SI24R1的引脚、程序是通用的,无需任何修改,市面上大部份的NRF24L01模块,用的是SI24R1的芯片。两者间通信也互通,已测试确认!
下面是一个通用模块的引脚,以安信可的SI24R1模块为例:
共8个引脚:
NRF的配置,就是把各种参数(数值),如频道,速率,目标地址等,用SPI方式写到指定地址(芯片的寄存器).
这个写入配置的动作,拆分开来看,理解为两部分,是在操作两种通信,别混乱。
首先主机按NRF datasheet的要求,设置和通过SPI通信,向NRF芯片特定地址(寄存器)写入参数值;
而这些写入的数值,就是用于控制NRF与别一个NRF的通信参数。
这两个通信,理解一下~
1:主机和NRF间的通信:
2:NRF和NRF间的收发通信:
编程顺序: GPIO配置 > SPI配置 > SPI(5个小函数) > 写NRF配置 > NRF中断处理(3种情况) > NRF收、发(2个主函数)
GPIO初始化代码:
/*** SPI通信引脚, CS, SCK, MOSI, MISO ***/
GPIOSet (NRF24L01_SPI_CSN_GPIO, NRF24L01_SPI_CSN_PIN, G_MODE_OUT , G_OTYPE_PP, G_OSPEED_25M, G_PUPD_UP , 0); // cs
GPIOSet (NRF24L01_SPI_CLK_GPIO , NRF24L01_SPI_CLK_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // sck
GPIOSet (NRF24L01_SPI_MOSI_GPIO ,NRF24L01_SPI_MOSI_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // mosi
GPIOSet (NRF24L01_SPI_MISO_GPIO ,NRF24L01_SPI_MISO_PIN , G_MODE_AF , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , G_AF_SPI2 ); // miso
/*** NRF控制引脚, CE, IRQ ***/
GPIOSet (NRF24L01_CE_GPIO , NRF24L01_CE_PIN , G_MODE_OUT , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_DOWN , 0); // CE
GPIOSet (NRF24L01_IRQ_GPIO , NRF24L01_IRQ_PIN , G_MODE_IN , G_OTYPE_PP , G_OSPEED_25M , G_PUPD_UP , 0); // IRQ NRF24L01芯片的IRQ引脚下拉能力很弱,注意外部上拉电阻大小,及MCU的上下拉
GPIOSet是个自己封装的初始化函数,不用管,重点看其中的参数就好,注意各上下拉。
SPI 初始化代码:
/*** SPI通信部分 ***/
CSN_HIGH; //失能NRF
NRF24L01_SPI_EN_CLOCK ; // 时钟
NRF24L01_SPIX->CR1 = 0; // 清0
NRF24L01_SPIX->CR1 |= 0<<0; // 采样沿数, NRF要求上升沿采样 CPHA:时钟相位,0x1=在第2个时钟边沿进行数据采样,
NRF24L01_SPIX->CR1 |= 0<<1; // 时钟线闲时极性, CPOL:时钟极性,0x1=空闲状态时,SCK保持高电平
NRF24L01_SPIX->CR1 |= 1<<2; // 主从模式, 0=从,1=主
NRF24L01_SPIX->CR1 |= 2<<3; // 波特率控制[5:3], 0=fPCLK/2, 1=/4倍 2=/8 3/16
NRF24L01_SPIX->CR1 |= 0<<7; // LSB先行, 0=MSB, 1=LSB
NRF24L01_SPIX->CR1 |= 1<<8; // 内部从器件选择,根据9位设置(失能内部NSS)
NRF24L01_SPIX->CR1 |= 1<<9; // 软件从器件管理 : 0=禁止软件管理从设备, 1=使能软件从器件管理(软件NSS)
NRF24L01_SPIX->CR1 |= 0<<11; // 数据帧格式, 0=8位, 1=16位
NRF24L01_SPIX->CR1 |= 1<<6; // 使能
SPI 初始化重点:
插个话题,为什么使用寄存器操作编程?因为:使用寄存器=简单+清晰,你看,不是吗?
SPI收发函数
/*****************************************************************************
*函 数: u8 SPI_RW(u8 Data)
*功 能: SPI写入一个字节,并返回一个字节
*参 数: 要写入的一字节
*返回值: 返回一字节数据
*****************************************************************************/
u8 SPI_SendByte(u8 Data)
{
u8 retry =0;
while((NRF24L01_SPIX ->SR & 2) == 0){ // 理解方式,应该把前式的结果理解为一个寄存器位值,如果这个位值是等号后面的值,就等待
retry++;
if(retry>200) return 0;
}
NRF24L01_SPIX ->DR = Data;
retry=0;
while((NRF24L01_SPIX->SR & 1) == 0 ){
retry++;
if(retry>200) return 0;
}
return NRF24L01_SPIX->DR ;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteReg(u8 reg,u8 value)
*功 能:向指定寄存器地址,写一个字节数据
*参 数:reg: 寄存器地址
* val: 要写入的值
*返回值:status
*****************************************************************************/
u8 Nrf24l01_WriteReg(u8 reg,u8 value)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg) ;
SPI_SendByte(value);
CSN_HIGH;
return status;
}
/*****************************************************************************
*函 数:u8 NRF24l01_read_reg(u8 reg)
*功 能:向指定寄存器地址,读出一字节数据
*参 数:reg: 寄存器地址
*返回值:reg_val(第二个读取到的字节)
*****************************************************************************/
u8 Nrf24l01_ReadReg(u8 reg)
{
u8 reg_val;
CSN_LOW;
SPI_SendByte(reg);
reg_val = SPI_SendByte(0xFF);
CSN_HIGH;
return reg_val;
}
/*****************************************************************************
*函 数:u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
*功 能:写一组数据到寄存器
*参 数:reg: 寄存器地址
* pBuf: 要写入数据的地址
* len: 要写入的数据长度
*返回值:status
*备 注:NRF2401代码移植只需把SPI驱动修改成自己的即可
*****************************************************************************/
u8 Nrf24l01_WriteBuf(u8 reg, u8 *pBuf, u8 len)
{
u8 status;
CSN_LOW;
status = SPI_SendByte(reg);
for(u8 i=0; i
注意:在进入下一步配置NRF24L01前,必须先测试一下SPI通信是否成功 。一般是往NRF的发送地址寄存器写入五个字节,再读出来,把读出的数据和原数据对比, 就能知道SPI是否配置正确、 NRF24L01是否连接成功。
代码程序中,有个连接测试函数,可以作模块的连接,没有贴上来,留邮箱吧。
使用上面初始化的SPI,和刚封装好的几个函数,就可以把需要的参数,写到NRF特定的地址(寄存器), 完成对其配置。
NRF24L01 参数配置代码:
/*** NRF24L01通信配置 30,2M,***/
CE_LOW; // 热待机模式, 只有在ce置低时,才能配置寄存器
//delayUs(2000); // PowerDown 切换为 PowerUp需要1.5ms
Nrf24l01_WriteReg(W_REGISTER + RF_CH, 30); // 射频通道,即频率(0-125)
Nrf24l01_WriteReg(W_REGISTER + RF_SETUP, 0x0F); // 设置TX发射参数,0db增益,2Mbps,低噪声增益关闭 (注意:低噪声增益关闭/开启直接影响通信,要开启都开启,要关闭都关闭0x0f)0x07
Nrf24l01_WriteReg(W_REGISTER + SETUP_AW, 0x03); // 地址长度,默认值时0x03,即5字节
Nrf24l01_WriteBuf(W_REGISTER + TX_ADDR, (u8*)TX_ADDRESS, 5); // 写TX节点地址, 地址宽度:5字节,40位
Nrf24l01_WriteBuf(W_REGISTER + RX_ADDR_P0, (u8*)TX_ADDRESS, 5); // 设置TX节点地址,主要为了使能ACK,, 地址宽度:5字节,40位
Nrf24l01_WriteReg(W_REGISTER + SETUP_RETR, 0x0A); // 设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次 0x1A
Nrf24l01_WriteReg(W_REGISTER + EN_RXADDR, 0x01); // 使能通道0的接收地址
Nrf24l01_WriteReg(W_REGISTER + EN_AA, 0x01); // 使能通道0自动应答
Nrf24l01_WriteReg(W_REGISTER + RX_PW_P0, 32); // 选择通道0的有效数据宽度
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0X7E); // 清除所有中断,防止一进去接收模式就触发中断
Nrf24l01_WriteReg(W_REGISTER+CONFIG, 0x0F); // 配置为接收模式
CE_HIGH; // CE置高,进入状态
重点:
为什么要先说中断?感觉先了解了中断,那么发送、接收就更好理解。
其实不应该叫中断的,但这样好理解,还是遵从约定俗成吧。
中断时, IRQ电平被拉低,是由NRF控制产生的,三种情况可触发:发送成功、达到重发最大次数、接收到数据。
发送成功:
达到重发最大次数:
接收到数据:
说说清理中断,要清理的中断有两处:
中断处理函数代码:
void NRF24L01_IRQ_IRQHANDLER(void)
{
u8 status=0 ;
CE_LOW; // 拉低CE,以便读取NRF中STATUS中的数据
status = Nrf24l01_ReadReg(R_REGISTER + STATUS); // 读取STATUS中的数据,以便判断是由什么中断源触发的IRQ中断
/*** 发送完成中断 ***/
if(status & STATUS_TX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_TX); // 清NRF中断:发送完成
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清发送缓冲区:TX_FIFO
printf("\r\n发送成功!!!!\r\n");
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 接收完成中断 ***/
if(status & STATUS_RX){
memset (NRF_RX_DATA , 0, 32);
Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 读数据
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX); // 清NRF中断:收到数据
Nrf24l01_WriteReg(FLUSH_RX,0xff); // 清除RX FIFO(注意:这句话很必要)
printf("\r\n接收到数据: %s\r\n", NRF_RX_DATA);
vNrf24l01_RxMode (); // 切换为接收状态
}
/*** 最大重发次数中断 ***/
if(status & STATUS_MAX){
Nrf24l01_WriteReg(W_REGISTER+STATUS, 0x70); // 清NRF中断:三个
Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO
printf("\r\n发送失败,达到最大重发次数!!!\r\n");
vNrf24l01_RxMode(); // 切换为接收状态
}
EXTI->PR |= NRF24L01_IRQ_PIN ; // 清理外部中断线标志位
}
NRF的数据收发,是一包一包进行的,一包(帧)数据:包括了前导码、目标地址、包控制域、有效数据、CRC, 但我们只管有效数据,其它的不用我们负责,NRF发送时自动打包,接收到数据时自动拆包。
每一包的有效数据最大为32个字节。当然,也可以只发一个字节的数据。
要发送的数据大于32字节,就要分包进行,自行手动分包处理。
因为在配置部分时,已配置好了频道,速率,重发次数等各种参数,在需要发送数据时,只要往芯片写入要发送的数据和地址,然后切换为发送状态,芯片就会自动发送。
发送成功(收到ack),会产生TX_DS中断。
发送失败了(达到最大重发次数), 也会产生MAR_RT中断。
在中断函数里,根据情况作处理就好。
发送数据代码:
void vNrf24l01_TxPacket(u8 *txbuf)
{
CE_LOW;
Nrf24l01_WriteBuf(W_TX_PAYLOAD, txbuf, 32); // 写数据到TX_BUFF
Nrf24l01_WriteBuf(W_REGISTER+TX_ADDR, (u8*)TX_ADDRESS, 5); // 写入要发送的目标地址
Nrf24l01_WriteBuf(W_REGISTER+RX_ADDR_P0, (u8*)TX_ADDRESS, 5); // 通道0的地址设为和目标地址一致,以接收自动回复auto_ack信号
Nrf24l01_WriteReg(W_REGISTER+CONFIG, 0x0E); // 设置为发送模式,开启所有中断
CE_HIGH;
}
发送就这几句!
重点:RX_ADDR_P0的地址和TX_ARRD一样,目的是自动应答。有个前提,在配置中已使能自动应答。
把操作封装成一个函数,要发什么,就往函数里掉数据就好,每次不要大于32字节。
当系统或程序运行时,大部分时间是运行在接收状态下的。如:
NRF有6个接收通道,指在可同时监听接收同一个频道,同一速率的6个不同设备的数据。
常用的只是通道P0, 如果只使能了通道P0,那就只能接收到P0中地址设备发来的数据。
可以使能全部6个通道,设置6个不同设备地址,就可以监听接收6个设备发来的数据(同一时间,只能接收其中1个设备的数据);
注意,接收数据,指在接收中断发生后,我们从RX_FIFO中把数据读存到主机。而中断发生前的监听接收,NRF自动完成。
接收数据的代码:
接收本来就是最简单的,没啥特别代码,下面的代码,只是在中断处理函数里,再贴出来而已。
Nrf24l01_ReadBuf(R_RX_PAYLOAD, NRF_RX_DATA , RX_PAYLO_WIDTH); // 读数据到数组
Nrf24l01_WriteReg(W_REGISTER+STATUS, STATUS_RX); // 清NRF中断:收到数据
Nrf24l01_WriteReg(FLUSH_RX,0xff); // 清除RX FIFO(注意:这句话很必要)
把接收到的数据,先存放NRF_RX_DATA数组,再进行处理,不要在中断函数中处理。
读完后,记得要清理RX_FIFO,不然它一直占用NRF的缓冲区。RX_FIFO共96字节,分成32字节3组,NRF每次接收到数据就存放到最后面的一组中,当存满了3组,后面再接收到的数据,就会被NRF掉弃。
原本是想把原工程代码上传上来方便下载的,但下载时要消耗积分......
哪位朋友需要原码的,留个邮箱地址,我几乎每天都上线,给你发一份。
有两个版本, F103c8和F429IG的, 注明你要哪个版本。
最后,如果你有更完善的代码,或修改后的代码,请回赠我一份,谢谢~~~
写了三四个小时,累~~~