[stm32] NRF24L01+USART搞定有线和无线通信

 新萄京冶金矿产     |      2019-12-05 13:32

新萄京官网8522,如何判断TDCS车站采集机通信是否中断?

 

答:采集机通信中断会影响相应的测试功能,当实测数据或站场显示不正常时,首先应该查看采集机是否死机。采集机通信中断,一般可听到工控机发出嘀嘀的报警声.主画面状态条中CAN通信打×,采集机通信状态窗口会自动弹出,如未弹出可在主窗口中点击鼠标右键的“通信窗”选择项,其中通信中断的采集机的连线呈灰色。

前言

一般进行远程监控时,2.4G无线通信是充当远程数据传输的一种方法。这时就需要在现场部分具备无线数据发送装置,而在上位机部分由于一般只有串口,所以将采集到的数据送到电脑里又要在上位机端设计一个数据接收的适配器。这里基于stm32分别设计了现场部分和适配器部分,这里只是基本通信功能实现的讲解,一些复杂的技术比如加密、可靠等要根据具体的应用来设计~

新萄京官网8522 1

总体说明

这里采用stm32作为MCU,采用nRF24L01作为2.4G通信模块。其中适配器中仅仅采用了USART和NRF24L01两个主要部分,负责将下位机通过2.4G发送过来的数据通过串口发送给上位机,或者将上位机的通过串口传来的数据通过2.4G发送给下位机来实现远程监控(没有采用uc-os操作系统,也没有界面,要用串口和上位机相连);其中下位机比较复杂,因为一般下位机是一个集成的系统,包括从各种传感器收集数据、向各种类型的驱动电路发送控制命令、将数据输给打印机或显示器、和无线通信或有线通信设备进行互相通信来实现数据传输等,这里的下位机比较简单:采用uc-os实时操作系统+uc-gui负责界面显示,外接7寸TFT液晶显示屏,和适配器类似也包括USART和NRF24L01通信部分,但是因为有了操作系统和可视化交互界面,所以也有点不同,接下来开始介绍。

适配器部分

这里介绍的流程是以main函数为基准,广度拓宽知识点,最后main函数说完,整个工程的细节也就大致能了解了~

 1 int main(void){
 2   uint8_t a=0;//LED高低电压控制
 3   /* System Clocks Configuration */
 4   RCC_Configuration();                                              //系统时钟设置    
 5   /*嵌套向量中断控制器 
 6       说明了USART1抢占优先级级别0(最多1位) ,和子优先级级别0(最多7位) */ 
 7   NVIC_Configuration();                                              //中断源配置
 8   /*对控制LED指示灯的IO口进行了初始化,将端口配置为推挽上拉输出,口线速度为50Mhz。PA9,PA10端口复用为串口1的TX,RX。
 9   在配置某个口线时,首先应对它所在的端口的时钟进行使能。否则无法配置成功,由于用到了端口B, 因此要对这个端口的时钟
10   进行使能,同时由于用到复用IO口功能用于配置串口。因此还要使能AFIO(复用功能IO)时钟。*/
11   GPIO_Configuration();                                              //端口初始化
12   SPI2_NRF24L01_Init();                                           //SPI2及NRF24L01接口初始化  
13   USART_Config(USART1);                                              //串口1初始化
14   /*NRF24L01设置为接收模式*/
15   RX_Mode(); 
16 
17    while (1)
18   {
19     if(usart_rec_flag==1) //判断是否收到一帧有效数据
20     {                                                  
21         usart_rec_flag=0;
22         NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF));
23         if(a==0){GPIO_SetBits(GPIOB, GPIO_Pin_5);a=1;}          //LED1  明暗闪烁                
24         else{GPIO_ResetBits(GPIOB, GPIO_Pin_5);a=0;}
25     }
26     if(rf_rec_flag==1)
27     {
28           rf_rec_flag=0;
29            for(i=0;i<32;i++)//发送字符串
30         {
31             USART_SendChar(USART1,TxBufferUSART[i]);
32         //    Delay(0x0000ff00);
33         }
34     }
35   }
36 }

