话不多说,直接先上nRF24L01的中文开发手册,https://pan.baidu.com/s/1exzhUFWcM6Q4R9JpYWnaYw,有需要者请自行下载。
玩动nRF24L01模块需要一定的对单片机寄存器操作及SPI通信知识储备。先从其硬件开始介绍:
该模块有8个引脚,功能描述如下:
在此不再赘述其引脚功能,在玩转这个模块之前,得准备2到3块stm32f103c8t6开发板(或者其他开发板),一对多通信下,最少得要三个独立的系统。
从nrf24l01中文开发手册中,我们可以知道其工作模式控制为这样:
其中,PWR_UP、PRIM_RX为配置寄存器的其中两个位,CE为引脚CE。而一对多和多对一的通信的双向通信就由这些个位和引脚的快速切换来实现,而手册中也明确如此写着。
我们分析一下一对多发送、多对一发送的实际传输模型,假设三个系统,一个负责中控端(即一对二收发的一方),其余两个负责客户端(即当中控端发出数据后,两个客户端会同时收到,而客户端可不定时异步给中控端发数据)。本人目前只研究到当中控端发送数据时,所有客户端会几乎同时收到数据,我试着只给其一发送,但未能实现效果,其实按理论说是可以单独收发的,但目前效果并不如意。不过,这并不影响中控端对客户端的控制,因为客户端可以根据与自己有用的信息才做处理,而客户端亦能将自身数据异步传给中控端,这就实现了我想要的联动控制效果。而一个中控端最多与六个客户端互相通信,这是手册上有说明的。
在未能领会贯通之前,其通信原理及电路设计原理我们不必过于死磕,我们的重点在于如何做到让这玩意快速为我所用。
首先,我们来看其SPI接口指令相关介绍:
其中指令设置中说到每一条指令的执行都必须通过一次CSN引脚输入电平由高到低的变化,后面的代码中会呈现出来。而这些个指令我们可以用宏定义的方式将它们定义起来以备编写通信程序中使用。如下所示。
#define READ_REG 0x00 //读配置寄存器,低5位为寄存器地址 #define WRITE_REG 0x20 //写配置寄存器,低5位为寄存器地址 #define RD_RX_PLOAD 0x61 //读RX有效数据,1~32字节 #define WR_TX_PLOAD 0xA0 //写TX有效数据,1~32字节 #define FLUSH_TX 0xE1 //清除TX FIFO寄存器.发射模式下用 #define FLUSH_RX 0xE2 //清除RX FIFO寄存器.接收模式下用 #define REUSE_TX_PL 0xE3 //重新使用上一包数据,CE为高,数据包被不断发送. #define NOP 0xFF //空操作,可以用来读状态寄存器 |
至于为什么需要指令去操作,这个问题我不再此详谈,这是SPI通信相关的知识点,有兴趣的道友可以看一看SPI通信的原理。
其次就是看寄存器,下面主要列举本次通信中用到的相关寄存器。
配置寄存器
使能自动应答寄存器
接收地址允许寄存器
设置地址宽度寄存器
自动重发延时
射频通道及射频增益寄存器
状态寄存器
各数据通道接收地址寄存器
发送地址
各数据通道有效数据载荷寄存器
上面的寄存器都是本次实验中需要用到的相关寄存器,其使用方法除上面有说明外,其截图于手册,可自行下载浏览,用时需详细了解才能理解其中的用法。
其三,要了解nRF24L01的工作模式,手册中也详细地叙述着,本人就抽离出增强型ShockBurst模式来探讨一下。在手册中这一章节是在寄存之前的,我之所以放在第三,是想让读者先对寄存器有一个大概的了解,再对工作模式的论述就更有印象了。
上面说到的第一、第二点是关键,至于其中说到的图5即为
此时的TX5可以理解第5号客户端,RX理解为中控端,由于期间涉及到硬件的自动应答,所以在除了客户端发送的40位地址要与中控端接收地址一致外,还要客户端的通道0的接收地址也要与前面的一致。还有,上面提到的第一、第三点,我们后面将会在程序中看到其具体呈现。
另外,还有一些需要注意的细节,本人将在后面的程序中提出。
最后,单片机stm32f103c8t6驱动nRF24L01模块的大概流程及配置步骤说明如下:
1)nRF24L01连接到stm32f103c8t6相应引脚的功能模式配置,包括CE(推完输出)、CSN(推完输出)、IRQ(中断输入)以及SCK、MOSI、MISO引脚的SPI模式配置
2)编写SPI接口函数,包括读、写寄存器操作,读取FIFO和写入FIFO操作
3)模式配置,包括发送模式、接收模式
4)检测应答(用于发送模式,此处发送完成标志检测用到的方式为查询方式,并非中断方式),接收数据(用于接收模式,此处接收完成标志检测亦用到查询方式检测)
5)主函数任务中的应用
下面,本人将用具体程序呈现出上面步骤的具体实现。
1)nRF24L01连接到stm32f103c8t6相应引脚的功能模式配置,包括CE(推完输出)、CSN(推完输出)、IRQ(中断输入)以及SCK、MOSI、MISO引脚的SPI模式配置
/*************************************************************** **SPI1初始化 **用于与NRF24L01通讯 **其中nRF24L01的SCK、MISO、MOSI对应stm32f103c8t6的PA5、PA6、PA7引脚 ****************************************************************/ void SPI1_Init(void) { RCC->APB2ENR|=1<<2; //PORTA时钟使能 RCC->APB2ENR|=1<<12; //SPI1时钟使能 //这里只针对SPI口初始化 GPIOA->CRL&=0X000FFFFF; GPIOA->CRL|=0XBBB00000;//PA5.6.7复用 GPIOA->ODR|=0X7<<5; //PA5.6.7上拉 SPI1->CR1|=0<<10;//全双工模式 SPI1->CR1|=1<<9; //软件nss管理 SPI1->CR1|=1<<8; SPI1->CR1|=1<<2; //SPI主机 SPI1->CR1|=0<<11;//8bit数据格式 SPI1->CR1&=~(1<<1); //空闲模式下SCK为低 SPI1->CR1&=~(1<<0); //数据采样从第1个时间边沿开始 SPI1->CR1|=2<<3; //Fsck=Fcpu/8 SPI1->CR1&=~(1<<7); //MSBfirst SPI1->CR1|=1<<6; //SPI设备使能 SPI1_ReadWriteByte(0xff);//启动传输 } /************************************** **RC522芯片初始化, **其中nRF24L01的CE、CSN、IRQ引脚对应stm32f103c8t6的PA4、PC4、PC5 **************************************/ void init_NRF24L01(void) { SPI1_Init(); //SPI 1初始化 RCC->APB2ENR|=1<<4; //PORTC时钟使能 //PA4 NRF_CE 使能引脚 GPIOA->CRL&=0xFFF0FFFF; //清0 GPIOA->CRL|=0X00030000; //推挽输出 GPIOA->ODR|=1<<4; //输出高电平 //PC4 NRF_CS 片选引脚 GPIOC->CRL&=0xFFF0FFFF; //清0 GPIOC->CRL|=0X00030000; //推挽输出 GPIOC->ODR|=1<<4; //输出高电平 //PC5 NRF_IRQ 中断引脚 GPIOC->CRL&=0xFF0FFFFF; //清0 GPIOC->CRL|=0X00800000; //上下拉输入 GPIOC->ODR &=~(1<<5); //输出高电平 Ex_NVIC_Config(GPIO_C,5,FTIR);//下降沿触发 MY_NVIC_Init(1,1,EXTI9_5_IRQn,1);//抢占优先级1\响应优先级1\通道5\中断分组1 NRF_CE = 0;//先不使能 NRF_CS = 1;//拉高片选 } |
这里面本人虽然配置了IRQ的中断输入 Ex_NVIC_Config(GPIO_C,5,FTIR);//下降沿触发
MY_NVIC_Init(1,1,EXTI9_5_IRQn,1);//抢占优先级1\响应优先级1\通道5\中断分组1
但实际程序中并未使用、后面将继续研究采用中断方式对模块一些动作完成标志的捕捉。所以读者可先自行跳过,有兴趣、有经验者也请不乏分享一下你们对这一方面的经验。
2)编写SPI接口函数,包括读、写寄存器操作,读取FIFO和写入FIFO操作
/************************************************************************* **SPIx 读写一个字节 **TxData:要写入的字节 **返回值:读取到的字节 *************************************************************************/ u8 SPI1_ReadWriteByte(u8 TxData) { u16 retry=0; while((SPI1->SR&1<<1)==0)//等待发送区空 { retry++; if(retry>=0XFFFE)return 0; //超时退出 } SPI1->DR=TxData; //发送一个byte retry=0; while((SPI1->SR&1<<0)==0) //等待接收完一个byte { retry++; if(retry>=0XFFFE)return 0; //超时退出 } return SPI1->DR; //返回收到的数据 } /********************************************************************** SPI读寄存器一字节函数 ***********************************************************************/ u8 NRFReadReg(u8 RegAddr) { u8 BackDate; NRF_CS = 0; //启动时序 SPI1_ReadWriteByte(RegAddr); //写寄存器地址 BackDate=SPI1_ReadWriteByte(0x00); //写入读寄存器指令 NRF_CS = 1; return(BackDate); //返回状态 } /*********************************************************************** SPI写寄存器一字节函数 ************************************************************************/ u8 NRFWriteReg(u8 RegAddr,u8 date) { u8 BackDate; NRF_CS = 0; //启动时序 BackDate = SPI1_ReadWriteByte(RegAddr); //写入地址 SPI1_ReadWriteByte(date); //写入值 NRF_CS = 1; return(BackDate); } /************************************************************************ SPI读取RXFIFO寄存器的值 *************************************************************************/ u8 NRFReadRxDate(u8 RegAddr,u8 *RxDate,u8 DateLen) { //寄存器地址//读取数据存放变量//读取数据长度//用于接收 u8 BackDate,i; NRF_CS = 0; //启动时序 BackDate = SPI1_ReadWriteByte(RegAddr); //写入要读取的寄存器地址 for(i = 0;i < DateLen;i++) //读取数据 { RxDate[i] = SPI1_ReadWriteByte(0); } NRF_CS = 1; return(BackDate); } /************************************************************************ SPI写入TXFIFO寄存器的值 ************************************************************************/ u8 NRFWriteTxDate(u8 RegAddr,u8 *TxDate,u8 DateLen) { //寄存器地址//写入数据存放变量//读取数据长度//用于发送 u8 BackDate,i; NRF_CS = 0; BackDate = SPI1_ReadWriteByte(RegAddr); //写入要写入寄存器的地址 for(i = 0;i < DateLen;i++) //写入数据 { SPI1_ReadWriteByte(*TxDate++); } NRF_CS = 1; return(BackDate); } |
上面都是些SPI经典读写接口函数,网上一搜一大把。但要根据模块的具体读写时序来编写,有些模块的SPI通信接口不全于经典SPI通信一致,所以还需回到手册中说事。
3)模式配置,包括发送模式、接收模式
客户端部分:
u8 TxAddr0[TX_ADR_WIDTH]= {0xE7,0xD3,0xF0,0x35,0x77}; //接收地址 u8 TxAddr1[TX_ADR_WIDTH]= {0xC2,0xC2,0xC2,0xC2,0xC2}; //接收地址 u8 TxAddr2[1] = {0xC3}; //接收地址 u8 TxAddr3[1]= {0xC4}; //接收地址 u8 TxAddr4[1] = {0xC5}; //接收地址 u8 TxAddr5[1]= {0xC6}; //接收地址 u8 RxAddr0[RX_ADR_WIDTH]= {0xE7,0xD3,0xF0,0x35,0x77}; //本地地址0 u8 RxAddr1[RX_ADR_WIDTH]= {0xC2,0xC2,0xC2,0xC2,0xC2}; //本地地址1 u8 RxAddr2[1] ={0xC3}; u8 RxAddr3[1] ={0xC4}; u8 RxAddr4[1] ={0xC5}; u8 RxAddr6[1] ={0xC6}; /************************************************************************* NRF设置为发送模式并发送数据 ***************************************************************************/ void NRFSetTxMode(u8 *TxDate,u8 *TxAddr,u8 ADR_WIDTH) { //发送模式 NRF_CE = 0; NRFWriteTxDate(WRITE_REG+TX_ADDR,TxAddr,ADR_WIDTH); //写寄存器指令+接收地址使能指令+接收地址+地址宽度 NRFWriteTxDate(WRITE_REG+RX_ADDR_P0,TxAddr,ADR_WIDTH); //为了应答接收设备,接收通道0地址和发送地址相同 NRFWriteTxDate(WR_TX_PLOAD,TxDate,TX_ADR_WIDTH); //写入数据 /******相关有关寄存器配置**************/ NRFWriteReg(WRITE_REG+EN_AA,0x01); // 使能接收通道0自动应答 NRFWriteReg(WRITE_REG+EN_RXADDR,0x01); // 使能接收通道0 NRFWriteReg(WRITE_REG+SETUP_RETR,0x0a); //自动重发延时等待250us+86us,自动重发10次 NRFWriteReg(WRITE_REG+RF_CH,0x40); //选择射频通道0x40 NRFWriteReg(WRITE_REG+RF_SETUP,0x07); //数据传输率1Mbps,发射功率0dBm,低噪声放大器增益 NRFWriteReg(WRITE_REG+CONFIG,0x7e); //CRC使能,16位CRC校验,上电 NRF_CE = 1; Delay_us(15); //保持10us秒以上 } /********************************************************************************* NRF设置为接收模式并接收数据 **********************************************************************************/ void NRFSetRXMode(void) //主要接收模式 { NRF_CE=0; NRFWriteTxDate(WRITE_REG+RX_ADDR_P0,RxAddr0,TX_ADR_WIDTH); //接收设备接收通道0使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P1,RxAddr0,TX_ADR_WIDTH); //接收设备接收通道1使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P2,RxAddr2,1); //接收设备接收通道1使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P3,RxAddr3,1); NRFWriteReg(WRITE_REG+EN_AA,0x0f); //使能接收通道0自动应答 NRFWriteReg(WRITE_REG+EN_RXADDR,0x0f); //使能接收通道0 NRFWriteReg(WRITE_REG+RF_CH,0xaa); //选择射频通道0xaa NRFWriteReg(WRITE_REG+RX_PW_P0,TX_PLOAD_WIDTH); //接收通道0选择和发送通道相同有效数据宽度 NRFWriteReg(WRITE_REG+RX_PW_P1,RX_PLOAD_WIDTH); //接收通道1选择和发送通道相同有效数据宽度 NRFWriteReg(WRITE_REG+RF_SETUP,0x07); //数据传输率1Mbps,发射功率0dBm,低噪声放大器增益*/ NRFWriteReg(WRITE_REG+CONFIG,0x7f); //CRC使能,16位CRC校验,上电,接收模式 NRF_CE = 1; Delay_us(15); //保持10us秒以上 } |
中控端部分:
u8 TxAddr0[TX_ADR_WIDTH]= {0xE7,0xD3,0xF0,0x35,0x77}; //接收地址 u8 TxAddr1[TX_ADR_WIDTH]= {0xC2,0xC2,0xC2,0xC2,0xC2}; //接收地址 u8 TxAddr2[1] = {0xC3}; //接收地址 u8 TxAddr3[1]= {0xC4}; //接收地址 u8 TxAddr4[1] = {0xC5}; //接收地址 u8 TxAddr5[1]= {0xC6}; //接收地址 u8 RxAddr0[RX_ADR_WIDTH]= {0xE7,0xD3,0xF0,0x35,0x77}; //本地地址0 u8 RxAddr1[RX_ADR_WIDTH]= {0xC2,0xC2,0xC2,0xC2,0xC2}; //本地地址1 u8 RxAddr2[1] ={0xC3}; u8 RxAddr3[1] ={0xC4}; u8 RxAddr4[1] ={0xC5}; u8 RxAddr6[1] ={0xC6}; /************************************************************************* NRF设置为发送模式并发送数据 ***************************************************************************/ void NRFSetTxMode(u8 *TxDate,u8 *TxAddr,u8 ADR_WIDTH) { //发送模式 NRF_CE = 0; NRFWriteTxDate(WRITE_REG+TX_ADDR,TxAddr,ADR_WIDTH); //写寄存器指令+接收地址使能指令+接收地址+地址宽度 NRFWriteTxDate(WRITE_REG+RX_ADDR_P0,TxAddr,ADR_WIDTH); //为了应答接收设备,接收通道0地址和发送地址相同 NRFWriteTxDate(WR_TX_PLOAD,TxDate,TX_PLOAD_WIDTH); //写入数据 /******相关有关寄存器配置**************/ NRFWriteReg(WRITE_REG+EN_AA,0x01); // 使能接收通道0自动应答 NRFWriteReg(WRITE_REG+EN_RXADDR,0x01); // 使能接收通道0 NRFWriteReg(WRITE_REG+SETUP_RETR,0x0a); //自动重发延时等待250us+86us,自动重发10次 NRFWriteReg(WRITE_REG+RF_CH,0xaa); //选择射频通道0xaa NRFWriteReg(WRITE_REG+RF_SETUP,0x07); //数据传输率1Mbps,发射功率0dBm,低噪声放大器增益 NRFWriteReg(WRITE_REG+CONFIG,0x0e); //CRC使能,16位CRC校验,上电 NRF_CE = 1; delay_us(15); //保持10us秒以上 } /********************************************************************************* NRF设置为接收模式并接收数据 **********************************************************************************/ void NRFSetRXMode(void) //主要接收模式 { NRF_CE=0; NRFWriteTxDate(WRITE_REG+RX_ADDR_P0,TxAddr0,TX_ADR_WIDTH); //接收设备接收通道0使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P1,TxAddr1,TX_ADR_WIDTH); //接收设备接收通道1使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P2,TxAddr2,TX_ADR_WIDTH); //接收设备接收通道1使用和发送设备相同的发送地址 NRFWriteTxDate(WRITE_REG+RX_ADDR_P3,TxAddr3,TX_ADR_WIDTH); //接收设备接收通道1使用和发送设备相同的发送地址 NRFWriteReg(WRITE_REG+EN_AA,0x0f); //使能接收通道0自动应答 NRFWriteReg(WRITE_REG+EN_RXADDR,0x0f); //使能接收通道0 NRFWriteReg(WRITE_REG+RF_CH,0x40); //选择射频通道0x40 NRFWriteReg(WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH); //接收通道0选择和发送通道相同有效数据宽度 NRFWriteReg(WRITE_REG+RX_PW_P1,RX_PLOAD_WIDTH); //接收通道1选择和发送通道相同有效数据宽度 NRFWriteReg(WRITE_REG+RX_PW_P2,RX_PLOAD_WIDTH); NRFWriteReg(WRITE_REG+RF_SETUP,0x07); //数据传输率1Mbps,发射功率0dBm,低噪声放大器增益*/ NRFWriteReg(WRITE_REG+CONFIG,0x0f); //CRC使能,16位CRC校验,上电,接收模式 NRF_CE = 1; delay_us(15); //保持10us秒以上 } |
上面看到的客户端和中控端的发送、接收模式的配置函数乍一看是一样的,其实当你细看的会发现一些不同的细节,在这里面的射频通道选择上本人采用收发不同频的操作,下面给个图说明更具说服力:
其中,中控端给客户端发送(客户端接收)数据的频道选择0xaa,客户端给中控端发送(中控端接收)数据的频道选择0x40,且两者的频道数间隔比较大,这样做是为了让客户端与客户端之间避免数据干扰,只让客户端端与中控端有数据往来。另外,在客户端的接收模式代码中,
NRFWriteTxDate(WRITE_REG+RX_ADDR_P0,RxAddr0,TX_ADR_WIDTH); //接收设备接收通道0使用和发送设备相同的发送地址
NRFWriteTxDate(WRITE_REG+RX_ADDR_P1,RxAddr0,TX_ADR_WIDTH); //接收设备接收通道1使用和发送设备相同的发送地址
NRFWriteTxDate(WRITE_REG+RX_ADDR_P2,RxAddr2,1); //接收设备接收通道1使用和发送设备相同的发送地址
NRFWriteTxDate(WRITE_REG+RX_ADDR_P3,RxAddr3,1);
其中只要接收通道0的接收地址配置成与中控端的发送数据地址相同即可,不用做其余通道的配置,而程序有写的初衷,本人是想验证中控端可否单独给某一个客户端发送数据用的,然而效果并不如意,后期再做调试看看。
在本地地址和接收地址中,我们可以看到客户端和中控端都一致,而各个通道上又有些不同,这在寄存器中有说明,读者可以细看手册中数据通道选择部分的相关知识。
在数据宽度设置上,发送方和接收方要保持一致,即TX_PLOAD_WIDTH和RX_PLOAD_WIDTH在中控端和客户端代码中的宏定义的值要相等。在自动重发机制中,本人选择自动重发延时等待250us+86us,自动重发10次,选定了重发机制,可以使得就算接收方未工作,发送方发送工作也能顺利执行下去,当其重发完10次后则视为一次成功发射(尽管接收方未能接收到数据)。其他一些通道允许配置和使能配置都在手册中有更详细的说明,在此就不细讲了。
4)检测应答(用于发送模式,此处发送完成标志检测用到的方式为查询方式,并非中断方式),接收数据(用于接收模式,此处接收完成标志检测亦用到查询方式检测)
/******************************************************************************** 检测应答信号 *******************************************************************************/ u8 CheckACK(void) { //用于发射 u8 sta; sta = NRFReadReg(READ_REG+STATUS); // 返回状态寄存器 if((sta&MAX_TX)||(sta&TX_OK)) //发送完毕中断 { NRFWriteReg(WRITE_REG+STATUS,0xff); //清除TX_DS或MAX_RT中断标志 NRF_CS = 0; SPI1_ReadWriteByte(FLUSH_TX); //用于清空FIFO !!关键!! NRF_CS = 1; return(0); } else return(1); } u8 nrf_buf1[5] = {0}; u8 nrf_buf2[5] = {0}; /******************************************************************************** 接收数据 *********************************************************************************/ void GetDate(void) { u8 sta; u8 RX_P_NO; //接收通道号 sta = NRFReadReg(READ_REG+STATUS); //发送数据后读取状态寄存器 if(RX_OK&sta) // 判断是否接收到数据 { LED1 = !LED1; RX_P_NO = sta & 0x0e; //获取通道号 NRF_CE = 0; //待机 switch(RX_P_NO) { case 0x00:NRFReadRxDate(RD_RX_PLOAD,nrf_buf1,RX_PLOAD_WIDTH);printf("get:%s\r\n",nrf_buf1);break;// 从RXFIFO读取数据通道0 case 0x02:NRFReadRxDate(RD_RX_PLOAD,nrf_buf1,RX_PLOAD_WIDTH);printf("get:%s\r\n",nrf_buf1);;break;// 从RXFIFO读取数据通道1 case 0x04:NRFReadRxDate(RD_RX_PLOAD,nrf_buf1,RX_PLOAD_WIDTH);printf("get:%s\r\n",nrf_buf1);;break;// 从RXFIFO读取数据通道1 default:break; } NRFWriteReg(WRITE_REG+STATUS,0xff); //接收到数据后RX_DR,TX_DS,MAX_PT都置高为1,通过写1来清楚中断标 NRF_CS = 0; SPI1_ReadWriteByte(FLUSH_RX); //用于清空FIFO !!关键!! NRF_CS = 1; } } |
在检测应答和接收数据方面,本人都采用了查询的方式进行,检测应答结合发送模式一块使用,接收数据结合接收模式一块使用。将在后面的主函数任务中呈现;
5)主函数任务中的应用
中控端主函数:
#include "sys.h" #include "led.h" #include "key.h" #include "NRF24L01.h" #include "delay.h" #include "timer3.h" #include "usart.h" u8 nrf_txbuf0[5] = {'s','i','k','a','i'}; u8 nrf_txbuf1[5] = {'h','e','l','l','o'}; u8 nrf_txbuf2[5] = {'b','y','e','!','!'}; u8 nrf_txbuf3[5] = {'n','i','h','a','o'}; int main() { /*********设备初始化************/ Stm32_Clock_Init(9);//系统时钟设置 delay_init(72); //延时函数初始化 uart_init(72,115200); init_NRF24L01(); led_init(); init_key(); Timer3_Init(10); while(1) { NRFSetRXMode(); GetDate(); if(KEY_STA) { switch(key_num) { case KEY_WP: LED1 = !LED1; NRFSetTxMode(nrf_txbuf2,TxAddr2,1); //发送数据 while(CheckACK()); //检测是否发送完毕 break; case KEY_0: LED1 = !LED1; NRFSetTxMode(nrf_txbuf0,TxAddr0,TX_ADR_WIDTH); //发送数据 while(CheckACK()); //检测是否发送完毕 break; case KEY_1: LED1 = !LED1; NRFSetTxMode(nrf_txbuf1,TxAddr1,TX_ADR_WIDTH); //发送数据 while(CheckACK()); //检测是否发送完毕 break; } } delay_ms(10); } } |
客户端主函数:
#include "stm32f10x.h" #include #include #include #include #include "Device_ID.h" #include "timer.h" #include "systick.h" #include "usart.h" #include "sys.h" #include "key.h" #include "led.h" #include "nrf24l01.h" u8 getData0[5] = {0}; u8 getData1[5] = {0}; u8 getData2[5] = {0}; u8 getData3[5] = {0}; u8 sendData0[5] = {'h','e','l','l','o'}; u8 sendData1[5] = {'g','o','d','b','y'}; u8 sendData2[5] = {'n','i','h','a','o'}; u8 sendData3[5] = {'s','i','k','a','i'}; int main(void) { Stm32_Clock_Init(9);//系统时钟设置 MySysTick_Config(); uart_init(72,115200); printf("hello world\r\n"); init_key(); led_init(); Timer3_Init(7200,10); init_NRF24L01();
while(1) { if(_3sSta) { _3sSta = 0; printf("sending msg.........\r\n"); NRFSetTxMode(sendData1,TxAddr1,TX_ADR_WIDTH); //发送数据 while(CheckACK()); //检测是否发送完毕 } NRFSetRXMode(); GetDate(); Delay_ms(10); } } |
从上面的主函数中我们可以看到,无论是客户端还是中控端,它们在大循环中主要的运行工作还是等待接收对方数据的到来。有些人可能会想它们在给对方发送数据的时候,对方会不会也正在给自身发送数据啊,这种情况虽然存在,但完全可以避免,我们想要的联动性控制效果肯定收发有序的过程,而且数据的发送和处理可视为在瞬间就完成的。延时部分也是很很小的。上面的任务中,我们可以了解到,客户端是在间隔3秒钟的时间给中控端发送定时数据(而这个时间间隔在每个客户端是可以不同的),中控端基本时刻在准备接收处理数据,唯有当按键被按下时才发送自身数据给客户端。
说到这里,nRF24L01的一对多、多对一通信已实现效果了。最后,本人再无私奉献出工程文件与道友们互相学习、借鉴、共勉。https://pan.baidu.com/s/1Io8_dq6btazC_wHL8nA6hw