stm32【 NRF24L01多通道、动态数据、主从一体(1)】 您所在的位置:网站首页 nrf24l01发送数据类型 stm32【 NRF24L01多通道、动态数据、主从一体(1)】

stm32【 NRF24L01多通道、动态数据、主从一体(1)】

2024-01-21 07:32| 来源: 网络整理| 查看: 265

nRF24L01 1对1通讯

测试平台:stm32f103c8t6 库版本:官方库3.5版本

对于2.4G无线射频通讯的知识在网上有很多,所以就不涉及原理的讲解,在这里感谢该博主的文章【NRF】,对无线通讯的理论知识和nRF模块数据手册深入进行了深度的分析,在多次阅读该文章后,终于将nRF24L01模块的单通道,多通道以及动态数据长度主从一体调试通过,最终封装成简单易用的个人库函数。

本文章主要从程序入手介绍如何使用nRF24L01模块的使用方法 并记录在nRF模块调试过程中遇到的问题

目录 nRF24L01 1对1通讯1、SPI通讯2、nRF24L01模块介绍3、nRF模块GPIO配置4、nRF读写API读寄存器写寄存器 5、nRF配置NRF寄存器常用寄存器功能nRF初始化PTX/PRX(发送模式/接收模式)中断服务处理函数nRF配置总结 6、nRF发送数据(单通道)7、nRF接收数据(单通道)8、nRF24L01一对一通信小结9、nRF多通道配置

1、SPI通讯

在调试nRF24L01模块前,要先保证自己的SPI通讯是正常的,我这里使用的是SPI1,配置按正常的配置就可以了,但是要注意的是时钟极性CPOL和时钟相位CPHA需根据nRF的时序图确定,串行时钟稳态要是低电平。 我第一次测试的时候就是复制以前的代码,时钟稳态是高电平结果,nRF模块都没有回应,后来仔细查找才发现的是这个问题

SPI_InitStructure.SPI_BaudRatePrescaler = speed; SPI_InitStructure.SPI_CPHA = SPI_CPHA_1Edge; //数据捕获于第一个时钟沿 SPI_InitStructure.SPI_CPOL = SPI_CPOL_Low; //串行时钟稳态 SPI_InitStructure.SPI_DataSize = SPI_DataSize_8b; //每次收发数据大小 SPI_InitStructure.SPI_Direction = SPI_Direction_2Lines_FullDuplex; //设置SPI单向/双向 SPI_InitStructure.SPI_Mode = SPI_Mode_Master; //设置SPI模式,主/从模式 SPI_InitStructure.SPI_FirstBit = SPI_FirstBit_MSB; //指定数据传输从MSB位还是LSB位开始 SPI_InitStructure.SPI_NSS = SPI_NSS_Soft; //NSS信号由硬件(NSS管脚)还是软件(使用SSI位)控制 SPI_InitStructure.SPI_CRCPolynomial = 7; //CRC值计算的多项式

以上是SPI的配置,管脚配置就不多说了,记得开启复用时钟就是了 SPI配置好后只需提供一个读写函数

//SPI读写函数 u8 SPI1_ReadWriteByte(u8 data) { u8 temp = 0; //等待发送数据寄存器清空,temp防止程序卡死 while(!(SPI1->SR & SPI_I2S_FLAG_TXE)){ temp++; if (temp > 200){return 0xFF;} } //发送数据 SPI1->DR = data; temp = 0; //等待接收数据寄存器非空,temp防止程序卡死 while(!(SPI1->SR & SPI_I2S_FLAG_RXNE)){ temp++; if (temp > 200){return 0xFF;} } return SPI1->DR; } //野火的源码 u8 SPI1_ReadWriteByte(u8 TxData) { u8 tmp = 0; while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_TXE)){ //等待发送结束 tmp++; if (tmp > 200) return 0; } SPI_I2S_SendData(SPI1, TxData); tmp = 0; while (!SPI_I2S_GetFlagStatus(SPI1, SPI_I2S_FLAG_RXNE)){ //等待接收完成 tmp++; if (tmp > 200) return 0; } return SPI_I2S_ReceiveData(SPI1); }

这是我在野火的例程里拿来用的,为了提高读写速度,我将所有库函数调用都替换成了寄存器操作,看不懂的可以直接用野火的源码

到此,SPI底层配置以及搞定

2、nRF24L01模块介绍

某宝上常见的模块是这样的(图源自网络,侵删) 在这里插入图片描述 一共有8个引出的接口,CLK、MISO、MOSI、CSN是连接SPI接口的 CE:使能nRF模块发送或接收功能 IRQ:当芯片发送数据成功(指发送出去并且接收到自动应答信号)或接收到有效数据的时候会产生一个低电平信号,这个管脚接到stm32的一个外部中断GPIO,就可以及时检查数据

3、nRF模块GPIO配置

在前面的SPI接口配置的时候,只配置了CLK、MISO、MOSI,NSS信号由是软件(使用SSI位)控制,因此还要配置CSN、CE、IRQ管脚,这是针对该模块的专用接口配置,所以我选择放在NRF.c文件里配置

