超级精简系列之二十:超级精简的基于状态机和FIFO的高效健壮帧接收实现

原创 嵌入式Lee 2024-04-25 08:00

一. 前言

在嵌入式开发中,不可避免的涉及到通讯协议处理,而通讯协议底层都依赖于数据帧的收发。一般帧的收发,尤其是接收有几个特点:一是帧本身不定长,二是每次收到的数据不定,三是存在丢包误码,数据异常等情况。所以能健壮的解决上述问题的协议和数据收发处理非常重要,本文就来分享下其一个实现。实际上我们就是实现链路层。

首先我们大致整理下需求。

1.链路层:设计帧格式,支持不定长包。

2.支持每次收到任意长数据进行处理。

3.在丢包,误码,数据异常时,仅影响本帧,不会导致后续帧接收错误。

4.设计FIFO,实现高效缓存,均衡数据的接收与处理,解耦应用层和底层。

二. 设计

2.1 帧格式设计

1.首先我们需要支持可变帧长,所以需要一个长度域,这里设计为4字节,可以根据实际应用修改,有些帧长没有这么大,设计为2字节甚至1字节即可。

2.然后我们设置一个两字节的头0x1A 0x2B,用于表示帧的开头,可以设置的特殊一点,减少和有效数据一样的概率,这里设置为2字节是一般两字节和有效数据一样的概率就比较小了,1字节概率会比较大。当然为了进一步减小概率可以将头设置为更长比如4字节,但是没太大必要,因为这里和有效数据一样也没关系,我们仅仅暂时以此作为开头,后面还要根据帧有效性判据判断本帧是否有效,无效则会丢弃该头,往后面继续搜寻头,所以和有效数据一样也没关系,只是这个概率的大小影响校验判断的频繁度,因为找到头接收到指定长度就要进行一次判断是否帧有效, 帧头比较特殊首先就过滤掉了很多,后面判断频次就少了。

3.有效帧判断,这里是重点,是可靠性的保证, 因为前面找到了头,也根据LEN接收到了指定的DATA之后(这里还需要对LEN本身的效范围进行有效性判断),就需要判断接收到的帧是否是有效的,一般使用校验信息来判断,例如使用CRCCHECKSUM, 奇偶校验等对LEN+DATA进行校验,根据需求,简单的甚至可以用一个固定的TAIL字段用于有效性校验。

判据有两个评价指标,其是矛盾的,要根据实际应用取舍。

1)检测错误的能力, 这是可靠性的保证,因为存在刚好数据中有和头一样的,以此作为HEADLEN也是有效范围,而且数据的校验信息也对的情况,毕竟任何一种校验手段都无法百分之百可靠。

所以对于可靠性要求高的使用CRC比使用CHECKSUM和奇偶校验更好,甚至需要增加数据内容更多的校验,来避免非有效帧判断为有效帧。

而对于数据可靠性要求没这么高,但是要求处理速率快的,可以使用CHECKSUM或者奇偶校验,甚至使用固定的TAIL即可。

2)计算复杂性

不同检测能力的校验算法和计算复杂性是矛盾体,所以要综合需求考虑。

以上两者一般是矛盾点,需要综合取舍。

我这里设计如下,实际应用可以根据需求修改各字段

HEAD设置为2字节0x1A 0x2B,0x1A在前;

LEN设置为4字节,因为我的需求需要传输比较长的帧,这里长度为整个帧长,包括HEADLENDATA,校验。因为方便代码计数已经接收的长度,无需再减去其他区域用于计算当前是否接收到指定长度的数据。

DATA 可以为0长。

校验信息,我这里设置为2字节,对LENDATA区域进行校验,HEAD是固定值就没必要进行校验了。作为演示后面我们测试代码就使用固定的2字节0x3C 0x4D

2.2 帧处理

根据前面的帧格式设计,帧接收有3个阶段:接收头,接收LEN,接收DATA和校验信息帧有效性判断。

所以使用状态机进行处理是最自然想到的方式。

接收到指定的数据后,就流入该状态机,实现帧的处理。

处理流程如下:

 

