DShot600 协议原理与实现 ------ 学习笔记
1. 协议概述
DShot(Digital Shot)是一种用于无人机飞控(FC)与电调(ESC)之间的数字通信协议,由 Flyduino 在 2016 年发布。与传统的 PWM/Oneshot/Multishot 等模拟协议不同,DShot 以二进制位的形式直接传输油门值,天然抗噪声和抖动。
DShot 各版本对比
| 参数 | DShot150 | DShot300 | DShot600 | DShot1200 |
|---|---|---|---|---|
| 比特率 | 150 kbit/s | 300 kbit/s | 600 kbit/s | 1200 kbit/s |
| 位周期 | 6.67 µs | 3.33 µs | 1.67 µs | 0.833 µs |
| T0H (逻辑0高电平) | 2.50 µs | 1.25 µs | 625 ns | 313 ns |
| T1H (逻辑1高电平) | 5.00 µs | 2.50 µs | 1.25 µs | 625 ns |
| 16位帧时长 | 106.72 µs | 53.28 µs | 26.72 µs | 13.28 µs |
| 理论最大更新率 | ~9.4 kHz | ~18.8 kHz | ~37.5 kHz | ~75 kHz |
| 双向DShot(遥测) | 有限支持 | ✅ 支持 | ✅ 支持 | ❌ 不支持 |
物理层与信号传输
DShot 采用单根信号线 + 共用地线传输,每台电机一条独立信号线,四轴共需 4 根信号线。
电气层面:
飞控 MCU 的 GPIO 引脚 ──── 信号线 ──── ESC 的 MCU GPIO 引脚
3.3V TTL 电平 3.3V / 5V 容受
(同一条线,双向模式下分时半双工)
GND ──────────────────── GND
核心特征:
| 特征 | 说明 |
|---|---|
| 电平标准 | TTL 数字电平,飞控侧 3.3V(STM32/ESP32),ESC 侧多数 3.3V 兼 5V 容受 |
| 传输方式 | 单端(非差分),非隔离,直接 GPIO 驱动 |
| 通信方向 | 单工 (FC → ESC)常态;双向模式下为分时半双工(同一线上 ESC 回传) |
| 空闲态 | 线路上拉保持高电平(MCU 内部上拉或外部电阻) |
| 线路数量 | 每台电机 1 根信号线 + 共用 GND,无独立时钟线 |
与模拟协议(PWM / Oneshot / Multishot)的物理层差异:
PWM / Oneshot / Multishot:
飞控输出: ▁▁▁▁▁█▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▇▁▁▁▁▁▁▁▁▁▁▁▁▁ (单个宽脉冲,周期可变)
|←──── 脉宽代表油门 ────→|
没有校验位,模拟量传输
DShot:
飞控输出: ▁▁█▆ ▁▁█▆ █▆ ▁▁█▆ ▁▁█▆ █▆ █▆ ▁▁█▆ ▁▁█▆ █▆ █▆ ▁▁█▆ ▁▁█▆ █▆ █▆ █▆ ▁▁
0 0 1 0 0 0 1 1 0 0 1 1 0 1 1 1 0 ← 16 bits
|←────────── 26.72 µs (DShot600) ──────────→|
带 CRC 校验位,数字量传输
双向模式下的线路状态切换:
FC 发送 DShot 帧 ESC 回传 GCR 遥测帧
(取反信号, bit11=1) (21-bit, 5/4 倍速率)
|←── 26.72µs ──→| ~30µs |←── ~28µs ──→|
┌────────────────┐ ┌──────────────┐
GPIO 线 │ FC 驱动 │ HIGH │ ESC 驱动 │ HIGH (空闲)
─────────┘ └────────┘ └──────────
FC GPIO 方向切换: ESC GPIO 方向切换:
OUTPUT → INPUT INPUT → OUTPUT
- 飞控发送完 16-bit 帧 + 2-bit 间隔后,立即将 GPIO 方向从 OUTPUT 切换为 INPUT
- ESC 检测到取反的 DShot 帧后,等待约 30--40µs,将自身 GPIO 从 INPUT 切换为 OUTPUT,回传 GCR 遥测帧
- 在约 30µs 的间隙内完成方向切换是双向 DShot 实现的关键难点,要求 MCU 能快速操作 GPIO 方向寄存器
| 对比维度 | 模拟协议(PWM/Oneshot/Multishot) | DShot |
|---|---|---|
| 信号本质 | 脉冲宽度代表油门(模拟量) | 16 位二进制比特流(数字量) |
| 抗干扰能力 | 弱 --- 脉宽被噪声拉伸即产生错误油门 | 强 --- 每位只有 0/1 判断,容错空间大 |
| 校验机制 | ❌ 无校验 | ✅ 4-bit CRC 校验 |
| 双向通信 | ❌ 不支持 | ✅ DShot300/600 支持 ESC 回传遥测 |
| 油门分辨率 | 通常 ~1000 级(受限于定时器时钟) | 2000 级(48--2047) |
| 是否需要校准 | 需要(PWM 行程校准) | 不需要(数字协议,固定映射) |
| 帧长一致性 | 可变(油门不同脉宽不同) | 固定(每帧始终 16 bit ≈ 26.72µs) |
信号完整性注意事项:
- 信号线建议 ≤ 30cm,过长时高频脉冲衰减严重,建议降级为 DShot300
- 信号线应远离电机相线(高电流 PWM)和电源线,防止电磁耦合导致误码
- 信号线上不要加滤波电容 --- 电容会吃掉 625ns 的窄脉冲
- 长距离场景可在 ESC 端串联 22--47Ω 终端电阻抑制信号反射
- 多轴场景下,信号线以星形从飞控引出,避免串扰
2. 核心原理
2.1 信号编码方式
DShot 采用脉冲宽度编码:每个位的周期固定(DShot600 为 1.67 µs),通过高电平持续时间区分逻辑 0 和 1。
逻辑 "0": ┌───┐ ┌───┐
│ │ ~625ns │ │
│ └──────────────────────│ └───
└──────────────────────────┘
|←────── 1.67µs ─────────→|
逻辑 "1": ┌──────────┐ ┌──────────┐
│ │ ~1250ns │ │
│ └─────────────│ └──
└────────────────────────┘
|←────── 1.67µs ────────→|
- 逻辑 0: 高电平 ≈ 625ns (37.5% 占空比), 低电平 ≈ 1.042µs
- 逻辑 1: 高电平 ≈ 1250ns (75% 占空比), 低电平 ≈ 417ns
空闲态为高电平,帧间隔至少需要 2 bit 周期的低电平。
2.2 帧格式 (16-bit)
DShot 每帧 16 位,分为三个字段:
| 位范围 | 长度 | 含义 | 说明 |
|---|---|---|---|
| bit 10:0 | 11 位 | 油门值 | 0--2047,0=停转,1--47=特殊命令,48--2047=油门 |
| bit 11 | 1 位 | 遥测请求标志 | 0=不请求遥测,1=请求遥测数据 |
| bit 15:12 | 4 位 | CRC 校验 | 对前 12 位数据计算的 4 位 CRC |
最终发送的 16 位帧组合方式:
frame = ((throttle << 1) | telemetry_bit) << 4 | crc
帧间隔(reset):帧结束后至少 2 bit 周期的低电平(约 3.33µs),用于 ESC 检测帧边界。
2.3 油门值范围详解
油门值(11 位,bit10:0)共 2048 个离散值(0--2047),分为三个功能区间:
油门值: 0 1 48 2047
│ Disarm │ 特殊命令 │ 正常油门 (0%--100%) │
│ 停转 │ 1--47 │ 48--2047 │
└─────────┴────────────┴───────────────────────────────┘
区间一:油门值 = 0 ------ Disarm(停转)
- ESC 完全解除武装,电机立即停止转动
- 需要重新走 ARM 流程才能再次驱动电机
- 安全用途:解锁开关关闭、失控保护、急停
区间二:油门值 = 1--47 ------ 特殊命令
- 此时 bit11(遥测标志位)必须置 1,ESC 才会将帧解释为命令
- 共 47 条命令,涵盖蜂鸣、电机方向、3D 模式、LED、保存设置等
- 详见 [第 4 节:特殊命令](#第 4 节:特殊命令)
区间三:油门值 = 48--2047 ------ 正常油门驱动
这是 DShot 协议的核心功能区间,油门值与推力输出的映射关系如下:
油门值 百分比 说明
────────────────────────────────────────────────
48 0.0% 最小油门 / 怠速(ARM 后电机起转的起始点)
↓ ↓ 线性映射
↓ ↓ percentage = (value - 48) / 1999 × 100%
↓ ↓
1024 ≈48.8% 中间点
↓ ↓
2047 100.0% 最大油门 / 满油
映射公式:
percentage = (throttle_value - 48) / 1999 × 100%
throttle_value = 48 + percentage / 100 × 1999
实例:
油门值 换算 约等于
───────────────────────────────────────────────────────
48 (48-48)/1999 × 100% = 0.000% → 怠速/0%油门
100 (100-48)/1999 × 100% = 2.60% → 低油门
500 (500-48)/1999 × 100% = 22.6% → 悬停油门(5寸机约此值)
1024 (1024-48)/1999 × 100% = 48.8% → 半油
1500 (1500-48)/1999 × 100% = 72.6% → 高油门
2000 (2000-48)/1999 × 100% = 97.6% → 接近满油
2047 (2047-48)/1999 × 100% = 100% → 满油
为什么 2047 是满油而不是 2048?
11 位二进制最大值为 2¹¹ - 1 = 2047。DShot 用完整的 11 位(0--2047)表示油门域,前 48 个值(0--47)被特殊命令和 Disarm 占用,留给实际油门的范围是 48--2047,共 2000 个步进 。这意味着 DShot 的油门分辨率为 2000 级,远超传统 PWM(通常约 1000 级),控制精度更高。
bit11 在油门模式下的作用:
在油门区间(48--2047)中,bit11 的含义切换为遥测请求:
bit[11] = 0:不请求遥测,ESC 只接收不回复bit[11] = 1:请求遥测,ESC 在收到帧后约 30--40µs 回传 GCR 遥测数据(仅双向 DShot 模式)
油门帧构造示例(油门值 = 500,不请求遥测):
throttle = 500 = 0b_001_1111_0100 (11 bits)
telem = 0 = 0b_0
packet = (500 << 1) | 0 = 1000 = 0b_0011_1110_1000 (12 bits)
CRC = 1000 ^ (1000>>4) ^ (1000>>8) = 1000 ^ 62 ^ 3
= 0b_0011_1110_1000 ^ 0b_0000_0011_1110 ^ 0b_0000_0000_0011
= 0b_0011_1101_0100
& 0x0F = 0b_0100 = 4
frame = (1000 << 4) | 4 = 16004 = 0x3E84
油门值与实际推力的关系:
DShot 传输的是线性油门值 ,但电机实际输出的推力与油门值非线性 。ESC 内部可以配置油门曲线来映射,飞控端也可以做 expo 补偿。飞控的 PID 控制器工作在归一化的油门空间中,DShot 的 2000 级精度确保了控制量的细腻度。
2.4 CRC 计算方法
CRC 为 4 位,通过对 12 位数据(11位油门 + 1位遥测标志)按 4 位一组逐级异或计算:
c
// 方法一:异或法
uint8_t calc_crc(uint16_t packet) {
// packet = ((throttle & 0x7FF) << 1) | (telem & 1); // 12 bits
uint8_t crc = 0;
uint16_t data = packet;
for (int i = 0; i < 3; i++) {
crc ^= data; // XOR by nibble
data >>= 4;
}
return crc & 0x0F;
}
// 方法二:移位异或法(等价)
crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
计算示例 --- 油门值 100、无遥测请求:
throttle = 100 = 0b00001100100
telem = 0
前12位数据构造:
packet = (100 << 1) | 0 = 200 = 0b000011001000
CRC 计算:
crc = 200 ^ (200>>4) ^ (200>>8) = 200 ^ 12 ^ 0 = 196
crc & 0x0F = 196 & 0x0F = 4
最终帧 = (200 << 4) | 4 = 3204 = 0x0C84
3. DShot600 关键时序
| 参数 | 数值 | 公式 |
|---|---|---|
| 比特率 | 600 kbit/s | --- |
| 位周期 | 1.67 µs | T = 1/600000 |
| T0H | 625 ns | T × 0.375 |
| T1H | 1250 ns | T × 0.75 |
| 16位帧时间 | 26.72 µs | 16 × 1.67µs |
| 含间隔的帧时间 | ~30 µs | 18 × 1.67µs |
| 帧间隔 (低电平) | ≥ 3.33 µs | ≥ 2 bit 周期 |
定时器配置的关键:在 STM32 上实现 DShot600,需要将定时器的 ARR(自动重装值)设置得足够大以获得足够的分辨率。例如:72 MHz 定时器时钟,预分频 3 → 18 MHz 计数频率,ARR = 29 → 30 个 tick = 1.67 µs。此时 BIT_0 的高电平约为 11 个 tick,BIT_1 的高电平约为 22 个 tick。
4. 特殊命令(油门值 0--47)
当油门值为 1--47 且遥测标志位(bit 11)置 1 时,帧被解析为特殊命令而非油门信号。
4.1 完整命令表
值 名称 说明 发送要求
─────────────────────────────────────────────────────────────────
0 MOTOR_STOP 停转(安全切断)
1 BEEP1 / BEACON1 蜂鸣音 1(寻机) 间隔 ≥380ms
2 BEEP2 / BEACON2 蜂鸣音 2 间隔 ≥380ms
3 BEEP3 / BEACON3 蜂鸣音 3 间隔 ≥400ms
4 BEEP4 / BEACON4 蜂鸣音 4 间隔 ≥400ms
5 BEEP5 / BEACON5 蜂鸣音 5 间隔 ≥400ms
6 ESC_INFO 查询 ESC 信息
7 SPIN_DIRECTION_1 正向旋转(永久) 连续 ≥6次
8 SPIN_DIRECTION_2 反向旋转(永久) 连续 ≥6次
9 3D_MODE_OFF 关闭 3D 模式 连续 ≥6次
10 3D_MODE_ON 开启 3D 模式 连续 ≥6次
11 SETTINGS_REQUEST 请求 ESC 设置
12 SAVE_SETTINGS 保存当前设置 连续 ≥6次, 等待 ≥12ms
20 SPIN_DIRECTION_NORMAL 临时正向旋转(断电恢复) 连续 ≥6次
21 SPIN_DIRECTION_REVERSED 临时反向旋转(断电恢复) 连续 ≥6次
22-29 LED0_ON ~ LED3_OFF 控制 LED (BLHeli_32)
30 AUDIO_STREAM_MODE KISS 音频流模式
31 SILENT_MODE KISS 静音模式
47 MAX 最大值(保留)
4.2 命令帧构造示例
以**命令 8(设置反向旋转)**为例:
命令值 = 8 = 0b_00000_01000
遥测标志 = 1
原数据12位 = (8 << 1) | 1 = 17 = 0b_0000_0001_0001
CRC = (0x011 ^ 0x00 ^ 0x00) & 0xF = 0
最终帧 = (17 << 4) | 0 = 0x0110
4.3 应用场景
- Turtle Mode(反乌龟模式): 用命令 21 临时反转两个电机,帮助翻倒的飞机自行翻正
- Props Out: 用命令 7/8 永久改变电机方向,实现桨叶外旋配置
- Beeper: 用命令 1--5 驱动电机线圈发声,替代外置蜂鸣器寻机
5. 双向 DShot(Bidirectional DShot)
双向 DShot 允许 ESC 在同一条信号线上回传遥测数据(eRPM),用于 RPM 陷波滤波,提升飞控性能。
5.1 工作原理
┌─────────┐ 发送 DShot 命令帧(bit11=1) ┌─────────┐
│ 飞控 │ ─────────────────────────────────────→ │ 电调 │
│ (FC) │ │ (ESC) │
│ │ ←───────────────────────────────────── │ │
└─────────┘ 回传 GCR 遥测帧(21 bit) └─────────┘
≈30--40 µs 后开始
- 飞控将正常的 DShot 帧取反后发送(CRC 按位取反),告诉 ESC "请求遥测"
- ESC 接收到取反帧后,在 ~30--40µs 后将信号线切换为输出模式
- ESC 以 5/4 倍 DShot 比特率回传 21 位 GCR 编码的遥测帧
- 飞控在半双工模式下接收并解码
关键约束 : 双向模式仅 DShot300 和 DShot600 可靠支持。DShot150 兼容性有限,DShot1200 因时序过于紧张无法支持。
5.2 GCR 编码(Group Coded Recording)
遥测数据使用 GCR(4→5) 编码,即每个 4 位半字节(nibble)映射为一个 5 位值。GCR 编码保证不会出现超过两个连续的 0,便于接收端做时钟恢复。
GCR 编码查找表
gcr_encode_table[16] = {
0b_00001, // 0 → 1
0b_00010, // 1 → 2
0b_00100, // 2 → 4
0b_01000, // 3 → 8
0b_10000, // 4 → 16
0b_10001, // 5 → 17
0b_10010, // 6 → 18
0b_10011, // 7 → 19
0b_10100, // 8 → 20
0b_10101, // 9 → 21
0b_10110, // 10 → 22
0b_11000, // 11 → 24
0b_11001, // 12 → 25
0b_11010, // 13 → 26
0b_11100, // 14 → 28
0b_11101, // 15 → 29
};
GCR 解码查找表(5→4)
gcr_decode_table[32] = {
[0b_00001] = 0, [0b_00010] = 1, [0b_00100] = 2, [0b_01000] = 3,
[0b_10000] = 4, [0b_10001] = 5, [0b_10010] = 6, [0b_10011] = 7,
[0b_10100] = 8, [0b_10101] = 9, [0b_10110] = 10, [0b_11000] = 11,
[0b_11001] = 12, [0b_11010] = 13, [0b_11100] = 14, [0b_11101] = 15,
// 非法码字 = 0xFF (无效)
};
5.3 遥测帧编码流程
16 bit 原始遥测数据 (12位有效 + 4位 CRC)
↓
拆分: N0 N1 N2 N3 (4个 nibble)
↓
GCR 编码: G0 G1 G2 G3 (4×5 = 20 bit)
↓
DMA 差分编码 + 加 start bit = 21 bit 最终帧
↓
以 5/4 × DShot 比特率串行输出
5.4 遥测帧解码流程
c
// 步骤1: 捕获21位原始帧
uint32_t gcr21 = capture_21_bits();
// 步骤2: 差分解码 (NRZI-like) → 恢复20位GCR
uint32_t gcr20 = gcr21 ^ (gcr21 >> 1);
// 步骤3: GCR 5→4 解码 → 得到16位数据
uint16_t raw_value = 0;
uint32_t mask = 0x1F; // 5-bit groups
for (int i = 0; i < 4; i++) {
uint8_t gcr_code = (gcr20 >> (i * 5)) & 0x1F;
uint8_t nibble = gcr_decode_table[gcr_code];
if (nibble == 0xFF) { /* GCR decode error */ }
raw_value |= (nibble << (i * 4));
}
// 步骤4: 验证 CRC
uint8_t received_crc = raw_value & 0x0F;
uint16_t data = raw_value >> 4;
uint8_t calc_crc = (data ^ (data >> 4) ^ (data >> 8)) & 0x0F;
if (calc_crc != received_crc) { /* CRC error, discard */ }
// 步骤5: 解析 eRPM
uint16_t exponent = (data >> 9) & 0x07; // 3 bits
uint16_t period_us = data & 0x01FF; // 9 bits mantissa
period_us = period_us << exponent; // 实际周期 (µs)
// 步骤6: 计算 eRPM(电转速 × 100)
// eRPM = (1000000 / period_us) × 60 / 100
// = (600000 / period_us)
uint16_t erpm_x100 = (6000000 + period_us / 2) / period_us;
// 步骤7: 机械 RPM (考虑磁极对数)
// RPM = eRPM × 100 / 磁极对数
uint16_t rpm = (erpm_x100 * 100) / motor_poles;
5.5 遥测数据帧格式
16-bit 原始遥测数据:
[eee] [mmmmmmmmm] [cccc]
↑ ↑ ↑
指数位 周期尾数 CRC校验
(3 bit) (9 bit) (4 bit)
eRPM 周期 = mantissa × 2^exponent (单位: µs)
5.6 扩展遥测 (EDT --- Extended DShot Telemetry)
现代 ESC 固件(AM32、Bluejay)通过前缀位区分帧类型:
前缀位 (4 bit) 帧类型
────────────────────────────
xxx1 或 0000 eRPM 帧(标准)
0010 温度 (°C)
0100 电压 (×0.25V)
0110 电流 (A)
1000 调试数据 1
1010 调试数据 2
1100 应力水平
1110 状态帧
6. STM32 实现方案(Timer + DMA)
6.1 整体架构
┌───────────────┐ DMA (内存→外设) ┌──────────────┐
│ DMA缓冲区 │ ──────────────────────────→ │ TIMx CCRx │ ──→ GPIO: PWM 输出
│ [18个值] │ 自动传输 │ (PWM模式) │
└───────────────┘ └──────────────┘
以 STM32F103C8T6(72 MHz)为例,DShot600 的定时器配置:
c
// TIM2 配置,用于 DShot600
// 72 MHz / 4 = 18 MHz 定时器时钟
TIM_TimeBaseStruct.TIM_Prescaler = 3; // 72/4 = 18 MHz
TIM_TimeBaseStruct.TIM_Period = 29; // 30 ticks = 1.67 µs
// DShot600 位定义(以 18MHz 定时器时钟下的 tick 数表示)
#define DSHOT600_BIT_PERIOD 30 // 1.67 µs = 30 ticks
#define DSHOT600_BIT_0_HIGH 11 // ~625 ns
#define DSHOT600_BIT_1_HIGH 22 // ~1250 ns
#define DSHOT600_BIT_LOW 0 // 0 = 低电平
6.2 DMA 缓冲区构造
c
uint16_t dma_buffer[18]; // 16位数据 + 2位帧间隔
void build_dma_buffer(uint16_t packet) {
for (int i = 0; i < 16; i++) {
if (packet & (1 << (15 - i))) {
dma_buffer[i] = DSHOT600_BIT_1_HIGH; // T1H
} else {
dma_buffer[i] = DSHOT600_BIT_0_HIGH; // T0H
}
}
// 帧间隔: 至少 2 bit 周期低电平
dma_buffer[16] = 0;
dma_buffer[17] = 0;
}
注意:缓冲区中的值是定时器 CCR 值(不是占空比)。PWM 模式配置为:到达 CCR 值时翻转输出,到达 ARR 时复位。因此每个 DMA 传输对应一个完整的位周期。
实际上更常用的方式是将定时器配置为单脉冲模式 (One Pulse Mode) ,并利用 OC 预装载 (OCxPE) 确保 DMA 写入的 CCR 值在下一个更新事件才生效。
6.3 完整发送流程
c
// 1. 构造帧(含 CRC)
uint16_t dshot_encode(uint16_t throttle, uint8_t telem) {
uint16_t packet = ((throttle & 0x7FF) << 1) | (telem & 1);
uint8_t crc = (packet ^ (packet >> 4) ^ (packet >> 8)) & 0x0F;
return (packet << 4) | crc;
}
// 2. 构造 DMA 缓冲区
void dshot_prepare_buffer(uint16_t frame) {
for (int i = 0; i < 16; i++) {
dma_buffer[i] = (frame & (1 << (15 - i)))
? DSHOT600_BIT_1_HIGH
: DSHOT600_BIT_0_HIGH;
}
dma_buffer[16] = 0; // 间隔位1
dma_buffer[17] = 0; // 间隔位2
}
// 3. 发送
void dshot_send(void) {
DMA_Cmd(DMA1_Channel7, DISABLE);
DMA_SetCurrDataCounter(DMA1_Channel7, 18);
DMA_Cmd(DMA1_Channel7, ENABLE);
TIM_Cmd(TIM2, ENABLE);
// DMA 传输完成后会在 ISR 中自动停止定时器
}
6.4 定时器配置要点(STM32 HAL)
c
// 关键点:
// 1. 使用 OC_Preload (TIM_OC2PreloadConfig) 防止 DMA 写入时出现毛刺
// 2. 配置为 PWM1 模式
// 3. 使能 DMA 传输到 CCR 寄存器
// 4. DMA 传输完成中断中禁用定时器输出
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OCIdleState = TIM_OCIdleState_Set; // 空闲态高电平
6.5 常见 STM32 系列定时器时钟计算
| MCU | 主频 | 推荐预分频 | 定时器时钟 | ARR | BIT_0 | BIT_1 |
|---|---|---|---|---|---|---|
| STM32F103 | 72 MHz | 3 | 18 MHz | 29 | 11 | 22 |
| STM32F4xx | 168 MHz | 7 | 21 MHz | 34 | 13 | 26 |
| STM32F7xx | 216 MHz | 11 | 18 MHz | 29 | 11 | 22 |
| STM32G4xx | 170 MHz | 7 | 21.25 MHz | 34 | 13 | 26 |
7. 其他 MCU 实现方案
7.1 ESP32 --- RMT 方式
ESP32 的 RMT(Remote Control Transceiver)外设天然适合生成精确的脉冲序列:
c
// RMT 每项 { level, duration },单位为 RMT tick (80 MHz = 12.5ns)
// DShot600: 1.67µs / 12.5ns = 133 ticks/bit
// T0H: 625ns / 12.5ns = 50 ticks
// T1H: 1250ns / 12.5ns = 100 ticks
rmt_item32_t dshot_items[18];
for (int i = 0; i < 16; i++) {
bool bit_val = (frame >> (15 - i)) & 1;
dshot_items[i].level0 = 1;
dshot_items[i].duration0 = bit_val ? 100 : 50; // T0H or T1H
dshot_items[i].level1 = 0;
dshot_items[i].duration1 = 133 - dshot_items[i].duration0; // 剩余 = 低电平
}
dshot_items[16] = (rmt_item32_t){{0, 133, 0, 0}}; // 间隔位1
dshot_items[17] = (rmt_item32_t){{0, 133, 0, 0}}; // 间隔位2
rmt_write_items(channel, dshot_items, 18, true); // true = 等待发送完成
7.2 Teensy --- UART 方式
利用硬件 UART 的起始位/停止位来合成脉冲波形:
c
// UART 在 8N1 模式下,每个字节 = 1 start bit (0) + 8 data bits + 1 stop bit (1)
// 通过设置波特率使每个 UART 帧 = 1 DShot 位周期
// 波特率 = 1 / 1.67µs ≈ 600kbaud
// 逻辑0: 发送 0x00 (只有起始位=低, 其余=高) → 短高脉冲
// 逻辑1: 发送 0xF0 (起始位+4高+4低) → 长高脉冲
7.3 方案对比
| 方案 | 精度 | CPU 占用 | 适用场景 |
|---|---|---|---|
| Timer + DMA | ★★★★★ | 极低 | STM32,推荐 |
| RMT | ★★★★★ | 低 | ESP32,推荐 |
| UART | ★★★★ | 低 | Teensy / 通用 MCU |
| 软件位脉冲 (bit-banging) | ★★ | 极高 | 应急/调试 |
8. ARM / 启动流程
DShot 上电后 ESC 不立即响应油门,需要完成上电→检测→ARM→运行流程:
上电 ──→ 3声短鸣 (初始化成功)
│
▼
检测到 DShot 信号 ──→ 1声低鸣 (ARM 开始)
│
▼
油门归零 ──→ 1声高鸣 (ARM 完成)
│
▼
正常油门响应 (48--2047)
ARM 注意事项
- 油门值
0= 完全解除 ARM(Disarm) - 油门值
48= 已 ARM + 零油门(可在此基础上增加油门值) - 上电后需持续发送 DShot 帧(帧间隔 ≤ 50ms),否则 ESC 会超时判定信号丢失并 Disarm
- 常规更新频率:8--32 kHz,即每 31.25--125 µs 发送一帧
9. CRC 容错与信号质量
9.1 4-bit CRC 的局限性
4 位 CRC 只能检测有限范围内的位翻转错误,无法检测所有组合错误。Betaflight 在实践中对每帧计算 CRC,若 CRC 错误则丢弃该帧(保留上一帧油门值),并对连续错误帧计数。
9.2 信号完整性建议
- 移除信号线上的滤波电容: DShot600 的 625ns 高脉冲会被 RC 滤波严重衰减
- 信号线长度 ≤ 30cm: 超过此长度建议降级为 DShot300
- 避免并行走线: DShot 信号线远离电源线和电机相线,防止耦合噪声
- 地线分离: 信号地与功率地分开走线,单点接地
- 终端匹配 (可选): 在 ESC 端串联 22--47Ω 电阻,抑制反射
10. 学习总结
DShot600 协议精华
| 要点 | 核心内容 |
|---|---|
| 信号本质 | 脉冲宽度编码,1.67µs/bit,T0H=625ns,T1H=1250ns |
| 帧结构 | 16-bit = 11(油门) + 1(遥测) + 4(CRC),含间隔约30µs |
| 油门范围 | 0=停转, 1-47=命令, 48-2047=油门(0-100%) |
| CRC 算法 | (data ^ (data>>4) ^ (data>>8)) & 0xF |
| 双向遥测 | GCR 5→4编码,21-bit 回传帧,eRPM = mantissa << exponent |
| MCU实现 | STM32用TIM+DMA,ESP32用RMT,Teensy用UART |
| ARM流程 | 上电3短鸣→检测信号1低鸣→油门归零1高鸣→可正常运行 |
| 适用场景 | 高速PID回路(16-32kHz),竞速级无人机 |
速记公式
帧构造: frame = ((throttle << 1) | telem) << 4 | crc
CRC计算: crc = (data ^ (data>>4) ^ (data>>8)) & 0xF
eRPM计算: erpm = 600000 / period_us
机械RPM: rpm = erpm × 100 / 磁极对数
关键参考
- 原始 DShot 协议文章: blck.mn/2016/11/dshot-the-new-kid-on-the-block
- Betaflight DShot 文档: betaflight.com/docs
- STM32 DShot600 参考实现: github.com/gorka3reta/STM32-BLHeli_S-DShot600
- Bi-directional DShot: github.com/symonb/Bidirectional-DSHOT-and-RPM-Filter
- ESP32 DShot 库: github.com/MichelJansson/DShot-ESP32RMT
- Extended DShot Telemetry: github.com/bird-sanctuary/extended-dshot-telemetry