/******************************************************************************** * @brief NRF_SPI的GPIO配置 * @param none * @retval none *******************************************************************************/ void NRF_SPI_Config(void) { GPIO_InitTypeDef GPIO_InitStructure; EXTI_InitTypeDef EXTI_InitStructure; //SPI1基础配置,注意时钟极性CPOL和时钟相位CPHA需根据nRF的时序图确定 //nRF模块最高频率可达10MHz,但是建议不要超过8MHz,这里是9MHz SPI_Config(SPI_BaudRatePrescaler_8); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE); /* 配置nRF的CE和CSN引脚 */ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_2 | GPIO_Pin_0; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; GPIO_Init(GPIOB, &GPIO_InitStructure); /*配置nRF的IRQ引脚*/ GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15; GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz; GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU ; //上拉输入,IRQ引脚会产生一个低电平中断 GPIO_Init(GPIOA, &GPIO_InitStructure); /* IRQ EXTI line mode config */ GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource15); EXTI_InitStructure.EXTI_Line = EXTI_Line15; EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt; EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling; //下降沿中断 EXTI_InitStructure.EXTI_LineCmd = ENABLE; EXTI_Init(&EXTI_InitStructure); /* config the NVIC */ NVIC_EXTI_GPIO_Config(); /* 拉高csn引脚,nRF进入空闲状态 */ NRF_CSN_HIGH; }

这里调用了底层SPI的配置【SPI_Config(SPI_BaudRatePrescaler_8);】,并设置了SPI频率,nRF模块最高频率可达10MHz,但是建议不要超过8MHz,这里是9MHz。 NRF_CSN_HIGH是宏定义,放在NRF.h里面,提高可移植性

/********************************************************************** 底层链接 ***********************************************************************/ /******************************CSN CE IRQ接口配置*******************************************/ #define NRF_CSN_HIGH GPIOB->BSRR = GPIO_Pin_2 #define NRF_CSN_LOW GPIOB->BRR = GPIO_Pin_2 #define NRF_CE_HIGH GPIOB->BSRR = GPIO_Pin_0 #define NRF_CE_LOW GPIOB->BRR = GPIO_Pin_0 #define NRF_CE_HeartBeart NRF_CE_HIGH;delay_us(15);NRF_CE_LOW //产生一个15us的脉冲,使nRF发送数据,发送单个数据包时可以使用 #define NRF_Read_IRQ (GPIOA->IDR & GPIO_Pin_15) //中断引脚,轮询时会用到,这里用外部中断方式 /********************************链接SPI读写接口******************************************/ #define SPI_NRF_RW(data) SPI1_ReadWriteByte(data)

在这里提供了所有nRF模块的GPIO定义,以及SPI接口,通过简单的修改就可以更换,方便移植 由于IRQ是采用外部中断的方式,所以还要配置NVIC

/******************************************************************************* * @brief GPIO外部中断NVIC配置,与使用的GPIO保持一致 * @param none * @retval none ******************************************************************************/ static void NVIC_EXTI_GPIO_Config(void) { NVIC_InitTypeDef NVIC_InitStructure; /* Configure one bit for preemption priority */ NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1); /* 配置中断源 */ NVIC_InitStructure.NVIC_IRQChannel = EXTI15_10_IRQn; NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; NVIC_InitStructure.NVIC_IRQChannelSubPriority = 2; NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; NVIC_Init(&NVIC_InitStructure); }

到此,nRF的GPIO以及全部配置完成,可以对模块进行读写操作了

4、nRF读写API

要想和nRF模块进行正常的通讯,首先要提供读写四个API 1、在寄存器里读出1byte数据 2、在寄存器里读出多byte数据 3、往寄存器里写入1byte数据 4、往寄存器里写入多byte数据

//头文件宏定义 //----------------------------------------------------------- //nRF寄存器读写 调用以下API均会刷新status的值 u8 NRF_SPI_ReadReg(u8 reg); u8 NRF_SPI_ReadBuf(u8 reg,u8 *buf,u8 len); u8 NRF_SPI_WriteReg(u8 reg,u8 dat); u8 NRF_SPI_WriteBuf(u8 reg ,u8 *buf,u8 len); //----------------------------------------------------------- 读寄存器 /******************************************************************************** * @brief 用于从NRF特定的寄存器读出1byte数据 * @param * @arg reg : NRF的指令+寄存器地址 * @retval 寄存器中的数据 *******************************************************************************/ u8 NRF_SPI_ReadReg(u8 reg) { u8 reg_val; NRF_CSN_LOW; /*置低CSN,使能SPI传输*/ status = SPI_NRF_RW(reg); /*发送寄存器号,顺便获得status值*/ reg_val = SPI_NRF_RW(NOP); /*读取寄存器的值*/ NRF_CSN_HIGH; /*CSN拉高,完成*/ return reg_val; } /******************************************************************************** * @brief 用于向NRF的寄存器中写入(len)byte数据 * @param * @arg reg : NRF的命令+寄存器地址 * @arg buf :用于存储将被读出的寄存器数据的数组,外部定义 * @arg len : buf的数据长度 * @retval NRF的status寄存器的状态 *******************************************************************************/ u8 NRF_SPI_ReadBuf(u8 reg,u8 *buf,u8 len) { u8 cnt; NRF_CSN_LOW; /*置低CSN,使能SPI传输*/ status = SPI_NRF_RW(reg); /*发送寄存器号,顺便获得status值*/ /*读取缓冲区数据*/ for(cnt = 0; cnt NRF_CSN_LOW; /*置低CSN,使能SPI传输*/ status = SPI_NRF_RW(reg); /*发送寄存器号,顺便获得status值*/ SPI_NRF_RW(data); /*向寄存器写入数据*/ NRF_CSN_HIGH; /*CSN拉高,完成*/ return status; /*返回STATUS寄存器的值*/ } /******************************************************************************** * @brief 用于向NRF的寄存器中写入(len)byte数据 * @param * @arg reg : NRF的命令+寄存器地址 * @arg buf :存储了将要写入写寄存器数据的数组,外部定义 * @arg len : buf的数据长度 * @retval NRF的status寄存器的状态 *******************************************************************************/ u8 NRF_SPI_WriteBuf(u8 reg ,u8 *buf,u8 len) { u8 byte_cnt; NRF_CSN_LOW; /*置低CSN,使能SPI传输*/ status = SPI_NRF_RW(reg); /*发送寄存器号,顺便获得status值*/ /*向缓冲区写入数据*/ for(byte_cnt = 0; byte_cnt u8 buf[5]={0xC2,0xC2,0xC2,0xC2,0xC2}; u8 buf1[5]; u8 i; NRF_CE_LOW; /*写入5个字节的地址. */ NRF_SPI_WriteBuf(W_REGISTER+TX_ADDR,buf,5); /*读出写入的地址 */ NRF_SPI_ReadBuf(R_REGISTER+TX_ADDR,buf1,5); NRF_CE_HIGH; /*比较*/ for(i=0;ireturn ERROR ;}//MCU与NRF不正常连接 } return SUCCESS ; //MCU与NRF成功连接 }

