在一些应用场合中,要求串口接收的数据不能丢同时又方便帧解析,我之前的做法是定义一个二维数组data[n][m],m的大小要大于最大帧长度,n用来指定帧缓存个数,每次接收到一帧数据二维数组下标n加1,将数组的最后两个字节用来存放帧长度,同时还需要用全局变量来维护帧个数,以及维护待解析的帧下标,以及即将新增的帧下标,这种做法有个缺点就是会浪费很多空间,因为帧的长度是不确定的,使得m的大小不好确定,实际使用时会尽量多定义一些。现提供一个更好的办法,解决上述的缺点。
这个方法通过使用两个FIFO来帧数据和帧解析位置,结构体定义如下:
/* FIFO */
typedef struct
{
uint32_t in; /* 写入数据时位置 */
uint32_t out; /* 读取数据时位置 */
uint32_t size; /* 缓存大小必须是2^n */
void* pbuf; /* 缓存指针 */
}s_kfifo_t;
typedef struct
{
uint8_t buff[512];
uint16_t frame[32]; /* 最大缓存的帧个数,每个成员保存帧长度,实际使用最大帧个数和帧长度有关 */
uint16_t frame_len; /* 当前帧长度 */
s_kfifo_t d_kfifo; /* 帧数据FIFO */
s_kfifo_t f_kfifo; /* 帧长度FIFO */
}rec_fifo_t;
s_kfifo_t 类型用于实现环形FIFO,rec_fifo_t类型用于定义接收数据缓存大小以及帧长度缓存,具体实现函数如下:
/* 变量定义 */
rec_fifo_t Rs232_Fifo=
{
.frame_len = 0,
.d_kfifo.in = 0,
.d_kfifo.out = 0,
.d_kfifo.size = 512,
.d_kfifo.pbuf = Rs232_Fifo.buff,
.f_kfifo.in = 0,
.f_kfifo.out = 0,
.f_kfifo.size = 32,
.f_kfifo.pbuf = Rs232_Fifo.frame
};
/**
* 说明 : 写一个数据到串口接收FIFO
* 参数 : rec_fifo, 串口接收帧控制体指针
* data, 输入数据
* 返回 : 0->写FIFO成功, 1->FIFO满了
*/
uint8_t Kfifo_Data_Put_One( rec_fifo_t *rec_fifo, uint8_t data)
{
uint32_t i;
/* 判断帧缓存是否满 */
if((rec_fifo->f_kfifo.size - rec_fifo->f_kfifo.in + rec_fifo->f_kfifo.out) < 1)
{
/** 帧满了,无法写入 */
return 1;
}
if((rec_fifo->d_kfifo.size - rec_fifo->d_kfifo.in + rec_fifo->d_kfifo.out) < 1)
{
/** fifo满了,无法写入 */
return 1;
}
/* 写数据到数据缓存 */
i=( rec_fifo->d_kfifo.in & ( rec_fifo->d_kfifo.size - 1 ) );
((uint8_t*)rec_fifo->d_kfifo.pbuf)[i] = data;
rec_fifo->frame_len += 1;
rec_fifo->d_kfifo.in += 1;
return 0;
}
/**
* 说明 : 写入一个数据到帧缓存
* 参数 : rec_fifo, 串口接收帧控制体指针
* 返回 : 0->写FIFO成功, 1->FIFO满了, 2->帧长度为0
*/
uint8_t Kfifo_Frame_Put_One( rec_fifo_t *rec_fifo)
{
uint32_t i;
/* 判断帧缓存是否满 */
if((rec_fifo->f_kfifo.size - rec_fifo->f_kfifo.in + rec_fifo->f_kfifo.out) < 1)
{
/** 帧满了,无法写入 */
return 1;
}
/* 写数据到数据缓存 */
i=( rec_fifo->f_kfifo.in & ( rec_fifo->f_kfifo.size - 1 ) );
((uint16_t*)rec_fifo->f_kfifo.pbuf)[i] = rec_fifo->frame_len;
rec_fifo->frame_len = 0;
rec_fifo->f_kfifo.in += 1;
return 0;
}
/**
* 说明 : 获取帧数据
* 参数 : fifo, FIFO指针
* ptr, 数据输出地址
* 返回 : 读取的数据个数
*/
uint16_t Kfifo_Get_Frame_Data( rec_fifo_t *rec_fifo, uint8_t* pdata)
{
uint32_t len;
uint16_t i, j;
uint16_t endlen;
uint8_t *pbuf;
/** 判断是否有数据可以读 */
if((rec_fifo->f_kfifo.in - rec_fifo->f_kfifo.out) < 1)
{
/** 帧为空 */
return 0;
}
/* 获取帧长度 */
i = rec_fifo->f_kfifo.out & ( rec_fifo->f_kfifo.size - 1 );
len = ((uint16_t*)rec_fifo->f_kfifo.pbuf)[i];
/* 读取帧数据,长度由上面计算获得 */
endlen = MIN( len, rec_fifo->d_kfifo.size - ( rec_fifo->d_kfifo.out & ( rec_fifo->d_kfifo.size - 1 ) ) );
pbuf = (uint8_t*)rec_fifo->d_kfifo.pbuf + ( rec_fifo->d_kfifo.out & ( rec_fifo->d_kfifo.size - 1 ) );
for( i = 0; i < endlen; i++ )
{
*( pdata++ ) = *( pbuf++ );
}
j = len - endlen;
if ( j > 0 )
{
pbuf = (uint8_t*)rec_fifo->d_kfifo.pbuf;
for( i = 0; i < j; i++ )
{
*( pdata++ ) = *( pbuf++ ) ;
}
}
rec_fifo->d_kfifo.out += len;
rec_fifo->f_kfifo.out += 1;
return len;
}
/**
* 说明 : USART1串口中断函数(调试串口RS232)
* 参数 : 无
* 返回 : 无
*/
void USART2_IRQHandler(void)
{
if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_RXNE) != RESET)
{
__HAL_UART_CLEAR_FLAG(&Rs232_Uart_Handle, UART_FLAG_RXNE);
/* 接收到的数据写入FIFO */
Kfifo_Data_Put_One(&Rs232_Fifo, Rs232_Uart_Handle.Instance->DR);
}
if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_IDLE) != RESET)
{
__HAL_UART_CLEAR_IDLEFLAG(&Rs232_Uart_Handle);
/* 接收完一帧 */
Kfifo_Frame_Put_One(&Rs232_Fifo);
if(Rs232_Usart_Callback != NULL)
{
/* 回调函数 */
Rs232_Usart_Callback();
}
}
if(__HAL_UART_GET_FLAG(&Rs232_Uart_Handle, UART_FLAG_ORE) != RESET)
{
__HAL_UART_CLEAR_OREFLAG(&Rs232_Uart_Handle);
}
}
/**
* 说明 : RS232接收处理函数
* 参数 : 无
* 返回 : 无
*/
void Rs232_Rec_Handle(void)
{
/* 轮询这个帧FIFO,不为空则返回帧长度,同时返回帧数据到指定缓存 */
Device.len = Kfifo_Get_Frame_Data(&Rs232_Fifo, Device.buff);
if(Device.len != 0)
{
if(Device.len < 4)
{
return;
}
if(Device.buff[0] == 0x68 && Device.buff[Device.len-1] == 0x16)
{
if(Device.buff[1] == 0x01) /* 设置运行模式 */
{
if(Device.buff[2] == 1) /* 4G测试模式 */
{
Device.mode = 1;
}
else if(Device.buff[2] == 2) /* GNSS测试模式 */
{
Device.mode = 2;
}
else /* 普通外设测试模式 */
{
Device.mode = 3;
}
}
}
}
}
上述方法简单易懂,移植也很简单,后续优化可以使用DMA接收,避免频繁中断,稳定性上上述方法带有数据长度溢出检查,以及帧个数溢出检查,经测试该方法能够稳定运行。