完善资料让更多小伙伴认识你,还能领取20积分哦, 立即完善>
一、思维导图
3种主要工作模式、8个命令字、每包32字节有效数据,等等这些,直接思维导图更有效,不用文字啰嗦。 对主要点进行了组织,包括了工作模式、命令字等图例,认真过一遍,心里马上就能有个框框。 点击一下图片能变成清晰的大图。当然,右击保存就更方便查看了。 二、 引脚连接,解释 明确一个,NRF24L01和SI24R1的引脚、程序是通用的,无需任何修改,市面上大部份的NRF24L01模块,用的是SI24R1的芯片。两者间通信也互通,已测试确认! 下面是一个通用模块的引脚,以安信可的SI24R1模块为例: 共8个引脚:
NRF的配置,就是把各种参数(数值),如频道,速率,目标地址等,用SPI方式写到指定地址(芯片的寄存器). 这个写入配置的动作,拆分开来看,理解为两部分,是在操作两种通信,别混乱。 首先主机按NRF datasheet的要求,设置和通过SPI通信,向NRF芯片特定地址(寄存器)写入参数值; 而这些写入的数值,就是用于控制NRF与别一个NRF的通信参数。 这两个通信,理解一下~ 1:主机和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 } 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 } CSN_HIGH; return status; } 注意:在进入下一步配置NRF24L01前,必须先测试一下SPI通信是否成功 。一般是往NRF的发送地址寄存器写入五个字节,再读出来,把读出的数据和原数据对比, 就能知道SPI是否配置正确、 NRF24L01是否连接成功。 代码程序中,有个连接测试函数,可以作模块的连接,没有贴上来,留邮箱吧。 四、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("rn发送成功!!!!rn"); 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("rn接收到数据: %srn", NRF_RX_DATA); vNrf24l01_RxMode (); // 切换为接收状态 } /*** 最大重发次数中断 ***/ if(status & STATUS_MAX){ Nrf24l01_WriteReg(W_REGISTER+STATUS, 0x70); // 清NRF中断:三个 Nrf24l01_WriteReg(FLUSH_TX, 0xff); // 清除TX_FIFO printf("rn发送失败,达到最大重发次数!!!rn"); 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字节。 七、接收数据 当系统或程序运行时,大部分时间是运行在接收状态下的。如:
常用的只是通道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掉弃。 |
|
|
|
只有小组成员才能发言,加入小组>>
调试STM32H750的FMC总线读写PSRAM遇到的问题求解?
1627 浏览 1 评论
X-NUCLEO-IHM08M1板文档中输出电流为15Arms,15Arms是怎么得出来的呢?
1550 浏览 1 评论
984 浏览 2 评论
STM32F030F4 HSI时钟温度测试过不去是怎么回事?
688 浏览 2 评论
ST25R3916能否对ISO15693的标签芯片进行分区域写密码?
1601 浏览 2 评论
1869浏览 9评论
STM32仿真器是选择ST-LINK还是选择J-LINK?各有什么优势啊?
651浏览 4评论
STM32F0_TIM2输出pwm2后OLED变暗或者系统重启是怎么回事?
520浏览 3评论
539浏览 3评论
stm32cubemx生成mdk-arm v4项目文件无法打开是什么原因导致的?
507浏览 3评论
小黑屋| 手机版| Archiver| 电子发烧友 ( 湘ICP备2023018690号 )
GMT+8, 2024-11-25 01:14 , Processed in 0.812983 second(s), Total 79, Slave 62 queries .
Powered by 电子发烧友网
© 2015 bbs.elecfans.com
关注我们的微信
下载发烧友APP
电子发烧友观察
版权所有 © 湖南华秋数字科技有限公司
电子发烧友 (电路图) 湘公网安备 43011202000918 号 电信与信息服务业务经营许可证:合字B2-20210191 工商网监 湘ICP备2023018690号