这里面的【W_REGISTER = 0x00】【R_REGISTER = 0x20】是nRF的操作指令 【TX_ADDR = 0x10】是nRF的寄存器地址,关于操作指令和寄存器再后面会讲到,这里可以替换进去测试能否进行正常的通讯。 除了SPI配置的那部分函数没有完整的给出来以外,以上所有的程序都是完整可用的,如果不能,就要查看自己的SPI接口和nRF接口配置有没有问题。

5、nRF配置

前面讲到SPI和nRF接口的配置,以及提供了4个读写API接口,由于都是比较简单的常规操作,我没有详细地讲解每行代码,接下来我会结合NRF数据手册对NRF配置函数进行详细的解释

另外提一点,网上的资料里有中文的数据手册,但是对比官方原版的英文数据手册可以看见,中文版的翻译不齐全,漏掉好多内容,接下来讲到的东西以原版数据手册为准

NRF寄存器

这里我先贴出完整的头文件,再详细讲解

/** ****************************************************************************** * @file NRF.h * @author Elliot-C * @version V1.0 * @date * @brief nrf24l01+ master-slaver bsp ****************************************************************************** * @attention * * * * ****************************************************************************** */ #ifndef __NRF_H #define __NRF_H #include "stm32f10x.h" #include "bsp_conf.h" /********************************************************************** 底层链接 ***********************************************************************/ /******************************CSN CE IRQ接口配置*******************************************/ #define NRF_CSN_HIGH GPIOB->BSRR = GPIO_Pin_2 #define NRF_CSN_LOW GPIOB->BRR = GPIO_Pin_2 #define NRF_CE_HIGH GPIOB->BSRR = GPIO_Pin_0 #define NRF_CE_LOW GPIOB->BRR = GPIO_Pin_0 #define NRF_CE_HeartBeart NRF_CE_HIGH;delay_us(15);NRF_CE_LOW //产生一个15us的脉冲,使nRF发送数据,发送单个数据包时可以使用 #define NRF_Read_IRQ (GPIOA->IDR & GPIO_Pin_15) //中断引脚,轮询时会用到,这里用外部中断方式 /********************************链接SPI读写接口******************************************/ #define SPI_NRF_RW(data) SPI1_ReadWriteByte(data) /********************************************************************** 宏定义 ***********************************************************************/ //==========================NRF24L01============================================ #define RX_MULIT_CHANNEL 1 //【0:单通道(默认通道0)】【1:多通道(6通道全开)】 #define DYNAMIC_PLOAD_LENGTH 1 //【0:固定数据长度】【1:动态数据长度】 #define STATIC_PLOAD_LENGTH 5 //设置数据长度(固定数据长度下使用) #define ADDRESS_WIDTH 5 //通道地址宽度 //#define TX_ADR_WIDTH 5 //发送端的接收通道地址宽度 //#define RX_ADR_WIDTH 5 //接收端的接收通道地址宽度 //#define TX_PLOAD_WIDTH 5 //要发送的有效数据长度(固定长度) //#define RX_PLOAD_WIDTH 5 //要接收的有效数据长度(固定长度),这个决定RX端FIFO达到多少数据量后触发中断 //------------------------RX通道地址【低字节在前!!!】----------------------------------------------------------------------- extern u8 RX_ADDRESS_0[ADDRESS_WIDTH]; extern u8 RX_ADDRESS_1[ADDRESS_WIDTH]; extern u8 RX_ADDRESS_2[ADDRESS_WIDTH]; extern u8 RX_ADDRESS_3[ADDRESS_WIDTH]; extern u8 RX_ADDRESS_4[ADDRESS_WIDTH]; extern u8 RX_ADDRESS_5[ADDRESS_WIDTH]; //=========================NRF24L01寄存器指令=================================== #define R_REGISTER 0x00 // 读寄存器指令 #define W_REGISTER 0x20 // 写寄存器指令 #define RD_RX_PLOAD 0x61 // 读取接收数据指令(PTX.PRX) #define WR_TX_PLOAD 0xA0 // 写待发数据指令(PTX) #define FLUSH_TX 0xE1 // 清空TX_FIFO内的数据指令(PTX.PRX) #define FLUSH_RX 0xE2 // 清空RX_FIFO内的数据指令(PTX.PRX) #define REUSE_TX_PL 0xE3 // 定义重复装载数据指令(PTX) #define R_RX_PL_WID 0x60 // 接收到的有效数据宽度 //#define W_ACK_PAYLOAD 0x?? // 没用到 //#define W_TX_PAYLOAD_NO_ACK 0xA0 // 没用到 #define NOP 0xFF // 保留 //========================SPI(nRF24L01)寄存器地址=============================== #define CONFIG 0x00 // 配置收发状态,CRC校验模式以及收发状态响应方式 #define EN_AA 0x01 // 自动应答功能设置 #define EN_RXADDR 0x02 // 可用信道设置 #define SETUP_AW 0x03 // 收发地址宽度设置 #define SETUP_RETR 0x04 // 自动重发功能设置 #define RF_CH 0x05 // 工作频率设置 #define RF_SETUP 0x06 // 发射速率、功耗功能设置 #define STATUS 0x07 // 状态寄存器 #define OBSERVE_TX 0x08 // 发送监测功能 #define CD 0x09 // 地址检测 #define RX_ADDR_P0 0x0A // 频道0接收数据地址 #define RX_ADDR_P1 0x0B // 频道1接收数据地址 #define RX_ADDR_P2 0x0C // 频道2接收数据地址 #define RX_ADDR_P3 0x0D // 频道3接收数据地址 #define RX_ADDR_P4 0x0E // 频道4接收数据地址 #define RX_ADDR_P5 0x0F // 频道5接收数据地址 #define TX_ADDR 0x10 // 发送地址寄存器 #define RX_PW_P0 0x11 // 接收频道0接收数据长度 #define RX_PW_P1 0x12 // 接收频道0接收数据长度 #define RX_PW_P2 0x13 // 接收频道0接收数据长度 #define RX_PW_P3 0x14 // 接收频道0接收数据长度 #define RX_PW_P4 0x15 // 接收频道0接收数据长度 #define RX_PW_P5 0x16 // 接收频道0接收数据长度 #define FIFO_STATUS 0x17 // FIFO栈入栈出状态寄存器 #define DYNPD 0x1C // 动态有效数据长度寄存器 #define FEATURE 0x1D // 与动态有效数据长度相关 //=============================RF24l01状态===================================== //CONFIG寄存器 #define PWR_UP 0x02 //Power Up #define PWR_DOWN 0x00 //Power Down #define PRIM_RX 0x01 //PRX #define PRIM_TX 0x00 //PTX //发生中断时,根据STATUS寄存器中的值来判断是哪个中断源触发了IRQ中断 #define IRQ_STATUS 0x70 //提取中断标志位 #define IRQ_CLEAR 0x7E //清除中断标志位 #define RX_DR 0x40 //数据接收完成中断 #define TX_DS 0x20 //数据发送完成中断 #define MAX_RT 0x10 //数据包重发次数超过设定值中断 //nRF配置 void NRF_SPI_Config(void); //nRF的SPI及CE CSN IRQ接口配置 void NRF_Config(void); //nRF初始化 u8 NRF_Check(void); //检查MCU和nRF的连接 //----------------------------------------------------------- //nRF寄存器读写 调用以下API均会刷新status的值 u8 NRF_SPI_ReadReg(u8 reg); u8 NRF_SPI_ReadBuf(u8 reg,u8 *buf,u8 len); u8 NRF_SPI_WriteReg(u8 reg,u8 dat); u8 NRF_SPI_WriteBuf(u8 reg ,u8 *buf,u8 len); //----------------------------------------------------------- //----------------------------------------------------------- //先调用【NRF_CE_LOW;】拉低进入待机模式或掉电模式 //再调用以下API void NRF_RX_Mode(void); //配置成PRX void NRF_TX_Mode(void); //配置成PTX //----------------------------------------------------------- //nRF_API void NRF_ReceivePacket(void); void NRF_SendPacket(u8 *RX_ADDRESS_X,u8 *TX_BUFF); //发送固定长度有效数据包 void NRF_SendPacket_DPL(u8 *RX_ADDRESS_X,u8 * TX_BUFF, u32 length); //发送动态长度有效数据包 #endif /* __NRF_H */

