本帖最后由 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,可以很方便的制作小型家用电子网络应用。
|