简介这份资源是野火电子于2021年5月发布的《电机应用开发实战指南—基于STM32》技术文档面向已具备单片机基础、希望系统入门电机控制的嵌入式工程师与高校学生解决从电机原理到STM32驱动落地之间缺少完整实践指引的问题。内容先梳理直流电机、步进电机、伺服电机与舵机的分类及工作原理再讲解有刷、无刷、步进和伺服四类驱动器的选型思路随后进入STM32定时器与PWM输出、编码器反馈等关键环节并配有配套硬件说明与推荐书单适合作为项目开发时的案头参考。压缩包仅1个PDF文件大小约35.84MB便于离线查阅与打印目录层级完整可直接跳转对应章节。目前已有863人学习适合按章节循序实践逐步搭建自己的电机控制方案。1. 从一块冒烟的驱动板说起这套 STM32 电机开发指南解决的是哪类问题焊好 MOS 管、接上 12V、下载程序、按下复位——电机没转驱动板先冒了烟。这类场景在电机应用开发入门阶段几乎是必修课PWM 频率设错、死区没配、电流采样零点没校准任何一个环节都能让功率级当场报废。这份《电机应用开发实战指南—基于 STM32》走的是另一条路它不直接丢出一段 FOC 代码而是从电机分类、驱动器分类讲起先把定时器、采样、编码器、PID 这几项基础铺平再用直流有刷、舵机、步进、无刷四条线各跑一遍完整实验。适合手上有 STM32 开发板和一台电机、不想在功率级上反复交学费的嵌入式工程师也适合刚接触电机控制、需要一条能照抄的验证路径的新手。它要解决的不是“电机怎么转”而是“怎么让电机按你要的方式、稳定地、不烧板地转”。2. 电机与驱动器怎么配对四类电机、四类驱动器的选型对照拿到一个控制需求第一步不是打开 Keil 建工程而是先回答两个问题负载要的是速度、位置还是力矩预算和精度能容忍到什么程度。这两问决定选哪类电机电机定了再决定驱动器拓扑最后才是 STM32 出什么信号。顺序反了后面所有调试都会变成补丁摞补丁。2.1 直流有刷、步进、无刷、舵机控制精度与成本的四条路线教程把电机拆成四类讲不是为了罗列概念而是因为这四类的控制接口完全不同选错了主控外设就白配了。类型主控给什么信号是否有闭环反馈典型精度成本量级常见落点直流有刷单路 PWM 两路方向靠外加编码器速度环中等低小车底盘、传送带、云台步进脉冲 方向 使能开环可加编码器定位精度高无累积误差中丝杆、3D 打印、光学平台无刷直流三相互补 PWM霍尔或无感反电动势高速段好需换相高风机、电动工具、云台舵机50 Hz PWM 脉宽内部电位器闭环角度约 1°视型号低机械臂关节、航模舵面四条路线的工程边界很清晰。步进电机开环跑得稳但一旦负载超矩就是静默丢步位置直接错掉且不会报警必须靠加减速曲线和合理细分来兜。无刷电机效率高、寿命长代价是你必须知道转子在哪霍尔装错 60° 电角度整机就抖成筛子。舵机最省事一根信号线给脉宽就行但它的“角度”是内部电位器自己认的主控并不知道真实位置做多关节联动时会积累误差。我在做小型机械臂选型时踩过一次坑四个关节全用舵机代码里按 0.5 ms 到 2.5 ms 线性映射角度结果装上负载后末端偏差肉眼可见。后来才明白舵机的死区、回差和供电跌落都会吃掉几度。这类场合要么换成带编码器的伺服方案要么在每个关节加一个外部角度传感器做二级校正。2.2 驱动器的作用与结构从控制信号到功率级的四层结构几乎所有电机驱动器都能拆成四层信号隔离与电平转换、预驱栅极驱动、功率级H 桥或三相半桥、采样与保护。区别只在每层的实现密度。有刷驱动最简单一颗双 H 桥芯片就能带动一个小功率减速电机STM32 出两路 PWM 控制正反转和调速。教程里这一章先讲控制电路原理再讲驱动芯片内部结构重点在两个容易被忽略的点一是 H 桥的上下管不能同时导通二是刹车短接绕组和惰行断开绕组是两种完全不同的停机行为代码里必须区分。无刷驱动多了一层换相逻辑。功率级是三相半桥六个管子互补 PWM 加死区任一瞬间只有两个管子有效导通。教程在无刷章节把控制电路原理、MOS 管搭建驱动板、速度控制原理分开讲这个拆法是对的因为大部分烧板事故发生在“预驱输出和功率管栅极之间”这一段而不是算法。步进驱动通常是一个细分器模块STM32 只负责发脉冲和方向。细分器内部自己完成相电流斩波主控完全看不到电流环。这种分工的好处是把实时性压力甩给了硬件坏处是脉冲频率上限受细分器光耦和输入滤波限制跑高速时脉冲发得再快电机也跟不上。舵机严格说不算驱动器它把驱动、减速箱、反馈电位器全封在一个壳里。主控只要保证 50 Hz 周期稳定、脉宽准确剩下的交给它。一个实用判断如果你的控制周期里出现了“读电流—算 PID—改占空比”这条链路那驱动器就必须支持电流采样回传如果只有“给个速度值”那用现成模块更省时间。2.3 把手册里的参数抄成选型表额定电压、KV 值、转矩与槽极电机手册上的参数很多真正影响驱动代码的其实只有六个。参数含义对代码的影响额定电压连续工作的母线电压决定 PWM 限幅上限与过压保护阈值KV 值每伏特对应的空载转速rpm/V估算最高转速反推 PWM 频率与测速量程额定转矩连续输出转矩决定电流环限流点最大电流短时允许电流硬件过流保护阈值必须低于它极对数极数除以 2电角度 机械角度 × 极对数换相表步进量槽极结构定子槽数 N、转子极数 P影响霍尔安装角度与齿槽转矩极对数是初学者最容易漏掉的一项。编码器读到的是机械角度而换相需要电角度。一个 14 极的无刷电机极对数是 7机械转一圈电角度转七圈。如果按机械角度去查六步换相表电机要么不转要么在某个位置剧烈抖动。具体做法先按 KV 值估算目标转速对应的母线占空比区间再按最大电流选分流电阻和运放增益最后按极对数把编码器计数换算成电角度。这三步做完硬件参数就固定下来了后面调 PID 才有稳定的基础。3. STM32 定时器是关键基础PWM、互补输出与输入捕获教程里定时器一章的篇幅最长这个安排合理。电机控制里所有和时间相关的事最终都落实到定时器上PWM 靠它产生换相靠它触发测速靠它计数ADC 采样靠它同步。这一章吃不透后面的实验就只能照抄出问题也不知道该看哪个寄存器。3.1 时基单元怎么算预分频、自动重装与目标频率STM32 的通用定时器和高级定时器都是同一套时基结构输入时钟先过预分频器 PSC得到计数时钟计数器在 0 到自动重装值 ARR 之间循环溢出时产生更新事件。PWM 频率的公式是f_pwm f_tim / ((PSC 1) × (ARR 1)) // 边沿对齐 f_pwm f_tim / ((PSC 1) × 2 × ARR) // 中心对齐手工试参数很费时间我一般先用一段脚本把可行解算出来再往寄存器里填。def calc_timer(tim_clk_hz, pwm_hz, arr_max65535): tim_clk_hz: 定时器输入时钟例如 168_000_000 pwm_hz: 目标 PWM 频率例如 20_000 返回 (psc, arr, 实际频率)优先取 PSC 最小的一组 for psc in range(0, 65536): arr round(tim_clk_hz / (pwm_hz * (psc 1))) - 1 if 0 arr arr_max: real tim_clk_hz / ((psc 1) * (arr 1)) return psc, arr, real raise ValueError(该时钟下无法同时满足 PSC / ARR 范围) print(calc_timer(168_000_000, 20_000)) # 输出 (0, 8399, 20000.0)脚本的逻辑是固定目标频率从小到大试 PSC反算 ARR第一个落在合法区间内的组合就是解。参数含义tim_clk_hz是你所用定时器实际挂载的时钟高级定时器一般等于主频通用定时器要看你有没有开 APB 预分频pwm_hz是电机驱动常用的 16 kHz 到 20 kHz低于 16 kHz 会听到啸叫高于 20 kHz 开关损耗上升。PSC 取最小的好处是分辨率最高占空比能调得更细。但要注意 ARR 也不能太小否则占空比调节档位不够低速时会出现明显的台阶感。有刷电机调速我一般要求 ARR 不低于 1000对应占空比分辨率千分之一。填寄存器时容易漏的一点预分频器和自动重装值都有影子寄存器只有在更新事件时才真正生效。初始化阶段直接写没问题运行中改 ARR 一定要等一次更新事件否则会出现一个周期的异常占空比。3.2 互补 PWM 与死区时间无刷驱动绕不过去的第一道坎有刷电机只需要一路 PWM 加一个方向脚无刷电机不行。三相半桥的每一相都是上下两个管子串联同一相的上下管绝对不能同时导通否则母线直接短路俗称直通。互补 PWM 就是让上下管信号永远相反而死区时间是在切换瞬间插入的一小段“两边都关”的空白保证先关的那个管子真正关断了另一个才开。死区太短会直通太长会畸变输出波形、降低有效电压。经验值低压小功率场合 0.5 μs 到 1 μs高压大功率场合按驱动器的关断延迟再加余量。高级定时器的死区由 BDTR 寄存器配置不同芯片的编码方式略有差异通常是“DTG 值乘以计数时钟周期”。/* TIM1 高级定时器中心对齐互补 PWM目标 20 kHz死区约 1 us */ htim1.Instance TIM1; htim1.Init.Prescaler 0; htim1.Init.CounterMode TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period 4199; /* 168MHz / (2 × 4200) 20 kHz */ htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter 0; HAL_TIM_PWM_Init(htim1); /* 通道 1 输出 互补输出死区时间填 DTG */ sBreakDeadTimeConfig.OffStateRunMode TIM_OSSR_ENABLE; sBreakDeadTimeConfig.OffStateIDLEMode TIM_OSSI_ENABLE; sBreakDeadTimeConfig.LockLevel TIM_LOCKLEVEL_OFF; sBreakDeadTimeConfig.DeadTime 168; /* 168 × (1/168MHz) ≈ 1 us */ sBreakDeadTimeConfig.BreakState TIM_BREAK_ENABLE; sBreakDeadTimeConfig.BreakPolarity TIM_BREAKPOLARITY_HIGH; HAL_TIMEx_ConfigBreakDeadTime(htim1, sBreakDeadTimeConfig);这段代码的关键点有三个。CounterMode选中心对齐输出波形的谐波分量更低电机电流纹波小代价是同等频率下 ARR 只能取到边沿对齐的一半。DeadTime填的是寄存器原始值而不是微秒数具体换算要看参考手册里 DTG 的分段编码168 这个数只对 168 MHz 主频加 1 分频成立。BreakState打开刹车输入非常值得做它是一条纯硬件通道过流比较器一翻转就直接把六路输出拉成安全状态不依赖软件响应相当于给功率级买了一份保险。调试这部分的习惯做法是先不接电机用示波器双通道同时看 CHx 和 CHxN确认死区真实存在且两边没有重叠再串一个限流电阻接母线空载给一个很小的占空比看六路波形是否对称。跳过这一步直接带电机上电是我见过最多的一次性烧板原因。3.3 输入捕获与编码器接口把转速测准的三个细节速度环能不能调好八成取决于测速质量。教程里给了两条路输入捕获测外部信号的周期或占空比编码器接口直接解码 A/B 相。输入捕获的用法是把通道配成上升沿触发捕获到边沿时把当前计数器的值搬到捕获寄存器两次捕获值相减就是周期。细节在于计数器会溢出差值算出来是负数时要补上 ARR。另一个细节是输入滤波器电机引线长、干扰大IC1F不配就会出现一堆虚假捕获测速值疯狂跳变。编码器接口更省事TIM 直接把 TI1 和 TI2 当作正交信号源硬件自动加计数或减计数。常见做法是把 ARR 设成 65535配一个 1 ms 定时中断读一次 CNT把这个值当作 M 法测速的脉冲数。三个容易出问题的点第一倍频方式。编码器接口默认在 A、B 两相的上升沿和下降沿都计数这就是四倍频。如果编码器标称 500 线实际每个机械圈会得到 2000 个计数。算转速时忘了乘 4转速就差了四倍。第二方向判断。交换 A、B 任意一相的接线计数方向就反了。这在整机上表现为“给定正转电机反转”或者速度环变成正反馈直接飞车。上电前先用串口打印 CNT手动转一圈看是递增还是递减比事后调试省事得多。第三低速段的量化误差。1 ms 采一次如果电机转得很慢两次采样之间计数只变化 0 到 1测速值就是台阶状的。解决方式要么拉长采样窗口要么在低速段改用 T 法测周期要么做一阶低通滤波但注意别把相位滞后拉得太大影响环路稳定性。4. 闭环落地电流电压采样、保护阈值与 PID 整定开环能转只是第一步真正让电机听指挥的是闭环。这一章把教程里分散在第 8 章、第 12 章、第 15 至 17 章的内容串起来采样是输入保护是底线PID 是策略上位机是眼睛。4.1 采样电路与 ADC分流电阻、分压网络和零点校准电流采样两条路线分流电阻加差分放大或者霍尔电流传感器。低端采样便宜但破坏地平面高端采样要处理共模电压差放得选高共模抑制比的。教程里用的是分流电阻方案配合运放把毫伏级压降放大到 ADC 的输入范围。双向电流有个关键设计把运放的输出偏置到 ADC 量程中点。3.3 V 供电时零电流对应 1.65 V 左右正电流往上走负电流往下走。这样 ADC 读数减去零点再乘系数就是实际电流。零点必须在每次上电时重新标定不能写成硬编码常量。运放失调、电阻公差、温度漂移都会让零点偏移几十毫伏对应到电流可能就是几百毫安。标定方法很简单上电后电机不使能连续采 64 次取平均存进一个全局变量。电压采样是电阻分压加一个电压跟随器做阻抗缓冲。分压比要按最高母线电压算留出 20% 余量。滤波电容不能太大否则过压响应会变慢也不能太小否则开关噪声全部进 ADC。RC 时间常数取开关周期的几倍是常见做法。ADC 配置上有两个参数直接影响精度。一是采样时间采样保持电容充电需要时间源阻抗越高采样时间要越长一般不低于 15 个 ADC 时钟周期。二是触发源用定时器的更新事件触发 ADC让采样点和 PWM 波形严格同步避开开关瞬间的尖峰。这是把噪声压下去最有效的一招比事后加滤波强得多。4.2 限流、过压、欠压把保护写成状态机而不是 if 堆叠保护逻辑写成一串分散的 if 判断是灾难的开始。条件之间互相覆盖故障恢复了却忘了重新使能或者故障发生了但被后面的逻辑清掉现场表现就是“有时候保护有时候不保护”这种随机性最难查。我的做法是固定一个三状态机IDLE、RUN、FAULT。状态进入条件动作退出条件IDLE上电复位输出关闭采样零点标定收到启动命令且无故障RUNIDLE 中启动开放 PWM正常闭环任一项保护触发或收到停止命令FAULT过流/过压/欠压/过温立即关断 PWM锁存故障码人工复位或上位机清除关键在“锁存”两个字。故障进入 FAULT 后即使测量值回到了正常范围也不能自动恢复必须有人明确清除。这不是保守是必须的电机堵转时电流会周期性回落自动恢复会导致反复启停功率管撑不过几次。阈值要带迟滞。比如过流保护设在 10 A恢复条件不能也是 10 A否则在 10 A 附近会反复跳。常见做法是回差设置在 10% 到 20%即降到 8 A 以下才允许清除故障标志。硬件层面再加一道把电流比较器的输出接到定时器的刹车输入。软件状态机最快也要几微秒才能响应硬件刹车是纳秒级。两道防线并存软件那道路负责记录故障原因和界面提示硬件那道路只负责保命。4.3 位置式与增量式 PID两种写法的适用边界教程在第 15 章把两种 PID 的 C 实现分开写这个区分很重要很多人直接抄一份位置式到处用结果积分饱和、切换冲击全来了。位置式输出的是绝对量u(k) Kp·e(k) Ki·Σe(i) Kd·(e(k) - e(k-1))它适合位置控制因为输出直接对应目标位置掉电重启后只要知道当前位置就能续上。缺点是积分项要一直累加一旦输出被限幅而误差还在积累就会出现积分饱和回调时严重超调。增量式输出的是变化量typedef struct { float kp, ki, kd; float err_prev, err_prev2; float out; float out_min, out_max; } pid_inc_t; /* 返回更新后的控制量调用周期必须固定 */ float pid_inc_calc(pid_inc_t *p, float target, float actual) { float err target - actual; /* 增量式本次输出 上次输出 增量 */ float du p-kp * (err - p-err_prev) p-ki * err p-kd * (err - 2.0f * p-err_prev p-err_prev2); p-out du; /* 输出限幅等价于抗积分饱和 */ if (p-out p-out_max) p-out p-out_max; if (p-out p-out_min) p-out p-out_min; p-err_prev2 p-err_prev; p-err_prev err; return p-out; }这段代码有几点需要说明。结构体里的ki和kd是离散化之后的系数不是教科书上的 Ki、Kd换算关系是ki Kp × T / Ti、kd Kp × Td / T其中 T 是调用周期。err_prev2存的是上上次误差用来算二阶差分。输出限幅放在累加之后它本身就起到了抗积分饱和的作用因为增量式的积分是隐含在输出累加里的一旦输出被卡住后续增量再大也推不上去。增量式天然适合速度环因为它输出的是占空比增量切换模式时不会产生大的跳变。位置环我还是用位置式配独立的积分分离和输出钳位。调用周期必须严格固定这是两种写法共同的前提。用定时器中断触发不要放在主循环里靠延时凑周期。周期一抖微分项的差分就变成噪声放大器波形上会看到 PWM 忽大忽小。4.4 上位机联调串口协议、数据帧与波形回灌调 PID 最痛苦的不是调参是看不见。串口打印一行一行的数字人眼根本看不出超调和振荡。教程第 16 章专门讲了上位机整定和协议解析这个环节值得单独拿出来做。协议设计不用复杂帧头加长度加命令字加数据加校验就够了。要注意两点下位机解析必须写成状态机按字节喂进去不能假设一次中断收完一整帧数据回传用定时器周期触发不要在串口中断里做浮点格式化和发长帧会把实时性拖垮。回传内容我一般固定四路目标值、实际值、PID 输出、电流。四路足够看出超调、稳态误差和限流介入的时机。采样率给到 100 Hz 到 200 Hz再高串口带宽吃紧再低看不到细节。5. 排查清单电机调试里最容易翻车的五个地方这一节全是踩过的坑按现象、原因、解决三段写。每一条背后都对应一次硬件损失或一整晚的排查。5.1 上电就发烫、一给占空比就烧管现象电机不转功率管几秒内明显发烫或者第一次给占空比就炸管。原因排在前两位的是直通和栅极驱动不足。直通多半来自死区没配、互补输出极性反了或者初始化时先开了 PWM 再配死区。栅极驱动不足则是预驱供电没到、自举电容容量不够或没充电导致 MOS 管工作在线性区内阻大、发热快。解决先断开电机母线串一个功率电阻限流用示波器同时看同一相的上下管驱动波形确认死区和时序。预驱部分检查自举电容是否在每个开关周期都有充电通路占空比接近 100% 时自举电容会亏电需要限制最大占空比或改用带电荷泵的方案。5.2 电机能转但一转就触发过流保护现象空载正常稍微加负载就报过流读回来的电流值明显偏离预期。原因八成是电流采样链路的问题。运放零点没标定、分流电阻方向焊反、放大倍数算错都会让 ADC 读数和真实电流对不上。也可能是保护阈值设得低于电机启动瞬间的浪涌电流。解决电机不使能读 ADC 原始码值应该在量程中点附近偏差超过几十个字就要查运放和偏置电路。使能后用手轻堵转轴看电流读数是否随负载单调上升。阈值设置要给启动留余量启动阶段的浪涌通常是额定电流的三到五倍可以做一个软启动斜坡或者启动窗口内放宽阈值。5.3 编码器读数只增不减、抖动或者方向反了现象手动转轴计数值朝一个方向一直加或者静止时数值乱跳。原因只增不减通常是 A/B 相有一路没接进来硬件只看到单路信号抖动是输入没有滤波或者上拉缺失悬空引脚拾到了干扰方向反了就是 A/B 接反。解决先用示波器看两路信号正交关系应该是相位差 90°边沿干净。加 RC 滤波和定时器的输入滤波位滤波宽度取信号最短脉宽的几分之一。方向问题直接交换 A、B 接线或者在软件里对计数差值取负。上电自检里固定加一条低速正转确认计数递增。5.4 PID 一调就震荡越小越抖、越大越迟钝现象比例系数稍微加大就出现等幅或发散振荡减小又响应太慢。原因采样周期和 PWM 周期不同步或者微分项把电流纹波当成了误差变化。也常见于测速值本身噪声大微分一放大就全是毛刺。解决ADC 和速度计算都用定时器触发让采样点和 PWM 对齐。微分项要么直接关掉要么加一阶低通截止频率设在速度环带宽的五到十分之一。整定顺序按先比例后积分最后微分每加一项都重新观察阶跃响应不要三项一起调。5.5 步进电机丢步、堵转位置对不上现象跑一段时间后位置偏移或者加减速阶段听到咔咔声然后停转。原因加减速曲线太陡启动频率超过了电机在当前负载下的牵入频率或者细分设置得高但脉冲频率跟不上实际转速需求和输出能力脱节也可能是驱动电流设定偏低。解决加梯形或 S 形加减速启动频率从低到高试找到不丢步的上限再留 30% 余量。细分数不是越高越好细分高只提升平滑度不提升转矩脉冲频率反而更容易撞到驱动器上限。驱动电流按电机额定值设定拨码或电位器调完后实测相电流确认。6. 进阶验证把一条速度环跑到可复现的验收标准调到“看起来能转”很容易调到“换个人接手也能复现”很难。我现在的习惯是给每条环定一张验收表参数和结果都写进去而不是靠记忆里的手感。验收项测试方法合格线阶跃响应目标转速从 0 跳到额定 50%记录波形上升时间小于 200 ms超调小于 10%稳态误差额定转速运行 30 s波动小于 ±2%无静差趋势抗扰恢复运行中手动加负载再撤除恢复时间小于 500 ms不触发保护堵转保护直接堵住转轴50 ms 内关断故障码正确锁存正反转切换满速正转后立即给反转指令先减速到零再反向无过流这张表的价值在于把“感觉还行”变成了可判定的结果。做完一次完整测试参数表、采样周期、PID 系数、保护阈值全部记在工程目录的说明文件里下次换板子直接对照。还有一个技巧值得单独说把目标值和实际值同时打到上位机看它们的包络而不是逐点数值。很多振荡在数字列表里看不出来画成曲线一眼就能认出是比例过大还是积分饱和。我一般会把电流也叠上去如果电流波形在每次超调时都顶到限流点那说明问题不在 PID而在限流点设得离工作点太近。说个血泪教训。早期做一台带减速箱的有刷电机速度环空载调得漂漂亮亮装上负载就振。查了两天才发现测速用的是 1 ms 定时读编码器计数减速比大导致低速段每毫秒只涨一两个数量化噪声直接把微分项喂饱了。后来改成低速段切 T 法测周期、高速段切 M 法同一组 PID 参数就稳了。从那以后我每次做测速都强制走一遍“最低速、最高速、负载突变”三个工况确认量化误差在可接受范围内才动 PID。希望帮到你。本文还有配套的精品资源点击获取