发新帖本帖赏金 20.00元(功能说明)我要提问
12下一页
返回列表
打印
[STM32U5]

利用nRF24L01组件组件小型控制网络

[复制链接]
4651|28
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
本帖最后由 suncat0504 于 2022-2-10 12:32 编辑

#申请原创# @21小跑堂
最新心血来潮,看着手里的一块STM32开发板和几个AT89C4051和nRF24L01无线模块,准备利用它们做成一套无线控制系统。由于nRF24L01无线模块的特点,打算利用不同的物理地址,通过程序控制,实现组网控制。有了想法,开始着手设计。
    首先是主控制系统,使用早期入手的智嵌STM32开发板。为啥要用这块板子呢?因为这个开发板上已经设置nRF24L01接口,不用单独设计了,拿来直接使用就行,很方便。
各个被控制的分机,使用AT89C4051。其实更想用STC家的产品,因为有看门狗,在无人值守的时候,更适合对应程序跑飞这种情况。奈何手里的这几片AT89C4051实在是闲置太久,本着不想浪费的原则,就只好将就一下了。为了防止程序跑飞,只能再考虑在各个分机的公用电源控制上,加一个单独的有看门狗的电源控制模块。
    主控制板的图片如下:

网口下面的那个8口双排插座的位置就是给nRF24L01用的。可以直接插上nRF24L01使用。而且卖家还提供了例程,例程里就有nRF24L01的,拿来改改就能用。话说“天下**一大抄,抄来抄去有提高”,其实程序也是这样,哈哈哈,题外话了。作为测试,修改目标是,按下开发板上四个按键中的某一个时,向地址匹配的分机发送信号。主机加载nRF24L01的样子如下:

分机的样子如下:

1对应的是接收信号用的nRF24L01模块。
2对应的外部测量信号输入端,配合AT89C4051的比较器使用。如果使用可以互换的STC芯片,这个地方可以用AD输入作为动作条件。
3对应的驱动外部继电器模块。提供5V、GND、驱动信号。驱动信号低电平有效。
4对应的是串口输入输出。主要是开发阶段测试用。实际应用时,可以不用串口。空出P3.0和P3,1给U3(24C64)作为控制信号用。
5对应的是拨码开关。目的是设置分机的地址。这个地址是和主控板的K1-K4一一对应的。
挂载了nRF24L01模块、继电器模块、连接串口的分机,如下图所示:

