STM32在使用NRF24L01中PC(电脑)连接无显示数据以及出现error的解决办法

NRF24L01_TX_ModeSTM32 在使用 NRF24L01 过程中遇到的问题


一、出现 NRF24L01 Error

1.1 问题情况

在使用 正点原子 的代码中有以下这样一段代码:

while(NRF24L01_Check())
{
    LCD_ShowString(30,130,200,16,16,"NRF24L01 Error");
    delay_ms(200);
    LCD_Fill(30,130,239,130+16,WHITE);
    delay_ms(200);
}
LCD_ShowString(30,130,200,16,16,"NRF24L01 OK");
  • 我们可以看到,这里调用了 NRF24L01 的自检函数,用于判断自检是否通过,当自检不通过时,在LCD上就会显示 NRF24L01 Error

1.2 解决办法

其实这个问题是 引脚冲突的问题,我们可以在正点原子提供的原理图(这里我使用的是 探索者F407的原理图为例)中可以看到 NRF24L01 的接口为:

在这里插入图片描述

其对应到 MCU 上为:

在这里插入图片描述

想必这里就可以看到问题了吧,连接 SPI1 的引脚同时也被用于 JTDOJTRST。那么我们看看这两个引脚有什么作用呢?

在这里插入图片描述

那到这里大家应该都明白了吧,这是 下载接口与SPI1冲突的问题,如果想结果NRF自检不过的问题,直接拔掉下载器与开发板的接口即可

1.3 总结

在本人使用过程中发现,我使用的是一块 STM32F407ZGT6 最小系统板。当然它也是具有 JTAG 接口,兼容 ST-LINK-V2下载器。

  • 断开 ST-Link 与电脑的连接,按下 RST 仍然不能解决出现 Error 的问题,需要拔下排线与开发板的连接
  • 在个别情况下,有时候即使不拔掉下载器也能正常运行,目前作者也不知道是什么原因

二、NRF24L01 与 PC(电脑)连接无法进行收发

2.1 问题情况

在使用 NRF24L01 进行与电脑的通信过程中,在某宝购买了 USB转NRF24L01模块,链接如下:USB无线串口模块串口转nRF24L01+数传通信遥控采集 开发转接板-淘宝网 (taobao.com),如下图所示:

在这里插入图片描述

它的原理也很简单,上面有一个STC的芯片作为主控,而NRF24L01就是与该芯片进行连接,然后将输出通过USART连接到一个串口芯片CH430T上,将串口信号转成USB信号,通过电脑上位机即可实现串口信息的查看与发送。

在这里插入图片描述

