学习记:
本文介绍3种使用串口接受一帧完整数据包的方法,串口接收数据是字节接收的,串口每接收1字节数据,产生一个串口中断,我们在中断中将接收到的数据存放到buf中进行保存,但是数据的发送和接收都是按照帧为单位进行传输的,因此我们要在接收数据的同时判断当前接收的数据是否是完整的一帧。
一般串口完整数据帧的定义:帧头(2字节,例如AA、BB) + 数据长度(2字节) + 数据 + CRC16校验(2字节) + 帧尾(2字节)
帧头、帧尾表示一帧数据的开始和结尾,数据长度表示当前数据帧中负载数据大小,CRC16校验用来检查接收到的数据是否正确。
第一种方法(根据帧头、帧尾进行判断):
串口在接收数据时,我门在串口中断函数中对接收到的每一字节数据进行判断,如果检测到帧头数据(例如AA、BB),我们开始将接收到的数据存到buf中,同时记录下该帧数据的数据长度字段,然后一直接收,直到接收到的数据长度与我们记录下的数据长度字段值一致或接收到帧尾数据,到此一帧数据接收完成,将数据扔到消息队列,等待任务处理即可。
假如接收的数据包格式如下 帧头(AA 、BB) + 数据长度 + 数据 + CRC校验 + 帧尾(CC、DD)
void USART1_IRQHandler(void) //串口中断处理函数
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
buf[buf_size++] = USART_ReceiveData(USART1);
if (buf_size >= 2)
{
if (buf[0] == 0xAA && buf[1] == 0xBB) //接收到帧头
{
//接收到帧尾
if (buf[buf_size] == 0xCC && buf[buf_size-1] == 0xDD)
{
//此处为数据包处理逻辑
buf_size = 0;
memset(buf,0,BUF_SIEZ);
}
}
else
{
buf_size = 0;
memset(buf,0,BUF_SIZE);
}
}
if(buf_size >= BUF_SIZE)
{
buf_size = 0;
memset(buf,0,BUF_SIZE);
}
}
}
描述
C 库函数 void *memset(void *str, int c, size_t n) 复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。
声明
下面是 memset() 函数的声明。
void *memset(void *str, int c, size_t n)
参数
- str -- 指向要填充的内存块。
- c -- 要被设置的值。该值以 int 形式传递,但是函数在填充内存块时是使用该值的无符号字符形式。
- n -- 要被设置为该值的字符数。
返回值
该值返回一个指向存储区 str 的指针。
实例
下面的实例演示了 memset() 函数的用法。
实例
#include <stdio.h>
#include <string.h>
int main ()
{
char str[50];
strcpy(str,"This is string.h library function");
puts(str);
memset(str,'$',7);
puts(str);
return(0);
}
让我们编译并运行上面的程序,这将产生以下结果:
This is string.h library function $$$$$$$ string.h library function
第二种方法(根据接收到的字符之间的间隔进行判断):
串口数传输都是使用标准波特率,因此串口传输一帧数据时,字符与字符之间的时间间隔是一个固定值,我们可以根据串口的波特率去计算串口每个字符的间隔时间,在数据接收的过程中判断当字符间隔大于3.5个(modbus协议常用),则认为当前数据帧传输完毕,具体方法如下:
我们先设置定时器超时时间为计算出的3.5字符间隔时间,然后在串口中断中每接收到一个字符,就将其保存至buf中,并刷新定时器计数值,如果串口接收到的数据时间间隔大于3.5个字符间隔,定时器就会进入超时中断,我们在定时器中断中判断当前buf中的数据是否完整,如果完整,则扔到消息队列中,等待任务去处理。
//本例中,如果串口字符间隔大于3ms,我们认为一帧数据接收完毕,如果使用的协议是Modbus 协议,则时间间隔应该设置为3.5字符间隔时间。
#define BUF_SIZE 128 // 定义串口接收buf 长度
typedef enum {DISABLE = 0, ENABLE = !DISABLE} ; //定义枚举类型
u16 buf_size = 0;
u8 buf[BUF_SIZE] = {0}; //定义串口接收缓存区
u16 TimerCount = 0;
u8 TimerEnable = ENABLE; //定义定时器计数使能标志位
void USART1_IRQHandler(void) //串口中断处理函数
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET) //判断是否接收中断标志位置位
{
buf[buf_size++]= USART_ReceiveData(USART1); //将接收到的数据存入buf
TimerCount=0;
TimerEnable = ENABLE; //置位定时器计数使能标志位
if(buf_size >= BUF_SIZE)
{
buf_size = 0; //接收数据缓冲区溢出,重新开始接收
memset(buf,0,BUF_SIZE);
}
}
}
void TIM1_IRQHandler(void) //定时器中断处理函数 每1ms产生一次中断
{
u8 cnt = 0;
if(TIM_GetITStatus(TIM1 , TIM_IT_Update) != RESET )
{
TIM_ClearITPendingBit(TIM1 , TIM_FLAG_Update); //清除定时器中断标志位
if(TimerEnable == ENABLE)
{
TimerCount++;
if(TimerCount > 3) //大于3ms,则判断为一帧数据接收完成
{
TimerCount = 0;
Timer.Enable = DISABLE;
//此处为数据包处理逻辑
buf_size = 0;
memset(buf,0,BUF_SIZE);
}
}
}
}
第三种方法(使用串口帧空闲中断,推荐使用):
串口IDLE中断,串口接收完完整的一帧数据自身产生的中断,配置使能该中断后,串口会判断总线上一个字节的时间间隔内有没有再次接收到数据,如果没有则当前一帧数据接收完成,产生IDLE中断。
使用方法,原串口配置不变,添加下列语句,开启IDLE中断:
#define BUF_SIZE 128 // 定义串口接收buf 长度
u16 buf_size = 0;
u8 buf[BUF_SIZE] = {0}; //定义串口接收缓存区
USART_ITConfig(USART1, USART_IT_IDLE, ENABLE);//开启串口帧空闲中断
void USART1_IRQHandler(void) //串口中断服务函数
{
if(USART_GetITStatus(USART1, USART_IT_RXNE) != RESET)
{
buf[buf_size++] = USART_ReceiveData(USART1);
}
if(buf_size >= BUF_SIZE )
{
buf_size = 0; //接收缓冲区溢出,重新开始接收
memset(buf,0,BUF_SIZE);
}
if(USART_GetITStatus(USART1, USART_IT_IDLE) != RESET) //当前为接收到一帧完整的数据包
{
USART1->SR; //先读SR
USART1->DR; //再度DR 清除帧空闲中断标志位
//此处为数据包处理逻辑
buf_size = 0;
memset(buf,0,BUF_SIZE);
}