对于

#include "bsp_conf.h"

这是包含了我工程的所有BSP头文件,但这里只用到了【delay.h】你们可用直接替换成自己的延时文件

底层链接和SPI链接前面讲过了,不再累述

通道地址是在多通道模式下发送数据时外部会调用到的,这里设置为extern,后面会讲解

接下来就是NRF的操作指令,对应的值都是可以在数据手册里第51页8.3.1章节里得到 在这里插入图片描述 【PTX:主发射模式】【PRX:主接收模式】 括号里面的表示该指令在那种模式下会用到 操作指令分为两种【只需操作指令】【操作指令+地址信息】 在前面的【void NRF_Check(void)】函数里就调用了一句

NRF_SPI_WriteBuf(W_REGISTER+TX_ADDR,buf,5);

这就是第二种操作指令,写指令【W_REGISTER】+寄存器地址【TX_ADDR】

length = NRF_SPI_ReadReg(R_RX_PL_WID);

上面这类型就是第一种操作指令,只需要写入指令【R_RX_PL_WID】就能得到数据

代码接下来的就是寄存器地址了,同样可以在数据手册9.1章节可以查到

常用寄存器功能 CONFIG 芯片的三种中断 【使能/失能】 CRC校验 【使能/失能】 CRC位数 【1byte/2byte】 芯片进入 【掉电模式/上电模式】 芯片进入 【发射模式/接收模式】 EN_AA 【使能/失能】通道0~5的自动应答功能 EN_RXADDR 【使能/失能】通道0~5接收通道,使其在PRX模式下能够接收数据 SETUP_AW 设置地址宽度,常用的是5byte长度,也就是前面见到的 u8 RX_ADDRESS_0[ADDRESS_WIDTH]; 这些地址数组的长度【ADDRESS_WIDTH】 SETUP_RETR 设置超时自动重发的时间和重发次数,具体看数据手册解释 RF_CH 设置使用的通信频段 RF_SETUP 设置射频通信速率和功率等 STATUS 这是前面提到过的一个很重要的寄存器 bit 说明 7 保留,必须为0 6 RX_DR 接收标志位,当接收到有效数据时置1 5 TX_DS 发送标志位,当发送成功时置1 4 MAX_RT 超时标志位,当重发超过设定次数时置1 以上三位任何一种情况都会触发IRQ 3:1 RX_P_NO 可以判断接收到的数据通道是来自哪个通道的 000~101,对应的通道 0 TX_FULL 判断TX_FIFO是否装满 注意 当时间成功发送出去,TX_FIFO会自动清空 在发送超时的情况下,TX_FIFO不会自动清空,当TX_FIFO填满,就不用能再发送数据给TX_FIFO了,必须清空 RX_ADDR_P0 RX_ADDR_P1 RX_ADDR_P2 RX_ADDR_P3 RX_ADDR_P4 RX_ADDR_P5 这六个寄存器是用来【存放】接收通道地址的,也就是前面提到的六个地址 //------------------------RX通道地址【低字节在前!!!】--------------------------------- u8 RX_ADDRESS_0[ADDRESS_WIDTH] = {0x00,0x34,0x22,0x1D,0x10}; //NRF的RX0通道的地址 u8 RX_ADDRESS_1[ADDRESS_WIDTH] = {0x01,0x34,0x22,0x1D,0x10}; //NRF的RX1通道的地址 u8 RX_ADDRESS_2[ADDRESS_WIDTH] = {0x02,0x34,0x22,0x1D,0x10}; //NRF的RX2通道的地址 u8 RX_ADDRESS_3[ADDRESS_WIDTH] = {0x03,0x34,0x22,0x1D,0x10}; //NRF的RX3通道的地址 u8 RX_ADDRESS_4[ADDRESS_WIDTH] = {0x04,0x34,0x22,0x1D,0x10}; //NRF的RX4通道的地址 u8 RX_ADDRESS_5[ADDRESS_WIDTH] = {0x05,0x34,0x22,0x1D,0x10}; //NRF的RX5通道的地址 在数据手册7.6章节里说明 通道0和通道1的值是任意的,但是不能相同 通道2~5和通道1共用高4位,最低1位必须不同,不然是接收不到数据的 而且是低字节在前!!!本人一开始设置的地址是高字节在在前,导致通道0和通道1都能通信,一切换到通道2~5就没有任何反应了 TX_ADDR 用来存放发送通道地址 RX_PW_P0 RX_PW_P1 RX_PW_P2 RX_PW_P3 RX_PW_P4 RX_PW_P5 这是配置每个通道的一次接收有效数据长度,最大值为32byte 当该通道接收到的数据等于设定的值,就会触发IRQ中断 FIFO_STATUS 存放TX_FIFO和RX_FIFO相关状态的寄存器,详见数据手册 DYNPD 【使能/失能】各个通道的动态数据接收功能 FEATURE 配合动态数据长度功能使用,开启动态数据长度自动应答功能等,详见数据手册