主控板主处理代码如下:
int main(void) {
         unsignedchar i=0, kv=0, tmp_buf[TX_PLOAD_WIDTH], addr1=0, addr2=0, errorFlag=0;

   /* 串口初始化 */
   Printf_Init();
   SysTick_Configuration();           //系统初始化     
       Key_Configuration();            // 初始化按键用到的GPIO口
         LED_Configuration();            // 初始化LED用到的GPIO口
         NVIC_Configuration();
   NRF24L01_Init();                            // 初始化处理器与NRF24L01连接的GPIO口
   
   while(NRF24L01_Check())        {                 //24L01在线检测
       printf("Didn't find NRF24L01,Please check whether NRF24L01 is online!\n\r");
                   delay_ms(2000);
    }
   printf("NRF24L01 OK\n\r");
   /*****熄灭四个led灯******/
         LED1_OFF;
         LED2_OFF;
         LED3_OFF;
         LED4_OFF;
   
         RX_Mode(kv);                                     //默认接收模式      
   // 打印本机地址
   NRF24L01_Read_Buf(RX_ADDR_P0, tmp_buf, RX_ADR_WIDTH);
   // 输出到串口
   printf("Receive address of channel 0:");
   for (i=0;i<5;i++) {
       printf("%c", tmp_buf);
    }
   printf("\n\r");
           
         while(1){                                                            
                   kv= Key_Value();                      //读取按键值
                   if(kv)          {                                             //如果有键按下,则进入到发送模式,并将键值发送出去
                            //设置发送数据
           tmp_buf[0]=0xC0;   // 指令码
           tmp_buf[1]=0x00;   // 通讯数据1
           tmp_buf[2]=0x14;   // 通讯数据2
           tmp_buf[3]=0x05;   // 通讯数据3
           tmp_buf[4]=getVerCode(tmp_buf, 0, 3);
           // 根据按键,设置目标分机地址
           addr1=TX_ADDRESS[3] + kv/10;
           addr2=TX_ADDRESS[4] + kv%10;
           
           // 转为发射模式
           TX_Mode(kv);
                            if(NRF24L01_TxPacket(tmp_buf)== TX_OK)   //如果发送成功
                            {
               printf("Sendingdata...\n\r");
                            }else {                                                                                               
   
                printf("Transfer failed.\n\r");
                       }
                            RX_Mode(kv);                                                //发送完成后进入到接收模式
                   }
      
                   //如果接收成功,则点亮相应的LED
       if(NRF24L01_RxPacket(tmp_buf) == 0)   {
           // 对方发射的目标机是本机?指令码是否正确?
           if (tmp_buf[0]==addr1 && tmp_buf[1]==addr2 &&tmp_buf[2]==0xC1) {
                // 判断校验码
                if (tmp_buf[3]!=8) {
                    printf("Wrong datalength.\n\r");
                    errorFlag=1;
                } else {
                    if(tmp_buf[8]!=getVerCode(tmp_buf, 0, 7)) {
                        printf("Errorcheck code.\n\r");
                        errorFlag=1;
                    } else {
                        // 根据对方分机的编号,点亮对应的LED
                        i=(addr1-0x30)*10 +(addr2-0x30);
                        if (i>0 && i< 5) {
                           
                            One_LED_ON(i);                        //点亮LED
                        } else {
                            printf("Wrongaddress of sub-device.\n\r");
                            errorFlag=1;
                        }                    
                    }
                }
           } else {
                errorFlag=1;
           }
           
           if (errorFlag == 1) {
                // 全亮,表示出错:可能数据错误,可能分机编号不对
                One_LED_ON(1);
                One_LED_ON(2);
                One_LED_ON(3);
                One_LED_ON(4);
                // 输出到串口
                printf("Receiveddata:\r\n");
                for(i=0;i<TX_PLOAD_WIDTH;i++) {
                    printf("%2x ",tmp_buf);
                }
                printf("\n\r");                  
           }
                   }
         }
}
主机nRF24L01模块的部分处理代码:
//启动NRF24L01发送一次数据
//txbuf:待发送数据首地址
//返回值:发送完成状况
u8 NRF24L01_TxPacket(u8 *txbuf) {
         u8state, i;   
   // 输出到串口
   printf("Sent data:");
   for (i=0;i<TX_PLOAD_WIDTH;i++) {
       printf("%2x ", txbuf);
    }
   printf("\n\r");
   
         Clr_NRF24L01_CE;
       NRF24L01_Write_Buf(WR_TX_PLOAD,txbuf,TX_PLOAD_WIDTH);//写数据到TX BUF 32个字节
        Set_NRF24L01_CE;                                     //启动发送        
         while(READ_NRF24L01_IRQ!=0);                         //等待发送完成
         state=NRF24L01_Read_Reg(STATUS);                     //读取状态寄存器的值      
         NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,state);      //清除TX_DS或MAX_RT中断标志
         if(state&MAX_TX)                                     //达到最大重发次数
         {
                   NRF24L01_Write_Reg(FLUSH_TX,0xff);               //清除TX FIFO寄存器
                   returnMAX_TX;
         }
         if(state&TX_OK)                                      //发送完成
         {
       NRF24L01_Write_Reg(FLUSH_TX,0xff);               //清除TX FIFO寄存器
                   returnTX_OK;
         }
         return0xff;                                        //其他原因发送失败
}
//启动NRF24L01接收一次数据
//rxbuf:待接收数据首地址
//返回值:0,接收完成;其他,错误代码
u8 NRF24L01_RxPacket(u8 *rxbuf) {
         u8state, i;                    
   
         state=NRF24L01_Read_Reg(STATUS);                //读取状态寄存器的值           
         NRF24L01_Write_Reg(SPI_WRITE_REG+STATUS,state);//清除TX_DS或MAX_RT中断标志
         if(state&RX_OK)                                 //接收到数据
         {
                   NRF24L01_Read_Buf(RD_RX_PLOAD,rxbuf,RX_PLOAD_WIDTH);//读取数据
                   NRF24L01_Write_Reg(FLUSH_RX,0xff);          //清除RX FIFO寄存器
        
       // 输出到串口
       printf("Received data::");
       for (i=0;i<TX_PLOAD_WIDTH;i++) {
           printf("%2x ", rxbuf);
       }
       printf("\n\r");
      
      
                   return0;
         }         
         return1;                                      //没收到任何数据
}
//该函数初始化NRF24L01到RX模式
//设置RX地址,写RX数据宽度,选择RF频道,波特率和LNA HCURR
//当CE变高后,即进入RX模式,并可以接收数据了                 
void RX_Mode(u8 offset) {
   u8  addr[RX_ADR_WIDTH],i;
   for (i=0;i<RX_ADR_WIDTH;i++) {
       addr=RX_ADDRESS;
    }
   // 根据预设子机地址,重新设定主机无线模块地址
   addr[RX_ADR_WIDTH-2]=offset/10 + addr[RX_ADR_WIDTH-2];
   addr[RX_ADR_WIDTH-1]=offset%10 + addr[RX_ADR_WIDTH-1];
   
         Clr_NRF24L01_CE;     
   //写RX节点地址
       NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)addr,RX_ADR_WIDTH);
   // 自动应答
       NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x01);
    //使能通道0的接收地址           
       NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01);
   
   //设置RF通信频率                    
       NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,40);
   
   //选择通道0的有效数据宽度        
       NRF24L01_Write_Reg(SPI_WRITE_REG+RX_PW_P0,RX_PLOAD_WIDTH);
   
   //设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
       NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x0f);
   
   //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,PRIM_RX接收模式
       NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG,0x0f);
   
   //CE为高,进入接收模式
       Set_NRF24L01_CE;                                
}                           
//该函数初始化NRF24L01到TX模式
//设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,
//选择RF频道,波特率和LNA HCURR PWR_UP,CRC使能
//当CE变高后,即进入RX模式,并可以接收数据了                 
//CE为高大于10us,则启动发送.   
void TX_Mode(u8 offset) {      
   u8  addr[TX_ADR_WIDTH],i;
   // 根据预设子机地址,重新设定主机无线模块地址
   for (i=0;i<TX_ADR_WIDTH;i++) {
       addr=TX_ADDRESS;
    }
   addr[TX_ADR_WIDTH-2]=offset/10 + addr[TX_ADR_WIDTH-2];
   addr[TX_ADR_WIDTH-1]=offset%10 + addr[TX_ADR_WIDTH-1];
   
   // 输出到串口
   printf("Target sub-device address:");
   for (i=0;i<TX_ADR_WIDTH;i++) {
       printf("%2x ", addr);
    }
   printf("\n\r");
         Clr_NRF24L01_CE;      
   //写TX节点地址
       NRF24L01_Write_Buf(SPI_WRITE_REG+TX_ADDR,(u8*)addr,TX_ADR_WIDTH);
   
   //设置TX节点地址,主要为了使能ACK           
       NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR_P0,(u8*)addr,RX_ADR_WIDTH);
   //使能通道0的自动应答   
       NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x01);  
   //NRF24L01_Write_Reg(SPI_WRITE_REG+EN_AA,0x00);   
   
   //使能通道0的接收地址  
       NRF24L01_Write_Reg(SPI_WRITE_REG+EN_RXADDR,0x01);
   
   //设置自动重发间隔时间:‘1111’-等待 4000+86us;最大自动重发次数:3次
    NRF24L01_Write_Reg(SPI_WRITE_REG+SETUP_RETR,0xf3);
   
   //设置RF通道为40
       NRF24L01_Write_Reg(SPI_WRITE_REG+RF_CH,40);      
   
   //设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
       NRF24L01_Write_Reg(SPI_WRITE_REG+RF_SETUP,0x0f);  
   
   //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,PRIM_RX发送模式,开启所有中断
       NRF24L01_Write_Reg(SPI_WRITE_REG+CONFIG,0x0e);   
   
   // CE为高,10us后启动发送
         Set_NRF24L01_CE;                                 
}                  
分机主处理代码:
void main (void) {
   bit et0Status=ET0;
   u8 i=0, state=0;
   
   // 初始化
   system_init();
   transstr("main start...\r\n");
   
   // 初始化nRF24L01关联口线
   nRF24L01P_Init();
   
   if(NRF24L01_Check()==1)         {                 //24L01在线检测
      transstr("Didn'tfind NRF24L01,Please check whether NRF24L01 is online!\r\n");
       delay_ms(300);
    }
   transstr("slaver-NRF24L01 OK\r\n");   
   
   // 接收模式
   nRF24L01P_RX_Mode();
   
   // 主循环
   while (1) {
        // 等待nRF2*4L01的中断信号,表明收到了来自主机的指令
       if (NRF24L01_IRQ==0) {
           transstr("main_NRF24L01_interrupt:\r\n");   
           
           // 保存当前定时器0中断状态,
           et0Status=ET0;
           // 禁止Timer0的中断
           ET0=0;
           TR0=0;
           // 检查在中断类型
           state=nRF24L01P_Read_Reg(REG_STATUS);
           if(state & RX_DR) {
                // 收到来自nRF24L01的中断请求,开始接收nRF24L01的数据
                if(!nRF24L01P_RxPacket(rx_buf)){
                    // 检查数据是什么指令,并执行对应的动作
                    AnalysisCommand();   
                } else {
                    // 没有收到数据?
                    
                }               
           }
           // 达到最大重发次数
           if(state&MAX_RT) {
                // 清除TX_DS或MAX_RT中断标志
                nRF24L01P_Write_Reg(WRITE_REG +REG_STATUS, state);
                // 清除nRF24L01的发送缓冲区
               nRF24L01P_Write_Reg(FLUSH_TX,0xff);               
           }
           // 发送完成
           if(state&TX_DS)        {
                // 清除TX_DS或MAX_RT中断标志
               nRF24L01P_Write_Reg(WRITE_REG +REG_STATUS, state);        
                // 清除nRF24L01的发送缓冲区
               nRF24L01P_Write_Reg(FLUSH_TX,0xff);
           }                        
               
           if (et0Status) {
                ET0=1;
                TR0=1;
           }
       }
    }
}
到这里,简单地使用STM32和51单片机,以nRF24L01实现组网通讯的实验,就完成了。后续准备在STM32开发板上增加系统设置、液晶显示等功能,实现动态地控制子设备的控制。比如用子设备控制什么时候开关灯、开关多久灯。本套系统中,因为使用了4位拨码开关来控制分机地址,所以是可以组建1个主机、16个子机的小型无线网络,当然需要修改下主机程序,以适应16个子机的选址。

