← 返回文章库

STM32 电机控制入门:PWM、编码器接口、FOC 概览

最后更新 2026-06-22
⏱ 约 20 分钟 🟡 涉接线/强电
你将学到
  • 搞清为什么电机控制场景几乎都选 STM32,呼应 S 卷选型那一篇
  • 看懂高级定时器 TIM1/TIM8 的互补 PWM + 死区时间是怎么驱动半桥/三相逆变的
  • 知道定时器的编码器接口模式能硬件直接读正交编码器,比软件中断省 CPU
  • 建立对 FOC(磁场定向控制)的整体认知,并明确指向 ST MCSDK,不自己从零撸

到这一卷你已经会用 CubeMX 配引脚、用 HAL 点灯读键、玩转 UART/定时器/ADC 了。现在来个真活儿:你要驱动一个电机。 可能是个直流有刷的小风扇,也可能是个航模上的无刷电机(BLDC)。你照着 ESP32 的思路想——不就 PWM 调个速嘛——结果一查无刷电机的资料,满屏 "FOC""三相""Clarke/Park 变换""死区",瞬间懵了:这跟点灯完全不是一个量级。

而且你会注意到一件事:网上但凡正经讲电机控制的,十有八九用的是 STM32,几乎没人拿 ESP32 干这个。 不是 ESP32 不行,是 STM32 在电机这件事上有几样 ESP32 给不了的硬件武器。这一篇就带你认全这几样武器——高级定时器的互补 PWM + 死区、定时器的编码器接口、以及 FOC 的整体思路——让你看懂电机控制在干什么、知道该往哪走,而不是被一堆名词劝退。

📌 说明

先把定位和丑话说清楚:这是一篇「概览级」的文章,不是完整的电机控制实现教程。 电机控制——尤其是无刷电机的 FOC——是嵌入式里公认的深水区,一套能跑、能调、不烧管子的 FOC 代码动辄上千行,还牵涉电流采样时序、PWM 与 ADC 同步、PID 整定、过流保护一大堆。本篇的目标是让你**「知道有这些东西、知道它们各自干什么、知道往哪走」**,给你一张地图和指路牌,不展开完整代码。文中的 HAL 片段只是主干流程参考,函数名/寄存器/通道强依赖你的芯片型号与 HAL 版本,以 CubeMX 实际生成的工程 + ST 官方文档为准,别当成确定无误。真要落地 FOC,正确的路是用 ST 官方的 MCSDK,后面会专门讲。

[!warning] 电机和强电不是闹着玩的,动手前务必看这段。 电机控制涉及几个实打实会伤人/烧设备的风险:① 堵转大电流——电机被卡住时电流瞬间飙到额定值的好几倍,分分钟烧 MOS 管、烧电机、烧电源;② 三相逆变的母线电压——做无刷/工业电机驱动时母线常是几十甚至几百伏直流,误触会电伤人,电解电容断电后还能存电几分钟;③ 直通短路——半桥的上下管同时导通(这正是下面"死区"要防的事)会瞬间烧管子。因此:第一次玩电机,请从低压(≤24V)有刷小电机、带限流的实验电源、串个保险丝开始;无刷/高压逆变请用成熟的开发板(如 ST 的电机控制评估板)配隔离,别自己空手搭裸功率级。 本篇所有参数/接线均为概览级参考,实际功率电路设计必须以芯片 datasheet、驱动 IC 手册和你的电源规格为准,涉强电时请有经验的人在场

读这篇前,你最好已经看过 S 卷的选型那一篇——知道"什么场景该用 STM32 而不是 ESP32"。本篇就是那个判断的一个最典型落地:电机控制就是该上 STM32 的场景。


为什么电机控制几乎都选 STM32

回到那个问题:调速不就是 PWM 吗,ESP32 也有 LEDC PWM,为什么电机控制几乎一边倒地用 STM32?

因为"驱动电机"远不止"输出一路 PWM"那么简单,它对定时器外设有几样很硬的要求,而这正是 STM32 的看家本领:

  • 要互补 PWM + 死区,这是驱动半桥的刚需。 驱动电机的功率级是半桥(上下两个 MOS 管),同一时刻绝不能上下管同时导通(否则母线直接短路烧管子)。所以你需要一路 PWM 和它"反相且带一小段都关断的间隙(死区)"的伴生波。STM32 的高级定时器(TIM1/TIM8)硬件直接生成互补输出 + 可配死区,不用 CPU 操心;ESP32 的普通 LEDC 给不了这个保证。
  • 要硬件编码器接口,闭环调速/定位的刚需。 想知道电机转了多快、转到哪了,得读编码器。STM32 定时器有编码器接口模式,硬件直接对正交信号计数,CPU 几乎零负担;ESP32 这边得靠 PCNT 或软件中断,吃 CPU 还容易丢脉冲。
  • G4 系列还带 CORDIC / FMAC 数学加速器,专为 FOC 而生。 FOC 里要大量算 sin/cos(坐标变换)和滤波,STM32G4 把这些做成了硬件加速单元,FOC 控制环能跑得又快又省 CPU。ST 干脆把 G4 定位成"电机控制专用线"。
  • 生态成熟:ST 有一整套官方电机控制方案(MCSDK、电机控制评估板、Motor Control Workbench 图形化配置),这是别家芯片很难比的——你不是孤军奋战。

