在嵌入式开发中,不可避免的涉及到通讯协议处理,而通讯协议底层都依赖于数据帧的收发。一般帧的收发,尤其是接收有几个特点:一是帧本身不定长,二是每次收到的数据不定,三是存在丢包误码,数据异常等情况。所以能健壮的解决上述问题的协议和数据收发处理非常重要,本文就来分享下其一个实现。实际上我们就是实现链路层。
首先我们大致整理下需求。
1.链路层:设计帧格式,支持不定长包。
2.支持每次收到任意长数据进行处理。
3.在丢包,误码,数据异常时,仅影响本帧,不会导致后续帧接收错误。
4.设计FIFO,实现高效缓存,均衡数据的接收与处理,解耦应用层和底层。
1.首先我们需要支持可变帧长,所以需要一个长度域,这里设计为4字节,可以根据实际应用修改,有些帧长没有这么大,设计为2字节甚至1字节即可。
2.然后我们设置一个两字节的头0x1A 0x2B,用于表示帧的开头,可以设置的特殊一点,减少和有效数据一样的概率,这里设置为2字节是一般两字节和有效数据一样的概率就比较小了,1字节概率会比较大。当然为了进一步减小概率可以将头设置为更长比如4字节,但是没太大必要,因为这里和有效数据一样也没关系,我们仅仅暂时以此作为开头,后面还要根据帧有效性判据判断本帧是否有效,无效则会丢弃该头,往后面继续搜寻头,所以和有效数据一样也没关系,只是这个概率的大小影响校验判断的频繁度,因为找到头接收到指定长度就要进行一次判断是否帧有效, 帧头比较特殊首先就过滤掉了很多,后面判断频次就少了。
3.有效帧判断,这里是重点,是可靠性的保证, 因为前面找到了头,也根据LEN接收到了指定的DATA之后(这里还需要对LEN本身的效范围进行有效性判断),就需要判断接收到的帧是否是有效的,一般使用校验信息来判断,例如使用CRC,CHECKSUM, 奇偶校验等对LEN+DATA进行校验,根据需求,简单的甚至可以用一个固定的TAIL字段用于有效性校验。
判据有两个评价指标,其是矛盾的,要根据实际应用取舍。
1)检测错误的能力, 这是可靠性的保证,因为存在刚好数据中有和头一样的,以此作为HEAD,LEN也是有效范围,而且数据的校验信息也对的情况,毕竟任何一种校验手段都无法百分之百可靠。
所以对于可靠性要求高的使用CRC比使用CHECKSUM和奇偶校验更好,甚至需要增加数据内容更多的校验,来避免非有效帧判断为有效帧。
而对于数据可靠性要求没这么高,但是要求处理速率快的,可以使用CHECKSUM或者奇偶校验,甚至使用固定的TAIL即可。
2)计算复杂性
不同检测能力的校验算法和计算复杂性是矛盾体,所以要综合需求考虑。
以上两者一般是矛盾点,需要综合取舍。
我这里设计如下,实际应用可以根据需求修改各字段
HEAD设置为2字节0x1A 0x2B,0x1A在前;
LEN设置为4字节,因为我的需求需要传输比较长的帧,这里长度为整个帧长,包括HEAD,LEN,DATA,校验。因为方便代码计数已经接收的长度,无需再减去其他区域用于计算当前是否接收到指定长度的数据。
DATA 可以为0长。
校验信息,我这里设置为2字节,对LEN和DATA区域进行校验,HEAD是固定值就没必要进行校验了。作为演示后面我们测试代码就使用固定的2字节0x3C 0x4D。
根据前面的帧格式设计,帧接收有3个阶段:接收头,接收LEN,接收DATA和校验信息帧有效性判断。
所以使用状态机进行处理是最自然想到的方式。
接收到指定的数据后,就流入该状态机,实现帧的处理。
处理流程如下:
需要重点关注的是,如上黄色部分,帧校验不合法或者长度不合法时,不能删除整个帧,只能删除帧头,继续索引后续内容确认后续处于什么状态。因为删除整个内容会导致如果后面有合法帧也会被删掉有效数据,一个典型的案例就是第一个帧丢失数据,第二帧正确时的场景。
如下所示:
第一帧处理,由于丢失了校验信息,根据LEN,会接收到下一帧HEAD部分,至此才接收够指定长度,开始校验,此时会校验失败,此时不能删除全部内容,否则会把后一帧的HEAD丢掉,而是要丢掉前面的HEAD继续搜寻下一个HEAD,这样会搜寻到下一帧的HEAD继续处理。当然这里所示恰好是到HEAD部分,也可能前一帧丢失更多,那么接收到了后一帧的更靠后区域,此时也要根据数据,相应的修改对应的所处状态机阶段。
以上实现了帧的处理,实际应用中,收到一帧后,后续还有源源不断的帧过来,此时就进行帧处理可能会来不及,所以可以设计帧FIFO先将接收到的帧缓存,然后在其他地方循环查询该缓存进行帧处理,一方面实现数据流的均衡一方面实现底层和应用的解耦。
此时上述流程图就需要增加获取FIFO和将数据写入FIFO的操作。
FIFO的设计参考前面的文章
《超级精简系列之十九:超级精简的循环FIFO池,C实现》https://mp.weixin.qq.com/s/PV-sUxzTEKbobgyt4BKRlA
Fifo_pool.c
#include
#include "fifo_pool.h"
/**
* \fn fifo_pool_in
* 往fifo pool里写数据
* \param[in] dev \ref fifo_pool_st
* \param[in] bu/ffer 待写入的数据
* \param[in] len 待写入的长度
* \retval 返回实际写入的数据量
*/
uint32_t fifo_pool_in(fifo_pool_st* dev, uint8_t* buffer, uint32_t len)
{
uint8_t* p;
uint32_t ret;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
if(len > dev->pool_len)
{
return 0;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
ret = 0;
}
else
{
p = dev->buffer + dev->in * dev->pool_len;
memcpy(p,buffer,len);
dev->in++;
if(dev->in >= dev->pool_num) /* 用减法代替取余 */
{
dev->in -= dev->pool_num;
}
dev->pool_cnt++;
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
ret = len;
return ret;
}
/**
* \fn fifo_pool_getpoolnum
* 获取有效的pool数
* \param[in] dev \ref fifo_pool_st
*/
uint32_t fifo_pool_getpoolnum(fifo_pool_st* dev)
{
uint32_t num;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return 0;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
num = dev->pool_cnt;
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
return num;
}
/**
* \fn fifo_pool_out
* 从fifo pool读出数据
* \param[in] dev \ref fifo_pool_st
* \param[in] buffer 存读出的数据
* \param[in] len 需要读出的数据长度
* \retval 返回实际读出的数据量 返回0表示满
*/
uint32_t fifo_pool_out(fifo_pool_st* dev, uint8_t* buffer, uint32_t len)
{
uint8_t* p;
uint32_t ret;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if((dev == 0) || (buffer == 0) || (len == 0))
{
return 0;
}
if(dev->buffer == 0)
{
return 0;
}
if(len > dev->pool_len)
{
return 0;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
ret = 0;
}
else
{
p = dev->buffer + dev->out * dev->pool_len;
memcpy(buffer, p, len);
dev->out++;
if(dev->out >= dev->pool_num) /* 用减法代替取余 */
{
dev->out -= dev->pool_num;
}
dev->pool_cnt--;
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
ret = len;
return ret;
}
/**
* \fn fifo_pool_init
* 初始化fifo pool
* \param[in] dev \ref fifo_pool_st
* \retval 0 成功
* \retval 其他值失败
*/
int fifo_pool_init(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return -1;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
dev->in = 0;
dev->pool_cnt = 0;
dev->out = 0;
if(dev->mutex_init != 0)
{
dev->mutex_init(dev->mutex);
}
#endif
return 0;
}
/**
* \fn fifo_pool_deinit
* 解除初始化fifo pool
* \param[in] dev \ref fifo_pool_st
* \retval 0 成功
* \retval 其他值失败
*/
int fifo_pool_deinit(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return -1;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_destroy != 0)
{
dev->mutex_destroy(dev->mutex);
}
#endif
return 0;
}
/**
* \fn fifo_pool_getinpool
* 获取fifo pool当前in位置pool
* \param[in] dev \ref fifo_pool_st
* \retval 返回当前in位置的pool地址, 返回0表示空
*/
uint8_t* fifo_pool_getinpool(fifo_pool_st* dev)
{
uint8_t* ret;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return 0;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
ret = 0;
}
else
{
ret = (dev->buffer + dev->in * dev->pool_len);
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
return ret;
}
/**
* \fn fifo_pool_submitinpool
* 提交当前in位置pool
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_submitinpool(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt >= dev->pool_num)
{
/* 满 */
}
else
{
dev->in++;
if(dev->in >= dev->pool_num) /* 用减法代替取余 */
{
dev->in -= dev->pool_num;
}
dev->pool_cnt++;
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
}
/**
* \fn fifo_pool_getoutpool
* 获取fifo pool当前out位置的pool
* \param[in] dev \ref fifo_pool_st
* \retval 返回当前out位置的pool地址, 返回0表示空
*/
uint8_t* fifo_pool_getoutpool(fifo_pool_st* dev)
{
uint8_t* ret;
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return 0;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
ret = 0;
}
else
{
ret = (dev->buffer + dev->out * dev->pool_len);
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
return ret;
}
/**
* \fn fifo_pool_releaseoutpool
* 释放当前out位置的pool
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_releaseoutpool(fifo_pool_st* dev)
{
#if FIFO_POOL_PARAM_CHECK
/* 参数检查 */
if(dev == 0)
{
return;
}
#endif
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_lock != 0)
{
dev->mutex_lock(dev->mutex);
}
#endif
if(dev->pool_cnt == 0)
{
/* 空 */
}
else
{
dev->out++;
if(dev->out >= dev->pool_num) /* 用减法代替取余 */
{
dev->out -= dev->pool_num;
}
dev->pool_cnt--;
}
#if FIFO_POOL_SUPPORT_LOCK
if(dev->mutex_unlock != 0)
{
dev->mutex_unlock(dev->mutex);
}
#endif
}
Fifo_pool.h
extern "C" {
/**
* \struct fifo_pool_st
* FIFO_POOL缓冲区结构.
*/
typedef struct
{
uint32_t in; /**< 写入索引 */
uint32_t out; /**< 读出索引 */
uint32_t pool_cnt; /**< 有效数据块数 */
uint32_t pool_len; /**< 每个块长度 */
uint32_t pool_num; /**< 最大块数 */
uint8_t* buffer; /**< 缓存,用户分配 */
/* 以下用于临界段管理 */
void* mutex; /**< 互斥量 */
void (*mutex_init)(void* mutex); /**< 互斥量初始化 */
void (*mutex_destroy)(void* mutex); /**< 删除互斥量 */
void (*mutex_lock)(void* mutex); /**< 获取互斥量 */
void (*mutex_unlock)(void* mutex); /**< 释放互斥量 */
} fifo_pool_st;
/**
* \fn fifo_pool_in
* 往fifo pool里写数据
* \param[in] dev \ref fifo_pool_st
* \param[in] buffer 待写入的数据
* \param[in] len 待写入的长度
* \retval 返回实际写入的数据量
*/
uint32_t fifo_pool_in(fifo_pool_st* dev, uint8_t* buffer, uint32_t len);
/**
* \fn fifo_pool_getpoolnum
* 获取有效的pool数
* \param[in] dev \ref fifo_pool_st
*/
uint32_t fifo_pool_getpoolnum(fifo_pool_st* dev);
/**
* \fn fifo_pool_out
* 从fifo pool读出数据
* \param[in] dev \ref fifo_pool_st
* \param[in] buffer 存读出的数据
* \param[in] len 需要读出的数据长度
* \retval 返回实际读出的数据量 返回0表示满
*/
uint32_t fifo_pool_out(fifo_pool_st* dev, uint8_t* buffer, uint32_t len);
/**
* \fn fifo_pool_init
* 初始化fifo pool
* \param[in] dev \ref fifo_pool_st
* \retval 0 成功
* \retval 其他值失败
*/
int fifo_pool_init(fifo_pool_st* dev);
/**
* \fn fifo_pool_deinit
* 解除初始化fifo pool
* \param[in] dev \ref fifo_pool_st
* \retval 0 成功
* \retval 其他值失败
*/
int fifo_pool_deinit(fifo_pool_st* dev);
/**
* \fn fifo_pool_getinpool
* 获取fifo pool当前in位置pool
* \param[in] dev \ref fifo_pool_st
* \retval 返回当前in位置的pool地址, 返回0表示空
*/
uint8_t* fifo_pool_getinpool(fifo_pool_st* dev);
/**
* \fn fifo_pool_submitinpool
* 提交当前in位置pool
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_submitinpool(fifo_pool_st* dev);
/**
* \fn fifo_pool_getoutpool
* 获取fifo pool当前out位置的pool
* \param[in] dev \ref fifo_pool_st
* \retval 返回当前out位置的pool地址, 返回0表示空
*/
uint8_t* fifo_pool_getoutpool(fifo_pool_st* dev);
/**
* \fn fifo_pool_releaseoutpool
* 释放当前out位置的pool
* \param[in] dev \ref fifo_pool_st
*/
void fifo_pool_releaseoutpool(fifo_pool_st* dev);
}
Frame.c
#include
#include "frame.h"
/**
* @fn frame_move_2_head
* @brief 调整移动到头开始处
* @param buffer 数据,可能会移动数据
* @param len 数据长度指针,可能会回写值
* @return
*/
static int frame_move_2_head(uint8_t* buffer, uint32_t* len)
{
int findhead = 0;
int headidx = 0;
uint8_t* p;
uint8_t* dst;
uint8_t* src;
uint8_t mvlen;
uint8_t rmlen;
if(*len < FRAME_HEAD_LEN)
{
/* 不到HEAD长不处理 */
return 0;
}
else
{
/* 查找头的位置 */
for(uint32_t i=0; i<(*len-(FRAME_HEAD_LEN-1)); i++)
{
p = buffer+i;
if(FRAME_GET_HEAD(p) == FRAME_HEAD)
{
headidx = i;
findhead = 1;
break;
}
}
}
if(findhead != 0)
{
/* 找到头,将头headidx移动到最开始处 */
}
else
{
/* 没有找到头,则只保留FRAME_HEAD_LEN-1个数据 */
headidx = *len - (FRAME_HEAD_LEN-1);
}
rmlen = headidx; /* 删除的长度 */
mvlen = *len - headidx; /* 待移动的数据的长度*/
dst = buffer; /* 目的地址 */
src = buffer+headidx; /* 源头地址 */
for(uint32_t i=0; i
{
*dst++ = *src++;
}
*len -= rmlen;
return 0;
}
/**
* @fn frame_remove_head
* @brief 删除头,然后调整移动到下一个头开始处
* @param buffer 数据,可能会移动数据
* @param len 数据长度指针,可能会回写值
* @return
*/
static int frame_remove_head(uint8_t* buffer, uint32_t* len)
{
/* 先删除一字节,然后调用frame_move_2_head移动到下一个头处 */
if(*len < FRAME_HEAD_LEN)
{
return 0;
}
for(uint32_t i=0; i<(*len-1); i++)
{
buffer[i] = buffer[i+1];
}
*len -= 1;
frame_move_2_head(buffer, len);
return 0;
}
/**
* @fn frame_init
* @brief 状态初始化
* @param dev \ref frame_dev_st
* @param check \ref frame_check_cb
* @param fifo_pool_dev \ref fifo_pool_st
*/
void frame_init(frame_dev_st* dev, frame_check_cb check, fifo_pool_st* fifo_pool_dev)
{
#if FRAME_PARAM_CHECK
if(dev == 0)
{
return;
}
#endif
dev->buffer = 0;
dev->len = 0;
dev->state = FRAME_STATE_HEAD;
dev->fifo_pool_dev = fifo_pool_dev;
dev->check = check;
}
/**
* @fn frame_handle
* @brief 数据处理
* @param dev \ref frame_dev_st
* @param buffer 数据缓存
* @param len 数据长度
*/
int frame_handle(frame_dev_st* dev, uint8_t* buffer, uint32_t len)
{
uint32_t tohandlelen = len;
uint32_t needlen = 0;
uint32_t cplen = 0;
uint8_t* p_data = buffer;
#if FRAME_PARAM_CHECK
if((dev == 0) || (buffer == 0) || (len == 0))
{
return -1;
}
#endif
do
{
if(dev->buffer == 0)
{
dev->buffer = fifo_pool_getinpool(dev->fifo_pool_dev);
if(dev->buffer != 0)
{
/* 恢复到默认状态 */
dev->len = 0;
dev->state = FRAME_STATE_HEAD;
}
else
{
/* 获取fifo失败 */
return -2;
}
}
switch(dev->state)
{
case FRAME_STATE_HEAD:
if(dev->len < FRAME_HEAD_LEN)
{
needlen = FRAME_HEAD_LEN - dev->len; /* 计算HEAD还需要的长度 */
cplen = needlen > tohandlelen ? tohandlelen : needlen; /* 计算本次可以拷贝进来的数据 */
memcpy(dev->buffer+dev->len,p_data,cplen); /* 拷贝本次的数据到缓存 */
p_data += cplen; /* 待处理数据指针递增 */
dev->len += cplen; /* 已经接收长度递增 */
tohandlelen -= cplen; /* 剩余待处理数据递减 */
if(dev->len >= FRAME_HEAD_LEN)
{
/* 足够头的数据,进行头处理 */
uint8_t* p = dev->buffer;
if(FRAME_GET_HEAD(p) == FRAME_HEAD)
{
/* 找到头,进行到下一阶段 */
dev->state = FRAME_STATE_LEN;
}
else
{
/* 没有找到头删除一字节,继续头处理 */
dev->len--;
dev->buffer[0] = dev->buffer[1];
}
}
else
{
/* 还不够头的数据 继续处理 */
}
}
else
{
/* 此状态不应该出现 */
}
break;
case FRAME_STATE_LEN:
if(dev->len < (FRAME_HEAD_LEN+FRAME_LEN_LEN))
{
needlen = (FRAME_HEAD_LEN+FRAME_LEN_LEN) - dev->len; /* 计算HEAD+LEN还需要的长度 */
cplen = needlen > tohandlelen ? tohandlelen : needlen; /* 计算本次可以拷贝进来的数据 */
memcpy(dev->buffer+dev->len,p_data,cplen); /* 拷贝本次的数据到缓存 */
p_data += cplen; /* 待处理数据指针递增 */
dev->len += cplen; /* 已经接收长度递增 */
tohandlelen -= cplen; /* 剩余待处理数据递减 */
if(dev->len >= FRAME_HEAD_LEN+FRAME_LEN_LEN)
{
/* 足够HEAD+LEN的数据,进行LEN处理 */
uint8_t* p = dev->buffer+FRAME_HEAD_LEN;
uint32_t len = FRAME_GET_LEN(p);
/* 一定要检查长度域,否则异常的长度将导致永远收不到异常长度的数据 */
if((len > dev->fifo_pool_dev->pool_len) || (len < (FRAME_HEAD_LEN+FRAME_LEN_LEN)))
{
/* 长度超过范围 删除HEAD到下一个HEAD,根据长度转到指定状态 */
frame_remove_head(dev->buffer, &(dev->len));
if(dev->len < FRAME_HEAD_LEN)
{
dev->state = FRAME_STATE_HEAD;
}
else if(dev->len < (FRAME_HEAD_LEN+FRAME_LEN_LEN))
{
dev->state = FRAME_STATE_LEN;
}
else
{
dev->state = FRAME_STATE_DATA;
}
}
else
{
/* 长度在范围内 */
dev->state = FRAME_STATE_DATA;
}
}
else
{
/* 还不够HEAD+LEN的数据 继续处理 */
}
}
else
{
/* 此状态不应该出现 */
}
break;
case FRAME_STATE_DATA:
{
uint8_t* p = dev->buffer+FRAME_HEAD_LEN;
uint32_t len = FRAME_GET_LEN(p);
/* 一定要检查长度域,因为长度域如果异常大于缓存大小,这里将永远进不来 */
if(dev->len <= len)
{
needlen = len - dev->len; /* 计算HEAD+LEN+data还需要的长度 */
cplen = needlen > tohandlelen ? tohandlelen : needlen; /* 计算本次可以拷贝进来的数据 */
memcpy(dev->buffer+dev->len,p_data,cplen); /* 拷贝本次的数据到缓存 */
p_data += cplen; /* 待处理数据指针递增 */
dev->len += cplen; /* 已经接收长度递增 */
tohandlelen -= cplen; /* 剩余待处理数据递减 */
if(dev->len >= len)
{
/* 足够HEAD+LEN+data的数据,进行帧有效性判断处理 */
if(dev->check != 0)
{
if(dev->check(p,dev->len-FRAME_HEAD_LEN) == 0)
{
/* 校验成功 入FIFO, 新的开始*/
fifo_pool_submitinpool(dev->fifo_pool_dev);
dev->buffer = 0;
dev->len = 0;
dev->state = FRAME_STATE_HEAD;
}
else
{
/* 校验失败 删除HEAD到下一个HEAD,根据长度转到指定状态 */
frame_remove_head(dev->buffer, &(dev->len));
if(dev->len < FRAME_HEAD_LEN)
{
dev->state = FRAME_STATE_HEAD;
}
else if(dev->len < (FRAME_HEAD_LEN+FRAME_LEN_LEN))
{
dev->state = FRAME_STATE_LEN;
}
else
{
dev->state = FRAME_STATE_DATA;
}
}
}
else
{
/* 没有实现该接口则默认认为有效 */
fifo_pool_submitinpool(dev->fifo_pool_dev);
dev->buffer = 0;
dev->len = 0;
dev->state = FRAME_STATE_HEAD;
}
}
else
{
/* 还不够HEAD+LEN+data的数据 继续处理 */
}
}
else
{
/* 此状态不应该出现 */
}
}
break;
default:
break;
}
}while(tohandlelen > 0);
return 0;
}
/**
* @fn frame_get
* @brief 获取帧
* @param dev \ref frame_dev_st
* @return \ref 帧缓存地址
*/
void* frame_get(frame_dev_st* dev)
{
#if FRAME_PARAM_CHECK
if(dev == 0)
{
return 0;
}
#endif
return fifo_pool_getoutpool(dev->fifo_pool_dev);
}
/**
* @fn frame_release
* @brief 释放帧
* @param dev \ref frame_dev_st
* @param frame 帧缓存地址
*/
void frame_release(frame_dev_st* dev)
{
#if FRAME_PARAM_CHECK
if(dev == 0)
{
return;
}
#endif
return fifo_pool_releaseoutpool(dev->fifo_pool_dev);
}
Frame.h
/* HEAD内容,长度,和获取HEAD的宏 */
/* LEN长度,和获取LEN的宏 */
typedef int (*frame_check_cb)(uint8_t* buffer, uint32_t len); /**< 帧有效性判断回调 返回0校验成功 其他值校验失败 buffer是从LEN开始, LEN是buffer长度 */
/**
* @enum frame_state_e
* @brief 帧状态机状态枚举
*/
typedef enum
{
FRAME_STATE_HEAD, /**< 接收帧头状态 */
FRAME_STATE_LEN, /**< 接收长度状态 */
FRAME_STATE_DATA, /**< 接收数据状态 */
} frame_state_e;
/**
* @struct frame_dev_st
* @brief 帧状态机数据结构
*/
typedef struct
{
frame_state_e state; /**< 当前状态 */
uint32_t len; /**< 已经接收的数据长度 */
uint8_t* buffer; /**< 帧缓存 */
fifo_pool_st* fifo_pool_dev; /**< \ref fifo_pool_st */
frame_check_cb check; /**< 帧有效性判断 */
} frame_dev_st;
/**
* @fn frame_init
* @brief 状态初始化
* @param dev \ref frame_dev_st
* @param check \ref frame_check_cb
* @param fifo_pool_dev \ref fifo_pool_st
*/
void frame_init(frame_dev_st* dev, frame_check_cb check, fifo_pool_st* fifo_pool_dev);
/**
* @fn frame_handle
* @brief 数据处理
* @param dev \ref frame_dev_st
* @param buffer 数据缓存
* @param len 数据长度
*/
int frame_handle(frame_dev_st* dev, uint8_t* buffer, uint32_t len);
/**
* @fn frame_get
* @brief 获取帧
* @param dev \ref frame_dev_st
* @return \ref 帧缓存地址
*/
void* frame_get(frame_dev_st* dev);
/**
* @fn frame_release
* @brief 释放帧
* @param dev \ref frame_dev_st
* @param frame 帧缓存地址
*/
void frame_release(frame_dev_st* dev);
测试数据如下
static uint8_t test_data[]=
{
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x01,0x01,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x02,0x02,0x3C,0x4E, /* NG 模拟校验错误 */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x03,0x03,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x04,0x3C,0x4D, /* NG 模拟丢数 */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x05,0x05,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0B,0x00,0x00,0x00,0x06,0x3C,0x4D, /* NG 模拟长度错误和丢数 */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x07,0x07,0x3C,0x4D, /* OK */
0x1A,0x2B,0x09,0x00,0x00,0x00,0x08,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x09,0x09,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0A,0x00,0x00,0x80,0x0A,0x0A,0x3C,0x4D, /* NG 模拟长度异常大*/
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x0B,0x0B,0x3C,0x4D, /* OK */
0x1A,0x2B,0x0B,0x00,0x00,0x00,0x0C,0x0C,0x3C,0x4D, /* NG 模拟长度域比实际大 */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x0D,0x0D,0x3C,0x4D, /* OK */
0x1A,0x2B,0x09,0x00,0x00,0x00,0x0E,0x0E,0x3C,0x4D, /* NG 模拟长度域比实际小 */
0x1A,0x2B,0x00,0x00,0x00,0x00,0x3C,0x4D, /* NG 长度域小于6 */
0x1A,0x2B,0x06,0x00,0x00,0x00, /* NG 0数据 */
0x1A,0x1A,0x09,0x00,0x00,0x00,0x10,0x3C,0x4D, /* NG 模拟HEAD错误 */
0x1A,0x2B,0x1A,0x2B,0x1A,0x2B,0x1A,0x2B,0x3C,0x4D, /* NG 模拟数据中有HEAD */
0x1A,0x2C,0x09,0x00,0x00,0x00,0x11,0x3C,0x4D, /* NG 模拟HEAD错误 */
0x1A,0x2B,0x0A,0x00,0x00,0x00,0x12,0x12,0x3C,0x4D, /* OK */
0x1A,0x2B,0x08,0x00,0x00,0x00,0x3C,0x4D, /* OK 0数据 */
};
在一个线程中模拟数据输入处理
static uint32_t sendlen=0;
uint32_t randlen=0;
srand(time(NULL));
randlen = rand()%sizeof(test_data)+1;
if((sendlen + randlen) > sizeof(test_data))
{
frame_handle(&s_frame_dev,&(test_data[sendlen]),sizeof(test_data)-sendlen);
frame_handle(&s_frame_dev,test_data,(sendlen + randlen) - sizeof(test_data));
}
else
{
frame_handle(&s_frame_dev,&(test_data[sendlen]),randlen);
}
sendlen += randlen;
sendlen %= sizeof(test_data);
另外一个线程模拟获取帧
while(1)
{
uint8_t* buffer = frame_get();
if(buffer != 0)
{
buffer+=FRAME_HEAD_LEN;
uint32_t len = FRAME_GET_LEN(buffer);
printf("len:%d\n",len);
buffer-=FRAME_HEAD_LEN;
for(uint32_t i=0;i
{
printf("%02x ",buffer[i]);
}
printf("\n");
frame_release(buffer);
}
delay(10);
}
return 0;
测试如下符合预期
以上基于状态机和FIFO实现了高效健壮的帧接收处理,代码具备可移植性,可以直接作为轮子嵌入到自己的项目中使用。
以下几点需要注意:
一定检查长度域;
错误帧不要全部丢弃已经接收的数据,而是只丢弃一个字节,继续往下搜索头;
根据实际选用帧有效判断方式。