实际上,以nRF24L01组网,除了改变物理地址方式实现,还可以使用其它方式,比如开放其它通道;比如使用相同的物理地址,采用禁止应答,在通讯数据中增设逻辑地址检查。逻辑地址一致的才应答。灵活使用nRF24L01,可以很方便的制作小型家用电子网络应用。


使用特权

评论回复

打赏榜单

21小跑堂 打赏了 20.00 元 2022-02-11
理由:恭喜通过原创文章审核!请多多加油哦!

沙发
xxdcq| | 2022-2-18 15:29 | 只看该作者
这个能不能测距和定位?

使用特权

评论回复
评论
suncat0504 2022-2-18 16:33 回复TA
STM32开发板肯定行啊 
板凳
updownq| | 2022-2-20 13:18 | 只看该作者
nRF24L01通信距离是多少

使用特权

评论回复
地板
jackcat| | 2022-2-21 11:55 | 只看该作者
nRF24L01支持多少个节点呢

使用特权

评论回复
5
suncat0504|  楼主 | 2022-2-21 15:15 | 只看该作者
本帖最后由 suncat0504 于 2022-2-21 15:22 编辑
jackcat 发表于 2022-2-21 11:55
nRF24L01支持多少个节点呢

我个人观点哈:因为有软地址的设置,加上通信频道的设置(即使软地址相同,仍然可以在通讯数据中设置匹配验证),理论上没有上限(说是没有上限,其实就是很大,足够用)。

