FEATURED · 精选文章

STM32三闭环位置式PID控制直流有刷电机实现与调试

发布时间 / 2026/9/12 10:55:48
来源 / 创域科博编辑部
栏目 / 资讯中心
STM32三闭环位置式PID控制直流有刷电机实现与调试 简介基于STM32-F4系列微控制器的直流有刷电机三闭环控制完整源码专为需要实现位置环、速度环、电流环协同控制的嵌入式开发者与电机控制工程师设计适合正在学习电机控制、调试PID参数或构建运动控制系统的技术人员参考。这套代码采用C语言与STM32 HAL库编写位置式PID算法直接落地外设初始化、中断处理和PWM输出均有清晰实现可快速移植到同类F4项目中。压缩包共含555个文件整体大小约20.51MB以HAL库C源文件、头文件、Keil工程文件、链接配置文件和汇编启动文件等为主工程结构完整可在Keil MDK中直接打开查看。当前已有401人学习/下载具有较好的实践参考价值源码中不仅包含GPIO、定时器、编码器接口等底层配置还提供独立的PID控制模块与位置环、速度环、电流环三层控制逻辑整合示例有助于理解从电流调节到速度闭环再到位置定位的完整控制链路。对于需要快速完成电机控制原型验证或深入分析三闭环PID实现的工程师这份代码是一份可以直接查阅的高质量参考资料。1. 三闭环级联的基本架构位置环、速度环、电流环如何从一套 PID 中得出直流有刷电机的控制在大多数入门例程里是一套速度环或者直接开环占空比。而标题里把位置环、速度环、电流环同时放进来说明要解决的场景不是简单转起来而是“转多少度停在哪、用什么速度转过去、过程中扭矩波动可控”。这类需求常见于桌面机械臂关节、数控滑台、云台稳定关节等负载变化明显的位置控制对象。三环级联的控制思路是最外层位置环根据目标位置和实际位置的误差输出速度期望中间速度环根据目标速度和实际速度的误差输出电流期望最内层电流环根据目标电流和实际电流的误差输出 PWM 占空比。三层闭环从外到内分别管“位置准不准”“速度稳不稳”“力矩跟不跟得上”。位置式 PID 在这里指的是 PID 计算输出的是绝对值例如电流环直接输出一个 PWM 占空比数值而不是在上一拍基础上叠加一个变化量。本文以 STM32F4 HAL 库为背景把三闭环的位置式 PID 实现、参数初值设定和调试顺序一次讲透。2. STM32-F4 外设分配与 HAL 库初始化PWM、编码器、ADC 三路信号从哪来2.1 用到的定时器与采样通道先把资源映射定下来三闭环控制需要同时获得电机转速、转子位置和绕组电流三个反馈量。常见做法是用一块 STM32F405 或 STM32F407PWM 输出用高级定时器 TIM1 的通道 1编码器反馈用 TIM4 的编码器接口模式电流采样用 ADC1 的通道 0。这样分配的原因在于 TIM1 属于高级定时器自带互补输出和死区插入适合驱动 H 桥功率级TIM4 支持编码器接口模式可以直接对 AB 相信号做 4 倍频正交解码ADC1 采样速率高可以放入 PWM 同步触发或定时器触发通道中。功能外设通道说明PWM 输出TIM1CH1互补输出 PWM1N用于 H 桥上下桥臂编码器输入TIM4CH1/CH2AB 相正交解码计数方向自动判断电流采样ADC1IN0采样电阻电压经运放放大后接入控制节拍TIM6——基本定时器中断中执行三环计算编码器模式不需要自己写方向判断逻辑STM32 的定时器会在硬件层面根据 A、B 相相位关系决定计数器加还是减。每次读取 TIM4-CNT即得到当前位置。电流采样在电机控制里必须用低电感采样电阻加高带宽运放而不是简单接一个分压电阻否则 PWM 斩波过程中采样到的电流会严重失真。2.2 HAL 库初始化代码与关键参数说明使用 CubeMX 生成初始化代码时PWM 频率、编码器分频系数和 ADC 采样时间是三个最直接影响控制效果的参数。/* 定时器1 PWM 初始化20kHz PWM 频率 */ TIM_HandleTypeDef htim1 {0}; TIM_OC_InitTypeDef sConfigOC {0}; TIM_BreakDeadTimeConfigTypeDef sBreakDeadTimeConfig {0}; htim1.Instance TIM1; htim1.Init.Prescaler 0; htim1.Init.CounterMode TIM_COUNTERMODE_CENTERALIGNED1; htim1.Init.Period 4200; /* 168MHz / 4200 / 2 20kHz */ htim1.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim1.Init.RepetitionCounter 0; htim1.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_ENABLE; HAL_TIM_PWM_Init(htim1); sConfigOC.OCMode TIM_OCMODE_PWM1; sConfigOC.Pulse 0; sConfigOC.OCPolarity TIM_OCPOLARITY_HIGH; sConfigOC.OCNPolarity TIM_OCNPOLARITY_HIGH; sConfigOC.OCFastMode TIM_OCFAST_DISABLE; sConfigOC.OCIdleState TIM_OCIDLESTATE_RESET; sConfigOC.OCNIdleState TIM_OCNIDLESTATE_RESET; HAL_TIM_PWM_ConfigChannel(htim1, sConfigOC, TIM_CHANNEL_1);参数说明中心对齐模式适合双极性电流采样PWM 计数向上和向下时各产生一次更新事件理论上可以做到在 PWM 中心点采样电流避开开关噪声。Period 按 168MHz 主频计算得到 20kHz 的 PWM 频率。有刷电机的 PWM 频率一般选 10kHz 到 30kHz 之间低于 10kHz 会出现明显电磁噪声高于 30kHz 会增加 H 桥开关损耗。中心对齐模式下50% 占空比对应电机两端电压为 0单片机输出的是对称 PWM 波形。这里 Period 的值需要结合死区时间一起考虑死区太短会有直通炸管风险太长会降低最大输出能力常见设备上将死区设在 100ns 到 500ns 之间。编码器接口初始化如下/* TIM4 编码器模式初始化 */ TIM_Encoder_InitTypeDef sEncoderConfig {0}; htim4.Instance TIM4; htim4.Init.Prescaler 0; htim4.Init.CounterMode TIM_COUNTERMODE_UP; htim4.Init.Period 0xFFFF; htim4.Init.ClockDivision TIM_CLOCKDIVISION_DIV1; htim4.Init.AutoReloadPreload TIM_AUTORELOAD_PRELOAD_DISABLE; sEncoderConfig.EncoderMode TIM_ENCODERMODE_TI12; sEncoderConfig.IC1Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC1Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC1Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC1Filter 0; sEncoderConfig.IC2Polarity TIM_ICPOLARITY_RISING; sEncoderConfig.IC2Selection TIM_ICSELECTION_DIRECTTI; sEncoderConfig.IC2Prescaler TIM_ICPSC_DIV1; sEncoderConfig.IC2Filter 0; HAL_TIM_Encoder_Init(htim4, sEncoderConfig);我在调试初期不会给编码器输入加滤波器即 IC1Filter 和 IC2Filter 保持为 0。这一点很多人会忽略滤波器本质上是一个数字低通会增加 1 到 2 个时钟周期的延迟这个延迟即使只有几微秒放在电流环的时间尺度上也会减小相位裕量。编码器线缆如果超过 30 厘米才需要把滤波系数开到 1 或 2。TIM4 的计数器是 16 位取值范围只有 65536高速旋转下必须用溢出中断记录进位否则位置会瞬间跳变。电流采样初始化需要注意采样时间不能太短/* ADC1 独立模式扫描关闭单通道采样 */ ADC_ChannelConfTypeDef sConfig {0}; hadc1.Instance ADC1; hadc1.Init.ClockPrescaler ADC_CLOCK_SYNC_PCLK_DIV4; hadc1.Init.Resolution ADC_RESOLUTION_12B; hadc1.Init.ScanConvMode DISABLE; hadc1.Init.ContinuousConvMode DISABLE; hadc1.Init.DiscontinuousConvMode DISABLE; hadc1.Init.EOCSelection ADC_EOC_SINGLE_CONV; HAL_ADC_Init(hadc1); sConfig.Channel ADC_CHANNEL_0; sConfig.Rank 1; sConfig.SamplingTime ADC_SAMPLETIME_84CYCLES; HAL_ADC_ConfigChannel(hadc1, sConfig);ADC 采样时间越长输入阻抗匹配越容易但采样窗口过大会把 PWM 斩波中点前后的电压平均掉。常见工程做法是把采样时刻对齐到 PWM 计数器匹配点由定时器触发 ADC 启动。上述配置中 84 个周期的采样时间在 168MHz 主频下约 500ns对应电机相电感几十到几百微亨的小型电机够用。若电机功率较大相电感小到微亨级则需要更短的采样时间和更高带宽的采样电路。3. 位置式 PID 与增量式 PID 的选择以及 C 语言实现3.1 为什么三闭环内层更适合位置式 PID位置式 PID 的输出是执行机构的绝对位置。在三闭环控制中最内层电流环输出是 PWM 占空比这是一个绝对值H 桥某时刻就是按某个特定占空比工作。位置式 PID 直接计算得到这个绝对值逻辑清晰初始化为 0 就是电机停转因此不需要像增量式那样计算“在上一次输出基础上加多少”。增量式 PID 输出的是增量值更容易做到手动模式下限位不跳变也天然不被累计误差困扰。但在电流环中一旦输出 PWM 限幅积分项饱和问题用增量式反而更隐蔽积分项被困在内部状态里从外部看输出已经限幅却不容易判断内部是否已经饱和。位置式 PID 将积分项独立存储限幅判断直接作用于积分累加值工程上更直观。两种实现的核心区别总结如下对比维度位置式 PID增量式 PID输出含义当前时刻的绝对值当前时刻与上一时刻的差值积分处理独立积分项手动抗饱和方便隐含在输出累积中需额外状态判断限幅实现输出限幅同时限幅积分项输出限幅但内部不一定退出饱和适用层级三闭环的电流、速度、位置都适用更适合远程遥控或手动、自动切换场景3.2 PID 结构体定义与位置式计算函数把 PID 参数、误差和输出集中在一个结构体里调试时能直接监控每一项。积分项和输出都做限幅避免电机长时间堵转后积分无限累加。typedef struct { float kp; float ki; float kd; float target; /* 目标值 */ float feedback; /* 反馈值 */ float error; /* 本次误差 */ float last_error; /* 上一次误差 */ float integral; /* 积分累加量 */ float integral_max; /* 积分限幅防止积分饱和 */ float output; float output_max; /* 输出限幅 */ } PID_TypeDef; void PID_Calc(PID_TypeDef *pid, float target, float feedback) { pid-target target; pid-feedback feedback; pid-error target - feedback; pid-integral pid-error; /* 积分限幅在积分累加阶段就截止避免输出被积分项完全拖死 */ if (pid-integral pid-integral_max) { pid-integral pid-integral_max; } else if (pid-integral -pid-integral_max) { pid-integral -pid-integral_max; } /* 微分项只作用于反馈值避免目标值突变时产生微分冲击 */ float derivative pid-feedback - (pid-feedback); /* 实际使用中应保存上一拍反馈值这里以伪代码方式展开 */ /* derivative pid-last_feedback - pid-feedback; */ pid-output pid-kp * pid-error pid-ki * pid-integral pid-kd * (pid-last_error - pid-error); /* 输出限幅 */ if (pid-output pid-output_max) { pid-output pid-output_max; } else if (pid-output -pid-output_max) { pid-output -pid-output_max; } pid-last_error pid-error; }上述代码中一个值得注意的设计是微分项写作last_error - error而不是常见的error - last_error。这样表示的是误差的变化率方向误差增大时这一项为负会抑制增大趋势。用位置式 PID 时如果直接按公式写error - last_error微分方向恰好相反会让系统更容易振荡。这个细节在实际调试中很难通过观察波形发现往往表现为收敛速度慢、超调偏大调 kp 和 ki 都无效最终查到是微分方向反了。3.3 三环的采样周期与 C 语言任务分配位置环、速度环、电流环的采样周期从外到内递进一般按 5ms、1ms、100us 的数量级分配。电流环必须最快因为电流的变化速度远快于速度和位置。用定时器中断嵌套或主循环按时间片调度皆可但电流环不建议在 RTOS 线程里做优先级和抢占抖动会导致 PWM 占空比更新时刻不稳定产生周期性的电流波动。常见的时间片组织方式如下volatile uint8_t speed_loop_flag 0; volatile uint8_t pos_loop_flag 0; /* 假设 TIM6 配置成 100us 周期 */ void TIM6_IRQHandler(void) { HAL_TIM_IRQHandler(htim6); CurrentLoop(); /* 每个 100us 执行一次 */ speed_loop_counter; if (speed_loop_counter 10) { speed_loop_counter 0; speed_loop_flag 1; } } /* 主循环中处理速度环和位置环 */ int main(void) { while (1) { if (speed_loop_flag) { speed_loop_flag 0; SpeedLoop(); pos_loop_counter; if (pos_loop_counter 5) { pos_loop_counter 0; PosLoop(); } } } }这个方案的优点是电流环始终严格跑在 100us 节拍上速度和位置环放在主循环里执行即使偶尔晚几百微秒对整定影响不大。位置环算出的目标速度值不会直接写进速度环而是先经过速度规划。只要位置环输出限幅合理即目标速度不超过电机额定转速整个级联就是稳定的。4. 三闭环联调顺序与位置式 PID 参数整定方法4.1 必须先电流环、再速度环、最后位置环顺序不能变三闭环级联控制的一个关键特征是内环的带宽必须大于外环。也就是说电流环响应的速度要快于速度环的指令变化速度环的快于位置环。工程上最稳妥的做法是从内到外逐环调节先把电流环调到能快速跟踪期望电流再锁定电流环参数去调速度环最后把速度和电流参数固定下来只调位置环。如果一开始就把三个环的参数同时放开手册上的调参经验完全失效因为位置环出现振荡时无法判断是位置环的超调还是速度环内部不稳定被放大了还是电流环跟踪不上产生了耦合。分级调试后每一层的表现都被下一层的内环“兜住”上一个环看到的是已经收敛好的被控对象。4.2 电流环调试时用什么观察指标先让电机堵转用手抓住转子不让转动然后给电流环一个固定目标电流。观察反馈电流是否迅速达到目标值是否有超调。电流环调试期间速度环和位置环输出全部置 0。记录大约 100ms 的阶跃响应看反馈电流到达目标值的 90% 需要多少微秒。电流环整定合格的标准是在 2 到 3 个 PWM 周期内达到目标电流且超调量不超过 5%。/* 仅启用电流环的调试函数 */ void CurrentLoop_Debug(void) { float target_current 1.0f; /* 堵转测试时目标电流 1A */ float actual_current Get_Current(); PID_Calc(pid_current, target_current, actual_current); Set_PWM_Duty(pid_current.output); }此阶段 P 值和 I 值可以先从 0 逐渐增大。P 值增大到电流响应出现 20% 以上超调时回退 30%然后逐步加 I 值消除静态偏差。微分项在电流环中通常不需要因为电流反馈有采样噪声微分项会把这些噪声放大造成 PWM 占空比剧烈抖动。只有当电流响应明显滞后而 P 值已经快到上限时才考虑加一个很小的微分项。4.3 速度环调试与位置环参数表速度环调试时给一个固定目标转速比如 1000RPM观察速度和电流波形。速度环的 P 值决定电机对负载扰动的抵抗能力但 P 值过大会让电流指令波动幅度增大这时听电机声音会明显变尖锐。速度环积分项的核心作用是消除稳态误差对于负载摩擦略大的有刷电机这个积分项不能太小否则负载变化后转速会存在几 RPM 的残余偏差。位置环是最容易调的因为有了内环之后位置环通常只需要一个比例系数就能工作。把位置环作为最外环输出限幅设为速度环允许的最大速度然后从很小的 P 值开始逐渐增加。观察电机从起始位置运动到目标位置的过程位置环整定合格的标准是到位后没有明显来回晃动响应过程中速度不超过限幅值。环节采样周期P 参数起点I 参数起点输出限幅电流环100us电机额定电压对应最高占空比的 1/100.5 / 电机电气时间常数占空比 ±99%速度环1ms使电机在 10% 指令下能启动按负载惯性逐次累加电流环给定 ±3A位置环5ms让电机以不超过 30% 限幅速度运动一般保持 0 或极小值速度给定 ±1500RPM表格中的初值是从大量小型直流有刷电机调试中归纳出来的经验量,具体数值要按电机额定电压、减速比、编码器线数做等比例换算。每次改参数后不直接试目标位置,先给低速、小行程的三角波位置指令,观察往返运动的对称性。4.4 防止 PID 饱和的一般做法积分限幅本身能防止大部分饱和问题,但三闭环中还有一类特殊的饱和,发生在内环输出受限时。比如位置环输出 5000RPM 的速度指令,而电机实际最高转速只有 3000RPM,此时速度环永远无法消除偏差,位置环积分会不断累加,最终当位置误差减小时,积分项仍然保持高位,产生明显超调。惯用处理方法是把位置环的积分项做成“条件积分”:位置误差大于某个阈值时不积分,进入误差带之后恢复积分。这个阈值一般设为运动总行程的 5% 到 10%。5. 用代码验证三闭环性能:阶跃响应、抗扰动与参数可视化5.1 串口实时输出反馈值,三层曲线同时观察技巧调参时如果只靠电机声音和手感,很难区分是哪个环出了问题。常见做法是用一个定时器,把位置反馈、速度反馈、电流反馈按同一时间戳打包,通过串口以 CSV 格式输出,PC 端随便用一个串口助手保存成文本文件,放入 Python 或 MATLAB 画图。要保证头文件里的浮点数格式一致,例如全部保留 3 位小数,否则解析程序会错位。输出数据不能放在 100us 的电流环中断里直接执行,串口发送会阻塞中断,导致电流环节拍抖动。正确的位置是在主循环中读取最近一次三层反馈的快照,再统一发送。即使发送频率只有 100Hz,也足以看到调参过程中振荡频率和幅值的变化趋势。5.2 位置阶跃响应的合格指标与快速判断方法给一个 90 度的位置阶跃,从运动开始到位置误差收敛到 0.5 度以内,时间不应超过 300ms(不含加减速规划时间)超调量应小于 5%,如果超过 10%,优先减小位置环 P 值到位后电机声音应迅速安静,如果持续高频蜂鸣,说明电流环或速度环存在振荡,应回到内环检查记录一段阶跃响应的位置误差曲线,如果发现误差单调减小但最终差一小段距离不动,说明位置环的积分项为 0且摩擦补偿不足,这时可以给位置环加一个很小的积分项。如果误差曲线在零点附近来回穿越,每次方向都变化,则是微分项作用过强或速度环限幅太大导致的单向超调。5.3 一个实用的速度前馈做法三闭环的响应速度受限于位置环带宽,为了让位置环快速跟上斜坡信号,常见做法是在位置环输出上叠加速度前馈。前馈量是目标位置的变化率乘以一个常数,这个常数一般取 0.6 到 0.9,因为被控对象并非刚体,前馈过大会让速度环出现正偏差。前馈值只叠加在速度环的给定上,不经过 PID 积分项,因此不会破坏闭环稳定性。float pos_target Get_Target_Position(); static float last_pos_target 0.0f; float velocity_feedforward (pos_target - last_pos_target) * 0.8f / POS_LOOP_PERIOD; last_pos_target pos_target; /* 位置环 PID 输出作为速度给定,叠加前馈 */ float speed_target pid_pos.output velocity_feedforward; PID_Calc(pid_speed, speed_target, Get_Actual_Speed());这段代码里前馈系数取 0.8,表示速度环目标中 80% 通过前馈到达,其余 20% 由位置误差修正。前提是编码器速度计算无滞后,上图所示代码直接把位置插分当作速度,位置环周期内位置变化量较小的情况下容易受量化噪声影响,更好的做法是在 DMA 中断里更新目标位置,减小时钟粒度带来的运算误差。验证前馈是否生效,可以对比施加阶跃和斜坡两种位置指令下的跟踪误差。若斜坡跟踪误差比阶跃小到 1/3 以下,说明前馈系数起效;若无明显变化,检查前馈计算是否被位置限幅截断。本文还有配套的精品资源点击获取
RELATED — 相关阅读

相关资讯

LATEST — 最新资讯

最新发布

TODAY — 本日精选

新闻

WEEKLY — 本周精选

新闻

MONTHLY — 本月精选

新闻