说起通信,首先想到的肯定是串口,日常中232和485的使用比比皆是,数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。

空闲中断断帧

一些mcu(如:stm32f103)在出厂时就已经在串口中封装好了一种中断——空闲帧中断,用户可以通过获取该中断标志位来判断数据是否接收完成,终端标志在中断服务函数中获取,使用起来相对简单。

void UART4_IRQHandler(void)
{uint8_t data = 0;data = data;if(USART_GetITStatus(LoraUSARTx, USART_IT_RXNE) == SET){USART_ClearITPendingBit(LoraUSARTx, USART_IT_RXNE);if(Lora_RecvData.Rx_over == 0)Lora_RecvData.RxBuf[Lora_RecvData.Rx_count++] = LoraUSARTx->DR;}if(USART_GetITStatus(LoraUSARTx, USART_IT_IDLE) == SET){data = LoraUSARTx->SR;data = LoraUSARTx->DR;Lora_RecvData.Rx_over = 1;    //接收完成}
}

例程中,当接收完成标志Lora_RecvData.Rx_over为1时,就可以获取uart4接收到的一帧数据,该数据存放在Lora_RecvData.RxBuf中。

超时断帧

空闲帧中断的使用固然方便,但是并不是每个mcu都有这种中断存在(只有个别高端mcu才有),那么这个时候就可以考虑使用超时断帧了。Modbus协议中规定一帧数据的结束标志为3.5个字符时长,那么同样的可以把这种断帧方式类比到串口的接收上,这种方法需要搭配定时器使用,其作用原理就是:串口进一次接收中断,就打开定时器超时中断,同时装载值清零(具体的装载值可以自行定义),只要触发了定时器的超时中断,说明在用户规定的时间间隔内串口接收中断里没有新的数据进来,可以认为数据接收完成。

uint16_t Time3_CntValue = 0;//计数器初值/******************************************************************************** TIM3中断服务函数******************************************************************************/
void Tim3_IRQHandler(void)
{if(TRUE == Tim3_GetIntFlag(Tim3UevIrq)){Tim3_M0_Stop();      //关闭定时器3Uart0_Rec_Count = 0;//接收计数清零Uart0_Rec_Flag = 1;   //接收完成标志Tim3_ClearIntFlag(Tim3UevIrq); //清除定时器中断}
}void Time3_Init(uint16_t Frame_Spacing)
{uint16_t u16ArrValue;//自动重载值uint32_t u32PclkValue;//PCLK频率stc_tim3_mode0_cfg_t     stcTim3BaseCfg;//结构体初始化清零DDL_ZERO_STRUCT(stcTim3BaseCfg);Sysctrl_SetPeripheralGate(SysctrlPeripheralTim3, TRUE); //Base Timer外设时钟使能stcTim3BaseCfg.enWorkMode = Tim3WorkMode0;              //定时器模式stcTim3BaseCfg.enCT       = Tim3Timer;                  //定时器功能,计数时钟为内部PCLKstcTim3BaseCfg.enPRS      = Tim3PCLKDiv1;               //不分频stcTim3BaseCfg.enCntMode  = Tim316bitArrMode;           //自动重载16位计数器/定时器stcTim3BaseCfg.bEnTog     = FALSE;stcTim3BaseCfg.bEnGate    = FALSE;stcTim3BaseCfg.enGateP    = Tim3GatePositive;Tim3_Mode0_Init(&stcTim3BaseCfg);                       //TIM3 的模式0功能初始化u32PclkValue = Sysctrl_GetPClkFreq();                                     //获取Pclk的值//u16ArrValue = 65535-(u32PclkValue/1000);                               //1ms测试u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing*10)/RS485_BAUDRATE*u32PclkValue);//根据帧间隔计算超时时间Time3_CntValue = u16ArrValue;                                                     //计数初值Tim3_M0_ARRSet(u16ArrValue);                            //设置重载值Tim3_M0_Cnt16Set(u16ArrValue);                         //设置计数初值Tim3_ClearIntFlag(Tim3UevIrq);                          //清中断标志Tim3_Mode0_EnableIrq();                                 //使能TIM3中断(模式0时只有一个中断)EnableNvic(TIM3_IRQn, IrqLevel3, TRUE);                 //TIM3 开中断
}/**************************此处省略串口初始化部分************************/
//串口0中断服务函数
void Uart0_IRQHandler(void)
{uint8_t rec_data=0;if(Uart_GetStatus(M0P_UART0, UartRC))         {Uart_ClrStatus(M0P_UART0, UartRC);        rec_data = Uart_ReceiveData(M0P_UART0);     if(Uart0_Rec_Count<UART0_BUFF_LENGTH)//帧长度{Uart0_Rec_Buffer[Uart0_Rec_Count++] = rec_data;                             }Tim3_M0_Cnt16Set(Time3_CntValue);//设置计数初值  Tim3_M0_Run();   //开启定时器3   超时即认为一帧接收完成}
}

例程所用的是华大的hc32l130系列mcu,其它类型的mcu也可以参考这种写法。其中超时时间的计算尤其要注意数据类型的问题,u16ArrValue = 65536 - (uint16_t)((float)(Frame_Spacing*10)/RS485_BAUDRATE*u32PclkValue);其中Frame_Spacing为用户设置的字符个数,uart模式为一个“1+8+1”共10bits。

状态机断帧

状态机,状态机,又是状态机,没办法!谁让它使用起来方便呢?其实这种方法我用的也不多,但是状态机的思想还是要有的,很多逻辑用状态机梳理起来会更加的清晰。相对于超时断帧,状态机断帧的方法节约了一个定时器资源,一般的mcu外设资源是足够的,但是做一些资源冗余也未尝不是一件好事,万一呢?对吧。

//状态机断帧
void UART_IRQHandler(void)      //作为485的接收中断
{uint8_t count = 0;unsigned char lRecDat = 0; if(/*触发接收中断标志*/)  {//清中断状态位rec_timeout = 5;if((count == 0)) //接收数据头,长度可以自定义{RUart0485_DataC[count++] = /*串口接收到的数据*/;gRecStartFlag = 1;return;}if(gRecStartFlag == 1){RUart0485_DataC[count++] = /*串口接收到的数据*/;if(count > MAXLEN) //一帧数据接收完成{count=0;gRecStartFlag = 0;if(RUart0485_DataC[MAXLEN]==CRC16(RUart0485_DataC,MAXLEN)){memcpy(&gRecFinshData,RUart0485_DataC,13);gRcvFlag = 1; //接收完成标志位}}          }return;    }return ;
}

这种做法适合用在一直有数据接收的场合,每次接收完一帧有效数据后就把数据放到缓冲区中去解析,同时还不影响下一帧数据的接收。整个接收状态分为两个状态——接收数据头和接收数据块,如果一帧数据存在多个部分的话还可以在此基础上再增加几种状态,这样不仅可以提高数据接收的实时性,还能够随时看到数据接收到哪一部分,还是比较实用的。

"状态机+FIFO"断帧

记得刚毕业面试的时候,面试官还问过我一个问题:如果串口有大量数据要接收,同时又没有空闲帧中断你会怎么做?没错,就是FIFO(当时并没有回答上来,因为没用过),说白了就是开辟一个缓冲区,每次接收到的数据都放到这个缓冲区里,同时记录数据在缓冲区中的位置,当数据到达要求的长度的时候再把数据取出来,然后放到状态机中去解析。当然FIFO的使用场合有很多,很多数据处理都可以用FIFO去做,有兴趣的可以多去了解一下。

/********************串口初始化省略,华大mcu hc32l130******************/
void Uart1_IRQHandler(void)
{uint8_t data;if(Uart_GetStatus(M0P_UART1, UartRC))         //UART0数据接收{Uart_ClrStatus(M0P_UART1, UartRC);        //清中断状态位data = Uart_ReceiveData(M0P_UART1);   //接收数据字节comFIFO(&data,1);}}/******************************FIFO*******************************/
volatile uint8_t     fifodata[FIFOLEN],fifoempty,fifofull;
volatile uint8_t     uart_datatemp=0;uint8_t comFIFO(uint8_t *data,uint8_t cmd)
{static uint8_t rpos=0;  //当前写的位置 position 0--99 static uint8_t wpos=0; //当前读的位置if(cmd==0) //写数据{if(fifoempty!=0)       //1 表示有数据 不为空,0表示空{*data=fifodata[rpos];fifofull=0;rpos++;if(rpos==FIFOLEN) rpos=0;if(rpos==wpos) fifoempty=0;return 0x01;} elsereturn 0x00;} else if(cmd==1) //读数据{if(fifofull==0){fifodata[wpos]=*data;fifoempty=1;wpos++;if(wpos==FIFOLEN) wpos=0;if(wpos==rpos) fifofull=1;return 0x01;} elsereturn 0x00;}return 0x02;
}/********************************状态机处理*******************************/
void LoopFor485ReadCom(void)
{uint8_t data;while(comFIFO(&data,0)==0x01){if(rEadFlag==SAVE_HEADER_STATUS) //读取头{if(data==Header_H){buffread[0]=data;continue;}if(data==Header_L){buffread[1]=data;if(buffread[0]==Header_H){rEadFlag=SAVE_DATA_STATUS;}} else{memset(buffread,0,Length_Data);}} else if(rEadFlag==SAVE_DATA_STATUS)  //读取数据{buffread[i485+2]=data;i485++;if(i485==(Length_Data-2)) //数据帧除去头{unsigned short crc16=CRC16_MODBUS(buffread,Length_Data-2);if((buffread[Length_Data-2]==(crc16>>8))&&(buffread[Length_Data-1]==(crc16&0xff))){rEadFlag=SAVE_OVER_STATUS;memcpy(&cmddata,buffread,Length_Data);  //拷贝Length_Struct个字节,完整的结构体} else{rEadFlag=SAVE_HEADER_STATUS;}memset(buffread,0,Length_Data);i485=0;break;}}}
}

好了,就这些吧,如果有没有注意到的地方,欢迎各位看官给出宝贵意见,相互学习,共同进步!

常用单片机编程思想及例程2——串口接收断帧相关推荐

  1. 单片机编程思想指导【绝对值得仔细看】

    单片机编程思想指导[绝对值得仔细看] 今日在电子爱好者论坛上看到此文,感觉对以后的学习有帮助,特复制下来以备后用. 发布: 2010-7-16 13:55 | 作者: cat_li | 来源: 电子爱 ...

  2. 当了12年大学教师,跟大家聊聊嵌入式工程师,硬核单片机编程思想

    当了12年大学教师,跟大家聊聊嵌入式工程师,硬核单片机编程思想 摘要:没有思想的裸程序就如一副人体骨架,有个人形,但没有人样,骨骼之间的关节都是靠胶水或拉线连接起来的,生硬而呆板.假如给骨架包上皮肉, ...

  3. 嵌入式牛人 | 这些单片机编程思想超硬核

    来源:面包板社区,整理:晓宇 微信公众号:芯片之家(ID:chiphome-dy) 01 裸编程是什么? 先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系 ...

  4. 一位嵌入式工程师,硬核单片机编程思想!

    (一)裸编程是什么? 先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系统,不要想歪咯. 在裸机上编程,就犹如在一片荒地上开垦,任何一锄头下去,都会碰到硬生 ...

  5. 搬上小板凳,听嵌入式大牛讲解硬核单片机编程思想!

    来源:面包板社区,整理:晓宇 微信公众号:芯片之家(ID:chiphome-dy) 01 裸编程是什么? 先声明一个概念,裸编程,指的是在裸机上编写程序,裸机,在单片机领域就是指带着硬件的单片机控制系 ...

  6. 单片机串口接收的几种常用的数据处理方法

    单片机串口接收的几种常用的数据处理方法 一.为什么串口接收的数据需要处理 我们在做项目的时候经常会用到串口,当我们用串口和别的设备通讯的时候就需要严格遵循通讯协议,然而,仅仅是遵循通讯协议是不够的,因 ...

  7. UART0串口编程(六):串口(UART0)之UC/OS(二)UC/OS下的串口接收任务编程

    串口(UART0)之UC/OS(二) 一.串口接收数据在UC/OS设计中应注意的问题 1.    串口通信的数据接收过程: 1>  UART 接收FIFO接收到预定字节后触发中断 2>   ...

  8. stc51单片机串口接收多字节数据

    stc51单片机串口接收多字节数据 简介 51单片机有2个定时器,一个做串口波特率,一个做数据截止帧延时检测,硬件平台测试使用的是stc8的单片机,但是可以往51移植 代码 #include &quo ...

  9. 串口接收不定长数据的几种方式

    在阅读本文前,你需要先做到串口成功接收一个数据(相信这一点是很简单的)  这几天简单总结了一下用串口怎么接收一帧数据的办法,个人使用的有三种,下面逐一介绍: 第一种:使用中断的方式: 这种在数据接收不 ...

最新文章

  1. oracle 开链,欧链OracleChain开启Dapps应用新时代
  2. pycharm中报错:Error: failed to send plot to http://127.0.0.1:63342
  3. 终于找到可以一文多发的平台了!
  4. 使用 postman 给 API 写测试
  5. HDU 2444 The Accomodation of Students 二分图匹配
  6. 使用Nodejs搭建server
  7. c语言窗口最大化,使用SDL处理最大化的窗口
  8. openoffice使用总结001---版本匹配问题unknown document format for file: E:\apache-tomcat-8.5.23\webapps\ZcnsDms\
  9. 利用stack结构,将中缀表达式转换为后缀表达式并求值的算法实现
  10. LeetCode 63.不同路径II(动态规划)
  11. k8s token 过期了怎么加入worker 节点
  12. 使用 Chrome Timeline 来优化页面性能 1
  13. coolfire的八篇入门文章(.txt)
  14. win10系统下摄像头无法打开的解决方法
  15. php 页面字体大小,网页中各种设置字体大小的方法总结
  16. 物联网竞赛-LoRa和NB-IOT模块学习知识汇总
  17. python pandas数据清洗:sample()函数
  18. UNREAL ENGINE 4.12 正式发布!下载地址
  19. “潮经济”的品牌营销和消费模式具有哪些特点?
  20. NXP JN5169 使用看门狗定时器

热门文章

  1. avue设置表格显示图片
  2. 中秋佳节,实现一个自定义任意路径嫦娥奔月程序:过什么节,代码走起
  3. Android Studio打包APK安装失败:应用是非正式版本,当前设备不支持安装
  4. 云开发端午节包粽子送祝福语微信小程序源码
  5. Git泄露 之Stash(做题过程)
  6. 从个人英雄到万能开发者,程序员3.0时代到来
  7. 【ubuntu】——安装wps
  8. PostgreSQL数据外部表使用(postgres_fdw)
  9. YUV是究竟什么意思
  10. LTE模三干扰的形成、影响和优化