需要重点关注的是,如上黄色部分,帧校验不合法或者长度不合法时,不能删除整个帧,只能删除帧头,继续索引后续内容确认后续处于什么状态。因为删除整个内容会导致如果后面有合法帧也会被删掉有效数据,一个典型的案例就是第一个帧丢失数据,第二帧正确时的场景。

如下所示:

第一帧处理,由于丢失了校验信息,根据LEN会接收到下一帧HEAD部分,至此才接收指定长度,开始校验,此时会校验失败,此时不能删除全部内容,否则会把后一帧的HEAD丢掉,而是丢掉前面的HEAD继续搜寻下一个HEAD这样会搜寻到下一帧的HEAD继续处理。当然这里所示恰好是到HEAD部分,也可能前一帧丢失更多,那么接收到了后一帧的更靠后区域,此时也要根据数据,相应的修改对应的所处状态机阶段。

2.3 FIFO设计

以上实现了帧的处理,实际应用中,收到一帧后,后续还有源源不断的帧过来,此时就进行帧处理可能会来不及,所以可以设计帧FIFO先将接收到的帧缓存,然后在其他地方循环查询该缓存进行帧处理,一方面实现数据流的均衡一方面实现底层和应用的解耦。

此时上述流程图就需要增加获取FIFO和将数据写入FIFO的操作。

FIFO的设计参考前面的文章

《超级精简系列之十九:超级精简的循环FIFO,C实现》https://mp.weixin.qq.com/s/PV-sUxzTEKbobgyt4BKRlA

三. 代码实现

3.1Fifo实现

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

#ifndef FIFO_POOL_H#define FIFO_POOL_H
#ifdef __cplusplusextern "C" {#endif #include
#define FIFO_POOL_PARAM_CHECK 0#define FIFO_POOL_SUPPORT_LOCK 1
/** * \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; /**< 缓存,用户分配 */#if FIFO_POOL_SUPPORT_LOCK /* 以下用于临界段管理 */ void* mutex; /**< 互斥量 */ void (*mutex_init)(void* mutex); /**< 互斥量初始化 */ void (*mutex_destroy)(void* mutex); /**< 删除互斥量 */ void (*mutex_lock)(void* mutex); /**< 获取互斥量 */ void (*mutex_unlock)(void* mutex); /**< 释放互斥量 */#endif} 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);
#ifdef __cplusplus}#endif
#endif


3.2帧处理实现

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

#ifndef FRAME_H#define FRAME_H
#include "fifo_pool.h"
#define FRAME_PARAM_CHECK 1
/* HEAD内容,长度,和获取HEAD的宏 */#define FRAME_HEAD_LEN 2#define FRAME_HEAD (uint16_t)0x2B1A#define FRAME_GET_HEAD(buffer) ((uint16_t)(buffer[0])|((uint16_t)buffer[1]<<8))
/* LEN长度,和获取LEN的宏 */#define FRAME_LEN_LEN 4#define FRAME_GET_LEN(buffer) ((uint32_t)buffer[0]|((uint32_t)buffer[1]<<8)|((uint32_t)buffer[2]<<16)|((uint32_t)buffer[3]<<24))
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);
#endif // FRAME_H

四. 测试

测试数据如下

 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实现了高效健壮的帧接收处理,代码具备可移植性,可以直接作为轮子嵌入到自己的项目中使用。

以下几点需要注意:

一定检查长度域;

错误帧不要全部丢弃已经接收的数据,而是只丢弃一个字节,继续往下搜索头;

根据实际选用帧有效判断方式。

