STM32
直播中

王静

7年用户 1558经验值
私信 关注
[问答]

怎样使用STM32F407和NRF2401 WIFI模块去完成NRF2401模块的配置呢

怎样使用STM32F407和NRF2401 WIFI模块去完成NRF2401模块的配置呢?其代码该如何去实现呢?

回帖(1)

张百玲

2021-12-16 14:39:02
注意: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 相互通信,完全不用考虑这个。
举报

更多回帖

×
20
完善资料,
赚取积分