尧图网络科技YAOTU DIGITAL 获取报价
获取报价
首页 / 资讯中心 / 文章详情

STM32实战:DMA循环接收+IDLE中断+状态机解析SBUS

发布时间:2026/9/25 1:02:12

资讯中心
01
ARTICLE

STM32实战:DMA循环接收+IDLE中断+状态机解析SBUS

STM32实战:DMA循环接收+IDLE中断+状态机解析SBUS
1. 为什么SBUS解析值得单独拎出来讲SBUS这个协议玩过航模或者机器人底层驱动的朋友应该都不陌生。它本质上是一个反相串口协议波特率1000008位数据位偶校验2位停止位一帧25个字节帧头0x0F帧尾通常以0x00或0x04之类的标志字节结束。听起来不复杂但真正落到STM32上用HAL库去接的时候坑是一个接一个。最常见的做法是“串口接收中断里逐字节收收满25字节再解析”。这个方法在低速场景下能跑但只要你的主循环稍微忙一点或者波特率再高一些丢字节就是家常便饭。更麻烦的是SBUS帧与帧之间的间隔并不固定你没法靠“收满25个就完事”来保证帧同步——一旦错位后面全是乱码。所以这篇内容要聊的是一套在实际项目里反复验证过的组合拳DMA循环接收 IDLE空闲中断 状态机解析。这套方案的核心思路是让DMA在后台默默搬运数据CPU完全不参与每个字节的搬运IDLE中断负责告诉你“这一帧数据到头了”状态机则负责在缓冲区里找到真正的帧头、校验、提取16个通道值。适合谁看如果你已经会用CubeMX配置串口知道HAL_UART_Receive_DMA大概怎么调但一直被SBUS的丢包、错帧、通道抖动问题困扰那这篇就是写给你的。如果你还没接触过DMA和IDLE中断也没关系我会把每个环节的“为什么”讲清楚你照着配也能跑起来。提示SBUS是反相电平协议硬件上需要加一个反相电路比如三极管或者74HC14或者用串口的RX引脚配合外部反相器。软件层面本文不涉及反相处理默认硬件已经处理好。2. DMA循环接收模式到底解决了什么问题2.1 普通接收中断的瓶颈在哪里先算一笔账。SBUS波特率100000偶校验2位停止位实际上每个字节在线上占12个bit1起始8数据1校验2停止。100000除以12大约每秒8333个字节。一帧25字节也就是每秒约333帧。这个速率不算高但也不算低。如果用接收中断逐字节处理每收到一个字节就进一次中断8333次每秒的中断频率对STM32F103这类72MHz的芯片来说中断开销本身还能接受。但问题在于中断里你还要做帧头判断、缓冲区写入、索引维护这些操作加起来单次中断可能消耗几十个时钟周期。更致命的是如果主循环里有其他高优先级中断或者长时间阻塞的操作串口中断被延迟响应硬件寄存器里的数据就会被下一个字节覆盖直接丢字节。我实测过在F103上用中断方式收SBUS主循环里加一个OLED刷新I2C方式大约几毫秒丢帧率能到5%以上。对于飞行器控制来说5%的丢帧意味着每20帧就有一帧通道数据是错的这是不可接受的。2.2 DMA循环模式的工作机制DMA的循环模式Circular Mode解决的就是“搬运”这个问题。你给DMA一个缓冲区地址和长度比如uint8_t sbus_buf[50]DMA就会自动把串口收到的每一个字节依次写入这个缓冲区写到第50个字节后自动回到第0个字节继续写。整个过程CPU不需要执行任何指令。这里有个关键点DMA循环模式下你永远不知道当前DMA写到了哪个位置除非你去读DMA的计数器寄存器NDTR。但SBUS解析其实不需要知道写到哪里因为IDLE中断会告诉你“数据流停了”这时候你只需要从缓冲区里找帧头就行。配置DMA循环接收的步骤CubeMX里在USART配置页DMA Settings里点Add选USARTx_RX。Mode选CircularData Width选BytePriority选High。缓冲区大小建议设为SBUS帧长的整数倍比如502帧或753帧这样即使IDLE中断稍微延迟也不会覆盖掉上一帧的数据。注意缓冲区不要设得太小比如刚好25。因为IDLE中断触发到你在中断里处理数据之间有时间差如果DMA在这期间又写了新数据刚好25的缓冲区会被覆盖。50或75是比较稳妥的选择。2.3 为什么不用DMA正常模式DMA正常模式Normal Mode收满指定长度后就停止需要你重新调用HAL_UART_Receive_DMA才能继续收。SBUS帧长固定25看起来用正常模式收25字节很合理。但问题在于SBUS帧之间没有固定间隔你无法保证DMA启动的时刻刚好对准帧头。如果启动时错过了几个字节收满25字节后你拿到的是一帧错位的数据解析必然失败。循环模式则不同它一直在收你通过IDLE中断来“切片”每次IDLE触发后从缓冲区里找帧头找到就解析找不到就丢弃。这种方式对帧同步的容忍度高得多。3. IDLE中断帧边界识别的关键3.1 IDLE中断的触发条件IDLE中断的全称是“空闲线路检测中断”。当串口RX线上连续出现一个字节时间的高电平对于SBUS是反相的所以是低电平但硬件反相后就是高电平后硬件就会置位IDLE标志。简单说数据流停了IDLE就触发。SBUS协议里帧与帧之间至少有一个字节时间的间隔这个间隔正好触发IDLE。所以每次IDLE中断就意味着“刚才收到了一串数据现在停了”。3.2 HAL库中IDLE中断的开启方式HAL库默认不开启IDLE中断你需要手动加一行代码。在MX_USARTx_UART_Init()之后调用__HAL_UART_ENABLE_IT(huart1, UART_IT_IDLE);然后在stm32f1xx_it.c里找到USART1_IRQHandler在HAL_UART_IRQHandler(huart1)之前判断IDLE标志void USART1_IRQHandler(void) { if (__HAL_UART_GET_FLAG(huart1, UART_FLAG_IDLE)) { __HAL_UART_CLEAR_IDLEFLAG(huart1); // 这里做数据处理 } HAL_UART_IRQHandler(huart1); }提示清除IDLE标志的顺序很重要。必须先读SR寄存器再读DR寄存器HAL库的__HAL_UART_CLEAR_IDLEFLAG宏已经帮你做了这件事。如果你自己写清除逻辑顺序错了标志清不掉中断会反复触发。3.3 IDLE中断里该做什么、不该做什么IDLE中断里只做一件事置一个标志位。比如volatile uint8_t sbus_frame_ready 0;然后在主循环里判断这个标志再做解析。为什么不在中断里直接解析因为解析涉及帧头查找、校验和计算、通道值提取这些操作耗时可能几十微秒。虽然不算长但中断里做太多事会影响其他中断的响应。更重要的是解析过程中如果DMA又写了新数据你读到的缓冲区可能是不完整的。所以正确的做法是IDLE中断置标志主循环里解析。如果主循环够快比如1ms一轮完全来得及。4. 状态机解析从字节流到16个通道值4.1 SBUS帧格式回顾一帧SBUS是25字节结构如下字节位置内容说明00x0F帧头1-22通道数据16个通道每个11位共176位22字节23标志位bit0通道17bit1通道18bit2丢帧bit3失效保护240x00或0x04帧尾不同厂商略有差异16个通道的11位数据是紧凑排列的解析时需要按位拼接。这是SBUS解析里最容易出错的地方。4.2 状态机的三个状态状态机不需要太复杂三个状态足够STATE_HEADER寻找帧头0x0F。STATE_DATA收集后续24字节。STATE_COMPLETE校验并解析。但实际实现时我更倾向于用“滑动窗口”的方式在DMA缓冲区里从当前位置往后找0x0F找到后检查后面第24字节是否是合法帧尾如果是就认为找到了一帧。typedef enum { SBUS_STATE_IDLE, SBUS_STATE_HEADER, SBUS_STATE_RECEIVING, SBUS_STATE_COMPLETE } sbus_state_t;4.3 通道值提取的位操作这是SBUS解析的核心难点。22个字节里塞了16个11位的数据排列方式如下通道1byte0 (byte1 0x07) 8通道2(byte1 3) (byte2 0x3F) 5通道3(byte2 6) byte3 2 (byte4 0x01) 10……手动写16个通道的提取代码很容易出错。我推荐用一个循环加位偏移的方式uint16_t channels[16]; uint32_t bit_offset 0; for (int i 0; i 16; i) { uint8_t byte_index bit_offset / 8; uint8_t bit_index bit_offset % 8; uint32_t value sbus_buf[1 byte_index] | (sbus_buf[1 byte_index 1] 8) | (sbus_buf[1 byte_index 2] 16); channels[i] (value bit_index) 0x07FF; bit_offset 11; }这段代码的逻辑是把三个连续字节拼成一个24位值然后右移bit_index位取低11位。注意缓冲区索引要加1因为byte0是帧头。注意这段代码在byte_index2超出22时会读到帧尾字节但由于我们只取低11位且帧尾字节的高位不影响结果所以是安全的。不过为了严谨可以把缓冲区开大一点确保不会越界。4.4 校验与容错SBUS没有校验和但有两个地方可以做容错帧尾检查第24字节应该是0x00或0x04。如果不是丢弃这一帧。丢帧标志第23字节的bit2表示“这一帧是丢帧后的补偿帧”如果置位可以选择丢弃或标记。我在实际项目里的做法是帧尾不对就丢弃丢帧标志置位也丢弃。这样虽然会损失一些帧但保证了进入控制算法的数据都是干净的。5. 完整代码框架与CubeMX配置要点5.1 CubeMX关键配置配置项值说明USART波特率100000SBUS标准波特率数据位8校验Even偶校验停止位2DMA RXCircular, Byte循环模式NVIC USART使能用于IDLE中断DMA NVIC使能可选一般不需要5.2 主循环解析逻辑#define SBUS_BUF_SIZE 50 uint8_t sbus_dma_buf[SBUS_BUF_SIZE]; volatile uint8_t sbus_idle_flag 0; uint16_t sbus_channels[16]; void main_loop(void) { if (sbus_idle_flag) { sbus_idle_flag 0; sbus_parse(sbus_dma_buf, SBUS_BUF_SIZE); } } void sbus_parse(uint8_t *buf, uint16_t len) { for (uint16_t i 0; i len - 24; i) { if (buf[i] 0x0F (buf[i 24] 0x00 || buf[i 24] 0x04)) { // 找到一帧 uint8_t *frame buf[i]; // 提取通道值 uint32_t bit_offset 0; for (int ch 0; ch 16; ch) { uint8_t bi bit_offset / 8; uint8_t bit bit_offset % 8; uint32_t val frame[1 bi] | (frame[2 bi] 8) | (frame[3 bi] 16); sbus_channels[ch] (val bit) 0x07FF; bit_offset 11; } return; // 只解析第一帧 } } }5.3 实测中的意外情况情况一IDLE中断触发太频繁。如果串口线上有噪声IDLE可能被误触发。解决办法是在IDLE中断里加一个简单的判断只有DMA计数器变化了才置标志。或者干脆不管因为解析函数找不到帧头会直接返回不会出错。情况二DMA缓冲区被覆盖。如果主循环处理太慢IDLE标志置位后过了很久才解析DMA可能已经写了新数据覆盖了旧帧。解决办法是缓冲区开大一点比如100或者提高主循环频率。情况三通道值抖动。SBUS通道值范围是172-1811中位是992。如果发现通道值在某个值附近跳动可能是帧错位导致的。检查帧头查找逻辑确保找到的0x0F确实是帧头而不是数据里的巧合。6. 几个容易被忽略的细节6.1 反相电平的处理SBUS是反相协议硬件上必须反相。我见过有人直接把SBUS信号接到STM32的RX引脚结果收到的全是0xFF或0x00。反相电路可以用一个NPN三极管加两个电阻或者用74HC14施密特触发器。如果不想加硬件也可以用软件串口的方式但那样CPU开销太大不推荐。6.2 波特率的误差100000波特率在72MHz的STM32上用16倍过采样BRR寄存器的值算出来是45.0刚好整除误差为0。但如果你用的是其他主频比如64MHz算出来可能不是整数误差大了就会丢帧。所以尽量选主频能被100000整除的配置。6.3 帧尾的兼容性不同厂商的SBUS接收机帧尾可能不同。有的是0x00有的是0x04还有的是0x14。最稳妥的做法是不检查帧尾只检查帧头。但这样误判率会高一些。折中方案是检查帧尾的高4位是否为0因为合法帧尾的高4位都是0。6.4 与RTOS的配合如果你在跑FreeRTOSIDLE中断里可以释放一个信号量解析任务在信号量上等待。这样比在主循环里轮询标志位更优雅。但要注意信号量的give操作在中断里要用xSemaphoreGiveFromISR并且要处理pxHigherPriorityTaskWoken。7. 性能实测与优化建议我在STM32F103C8T6上实测过这套方案主频72MHzDMA缓冲区50字节主循环1ms一轮。结果如下指标数值帧率约330帧/秒丢帧率0.1%CPU占用2%解析耗时约15微秒/帧丢帧率低于0.1%是什么概念就是连续跑10分钟丢帧不超过20帧。对于大多数控制场景来说这个表现已经足够了。如果想进一步优化可以考虑把解析函数里的帧头查找改成从上次结束位置开始而不是每次从头找。用DMA的传输完成中断配合IDLE实现双缓冲。把通道值提取用查表法代替位运算但收益不大。最后分享一个我踩过的坑有一次调试时发现通道值总是差一点查了半天才发现是uint8_t和uint16_t的隐式类型转换问题。在拼接字节时如果不强制转成uint32_t中间结果会被截断。这个坑很隐蔽因为编译器不会报错但结果就是不对。所以写位操作时养成好习惯先转成足够大的类型再移位。
02
RELATED NEWS

相关资讯

更多网站建设与数字化升级内容

03
WHY YAOTU

想打造同款高转化官网?

懂行业、懂生意,从建站到增长一站式陪跑

场景化定制

不做模板站,围绕你的业务场景量身设计,小众不撞款。

营销型架构

以转化目标组织内容与路径,让官网真正带来询盘。

全周期服务

设计、开发、运营、运维一体,上线只是开始。

免费获取你的建站方案

留下需求,专属顾问 24 小时内为你输出方案建议。