这篇文档说明 SBUS 接收机如何连接到 STM32,为什么很多情况下需要做硬件反相电路,SBUS 数据帧如何解析,以及如何在 STM32 代码中接收并解包 16 个遥控通道。
适用场景:
- 接收机输出 SBUS 信号
- 主控为 STM32
- 使用 UART/USART 接收 SBUS
- 需要自己解析遥控器通道值、失控标志、丢帧标志
安全提醒:调试飞控、遥控链路、电机输出时,电机测试和代码联调阶段建议卸桨。SBUS 解析错误可能导致油门误判,必须先在串口打印或调试器中验证通道值正确后再接入电机控制逻辑。
1. SBUS 是什么
SBUS 是一种常见的遥控接收机串行输出协议,常用于 Futaba、FrSky、Radiomaster、乐迪、富斯等接收机或兼容设备。
SBUS 的特点:
- 一根信号线传输多个通道
- 常见为 16 个比例通道 + 2 个数字通道
- 固定 25 字节一帧
- 常见串口参数为
100000 baud, 8E2 - 信号通常是反相串口
- 每帧包含通道数据、丢帧标志、失控保护标志
SBUS 接收链路可以理解为:
遥控器
SBUS 接收机
反相电路或 MCU 硬件反相
STM32 UART RX
SBUS 帧同步与解析
16 路通道值
Failsafe / Frame Lost
2. 为什么要做硬件反相电路
2.1 普通 UART 和 SBUS 的电平逻辑不同
普通 UART 的空闲电平通常是高电平:
text
普通 UART:
空闲 = 高电平
起始位 = 低电平
数据位 = 按 LSB first 发送
停止位 = 高电平
很多 SBUS 接收机输出的是反相串口信号,也就是相对于普通 UART 逻辑取反:
text
SBUS 反相信号:
空闲 = 低电平
起始位 = 高电平
数据位 = 全部反相
停止位 = 低电平
如果直接把反相 SBUS 信号接到普通 STM32 UART RX 上,UART 看到的起始位、停止位和数据位都是反的,结果通常是:
- 收不到稳定数据
- 收到乱码
- 偶尔出现错误字节
- 无法稳定找到
0x0F帧头 - UART 频繁出现 framing error、parity error
所以需要在进入 STM32 UART 之前把 SBUS 信号再反相一次,使它变回普通 UART 能识别的电平。
接收机 SBUS 输出
反相 UART
硬件反相电路
再反相一次
STM32 UART RX
普通 UART 逻辑
2.2 哪些情况下必须硬件反相
以下情况建议或必须使用硬件反相:
- 使用的 STM32 UART 不支持 RX 反相功能
- 使用的 HAL/库没有方便开启
RXINV - 想让代码保持普通 UART 配置,减少平台依赖
- 接收机输出为标准反相 SBUS,而 STM32 直接接收乱码
- 后续可能更换 MCU,希望硬件层统一处理
2.3 哪些情况下可以不做硬件反相
有些 STM32 系列 USART 支持硬件反相输入,例如部分 F3、F7、G4、H7、L4 等系列支持类似 RXINV 的功能。此时可以在 USART 外设里开启 RX 反相,让 MCU 内部完成反相。
这种情况下链路是:
接收机 SBUS 输出
STM32 UART RX
USART RXINV 内部反相
SBUS 解析代码
但是要注意:
- 不是所有 STM32 都支持 RX 反相
- 不同系列寄存器和 HAL 接口不同
- 如果后续换芯片,代码可能要改
- 有些开发板的接收脚可能经过其他电路,不能只看芯片手册
因此,从工程稳定性来说,外部硬件反相是比较直观、容易排查的方案。
3. SBUS 串口参数
常见 SBUS 参数如下:
| 项目 | 参数 |
|---|---|
| 波特率 | 100000 |
| 数据位 | 8 bit |
| 校验 | Even parity |
| 停止位 | 2 stop bits |
| 帧长度 | 25 bytes |
| 帧头 | 0x0F |
| 通道位宽 | 11 bit |
| 比例通道数量 | 16 |
| 数字通道 | CH17、CH18 |
STM32 UART 应配置为:
text
BaudRate = 100000
WordLength = 9B 或 8B + parity,取决于 HAL 系列定义
Parity = EVEN
StopBits = 2
Mode = RX
在 STM32 HAL 中,启用校验位时,很多系列会把 WordLength = UART_WORDLENGTH_9B 和 Parity = UART_PARITY_EVEN 组合起来使用,实际有效数据仍然是 8 bit。具体以芯片系列 HAL 为准。
4. SBUS 25 字节帧格式
SBUS 一帧固定 25 字节:
text
Byte 0 : 帧头,固定 0x0F
Byte 1-22 : 16 个通道数据,每个通道 11 bit,连续打包
Byte 23 : 标志位 flags
Byte 24 : 帧尾,常见为 0x00,也可能因设备而异
整体结构:
text
┌────────┬──────────────────────────────┬────────┬────────┐
│ Byte 0 │ Byte 1 ... Byte 22 │ Byte23 │ Byte24 │
├────────┼──────────────────────────────┼────────┼────────┤
│ 0x0F │ 16 channels, 11 bits each │ flags │ end │
└────────┴──────────────────────────────┴────────┴────────┘
16 个通道一共需要:
text
16 channels * 11 bits = 176 bits = 22 bytes
所以 Byte 1 到 Byte 22 正好装下 16 个 11 bit 通道。
5. SBUS 通道数据如何解析
5.1 通道值范围
SBUS 通道原始值通常大约是:
text
最小值:172 左右
中位值:992 左右
最大值:1811 左右
不同遥控器、接收机、端点设置可能略有差异。工程里通常会把它映射成 PWM 风格的范围:
text
172 -> 1000 us
992 -> 1500 us
1811 -> 2000 us
5.2 位打包方式
SBUS 的通道数据不是一个通道占两个字节,而是连续按 11 bit 紧密打包。
示意:
text
Byte 1 Byte 2 Byte 3 Byte 4 ...
bbbbbbbb bbbbbbbb bbbbbbbb bbbbbbbb ...
└──CH1──┘└CH2 crosses byte boundary...
每个通道取 11 bit,并用 0x07FF 掩码保留低 11 位。
5.3 flags 字节
Byte 23 是标志位:
| 位 | 含义 |
|---|---|
| bit0 | 数字通道 CH17 |
| bit1 | 数字通道 CH18 |
| bit2 | frame lost,当前帧丢失或链路不稳定 |
| bit3 | failsafe,接收机进入失控保护 |
| bit4-bit7 | 通常保留 |
解析时一定要检查 frame lost 和 failsafe,不能只看通道值。因为接收机失控时,通道可能保持最后值,或者输出预设 failsafe 值。
6. 接收机如何连接到 STM32
6.1 使用硬件反相电路的推荐连接
推荐链路:
SBUS Signal
UART RX Signal
GND
SBUS 接收机
反相电路
STM32 USART_RX
STM32 GND
5V 或 3.3V
基本接线:
| 接收机引脚 | 连接位置 |
|---|---|
SBUS/S.PORT/SBUS OUT |
反相电路输入 |
GND |
STM32 GND |
VCC |
按接收机要求接 5V 或 3.3V |
| 反相电路输出 | STM32 某个 USART_RX |
注意:
- 接收机 GND 必须和 STM32 GND 共地
- 不要把 5V 信号直接接到不耐 5V 的 STM32 引脚
- 如果接收机由飞控 5V 供电,STM32 板和接收机仍然要共地
- 只需要接 SBUS 信号到 STM32 RX,一般不需要 STM32 TX 接回接收机
7. 如何做硬件反相
7.1 方案一:NPN 三极管反相
这是最容易搭出来的方案,适合验证和小批量硬件。
推荐器件:
- NPN 三极管:S8050、2N3904、BC817、MMBT3904 等
- 基极电阻:
4.7kΩ - 10kΩ - 集电极上拉:
4.7kΩ - 10kΩ到 3.3V - 基极下拉:
47kΩ - 100kΩ,可选,增强抗干扰
电路图:
text
3.3V
|
4.7k
|
+------------------> STM32 USART_RX
|
C |
SBUS_IN --10k--B NPN
E |
|
GND
可选:
NPN 基极 B --100k-- GND
工作原理:
- SBUS_IN 为高电平时,NPN 导通,输出被拉到低电平
- SBUS_IN 为低电平时,NPN 截止,输出被上拉到 3.3V
- 所以输出信号相对于输入信号反相
这个电路还有一个好处:输出上拉到 3.3V,可以让 STM32 RX 看到安全的 3.3V 电平。
7.2 NPN 反相电路的实际连接
text
接收机 SBUS 信号线
|
10k
|
+---- NPN 基极 B
NPN 发射极 E ---- GND
NPN 集电极 C ---- STM32 USART_RX
|
4.7k
|
3.3V
接收机 GND ------------------- STM32 GND
焊接时建议:
- 反相电路尽量靠近 STM32 RX 引脚
- SBUS 信号线不要和电机三相线、电源大电流线并排太长
- 如果线较长,可以使用屏蔽线或双绞线,信号线和 GND 成对走
- 上拉必须接 STM32 的 3.3V,不建议上拉到 5V
7.3 方案二:逻辑门反相器
可以使用单门反相器芯片,例如:
74LVC1G0474LVC1G14SN74LVC1G04SN74LVC1G14
推荐使用带施密特触发输入的 74LVC1G14,抗干扰能力更好。
连接方式:
text
接收机 SBUS_OUT ----> 74LVC1G14 输入
74LVC1G14 输出 ----> STM32 USART_RX
74LVC1G14 VCC ----> 3.3V
74LVC1G14 GND ----> GND
接收机 GND ---------> STM32 GND
注意:
- 芯片供电用 3.3V
- 输入是否能承受 5V 要看具体型号数据手册
- 如果接收机 SBUS 输出是 5V,而逻辑芯片输入不耐 5V,需要先分压或选用 5V tolerant 输入器件
7.4 方案三:STM32 内部 RX 反相
如果芯片支持 USART RX 反相,可以不加外部反相电路,直接配置寄存器。
概念上类似:
c
// 不同 STM32 系列写法不同,此处只表达思路
USARTx->CR2 |= USART_CR2_RXINV;
使用 CubeMX/HAL 时,有些系列可以在 UART Advanced Features 中打开:
text
RX Pin Active Level Inversion: Enable
这种方案硬件最简单,但代码和芯片型号绑定更强。如果你要做兼容性较强的飞控板,外部反相更容易排查。
8. STM32 代码如何接收 SBUS
8.1 接收策略
SBUS 每帧 25 字节,可以用几种方式接收:
| 方法 | 优点 | 缺点 |
|---|---|---|
| UART 中断逐字节接收 | 简单直观 | CPU 中断较多 |
| DMA 循环缓冲区 | 稳定、低开销 | 代码稍复杂 |
| DMA + IDLE 中断 | 常用、易同步 | 要处理半帧和粘包 |
对于飞控或遥控输入,推荐:
- UART 使用 DMA 循环接收
- 软件扫描缓冲区寻找
0x0F - 找到 25 字节后检查帧尾和 flags
- 解包 16 个通道
8.2 SBUS 数据结构
c
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>
#define SBUS_FRAME_SIZE 25
#define SBUS_START_BYTE 0x0F
#define SBUS_END_BYTE 0x00
typedef struct {
uint16_t ch[16];
bool ch17;
bool ch18;
bool frame_lost;
bool failsafe;
bool valid;
} sbus_data_t;
8.3 解析 25 字节 SBUS 帧
下面代码输入一帧 25 字节数据,输出 16 个通道和状态标志。
c
static bool sbus_parse_frame(const uint8_t frame[SBUS_FRAME_SIZE], sbus_data_t *out)
{
if (frame == NULL || out == NULL) {
return false;
}
if (frame[0] != SBUS_START_BYTE) {
out->valid = false;
return false;
}
/*
* 大多数 SBUS 接收机帧尾为 0x00。
* 有些设备的 Byte24 可能携带扩展信息,工程中可以先不强制判断帧尾,
* 或只在调试阶段打印观察。
*/
if (frame[24] != SBUS_END_BYTE) {
/* 如果你的接收机帧尾不是 0x00,可以放宽这个判断。 */
out->valid = false;
return false;
}
out->ch[0] = ((frame[1] | frame[2] << 8) & 0x07FF);
out->ch[1] = ((frame[2] >> 3 | frame[3] << 5) & 0x07FF);
out->ch[2] = ((frame[3] >> 6 | frame[4] << 2 | frame[5] << 10) & 0x07FF);
out->ch[3] = ((frame[5] >> 1 | frame[6] << 7) & 0x07FF);
out->ch[4] = ((frame[6] >> 4 | frame[7] << 4) & 0x07FF);
out->ch[5] = ((frame[7] >> 7 | frame[8] << 1 | frame[9] << 9) & 0x07FF);
out->ch[6] = ((frame[9] >> 2 | frame[10] << 6) & 0x07FF);
out->ch[7] = ((frame[10] >> 5 | frame[11] << 3) & 0x07FF);
out->ch[8] = ((frame[12] | frame[13] << 8) & 0x07FF);
out->ch[9] = ((frame[13] >> 3 | frame[14] << 5) & 0x07FF);
out->ch[10] = ((frame[14] >> 6 | frame[15] << 2 | frame[16] << 10) & 0x07FF);
out->ch[11] = ((frame[16] >> 1 | frame[17] << 7) & 0x07FF);
out->ch[12] = ((frame[17] >> 4 | frame[18] << 4) & 0x07FF);
out->ch[13] = ((frame[18] >> 7 | frame[19] << 1 | frame[20] << 9) & 0x07FF);
out->ch[14] = ((frame[20] >> 2 | frame[21] << 6) & 0x07FF);
out->ch[15] = ((frame[21] >> 5 | frame[22] << 3) & 0x07FF);
const uint8_t flags = frame[23];
out->ch17 = (flags & (1U << 0)) != 0;
out->ch18 = (flags & (1U << 1)) != 0;
out->frame_lost = (flags & (1U << 2)) != 0;
out->failsafe = (flags & (1U << 3)) != 0;
out->valid = true;
return true;
}
8.4 原始值映射到 1000-2000
c
static uint16_t sbus_to_pwm(uint16_t raw)
{
const int32_t in_min = 172;
const int32_t in_max = 1811;
const int32_t out_min = 1000;
const int32_t out_max = 2000;
int32_t value = raw;
if (value < in_min) {
value = in_min;
} else if (value > in_max) {
value = in_max;
}
return (uint16_t)(out_min + (value - in_min) * (out_max - out_min) / (in_max - in_min));
}
通道映射示例:
c
uint16_t roll = sbus_to_pwm(sbus.ch[0]);
uint16_t pitch = sbus_to_pwm(sbus.ch[1]);
uint16_t throttle = sbus_to_pwm(sbus.ch[2]);
uint16_t yaw = sbus_to_pwm(sbus.ch[3]);
不同遥控器的通道顺序可能不同。常见有:
text
AETR: Aileron, Elevator, Throttle, Rudder
TAER: Throttle, Aileron, Elevator, Rudder
实际工程中应通过串口打印确认每个摇杆对应哪个通道。
9. HAL UART 初始化示例
以下是典型 STM32 HAL 配置示例。不同芯片系列的 WordLength 定义可能不同,需要按你的工程实际调整。
c
UART_HandleTypeDef huart1;
static void MX_USART1_UART_Init(void)
{
huart1.Instance = USART1;
huart1.Init.BaudRate = 100000;
huart1.Init.WordLength = UART_WORDLENGTH_9B;
huart1.Init.StopBits = UART_STOPBITS_2;
huart1.Init.Parity = UART_PARITY_EVEN;
huart1.Init.Mode = UART_MODE_RX;
huart1.Init.HwFlowCtl = UART_HWCONTROL_NONE;
huart1.Init.OverSampling = UART_OVERSAMPLING_16;
if (HAL_UART_Init(&huart1) != HAL_OK) {
Error_Handler();
}
}
如果你的 STM32 支持内部 RX 反相,可能需要增加类似配置:
c
UART_AdvFeatureInitTypeDef adv = {0};
adv.AdvFeatureInit = UART_ADVFEATURE_RXINVERT_INIT;
adv.RxPinLevelInvert = UART_ADVFEATURE_RXINV_ENABLE;
if (HAL_UARTEx_AdvFeatureConfig(&huart1, &adv) != HAL_OK) {
Error_Handler();
}
注意:这段高级特性配置不是所有 STM32 HAL 都有,具体以你的芯片系列为准。
10. UART 中断逐字节接收示例
这个示例适合先验证功能,代码直观。后续可以再改成 DMA。
c
static uint8_t sbus_rx_byte;
static uint8_t sbus_frame[SBUS_FRAME_SIZE];
static uint8_t sbus_index = 0;
static sbus_data_t sbus;
void sbus_start_receive_it(void)
{
HAL_UART_Receive_IT(&huart1, &sbus_rx_byte, 1);
}
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
if (huart->Instance != USART1) {
return;
}
if (sbus_index == 0) {
if (sbus_rx_byte == SBUS_START_BYTE) {
sbus_frame[sbus_index++] = sbus_rx_byte;
}
} else {
sbus_frame[sbus_index++] = sbus_rx_byte;
if (sbus_index >= SBUS_FRAME_SIZE) {
sbus_parse_frame(sbus_frame, &sbus);
sbus_index = 0;
}
}
HAL_UART_Receive_IT(&huart1, &sbus_rx_byte, 1);
}
这个版本的同步逻辑比较简单:只有遇到 0x0F 才开始收一帧。如果中途丢字节,下一帧会重新同步。
11. DMA 循环缓冲区接收思路
飞控代码里更推荐 DMA 循环接收,因为 SBUS 是持续不断的数据流。
UART RX DMA circular buffer
定时扫描新数据
寻找 0x0F 帧头
检查后续是否够 25 字节
复制 25 字节到 frame
检查帧尾和 flags
解包 16 通道
伪代码:
c
uint8_t dma_buffer[128];
uint16_t last_pos = 0;
void sbus_poll_dma(void)
{
uint16_t pos = sizeof(dma_buffer) - __HAL_DMA_GET_COUNTER(huart1.hdmarx);
while (last_pos != pos) {
uint8_t b = dma_buffer[last_pos];
last_pos = (last_pos + 1) % sizeof(dma_buffer);
sbus_feed_byte(b);
}
}
其中 sbus_feed_byte() 可以复用逐字节接收的状态机逻辑。
12. 完整逐字节状态机示例
下面是和 UART 中断、DMA 轮询都能配合的通用喂字节解析器。
c
typedef struct {
uint8_t frame[SBUS_FRAME_SIZE];
uint8_t index;
sbus_data_t data;
bool updated;
} sbus_parser_t;
static sbus_parser_t parser;
void sbus_parser_init(sbus_parser_t *p)
{
p->index = 0;
p->updated = false;
p->data.valid = false;
}
void sbus_feed_byte(sbus_parser_t *p, uint8_t byte)
{
if (p->index == 0) {
if (byte != SBUS_START_BYTE) {
return;
}
}
p->frame[p->index++] = byte;
if (p->index >= SBUS_FRAME_SIZE) {
p->updated = sbus_parse_frame(p->frame, &p->data);
p->index = 0;
}
}
主循环使用方式:
c
if (parser.updated) {
parser.updated = false;
if (!parser.data.failsafe && !parser.data.frame_lost) {
uint16_t throttle = sbus_to_pwm(parser.data.ch[2]);
/* 使用 throttle、roll、pitch、yaw 等通道 */
} else {
/* 进入失控保护逻辑 */
}
}
13. 调试方法
13.1 先看 UART 是否收到正确帧头
如果硬件反相和 UART 参数正确,接收缓存中应该稳定出现:
text
0x0F ... 25 bytes ... 0x00
0x0F ... 25 bytes ... 0x00
如果看到的是乱码,优先检查:
- 是否反相
- 波特率是否为 100000
- 是否 Even parity
- 是否 2 stop bits
- GND 是否共地
- RX 引脚是否选错
- 接收机是否真的输出 SBUS,而不是 IBUS、CRSF、PPM
13.2 打印原始通道值
建议先打印 16 个原始通道:
text
CH1=992 CH2=993 CH3=172 CH4=991 ...
然后依次拨动摇杆和开关,确认:
- 哪个摇杆对应哪个通道
- 最小值和最大值是否合理
- 中位是否接近 992
- failsafe 断开遥控器后是否置位
13.3 用示波器或逻辑分析仪验证
如果有示波器,建议测三个点:
text
测点 1:接收机 SBUS 原始输出
测点 2:反相电路输出
测点 3:STM32 RX 引脚
正确现象:
- 测点 1 和测点 2 波形逻辑相反
- 测点 2 空闲状态应为高电平
- STM32 RX 引脚电平不超过 3.3V
- 一帧周期大约在几毫秒到十几毫秒量级,取决于接收机输出频率
14. 常见问题
14.1 风险:接收机输出 5V,STM32 RX 不耐 5V
如果接收机 SBUS 信号是 5V,而 STM32 RX 引脚不耐 5V,直接连接可能损坏 MCU。
使用 NPN 反相电路时,输出由 3.3V 上拉决定,因此 STM32 RX 侧是 3.3V,相对安全。基极侧通过 10k 电阻限流,也比较稳妥。
14.2 收不到 0x0F
优先检查:
- 反相方向是否正确
- UART 参数是否是
100000 8E2 - 是否误接到了 S.PORT、CRSF、IBUS 口
- RX 和 TX 是否接反
- 接收机是否已经对频并正常输出
14.3 能收到数据但通道乱跳
可能原因:
- 没有共地
- 信号线太长或靠近电机/电调线
- 反相电路上拉太弱
- 没有处理 UART 错误标志
- 帧同步逻辑太弱,丢字节后没有重新找帧头
14.4 failsafe 该怎么处理
建议策略:
failsafe = true:立即进入失控保护,不再信任摇杆通道frame_lost = true:可以短时间容忍,但要计数;连续多帧丢失后进入保护- 长时间没有收到新 SBUS 帧:进入保护
示例逻辑:
c
if (sbus.failsafe) {
enter_failsafe();
}
if (sbus.frame_lost) {
lost_frame_count++;
} else {
lost_frame_count = 0;
}
if (lost_frame_count > 10) {
enter_failsafe();
}
15. 推荐硬件原理图总结
如果只是做 STM32 读取 SBUS,推荐最小硬件如下:
text
接收机 VCC -------- 5V 或 3.3V,按接收机规格
接收机 GND -------- STM32 GND
接收机 SBUS -------- 10k -------- NPN B
NPN E -------- GND
NPN C -------- STM32 USART_RX
NPN C -------- 4.7k -------- 3.3V
NPN B -------- 100k -------- GND,可选
用一句话概括:
text
SBUS 原始信号通常是反相 UART,STM32 普通 UART 不能直接识别;
用 NPN 或逻辑门把它反相成普通 UART,再用 100000 8E2 接收 25 字节帧;
从 Byte1-22 解出 16 个 11 bit 通道,从 Byte23 读取失控和丢帧标志。
16. 建议的开发顺序
- 先确认接收机确实输出 SBUS。
- 搭 NPN 或逻辑门反相电路。
- STM32 UART 配置为
100000 8E2。 - 串口接收原始字节,确认稳定出现
0x0F帧头。 - 使用 25 字节帧解析函数解包 16 个通道。
- 打印通道原始值,确认摇杆和通道对应关系。
- 映射到 1000-2000 或内部控制量。
- 加入
failsafe、frame_lost、超时保护。 - 最后再接入飞控、电机或舵机控制逻辑。