电机控制:H 桥、死区与互补输出(以 Cortex-M4 为例)
《定时器与 PWM》 结尾的速查表里,电机那一行写着”~20kHz,高级定时器(TIM1/TIM8)”,末尾一句”死区和互补输出下篇见”。今天兑现这笔账。但先看一个现场——一个几乎每个自己搭过电机驱动的人都经历过的现场:
你用定时器的两路通道引出一对 PWM,软件保证它们反相。示波器一看:完美互补,沿对得整整齐齐,比钟表还准。你满意地把它接上四颗 MOS 管搭的 H 桥,接上电机,上电——转了!正转、反转、调速,一切正常。然后某个瞬间,没有任何代码错误、没有任何波形异常,一颗 MOS 管”啪”地一声(有时是真的能听见)烧了。
拆下来看,坏的总是同一对:同一侧半桥的上下两颗。示波器上明明严丝合缝的两路信号,怎么就把管子干掉了?因为这次错的不在信号,在管子本身:MOS 管的关断比开通慢,逻辑上无缝的互补,到了硅片上就叠出一段”两个都导通”的窗口。这篇文章讲的就是怎么把这个窗口焊死——以及为什么 MCU 干脆把这件事做成了硬件。
还是那块 STM32F407。分三层:先让一对互补 PWM 从 TIM1 的两只脚上跑出来,再看清 H 桥上电流到底在怎么走,最后想明白——为什么”驱动电机”这件事,骨子里是给一个不肯瞬间停下的电流当交通警察。
L1:先让它转起来——一对天生互补的 PWM
你在第一层。目标:用最小代码让 TIM1 输出”互补 + 死区”的一对 PWM,对高级定时器和普通定时器的区别有个地图感。
上一篇的呼吸灯用的是 TIM4,这次的主角换成 TIM1——同门里辈分最高的”高级定时器”。输出仍是两根引脚:PA8 是 TIM1_CH1(接半桥上管),PA7 是 TIM1_CH1N(接下管,N = Negative,CH1 的反相双胞胎):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
#define RCC_AHB1ENR (*(volatile unsigned int*)0x40023830)
#define RCC_APB2ENR (*(volatile unsigned int*)0x40023844)
#define GPIOA_MODER (*(volatile unsigned int*)0x40020000)
#define GPIOA_AFRL (*(volatile unsigned int*)0x40020020)
#define GPIOA_AFRH (*(volatile unsigned int*)0x40020024)
#define TIM1_CR1 (*(volatile unsigned int*)0x40010000)
#define TIM1_CCMR1 (*(volatile unsigned int*)0x40010018)
#define TIM1_CCER (*(volatile unsigned int*)0x40010020)
#define TIM1_EGR (*(volatile unsigned int*)0x40010014)
#define TIM1_PSC (*(volatile unsigned int*)0x40010028)
#define TIM1_ARR (*(volatile unsigned int*)0x4001002C)
#define TIM1_CCR1 (*(volatile unsigned int*)0x40010034)
#define TIM1_BDTR (*(volatile unsigned int*)0x40010044)
int main(void)
{
RCC_AHB1ENR |= 1u << 0; /* 打开 GPIOA 的时钟 */
RCC_APB2ENR |= 1u << 0; /* 打开 TIM1 的时钟——注意这条总线是 APB2 */
GPIOA_MODER |= (2u << 14) | (2u << 16); /* PA7、PA8 -> 复用(10) */
GPIOA_AFRL |= 1u << 28; /* PA7 -> AF1 = TIM1_CH1N(下管) */
GPIOA_AFRH |= 1u << 0; /* PA8 -> AF1 = TIM1_CH1(上管) */
TIM1_PSC = 0;
TIM1_ARR = 8399; /* 168MHz / 8400 = 20kHz:电机的入场频率 */
TIM1_CCR1 = 2100; /* 占空比 25% */
TIM1_CCMR1 = (6u << 4) | (1u << 3);/* OC1M=110:PWM 模式 1 | OC1PE:预装,老朋友 */
TIM1_CCER = (1u << 0) | (1u << 2);/* CC1E | CC1NE:主输出和反相输出一起开闸 */
TIM1_BDTR = (1u << 15) | 0x54; /* MOE 总闸 + 死区 500ns(0x54 怎么来的,动手环节算) */
TIM1_EGR = 1u << 0; /* UG:先装一次(PSC=0 本就是复位值,这行在此处是仪式) */
TIM1_CR1 = (1u << 7) | (1u << 0); /* ARPE + CEN:预装 + 开数 */
while (1) {
TIM1_CCR1 = 2100; /* 调速 = 改 CCR,和呼吸灯一个姿势 */
}
}
烧进去(板子上没有半桥也没关系,示波器看 PA8/PA7 两根线):两路 20kHz 的 PWM 严格反相,且每个切换沿之间都隔着一段两个同时为低的空隙——那段空隙就是死区,本文的题眼。主循环照旧只剩”改 CCR”一件事,波形是硬件自己长出来的。
先钉几个眼生的地方:
RCC_APB2ENR:TIM1 挂在 APB2 总线上,不是上一篇 TIM4 的 APB1。这条总线的定时器时钟是 168MHz(APB2 84MHz × 2 翻倍,那个”手册缝里的知识”在另一条总线上再演一遍)。所以这次 20kHz 的账是 168MHz ÷ 8400:PSC=0、ARR=8399。CC1NE:CCER 里两个开关一起拨(bit0 管 CH1、bit2 管 CH1N),引脚上才有互补输出。互补不是软件在算反相——是同一个比较器直接驱动两根输出线,一正一反,天生同步,零拍延迟。TIM1_BDTR:这个寄存器是高级定时器最醒目的身份证。bit15 的 MOE 是输出总闸——TIM1 的通道配置得再对,MOE 不置 1,引脚上鸦雀无声。为什么要有这样一道总闸?L2 讲”刹车”时见。低 8 位 DTG 就是死区长度,0x54 编码的是 500ns。- 至于另一座半桥(电机的另一端),这篇先静态接线:下管常开、上管常关,电机单向转。方向、刹车、能耗制动的完整开关矩阵值得单独一节,本篇先把”一对管子为什么必须互补 + 死区”这件事吃透。
到这里你已经能让半座桥正确地开关了。但欠着一串解释:H 桥的另外三颗管子都在干嘛?为什么非要”互补”?死区那 500ns 里电流去了哪?——往下。
L2:懂原理——电流有记忆,开关不理想
到这一层,问题变了:不再问”怎么配”,而是问”H 桥上到底发生了什么”。
先看地形:H 桥就是四个开关摆出一个字母
《GPIO》 里点灯,一根引脚推挽输出就够了。但电机要正转、反转、调速、刹车,一根线不够——得把电机的两端都接进电路,每一端都能向上够到电源、向下够到地。这就是 H 桥,名字就是它的形状:
1
2
3
4
5
6
7
8
9
10
11
12
VM ─────┬──────────┬───── ← 电机电源(比如 12V),不是 MCU 的 3.3V
│ │
┌─┴─┐ ┌─┴─┐
│Q1 │ │Q3 │ ← 左、右两座"半桥"的上管
└─┬─┘ └─┬─┘
│A │B
├──[电机]──┤ ← 电机横在两个中点 A、B 之间
┌─┴─┐ ┌─┴─┐
│Q2 │ │Q4 │ ← 下管
└─┬─┘ └─┬─┘
│ │
GND ────┴──────────┴─────
看这张图时注意:Q1/Q2 管着 A 点的电位,Q3/Q4 管着 B 点。导通 Q1+Q4,电流从 A 流向 B,正转;导通 Q3+Q2,电流反向,反转。四个开关,就是电机的方向盘加油门。
但最容易看漏的是 Q2 的位置——它不是 Q1 的备胎。这就要说到本篇最重要的一个物理事实。
电流有记忆:互补的”另一半”是续流的高速公路
上一篇说 LED 时讲过:PWM 的”平均”是负载自己积出来的。电机比 LED 极端得多——它的绕组就是电感,而且转起来之后还是台发电机(反电动势)。电感的电流不能突变,就像高速行驶的卡车不能一脚刹停。可以把电流想成那辆卡车:Q1 导通时它在加速,Q1 关断的瞬间,它不会跟着熄火,而是按原来的方向继续冲。
(这个类比哪里失效:卡车刹不住会追尾,电流不会——电感电流永远”有路可走”,你要决定的只是这条路贵不贵。下面马上看到。)
Q1 断了,路只剩一条:穿过电机,经 Q4 回到地,再从 Q2(或它的体二极管)绕回 A 点。把两条路径排开看:
1
2
3
4
Q1 导通(驱动):VM ──▶ Q1 ──▶ A ──▶ 电机 ──▶ B ──▶ Q4 ──▶ GND
Q1 关断(续流):A ──▶ 电机 ──▶ B ──▶ Q4 ──▶ GND ──▶ Q2 ──▶ 回到 A
(绕成一个环,电流不停,只是不再从电源取能量)
于是”互补输出”存在的理由水落石出:
下管的互补 PWM 不是备用开关,是给那个不肯停下的电流预备的环路。
这也解释了为什么互补必须由硬件保证。你当然可以用两路普通 PWM 加软件保证反相——但《定时器与 PWM》 已经证明,软件反相的沿会被中断和调度拉走几微秒。那几微秒里,续流电流只能硬走体二极管(多付约 0.7V 的压降,发热);而更糟的情况在下面——它可能撞上直通。
死区:管子关不干净这件事,硬件替你认了
现在逼近文首烧管子的现场。逻辑上,”Q1 关断”和”Q2 开通”是同一拍,无缝互补,零间隙。但硅片上有两个物理事实不答应:
事实一:MOS 管的关断比开通慢。 开通是驱动器给栅极充电、推过米勒平台;关断是栅极放电、漏源电压缓慢爬升——典型分立管加驱动器的组合里,关断比开通慢几十到几百纳秒。
事实二:体二极管有反向恢复。 刚才说过,Q1 关断后电流正从 Q2 的体二极管里过。二极管从导通到关断,得先把内部存储的载流子抽干(反向恢复),这段时间它对反向电流几乎不设防。
两个事实一叠加:上管的关断拖尾,撞上下管的开通前沿(前沿可能还踩在二极管的反向恢复上)——出现一小段上下管同时导通。这段的学名叫直通(shoot-through),后果是电流从 VM 出发、穿过 Q1 和 Q2、直奔地,中间只有两颗管子的导通电阻。12V ÷ (20mΩ + 20mΩ) = 300A。这笔账动手环节按给你看,先看图:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
死区介入之前(逻辑无缝互补):
上管逻辑 ────┐ ┌────────┐ ┌───
└────────┘ └────────┘
下管逻辑 ────────────┐ ┌────────┐ ┌──
└────────┘ └────────┘
↑ 沿完全对齐——但沿对齐 ≠ 状态对齐:
上管"正在关",下管"已经开",直通窗口
死区介入之后(硬件把两个沿强行拉开):
上管逻辑 ────┐ ┌────────┐ ┌───
└──────┘ └──────┘
下管逻辑 ─────────────┐ ┌────────┐ ┌──
└──────┘ └──────┘
↑←500ns→ 两个都低:上管的拖尾落在
"两个都关断"里,直通被物理焊死
看这张图时注意第二段里那个空隙:死区不是”少开了 500ns 的电机”,它是给关断拖尾买的保险。这 500ns 里电流并没有停——它从 Q2 的体二极管借道绕环,多付约 0.7V 的”过路费”;死区一过,Q2 的沟道开通,接管成 20mΩ 的低阻通道。这种”体二极管先扛一下、晶体管随即接管”的打法,电源领域有个正经名字:同步整流——它在电机驱动里是免费附赠的,因为互补信号本来就在。
顺带把频率的账补齐:20kHz 是入场券(高出人耳可听频段,低了电机绕组会啸叫——上一篇速查表里的旧账),而 168MHz 出 20kHz,一轮 8400 格占空比档位;死区 500ns 占周期 50µs 的 1%——频率往上冲、死区往下压,挤的都是同一根钟的分辨率。三笔账搅在一起,就是”动手环节”要按的计算器。
TIM1 为什么”高级”:三件防身武器
现在回头看 L1 那几个新面孔,TIM1/TIM8 与 TIM4 的区别也就清楚了——所谓高级定时器,就是通用定时器外加一层面向功率电子的护甲:
- 互补输出:一个比较器驱动 CH1/CH1N 两根线,天生同步,软件反相的中断延迟问题从根上不存在。
- 死区插入:DTG 编码在 BDTR 里写死,两个沿之间的空隙由硬件保证——软件想让它们重叠都做不到。
- 刹车:MOE 总闸不归软件独管。BKIN 引脚一拉高(典型接法:接过流比较器),硬件自动清掉 MOE,所有输出同一拍全部关断。注意这条反应链里没有 CPU、没有中断、没有任务调度——纯组合逻辑,纳秒级。过流到关断,软件方案写得再好也是微秒级起步,而直通从发生到烧管,可能只有几百纳秒。这就是 MOE 存在的理由:安全这种事,不能走软件。
这是本系列反复出现的母题的又一次出场:《Cortex-M4 与 MCU 开发》 里外设替 CPU 干活,《定时器与 PWM》 里定时器替 CPU 管时间,如今高级定时器把”看着自己的开关别短路”也外包了出去——而且用的是最不让软件插手的方式。
到这里原理齐了。但有个更根本的问题压着:为什么驱动电机非得用”满幅开关 + 让负载自己滤平均”这么绕的方式?直接给电机一个 25% 幅度的模拟电压不行吗?
L3:想得透——为什么非得开关,不能温文尔雅
到这一层,问题变成:这些设计是理所当然的吗?换掉行不行?
本质:H 桥是电流的路由器,死区是给物理不完美交的税
去掉所有术语,本篇讲的是这样一件事:
电机里的电流有记忆(电感),H 桥的职责是永远给它安排好去路:正路(驱动)、环路(续流),唯独死路(直通)不许有。死区不是功能,是硬件向”开关关不干净”这个物理事实交的税。
给同事的一句话:H 桥不是”给电机发电压的开关阵列”,是”给一个刹不住车的电流当交通警察”;死区就是路口那盏特意错峰的红灯——不是效率设计,是防撞设计。
三个”被否决的方案”
本质立住了,还得追问一层:为什么是这个方案,而不是显然的替代方案?
被否决的方案一:线性驱动。 用工作在线性区的功率管,直接给电机输出”25% 幅度的平滑 3V”,多体面。代价是热:线性管两端的压降乘电流全是功率损耗。12V 母线、电机吃到 3V/3A 时,线性管上烧的是 (12−3)V × 3A = 27W——一颗不带散热片能烫到冒烟的管子。PWM 方案里管子只有两种状态:全开(压降趋零,损耗 ≈ 电流² × 20mΩ)和全关(没有电流),怎么算都比线性区体面。上一篇说”数字系统最擅长满血 0 和 1,把中间态外包给物理惯性”——这里是同一句话的功率版:晶体管当开关最省,当电阻最费。
被否决的方案二(其实没被否决,被收编):锁相反驱(locked-antiphase)。 H 桥两侧永远反相开关:左半桥给 PWM,右半桥给反相 PWM,占空比 50% 时电机两端平均 0V,25% 时平均 −6V,75% 时 +6V。它的妙处是静止时电机”绷着劲”——两侧以 20kHz 交替推拉,像被两只手轮番拍打,抗扰动刚度极好,平衡机器人(两轮直立那种)的正解就常用它。代价是零指令时也有电流纹波、开关损耗翻倍。选不选它,取决于你要”省电安静”还是”静止刚度”——所以它没死,成了工具箱里的另一把扳手。
被否决的方案三:继电器换向 + 单路 PWM。 这是历史原路:早期设备(以及今天的汽车里大量电路)的方向控制就是继电器触点。机械触点的毛病是命门性的:寿命以十万次计(电子开关以年计)、换向要毫秒级(电子开关纳秒级)、带载断开会拉电弧烧触点。从继电器到 MOS 管,就是”机械开关”被电子开关收编的标准历程——转速可以 PWM、转向只能”咔哒”的时代,就这样过去了。
一段顺手的历史:你烧的这颗管子,功放工程师早烧过
把喇叭的音圈和电机的绕组放在一起看:都是电感,都是”给 PWM 当低通滤波器、自己看见平均值”的负载。所以 Class-D 音频功放的输出级,和电机 H 桥是同一个东西——同样的互补开关对、同样的直通风险、同样的死区,连名字都一字不差地叫 dead time。区别只在细节的追求方向:电机驱动里死区是保险,功放里死区还是失真的来源(死区畸变,dead-time distortion,音频领域的一摞论文都在治它)。你不必为烧过管子难为情——这张门票,几代工程师都买过。
(另一段小历史:淘宝十几块还在卖的 L298N 模块,里面是双极型达林顿管,两对管子的饱和压降加起来轻松掉 2V 上下——带 2A 负载就是 4W+ 的热。它烫不是你焊得不好,是上世纪的工艺本来就这么费。今天选它通常只有一个理由:便宜,且手边就有。)
一个反直觉收尾:死区里最忙的是电机两端
你可能以为死区是”输出的空窗”——两个管子都关了,电机上什么都没发生。恰恰相反:那 500ns 里,体二极管在换班导通、续流电流在改道、电机两端的电压在跳变——开关电源和电机驱动里最让 EMC 工程师头疼的电压尖峰,多半就诞生在这里。示波器上看到的那个毛刺不是干扰,是物理在收税的单据。
动手环节:一支笔,把死区和直通算清楚
这次的实验不需要板子,一支笔加一个计算器。
实验一:给死区编码。 TIM1 时钟 168MHz,CKD=00 时 Tdts ≈ 5.95ns。BDTR 的 DTG 是个分级压缩表:
- DTG[7:5] = 0xx:DT = DTG[7:0] × Tdts,最多 126 × 5.95 ≈ 750ns
- DTG[7:6] = 10:DT = (64 + DTG[5:0]) × 2 × Tdts,到约 1.5µs
- 再往上还有 ×8、×16 两级,8 位最远编码到约 6µs
先预测:想要 1µs 死区,DTG 写几?(提示:先算 1µs 是多少个 Tdts,再看第一档装不装得下。)
| 答案:1µs ÷ 5.95ns ≈ 168 个 Tdts,第一档上限 126,装不下;进第二档:(64 + x) × 2 = 168 → x = 20 = 0b010100,加上档位前缀 10 → DTG = 0b10_010100 = 0x94,BDTR = (1«15) | 0x94。回头验证 L1 的 0x54:0x54 = 0b0101_0100,DTG[7:5] = 010 属于第一档,DT = 84 × 5.95 ≈ 500ns,对上了。顺手咂摸一下这个设计:一张 8 位的表,靠 ×1/×2/×8/×16 的梯子覆盖 0–6µs——用位数换量程,和定时器 PSC/ARR 两根齿轮是同一个思想。 |
实验二:直通的账。 VM = 12V,上下管的 Rds(on) 都是 20mΩ,直通窗口 200ns,20kHz 下每周期两次切换。先预测:直通的峰值电流多大?平均下来灌进管子的功率多大?
答案:理想峰值 12V ÷ 40mΩ = 300A(实际到不了这么高——回路里总有纳亨级的寄生电感,电流爬升需要时间——但几十安轻松);瞬时功率 12V × 300A = 3600W;单次能量 3600W × 200ns = 0.72mJ;每秒 40000 次切换,0.72mJ × 40000 ≈ 28.8W。一颗平时几瓦就烫手的管子,在”逻辑波形完全正确”的前提下被灌了近 30W 的脉冲热——这就是文首那句”信号没错,管子照烧”的全部算术。
实验三:死区吃掉的占空比。 500ns 死区、20kHz(周期 50µs),死区大约吃掉多少有效占空比?母线 12V 时对应多少平均电压?
答案:每个切换沿 500ns,一个周期内两次有效,合计约 1µs / 50µs = 2%;12V × 2% ≈ 0.24V 的平均电压损失。日常调速无感,但做精密速度环(下一站的事)时,这 0.24V 是个要标定补偿的系统误差——死区不是免费的,账迟早要还。
三个自测问题,能答上说明这三层你真爬过了:
- 示波器上两路 PWM 完美互补、沿对齐,为什么还会烧管?(答案在 L2:沿对齐 ≠ 状态对齐——关断拖尾撞开通前沿,直通窗口。)
- 死区那 500ns 里,电机电流停了吗?走哪条路?(答案在 L2:不停,从下管体二极管借道绕续流环,多付约 0.7V。)
- TIM4 也能出 20kHz PWM,为什么驱动分立 H 桥还是推荐 TIM1/TIM8?(答案在 L2:互补、死区、MOE/BKIN 刹车三件武器;用 TIM4 也行,前提是外接的驱动芯片把死区和直通保护在芯片里做掉。)
收尾地图
把整篇收进一张速查表:
| 你想干什么 | 配方 | 一句话理由 |
|---|---|---|
| 快速原型/小电机 | 集成驱动芯片(如 DRV8833)+ 任意定时器单路 PWM | 死区和直通保护芯片里做掉了,普通定时器就够 |
| 分立 MOS 管 H 桥 | TIM1/TIM8 互补输出 + 死区 + MOE | 本文主线,三件武器全用上 |
| 可能堵转/要安全余量 | BKIN 刹车输入接过流比较器 | 关断走纯硬件,不走软件 |
| 平衡机器人/要静止刚度 | 锁相反驱(两侧永远反相) | 50% 占空比零电压但绷紧,抗扰 |
| 方向偶尔切换的低要求场合 | 继电器 + 单路 PWM | 机械开关的遗老,胜在便宜皮实 |
| 往 20kHz 以上冲 | 先按实验二、三的账验算 | 频率、死区、分辨率共用同一根钟 |
下一站,就是从第一段一直铺垫到这里的闭环:开环转起来只是入场券,”转到指定速度”得先问电机”你转多快了”。增量编码器的 A/B 相正交信号,定时器的编码器模式数边沿比谁都稳(对,还是 TIM——TIM2 到 TIM5 都有这个模式);再配上 PID,”目标 3000 转”和”实际 2996 转”之间的差距才有人管。机器人的轮子从今天开始转,下一课让它知道自己在转。