一句话:电机控制对"定时器能力 + 数学加速 + 配套生态"的要求,恰好是 STM32(尤其 G4)的强项。 这就是为什么一碰电机,老炮就让你换 STM32——这也正是 S 卷选型那篇讲的"按场景挑芯片"的活例子。


武器一:高级定时器的互补 PWM + 死区

先看最基础、也最常用的一件武器:用高级定时器输出一对带死区的互补 PWM。 这是所有电机功率驱动的地基。

普通 PWM 和"互补 PWM"差在哪

ESP32 调速那种 PWM,是一路方波,占空比决定平均电压,调速就靠改占空比。这个 STM32 普通定时器也能干。

但驱动半桥需要的是一对信号:主输出 CHx 和它的互补输出 CHxN,两者相位相反——主路高时补路低、主路低时补路高,正好分别控制半桥的上管和下管。关键在于:在两路切换的瞬间,必须有一小段"上下管都关断"的间隙,这就是死区(dead-time)。 没有死区,上管还没完全关、下管就开了,两管短暂同时导通——母线直通短路,管子瞬间冒烟。

STM32 的高级定时器 TIM1 / TIM8 天生支持这个:你配一个通道的主输出,它自动给你生成带可调死区的互补输出,死区时间用一个寄存器配(几十到几百纳秒级),全程硬件完成,CPU 不用管。普通定时器(TIM2~TIM5 那些)做不到互补+死区,这是"高级"两个字的核心价值之一。

在 CubeMX 里怎么配(概览)

流程上(以 TIM1 为例,具体引脚/通道以你的芯片和 CubeMX 实际生成为准):

  1. 在 CubeMX 里选 TIM1,把某个通道(如 Channel 1)的模式设为 PWM Generation CH1 CH1N——注意是带 CH1N 的那个选项,这才会启用互补输出。
  2. 在该定时器的参数里设好 PSC(预分频)/ARR(自动重装值) 决定 PWM 频率(电机 PWM 常用 10kHz~20kHz,避开人耳啸叫又不过高),并在 Break and Dead Time 一栏里配 Dead Time(死区时间)
  3. GENERATE,它会生成 htim1 句柄和初始化代码。

生成后,启动互补 PWM 输出的主干调用大概长这样——这是片段,不是完整工程,仅示意"怎么启 + 怎么改占空比"

/* USER CODE BEGIN 2 */
// 启动 TIM1 通道1 的主输出 + 互补输出(CubeMX 已在 MX_TIM1_Init 里配好死区)
HAL_TIM_PWM_Start(&htim1, TIM_CHANNEL_1);     // 主输出 CH1
HAL_TIMEx_PWMN_Start(&htim1, TIM_CHANNEL_1);  // 互补输出 CH1N —— 注意是 PWMN,多了个 N
/* USER CODE END 2 */

/* 改占空比 = 调速:把比较值写进 CCR1(占 ARR 的比例就是占空比) */
// 假设 ARR = 999,下面这句约 50% 占空比
__HAL_TIM_SET_COMPARE(&htim1, TIM_CHANNEL_1, 500);

注意那个 HAL_TIMEx_PWMN_Start——比普通 HAL_TIM_PWM_Start 多了个 N,专门启互补输出,外加 Ex(Extended)前缀,这正是高级定时器特有的 API。死区你在 CubeMX 里配好后,HAL 初始化时就写进 BDTR 寄存器了,运行时不用管。

🚧 避坑

死区配多少不是拍脑袋,强依赖你的 MOS 管/驱动 IC 的关断时间。 死区太短,上下管来不及完全关断就换向,直通短路烧管;死区太长,输出波形失真、效率下降。正确做法是查你功率 MOS 管和栅极驱动 IC 的 datasheet,按它的关断延迟 + 余量算,别照搬网上某个具体数值。这是 R2 涉强电的地方,宁可保守取大一点先不烧管,再慢慢压。


武器二:定时器编码器接口(硬件读正交编码器)

第二件武器解决"怎么知道电机转了多快、转到哪"的问题——编码器接口模式