我们可以看到它的介绍,可以实现一对一、一对多通信,甚至是PC与MCU相互通信,它也有配套的上位机程序,可以快速通过串口进行设置,也可以通过AT指令进行设置,可以说是非常方便啦。那么在以下情况下:

  • 我首先配置PC端的NRF模块:

    最好还是设置的时候两个地址设置成一样,否则在STM32上切换模式很麻烦

    在这里插入图片描述

    • 这里我解释下:

      • 波特率:是串口芯片的波特率,正如前面所说,该模块是通过串口将SPI信息发送给PC的

      • 目标地址目标地址通俗来讲,就是你要给哪个NRF模块发送信息,就填写这个模块的地址

      • 本地地址:显而易见,这是本模块的地址

      • 通讯频率:通讯频率不是所以为的SPI速率,这是无线信号的发射频率

      • 校验方式:可选择 8 或 16 位CRC校验,默认为16

      • 空中速率:这个就是空中传输的最大速率啦

        注:设置完成一定记得点 应用 否则无效

  • 其次我们设置STM32端的NRF模块:

    • 我使用的是正点原子的例程,板子用的是 STM32F407ZGT6 最小系统板,我们可以在正点原子的代码中看到如下:

      • 24l01.c文件

        const u8 TX_ADDRESS[TX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址
        const u8 RX_ADDRESS[RX_ADR_WIDTH]={0x34,0x43,0x10,0x10,0x01}; //发送地址
        

        这里的注释不是我写错了,这是正点原子的代码本来就是这样写的。

        我们对其进行更改,以适配我们自己设置的模块地址

        const u8 TX_ADDRESS[TX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 发送地址
        const u8 RX_ADDRESS[RX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 接收地址
        
        • 可以根据前面提到的在PC端设置情况来看

          • 发送地址:是由STM32发送给PC,而之前设置的PC端的NRF模块地址为:0xFF,0xFF,0xFF,0xFF,0xFF
          • 接收地址:是由STM32接收PC发送来的信息,而之前设置的NRF模块的目的地址为:0xA5,0xA5,0xA5,0xA5,0xA5
        • 其实这也很好理解,我们先看设置地址的代码部分:

          // NRF24L01_RX_Mode 函数中
          NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH);//写RX节点地址
          
          // NRF24L01_TX_Mode 函数中
          NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址 
          NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK	
          
          • 我们将 NRF 设置成 接收模式 的时候,在 NRF24L01_RX_Mode 函数中,写到了将 RX_ADDR_P0 数据通道0接收地址寄存器的值设置为 RX_ADDRESS 的值

          • 我们将 NRF 设置成 接收模式 的时候,在 NRF24L01_TX_Mode 函数中,写到了将TX_ADDR 发送地址寄存器的值设置为 TX_ADDRESS 的值,将 RX_ADDR_P0 数据通道0接收地址寄存器的值设置为 RX_ADDRESS 的值

            实际上呢,可以这么理解,TX_ADDR寄存器的值就是本模块在发送数据的时候需要的对方的地址,而RX_ADDR_P0寄存器的值就是应答方的地址,设应答,地址就是谁的

    • 我们在主函数简单写一段测试发送的函数:

      int main(void)
      { 
      	u8 t=0;			 
      	u8 tmp_buf[32] = {0};	
      	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
      	delay_init(168);  //初始化延时函数
      	uart_init(9600);	//初始化串口波特率为115200
       	NRF24L01_Init();    		//初始化NRF24L01 
      
      	while(NRF24L01_Check())
      	{
      		printf("NRF_ERROR!!!\r\n");
       		delay_ms(200);
      	}
      	
      	printf("NRF_TX_MODE ... \r\n");
      	delay_ms(500);
      	NRF24L01_TX_Mode();	// 设置为发送模式
      	
      	tmp_buf[0] = 'A';
      	tmp_buf[1] = 'B';
      	tmp_buf[2] = 'C';
      	tmp_buf[3] = 'D';
      	tmp_buf[4] = 'E';
          printf("Send message: ");
      	for (t=0; t < 32; t++)
      		printf("%x ", tmp_buf[t]);
          printf("\r\n");
      
       	while(1)
      	{	  		    		    				 
      		if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
      		{
      			printf("\r\nSend Date: ");
      			for (t=0; t < 32; t++)
      				printf("%x ", tmp_buf[t]);	   
      		} else {										   	
      			printf("\r\nSend failed!\r\n");
      		}
      		delay_ms(1500);
      	}   
      }
      
      • 我们可以看到这实现了一个简单的逻辑,就是循环发送 A、B、C、D、E (均为字符型),**但是我们会发现,PC端的NRF收不到任何信息

        在这里插入图片描述

在这里插入图片描述

  • 看似一切都没有问题,但是就是收不到信息,怎么改配置参数都没用,这时候就有很多人陷入了疑惑,那我相信,肯定有很多人选择再买一个这个模块,尝试PC与PC之间的通信吧,然后你会发现PC与PC没问题,反而移植到单片机上就不行了。小小的脑瓜,大大的疑惑!!!

2.2 解决方法

首先,我们看下这个 转串口模块 的使用说明,里面提到了:

在这里插入图片描述

  • 这是我们第一个需要改动的地方

    • 通过对上面的描述,我们知道了,一次发送的32字节被压缩到了31字节,第一位被用于描述数据长度,确实很过分,但毕竟是别人的通信协议。那么我们对主函数进行修改:

      这里不用着急复制,后面我会放出完整代码

      tmp_buf[0] = 5;
      tmp_buf[1] = 'A';
      tmp_buf[2] = 'B';
      tmp_buf[3] = 'C';
      tmp_buf[4] = 'D';
      tmp_buf[5] = 'E';
      printf("Send message: ");
      for (t=0; t < 32; t++)
          printf("%x ", tmp_buf[t]);
      printf("\r\n");
      

      将第一次数据填入本帧数据的有效数据长度,然后再进行发送。很多人觉得这下彻底行了,结果结局还挺让人失望的,满怀期待啊,结果还是发现收不到任何东西(啊,那个串口就很过分啊,怎么一直打印 “Send Falied!” 啊)

其实,bug并没有解决,没有那么简单。

  • 这是我们第二个需要改动的地方:

    • 我们先看到我们设置的 NRF24L01_TX_Mode() 的函数源码:

      //该函数初始化NRF24L01到TX模式
      //设置TX地址,写TX数据宽度,设置RX自动应答的地址,填充TX发送数据,选择RF频道,波特率和LNA HCURR
      //PWR_UP,CRC使能
      //当CE变高后,即进入RX模式,并可以接收数据了		   
      //CE为高大于10us,则启动发送.	 
      void NRF24L01_TX_Mode(void)
      {														 
      	NRF24L01_CE=0;	    
       	NRF24L01_Write_Buf(NRF_WRITE_REG+TX_ADDR,(u8*)TX_ADDRESS,TX_ADR_WIDTH);//写TX节点地址 
       	NRF24L01_Write_Buf(NRF_WRITE_REG+RX_ADDR_P0,(u8*)RX_ADDRESS,RX_ADR_WIDTH); //设置TX节点地址,主要为了使能ACK	  
      
        	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_AA,0x01);     //使能通道0的自动应答    
        	NRF24L01_Write_Reg(NRF_WRITE_REG+EN_RXADDR,0x01); //使能通道0的接收地址  
        	NRF24L01_Write_Reg(NRF_WRITE_REG+SETUP_RETR,0x1a);//设置自动重发间隔时间:500us + 86us;最大自动重发次数:10次
        	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);       //设置RF通道为40
        	NRF24L01_Write_Reg(NRF_WRITE_REG+RF_SETUP,0x0f);  //设置TX发射参数,0db增益,2Mbps,低噪声增益开启   
        	NRF24L01_Write_Reg(NRF_WRITE_REG+CONFIG,0x0e);    //配置基本工作模式的参数;PWR_UP,EN_CRC,16BIT_CRC,接收模式,开启所有中断
      	NRF24L01_CE=1;//CE为高,10us后启动发送
      }
      
      • 这段函数设置了各种各样的模式,你要粗略的一看,好像确实没啥问题,但是它其实是有问题的

        • 在PC端,我们设置了NRF模块默认的通讯频率为:2.400GHz,可是在STM32上怎么没有这个参数呢?其实当你翻阅 NRF24L01 的数据手册的时候,你就会看到以下内容:

          在这里插入图片描述

          • 是不是有点眼熟啊,RF_CH 寄存器对吧,看看我们怎么设置的

            NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,40);       //设置RF通道为40
            

            其中 NRF_WRITE_REG 表示 写寄存器RF_CH 是该寄存器的地址,我们向这个寄存器写入了40,那代入上面的公式:
            F 0 = 2400 + R F _ C H = 2400 + 40 = 2440 ( M H z ) = 2.440 ( G H z ) F_0=2400+RF\_CH=2400+40=2440(MHz)=2.440(GHz) F0=2400+RF_CH=2400+40=2440(MHz)=2.440(GHz)
            那这就有问题了吧,频率都不匹配,怪不得收不到数据,那么我们就有了思路,进行以下更改:

            NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,0);       //设置RF通道为0
            

