eric2013 发表于 2022-9-13 05:24:04

高效可靠的数据字节编码算法COBS,可用于串口通信

https://en.wikipedia.org/wiki/Consistent_Overhead_Byte_Stuffing

以modbus rtu通信为例,一般是3.5字符作为帧间隔区分,而使用COBS就不需要这样了,他是将范围 内的任意字节字符串转换为 范围内的字节。从数据中消除所有0,现在可以使用0来明确标记转换后数据的结束
.
https://img.anfulai.cn/dz/attachment/forum/202209/13/022708msyxmj5h6maccpky.png
https://img.anfulai.cn/dz/attachment/forum/202209/13/022754ewa8gar5j3quv3zu.png




#include <stddef.h>
#include <stdint.h>
#include <assert.h>

/** COBS encode data to buffer
      @param data Pointer to input data to encode
      @param length Number of bytes to encode
      @param buffer Pointer to encoded output buffer
      @return Encoded buffer length in bytes
      @note Does not output delimiter byte
*/
size_t cobsEncode(const void *data, size_t length, uint8_t *buffer)
{
      assert(data && buffer);

      uint8_t *encode = buffer; // Encoded byte pointer
      uint8_t *codep = encode++; // Output code pointer
      uint8_t code = 1; // Code value

      for (const uint8_t *byte = (const uint8_t *)data; length--; ++byte)
      {
                if (*byte) // Byte not zero, write it
                        *encode++ = *byte, ++code;

                if (!*byte || code == 0xff) // Input is zero or block completed, restart
                {
                        *codep = code, code = 1, codep = encode;
                        if (!*byte || length)
                              ++encode;
                }
      }
      *codep = code; // Write final code value

      return (size_t)(encode - buffer);
}

/** COBS decode data from buffer
      @param buffer Pointer to encoded input bytes
      @param length Number of bytes to decode
      @param data Pointer to decoded output data
      @return Number of bytes successfully decoded
      @note Stops decoding if delimiter byte is found
*/
size_t cobsDecode(const uint8_t *buffer, size_t length, void *data)
{
      assert(buffer && data);

      const uint8_t *byte = buffer; // Encoded input byte pointer
      uint8_t *decode = (uint8_t *)data; // Decoded output byte pointer

      for (uint8_t code = 0xff, block = 0; byte < buffer + length; --block)
      {
                if (block) // Decode block byte
                        *decode++ = *byte++;
                else
                {
                        if (code != 0xff) // Encoded zero, write it
                              *decode++ = 0;
                        block = code = *byte++; // Next block length
                        if (!code) // Delimiter code found
                              break;
                }
      }

      return (size_t)(decode - (uint8_t *)data);
}

advancechao 发表于 2022-9-13 08:05:23

这个方便{:22:}

滴滴滴 发表于 2022-9-13 09:18:26

网页版本PDF可以参考!

eric2013 发表于 2022-9-13 17:29:55

滴滴滴 发表于 2022-9-13 09:18
网页版本PDF可以参考!

谢谢整理分享。

252514251 发表于 2022-9-15 15:43:47

这种稍难理解
跟一般用c0 做起始 终止 db dc dd 转义 比起来有什么优势

qq371833846 发表于 2022-9-15 17:14:29

252514251 发表于 2022-9-15 15:43
这种稍难理解
跟一般用c0 做起始 终止 db dc dd 转义 比起来有什么优势

你这种是不是交通信号机的协议?

252514251 发表于 2022-9-15 19:55:09

qq371833846 发表于 2022-9-15 17:14
你这种是不是交通信号机的协议?

算比较标准的转义吧
COBS看代码算法不大好理解,但在 数据中c0非常多时 确实效率高多了

armfly 发表于 2022-9-16 00:04:01

我觉得优势是
1、它只增加固定开销的字节,因此接收和解压不需要预留2倍最大帧长的缓冲区。
2、0结束很香,很多字符串处理函数可以使用

maoxia007 发表于 2022-9-19 23:14:46

一直在想是不是有一种方法可以去掉超时来区分十六进制数据帧的办法
没想到竟然有人创作了,太流弊了,佩服佩服~
同时也感谢楼主分享~~

way2888 发表于 2022-9-27 07:54:11

这个能实时解析吗?如果要收齐数据包才能解析的话,数据输出又占一份ram,数据包大的话,对ram小的MCU来说压力比较大了,还不如自己做个转义方便,实时解析不会占用2份ram,而且这种转义效率不高

mygod 发表于 2022-10-7 14:59:16

maoxia007 发表于 2022-9-19 23:14
一直在想是不是有一种方法可以去掉超时来区分十六进制数据帧的办法
没想到竟然有人创作了,太流弊了,佩服 ...

早就有方法了,都是通过头+数据结构,头中有包序号和包长度,接收就先接固定头,然后从头中得到到大小

amfy 发表于 2022-10-10 09:19:57