第4行RCC初始化主要是系统时钟和外设时钟配置,这里注意要使能RCC_APB2Periph_USART1,当时忘了使能这个结果串口出现异常,我还以为是初始化和中断向量什么的弄错了呢,浪费了很长时间。

1 /*--------------------------------------------------------------------------------------
2 系统时钟配置为72MHZ+外设时钟配置*/ 
3 void RCC_Configuration(void){
4    SystemInit(); 
5    RCC_APB2PeriphClockCmd( RCC_APB2Periph_USART1 |RCC_APB2Periph_GPIOA | RCC_APB2Periph_GPIOB |RCC_APB2Periph_AFIO  , ENABLE);  
6 }

第7行中断向量初始化设置,主要是设置串口接收中断和NRF24L01中断的,这样设置好了之后当串口中断被触发时其对应的中断子程序将被执行(这个科班的大概都知道这里就不多说了),所以我们就要在stm32f10x_it.c里实现他们各自的中断子程序了(这个一会再详细介绍,咱们先把整个框架了解下)。另外说一句,这里的的优先级组将影响主优先级和子优先级数量具体请参考stm32f10X_的固件库的NVIC.

 1 void NVIC_Configuration(void){
 2  /*  结构声明*/
 3   NVIC_InitTypeDef NVIC_InitStructure;
 4   EXTI_InitTypeDef EXTI_InitStructure;        
 5   
 6   /* 优先级组 1  */    
 7   NVIC_PriorityGroupConfig(NVIC_PriorityGroup_1);           
 8   
 9   /* Enable the USART1 Interrupt */
10   NVIC_InitStructure.NVIC_IRQChannel = USART1_IRQn;                     //设置串口1中断
11   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;             //抢占优先级 0
12   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0;                //子优先级为0
13   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                    //使能
14   NVIC_Init(&NVIC_InitStructure);                                              
15 
16                                                                     
17   NVIC_InitStructure.NVIC_IRQChannel = EXTI0_IRQn;                    //NRF24L01 中断响应
18   NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;            //抢占优先级 0
19   NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;                //子优先级为1
20   NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;                    //使能
21   NVIC_Init(&NVIC_InitStructure);                                                
22 
23   GPIO_EXTILineConfig(GPIO_PortSourceGPIOA, GPIO_PinSource0);       //NRF24L01 IRQ  PA0
24   
25   EXTI_InitStructure.EXTI_Line = EXTI_Line0;                       //NRF24L01 IRQ PA0
26   EXTI_InitStructure.EXTI_Mode = EXTI_Mode_Interrupt;               //EXTI中断
27   EXTI_InitStructure.EXTI_Trigger = EXTI_Trigger_Falling;           //下降沿触发
28   EXTI_InitStructure.EXTI_LineCmd = ENABLE;                           //使能
29   EXTI_Init(&EXTI_InitStructure);    
30 }

第11行的GPIO初始化,主要是对通用IO口的属性设置和初始化,这里一定要对串口所需的A9和A10配置好!

 1 void GPIO_Configuration(void){
 2   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;                     //LED1控制--PB5
 3   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;             //推挽输出
 4   GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
 5   GPIO_Init(GPIOB, &GPIO_InitStructure);                     
 6 
 7   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;                      //USART1 TX
 8   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;             //复用推挽输出
 9   GPIO_Init(GPIOA, &GPIO_InitStructure);                     //A端口 
10 
11   GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;                  //USART1 RX
12   GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;        //复用开漏输入
13   GPIO_Init(GPIOA, &GPIO_InitStructure);                      //A端口 
14 }

第12行的SPI2_NRF24L01_Init();主要是驱动NRF24L01的接口初始化,因为NRF24L01采用的是SPI通信,所以这里免不了SPI的设置和相关操作了,不过幸好都封装好了~像以前在51上做SPI就得自己模拟SPI,没有示波器调试起来甚是坑~此外这里我已经把NRF24L01的整个驱动都封装在NRF24L01.c这个文件里了,当想用的时候只要在中断向量里设置其中断接收函数,并在it.c里实现其接收函数;一般主函数里用到的是其初始化函数SPI2_NRF24L01_Init();和 RX_Mode();,当在过程中想利用NRF24L01向外发数据时只要调用函数void NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes):

 1 /****************************************************************************
 2 * 名    称:NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)
 3 * 功    能:将保存在接收缓存区的32字节的数据通过NRF24L01+发送出去
 4 * 入口参数:data_buffer   待发送数据
 5             Nb_bytes      待发送数据长度
 6 * 出口参数:无
 7 * 说    明:数据小于32,把有效数据外的空间用0填满
 8 * 调用方法:RX_Mode();
 9 ****************************************************************************/
