nRF24L01一对多通信及多对一通信(一收多发、一发多收)

2023-10-24 11:50

本文主要是介绍nRF24L01一对多通信及多对一通信(一收多发、一发多收),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

话不多说,直接先上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 <stdio.h>  
#include <string.h>  
#include <stdbool.h>
#include <math.h>
#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

这篇关于nRF24L01一对多通信及多对一通信(一收多发、一发多收)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/274971

相关文章

系统架构师考试学习笔记第三篇——架构设计高级知识(20)通信系统架构设计理论与实践

本章知识考点:         第20课时主要学习通信系统架构设计的理论和工作中的实践。根据新版考试大纲,本课时知识点会涉及案例分析题(25分),而在历年考试中,案例题对该部分内容的考查并不多,虽在综合知识选择题目中经常考查,但分值也不高。本课时内容侧重于对知识点的记忆和理解,按照以往的出题规律,通信系统架构设计基础知识点多来源于教材内的基础网络设备、网络架构和教材外最新时事热点技术。本课时知识

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

vue2 组件通信

props + emits props:用于接收父组件传递给子组件的数据。可以定义期望从父组件接收的数据结构和类型。‘子组件不可更改该数据’emits:用于定义组件可以向父组件发出的事件。这允许父组件监听子组件的事件并作出响应。(比如数据更新) props检查属性 属性名类型描述默认值typeFunction指定 prop 应该是什么类型,如 String, Number, Boolean,

linux中使用rust语言在不同进程之间通信

第一种:使用mmap映射相同文件 fn main() {let pid = std::process::id();println!(

C++编程:ZeroMQ进程间(订阅-发布)通信配置优化

文章目录 0. 概述1. 发布者同步发送(pub)与订阅者异步接收(sub)示例代码可能的副作用: 2. 适度增加缓存和队列示例代码副作用: 3. 动态的IPC通道管理示例代码副作用: 4. 接收消息的超时设置示例代码副作用: 5. 增加I/O线程数量示例代码副作用: 6. 异步消息发送(使用`dontwait`标志)示例代码副作用: 7. 其他可以考虑的优化项7.1 立即发送(ZMQ_IM

VB和51单片机串口通信讲解(只针对VB部分)

标记:该篇文章全部搬自如下网址:http://www.crystalradio.cn/thread-321839-1-1.html,谢谢啦            里面关于中文接收的部分,大家可以好好学习下,题主也在研究中................... Commport;设置或返回串口号。 SettingS:以字符串的形式设置或返回串口通信参数。 Portopen:设置或返回串口

深入理解TCP通信

这大概是自己博客上面第三次写TCP通信demo了,总是写同样的内容也不太好啊,不过每一次都比前一次进步一点。这次主要使用了VIM编辑工具、gdb调试、wireshirk、netstat查看网络状态。 参考《C++服务器视频教程》、《Unix网络编程》 一、VIM常用命令 vim server.cpp #打开一个文件:w 写入文件:wq 保存并退出:q! 不保存退出显示行号

电子电气架构---私有总线通信和诊断规则

电子电气架构—私有总线通信和诊断规则 我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节能减排。 无人问津也好,技不如人也罢,你都要试着安静下来,去做自己该做的事.而不是让内心的烦躁、

DoIP-ISO 13400-1 道路车辆-基于互联网协议的诊断通信(DoIP)-第 1 部分:一般信息和用例定义 (1/2)

如下内容基于2011版本的 ISO 13400开展,内容较多,拆分为2篇,此篇为 1/2。 前言 ISO(国际标准化组织)是一个全球范围内的国际标准机构联合体(ISO 成员机构)。国际标准的制备工作通常通过 ISO 技术委员会进行。每个相关成员机构都有权在已建立的技术委员会中代表其利益。与 ISO 保持联系的国际组织、政府和非政府组织也参与这项工作。ISO 与国际电工委员会(IEC)在所有电气

微软C#套接字异步通信代码

Asynchronous Server Socket Example.NET Framework 4 其他版本 The following example program creates a server that receives connection requests from clients. The server is built with an asynchronous socket,