上面就是对常用的寄存器的功能解释,没有讲到的寄存器可以看数据手册

只要在正确时刻正确配置好以上的寄存器,就能顺利使用NRF模块的所有通道、动态数据长度等功能,

nRF初始化

这里正式开始对nRF模块进行初始化配置 首先要数据手册6.1.1章节对于芯片的运行流程解释,不清晰的话还请看数据手册里的,这个流程图告诉了我们应该如何配置才能正常使用芯片 在这里插入图片描述 首先是芯片上电,1m后处于默认掉电模式,也可以理解为低功耗模式,当PWR_UP = 1的时候,进入standby-1模式,也就是待机模式

在待机模式下,若PRIM_RX = 1且CE = 1,会进入接收模式,接收数据

在待机模式下,若PRIM_RX = 0且CE = 1且TX_FIFO非空,会进入发射模式,发送数据

对这张图更详细的解释可以看其他博主的文章【NRF】

下面就可以根据这个流程图来编写配置函数了

//拉低CE,注意:需要将CE拉低,使其进入待机或者掉电模式才能读/写nRF寄存器 NRF_CE_LOW; //初始化NRF(看数据手册) NRF_SPI_WriteReg(W_REGISTER | SETUP_AW, 0x03); //配置通信地址的长度,默认值时0x03,即地址长度为5字节 NRF_SPI_WriteReg(W_REGISTER | SETUP_RETR, 0x15); //自动重发延迟为500+86us,重发次数5次 NRF_SPI_WriteReg(W_REGISTER | RF_SETUP, 0x07); //设置发射速率为1MHZ,发射功率为最大值0dB NRF_SPI_WriteReg(W_REGISTER | RF_CH, 30); //设置通道通信频率,工作通道频率可由以下公式计算得出:Fo=(2400+RF_CH)MHz.并且射频收发器工作的频率范围从2.400-2.525GHz

▲先拉低CE,在芯片处于待机或掉电模式,才能对NRF芯片进行寄存器操作 这里配置了通信地址宽度,自动重发,发射速率以及通信频段,具体看注释

//---------------------------PRX:单通道配置------------------------------------------------------------------------ NRF_SPI_WriteBuf(W_REGISTER | RX_ADDR_P0, RX_ADDRESS_0, ADDRESS_WIDTH); //配置本机接收通道0的接收数据的地址 NRF_SPI_WriteReg(W_REGISTER | EN_AA, 0x01); //接收数据后,只允许频道0自动应答 NRF_SPI_WriteReg(W_REGISTER | EN_RXADDR, 0x01); //只允许频道0接收数据 NRF_SPI_WriteReg(W_REGISTER | RX_PW_P0, STATIC_PLOAD_LENGTH);

▲设置PRX模型单通道(默认通道0)的接收地址宽度,自动应答、使能通道0接收功能、接收数据长度(当接收到这么多个数据就会触发IRQ中断)

NRF_SPI_WriteReg(W_REGISTER | CONFIG, 0x0C | PWR_UP | PRIM_RX); //默认处于接收模式

