注意:1.本实验不是利用传统意义上的多通道实现的
一、功能方案与分析
实验内容
使用 STM32F407 开发板、NRF2401 WIFI 模块,完成以下内容:
- 使用 USB 转 NRF2401 模块完成对 NRF2401 模块的配置,并记录地址、频率等配置的具体情况;
- STM32 开发板上电后,对 NRF2401 的接入是否正确进行检测,并在 LCD 屏上进行模块状态显式;
- NRF2401 模块连接正常后,使用 KEY0 和 KEY1 来控制模块的收发模式,且按 KEY0 进入接收模式,短按 KEY1 进入发送 1 模式,长按 KEY1 进入发送 2 模式;
- 构建一对多的无线传输网络,实现两发一收的传输功能。其中节点 1 为发送 1 模式,每隔 1s 发送一次本机采集的光照强度;节点 2 为发送 2 模式,每隔 2s 发送一次 “IoT BCU”,且每发一次字符串循环左移一位;节点 3 为接收模式,同时接收节点1和节点 2 的数据,并在 LCD 屏幕上进行显示。注意三个节点的工作模式均应可以通过 KEY0 和 KEY1 完成切换。
硬件设计
实验功能简介:开机的时候先检测 NRF24L01 模块是否存在,在检测到 NRF24L01 模块之后,根据 KEY0 和 KEY1 的设置来决定模块的工作模式,在设定好工作模式之后,就会发送/接收数据,可以通过 KEY0 和 KEY1 完成模式切换。
所要用到的硬件资源如下:
- LED0 模块
- KEY0 和 KEY1 按键
- TFTLCD 模块
- NRF24L01 模块
NRF24L01 模块属于外部模块,开发板上 NRF24L01 模块接口和 STM32F4 的连接情况,他们的连接关系下图所示:
这里 NRF24L01 也是使用的 SPI1,和 W25Q128 共用一个 SPI 接口,所以在使用的时候,他们分时复用 SPI1 。本章我们需要把 W25Q128 的片选信号置高,以防止这个器件对NRF24L01的通信造成干扰。另外, NRF_IRQ 和 RS485_RE 共用了 PG8 ,所以,他们不能同时使用,不过我们一般用不到 NRF_IRQ 这个信号,因此, RS485 和 NRF 一般也可以同时使用。
功能流程图
模块的配置
利用 USB 转 NRF24L01 上位机对模块进行配置。
其实没什么用,到时候用单片机,配置代码全在单片机中,其中还有一个很奇怪的现象,用上位机或者AT指令改完配置之后,把NRF24L01模块插入另一个转换器中,用AT指令显示配置会发现配置变成了另外一个。
二、代码实现
模块检测
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI1_SetSpeed(SPI_SPEED_8);
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);
NRF24L01_Read_Buf(TX_ADDR,buf,5);
for(i=0;i<5;i++)if(buf
!=0XA5)break;
if(i!=5)return 1;
return 0;
}
模块检测的原理为:在模块的寄存器上写入数据,然后再将数据读取出来,若读取的数据与原理相同,则表明,模块存在且功能正常。
按键相关
void EXTI3_IRQHandler(void)
{
delay_ms(10);
if(KEY1==0)
{
delay_ms(2000);
if(KEY1==0){
printf("进入发送2模式n");
mode=2;
}
else {printf("进入发送1模式n");mode=1;}
}
EXTI->PR=1<<3;
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);
if(KEY0==0)
{
delay_ms(10);
if(KEY0==0)
{
printf("进入接收模式n");
mode=3;
}
}
EXTI->PR=1<<4;
}
void EXTIX_Init(void)
{
KEY_Init();
Ex_NVIC_Config(GPIO_E,3,FTIR);
Ex_NVIC_Config(GPIO_E,4,FTIR);
MY_NVIC_Init(2,2,EXTI3_IRQn,2);
MY_NVIC_Init(2,2,EXTI4_IRQn,2);
}
长短按键的实现主要是依靠外部中断。利用延迟函数,判断按键前后的状态来实现 key1 的长按和短按。利用全局变量 mode 获取按键按下后得到的值,然后将 mode 传到主函数,进入相应的模式。
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
在主函数中的相关模式下加入如上代码,可以实现当前模式结束,判断按键,进入下一个模式,实现利用 key1 和 key0 完成模式转换操作。前提是将key.c中无键值返回改为除0和1以外的其他数。
对于 key1和 key2的切换不太好用,也许在主函数整体加个while(1)会比较好。
定时器相关
TIM3_Int_Init(10000-1,8400-1);
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)
{
time++;
}
TIM3->SR&=~(1<<0);
}
利用公式可以求出现在计时器为1秒。利用while循环,time 每增大1就过去1秒,到达一定数值后利用break函数跳出循环,这样就能实现相应的功能。
发送模式1(key1短按)
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,150,200,16,16,"NRF24L01 TX_Mode1");
NRF24L01_TX_Mode();
while(1)
{
adcx=Lsens_Get_Val();
bon[0]=adcx/10;
bon[1]=adcx%10;
bon[2]=37;
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
LCD_ShowString(30,170,239,32,16,"Sended DATA:(LSENS_VAL)");
LCD_ShowString(30,190,55,16,16,tmp_buf);
LCD_DrawLine(47, 205, 56, 191);
LCD_Draw_Circle(50,192,2);
LCD_Draw_Circle(54,202,2);
tmp_buf[0]=bon[0]+48;
tmp_buf[1]=bon[1]+48;
for(t=3;t<32;t++)
{
tmp_buf[t]=' ';
}
tmp_buf[31]=3;
tmp_buf[32]=0;
}else
{
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,170,lcddev.width-1,32,16,"Send Failed ");
};
while(1)
{
if(time>=1){time=0;break;}
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
}
首先是LCD_Fill()函数进行屏幕清除,NRF24L01_TX_Mode()进入发送模式,利用 adcx 获取光照强度。将获取到的 adcx 值进行一些运算存贮在 bon[ ] 数组中,然后将 bon[ ] 数组中的值赋值给 tmp_buf[ ] 数组中,利用for循环将 tmp_buf[ ] 数组中没有数据的位置空,最后在 tmp_buf[ ] 数组中的倒数第二位加上标识符确定身份,在 tmp_buf[ ] 数组中的最后一位加入结束符。至此第一个数据包制作完成,进入while(1)语句中延迟 1 秒后继续发送数据包。
标识符是为了识别是那个单片机传的数据,所以三套单片机的程序是有一些区别的。
发送模式2(key1长按)
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
LCD_ShowString(30,170,239,32,16,"Sended DATA:");
LCD_ShowString(30,190,55,16,16,tmp_buf);
key=mode;
key1=mode;
for(t=0;t<32;t++)
{
tmp_buf[t]=a[key-key1];
key++;
if(key>=key1+7)key=0;
}
mode++;
tmp_buf[31]=3;
tmp_buf[32]=0;
for(mode=0;mode<6;mode++)
{
bin[0]=a[mode];
a[mode]=a[mode+1];
a[mode+1]=bin[0];
}
}else
{
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,170,lcddev.width-1,32,16,"Send Failed ");
};
mode=0;
while(1)
{
if(time>=2){time=0;break;}
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
发送模式 2 与发送模式 1 同理,只是将光照强度改为数组 a[7]={“IOT-BCU”} 的值。为了实现每 2 秒左移的功能,利用for语句将第一个值右移6次即等价于数组左移 1 次。然后就是相同的操作,最后在 tmp_buf[ ] 数组中的倒数第二位加上标识符确定身份,在 tmp_buf[ ] 数组中的最后一位加入结束符。至此第一个数据包制作完成,进入while(1)语句中延迟 2 秒后继续发送数据包。
这里写啰嗦了,第一次是想用另一种方法写的,结果失败了,后面又懒得改了。
接收模式(key0短按)
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,150,200,16,16,"NRF24L01 RX_Mode");
LCD_ShowString(30,170,200,16,16,"Received DATA:");
NRF24L01_RX_Mode();
while(1)
{
if(NRF24L01_RxPacket(tmp_buf)==0)
{
tmp_buf[32]=0;
for(i=i;i<32;i++)
{
tmp_buf=tmp_buf[i+1];
}
if(tmp_buf[0]<58&&tmp_buf[31]==1&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 205, 56, 191);
LCD_Draw_Circle(50,192,2);
LCD_Draw_Circle(54,202,2);
}
if(tmp_buf[0]<58&&tmp_buf[31]==2&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 225, 56, 211);
LCD_Draw_Circle(50,212,2);
LCD_Draw_Circle(54,222,2);
}
if(tmp_buf[0]<58&&tmp_buf[31]==3&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 245, 56, 231);
LCD_Draw_Circle(50,232,2);
LCD_Draw_Circle(54,242,2);
}
if(tmp_buf[31]==1)LCD_ShowString(30,190,55,16,16,tmp_buf);
if(tmp_buf[31]==2)LCD_ShowString(30,210,55,16,16,tmp_buf);
if(tmp_buf[31]==3)LCD_ShowString(30,230,55,16,16,tmp_buf);
}
else delay_us(100);
t++;
if(t==10000)
{
t=0;
LED0=!LED0;
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
}
与发送模式大同小异。清屏,进入接收模式,将 tmp_buf[ ] 数组中的 32 位数据依次显示在屏幕上,其中利用了if语句,判断数据的格式、标识符来确定输出的位置及信息。
三、实验结果与分析
实验现象:
程序刚开始运行,没有插入NRF24L01模块。会显示“NRF24L01 Error”不断闪
插入 NRF2401 模块之后。
让其中 1 号和 2 号板子进入发送模式 1,3 号板子进入接收模式。
进入模式 2。
总结
问题一:check() 函数没有问题,可是屏幕上一直显示 “NRF24L01 Error”。
解决:ST-Link 与 NRF24L01 冲突,导致 NRF24L01 模块无法正常的写读。在烧录程序之后,将 ST-Link 断开再调试。
问题二:利用通道 1 和 2 实现一收多发存在一系列问题。
解决:将三个模块的收发地址全部改为相同的地址。tmp_buf 的倒数第二位存入标识符再发送,在读取数据的时候,根据标识符即可判断数据来自那个模块。
问题三:光照强度存入 tmp_buf 中,最后无法显示或者显示乱码。
解决:tmp_buf 存入的数据为 ASCII 码,故要在相应的位上加 48。
问题四:通信协议
解决:根据相关资料,tmp_buf 的第 0 个字节系统保留,用于每次传输的数据包长度统计。这个是针对于使用了这中上位机模块如果使用的是两个 24L01 相互通信,完全不用考虑这个。
注意:1.本实验不是利用传统意义上的多通道实现的
一、功能方案与分析
实验内容
使用 STM32F407 开发板、NRF2401 WIFI 模块,完成以下内容:
- 使用 USB 转 NRF2401 模块完成对 NRF2401 模块的配置,并记录地址、频率等配置的具体情况;
- STM32 开发板上电后,对 NRF2401 的接入是否正确进行检测,并在 LCD 屏上进行模块状态显式;
- NRF2401 模块连接正常后,使用 KEY0 和 KEY1 来控制模块的收发模式,且按 KEY0 进入接收模式,短按 KEY1 进入发送 1 模式,长按 KEY1 进入发送 2 模式;
- 构建一对多的无线传输网络,实现两发一收的传输功能。其中节点 1 为发送 1 模式,每隔 1s 发送一次本机采集的光照强度;节点 2 为发送 2 模式,每隔 2s 发送一次 “IoT BCU”,且每发一次字符串循环左移一位;节点 3 为接收模式,同时接收节点1和节点 2 的数据,并在 LCD 屏幕上进行显示。注意三个节点的工作模式均应可以通过 KEY0 和 KEY1 完成切换。
硬件设计
实验功能简介:开机的时候先检测 NRF24L01 模块是否存在,在检测到 NRF24L01 模块之后,根据 KEY0 和 KEY1 的设置来决定模块的工作模式,在设定好工作模式之后,就会发送/接收数据,可以通过 KEY0 和 KEY1 完成模式切换。
所要用到的硬件资源如下:
- LED0 模块
- KEY0 和 KEY1 按键
- TFTLCD 模块
- NRF24L01 模块
NRF24L01 模块属于外部模块,开发板上 NRF24L01 模块接口和 STM32F4 的连接情况,他们的连接关系下图所示:
这里 NRF24L01 也是使用的 SPI1,和 W25Q128 共用一个 SPI 接口,所以在使用的时候,他们分时复用 SPI1 。本章我们需要把 W25Q128 的片选信号置高,以防止这个器件对NRF24L01的通信造成干扰。另外, NRF_IRQ 和 RS485_RE 共用了 PG8 ,所以,他们不能同时使用,不过我们一般用不到 NRF_IRQ 这个信号,因此, RS485 和 NRF 一般也可以同时使用。
功能流程图
模块的配置
利用 USB 转 NRF24L01 上位机对模块进行配置。
其实没什么用,到时候用单片机,配置代码全在单片机中,其中还有一个很奇怪的现象,用上位机或者AT指令改完配置之后,把NRF24L01模块插入另一个转换器中,用AT指令显示配置会发现配置变成了另外一个。
二、代码实现
模块检测
u8 NRF24L01_Check(void)
{
u8 buf[5]={0XA5,0XA5,0XA5,0XA5,0XA5};
u8 i;
SPI1_SetSpeed(SPI_SPEED_8);
NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,buf,5);
NRF24L01_Read_Buf(TX_ADDR,buf,5);
for(i=0;i<5;i++)if(buf
!=0XA5)break;
if(i!=5)return 1;
return 0;
}
模块检测的原理为:在模块的寄存器上写入数据,然后再将数据读取出来,若读取的数据与原理相同,则表明,模块存在且功能正常。
按键相关
void EXTI3_IRQHandler(void)
{
delay_ms(10);
if(KEY1==0)
{
delay_ms(2000);
if(KEY1==0){
printf("进入发送2模式n");
mode=2;
}
else {printf("进入发送1模式n");mode=1;}
}
EXTI->PR=1<<3;
}
void EXTI4_IRQHandler(void)
{
delay_ms(10);
if(KEY0==0)
{
delay_ms(10);
if(KEY0==0)
{
printf("进入接收模式n");
mode=3;
}
}
EXTI->PR=1<<4;
}
void EXTIX_Init(void)
{
KEY_Init();
Ex_NVIC_Config(GPIO_E,3,FTIR);
Ex_NVIC_Config(GPIO_E,4,FTIR);
MY_NVIC_Init(2,2,EXTI3_IRQn,2);
MY_NVIC_Init(2,2,EXTI4_IRQn,2);
}
长短按键的实现主要是依靠外部中断。利用延迟函数,判断按键前后的状态来实现 key1 的长按和短按。利用全局变量 mode 获取按键按下后得到的值,然后将 mode 传到主函数,进入相应的模式。
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
在主函数中的相关模式下加入如上代码,可以实现当前模式结束,判断按键,进入下一个模式,实现利用 key1 和 key0 完成模式转换操作。前提是将key.c中无键值返回改为除0和1以外的其他数。
对于 key1和 key2的切换不太好用,也许在主函数整体加个while(1)会比较好。
定时器相关
TIM3_Int_Init(10000-1,8400-1);
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)
{
time++;
}
TIM3->SR&=~(1<<0);
}
利用公式可以求出现在计时器为1秒。利用while循环,time 每增大1就过去1秒,到达一定数值后利用break函数跳出循环,这样就能实现相应的功能。
发送模式1(key1短按)
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,150,200,16,16,"NRF24L01 TX_Mode1");
NRF24L01_TX_Mode();
while(1)
{
adcx=Lsens_Get_Val();
bon[0]=adcx/10;
bon[1]=adcx%10;
bon[2]=37;
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
LCD_ShowString(30,170,239,32,16,"Sended DATA:(LSENS_VAL)");
LCD_ShowString(30,190,55,16,16,tmp_buf);
LCD_DrawLine(47, 205, 56, 191);
LCD_Draw_Circle(50,192,2);
LCD_Draw_Circle(54,202,2);
tmp_buf[0]=bon[0]+48;
tmp_buf[1]=bon[1]+48;
for(t=3;t<32;t++)
{
tmp_buf[t]=' ';
}
tmp_buf[31]=3;
tmp_buf[32]=0;
}else
{
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,170,lcddev.width-1,32,16,"Send Failed ");
};
while(1)
{
if(time>=1){time=0;break;}
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
}
首先是LCD_Fill()函数进行屏幕清除,NRF24L01_TX_Mode()进入发送模式,利用 adcx 获取光照强度。将获取到的 adcx 值进行一些运算存贮在 bon[ ] 数组中,然后将 bon[ ] 数组中的值赋值给 tmp_buf[ ] 数组中,利用for循环将 tmp_buf[ ] 数组中没有数据的位置空,最后在 tmp_buf[ ] 数组中的倒数第二位加上标识符确定身份,在 tmp_buf[ ] 数组中的最后一位加入结束符。至此第一个数据包制作完成,进入while(1)语句中延迟 1 秒后继续发送数据包。
标识符是为了识别是那个单片机传的数据,所以三套单片机的程序是有一些区别的。
发送模式2(key1长按)
if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
{
LCD_ShowString(30,170,239,32,16,"Sended DATA:");
LCD_ShowString(30,190,55,16,16,tmp_buf);
key=mode;
key1=mode;
for(t=0;t<32;t++)
{
tmp_buf[t]=a[key-key1];
key++;
if(key>=key1+7)key=0;
}
mode++;
tmp_buf[31]=3;
tmp_buf[32]=0;
for(mode=0;mode<6;mode++)
{
bin[0]=a[mode];
a[mode]=a[mode+1];
a[mode+1]=bin[0];
}
}else
{
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,170,lcddev.width-1,32,16,"Send Failed ");
};
mode=0;
while(1)
{
if(time>=2){time=0;break;}
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
发送模式 2 与发送模式 1 同理,只是将光照强度改为数组 a[7]={“IOT-BCU”} 的值。为了实现每 2 秒左移的功能,利用for语句将第一个值右移6次即等价于数组左移 1 次。然后就是相同的操作,最后在 tmp_buf[ ] 数组中的倒数第二位加上标识符确定身份,在 tmp_buf[ ] 数组中的最后一位加入结束符。至此第一个数据包制作完成,进入while(1)语句中延迟 2 秒后继续发送数据包。
这里写啰嗦了,第一次是想用另一种方法写的,结果失败了,后面又懒得改了。
接收模式(key0短按)
LCD_Fill(0,170,lcddev.width,170+16*3,WHITE);
LCD_ShowString(30,150,200,16,16,"NRF24L01 RX_Mode");
LCD_ShowString(30,170,200,16,16,"Received DATA:");
NRF24L01_RX_Mode();
while(1)
{
if(NRF24L01_RxPacket(tmp_buf)==0)
{
tmp_buf[32]=0;
for(i=i;i<32;i++)
{
tmp_buf=tmp_buf[i+1];
}
if(tmp_buf[0]<58&&tmp_buf[31]==1&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 205, 56, 191);
LCD_Draw_Circle(50,192,2);
LCD_Draw_Circle(54,202,2);
}
if(tmp_buf[0]<58&&tmp_buf[31]==2&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 225, 56, 211);
LCD_Draw_Circle(50,212,2);
LCD_Draw_Circle(54,222,2);
}
if(tmp_buf[0]<58&&tmp_buf[31]==3&&tmp_buf[0]!='-')
{
LCD_ShowString(150,170,200,16,16,"(LSENS_VAL)");
LCD_DrawLine(47, 245, 56, 231);
LCD_Draw_Circle(50,232,2);
LCD_Draw_Circle(54,242,2);
}
if(tmp_buf[31]==1)LCD_ShowString(30,190,55,16,16,tmp_buf);
if(tmp_buf[31]==2)LCD_ShowString(30,210,55,16,16,tmp_buf);
if(tmp_buf[31]==3)LCD_ShowString(30,230,55,16,16,tmp_buf);
}
else delay_us(100);
t++;
if(t==10000)
{
t=0;
LED0=!LED0;
}
if(KEY_Scan(0)==1||KEY_Scan(0)==0){break;}
}
与发送模式大同小异。清屏,进入接收模式,将 tmp_buf[ ] 数组中的 32 位数据依次显示在屏幕上,其中利用了if语句,判断数据的格式、标识符来确定输出的位置及信息。
三、实验结果与分析
实验现象:
程序刚开始运行,没有插入NRF24L01模块。会显示“NRF24L01 Error”不断闪
插入 NRF2401 模块之后。
让其中 1 号和 2 号板子进入发送模式 1,3 号板子进入接收模式。
进入模式 2。
总结
问题一:check() 函数没有问题,可是屏幕上一直显示 “NRF24L01 Error”。
解决:ST-Link 与 NRF24L01 冲突,导致 NRF24L01 模块无法正常的写读。在烧录程序之后,将 ST-Link 断开再调试。
问题二:利用通道 1 和 2 实现一收多发存在一系列问题。
解决:将三个模块的收发地址全部改为相同的地址。tmp_buf 的倒数第二位存入标识符再发送,在读取数据的时候,根据标识符即可判断数据来自那个模块。
问题三:光照强度存入 tmp_buf 中,最后无法显示或者显示乱码。
解决:tmp_buf 存入的数据为 ASCII 码,故要在相应的位上加 48。
问题四:通信协议
解决:根据相关资料,tmp_buf 的第 0 个字节系统保留,用于每次传输的数据包长度统计。这个是针对于使用了这中上位机模块如果使用的是两个 24L01 相互通信,完全不用考虑这个。
举报