电机闭环(精确调速、精确定位)的前提是能测出转速和位置。常见做法是给电机装一个正交编码器,它转的时候吐出两路相位差 90° 的方波(A 相、B 相),靠两路谁超前谁判断转向、靠脉冲个数判断转过的角度。

软件读法是:每来一个边沿触发一次中断,在中断里自己判断方向、加减计数。电机转得快时脉冲密集,中断风暴会把 CPU 吃光,还可能丢脉冲。

STM32 定时器的编码器接口模式(Encoder Mode) 把这件事硬件化了:你把编码器的 A、B 两相接到定时器的两个输入通道,配成编码器模式,定时器的计数器(CNT)就自动跟着编码器正转加、反转减,方向判断、计数全由硬件完成,CPU 只需要想读的时候读一下 CNT 寄存器就行——几乎零负担,也不会丢脉冲。

概览级用法(以实际生成为准):在 CubeMX 里把某个定时器(如 TIM3)的 Combined Channels 设成 Encoder Mode,配好两路输入引脚,生成后这么用:

/* USER CODE BEGIN 2 */
HAL_TIM_Encoder_Start(&htim3, TIM_CHANNEL_ALL);  // 启动编码器模式,硬件开始自动计数
/* USER CODE END 2 */

/* 任意时刻直接读当前计数值 = 当前位置 */
int32_t pos = (int32_t)__HAL_TIM_GET_COUNTER(&htim3);

/* 测速:定时(比如每 10ms)读一次 CNT,两次差值 / 时间 = 转速 */
// 上一次的计数存到 last_cnt,本次减去它,再换算成 RPM(要知道编码器线数和倍频)

最值得记的一点:编码器接口模式让"读编码器"从一件吃 CPU 的中断活,变成了一次寄存器读取。 这是 STM32 做电机闭环又一个 ESP32 不好替代的硬件红利。


武器三(深水区):FOC 磁场定向控制概览

前面两件武器,有刷电机基本够用了:互补 PWM 驱动 H 桥、编码器读转速,调个 PID 就能闭环调速。但你要驱动无刷电机(BLDC/PMSM)想要平顺、高效、低噪——就绕不开 FOC。 这是这一篇的深水区,我们只讲清"它在干什么、为什么要它、该怎么落地",不展开实现

FOC 想解决什么

无刷电机有三相绕组,没有电刷,靠控制器按转子位置依次给三相通电来转。最朴素的控制是"六步换相(方波驱动)",简单但有顿挫、噪声大、效率不高

FOC(Field-Oriented Control,磁场定向控制) 的核心思想是:把交流电机当成直流电机来控制。 它实时知道转子转到哪了,然后把三相的电流,数学变换成两个互相垂直的量——一个管"产生扭矩"(交轴 q),一个管"产生磁场"(直轴 d)——像调直流电机一样分别精确控制它们。结果就是电机运转极其平顺、扭矩响应快、效率高、噪声小。你手里那些安静又有劲的云台、电动工具、好一点的风扇,背后基本都是 FOC。

它的处理链路(认个脸熟,不要求会推)

FOC 一个控制周期里,大致是这么一条流水线,记住名字和作用即可:

  1. 采三相电流 + 读转子角度(编码器/霍尔/无感估算)。
  2. Clarke 变换:把三相电流(Ia/Ib/Ic)变成两相静止坐标(α/β)——三个量压成两个,简化。
  3. Park 变换:再用转子角度把 α/β 旋转到跟着转子转的坐标系(d/q)——这一步是 FOC 的灵魂,变换之后扭矩分量 q 和励磁分量 d 就解耦了,能分开调。
  4. 电流环(PI 控制器):把 d、q 电流调到目标值(通常 d 给 0、q 给扭矩指令)。
  5. 反 Park / 反 Clarke + SVPWM:把控制器输出再变换回三相,生成驱动三相逆变器的 PWM 占空比。
  6. 回到第 1 步,整个环以 PWM 频率(十几 kHz)不停地跑。

这里你应该回过味来了:第 5 步要发的就是三相互补 PWM(武器一),第 1 步的角度可以来自编码器(武器二),第 2/3 步的 sin/cos 正是 STM32G4 的 CORDIC 硬件加速的活。 三件武器在 FOC 里全用上了——这就是为什么电机控制非 STM32 不可。

怎么落地:用 ST 的 MCSDK,别从零撸

看到这条流水线你大概也感觉到了:FOC 是深水区。 自己从零实现一套——电流采样要和 PWM 精确同步、SVPWM 要写对、两个 PI 环要整定、还要做过流/堵转保护——是个能耗你几周甚至几个月、还容易烧管子的硬核工程。老炮的诚实建议:第一次做 FOC,不要自己从零撸。