10 void NRF_Send_Data(uint8_t* data_buffer, uint8_t Nb_bytes)
11 {  
12     uchar i=0;  
13     MODE_CE(0);                                 //NRF 模式控制     
14 
15     SPI_RW_Reg(WRITE_REG1+STATUS,0xff);         //设置状态寄存器初始化
16     SPI_RW_Reg(0xe1,0);                         //清除TX FIFO寄存器
17     SPI_RW_Reg(0xe2,0);                         //清除RX FIFO寄存器
18     TX_Mode();                                 //设置为发送模式
19     delay_ms(1);
20     if(Nb_bytes<32){                         //当接收到的USB虚拟串口数据小于32,把有效数据外的空间用0填满
21         for(i=Nb_bytes;i<32;i++) data_buffer[i]=0;
22     }
23     MODE_CE(0);
24       SPI_Write_Buf(WR_TX_PLOAD, data_buffer, TX_PLOAD_WIDTH);        //发送32字节的缓存区数据到NRF24L01
25     MODE_CE(1);                                                        //保持10us以上,将数据发送出去        
26 } 

第13行是USART初始化,包括波特率、数据位、停止位等~

 1 void USART_Config(USART_TypeDef* USARTx){
 2   USART_InitStructure.USART_BaudRate = 9600;                        //速率9600bps
 3   USART_InitStructure.USART_WordLength = USART_WordLength_8b;        //数据位8位
 4   USART_InitStructure.USART_StopBits = USART_StopBits_1;            //停止位1位
 5   USART_InitStructure.USART_Parity = USART_Parity_No;                //无校验位
 6   USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   //无硬件流控
 7   USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;                    //收发模式
 8 
 9   /* Configure USART1 */
10   USART_Init(USARTx, &USART_InitStructure);                            //配置串口参数函数
11  
12   
13   /* Enable USART1 Receive and Transmit interrupts */
14   USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                    //使能接收中断
15   USART_ITConfig(USART1, USART_IT_TXE, ENABLE);                        //使能发送缓冲空中断   
16 
17   /* Enable the USART1 */
18   USART_Cmd(USART1, ENABLE);    
19 }

同样的类似于NRF24L01一旦初始化之后,其数据接收一般采用中断方式、数据发送一般采用直接发送的方式。所以在中断向量里也要设置,也要在it.c中实现其接收中断子函数。其发送直接调用stm32f10的固件库函数(这里我稍加封装了下):其实就是发送一个data之后要监听是否发送完成才能进行下次发送~

1 void USART_SendChar(USART_TypeDef* USARTx,uint8_t data){
2     USART_SendData(USARTx,data);
3     while(USART_GetFlagStatus(USARTx,USART_FLAG_TC)==RESET);
4 }

接下来进入while循环,不断进行监听看是否有串口接收标志位置1或者无线模块接收标志位置1,如果有表明相应的有数据从该通道传送过来。当是从串口传来的数据表明数据是从上位机发送来的数据,并且想把该数据通过2.4G发送出去,所以这里调用:NRF_Send_Data(TxBufferRF,sizeof(TxBufferRF));将数据发送出去;当数据是从2.4G通道中传过来的,表明数据是从下位机传送过来的想给上位机,于是调用串口发送函数将数据发送给上位机:USART_SendChar(USART1,TxBufferUSART[i]);

新萄京官网8522 2

看了上图适配器端的数据交换过程就明白了串口中断和无线中断大致要干的事了,这里我就不多介绍,看看下面的代码就明白了(在stm32f10x_it.c中),要再次提醒的是无论是串口还是无线其接收都是采用中断,而发送采用循环直接发送,他们的中断和中断向量有关并要在stm32f10x_it.c里实现相应的中断子程序~

