STM32F407ZET6+NRF24L01实现一收多发(一发多收)

注意:1.本实验不是利用传统意义上的多通道实现的

一、功能方案与分析

实验内容

  使用 STM32F407 开发板、NRF2401 WIFI 模块,完成以下内容:

  1. 使用 USB 转 NRF2401 模块完成对 NRF2401 模块的配置,并记录地址、频率等配置的具体情况;
  2. STM32 开发板上电后,对 NRF2401 的接入是否正确进行检测,并在 LCD 屏上进行模块状态显式;
  3. NRF2401 模块连接正常后,使用 KEY0 和 KEY1 来控制模块的收发模式,且按 KEY0 进入接收模式,短按 KEY1 进入发送 1 模式,长按 KEY1 进入发送 2 模式;
  4. 构建一对多的无线传输网络,实现两发一收的传输功能。其中节点 1 为发送 1 模式,每隔 1s 发送一次本机采集的光照强度;节点 2 为发送 2 模式,每隔 2s 发送一次 “IoT BCU”,且每发一次字符串循环左移一位;节点 3 为接收模式,同时接收节点1和节点 2 的数据,并在 LCD 屏幕上进行显示。注意三个节点的工作模式均应可以通过 KEY0 和 KEY1 完成切换。

硬件设计

  实验功能简介:开机的时候先检测 NRF24L01 模块是否存在,在检测到 NRF24L01 模块之后,根据 KEY0 和 KEY1 的设置来决定模块的工作模式,在设定好工作模式之后,就会发送/接收数据,可以通过 KEY0 和 KEY1 完成模式切换。

  所要用到的硬件资源如下:

  1. LED0 模块
  2. KEY0 和 KEY1 按键
  3. TFTLCD 模块
  4. NRF24L01 模块

  NRF24L01 模块属于外部模块,开发板上 NRF24L01 模块接口和 STM32F4 的连接情况,他们的连接关系下图所示:

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第1张图片
  这里 NRF24L01 也是使用的 SPI1,和 W25Q128 共用一个 SPI 接口,所以在使用的时候,他们分时复用 SPI1 。本章我们需要把 W25Q128 的片选信号置高,以防止这个器件对NRF24L01的通信造成干扰。另外, NRF_IRQ 和 RS485_RE 共用了 PG8 ,所以,他们不能同时使用,不过我们一般用不到 NRF_IRQ 这个信号,因此, RS485 和 NRF 一般也可以同时使用。

功能流程图

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第2张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第3张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第4张图片

模块的配置

  利用 USB 转 NRF24L01 上位机对模块进行配置。

其实没什么用,到时候用单片机,配置代码全在单片机中,其中还有一个很奇怪的现象,用上位机或者AT指令改完配置之后,把NRF24L01模块插入另一个转换器中,用AT指令显示配置会发现配置变成了另外一个。

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第5张图片

二、代码实现

模块检测

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[i]!=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);
}

  第一行在main函数中,用来调整定时器的频率。 T o u t = [ ( a r r + 1 ) × ( p s c + 1 ) ] Tout={\left[{(arr+1)\times(psc+1)}\right]} Tout=[(arr+1)×(psc+1)]  利用公式可以求出现在计时器为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[i]=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”不断闪烁。

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第6张图片
  插入 NRF2401 模块之后。

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第7张图片
  让其中 1 号和 2 号板子进入发送模式 1,3 号板子进入接收模式。

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第8张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第9张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第10张图片
  进入模式 2。

STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第11张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第12张图片
STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第13张图片

总结

问题一:check() 函数没有问题,可是屏幕上一直显示 “NRF24L01 Error”。

  解决:ST-Link 与 NRF24L01 冲突,导致 NRF24L01 模块无法正常的写读。在烧录程序之后,将 ST-Link 断开再调试。

问题二:利用通道 1 和 2 实现一收多发存在一系列问题。

  解决:将三个模块的收发地址全部改为相同的地址。tmp_buf 的倒数第二位存入标识符再发送,在读取数据的时候,根据标识符即可判断数据来自那个模块。

问题三:光照强度存入 tmp_buf 中,最后无法显示或者显示乱码。

  解决:tmp_buf 存入的数据为 ASCII 码,故要在相应的位上加 48。

问题四:通信协议

  解决:根据相关资料,tmp_buf 的第 0 个字节系统保留,用于每次传输的数据包长度统计。这个是针对于使用了这中上位机模块如果使用的是两个 24L01 相互通信,完全不用考虑这个。

最后附上调试成功的代码

链接:https://pan.baidu.com/s/18P0SynB54PjweIedVff3AA
提取码:cyqy
微信扫码:STM32F407ZET6+NRF24L01实现一收多发(一发多收)_第14张图片

你可能感兴趣的:(嵌入式,通信,单片机,嵌入式,串口通信,物联网)