使用特权

评论回复
6
suncat0504|  楼主 | 2022-2-21 15:21 | 只看该作者
updownq 发表于 2022-2-20 13:18
nRF24L01通信距离是多少

通信距离和发射功率有关。PCB天线场合,空旷地带几十米没问题。

使用特权

评论回复
7
isseed| | 2022-2-21 18:18 | 只看该作者
nRF24L01怎么确定的地址呢?  

使用特权

评论回复
评论
suncat0504 2022-2-21 19:10 回复TA
通常,一个nRF24L01的收发地址使用同一个。而同一时刻,nRF24L01只能保持收或发的一种状态 
suncat0504 2022-2-21 19:06 回复TA
// 设置发射用的地址 NRF24L01_Write_Buf(SPI_WRITE_REG+TX_ADDR,(u8*)addr,TX_ADR_WIDTH); // 设置接收用的地址 NRF24L01_Write_Buf(SPI_WRITE_REG+RX_ADDR,(u8*)addr,TX_ADR_WIDTH); 两个地址可以一致,也可以不同。如果某个nRF24L01模块的接收用地址,和某个发射端nRF24L01模块的发射地址一致时,才能接收到这个发射模块发送的数据。 
8
suncat0504|  楼主 | 2022-2-21 18:56 | 只看该作者
isseed 发表于 2022-2-21 18:18
nRF24L01怎么确定的地址呢?