▲然后就是CONFIG寄存器的配置了,该寄存器的最低两位正是前面提到的【PWR_UP】和【PRIM_RX】,而在头文件里我没有解释的一段

//CONFIG寄存器 #define PWR_UP 0x02 //Power Up #define PWR_DOWN 0x00 //Power Down #define PRIM_RX 0x01 //PRX #define PRIM_TX 0x00 //PTX

这正是给CONFIG寄存器配置使用的,配置成【待机/掉电】【PTX/PRX】组合模式,这样提高了代码的阅读性,当然也可以选择直接赋值,由于这是主从一体的程序,初始化默认配置从接收模式,只是在需要发送的时候才进入发送模式

NRF_SPI_WriteBuf(W_REGISTER | TX_ADDR, RX_ADDRESS_0, ADDRESS_WIDTH);//发送的数据包中被一块打包进去的接收端NRF的接收通道的地址

▲接下来配置PTX模式下的发送地址 看到这里估计大家会有疑问,为什么发送地址写的是接收地址,我觉得这也是很多教程里面存在的一个误导 细心的朋友可能会发现在我贴出来的头文件【NRF.h】里有这么一段

//==========================NRF24L01============================================ #define RX_MULIT_CHANNEL 1 //【0:单通道(默认通道0)】【1:多通道(6通道全开)】 #define DYNAMIC_PLOAD_LENGTH 1 //【0:固定数据长度】【1:动态数据长度】 #define STATIC_PLOAD_LENGTH 5 //设置数据长度(固定数据长度下使用) #define ADDRESS_WIDTH 5 //通道地址宽度 //#define TX_ADR_WIDTH 5 //发送端的接收通道地址宽度 //#define RX_ADR_WIDTH 5 //接收端的接收通道地址宽度 //#define TX_PLOAD_WIDTH 5 //要发送的有效数据长度(固定长度) //#define RX_PLOAD_WIDTH 5 //要接收的有效数据长度(固定长度),这个决定RX端FIFO达到多少数据量后触发中断

这里有四句注释掉的宏定义,在很多教程和例程里都有这么定义,而且在那些例程里除了发送地址【RX_ADDRESS_0[ADDRESS_WIDTH];】之外还有一个接收地址【RX_ADDRESS[ADDRESS_WIDTH];】这两者的赋值是一样的,一个发送地址,一个接收地址 所以那些例程里是这么写的

NRF_SPI_WriteBuf(W_REGISTER | TX_ADDR, TX_ADDRESS, ADDRESS_WIDTH);

但是在我深入了解NRF和编程的途中了解到,其实是不存在所谓的发送地址 【RX_ADDRESS[ADDRESS_WIDTH];】

要知道,两个模块要通信,首先要处于相同的频段、相同的通信速率,也就是一开始那些配置

接着,还要在PTX端发射数据前,先在【TX_ADDR寄存器】填入一个地址,这个地址是会附带在数据前面广播出去,如果PRX端接收到这个地址,并且和自己的【RX_ADDR_Px(x = 0~5)寄存器】里面任意一个地址对应上了,那么PRX端会接收接下来的数据,否则等待下一次数据的到来。

这就说明了【TX_ADDR寄存器】里存放的地址,是PTX端这一次发送希望PRX端用哪一个通道接收,因此填入的是接收端的一个通道地址【RX_ADDRESS_x[ADDRESS_WIDTH]】

NRF_CE_HIGH; delay_ms(2);

▲最后拉高CE,告知NRF芯片,已经完成对你的寄存器操作了

▼上面的代码整合起来及时NRF模块的初始化函数了

