ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

S.BUS协议详解:纯C语言实现编解码及STM32移植实践

S.BUS协议详解:纯C语言实现编解码及STM32移植实践 1. 项目概述S.BUS协议到底是什么为什么值得自己写一套编解码做航模、穿越机或者机器人竞赛的朋友对S.BUS这个名字应该都不陌生。它是由Futaba提出的一种串行总线协议用来在接收机和飞控、舵机之间传递遥控通道数据。比起传统PWM接收机一根信号线只能传一个通道S.BUS只用一根线就能串行传输最多16个通道的数据而且刷新率还能做到很高所以现在几乎所有主流飞控、舵机、遥控器都把它当成标配接口。但是协议好用是一回事能不能读懂它、驾驭它是另一回事。我最早接触S.BUS是在做一个地面机器人项目的时候主控是STM32接收机用的是某品牌的S.BUS输出结果发现飞控例程里的S.BUS解析代码一坨浆糊中断里读串口、DMA搬运、位操作混在一起出了bug非常难查。后来我一气之下把协议手册翻出来自己用纯C语言从零写了一套S.BUS编解码源码解码器负责把接收机的串行数据还原成16个通道值编码器负责把通道值打包成S.BUS帧发给舵机或者下一级设备。整个工程不依赖任何特定芯片的库函数只要是带UART的MCU都能用。这篇文章就把这套源码的设计思路、协议细节、实现过程和踩坑经验完整梳理一遍。适合的人主要有三类一是做飞控、机器人、遥控模型相关开发的嵌入式工程师二是正在学习串口协议解析、想找一个真实协议练手的学生三是纯粹对S.BUS协议好奇、想搞清楚它内部机理的玩家。读完你不仅能看懂S.BUS的每一bit是怎么来的还能直接把这套C代码移植到自己的项目里。2. 协议基础与整体设计思路2.1 S.BUS的物理层和数据格式S.BUS物理层使用的还是UART串口但它和普通串口有几个关键区别很多人第一次调不通就是栽在这里。首先是电平。S.BUS信号是从接收机输出的是反相信号也就是说普通UART空闲时是高电平S.BUS空闲时是低电平起始位、停止位的极性全部反过来。所以直接用USB转TTL模块去接S.BUS接口大概率读出来全是乱码。最常用的解决办法是在硬件上加一个反相器比如三极管或者专用的S.BUS反相电路或者在MCU端使用支持电平极性配置的串口外设。STM32的USART就支持RXINV和TXINV位可以直接在寄存器层面把极性翻转过来省掉一颗反相芯片。其次是波特率。S.BUS的波特率不是常规的9600、115200而是100000bps而且数据位8位停止位2位偶校验也就是常说的8E2。这里有个容易忽略的点100000bps这个波特率用标准波特率发生器去分频很多芯片是分不出刚好100000的。比如STM32F1系列72MHz主频下USARTDIV 72000000 / 100000 720刚好整除问题不大。但如果是其他主频算出来不是整数实际波特率会有偏差。一般几百bps的偏差S.BUS还能容忍但偏差太大会直接导致帧同步失败。所以移植到不同MCU上时第一件事就是确认串口实际波特率和100000的误差。然后是数据帧格式。S.BUS一帧固定25字节所有字节连在一起构成一个完整的控制周期。帧结构如下第1个字节是帧头固定为0x0F之后22个字节是16个通道的数据每个通道11bit22字节正好是176bit16×11176全部塞满第24个字节是标志位bit7是通道17bit6是通道18bit5是信号丢失指示bit4是失败安全指示其余bit保留为0第25个字节是帧尾固定为0x00。整个帧长25字节以100000bps、8E2格式传输每帧耗时约2.5ms。不过实际接收机并不是每2.5ms发一帧Futaba接收机一般是7ms左右发一帧也就是约140Hz的刷新率有些高速接收机可以到5ms甚至更短。解析的时候不能假设帧间隔固定必须以帧头和帧尾作为同步依据。2.2 为什么选择纯C语言实现而不是依赖某个芯片的库我在设计这套源码时给自己定了一条规矩不绑定任何芯片厂商的SDK。所有的串口收发抽象成几个极简的接口用户在自己平台上只要实现这几个接口就行。这么设计有几个实际好处。第一是代码的可移植性。今天用STM32明天换GD32后天可能用到ESP32或者某款国产MCU上只要把串口读写函数换掉核心的解析和打包逻辑一行都不用动。第二是便于测试。纯C代码可以在PC上用Visual Studio或者GCC直接编译配合一个虚拟串口或者回环测试程序就能把编解码逻辑跑起来不用每次都在开发板上点灯调试。第三是可读性好。中断、DMA这些硬件相关的东西和业务逻辑混在一起是嵌入式代码最难维护的一种状态。把S.BUS协议解析做成一个独立模块接一个字节进一个字节处理完就吐结果思路会非常清晰。这里也顺带提一句网上很多S.BUS解析代码喜欢用DMA空闲中断来收这在高速场景下确实效率高但它和协议解析强耦合DMA配置不同代码就废了。我这套做法是朴素的状态机逐字节解析MCU主频只要不是特别低解析25字节的耗时可以忽略不计通用性反而最好。如果你追求极致性能看懂这套状态机之后再去套DMA也是水到渠成的事。2.3 编解码的完整数据流严格来说S.BUS的编解码涉及两条方向完全相反的数据流解码方向接收机 - MCU的UART RX引脚 - 串口中断逐字节送入解码器 - 解码器完成帧同步、校验、位拆解 - 输出16个通道值和一个状态结构体。编码方向用户程序给出16个通道值 - 编码器把每个通道的11bit数据依次填入帧缓冲区 - 设置标志位、帧头帧尾 - 交给UART TX发送 - 接收方舵机或者其他设备解析该帧。解码器需要解决的核心问题是位拆解。16个通道每个占11bit但字节的边界是8bit所以通道数据会跨字节。比如通道0的数据占字节1的全部8bit再占字节2的前3bit通道1的数据接着占字节2的后5bit再占字节3的前6bit以此类推。这种跨字节位域操作是S.BUS解析里最绕脑子的地方也是很多初学者看别人代码看不懂的地方。编码器正好相反它要做的是把16个11bit的数值按顺序“铺”进22字节的缓冲区里同样要处理跨字节的问题。我在这套源码里两种操作都实现了一个是解包一个是打包代码逻辑对称对照着看特别清晰。3. 核心数据结构与模块接口设计3.1 解码输出的数据结构在写代码之前先把数据模型定清楚。我给解码器设计了一个结构体用来承载一帧解析出来的所有信息typedef struct { uint16_t channel[16]; // 16个通道值范围0~2047 uint8_t ch17; // 第17通道开关量 uint8_t ch18; // 第18通道开关量 uint8_t signal_lost; // 信号丢失标志 uint8_t failsafe_active; // 失控保护标志 uint8_t frame_valid; // 当前帧是否有效 } sbus_frame_t;通道值为什么是0~2047而不是直接换算成百分比或者PWM脉宽因为11bit无符号数的范围就是0~2047这是协议层的原始码值。飞控拿到这个值之后一般会把它映射成1000~2000的PWM值或者-100%~100%的控制量。协议层和设备层分开是模块化设计的基本原则。如果你在协议层就做了映射那不同用途的设备就得各写一套协议解析代码复用性会很差。编码器的输入也复用了这个结构体这样设计的好处是你可以在两个S.BUS设备之间做一个透传网关。解码器收下一帧把sbus_frame_t交给编码器编码器再打包发出去中间想改哪个通道的值直接改结构体里的channel数组就行。这种用法在做遥控信号中继、仿真测试、地面站注入时非常实用。3.2 状态机解码器的工作流程解码器最忌讳的做法是收满25字节后一次性解析因为串口中断什么时候来、来多少字节完全不可控一旦多收一个字节或者丢一个字节就会出现整帧错位。我用的方案是逐字节滑动的状态机原理很简单进来的每一个字节都先检测是不是0x0F如果是就认为这是潜在的帧头开始往缓冲区里存字节之后每存一字节计数器加一直到收满25字节此时检查最后一字节是不是0x00如果是0x00且前面解析出来的数据合理就认定这是一帧完整的有效数据立即解码如果不是说明这一帧是噪声干扰或者中间丢字节了把缓冲区清掉重新等待0x0F。这里有个细节值得展开为什么以0x0F开头、以0x00结尾作为校验因为S.BUS协议本身没有传统意义上的CRC校验它的可靠性就建立在帧头帧尾的固定值上。0x0F和0x00这两个值在正常数据段里也可能出现所以单纯靠头尾不能绝对避免误判。但实际使用中S.BUS信号是周期性的只要连续几帧都能头尾对上基本可以确认同步。如果数据段里恰好有一个0x0F它会被当成潜在帧头重新对齐最多导致一帧解析失败下一帧就能恢复。这也说明解码器在收到完整25字节之前必须每来一个字节就做一次帧头检测而不是只在缓冲区的第一个字节位置检测。3.3 字节流缓冲区与状态枚举为了把状态机写清楚我用一个枚举定义了解码器的运行状态typedef enum { SBUS_STATE_WAIT_HEADER 0, // 等待帧头0x0F SBUS_STATE_RECEIVING, // 正在接收帧数据 SBUS_STATE_FRAME_READY // 一帧收满待解析 } sbus_rx_state_t;对应的核心处理函数是sb_sbus_decode它的输入是单个字节输出是解析结果。每来一个字节调用一次函数内部自己维护状态和缓冲区sbus_status_t sb_sbus_decode(uint8_t byte, sbus_frame_t *out_frame) { static uint8_t buf[25]; static uint8_t buf_idx 0; static sbus_rx_state_t state SBUS_STATE_WAIT_HEADER; ... }用static变量保存状态好处是接口极简缺点是重入性差。如果两个接收机同时接入MCU就得把状态和缓冲区放进一个上下文结构体里通过指针传入函数。我在最终发布的版本里用的是上下文结构体方案这里为了演示先简化了。实际项目建议一开始就用上下文结构体不要贪图省事。整个解码流程分三步第一步检测帧头第二步收满25字节第三步校验帧尾并解码。每一步的逻辑都封装成独立的小函数主流程清晰问题定位也容易。下面我把解码中最重要的位拆解逻辑单独拎出来讲。3.4 11bit通道数据的拆解与还原如果你看过S.BUS的协议手册会发现一个帧里通道数据的排列方式用文字描述是一长串“通道0占bit0~bit10通道1占bit11~bit21……”这种话。但落到代码里最直观的实现方式其实是按位顺序读取把22字节看成一个连续的bit流每次取11bit取完16次刚好取完176bit。C语言里按位读取最省事的方法是自定义一个“位读取器”typedef struct { const uint8_t *data; uint16_t bit_pos; } bit_reader_t; uint16_t read_bits(bit_reader_t *reader, uint8_t count) { uint16_t value 0; for (uint8_t i 0; i count; i) { uint16_t byte_idx reader-bit_pos 3; uint8_t bit_idx reader-bit_pos 0x07; value | ((reader-data[byte_idx] bit_idx) 0x01) i; reader-bit_pos; } return value; }这个函数每读一位先算出它落在哪个字节的哪一位取出来之后放到value的第i位。之所以用 i而不是直接让数据保持原有顺序是因为S.BUS是低位在前第一个bit就是通道值的最低位所以必须把先读到的bit放到低位。这个细节反了整个通道值顺序就会乱而且乱得毫无规律非常难排查。对应地编码的时候用“位写入器”typedef struct { uint8_t *data; uint16_t bit_pos; } bit_writer_t; void write_bits(bit_writer_t *writer, uint16_t value, uint8_t count) { for (uint8_t i 0; i count; i) { uint16_t byte_idx writer-bit_pos 3; uint8_t bit_idx writer-bit_pos 0x07; if ((value i) 0x01) { writer-data[byte_idx] | (1 bit_idx); } else { writer-data[byte_idx] ~(1 bit_idx); } writer-bit_pos; } }这两个函数就是整个S.BUS编解码的核心工具。有了它们解码就是把22字节的数据塞进bit_reader连续读16次每次11bit编码就是把16个通道值通过bit_writer连续写进22字节缓冲区代码短小直观而且不容易出错。很多网上的代码用一大堆位移和掩码来硬算通道数据逻辑绕不说换个场景基本没法复用。我后来把这两个工具函数分离出来整个项目的可读性提高了一个档次。4. 完整C语言源码实现与逐段分析4.1 通道值合法范围与默认值处理S.BUS的11bit通道值虽然理论范围是0~2047但实际的遥控器中立值通常落在992~1024附近Futaba手册给出的标准中立值是992对应1.52ms脉宽有些遥控器用的是1024。这意味着解码器不应该假设某个固定值是中立值而是原样输出码值由上层根据遥控器校准结果去做映射。编码器这边发送出去的通道值如果超出0~2047必须做钳位处理。我在编码函数里加上了一个范围检查if (ch_value 2047) ch_value 2047; if (ch_value 0) ch_value 0;虽然S.BUS数据本身没有超范围概念但11bit定死了只能装0~2047超出部分写入缓冲区时高位会被截掉收端解析出来就成了一个莫名其妙的数值。与其让错误在远端暴露不如在源头就拦掉。另外在初始化结构体时我建议把所有通道值设到中位而不是清0。很多飞控在启动阶段如果收到全0通道数据会误判为油门最低甚至直接触发失控保护。稳妥的做法是初始化成中位值992等收到第一帧有效数据后再覆盖。4.2 解码函数完整实现下面这段是解码器的核心代码完整展示了从字节输入到通道输出的全过程。为了阅读方便我加了不少注释#include stdint.h #include string.h #define SBUS_FRAME_SIZE 25 #define SBUS_HEADER 0x0F #define SBUS_FOOTER 0x00 #define SBUS_CHANNEL_BITS 11 #define SBUS_CHANNEL_COUNT 16 typedef struct { uint8_t raw[SBUS_FRAME_SIZE]; uint8_t idx; uint8_t receiving; } sbus_decoder_t; static void decode_channels(const uint8_t raw[22], uint16_t channel[16]) { bit_reader_t reader; reader.data raw; reader.bit_pos 0; for (int i 0; i 16; i) { channel[i] read_bits(reader, 11); } } int sb_sbus_parse_byte(sbus_decoder_t *dec, uint8_t byte, sbus_frame_t *frame) { if (!dec-receiving) { // 空闲状态只等帧头 if (byte SBUS_HEADER) { dec-receiving 1; dec-idx 0; dec-raw[dec-idx] byte; } return 0; } // 正在接收状态累积到25字节 dec-raw[dec-idx] byte; if (dec-idx SBUS_FRAME_SIZE) { dec-receiving 0; // 校验帧尾 if (dec-raw[24] ! SBUS_FOOTER) { return -1; // 帧尾错误 } // 解析16个通道 decode_channels(dec-raw[1], frame-channel); // 解析标志字节 uint8_t flag dec-raw[23]; frame-ch17 (flag 7) 0x01; frame-ch18 (flag 6) 0x01; frame-signal_lost (flag 5) 0x01; frame-failsafe_active (flag 4) 0x01; frame-frame_valid 1; return 1; } return 0; }有个看起来不起眼但很重要的点进入接收状态后我并没有每个字节都检测0x0F而是直接累积。这意味着如果数据段里恰好出现0x0F当前这一帧会被干扰。前面我在设计思路里说每字节都要检测帧头这里似乎矛盾了。实际上这里的策略是帧头优先的追求同步一旦在接收状态中检测到0x0F就立即重置缓冲区重新对齐。我贴的版本为了简洁去掉了这一步。如果你要应对强干扰环境建议在接收状态的每个字节处也做一次0x0F检测逻辑很简单if (byte SBUS_HEADER dec-idx 1) { dec-idx 0; dec-raw[dec-idx] byte; return 0; }这里判断dec-idx 1是为了避免帧的第一个数据字节就是0x0F而导致误重置。4.3 编码函数完整实现编码函数相对简单核心就是构建帧头和帧尾中间22字节用bit_writer依次写入16个通道值void sb_sbus_encode(sbus_frame_t *frame, uint8_t output[25]) { output[0] SBUS_HEADER; bit_writer_t writer; writer.data output 1; // 从第二个字节开始写通道数据 writer.bit_pos 0; for (int i 0; i 16; i) { uint16_t value frame-channel[i]; if (value 2047) value 2047; write_bits(writer, value, 11); } uint8_t flag 0; if (frame-ch17) flag | (1 7); if (frame-ch18) flag | (1 6); if (frame-signal_lost) flag | (1 5); if (frame-failsafe_active) flag | (1 4); output[23] flag; output[24] SBUS_FOOTER; }所有通道值都按顺序写进去之后flag字节直接按位拼出来。这里的output[23]正好是第24个字节也就是协议里从0开始数下标23的位置对应通道数据22字节后的那张“第17/18通道标志位”字节。初学的时候我一度在这个下标上犯迷糊把数据写到了第23字节、标志位写到第24字节结果通道17和通道18怎么都不对。后来强迫自己写代码前先在纸上画好字节序号和数据排列图彻底解决了这类问题。4.4 基于位操作的另一种高效解码方式读位法虽然直观但每个bit都要循环判断代码效率不是最优。如果你需要在一个低主频MCU上同时解析多路S.BUS信号可以考虑用32位变量一次读取4字节的方式来加速。核心思路是把22字节按每4字节一组读成uint32_t然后从总共176bit里按位切片。这样做的难度在于边界处理后续改良时我记得遇到过很多奇怪的对齐问题需要仔细画bit索引表。不过我的看法是S.BUS解码即使逐bit处理25字节的耗时在72MHz主频下也就几十微秒完全够用。除非你的MCU主频只有几MHz否则没有必要为了这点性能牺牲代码清晰度。做嵌入式开发久了你会发现可维护性通常比微小的性能提升更重要尤其是在协议解析这种到处都是魔数的地方。4.5 移植到任意MCU的串口对接示例源码里编解码模块不依赖任何硬件但串口数据总得进来、总得出去。下面以STM32的HAL库为例说说怎么把裸的解析函数接到实际项目里。接收方向最简单可靠的方式是在串口接收中断里逐字节喂给解析器void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart huart3) { int result sb_sbus_parse_byte(sbus_decoder, rx_byte, sbus_frame); if (result 1) { // 收到一帧完整数据置标志位供主循环处理 sbus_frame_ready 1; } HAL_UART_Receive_IT(huart3, rx_byte, 1); } }注意使用HAL库时必须在一开始就调用HAL_UART_Receive_IT(huart3, rx_byte, 1)使能接收中断然后在回调里重新使能形成持续接收。发送方向直接把编码器填好的25字节数组交给串口发送uint8_t sbus_tx_buf[25]; sb_sbus_encode(sbus_frame_out, sbus_tx_buf); HAL_UART_Transmit(huart3, sbus_tx_buf, 25, 10);如果你用的是STM32的USART且打开了RXINV极性反转那么接收信号不需要外部反相器就能直接接入。发送S.BUS给舵机时注意舵机那边要求的也是反相S.BUS信号所以TX也需要开TXINV或者加反相电路。5. 常见问题与排查技巧实录5.1 串口波特率不符导致一帧都收不到这绝对是S.BUS调试里遇到最多的问题。现象是解码器长时间收不到完整帧状态一直停留在等待帧头或者偶尔收到一帧但很快又丢。你用逻辑分析仪抓波形会发现信号明显有但主控就是解析不对。排查方法如下先用示波器或逻辑分析仪测量S.BUS线上1bit的时间宽度。100000bps下1bit应该是10微秒。如果测得12.5微秒那实际上是80000bps可能你的接收机工作在旧版协议或者你选错了输出模式如果测得10.4微秒说明主控串口配置和信号源之间有偏差需要检查MCU时钟配置和波特率寄存器分频是否精确。很多时候问题出在MCU外部晶振用了8MHz但软件里配置成了12MHz导致整个串口波特率全偏。5.2 信号极性反了导致的乱码反相信号是S.BUS最容易踩的第二个坑。普通TTL串口空闲时是高电平而S.BUS空闲时是低电平两者极性完全相反。常见的表现是逻辑分析仪设置成UART协议解码选择8E2、100000bps能解出数据但数据字节看起来非常奇怪甚至分析仪显示一串错误因为起始位和停止位的位置全部反了。解决办法有几种。硬件上用一个NPN三极管加两个电阻就能构成最简单的反相器网上各种方案都有成本不到一块钱。软件上STM32的USART_CR1寄存器里有RXINV和TXINV位分别控制接收和发送的极性反转。国产很多MCU的UART外设也支持极性配置具体看参考手册。我自己的经验是如果用软件极性反转能解决问题就不要在硬件上加反相器少一个器件就少一个故障点。5.3 数据通道值乱序或出现不规律的大数这类问题一般出在位拆解逻辑上。如果通道0的值正确但通道1的值明显不对而且数值像随机数一样跳变基本可以断定是bit_pos递进逻辑或者位移方向出了问题。可以写一个白盒测试构造一个已知的帧比如让所有通道都是固定的1024然后把编码函数的结果直接喂给解码函数看解码结果和输入是否一致。如果编解码对不上直接把两个函数的bit_pos变化过程打印出来对比哪个位置开始出现偏差。我自己遇到过的一个低级错误是编码器里把位写入顺序搞反了导致所有通道数据整体右移了3bit。表现出来的是通道0低3位永远为0通道0和通道1的值发生混叠。这种问题靠肉眼很难看出来但用固定数据的回环测试一测就露馅。5.4 通道17、通道18和标志位偶尔丢失S.BUS帧的第24个字节下标23是标志字节很多新手容易把它忽略。如果你的上层代码只看channel数组那17、18通道和失控保护状态永远不会更新。更隐蔽的问题是某些接收机在正常工作时标志字节并不总是0x00它的低4位可能保留着一些未定义的厂商位所以解析时只取高4位不要对低4位做任何假设。还有一点要特别注意S.BUS的失败安全failsafe标志和信号丢失signal lost标志是独立的不要把它们混为一谈。信号丢失指接收机当前没有收到遥控信号失败安全指接收机已经切入了失控保护模式。在写飞控逻辑时应该优先响应失败安全标志其次才是信号丢失标志。5.5 常见问题速查表现象故障原因排查与解决完全收不到数据波特率不匹配示波器测实际bit宽度确认是否为10us数据乱码信号极性反了开启串口RXINV或加反相电路偶尔丢帧数据段出现0x0F导致误同步在接收状态中持续检测帧头并重新对齐通道值顺序错乱位拆解方向或bit_pos错误编解码回环白盒测试第17/18通道无效未解析标志字节检查raw[23]的bit7、bit6通道值偶尔为0或2047初始化未设中位通道数组默认初始化为992一帧时间忽然变长帧间隔不是固定的不要假设帧间隔以头尾为准每帧独立同步5.6 调试工具推荐做S.BUS调试一个趁手的工具能省下大量时间。逻辑分析仪是必备的推荐采样率至少10MHz的型号太低的采样率抓100000bps信号会有毛刺。电脑端用Saleae Logic配合它的UART协议解析器可以方便地设置8E2和100000bps直接看到原始帧内容。如果你手头没有逻辑分析仪也可以用两块USB转TTL模块一块接接收机、一块接PC串口助手设置好反相和8E2参数后直接在PC端观察数据帧。另外一个特别推荐的做法是在PC上编译运行你的编解码器用文件模拟串口数据源。把逻辑分析仪抓到的原始二进制文件导出成数组写一个小程序逐字节喂给解码器然后对比解码出来的通道值是否和遥控器实际动作一致。这套方法我在开发阶段反复使用定位速度比自己凭空猜快得多。6. 使用心得体会与扩展建议S.BUS这套源码写完之后我最大的体会是协议本身不复杂复杂的是在真实环境里让它稳定工作。帧头帧尾同步、极性反转、波特率偏差、位拆解顺序每一个环节单独拿出来都简单但合在一起任何一环出问题都会让整条链路失效。这也是为什么我强烈建议把编解码逻辑独立成模块并且从一开始就写回环测试的原因。在实际项目中我后来把这套S.BUS模块直接复用到三个不同平台STM32F1、STM32F4和一款国产RISC-V内核MCU。移植过程基本就是重写串口底层编解码部分一行代码都没改。有一次在RISC-V平台上因为HAL库的串口读函数实现不同导致字节丢失当时也是靠状态机日志快速定位没费太多工夫。如果你后续想在这个基础上继续扩展有几个方向可以考虑把解码器的逐字节解析改成DMA空闲中断模式适合需要同时处理多路S.BUS信号的高负载场景在编码器里加入通道映射和曲线处理做成一个可配置的PWM转S.BUS转换器可以接传统PWM接收机输出给S.BUS舵机把编解码器包装成RTOS下的独立任务用消息队列传递sbus_frame_t结构体彻底解耦中断和逻辑处理如果要用在工业级产品上建议在协议上层再叠一层帧连续性检测连续多帧校验失败则主动进入安全状态。这套源码本身没有使用任何奇技淫巧全部是C语言最基础的元素结构体、枚举、位运算、静态缓冲。好处是任何写过一点嵌入式C的人都能看懂改起来也没有心理负担。对初学者来说S.BUS协议是一个非常好的练手项目它麻雀虽小五脏俱全真实串口通信、帧同步、位操作、状态机、移植适配这些嵌入式开发的经典技能点全占了。按照这篇文章的思路从头写一遍收获会比直接下载别人的代码抄一遍大得多。
返回列表