当前位置: 首页 > news >正文

嵌入式经常用到串口,如何判断串口数据接收完成?

说起通信,首先想到的肯定是串口,日常中232和485的使用比比皆是,数据的发送、接收是串口通信最基础的内容。这篇文章主要讨论串口接收数据的断帧操作。
空闲中断断帧
一些mcu(如:stm32f103)在出厂时就已经在串口中封装好了一种中断——空闲帧中断,用户可以通过获取该中断标志位来判断数据是否接收完成,中断标志在中断服务函数中获取,使用起来相对简单。
在这里插入图片描述
例程中,当接收完成标志 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(); //关闭定时器3
    Uart0_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;                  //定时器功能,计数时钟为内部PCLK
stcTim3BaseCfg.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_Spacing10)/RS485_BAUDRATEu32PclkValue);//根据帧间隔计算超时时间
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;
    } 
    else
        return 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;
    } else
        return 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;
        }
    }
}

}

相关文章:

  • IIC总线,也称为I²C或Inter-Integrated Circuit协议
  • bootplus管理系统 file/download 任意文件下载漏洞
  • Python与R机器学习(1)支持向量机
  • AI技术未来趋势
  • 人工智能泡沫效应
  • mysql WITH的多种用法与示例
  • Day2:强化学习之TD learning
  • [特殊字符] 顺序容器全操作解析(含string完整版)
  • MySQL-SQL
  • ToDesk云电脑将终结显卡溢价,Web端浏览器、安卓、IOS免费试用
  • Ubuntu 24.04 安装 Redis
  • 什么是Docker多架构容器镜像
  • 云原生作业五
  • Python排序算法详解
  • 一键高清修复、智能剪辑,媒体处理还能多智能?
  • 什么是网关?网关有什么作用?API网关的主要功能,SpringCloud可以选择有哪些API网关?什么是限流算法?网关如何实现限流?一篇文章读懂网关的前世今生
  • 国产网络变压器有哪些品牌比较好
  • Record-Mode 备案免关站插件,让 WordPress 备案不影响 SEO 和收录
  • 网络安全-防御 第一次作业(由于防火墙只成功启动了一次未补截图)
  • redis之lua实现原理
  • 西安碑林“石刻艺术室”焕新:从汉画像石看到唐代石虎
  • 印度空军为“阵风”战机换装国产导弹,以增强作战能力推动国防自主
  • 央企通号建设集团有限公司原党委常委、副总经理叶正兵被查
  • 国家发改委:内卷式竞争扭曲市场机制、扰乱公平竞争秩序,必须整治
  • 在越剧之乡嵊州,浙江音乐学院越剧学院成立
  • 两名游客刻划八达岭长城,被拘5日罚200元