正确的路是用 ST 官方的 MCSDK(Motor Control Software Development Kit,电机控制软件开发包),对应的 CubeMX 扩展包叫 X-CUBE-MCSDK

  • 它内置一个图形化的 Motor Control Workbench,你填电机参数(极对数、电阻电感、额定电流……)、选你的功率板和反馈方式,它自动生成一整套调好的 FOC 工程——Clarke/Park/SVPWM/电流环/保护全帮你写好。
  • 配套有官方的电机控制评估板和文档,能让你先在成熟硬件上把 FOC 跑通、看波形、调参数,再迁到自己的板子。
  • 它对 G4 的 CORDIC/FMAC 加速也做了适配。

也有开源选择(比如 SimpleFOC、VESC 这类社区方案),适合学习和爱好者项目。但**「理解原理用本篇这张图,落地实现用 MCSDK 或成熟开源框架」**,是性价比最高的路线。指望照一篇博客手敲出能跑的 FOC,基本都是踩坑收场。

📌 说明

本节所有 FOC 描述都是概念层面的概览,目的是让你看懂这条流水线、认得这些名词、知道用 MCSDK 落地。具体的电流采样时序、SVPWM 实现、PI 参数整定、保护逻辑,请以 ST MCSDK 的官方文档和示例工程为准(见上方 sources)。本篇不提供任何可直接用于驱动真实功率级的 FOC 代码——那需要在成熟硬件上反复验证,不是看文章能替代的。


避坑 / 常见误区

  • 以为电机调速 = 一路 PWM。 有刷直流小电机大致是这样(H 桥 + 一路 PWM 调占空比),但无刷电机完全是另一个世界(三相 + FOC)。先分清你驱的是哪种电机,别拿有刷的思路去套无刷。
  • 半桥忘了死区,直接烧管。 这是新手做功率级最经典的事故。只要是半桥/全桥/三相逆变,死区是底线,且要按你 MOS/驱动 IC 的关断时间算,别省、别拍脑袋。
  • 想自己从零撸 FOC。 上面说过了——深水区,第一次别这么干,用 MCSDK 把它跑通理解了再说。把时间花在懂原理和调参上,而不是重造 SVPWM 的轮子。
  • 裸手搭高压三相功率级。 母线几十上百伏不是闹着玩的,电容存电会电人。学习阶段请用成熟开发板 + 隔离 + 低压先行,别在面包板上插裸功率管玩高压。
  • 编码器还在用软件中断硬扛。 既然上了 STM32,编码器就用定时器的编码器接口模式,省 CPU 还不丢脉冲,没理由再用一堆外部中断去数脉冲。

小结 · 你现在掌握了什么

  • 你明白了为什么电机控制几乎都选 STM32:高级定时器的互补 PWM + 死区(驱半桥刚需)、定时器编码器接口(闭环刚需)、G4 的 CORDIC/FMAC(给 FOC 加速)、加上成熟的官方生态——这正是 S 卷选型那篇"按场景挑芯片"的典型落地。
  • 你认识了武器一:高级定时器 TIM1/TIM8 的互补 PWM + 死区——为什么半桥必须有死区(防直通短路)、CubeMX 里怎么配、HAL_TIMEx_PWMN_Start 那个多出来的 N 是什么。
  • 你认识了武器二:定时器编码器接口模式——硬件自动对正交编码器计数,把"读编码器"从吃 CPU 的中断活变成一次寄存器读取。
  • 你建立了对 FOC(磁场定向控制) 的整体认知:Clarke→Park→电流环→反变换+SVPWM 这条流水线在干什么、为什么它让无刷电机平顺高效,以及三件武器如何在 FOC 里全部用上。
  • 最重要的两条诚实话:① 本篇是概览级——给地图和指路牌,不展开完整实现,代码都只是主干片段,函数名/参数以 CubeMX 生成 + ST 文档为准;② FOC 是深水区,第一次别自己从零撸,用 ST 的 MCSDK / X-CUBE-MCSDK 落地;以及涉电机/强电务必守住安全框那几条底线。

电机这块属于"知道往哪走"的概览了。S 卷最后一块拼图是工业现场的通信——电机、PLC、传感器之间怎么用工业总线连起来。接着看 STM32 工业总线入门(CAN/RS485/Modbus),把这一卷的工业能力补齐。想回看整条迁移路线,去 STM32 迁移卷总览

📄 来源 / 自校链接

本文为公开资料整理,非亲测。关键参数与代码请结合实物与下列官方来源验证。

内容有错、看不懂、或想看下一期?告诉我们 →

本文为公开资料的学习整理,非亲测。涉接线/花钱/合规的步骤请结合实物与官方最新资料验证,风险自负。见免责声明