每次看到这个帖子都会琢磨一下,今天总算是看明白这个编码的算法了:编码后数据的第一个字节,标明数据中第一个0出现的位置,然后是实际的数据,到0的位置时,填充后续数据中下一个0的位置,直到最后数据结束,编码增加一个0字节作为编码结束。
想法很巧妙,最多也就是额外占用两个字节,秒杀了ppp编码的利用率,编码解码也很简单。
然而,因为使用了一个字节来标记数据中0的位置,就导致实际数据的长度限制在了256字节以内。如果使用两个字节标记位置,利用率就和ppp编码一样了。
我实际使用的通信协议,都是自定义的,大部分数据都是几百到上K字节,分包虽然开发难度不大,但是维护、效率和对外接口的沟通等等成本很高,大包数据实属无奈。
有没有像这样简单高效,又能支持几K以上数据的编码方式呢。

ccc228 发表于 2022-10-16 15:30:37

好东西,计划项目上使用 :lol

Zhyolo 发表于 2022-10-17 09:54:43

amfy 发表于 2022-10-10 09:19
每次看到这个帖子都会琢磨一下,今天总算是看明白这个编码的算法了:编码后数据的第一个字节,标明数据中第 ...

如果做成每255个字节作为一个段,每个段都用这个方式进行标记的话是不是能满足你的要求呢

Zhyolo 发表于 2022-10-17 11:20:39

Zhyolo 发表于 2022-10-17 09:54
如果做成每255个字节作为一个段,每个段都用这个方式进行标记的话是不是能满足你的要求呢

刚分析了下提供的代码,我上面说的这个方式好像是实现了的

庄永 发表于 2022-10-17 15:19:50

amfy 发表于 2022-10-10 09:19
每次看到这个帖子都会琢磨一下,今天总算是看明白这个编码的算法了:编码后数据的第一个字节,标明数据中第 ...

有幸接触到一些老外牛逼的模块,数据量大他们的协议都是自定义,而且预留很多保留位便于扩展。老外基础研究比我们深入,他们都没轻易用什么大包支持的标准协议,应该是没有

amfy 发表于 2022-10-17 17:05:49

Zhyolo 发表于 2022-10-17 11:20
刚分析了下提供的代码,我上面说的这个方式好像是实现了的

那就是数据分包了,这个东西原理简单,实现起来也不算难。
只是我们开发产品经常和客户对接通信协议,对方做什么开发的都有,网页,plc,c++等。给他们讲明白现在的协议就已经很费劲了,要让他们再开发分包功能,估计更加要命。

amfy 发表于 2022-10-17 17:08:38

庄永 发表于 2022-10-17 15:19
有幸接触到一些老外牛逼的模块,数据量大他们的协议都是自定义,而且预留很多保留位便于扩展。老外基础研 ...

ppp编码支持无限大的包,
问题是数据包大了之后,对通信的稳定性要求就会很高。重传的代价也会很大。
这东西没有万金油,只有最适合自己的。

庄永 发表于 2022-10-17 21:55:40

amfy 发表于 2022-10-17 17:08
ppp编码支持无限大的包,
问题是数据包大了之后,对通信的稳定性要求就会很高。重传的代价也会很大。
...

ppp编码没了解过

apleilx 发表于 2022-11-4 13:06:23

amfy 发表于 2022-10-10 09:19
每次看到这个帖子都会琢磨一下,今天总算是看明白这个编码的算法了:编码后数据的第一个字节,标明数据中第 ...

并没有限制在256内,只不过每增加256字节数据要增加一字节额外编码

amfy 发表于 2023-3-2 13:06:42

今天和某个牛逼的AI讨论了cobs编码, 受到了很大的启发. 想到了一种扩展cobs编码, 能支持无限长的数据长度. 依旧保持超高的字节效率.
现在的编码方式, 第一个字节表明后续第一个0出现的位置, 在最后编码结尾00之后, 增加一个字节, 表明在本段编码结束之后,是否还有下一段编码. 比如0a表示没有下一段数据了, 0c表示还有下一段数据.
如果是0c,则从下一个字节开始, 重新执行一遍解码, 结束之后再判断下一个字节是0a还是0c, 直到最后是0a为止.
这样,原有的编码机制和传输机制不变, 如果程序是传统cobs编码机制, 则只需要遵循传统, 什么也不需要改动, 如果程序支持扩展的cobs编码, 则可以传输无限长的数据.
这样, 每256字节仅需要增加一个字节的额外消耗, 不增加编码解码的负担. 可以无缝兼容已有的cobs设备, 对方不需要做任何改动.

amfy 发表于 2023-3-2 15:22:07

apleilx 发表于 2022-11-4 13:06
并没有限制在256内,只不过每增加256字节数据要增加一字节额外编码

一开始我一直都没看懂, 还是水平问题. 中间找资料也没看到明确说支持超过254长度的信息. 看你说的这个, 仔细琢磨了一下, 确实是支持超过254的.
我又找了相关的论文, 找到了明确支持超过254字节的例子. 在我看来, 目前这个就已经很好了, 足够简单, 足够灵活.


页: [1]
查看完整版本: 高效可靠的数据字节编码算法COBS,可用于串口通信