# Visual_usart **Repository Path**: ytie/visual_usart ## Basic Information - **Project Name**: Visual_usart - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-03 - **Last Updated**: 2026-08-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 整体思路 ### 1.问题分析 现有的通信协议不能较好的处理以下情况: a.半包、粘包 b.前一帧未收完新帧头已到,无法丢弃前面的垃圾数据,直接解析出后面的正确完整帧 c.数据段中有字节与帧头相同,此时不能将其误认为帧头,而应该将其作为正常数据段的一部分进行解析 d.DataLength突变,如突变为很大的值,可能会一下丢掉许多正确的帧 ### 2.修改内容 由于考虑到修改的内容尽可能少且与原版风格尽可能保持一致,增加环形缓冲区处理拆包的情况。 ### 3.具体思路 用环形缓冲区+双指针(保留现在Receive_Handle的大概逻辑,在此基础上改动:现有的方案是边读边推进指针,而且对于数据未收满的情况直接return 0,没有做处理,把它改为先预浏览后推进,并且修改循环中遇到数据未收满情况的处理逻辑), 不用状态机。 详细构想如下: #### 3.1 核心流程 预览+校验+跳转读指针 ;CRC校验通过前不消费字节,pSrcData指针也不动 #### 3.2 缓冲区设计 在Receive_Handle函数外设置一个环形缓冲区用来储存残帧,缓冲区大小设为合适长度,至少是两倍的最大帧长度. 每次接收的数据要添加到环形缓冲区中,更改标记当前最末尾数据的写指针。 #### 3.3 双指针设计 设置三个指针(数字,不是真正意义上的指针)。读指针标记现在正在处理的有效数据的开始,写指针标记现在接收的最末尾字节的位置。中间在来一个指针进行遍历处理接收的数据解析数据。 #### 3.4 极端情况处理逻辑 以上设计可以天然实现半包的拼包和粘包的正确解析,下面考虑几种极端情况的处理细节: a.前一帧未收完新帧头已到,例:AA 02(任意在帧头字段的垃圾数据)+AA+正确的完整一帧,如何丢弃前面的错误帧头即数据解析出后面的正确帧? 思路:校验失败后,读指针前进1字节(或者直接跳到下一个AA),继续进行解析即可。 用搜索指针读DataLength。垃圾数据拼出的DataLength要么超大被范围检查拦住,要么勉强合法但CRC8/CRC16校验不过,从而被过滤掉。而解析错误的帧"吞掉"的字节只是被预览读取,没有被实际舍弃,仍在被“读指针”标记的有效缓冲区里。 b.当数据段有字节与帧头相同,如何把它正确的认为成数据而不是新帧头? 核心思路:边界由DataLength决定,不由SOF字节值决定 pFrame指向真SOF,读DataLength的值后,整帧的边界就确定了。Data段里的AA只是被当作普通数据字节参与CRC16计算。pSrcData始终指着帧头SOF不动,不会移动到Data段里去"找新SOF"。CRC通过后pSrcData直接跳整帧,Data里的AA被一次性跨过。 c.若DataLength突变为非常大的值,如何做保护? 核心思路:范围检查是第一道防线,不等待、不校验直接丢弃 读完DataLength立刻判断 DataLength == 0 || DataLength > MAX_DATA_LEN。越界直接pSrcData++退1字节,不读后续字段、不等待数据、不调用CRC——最快路径被拦。若DataLength变小则范围检查放行,由CRC8拦截,CRC16兜底。 ### 4. 接收注册问题 每一个接收设备分配一个环形缓冲区。 但是原来的`Receive_Handle` 函数没有接收设备的句柄,导致无法让底层函数明确应该使用哪个缓冲区。现在加上这个设备参数,一并传给底层。 具体分配的思路是,新建一个`rt_device_t` 类型的数组来记录绑定关系。拿到一个`device`参数之后,顺序从前往后遍历这个绑定数组,如果目前已经有的绑定关系没有当前设备,一直循环到数组没有绑定关系,即值为`NULL`,那么就把当前设备和这个下标绑定,返回下标并把设备记录到数组的这个位置(在最末端),作为一个新建的关系;另一种是在遍历过程发现了有数组的值和当前设备一致,这说明之前已经注册过这个设备,所以直接返回这个下标即可。 #### 实现效果 在`Receive_Handle`函数中调用此注册接收设备函数,实现传入一个接收设备,传出唯一映射为的数(范围在0--MAX_DEVICE_NUM)之间。