void NRF_Config(void) { NRF_SPI_Config(); //拉低CE,注意:需要将CE拉低,使其进入待机或者掉电模式才能读/写nRF寄存器 NRF_CE_LOW; //初始化NRF(看数据手册) NRF_SPI_WriteReg(W_REGISTER | SETUP_AW, 0x03); //配置通信地址的长度,默认值时0x03,即地址长度为5字节 NRF_SPI_WriteReg(W_REGISTER | SETUP_RETR, 0x15); //自动重发延迟为500+86us,重发次数5次 NRF_SPI_WriteReg(W_REGISTER | RF_SETUP, 0x07); //设置发射速率为1MHZ,发射功率为最大值0dB NRF_SPI_WriteReg(W_REGISTER | RF_CH, 30); //设置通道通信频率,工作通道频率可由以下公式计算得出:Fo=(2400+RF_CH)MHz.并且射频收发器工作的频率范围从2.400-2.525GHz //---------------------------PRX:单通道配置------------------------------------------------------------------------ NRF_SPI_WriteBuf(W_REGISTER | RX_ADDR_P0, RX_ADDRESS_0, ADDRESS_WIDTH); //配置本机接收通道0的接收数据的地址 NRF_SPI_WriteReg(W_REGISTER | EN_AA, 0x01); //接收数据后,只允许频道0自动应答 NRF_SPI_WriteReg(W_REGISTER | EN_RXADDR, 0x01); //只允许频道0接收数据 //---------------------------PRX:数据接收长度配置---------------------------------------------------------------------------- NRF_SPI_WriteReg(W_REGISTER | RX_PW_P0, STATIC_PLOAD_LENGTH); //---------------------------PTX:单通道配置------------------------------------------------------------------------ //发送通道地址,由于是单通道,在这里配置一次就可以了(默认通道0) NRF_SPI_WriteBuf(W_REGISTER | TX_ADDR, RX_ADDRESS_0, ADDRESS_WIDTH); //发送的数据包中被一块打包进去的接收端NRF的接收通道的地址 NRF_SPI_WriteReg(W_REGISTER | CONFIG, 0x0C | PWR_UP | PRIM_RX); //默认处于接收模式 NRF_CE_HIGH; delay_ms(2); } PTX/PRX(发送模式/接收模式)

这是一份主从一体的代码,NRF模块会一直运行在PRX模式下,等待接收数据,只有主动切换到发送模式才能发送数据,发射完成后就切换回接收模式,等待数据 因此接下来是发送和接收模式切换函数

/******************************************************************************** * @brief 配置发送模式(PTX)并进入待机1模式(standby-1),先调用【NRF_CE_LOW】再调用此API 往TX_FIFO写好数据后调用再【NRF_CE_HIGH】,nRF自动延迟130us后会进入发送模式,发送数据 CE为高电平、TX_FIFO为空,会进入待机2模式(standby-2) * @param none * @retval none *******************************************************************************/ void NRF_TX_Mode(void) { NRF_MODE = 0; //模式转换标志位 NRF_SPI_WriteReg(W_REGISTER | STATUS, IRQ_CLEAR); //清除所有中断标志,防止一进入发射模式就触发中断 NRF_SPI_WriteReg(W_REGISTER | CONFIG, 0x0C | PWR_UP | PRIM_TX); //将NRF配置成待机发射模式(PTX模式) delay_ms(2); //nRF延时1.5ms后进入待机1模式(standby-1) // NRF_CE_HIGH; //CE拉高后,nRF自动延迟130us后,进入发送模式 // delay_us(150); //PTX模式下这个延时没用 } /******************************************************************************** * @brief 配置接收模式(PRX)并进入待机1模式(standby-1),先调用【NRF_CE_LOW】再调用此API 在PRX下只有CE保存高电平的期间可以接收数据 * @param none * @retval none *******************************************************************************/ void NRF_RX_Mode(void) { NRF_MODE = 1; //模式转换标志位 // NRF_CE_LOW; //拉低CE,进入待机模式或掉电模式 NRF_SPI_WriteReg(W_REGISTER | STATUS, IRQ_CLEAR); //清除所有中断标志,防止一进入接收模式就触发中断 NRF_SPI_WriteReg(W_REGISTER | CONFIG, 0x0C | PWR_UP | PRIM_RX); //将NRF配置为待机接收模式(PRX) delay_ms(2); //nRF延时1.5ms后进入待机1模式(standby-1) // NRF_CE_HIGH; //CE拉高后,nRF自动延迟130us后,进入接收模式 // delay_us(150); //PRX模式下该延时也可以做其他的处理 }

▲注释都很详细,这里我为了精简代码,将CE的置高置低都注释了,采用外部操作,当然不放心的话也是可以取消注释的,这样做是考虑到调用这两个函数之后还会对其他寄存器进行操作,因此CE先拉高,等所有操作完成才拉低,这在后面的中断函数会具体讲。 这两个函数开头都有对NRF_MODE进行赋值,这是一个u8类型的全局变量,用来判断目前处于那种模式

u8 NRF_MODE = 1; //模式标志位【0:PTX 1:PRX】 中断服务处理函数

当芯片接收到数据、发送数据成功、发送失败,都会在IRQ引脚产生一个低电平,而我们对连接IRQ引脚的GPIO设置成了外部中断触发(下降沿),每当有信息来的时候就可以触发中断服务程序

/******************************************************************************** * @brief IQR中断服务处理函数 * @param none * @retval none *******************************************************************************/ void EXTI15_10_IRQHandler(void) { NRF_CE_LOW; //拉低CE,以便读取NRF中STATUS中的数据 status = NRF_SPI_ReadReg(R_REGISTER | STATUS); //读取STATUS的值 status &= IRQ_STATUS; //提取IRQ标志位 switch(status){ case MAX_RT : NRF_SPI_WriteReg(FLUSH_TX, 0xff); //数据发出去没有回应 printf("NO ACK\r\n"); break; case TX_DS : printf("Send ok\r\n"); //发送数据完成 break; case RX_DR : printf("receive\r\n");NRF_ReceivePacket(); //接收到数据 break; case TX_DS | RX_DR : printf("Auto-ACK-PAYLOAD\r\n"); //发送完成并且接收到的Auto-ACK带有【有效数据】 break; } NRF_SPI_WriteReg(W_REGISTER | STATUS,IRQ_CLEAR);//清除STATUS中断标志位 if(!NRF_MODE){ NRF_RX_Mode(); } NRF_CE_HIGH; EXTI->PR = EXTI_Line15; //清除GPIO的中断标志位 }

还记得在nRF读写API那章节里提到了一个全局变量status吗

u8 status; //接收从STATUS寄存器中返回的值,只要对nRF寄存器进行读写操作,都会刷新该值

在中断处理服务函数里面就有用到这个变量了,是用来存放当前【STATUS寄存器】的值,通过对该值进行分析就可以知道中断的类型,这里用到的宏定义在【NRF.h】里出现过

//发生中断时,根据STATUS寄存器中的值来判断是哪个中断源触发了IRQ中断 #define IRQ_STATUS 0x70 //提取中断标志位 #define IRQ_CLEAR 0x7E //清除中断标志位 #define RX_DR 0x40 //数据接收完成中断 #define TX_DS 0x20 //数据发送完成中断 #define MAX_RT 0x10 //数据包重发次数超过设定值中断

没错,这和配置【CONFIG寄存器】的方式是一样的,至于为什么是这些值,可以根据数据手册对【STATUS寄存器】的解释来计算出,清除中断标志位是要往7:5bit写入1,没有看错,就是写入1 产生中断的时候7:5bit对应的位会置1,清除也是要写1,而且IRQ在产生低电平后,是一直保持低电平的,直到清除【STATUS寄存器】,才会恢复高电平,所以退出中断服务程序前记得清除寄存器

判断为MAX_RT中断的时候,要及时清空【TX_FIFO】内的数据,这个在寄存器解释那里提过了,否则你测试时如果第一次发送数据没有应答,再发送两次也没有成功就会写满【TX_FIFO】,再也发送不了。

判断为TX_DS中断,只有PTX方有这个中断,说明收发双方都正常运行,PTX方的TX_FIFO会自动清除

判断为RX_DR中断,一般是PRX方会有这个中断,这时要主动读取【RX_FIFO】内的数据,读取后nRF会自动清除数据。如果不读取的话,数据会一直保留,接收到三次数据后【RX_FIFO】会写满,不再接收数据(阻塞),等待数据被读走,只有当【RX_FIFO】有空间,才会再次接收数据,因此这里调用了一个接收函数

我在一开始测试的时候是没有设置接收函数的,测试发送三次成功后,就一直都是发送失败,后来才知道是【RX_FIFO】装满了这个原因

在switch判断里还有种中断TX_DS | RX_DR,这是涉及到自动应答Auto-ACK数据包里有没有携带有效数据的判断;

对于PTX,发送数据后会等待ACK,PRX接收到数据后会触发IRQ中断,同时等待130us后,发送Auto-ACK数据包,如果此时PRX的TX_FIFO中有【有效数据】,那么Auto-ACK数据包会携带【有效数据】一起发送给PTX,这时PTX接收到ACK,判断发送成功,TX_DS置1,同时数据包里带有【有效数据】,RX_DR置1,然后产生IRQ中断,那么就会触发这个判断

对于PRX,当PTX再一次发送新的数据包过来时,如果上一次发给PTX的Auto-ACK数据包会携带【有效数据】那么会就会触发该中断,其中TX_DS置1:说明收到新的数据包,RX_DR置1:说明上一次Auto-ACK的有效数据PTX已经接收到了(因为这次接收到的是新的数据包,PTX没有触发超时重发)

在该博主的文章里对中断机制有更详细的介绍【11. nrf24l01的中断详解】

回到中断服务程序,判断中断类型并处理后,就可以清空【STATUS寄存器】 如果进入中断服务程序前是处于PTX模式【NRF_MODE = 0】那么就进入PRX模式,就是说nRF芯片会一直处于接收模式,只有需要发送的时候才会进入接收模式,发送完又进入接收模式

到此,中断服务程序已经结束,退出前记得拉高CE和清除IO口的中断标志就行了

nRF配置总结

这一章节给出了几个函数

void NRF_Config(void); //nRF初始化 void NRF_RX_Mode(void); //配置成PRX void NRF_TX_Mode(void); //配置成PTX void EXTI15_10_IRQHandler(void) //中断服务

但这只是基础的配置函数以及对于IRQ的处理函数,下一章节才开始正式介绍的发送接收函数编写

6、nRF发送数据(单通道) /******************************************************************************** * @brief PTX模式下发送一个固定长度的数据包 * @param RX_ADDRESS_X 发送到的通道地址,在单通道模式下该值无效,默认通过通道0发送 * @param TX_BUFF 待发送的数据缓冲区 * @retval none *******************************************************************************/ void NRF_SendPacket(u8 *TX_BUFF) { NRF_CE_LOW; //拉低CE,进入待机模式或掉电模式 NRF_TX_Mode(); //配置成PTX待机模式 //---------------------------单通道发送地址------------------------------------------------------------ //在NRF_Config里设置一次就行 //将数据写入TX端的FIFO中,写入的个数与TX_PLOAD_WIDTH设置值相同 NRF_SPI_WriteBuf(WR_TX_PLOAD, TX_BUFF, STATIC_PLOAD_LENGTH); NRF_CE_HIGH; //拉高CE,准备发射TX端FIFO中的数据 //CE拉高后,nRF自动延迟130us后,发送数据 }

没错,对于单通道来说,发送函数就这么简单 拉低CE-----调用指令【WR_TX_PLOAD】,把TX_BUFF数组的数据发送到nRF芯片的【TX_FIFO】,这是发送固定长度【STATIC_PLOAD_LENGTH】的数据-----拉高CE 回顾nRF初始化那章节关于芯片运行流程的图,可以知道当CE拉高,处于发送模式,TX_FIFO非空,那么在130us后,芯片会进入发送模式将数据发送出去

7、nRF接收数据(单通道) /******************************************************************************** * @brief PRX模式下从RX_FIFO中接收一个数据包 同时读出通道值 * @param none * @retval none *******************************************************************************/ void NRF_ReceivePacket(void) { u8 i; u32 length; NRF_CE_LOW; length = STATIC_PLOAD_LENGTH; //从RX端的FIFO中读取数据,并存入指定的区域 //注意:读取完FIFO中的数据后,NRF会自动清除其中的数据 NRF_SPI_ReadBuf(RD_RX_PLOAD,RX_FIFO_Buff,STATIC_PLOAD_LENGTH); //读取通道值 RX_pipe = (status & 0x0E) >> 1; //打印数据 printf("pipe%d length:%d\r\n",RX_pipe,length); for(i = 0; i 0x11,0x12,0x13,0x14,0x11,0x12,0x13,0x14}; /** * @brief 主函数 * @param 无 * @retval 无 */ int main(void) { USART1_Config(115200); KEY_Config(); printf("NRF master test\r\n"); NRF_SPI_Config(); if(NRF_Check() == SUCCESS){printf("nRF connected with MCU successful\r\n");} else{printf("error\r\n");} NRF_Config(); delay_ms(100); while(1){ if(Key_flag){ Key_flag = 0; NRF_SendPacket(RX_ADDRESS_1,pack); } } /* add your code here ^_^. */ } 9、nRF多通道配置

另开新帖



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有