评论
  • PNT、GNSS、GPS均是卫星定位和导航相关领域中的常见缩写词,他们经常会被用到,且在很多情况下会被等同使用或替换使用。我们会把定位导航功能测试叫做PNT性能测试,也会叫做GNSS性能测试。我们会把定位导航终端叫做GNSS模块,也会叫做GPS模块。但是实际上他们之间是有一些重要的区别。伴随着技术发展与越发深入,我们有必要对这三个词汇做以清晰的区分。一、什么是GPS?GPS是Global Positioning System(全球定位系统)的缩写,它是美国建立的全球卫星定位导航系统,是GNSS概
    德思特测试测量 2025-01-13 15:42 65浏览
  • 流量传感器是实现对燃气、废气、生活用水、污水、冷却液、石油等各种流体流量精准计量的关键手段。但随着工业自动化、数字化、智能化与低碳化进程的不断加速,采用传统机械式检测方式的流量传感器已不能满足当代流体计量行业对于测量精度、测量范围、使用寿命与维护成本等方面的精细需求。流量传感器的应用场景(部分)超声波流量传感器,是一种利用超声波技术测量流体流量的新型传感器,其主要通过发射超声波信号并接收反射回来的信号,根据超声波在流体中传播的时间、幅度或相位变化等参数,间接计算流体的流量,具有非侵入式测量、高精
    华普微HOPERF 2025-01-13 14:18 94浏览
  • 在不断发展的电子元件领域,继电器——作为切换电路的关键设备,正在经历前所未有的技术变革。固态继电器(SSR)和机械继电器之间的争论由来已久。然而,从未来发展的角度来看,固态继电器正逐渐占据上风。本文将从耐用性、速度和能效三个方面,全面剖析固态继电器为何更具优势,并探讨其在行业中的应用与发展趋势。1. 耐用性:经久耐用的设计机械继电器:机械继电器依靠物理触点完成电路切换。然而,随着时间的推移,这些触点因电弧、氧化和材料老化而逐渐磨损,导致其使用寿命有限。因此,它们更适合低频或对切换耐久性要求不高的
    腾恩科技-彭工 2025-01-10 16:15 77浏览
  • 01. 什么是过程能力分析?过程能力研究利用生产过程中初始一批产品的数据,预测制造过程是否能够稳定地生产符合规格的产品。可以把它想象成一种预测。通过历史数据的分析,推断未来是否可以依赖该工艺持续生产高质量产品。客户可能会要求将过程能力研究作为生产件批准程序 (PPAP) 的一部分。这是为了确保制造过程能够持续稳定地生产合格的产品。02. 基本概念在定义制造过程时,目标是确保生产的零件符合上下规格限 (USL 和 LSL)。过程能力衡量制造过程能多大程度上稳定地生产符合规格的产品。核心概念很简单:
    优思学院 2025-01-12 15:43 147浏览
  • ARMv8-A是ARM公司为满足新需求而重新设计的一个架构,是近20年来ARM架构变动最大的一次。以下是对ARMv8-A的详细介绍: 1. 背景介绍    ARM公司最初并未涉足PC市场,其产品主要针对功耗敏感的移动设备。     随着技术的发展和市场需求的变化,ARM开始扩展到企业设备、服务器等领域,这要求其架构能够支持更大的内存和更复杂的计算任务。 2. 架构特点    ARMv8-A引入了Execution State(执行状
    丙丁先生 2025-01-12 10:30 109浏览
  • 电动汽车(EV)正在改变交通运输,为传统内燃机提供更清洁、更高效的替代方案。这种转变的核心是电力电子和能源管理方面的创新,而光耦合器在其中发挥着关键作用。这些不起眼的组件可实现可靠的通信、增强安全性并优化电动汽车系统的性能,使其成为正在进行的革命中不可或缺的一部分。光耦合器,也称为光隔离器,是一种使用光传输电信号的设备。通过隔离高压和低压电路,光耦合器可确保安全性、减少干扰并保持信号完整性。这些特性对于电动汽车至关重要,因为精确控制和安全性至关重要。 光耦合器在电动汽车中的作用1.电池
    腾恩科技-彭工 2025-01-10 16:14 59浏览
  • Snyk 是一家为开发人员提供安全平台的公司,致力于协助他们构建安全的应用程序,并为安全团队提供应对数字世界挑战的工具。以下为 Snyk 如何通过 CircleCI 实现其“交付”使命的案例分析。一、Snyk 的挑战随着客户对安全工具需求的不断增长,Snyk 的开发团队面临多重挑战:加速交付的需求:Snyk 的核心目标是为开发者提供更快、更可靠的安全解决方案,但他们的现有 CI/CD 工具(TravisCI)运行缓慢,无法满足快速开发和部署的要求。扩展能力不足:随着团队规模和代码库的不断扩大,S
    艾体宝IT 2025-01-10 15:52 155浏览
  • 根据Global Info Research(环洋市场咨询)项目团队最新调研,预计2030年全球无人机电池和电源产值达到2834百万美元,2024-2030年期间年复合增长率CAGR为10.1%。 无人机电池是为无人机提供动力并使其飞行的关键。无人机使用的电池类型因无人机的大小和型号而异。一些常见的无人机电池类型包括锂聚合物(LiPo)电池、锂离子电池和镍氢(NiMH)电池。锂聚合物电池是最常用的无人机电池类型,因为其能量密度高、设计轻巧。这些电池以输出功率大、飞行时间长而著称。不过,它们需要
    GIRtina 2025-01-13 10:49 81浏览
  • 说到福特,就要从亨利·福特(Henry Ford)这个人物说起。在发明大王爱迪生的电气工厂担任工程师的福特下班后,总是在自家仓库里努力研究和开发汽车。1896年,福特终于成功制造出一辆三轮车,开启了福特汽车的传奇。最初几年,福特都是独自制造汽车并同时进行销售。 (今天很多人都知道的精益管理中的5S方法,或多或少地受到了福特 CANDO方法的影响。)1903年,福特从牧师、律师、银行家、会计师等十一位股东那里筹集了十万美元,并在自家庭院成立了美国第五百零三家汽车公司——福特汽车公司(Fo
    优思学院 2025-01-10 11:21 29浏览
  • 随着数字化的不断推进,LED显示屏行业对4K、8K等超高清画质的需求日益提升。与此同时,Mini及Micro LED技术的日益成熟,推动了间距小于1.2 Pitch的Mini、Micro LED显示屏的快速发展。这类显示屏不仅画质卓越,而且尺寸适中,通常在110至1000英寸之间,非常适合应用于电影院、监控中心、大型会议、以及电影拍摄等多种室内场景。鉴于室内LED显示屏与用户距离较近,因此对于噪音控制、体积小型化、冗余备份能力及电气安全性的要求尤为严格。为满足这一市场需求,开关电源技术推出了专为
    晶台光耦 2025-01-13 10:42 112浏览
  • 随着通信技术的迅速发展,现代通信设备需要更高效、可靠且紧凑的解决方案来应对日益复杂的系统。中国自主研发和制造的国产接口芯片,正逐渐成为通信设备(从5G基站到工业通信模块)中的重要基石。这些芯片凭借卓越性能、成本效益及灵活性,满足了现代通信基础设施的多样化需求。 1. 接口芯片在通信设备中的关键作用接口芯片作为数据交互的桥梁,是通信设备中不可或缺的核心组件。它们在设备内的各种子系统之间实现无缝数据传输,支持高速数据交换、协议转换和信号调节等功能。无论是5G基站中的数据处理,还是物联网网关
    克里雅半导体科技 2025-01-10 16:20 101浏览
  • 随着全球向绿色能源转型的加速,对高效、可靠和环保元件的需求从未如此强烈。在这种背景下,国产固态继电器(SSR)在实现太阳能逆变器、风力涡轮机和储能系统等关键技术方面发挥着关键作用。本文探讨了绿色能源系统背景下中国固态继电器行业的前景,并强调了2025年的前景。 1.对绿色能源解决方案日益增长的需求绿色能源系统依靠先进的电源管理技术来最大限度地提高效率并最大限度地减少损失。固态继电器以其耐用性、快速开关速度和抗机械磨损而闻名,正日益成为传统机电继电器的首选。可再生能源(尤其是太阳能和风能
    克里雅半导体科技 2025-01-10 16:18 89浏览
  • 新年伊始,又到了对去年做总结,对今年做展望的时刻 不知道你在2024年初立的Flag都实现了吗? 2025年对自己又有什么新的期待呢? 2024年注定是不平凡的一年, 一年里我测评了50余块开发板, 写出了很多科普文章, 从一个小小的工作室成长为科工公司。 展望2025年, 中国香河英茂科工, 会继续深耕于,具身机器人、飞行器、物联网等方面的研发, 我觉得,要向未来学习未来, 未来是什么? 是掌握在孩子们生活中的发现,和精历, 把最好的技术带给孩子,
    丙丁先生 2025-01-11 11:35 107浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