NRF24L01模块数据,网上已经很详细了,这里不再重复描述知识点。
本文的目的是将主要知识点连接起来,整理成一套完整的步骤,使设备能够快速启动并汇入工作。
按操作顺序分为七个步骤,注明重点和暗坑。方便自己管理知识,方便同行兄弟查阅。
在代码方面,尽量解释一行代码。(如果有纠正和问题,可以留言交流,快速回复。
目录
一、思维导图
二、 引脚连接,解释
三、SPI初始化,函数包装
四、NRF24L01参数写入
五、中断处理函数
六、发送数据
七、接收数据
八、代码下载
直接思维导图更有效,有三种主要工作模式,八个命令字,每包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)stron>
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<len; i++) { SPI_SendByte(pBuf[i]); } CSN_HIGH; return status; } /***************************************************************************** *函 数: u8 vNrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len) *功 能: 向指定寄存器地址,读出指定长度的数据 *参 数: reg : 寄存器地址 * pBuf : 数据存放缓冲区 * len : 读取的字节数量 *返回值: status : 设备状态字 *****************************************************************************/ u8 Nrf24l01_ReadBuf(u8 reg, u8 *pBuf, u8 len) { u8 status; CSN_LOW; status = SPI_SendByte(reg); for(u8 i = 0; i<len ;i++){ pBuf[i] = SPI_SendByte(0xFF); } CSN_HIGH; return status; }
注意:在进入下一步配置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掉弃。
两种方式,
1:代码已打包并上传成CSDN下载资源:
无线通信_NRF24L01.zip_nrf24l01发送前导码-嵌入式文档类资源-CSDN下载
2:由于咨询代码的人多,已把代码打包上传到Q群,群文件夹里找吧:262901124.
最后,如果你有更完善的代码,或修改后的代码,请回赠我一份,谢谢~~~
写了三四个小时,累~~~
标签: 2401电阻值