由此,我们结束改 bug 的工作,再次运行代码,发现能正常工作了,可喜可贺啊!!!

在这里插入图片描述

在这里插入图片描述

2.3 总结

  • 要想实现通信,首先需要明确通信数据帧格式,其次需要使两个模块进行匹配

以下就是主函数部分:

  • STM32的NRF设置为发数据模式:

    #include "sys.h"
    #include "delay.h"
    #include "usart.h"
    #include "spi.h"
    #include "24l01.h"	 
    
    /*
    *	[注意]:一定要记得更改 24l01.c 中的 NRF24L01_TX_Mode() 函数里面的:
    *		    NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,0);       //设置RF通道为40
    *	[注意]:要根据你自己设置的模块地址,更改模块的地址信息,同样在 24l01.c 中
    *			const u8 TX_ADDRESS[TX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 发送地址
    *			const u8 RX_ADDRESS[RX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 接收地址
    */
    	
    int main(void)
    { 
    	u8 t=0;			 
    	u8 tmp_buf[32] = {0};	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    	delay_init(168);  //初始化延时函数
    	uart_init(9600);	//初始化串口波特率为115200
     	NRF24L01_Init();    		//初始化NRF24L01 
    
    	while(NRF24L01_Check())
    	{
    		printf("NRF_ERROR!!!\r\n");
     		delay_ms(200);
    	}
    	printf("NRF_OK!!!\r\n");
    	
    	printf("SET NRF_TX_MODE ... \r\n");
    	delay_ms(500);
    	NRF24L01_TX_Mode();
    	
    	/*
    	*	组装数据帧,第一位是有效数据的长度
    	*/
    	tmp_buf[0] = 5;
    	tmp_buf[1] = 'A';
    	tmp_buf[2] = 'B';
    	tmp_buf[3] = 'C';
    	tmp_buf[4] = 'D';
    	tmp_buf[5] = 'E';
    	printf("Send message: ");
    	for (t=0; t < 32; t++)
    		printf("%x ", tmp_buf[t]);
    	printf("\r\n");
    
     	while(1)
    	{	  		    		    				 
    		if(NRF24L01_TxPacket(tmp_buf)==TX_OK)
    		{
    			printf("\r\nSend Date: ");
    			for (t=0; t < 32; t++)
    				printf("%x ", tmp_buf[t]);	   
    		}else
    		{										   	
    			printf("\r\nSend failed!\r\n");
    		}
    		delay_ms(1500);
    	} 
    }
    
  • 同时,实现接收数据代码:

    #include "sys.h"
    #include "delay.h"
    #include "usart.h"
    #include "spi.h"
    #include "24l01.h"	 
    
    /*
    *	[注意]:一定要记得更改 24l01.c 中的 NRF24L01_RX_Mode() 函数里面的:
    *		    NRF24L01_Write_Reg(NRF_WRITE_REG+RF_CH,0);       //设置RF通道为40
    *	[注意]:要根据你自己设置的模块地址,更改模块的地址信息,同样在 24l01.c 中
    *			const u8 TX_ADDRESS[TX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 发送地址
    *			const u8 RX_ADDRESS[RX_ADR_WIDTH]={0xA5,0xA5,0xA5,0xA5,0xA5}; // 接收地址
    */
    	
    int main(void)
    { 
    	u8 t=0;			 
    	u8 tmp_buf[32] = {0};	
    	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//设置系统中断优先级分组2
    	delay_init(168);  //初始化延时函数
    	uart_init(9600);	//初始化串口波特率为115200
     	NRF24L01_Init();    		//初始化NRF24L01 
    
    	while(NRF24L01_Check())
    	{
    		printf("NRF_ERROR!!!\r\n");
     		delay_ms(200);
    	}
    	printf("NRF_OK!!!\r\n");
    	
    	printf("SET NRF_RX_MODE ... \r\n");
    	delay_ms(500);
    	NRF24L01_RX_Mode();
    	
    
     	while(1)
    	{	  		    		    				 
    		if(NRF24L01_RxPacket(tmp_buf)==0)//一旦接收到信息,则显示出来.
    		{
    			printf("\r\nRecive Date: ");
    			for (t=0; t < 32; t++)
    				printf("%x ", tmp_buf[t]);
    		}else delay_us(100);
    	} 
    }
    
    • 这里需要更改 RX_ADDRESS 如下:

      • 最好还是设置的时候两个地址设置成一样,否则切换模式很麻烦
      const u8 TX_ADDRESS[TX_ADR_WIDTH]={0xFF,0xFF,0xFF,0xFF,0xFF}; // 发送地址
      const u8 RX_ADDRESS[RX_ADR_WIDTH]={0xA5,0xA5,0xA5,0xA5,0xA5}; // 接收地址
      
    • 附结果图:

      在这里插入图片描述

      其第一个字节为0x03是因为这是模块协议中自带的,第一位为发送的数据长度,在说明文档中有写:

      在这里插入图片描述

三、写在最后

遇到问题还是要多去看看源码,别人的代码不一定都通用,多去翻翻芯片数据手册,即使他是英文的,但现在不是有那么多翻译工具吗,试着去理解,不能一味地复制粘贴,那多没意思啊。最后,希望看到这篇文章的你,能够顺利解决bug,哈哈哈,说来惭愧,这个问题也困扰了我几个小时,本着开源精神,大家一起参考借鉴。

最后的最后,介绍下自己,欢迎大家关注我的 CSDN 账号,以后有好的解决问题办法会及时公布开源!!!本文章的代码以后再考虑开不开源吧,目前事情还比较多,有空了会试着整理出来。

个人CSDN账号:刘梓谦_-CSDN博客

Gitee:刘佳豪 (liu-jiahaohappy) - Gitee.com

GitHub:Jiahao-Liu29 (github.com)

  • 10
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值