精华内容
下载资源
问答
  • 因此在stm32的串口中断中,还有一个IDLE中断,用来产生串口接受一帧数据而产生的中断,比如说串口发来n个数据,会产生n次接收中断和一次IDLE中断,因此方便使用者来接收主机发送或者从机返回的数据! 原文链接:...

    stm32支持接受单个数据或者一帧数据,若配置单个数据接收中断的话,会出现接收包丢包,数据不完整的情况!因此在stm32的串口中断中,还有一个IDLE中断,用来产生串口接受一帧数据而产生的中断,比如说串口发来n个数据,会产生n次接收中断和一次IDLE中断,因此方便使用者来接收主机发送或者从机返回的数据!

    原文链接:https://blog.csdn.net/qq_35341807/article/details/79157437

    1、配置串口中断

    void USART1_Configuration(void)
    {
    	USART_InitTypeDef USART_InitStructure;
    	GPIO_InitTypeDef GPIO_InitStructure;
    	/* Configure USART1 Rx (PA.10) as input floating */
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_10;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING;		  //¸¡¿ÕÊäÈëģʽ	   
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	/* Configure USART1 Tx (PA.09) as alternate function push-pull */
    	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_9;
    	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;			  //¸
    	GPIO_Init(GPIOA, &GPIO_InitStructure);
    	
    	USART_InitStructure.USART_BaudRate = 9600;						//
    	USART_InitStructure.USART_WordLength = USART_WordLength_8b;		//
    	USART_InitStructure.USART_StopBits = USART_StopBits_1;			//
    	USART_InitStructure.USART_Parity = USART_Parity_No;				//
    	USART_InitStructure.USART_HardwareFlowControl = USART_HardwareFlowControl_None;   //
    	USART_InitStructure.USART_Mode = USART_Mode_Rx | USART_Mode_Tx;					
    	/* Configure USART1 */
    	USART_Init(USART1, &USART_InitStructure);							
    	/* Enable USART1 Receive and Transmit interrupts */
    	USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);                    //
    	USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//
    	/* Enable the USART1 */
    	USART_Cmd(USART1, ENABLE);	                  //
    }
    

    关键

    USART_ITConfig(USART1, USART_IT_RXNE, ENABLE);//没收到一个字节进入一次中断
    USART_ITConfig(USART1, USART_IT_IDLE, ENABLE); //开启串口空闲中断,每收到一帧数据进入一次中断
    

    2、串口中断函数

    处理一帧数据中所需要的内容

    一帧数据原始内容:
    在这里插入图片描述
    我要提取出这帧数据中的31.93

    char rece_buffer[BUFSIZ];
    int RxCounter;
    
    void USART1_IRQHandler(void)	
    {
    	 u8 clear=clear;
    	 USART_ClearFlag(USART1,USART_FLAG_TC);
     //接收一帧数据
    	 if(USART_GetITStatus(USART1,USART_IT_RXNE)!=Bit_RESET)        
    	   {
     
               rece_buffer[RxCounter++]=USART1->DR;
    	   }
    		 
    	 else if(USART_GetFlagStatus(USART1,USART_FLAG_IDLE)!=Bit_RESET)
    	        {					
                    clear=USART1->SR;
                    clear=USART1->DR;						
    	              RxCounter=0;
    						
    						//温度传感器的一帧数据,对需要的内容进行提取
    						if((rece_buffer[0]=='o')&&(rece_buffer[1]=='b')&&(rece_buffer[2]=='j'))
    						{
    								if(rece_buffer[9]=='-')  //¸ºÊý
    								{
    								;
    								}
    						else
                {
    								if(rece_buffer[10]=='.')   //x.xx
    								{
    								obj=(rece_buffer[9]-0x30)*100+(rece_buffer[11]-0x30)*10+(rece_buffer[2]-0x30);
    								}
    								if(rece_buffer[11]=='.')   //xx.xx
    								{
    								obj=(rece_buffer[9]-0x30)*1000+(rece_buffer[10]-0x30)*100+(rece_buffer[12]-0x30)*10+(rece_buffer[13]-0x30);
    								}
    								if(rece_buffer[12]=='.')   //xxx.xx
    								{
    								obj=(rece_buffer[9]-0x30)*10000+(rece_buffer[10]-0x30)*1000+(rece_buffer[11]-0x30)*100+(rece_buffer[13]-0x30)*10+(rece_buffer[14]-0x30)*10;
    								}
    								
    						    obj_T=obj/100;
    						}
    						printf("目标温度:%f",obj_T);
    						}
    						//printf("%s",rece_buffer);
                    }	
    					
    }
    

    最终获得需要是数据
    在这里插入图片描述

    展开全文
  • 串口完整帧数据接收的实现方式

    万次阅读 多人点赞 2018-08-23 10:21:28
    本人采用的STM32HAL库,部分函数为库函数提供,其中硬件初始化反初始化函数部分需要自己实现,这里不给出实现代码,数据帧接收实现方式基本通用于所有串口通信,以下是实现的代码。 附:如果你想使用字符串形式的...

    本人采用的STM32HAL库,部分函数为库函数提供,其中硬件初始化反初始化函数部分需要自己实现,这里不给出实现代码,数据帧接收实现方式基本通用于所有串口通信,以下是实现的代码。

    附:如果你想使用字符串形式的协议,可参考我微信公众号中的这篇文章(https://mp.weixin.qq.com/s/qP4wxFnwK5aUR1YgX4F9bA),协议解析更加简单更加严密,协议格式是"AT+CMD=XXXX\r\n"。

    所使用的协议格式:AA 55/56/57... length data sum(帧头:AA 55/56/57...;length:剩余数据长度;data:有效数据区;sum:校验和不包含帧头),可识别多种帧头数据,只需添加第二个帧头即可;

    #ifndef	__UART_H
    #define __UART_H
    
    #include "stm32f1xx_hal.h"
    
    #include "./UART/uart1.h"
    #include "./UART/uart2.h"
    #include "./UART/uart3.h"
    #include "./UART/uart4.h"
    #include "./UART/uart5.h"
    
    /*帧头长度*/
    #define FRAME_HEADER_LENGTH     2U
    /*帧尾长度(即校验和)*/
    #define FRAME_TAIL_LENGTH       1U
    
    /*帧头相同字节(第一字节)*/
    #define FRAME_HEAD_SAME_AA      0xAA
    /*帧头区别字节(第二字节)*/
    #define FRAME_HEAD_DIFF_55      0x55
    #define FRAME_HEAD_DIFF_56      0x56
    #define FRAME_HEAD_DIFF_57      0x57
    
    /*接收缓冲区长度*/
    #define RX_BUF_1_LENGTH         50U
    
    /*接收协议公共变量*/
    typedef struct{
      volatile uint8_t step;           /*switch 语句跳转条件*/
      volatile uint8_t tmpCnt;         /*用于计数的临时变量*/
      volatile uint8_t aRxBufIndex;    /*接收数据缓冲区索引*/
      uint8_t aRxBuf_1[RX_BUF_1_LENGTH];
    }protocolComType_t;
    
    /*串口接收协议结构体*/
    typedef struct{
      protocolComType_t  uart1Ptc;
      protocolComType_t  uart2Ptc;
      protocolComType_t  uart3Ptc;
      protocolComType_t  uart4Ptc;
      protocolComType_t  uart5Ptc;
    }uartPtcType_t;
    
    extern uartPtcType_t uartPtc;/*声明接收结构体*/
    
    /*校验和计算*/
    uint8_t CheckSumCal(uint8_t *pData,uint32_t num);
    
    #endif/*__UART_H*/
    
    #include "./UART/uart.h"
    
    /*是否使用定时器监测串口,0U不使用,1U使用*/
    #define IF_USE_TIM_MONITOR    0U
    
    uartPtcType_t uartPtc;/*定义一个接收结构体*/
    
    /*串口1接收完成回调函数*/
    static void HAL_UART1_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
    /*串口2接收完成回调函数*/
    static void HAL_UART2_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
    /*串口3接收完成回调函数*/
    static void HAL_UART3_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
    /*串口4接收完成回调函数*/
    static void HAL_UART4_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
    /*串口5接收完成回调函数*/
    static void HAL_UART5_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data);
    
    /*校验和计算*/
    uint8_t CheckSumCal(uint8_t *pData,uint32_t num)
    {
        if(pData == NULL){ return 0x00; }
        if(num == 0){ return 0x00; }
    	
        /*将校验和字节置为0x00*/
        pData[num - 1] = 0x00;
        /*除去帧头和校验位本身的校验和*/
        for(uint32_t i = 0;i<(num - FRAME_HEADER_LENGTH - FRAME_TAIL_LENGTH);i++)
        {
            /*仅保留低位*/
            pData[num - 1] += (0xff & (pData[i + FRAME_HEADER_LENGTH]));
        }
        return (pData[num - 1]);
    }
    
    /*串口外设初始化*/
    /*
    被调用流程:
    用户串口初始化函数->HAL_UART_Init();->HAL_UART_MspInit();->根据不同串口调用不同 Msp 初始化函数
    */
    void HAL_UART_MspInit(UART_HandleTypeDef *huart)
    {
      /*用于检测到串口发生 ORE 错误时,清除此标志位,以使串口恢复工作*/
      #if (IF_USE_TIM_MONITOR > 0U)
      TIM_InitCfg();
      #endif
      
      if((huart->Instance) == USART1)
      {
        HAL_UART1_MspInit(huart);
      }
      else if((huart->Instance) == USART2)
      {
        HAL_UART2_MspInit(huart);
      }
      else if((huart->Instance) == USART3)
      {
        HAL_UART3_MspInit(huart);
      }
      else if((huart->Instance) == UART4)
      {
        HAL_UART4_MspInit(huart);
      }
      else if((huart->Instance) == UART5)
      {
        HAL_UART5_MspInit(huart);
      }
    }
    
    /*串口外设反初始化*/
    void HAL_UART_MspDeInit(UART_HandleTypeDef	*huart)
    {
      if((huart->Instance) == USART1)
      {
        HAL_UART1_MspDeInit();
      }
      else if((huart->Instance) == USART2)
      {
        HAL_UART2_MspDeInit();
      }
      else if((huart->Instance) == USART3)
      {
        HAL_UART3_MspDeInit();
      }
      else if((huart->Instance) == UART4)
      {
        HAL_UART4_MspDeInit();
      }
      else if((huart->Instance) == UART5)
      {
        HAL_UART5_MspDeInit();
      }
    }
    
    /*发送完成回调函数*/
    void HAL_UART_TxCpltCallback(UART_HandleTypeDef *huart)
    {
      if((huart->Instance) == USART1)
      {
      }
      else if((huart->Instance) == USART2)
      {
      }
      else if((huart->Instance) == USART3)
      {
      }
      else if((huart->Instance) == UART4)
      {
      }
      else if((huart->Instance) == UART5)
      {
      }
    }
    /*接收完成回调函数*/
    void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
    {
        if((huart->Instance) == USART1)
        {
            HAL_UART1_RxCpltCallback(&uartPtc.uart1Ptc,uart1SingleByteRecBuf);
        }
        else if((huart->Instance) == USART2)
        {
            HAL_UART2_RxCpltCallback(&uartPtc.uart2Ptc,uart2SingleByteRecBuf);
        }
        else if((huart->Instance) == USART3)
        {
            HAL_UART3_RxCpltCallback(&uartPtc.uart3Ptc,uart3SingleByteRecBuf);
        }
        else if((huart->Instance) == UART4)
        {
            HAL_UART4_RxCpltCallback(&uartPtc.uart4Ptc,uart4SingleByteRecBuf);
        }
        else if((huart->Instance) == UART5)
        {
            HAL_UART5_RxCpltCallback(&uartPtc.uart5Ptc,uart5SingleByteRecBuf);
        }
    }
    
    /*初始化结构体变量*/
    static void InitPtcStruct(protocolComType_t *pUartHandle)
    {
      pUartHandle->step         = 0;
      pUartHandle->tmpCnt       = 0;
      pUartHandle->aRxBufIndex  = 0;
    }
    
    /*串口1接收完成回调函数*/
    static void HAL_UART1_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
    {
        switch(pUartHandle->step)
        {
            case 0:
                if(data == FRAME_HEAD_SAME_AA)/*帧头正确*/
                {
                    pUartHandle->step++;/*跳转下一步骤*/
                    pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
                }
            break;
            case 1:
                if((data == FRAME_HEAD_DIFF_55) || (data == FRAME_HEAD_DIFF_56) || (data == FRAME_HEAD_DIFF_57))/*帧头正确*/
                {
                    pUartHandle->step++;/*跳转下一步骤*/
                    pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
                }
                else if(data == FRAME_HEAD_SAME_AA)
                    pUartHandle->step = 1;/*第一帧头重复,回到第二帧头判断处,AA AA 情况*/
                else
                    InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
            break;
            case 2:
                pUartHandle->tmpCnt = data;/*临时计数值*/
                pUartHandle->step++;/*跳转下一步骤*/
                pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;/*压入缓冲区*/
                if(((RX_BUF_1_LENGTH - pUartHandle->aRxBufIndex) < data) || (data == 0))
                {/*缓冲区溢出或数据长度为 0*/
                    InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
                }
            break;
            case 3:
                if(--pUartHandle->tmpCnt)
                {/*接收数据到缓冲区*/
                    pUartHandle->aRxBuf_1[pUartHandle->aRxBufIndex++] = data;
                    if(pUartHandle->aRxBufIndex >= RX_BUF_1_LENGTH)
                    {/*长度被意外修改,导致缓冲区溢出*/
                        InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
                    }
                }
                else
                {
                    /*检查校验和并写入缓冲区*/
                    if(CheckSumCal(pUartHandle->aRxBuf_1,pUartHandle->aRxBufIndex + 1) == data)
                    {
    //                PRINTF("uart1\n");
    //                for(uint32_t i = 0;i<pUartHandle->aRxBufIndex + 1;i++)
    //                PRINTF("%02x\t",pUartHandle->aRxBuf_1[i]);PRINTF("\n");
                
                    /*这里可结合上篇文章将数据存入环形缓冲区(也可不存直接处理这里接收的数据,不过数据量太大时可能会丢帧),并且设置标志位或是发送信号量给任务以处理接收到的数据*/
                    }
    					
                    InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
                }
            break;
            
            default:
                InitPtcStruct(pUartHandle);/*初始化结构体值,准备下一次接收*/
            break;
        }
    		
        #if UART1_USE_DMA
        /*DMA 接收开启循环模式不需要再次触发*/
        #else
        HAL_UART_Receive_IT(&huart1,&uart1SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
        #endif
    }
    
    /*串口2接收完成回调函数*/
    static void HAL_UART2_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
    {
      (void)pUartHandle;
      (void)data;
    
      #if UART2_USE_DMA
      /*DMA 接收开启循环模式不需要再次触发*/
      #else
      HAL_UART_Receive_IT(&huart2,&uart2SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
      #endif
    }
    
    /*串口3接收完成回调函数*/
    static void HAL_UART3_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
    {
      (void)pUartHandle;
      (void)data;
      
      #if UART3_USE_DMA
      /*DMA 接收开启循环模式不需要再次触发*/
      #else
      HAL_UART_Receive_IT(&huart3,&uart3SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
      #endif
    }
    
    /*串口4接收完成回调函数*/
    static void HAL_UART4_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
    {
      (void)pUartHandle;
      (void)data;
      
      #if UART4_USE_DMA
      /*DMA 接收开启循环模式不需要再次触发*/
      #else
      HAL_UART_Receive_IT(&huart4,&uart4SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
      #endif
    }
    
    /*串口5接收完成回调函数*/
    static void HAL_UART5_RxCpltCallback(protocolComType_t *pUartHandle,uint8_t data)
    {
      (void)pUartHandle;
      (void)data;
      
      HAL_UART_Receive_IT(&huart5,&uart5SingleByteRecBuf,1);/*再次开启中断,触发下一次接收*/
    }
    

    uart(x)SingleByteRecBuf:(x)为数字1-5,此变量定义在串口初始化文件中,这里没有给出,在串口初始化完成后需要调用一次开启接收中断函数(即HAL_UART_Receive_IT();或HAL_UART_Receive_DMA();)。

    所有串口均可直接复制串口一中的代码直接使用(只需要修改数据存储部分和标志位设置或是信号量发送部分),那为什么代码完全相同还要分开实现呢,因为每个串口接入的设备不一样,通讯协议也不一定一样,这样可以分开实现,灵活性更强。

    代码实现已处理发现的所有边界条件,可直接用于项目中,未经允许,请勿转载。

    展开全文
  • 串口数据帧同步

    2020-03-25 21:44:57
    每次拿到缓冲区数据之后,判断是否满足帧头,进而开始新的一帧数据计数存储, 最终满足一帧数据之后 将数据发送出来. 原理 我们使用自定义的存储结果 作为自己的存储结果, 一般来说在项目中使用就是帧数据长度是一致的,...

    介绍

    串口是十分常用的一个资源, 每次需要进行处理串口数据, 但是 PC 上的串口存在缓冲区机制, 导致串口跟我们在单片机中的使用方式可能有所不同,
    每次拿到缓冲区数据之后,判断是否满足帧头,进而开始新的一帧数据计数存储, 最终满足一帧数据之后 将数据发送出来.

    原理

    我们使用自定义的存储结果 作为自己的存储结果, 一般来说在项目中使用就是帧数据长度是一致的, 我们的程序也是基于这个原理的

    class SerialData
    {
        public:
        uchar *data_;   ///< 帧数据
        uchar *head_;   ///< 帧头数据
        int dat_len_;   ///< 数据帧长度
    }
    

    我们需要根据自己的协议初始化数据长度和数据帧头内容 假设我们的数据为 14 个字节 包括 一般使用串口通信的数据都是按照这种格式进行的数据发送,

    [帧头] [长度] [地址] [指令] [数据] [校验]
    00 01 02 03 04 05 06 07 08 09 10 11 12 13
    68 0D 00 84 00 04 09 11 79 45 00 81 13 01

    frame_len = 14;
    data_ = new uchar[frame_len];               ///< 存储数据
    head_ = new uchar[4]{ 0x68,0x0D,0x00,0x84 }; ///< 旧版协议头
    

    实际上我们确定通信协议之后,PC不存在命令之类的解析的话,可以把前面四个字节处理帧头,
    每次接收到帧头四个字节的时候表示数据已经重新开始了一帧,将收集到的数据存储相应的流程即可,继续处理后续数据

    1. 如果数据正常, 读取到帧头数据之后,假设帧头数据一致, 数据依次往后填充数据,等到数组填满,会再次获取到帧头数据
    2. 根据上一帧的状态码, 确定当前值, 如果满足设定的四个值, 满足帧头数据, 处理后续字节
    3. 将数据与帧头数组依次比较,使用 flg_statusflg\_status 记录上一次匹配的位置, 如果不一致, 置零, 下次重新开始,
    4. 每次判断四个字节一致之后, flg_statusflg\_status 累加到了 4 , 这时候进行 数据校验, 根据结果,将当前数据帧的值 压入队列中,
    5. 以上方法会导致第一帧校验失败,但是后续的帧处理结果可以保证不会出现问题, 相当于将四个字符作为了帧尾处理
        static int data_cnt = 0;
        static int flg_status = 0;  // 0 普通数据,与数据头1 比较, 1 数据头1,2 数据头2, 3 数据头3,
                                    // 每次将缓冲区 数据全部读出来,
                                    // 判断上一帧读取到了什么状态, 数据分为 前4个字节的帧头数据和 后面的数据
                                    // 1.读取到了帧头 // 2. 读取到了一般数据帧
                                    // 每次计数, 读取到帧头的时候, 判断数据是否已经满了, 已经满了进行数据校验, 满足就加入数据
                                    // 数据未满, 则可能数据未满, 
        QByteArray recv = serial_->readAll();   // QSerial  读取的数据类型是 QByteArrray
        int N = recv.size();
        if (recv.isEmpty())
        {
            Sleep(5);   // 休眠5毫秒
            LError("recv data is empty");
            return false;
        }
        // 遍历得到的数据数组
        for (int i = 0; i < N; i++)
        {
            uchar tmp = char2uchar(recv[i]);
    
            // 每次来了数据与上次的置位比较, 如果一致, 比较后一位, 
            //  如果前面4位都一致,表示已经开始了新的一帧, 将数据依次复制
            //    如果数据已经满足帧头, 将之前的数据 校验满足之后加入队列中, 其他线程处理
            if (tmp == m_Frame.head_[flg_status])
            {
                flg_status++;
                if (flg_status == 4)
                {
                    // 由于存在每次存入数据的时候存在上一帧的数据 问题 所以这里校验可能失败
                    m_read_queue_->Push(m_Frame);
                    // 更新前面4个字节的帧头数据
                    for (int i = 0; i < 4; i++)
                        m_Frame.data_[i] = m_Frame.head_[i];
                    // 匹配重新置零, 同时将数据前面数据复制过去
                    flg_status = 0;
                }
            }
            else
            {
                flg_status = 0; // 如果前面的匹配上了, 后面匹配不上, 则重新开始进行匹配
            }
            m_Frame.data_[data_cnt++] = tmp;    // 移动指针, 将数据存储到数组中
            // 超过设定值, 减去一个长度,避免越界  // 与取与操作 % 处理一致 
            // 重新开始的位置 从可以刷新的数据开始
            if (data_cnt == m_Frame.dat_len_)
                data_cnt = 0;
        }
    

    更多

    这个程序是自己目前在使用的方法,简单有效, 适用于串口通信的数据处理,十分好用, 这个算是项目文档 的一部分,后续可能再拓展, 但是基本框架不变, 测试起来也很好用

    自己测试过程可以使用 VSPD虚拟串口 6.9 汉化版 模拟出来两个相连的串口,初始设置一下就好, 然后使用普通的串口调试软件打开两个串口即可进行通信,十分方便,

    参考链接

    展开全文
  • STM32串口接收一帧数据的写法,数据包括帧头,功能帧,数据帧,校验位!
  • 串口通信帧的同步方法(识别一帧数据的起始结束)  2012-07-08 23:12:10| 分类: 我的自学实践|举报|字号 订阅   引 言  串口通信是日前单片机和DSP等嵌入式系统...

    串口通信帧的同步方法(识别一帧数据的起始结束)  

    2012-07-08 23:12:10|  分类: 我的自学实践|举报|字号 订阅

     

    引 言
        串口通信是日前单片机和DSP等嵌入式系统之间,以及嵌入式系统与PC机或无线模块之间的一种非常重要且普遍使用的通信方式在嵌入式系统的硬件结构中,通常只有一个8位或16位的CPU,不仅要完成主流程的工作,同时还要处理随时发生的各种中断,因而嵌入式系统中的串口通信程序设计与PC机有很大的不同若嵌入式系统中.中断服务子程序在系统运行过程中占用了较多的时间,就有可能在中断眼务子程序正运行时,又产生一个同类型或其他类型的中断,从而造成主程序得不到执行或后续中断数据丢失所以,嵌入式系统中的串口通信虽然看似简单,但其中仍有许多问题值得研究,例如串口通信过程中的帧同步问题本文针对该问题给出了逐次比较、基于FIFO队列和基于状态机的3种帧同步方法通过测试、分析和比较得出,基于有限状态机的方法是嵌入式系统串口通信中很有效的帧同步方法,同时也是一种很不错的串口通信程序设计结构 。   串口通信的数据帧结构
        现代工业控制,往往需要由多个独立的控制模块来共同完成它们之间通过串口通信完成复杂的控制过程,必须在通信过程中加入必要的通信协议,以提高系统的可靠性和稳定性;而要完成特定的通信协议,就得有一定的同步机制下面介绍一下简化的串口通信数据帧结构,以便分析说明嵌入式系统串口通信过程中的帧同步方法。

        假定串口发送的数据帧结构为:
         

     

    图片

     

        其中:包头用于同步,一般是一个或多个ASCII字符,本文中假定数据帧同步头有2字节(0xAA0x55);包长表示数据包中除去包头和包长的字节数,一般用约定好的几个字节表示;类型为通信协议里规定的命令类型;数据为应发送的主要信息;校验通常采用单字节异或的方法。

    串口通信中的帧同步方法
    2逐次比较的帧同步方法
        首先等待串口数据,将接收到的第1个字节数据与约定好的包头信息的第1个字节进行比较如果不正确,则等待新字节,直到接收的数据与包头信息的第1个字节相同第1个字节比较正确以后,将收到的第2个字节与包头信息的第2个字节进行比较如果仍然正确,则说明串口接收已经同步,可以开始接收数据帧中的数据部分;否则,重新开始同步过程其程序流程如图1所示。

     

    图片

     

        此种方法代码量小,编程简单,一般用于在主程序中以非中断方式接收串口数据、实时性很差、数据帧较短的场合但是,在串口速度过快且包头字节数比较多的情况下,串口实现同步花费的时间很长或很难实现同步例如,串口接收到序列Ox0O OxAA0xAA 0x55,当遇到第一个0xAA时,该方法认为第1个字节正确开始比较第2个同步头第2个字节仍是0xAA而不是0x55,所以必须等待新的字节重新开始比较第1个同步头而紧随其后的是0x55,因而,此时包头的第1个字节也没有同步上事实上,0x00 OxAA是干扰字节,0xAA 0x55才是通信协议中的同步头。

    2.2 基于FIFO队列的帧同步方法
        根据同步包头的长度,定义一个相同长度的全局字节数组,把该数组看成是一个如图2所示的先入先出(FIFO)的队列程序流程如图3所示。

     

    图片

     

        本例中定义两个字节HEADlHEAD2,都初始化为0xFF同步时,丢弃数组头字节HEADl,数组中的所有数据向前移动一个字节,串口接收到的新字节存入数组末字节HEAD2中,将整个数组与协议中的包头信息比较如果正确,则置位已同步标志位,然后开始接收、存储有用数据;否则,继续等待同步串口数据接收完后,不仅要清除已同步标志,还要把HEADlHEAD2两个字节都赋值0xFF;否则,将会影响下一帧数据的同步和接收用前面提到的序列0x00 0xAA 0xAA 0x55…”进行测试,随着串口接收中断收到新的字节帧同步队列中的数据依次为:[0xFF0xFF][0x000xFF][0xAA0x00][0xAA0xAA][0x550xAA]此时,该算法检测出[HEAD2HEAD2]==[0x550xAA],从而实现了同步,置位已同步标志位以便下次进入串口接收中断服务子程序时开始接收数据包的数据部分。

        此种方法与逐次比较的帧同步方法相比,能够比较快速、正确地检测出同步包头;但是如果包头的字节数很多,同步过程中每次进入串口中断服务子程序都要进行大量的字节搬移,将必然耗费很长的时间为了使嵌入式系统更健壮,程序设计应把握的基本原则之一就是使中断处理程序最短所以基于FIFO队列的帧同步方法也不是最优的。

    2.3 基于有限状态机的帧同步方法
        为解决以上问题,可以采用基于有限状态机的设计方法该方法将数据帧的接收过程分为若干个状态:接收信息头HEADl状态、接收信息头HEAD2状态、接收包长状态、接收数据类型状态、接收数据状态及接收校验和状态系统的初始状态为HEADl状态,各接收状态间的状态转移图如图4所示,仍用前面提到的序列0x00 0xAA 0xAA 0x55…”进行测试随着串口接收中断新字节的接收,系统的接收状态依次为HEAD1HEAD1HEAD2HEAD2LEN可见此时就是同步状态该方法也快速、有效地实现了同步;但是需要注意的是,在每一次接收完1帧完整的数据之后,必须把系统的接收状态重新设置为HEADl,否则将会影响下一帧的数据接收。

     

    图片

     

        此后,程序按照协议开始依次接收数据帧长度、命令类型、数据和校验位接收完后,重新设置系统接收状态为HEADl,同时对该数据帧进行校验校验正确后,利用消息机制通知主程序根据命令类型对数据帧进行处理或执行相应的命令操作。
        下面给出该方法在KeilC5l中的示例程序:
        

     

    图片

     


        
        由于采用了状态机和消息机制的结构,上述设计思路快速有效地实现了串口通信的同步,而且程序结构清晰,便于维护,也易于向其他的串口通信协议移植另外,串口中断服务子程序中需要处理的工作很少,每个串口接收中断平均耗时不超过20个机器周期(在单片机AT89C5l中),大大减轻了串口接收中断服务程序的压力,缓解了嵌入式系统有限资源与需求之问的矛盾,提高了嵌入式系统的稳定性。

    结论
        从上面的分析和测试可以看出,基于有限状态机的串口通信帧同步方法是本文中提出的3种帧方法中最优的,结构清晰且系统资源利用率高。

        对一个有着完整通信协议的串口中断来说,因为要比较命令头、完成校验、解析数据等需要耗费大量的机器周期,所以嵌入式系统中的串口中断服务程序设计显得更为重要在实际的串口通信程序中,可采用状态机和消息机制相结合的方法,仅在中断服务程序中设置一个标志,而在主程序中根据相应标志来作处理,这样就回避了某些中断可能需要较长处理时间的问题在程序结构上,由于采用状态机的结构,既提高了可读性同时又提高了运行速度,因而该方法不仅是一种很好的帧同步方法,还是一种很不错的串口通信程序设计方法。


    引 言

      串口通信是日前单片机和DSP等嵌入式系统之间,以及嵌入式系统与PC机或无线模块之间的一种非常重要且普遍使用的通信方式。在嵌入式系统的硬件结构中,通常只有一个8位或16位的CPU,不仅要完成主流程的工作,同时还要处理随时发生的各种中断,因而嵌入式系统中的串口通信程序设计与PC机有很大的不同。若嵌入式系统中.中断服务子程序在系统运行过程中占用了较多的时间,就有可能在中断眼务子程序正运行时,又产生一个同类型或其他类型的中断,从而造成主程序得不到执行或后续中断数据丢失。所以,嵌入式系统中的串口通信虽然看似简单,但其中仍有许多问题值得研究,例如串口通信过程中的帧同步问题。本文针对该问题给出了逐次比较、基于FIFO队列和基于状态机的3种帧同步方法。通过测试、分析和比较得出,基于有限状态机的方法是嵌入式系统串口通信中很有效的帧同步方法,同时也是一种很不错的串口通信程序设计结构。

      1 串口通信的数据帧结构

      现代工业控制,往往需要由多个独立的控制模块来共同完成。它们之间通过串口通信完成复杂的控制过程,必须在通信过程中加入必要的通信协议,以提高系统的可靠性和稳定性;而要完成特定的通信协议,就得有一定的同步机制。下面介绍一下简化的串口通信数据帧结构,以便分析说明嵌入式系统串口通信过程中的帧同步方法。

      假定串口发送的数据帧结构为:

    串口发送的数据帧结构

      其中:包头用于同步,一般是一个或多个ASCII字符,本文中假定数据帧同步头有2字节(0xAA、0x55);包长表示数据包中除去包头和包长的字节数,一般用约定好的几个字节表示;类型为通信协议里规定的命令类型;数据为应发送的主要信息;校验通常采用单字节“异或”的方法。

      2 串口通信中的帧同步方法

      2.1 逐次比较的帧同步方法

      首先等待串口数据,将接收到的第1个字节数据与约定好的包头信息的第1个字节进行比较。如果不正确,则等待新字节,直到接收的数据与包头信息的第1个字节相同。第1个字节比较正确以后,将收到的第2个字节与包头信息的第2个字节进行比较。如果仍然正确,则说明串口接收已经同步,可以开始接收数据帧中的数据部分;否则,重新开始同步过程。其程序流程如图1所示。

    程序流程图

      此种方法代码量小,编程简单,一般用于在主程序中以非中断方式接收串口数据、实时性很差、数据帧较短的场合。但是,在串口速度过快且包头字节数比较多的情况下,串口实现同步花费的时间很长或很难实现同步。例如,串口接收到序列Ox0O OxAA0xAA 0x55…,当遇到第一个“0xAA”时,该方法认为第1个字节正确开始比较第2个同步头。第2个字节仍是“0xAA”而不是“0x55”,所以必须等待新的字节重新开始比较第1个同步头。而紧随其后的是“0x55”,因而,此时包头的第1个字节也没有同步上。事实上,“0x00 OxAA”是干扰字节,“0xAA 0x55”才是通信协议中的同步头。

      2.2 基于FIFO队列的帧同步方法

      根据同步包头的长度,定义一个相同长度的全局字节数组,把该数组看成是一个如图2所示的先入先出(FIFO)的队列。程序流程如图3所示。

    FIFO方式的帧同步头队列

    点击看大图

      本例中定义两个字节HEADl和HEAD2,都初始化为0xFF。同步时,丢弃数组头字节HEADl,数组中的所有数据向前

    移动一个字节,串口接收到的新字节存入数组末字节HEAD2中,将整个数组与协议中的包头信息比较。如果正确,则置位已同步标志位,然后开始接收、存储有用数据;否则,继续等待同步。串口数据接收完后,不仅要清除已同步标志,还要把HEADl和HEAD2两个字节都赋值0xFF;否则,将会影响下一帧数据的同步和接收。用前面提到的序列“0x00 0xAA 0xAA 0x55…”进行测试,随着串口接收中断收到新的字节。帧同步队列中的数据依次为:[0xFF,0xFF]→[0x00,0xFF]→[0xAA,0x00]→[0xAA,0xAA]→[0x55,0xAA]。此时,该算法检测出[HEAD2,HEAD2]==[0x55,0xAA],从而实现了同步,置位已同步标志位以便下次进入串口接收中断服务子程序时开始接收数据包的数据部分。

      此种方法与逐次比较的帧同步方法相比,能够比较快速、正确地检测出同步包头;但是如果包头的字节数很多,同步过程中每次进入串口中断服务子程序都要进行大量的字节搬移,将必然耗费很长的时间。为了使嵌入式系统更健壮,程序设计应把握的基本原则之一就是使中断处理程序最短。所以基于FIFO队列的帧同步方法也不是最优的。

    2.3 基于有限状态机的帧同步方法

      为解决以上问题,可以采用基于有限状态机的设计方法。该方法将数据帧的接收过程分为若干个状态:接收信息头HEADl状态、接收信息头HEAD2状态、接收包长状态、接收数据类型状态、接收数据状态及接收校验和状态。系统的初始状态为HEADl状态,各接收状态间的状态转移图如图4所示,仍用前面提到的序列“0x00 0xAA 0xAA 0x55…”进行测试。随着串口接收中断新字节的接收,系统的接收状态依次为HEAD1→HEAD1→HEAD2→HEAD2→LEN。可见此时就是同步状态。该方法也快速、有效地实现了同步;但是需要注意的是,在每一次接收完1帧完整的数据之后,必须把系统的接收状态重新设置为HEADl,否则将会影响下一帧的数据接收。

    点击看大图

      此后,程序按照协议开始依次接收数据帧长度、命令类型、数据和校验位。接收完后,重新设置系统接收状态为HEADl,同时对该数据帧进行校验。校验正确后,利用消息机制通知主程序根据命令类型对数据帧进行处理或执行相应的命令操作。

      下面给出该方法在KeilC5l中的示例程序:

    点击看大图

    点击看大图

    点击看大图

      由于采用了状态机和消息机制的结构,上述设计思路快速有效地实现了串口通信的同步,而且程序结构清晰,便于维护,也易于向其他的串口通信协议移植。另外,串口中断服务子程序中需要处理的工作很少,每个串口接收中断平均耗时不超过20个机器周期(在单片机

     
    AT89C5l中),大大减轻了串口接收中断服务程序的压力,缓解了嵌入式系统有限资源与需求之问的矛盾,提高了嵌入式系统的稳定性。

      3 结论

      从上面的分析和测试可以看出,基于有限状态机的串口通信帧同步方法是本文中提出的3种帧方法中最优的,结构清晰且系统资源利用率高。

      对一个有着完整通信协议的串口中断来说,因为要比较命令头、完成校验、解析数据等需要耗费大量的机器周期,所以嵌入式系统中的串口中断服务程序设计显得更为重要。在实际的串口通信程序中,可采用状态机和消息机制相结合的方法,仅在中断服务程序中设置一个标志,而在主程序中根据相应标志来作处理,这样就回避了某些中断可能需要较长处理时间的问题。在程序结构上,由于采用状态机的结构,既提高了可读性。同时又提高了运行速度,因而该方法不仅是一种很好的帧同步方法,还是一种很不错的串口通信程序设计方法。


    展开全文
  • STM32串口中断接收一帧数据 IDLE即串口空闲中断,串口收到一帧数据后,发生的中断。比如说给单片机一次发来1个字节,或者一次发来8个字节,这些一次发来的数据,就称为一帧数据,也可以叫做一包数据。因为只有接收...
  • 教你使用stm32接收串口一帧数据

    万次阅读 多人点赞 2018-01-25 08:47:39
    因此在stm32的串口中断中,还有一个IDLE中断,用来产生串口接受一帧数据而产生的中断,比如说串口发来n个数据,会产生n次接收中断和一次IDLE中断,因此方便使用者来接收主机发送或者从机返回的数据!若想发送一帧...
  • 串口发送一帧数据时,两个字节的间隔时间是多少?
  • 串口接收一帧数据及解析

    万次阅读 多人点赞 2018-05-25 11:28:16
    3. 下位机中的数据接收和协议解析 下位机接收数据也有两种方式,、等待接收,处理器一直查询串口状态,来判断是否接收到数据。二、中断接收。两种方法的优缺点在此前的篇关于串口通信的文章中详细讨论过。得出...
  • 实现串口数据帧,有很多的方法,比如使用串口的IDLE中断进行断,使用定时器根据时间断、使用特殊标识符进行断等等,刚开始工作的时候,我自己就写好几个版本,基本上个项目个版本,但是现在...
  • 串口可以自动分辨一帧数据

    千次阅读 2018-01-02 10:39:49
    串口接收完整一帧数据处理方式 原文地址:让串口可以自动分辨一帧数据作者:李冬冬 有时我们希望串口接收到数据后,在该帧数据的末尾加上一些标志,比如这是第几帧或接收的时间等等。那么我们就需要知道什么...
  • 串口通信是单片机和DSP等嵌入式系统之间,以及嵌入式系统与PC机或无线模块之间的种非常重要且普遍使用的通信方式。在嵌入式系统的硬件结构中,通常只有个8位或16位的CPU,不仅要完成主流程的工作,同时还要处理...
  • 实现串口数据帧,有很多的方法,比如使用串口的IDLE中断进行断,使用定时器根据时间断、使用特殊标识符进行断等等,刚开始工作的时候,我自己就写好几个版本,基本上个项目个版本,但是现在...
  • stm32串口一帧数据丢失

    千次阅读 2017-08-22 11:22:24
    STM32串口发送必须先检测状态,否则第个字节无法发出,发送完毕,必须检测发送状态是否完成,否则,发送不成功,使用stm32f10x调试串口通讯时,发现个出错的现象,硬件复位重启之后,发送测试数据0x01 0x02 0x03...
  • stm32串口中断接收一帧数据

    万次阅读 多人点赞 2017-02-18 17:11:53
    最近用到stm32的串口,中断个字符个字符接收好心累,网上度了一下发现了篇好的帖子,和大家分享一下,原贴地址:http://www.51hei.com/bbs/dpj-39885-1.html 再次感谢原贴楼主的分享,为了方便大家,我把原文...
  • 1 使用定时器判断这种方式建立在两...enter image description here关于定时器的设定时间有这样几个问题其一是如果定时器超时时间大于发送两帧数据的时间间隔,则接收到的一帧数据实际上是几帧,更可能定时器无法超...
  • 使用stm32串口发送一帧数据,具体程序如下,相关知识点请自行脑补。 1、串口发送字符串 /***************** 发送一个字节 **********************/ //myUSARTx:具体串口 //ch: 一个8位的字节 void Debug_SendByte...
  • 很少看到有资料写如何以中断的方式发送一帧数据,如果以等待的发送数据帧,对高速运行的单片机来说是很浪费时间的,下面就介绍一种使用中断方式发送数据帧,操作平台采用51 mcu 首先定义一个数据帧的结构体,该...
  • STC单片机串口接收一帧数据全为0的原因和解决办法 typedef unsigned char BYTE; void UART_RX_DATA_JX(void) { volatile BYTE Uart_Rx_temp = SBUF;//此处volatile修饰必须加,否则收到数据全为0.另一种解决方式...
  • 串口通信是单片机和DSP等嵌入式系统之间,以及嵌入式系统与PC机或无线模块之间的种非常重要且普遍使用的通信方式。在嵌入式系统的硬件结构中,通常只有个8位或16位的CPU,不仅要完成主流程的工作,同时还要处理...
  • 本文描述了种简便易用的Linux下串口Modbus协议的接收检测处理方法,适合实现Linux下的Modbus协议处理。

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,184
精华内容 473
关键字:

串口一帧数据