地址你随意定。一般是5个16进制字节的数据。“12345”可以,“abcde”可以,{0x01,0x02,0x01,0x00,0x01}也行。收发两侧的nRF24L01只要地址一致,就能建立通讯。

使用特权

评论回复
评论
suncat0504 2022-2-21 18:58 回复TA
nRF24L01有专门的地址寄存器,通过指令初始化设置,你把5个字节的地址数据,按照协议传输给nRF24L01就可以了。 
9
everyrobin| | 2022-2-21 21:08 | 只看该作者
nRF24L01可以组网吗?

使用特权

评论回复
10
pl202| | 2022-2-21 21:35 | 只看该作者
求nrf24l01控制的程序

使用特权

评论回复
11
deliahouse887| | 2022-2-21 22:03 | 只看该作者
最近在研究zigbee的组网呢  

使用特权

评论回复
12
updownq| | 2022-2-21 22:30 | 只看该作者
控制网络效果怎么样   

使用特权

评论回复
13
modesty3jonah| | 2022-2-22 15:50 | 只看该作者
NRF24L01能像ESP-8226那样无线控制手机APP吗

使用特权

评论回复
14
pentruman| | 2022-2-22 16:22 | 只看该作者
能不能开发手机APP通过NRF24L01实现控制电路的功能

使用特权

评论回复
15
xiaoyaodz| | 2022-2-22 16:59 | 只看该作者
NRF24L01能一对多组网吗?

使用特权

评论回复
16
foxsbig| | 2022-2-22 17:05 | 只看该作者
这是射频模块吧

使用特权

评论回复
17
zerorobert| | 2022-2-22 17:27 | 只看该作者
NRF24L01无线模块的收发距离是多少?

使用特权

评论回复
18
pmp| | 2022-2-22 18:05 | 只看该作者
怎么实现nRF24L01的同时的收发?  

使用特权

评论回复
19
fengm| | 2022-2-22 18:37 | 只看该作者
如何用nrf24l01无线模块实现图像传输

使用特权

评论回复
评论
suncat0504 2022-2-22 19:05 回复TA
图像数据的数据量一般比较大,用nrf24l01好像不太合适,它一次只能传输32字节的数据。如果你一定要用它,恐怕需要建立通讯协议。 
20
adolphcocker| | 2022-2-22 19:14 | 只看该作者
使用nrf24l01无线模块如何 实现多通道?

使用特权

评论回复
发新帖 本帖赏金 20.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

认证:大连伊飞特信息技术有限公司软件工程师
简介:本人于1993年毕业于大连理工大学。毕业后从事单片机开发工作5年,之后转入软件开发工作至今。

119

主题

3655

帖子

5

粉丝