超级精简系列之二十:超级精简的基于状态机和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实现了高效健壮的帧接收处理,代码具备可移植性,可以直接作为轮子嵌入到自己的项目中使用。

以下几点需要注意:

一定检查长度域;

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

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

评论
  • 智能汽车可替换LED前照灯控制运行的原理涉及多个方面,包括自适应前照灯系统(AFS)的工作原理、传感器的应用、步进电机的控制以及模糊控制策略等。当下时代的智能汽车灯光控制系统通过车载网关控制单元集中控制,表现特殊点的有特斯拉,仅通过前车身控制器,整个系统就包括了灯光旋转开关、车灯变光开关、左LED前照灯总成、右LED前照灯总成、转向柱电子控制单元、CAN数据总线接口、组合仪表控制单元、车载网关控制单元等器件。变光开关、转向开关和辅助操作系统一般连为一体,开关之间通过内部线束和转向柱装置连接为多,
    lauguo2013 2024-12-10 15:53 93浏览
  • 我的一台很多年前人家不要了的九十年代SONY台式组合音响,接手时只有CD功能不行了,因为不需要,也就没修,只使用收音机、磁带机和外接信号功能就够了。最近五年在外地,就断电闲置,没使用了。今年9月回到家里,就一个劲儿地忙着收拾家当,忙了一个多月,太多事啦!修了电气,清理了闲置不用了的电器和电子,就是一个劲儿地扔扔扔!几十年的“工匠式”收留收藏,只能断舍离,拆解不过来的了。一天,忽然感觉室内有股臭味,用鼻子的嗅觉功能朝着臭味重的方向寻找,觉得应该就是这台组合音响?怎么会呢?这无机物的东西不会腐臭吧?
    自做自受 2024-12-10 16:34 163浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-11 17:58 75浏览
  • 天问Block和Mixly是两个不同的编程工具,分别在单片机开发和教育编程领域有各自的应用。以下是对它们的详细比较: 基本定义 天问Block:天问Block是一个基于区块链技术的数字身份验证和数据交换平台。它的目标是为用户提供一个安全、去中心化、可信任的数字身份验证和数据交换解决方案。 Mixly:Mixly是一款由北京师范大学教育学部创客教育实验室开发的图形化编程软件,旨在为初学者提供一个易于学习和使用的Arduino编程环境。 主要功能 天问Block:支持STC全系列8位单片机,32位
    丙丁先生 2024-12-11 13:15 60浏览
  • 全球知名半导体制造商ROHM Co., Ltd.(以下简称“罗姆”)宣布与Taiwan Semiconductor Manufacturing Company Limited(以下简称“台积公司”)就车载氮化镓功率器件的开发和量产事宜建立战略合作伙伴关系。通过该合作关系,双方将致力于将罗姆的氮化镓器件开发技术与台积公司业界先进的GaN-on-Silicon工艺技术优势结合起来,满足市场对高耐压和高频特性优异的功率元器件日益增长的需求。氮化镓功率器件目前主要被用于AC适配器和服务器电源等消费电子和
    电子资讯报 2024-12-10 17:09 95浏览
  • 时源芯微——RE超标整机定位与解决详细流程一、 初步测量与问题确认使用专业的电磁辐射测量设备,对整机的辐射发射进行精确测量。确认是否存在RE超标问题,并记录超标频段和幅度。二、电缆检查与处理若存在信号电缆:步骤一:拔掉所有信号电缆,仅保留电源线,再次测量整机的辐射发射。若测量合格:判定问题出在信号电缆上,可能是电缆的共模电流导致。逐一连接信号电缆,每次连接后测量,定位具体哪根电缆或接口导致超标。对问题电缆进行处理,如加共模扼流圈、滤波器,或优化电缆布局和屏蔽。重新连接所有电缆,再次测量
    时源芯微 2024-12-11 17:11 104浏览
  • 在智能化技术快速发展当下,图像数据的采集与处理逐渐成为自动驾驶、工业等领域的一项关键技术。高质量的图像数据采集与算法集成测试都是确保系统性能和可靠性的关键。随着技术的不断进步,对于图像数据的采集、处理和分析的需求日益增长,这不仅要求我们拥有高性能的相机硬件,还要求我们能够高效地集成和测试各种算法。我们探索了一种多源相机数据采集与算法集成测试方案,能够满足不同应用场景下对图像采集和算法测试的多样化需求,确保数据的准确性和算法的有效性。一、相机组成相机一般由镜头(Lens),图像传感器(Image
    康谋 2024-12-12 09:45 63浏览
  • 近日,搭载紫光展锐W517芯片平台的INMO GO2由影目科技正式推出。作为全球首款专为商务场景设计的智能翻译眼镜,INMO GO2 以“快、准、稳”三大核心优势,突破传统翻译产品局限,为全球商务人士带来高效、自然、稳定的跨语言交流体验。 INMO GO2内置的W517芯片,是紫光展锐4G旗舰级智能穿戴平台,采用四核处理器,具有高性能、低功耗的优势,内置超微高集成技术,采用先进工艺,计算能力相比同档位竞品提升4倍,强大的性能提供更加多样化的应用场景。【视频见P盘链接】 依托“
    紫光展锐 2024-12-11 11:50 65浏览
  • 概述 通过前面的研究学习,已经可以在CycloneVGX器件中成功实现完整的TDC(或者说完整的TDL,即延时线),测试结果也比较满足,解决了超大BIN尺寸以及大量0尺寸BIN的问题,但是还是存在一些之前系列器件还未遇到的问题,这些问题将在本文中进行详细描述介绍。 在五代Cyclone器件内部系统时钟受限的情况下,意味着大量逻辑资源将被浪费在于实现较大长度的TDL上面。是否可以找到方法可以对此前TDL的长度进行优化呢?本文还将探讨这个问题。TDC前段BIN颗粒堵塞问题分析 将延时链在逻辑中实现后
    coyoo 2024-12-10 13:28 108浏览
  • 铁氧体芯片是一种基于铁氧体磁性材料制成的芯片,在通信、传感器、储能等领域有着广泛的应用。铁氧体磁性材料能够通过外加磁场调控其导电性质和反射性质,因此在信号处理和传感器技术方面有着独特的优势。以下是对半导体划片机在铁氧体划切领域应用的详细阐述: 一、半导体划片机的工作原理与特点半导体划片机是一种使用刀片或通过激光等方式高精度切割被加工物的装置,是半导体后道封测中晶圆切割和WLP切割环节的关键设备。它结合了水气电、空气静压高速主轴、精密机械传动、传感器及自动化控制等先进技术,具有高精度、高
    博捷芯划片机 2024-12-12 09:16 69浏览
  • 首先在gitee上打个广告:ad5d2f3b647444a88b6f7f9555fd681f.mp4 · 丙丁先生/香河英茂工作室中国 - Gitee.com丙丁先生 (mr-bingding) - Gitee.com2024年对我来说是充满挑战和机遇的一年。在这一年里,我不仅进行了多个开发板的测评,还尝试了多种不同的项目和技术。今天,我想分享一下这一年的故事,希望能给大家带来一些启发和乐趣。 年初的时候,我开始对各种开发板进行测评。从STM32WBA55CG到瑞萨、平头哥和平海的开发板,我都
    丙丁先生 2024-12-11 20:14 58浏览
  • 习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习笔记&记录学习习笔记&记学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记录学习学习笔记&记
    youyeye 2024-12-10 16:13 113浏览
  • 一、SAE J1939协议概述SAE J1939协议是由美国汽车工程师协会(SAE,Society of Automotive Engineers)定义的一种用于重型车辆和工业设备中的通信协议,主要应用于车辆和设备之间的实时数据交换。J1939基于CAN(Controller Area Network)总线技术,使用29bit的扩展标识符和扩展数据帧,CAN通信速率为250Kbps,用于车载电子控制单元(ECU)之间的通信和控制。小北同学在之前也对J1939协议做过扫盲科普【科普系列】SAE J
    北汇信息 2024-12-11 15:45 104浏览
  • RK3506 是瑞芯微推出的MPU产品,芯片制程为22nm,定位于轻量级、低成本解决方案。该MPU具有低功耗、外设接口丰富、实时性高的特点,适合用多种工商业场景。本文将基于RK3506的设计特点,为大家分析其应用场景。RK3506核心板主要分为三个型号,各型号间的区别如下图:​图 1  RK3506核心板处理器型号场景1:显示HMIRK3506核心板显示接口支持RGB、MIPI、QSPI输出,且支持2D图形加速,轻松运行QT、LVGL等GUI,最快3S内开
    万象奥科 2024-12-11 15:42 80浏览
  • 【萤火工场CEM5826-M11测评】OLED显示雷达数据本文结合之前关于串口打印雷达监测数据的研究,进一步扩展至 OLED 屏幕显示。该项目整体分为两部分: 一、框架显示; 二、数据采集与填充显示。为了减小 MCU 负担,采用 局部刷新 的方案。1. 显示框架所需库函数 Wire.h 、Adafruit_GFX.h 、Adafruit_SSD1306.h . 代码#include #include #include #include "logo_128x64.h"#include "logo_
    无垠的广袤 2024-12-10 14:03 74浏览
我要评论
0
点击右上角,分享到朋友圈 我知道啦
请使用浏览器分享功能 我知道啦