新萄京官网8522 3新萄京官网8522 4

 1 /******************************************************************************/
 2 /*            STM32F10x Peripherals Interrupt Handlers                        */
 3 /******************************************************************************/
 4 
 5 /**
 6   * @brief  This function handles USART1 global interrupt request.
 7   * @param  None
 8   * @retval : None
 9   */
10 void USART1_IRQHandler(void)      //串口1 中断服务程序
11 {
12   unsigned int i;
13   if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)       //判断读寄存器是否非空
14   {    
15     
16     RxBufferUSART[RxCounter1++] = USART_ReceiveData(USART1);   //将读寄存器的数据缓存到接收缓冲区里
17     
18     if(RxBufferUSART[RxCounter1-2]==0x0d&&RxBufferUSART[RxCounter1-1]==0x0a)     //判断结束标志是否是0x0d 0x0a
19     {
20       for(i=0; i< RxCounter1; i++) TxBufferRF[i] = RxBufferUSART[i];          //将接收缓冲器的数据转到发送缓冲区,准备转发
21       usart_rec_flag=1;                                                             //接收成功标志
22       TxBufferRF[RxCounter1]=0;                                             //发送缓冲区结束符    
23       TxCounter1=RxCounter1;
24       RxCounter1=0;
25     }
26   }
27   
28   if(USART_GetITStatus(USART1, USART_IT_TXE) != RESET)                   //这段是为了避免STM32 USART 第一个字节发不出去的BUG 
29   { 
30      USART_ITConfig(USART1, USART_IT_TXE, DISABLE);                         //禁止发缓冲器空中断, 
31   }    
32 }  
33 /*******************************************************************************
34 * Function Name  : EXTI0 中断函数
35 * Description    : NRF24L01中断服务程序
36 * Input          : None
37 * Output         : None
38 * Return         : None
39 *******************************************************************************/
40 void EXTI0_IRQHandler(void){
41     u8 i=0;
42      u8 status;    
43     if(EXTI_GetITStatus(EXTI_Line0) != RESET)            //判断是否产生了EXTI0中断
44       {
45         if(GPIO_ReadInputDataBit(GPIOA,GPIO_Pin_0)==0){ //判断是否是PA0线变低            
46             status=SPI_Read(READ_REG1+STATUS);            // 读取状态寄存其来判断数据接收状况    
47             if(status & 0x40)                            // 判断是否接收到数据                   
48             {            
49                 //GPIO_ResetBits(GPIOB, GPIO_Pin_5);   
50                  SPI_Read_Buf(RD_RX_PLOAD,rx_buf,TX_PLOAD_WIDTH);  //从接收缓冲区里读出数据
51                 for(i=0; i<32; i++)TxBufferUSART[i] = rx_buf[i];  //向USB 端点1的缓冲区里放置数据      
52                 rf_rec_flag=1;
53             }
54             else if((status &0x10)>0){                     //发射达到最大复发次数                
55                 SPI_RW_Reg(0xe1,0);                          //清除发送缓冲区                  
56                 RX_Mode();                                 //进入接收模式                   
57             }
58             else if((status &0x20)>0){                     //发射后收到应答 
59                 GPIO_SetBits(GPIOB, GPIO_Pin_5);   
60                 SPI_RW_Reg(0xe1,0);                         //清除发送缓冲区              
61                 RX_Mode();                                 //进入接收模式                   
62             }
63             SPI_RW_Reg(WRITE_REG1+STATUS, status);         //清除07寄存器标志
64         }        
65         EXTI_ClearITPendingBit(EXTI_Line0);             //清除EXTI0上的中断标志              
66     } 
67 }

串口和无线接收中断子程序

下位机部分

上面说过一般具有远程通信能力的嵌入式系统其下位机部分往往要干很多事,这里我们采用stm32作为MCU并搭载uc-OS实时操作系统负责任务调度,同时采用7寸TFT彩屏和uc-GUI设计可视化人机交互界面。其中任务包括主任务、界面任务和触摸任务,主任务负责建立其他任务,界面任务中将包含整个人机交互界面的界面刷新逻辑,触摸任务负责获取触摸位置数据获取~