资讯动态

STM32F103三环PID控制直流有刷电机实战:从硬件选型到参数整定

发布时间:2026/9/29 19:28:10 来源:尧图企业网站定制
1. 为什么直流有刷电机的三环PID值得认真对待搞电机控制的朋友大概率都接触过直流有刷电机它结构简单、驱动容易、成本低廉从玩具小车到工业传送带从云台稳定到精密泵控到处都有它的身影。但很多人对它的控制认知停留在“给PWM就能转”的阶段一旦遇到负载扰动、转速波动、位置精度要求高的场景单环控制就捉襟见肘了。三环PID控制——电流环、速度环、位置环——就是解决这类问题的经典方案也是运动控制领域最基础也最考验功力的架构。我这次做的项目主控用的是STM32F103C8T6驱动芯片是常见的H桥方案电机是一台24V/额定转速3000rpm的直流有刷电机带500线增量式编码器。目标很明确实现位置、速度、电流三个闭环的串级控制位置环响应无超调速度环抗扰能力强电流环响应快且稳定。听起来像是教科书上的标准案例但实际从硬件选型到软件调试踩的坑远比想象中多。这篇文章就把整个项目的设计思路、硬件配置、软件实现和调试过程完整拆开讲适合有STM32基础、想深入理解串级PID的嵌入式工程师也适合正在做电机控制相关毕业设计的同学参考。注意三环PID不是三个PID简单堆在一起串级结构有严格的执行顺序和参数依赖关系搞错顺序会导致系统震荡甚至失控。2. 三环PID控制的整体设计思路拆解2.1 串级控制的核心逻辑从外到内逐级细化三环PID的本质是串级控制Cascade Control它的核心思想是把一个复杂的控制任务分解成多个层次每一层负责不同的物理量外环的输出作为内环的输入。具体到直流有刷电机位置环是最外环输入是目标位置与实际位置的偏差输出是速度指令。速度环是中间环输入是位置环给出的速度指令与实际速度的偏差输出是电流指令。电流环是最内环输入是速度环给出的电流指令与实际电流的偏差输出是PWM占空比。为什么要这样分层因为位置的变化本质上是速度的积分速度的变化本质上是加速度的积分而加速度由力矩决定力矩又正比于电流。把控制任务按物理量的时间尺度分层内环响应快、外环响应慢每一环只需要处理自己频段内的扰动整体系统的稳定性和动态性能都会大幅提升。打个比方这就像公司管理老板位置环定目标经理速度环拆解任务员工电流环执行具体操作。老板不需要管每个员工怎么干活只需要根据结果调整给经理的指标经理也不需要管老板的战略只需要根据指标调整给员工的指令。层级清晰各司其职。2.2 为什么选STM32F103而不是更高级的芯片市面上能跑三环PID的MCU很多STM32F4、G4、H7系列性能都更强甚至还有专用的电机控制芯片。我选F103C8T6的原因很实际成本敏感F103C8T6核心板价格极低批量采购更便宜对于成本敏感的电机控制产品来说性能够用就没必要上更贵的。资源匹配三环PID的计算量并不大F103的72MHz主频、20KB RAM、64KB Flash完全够用。电流环和速度环跑在10kHz位置环跑在1kHzCPU占用率不到30%。生态成熟F103的教程、例程、社区资源极其丰富遇到问题容易找到参考。标准库和HAL库都有大量现成代码可以借鉴。外设够用需要的高级定时器TIM1生成互补PWM带死区需要ADC采集电流需要编码器接口读位置和速度需要USART做调试输出F103全都有。当然如果你的应用需要更复杂的算法比如无感FOC、自适应控制或者需要更高的PWM频率比如20kHz以上那F103可能就不够了得考虑F4或G4系列。但对于有刷电机的三环PIDF103是性价比最高的选择。2.3 硬件架构的总体设计整个系统的硬件框图可以这样理解电源部分24V主电源给电机供电通过降压模块得到12V给驱动芯片再通过AMS1117得到3.3V给STM32和编码器。驱动部分H桥驱动芯片我用的是DRV8871内置电流检测和过流保护接收STM32的PWM信号驱动电机正反转。反馈部分增量式编码器输出A/B两相正交信号接到STM32的TIM2编码器接口模式电流检测通过采样电阻运放输出模拟电压到STM32的ADC引脚。调试部分USART1接USB转串口模块用CRT调试软件或串口助手查看实时数据、发送调参指令。这个架构不复杂但每个环节都有细节需要注意后面会逐一展开。3. 硬件配置的关键细节与避坑指南3.1 主控最小系统与引脚分配STM32F103C8T6的最小系统包括8MHz晶振HSE、32.768kHz晶振LSE可选、复位电路、BOOT选择电路、SWD调试接口。这些是基础不展开。重点说引脚分配因为引脚分配不合理会导致后期改板或者飞线。我的引脚分配方案功能引脚外设说明PWM输出PA8TIM1_CH1互补输出带死区PWM互补PA7TIM1_CH1N驱动H桥下管电流采样PA0ADC1_IN0采样电阻电压编码器A相PA0TIM2_CH1注意与ADC冲突编码器B相PA1TIM2_CH2正交解码串口TXPA9USART1_TX调试输出串口RXPA10USART1_RX接收指令按键PB0GPIO输入启停控制LEDPC13GPIO输出状态指示这里有个坑PA0同时是ADC1_IN0和TIM2_CH1如果编码器A相接在PA0电流采样就不能用PA0。我后来把电流采样改到了PA1编码器A/B相改到PA6/PA7TIM3_CH1/CH2。所以引脚分配一定要提前规划好避免外设冲突。提示STM32的引脚复用功能很多分配时先列出所有需要的外设再查数据手册的复用表确保没有冲突。特别是ADC和定时器通道经常打架。3.2 H桥驱动电路的设计要点H桥驱动是直流有刷电机控制的核心。我用的是DRV8871它内部集成了H桥MOSFET、电流检测、过流保护、过热保护外围电路简单只需要几个电容和电阻。但有几个细节要注意PWM频率选择DRV8871支持最高100kHz的PWM频率。我选的是20kHz高于人耳听觉范围避免电机发出啸叫。同时20kHz对于F103的72MHz主频来说ARR值可以设得比较合理72000000/200003600分辨率足够。死区时间H桥上下管不能同时导通否则会短路烧毁。DRV8871内部有死区控制但STM32的TIM1也可以配置死区时间。我设置了500ns的死区通过TIM1的BDTR寄存器配置。电流检测DRV8871的IPROPI引脚输出与负载电流成正比的电流通过一个电阻转换成电压。我用的电阻是1kΩ满量程对应约2A电流输出电压范围0-3.3V正好匹配STM32的ADC输入范围。续流二极管虽然DRV8871内部有体二极管但电机是感性负载关断时会产生反向电动势。建议在电机两端并联一个肖特基二极管如SS34加速续流保护芯片。3.3 编码器接口与测速方法增量式编码器输出A/B两相正交信号STM32的定时器编码器接口模式可以自动计数和判向。我的编码器是500线四倍频后每转2000个计数。TIM2配置为编码器模式ARR设为65535计数器自动增减。测速方法有两种M法频率法固定时间间隔如1ms读取编码器计数值计算速度。适合高速测量低速时精度差。T法周期法测量两个编码器脉冲之间的时间计算速度。适合低速测量高速时精度差。M/T法结合两者低速用T法高速用M法全程精度都高。我用的是M法因为速度环跑在1kHz每1ms读取一次编码器计数速度计算如下// 每1ms调用一次 int16_t encoder_count TIM2-CNT; // 读取编码器计数 TIM2-CNT 0; // 清零 float speed_rpm (encoder_count / 2000.0f) * 60.0f / 0.001f; // 转换为rpm但这里有个问题1ms内如果编码器计数很少低速时速度计算会有较大量化误差。解决办法是低速时改用T法或者增加测速周期。我后来把速度环周期改成2ms低速精度有所改善。注意编码器计数清零后如果电机正在反转计数会是负数需要做符号处理。另外TIM2的CNT是16位1ms内最大计数不能超过65535否则会溢出。500线编码器四倍频后2000计数/转3000rpm时1ms内计数为100远小于65535安全。3.4 电流采样电路的设计与校准电流采样是电流环的基础采样不准电流环就是摆设。我用的是采样电阻运放方案采样电阻0.1Ω/1W的精密电阻串联在H桥的下管和地之间。运放INA282增益50倍共模抑制比高适合电流采样。输出采样电阻上的电压经过INA282放大后输出到STM32的ADC引脚。计算过程电机堵转电流最大2A采样电阻0.1Ω压降0.2V放大50倍后是10V超过STM32的3.3V ADC量程。所以实际采样电阻要改小或者运放增益降低。我最终用的是0.05Ω采样电阻增益50倍2A时输出5V还是超。最后换成0.02Ω采样电阻增益50倍2A时输出2V在3.3V量程内留有余量。ADC配置为12位参考电压3.3V分辨率3.3/4096≈0.8mV。对应电流分辨率0.8mV / (0.02Ω × 50) 0.8mA。这个精度对于电流环来说足够了。校准方法电机不转时读取ADC值作为零点偏移然后给一个已知电流比如用电子负载拉1A读取ADC值计算增益系数。实际代码中float current_offset 2048.0f; // 零点偏移实际测量得到 float current_gain 0.001f; // 增益系数实际标定得到 float current (ADC_value - current_offset) * current_gain;提示电流采样电阻要选低温漂的如锰铜电阻否则温度变化会导致采样值漂移。运放的供电要干净最好加LC滤波否则PWM噪声会耦合到电流采样上。4. 软件架构与三环PID的实现细节4.1 定时器中断与任务调度三环PID的执行频率不同电流环10kHz每100μs一次速度环1kHz每1ms一次位置环100Hz每10ms一次。我用TIM3作为系统时基配置为100μs中断一次在中断里做任务计数void TIM3_IRQHandler(void) { if (TIM3-SR TIM_SR_UIF) { TIM3-SR ~TIM_SR_UIF; static uint8_t count_1ms 0; static uint8_t count_10ms 0; // 电流环每100μs执行 current_loop(); count_1ms; if (count_1ms 10) { // 1ms count_1ms 0; speed_loop(); // 速度环 } count_10ms; if (count_10ms 100) { // 10ms count_10ms 0; position_loop(); // 位置环 } } }这种调度方式简单可靠但要注意中断执行时间不能超过100μs否则会丢失中断。电流环的计算量要控制好PID计算ADC读取PWM更新F103在72MHz下大约需要20μs安全。4.2 电流环的实现与参数整定电流环是最内环响应最快它的性能直接影响整个系统的动态响应。电流环的输入是速度环输出的电流指令反馈是ADC采样的实际电流输出是PWM占空比。电流环的PID参数整定方法先只用P把I和D设为0逐渐增大P直到电流响应开始震荡然后取震荡临界值的60%作为P。再加I逐渐增大I消除稳态误差。I太大会导致超调太小消除误差慢。一般I取P的1/10到1/5。D一般不用电流环的反馈噪声较大D会放大噪声导致PWM抖动。除非电流采样非常干净否则不建议加D。我最终的电流环参数Kp0.8Ki0.1Kd0。电流环的响应时间约1ms超调小于10%。代码实现typedef struct { float Kp, Ki, Kd; float integral; float prev_error; float output_max; float output_min; } PID_TypeDef; float PID_Calc(PID_TypeDef *pid, float target, float actual) { float error target - actual; pid-integral error; // 积分限幅 if (pid-integral pid-output_max) pid-integral pid-output_max; if (pid-integral pid-output_min) pid-integral pid-output_min; float derivative error - pid-prev_error; pid-prev_error error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if (output pid-output_max) output pid-output_max; if (output pid-output_min) output pid-output_min; return output; }注意积分限幅很重要否则积分项会无限累积导致系统失控。输出限幅也要加防止PWM占空比超过100%。4.3 速度环的实现与抗扰设计速度环的输入是位置环输出的速度指令反馈是编码器计算的实际速度输出是电流指令。速度环的响应比电流环慢但比位置环快。速度环的PID参数整定先整定电流环电流环没调好速度环不可能调好。速度环先只用P逐渐增大P直到速度响应开始震荡取临界值的50%作为P。再加I消除稳态速度误差。I太大会导致速度超调太小恢复慢。D可以少量使用速度环的反馈噪声比电流环小可以加少量D来抑制超调。但D太大会导致速度抖动。我最终的速度环参数Kp2.0Ki0.05Kd0.01。速度环的响应时间约10ms超调小于5%。抗扰设计速度环的输出电流指令要限幅防止电流过大烧毁电机。我设置的电流限幅是±1.5A对应电机额定电流的75%。4.4 位置环的实现与无超调策略位置环是最外环输入是目标位置反馈是编码器累计计数输出是速度指令。位置环的响应最慢但精度要求最高。位置环的PID参数整定位置环一般只用P位置环的积分项容易导致超调除非有稳态位置误差否则不加I。D可以加位置环的D可以抑制超调但会放大编码器噪声。如果编码器分辨率高可以加少量D。P从小往大调逐渐增大P直到位置响应开始震荡取临界值的40%作为P。我最终的位置环参数Kp5.0Ki0Kd0.1。位置环的响应时间约100ms无超调。无超调策略位置环的输出速度指令要限幅防止速度过快导致过冲。我设置的速度限幅是±2000rpm。另外位置接近目标时可以降低速度指令实现平滑停止。float position_loop(float target_pos, float actual_pos) { float error target_pos - actual_pos; float speed_cmd PID_Calc(pos_pid, target_pos, actual_pos); // 接近目标时降速 if (fabs(error) 100) { // 100个计数以内 speed_cmd * 0.5f; } // 速度限幅 if (speed_cmd 2000) speed_cmd 2000; if (speed_cmd -2000) speed_cmd -2000; return speed_cmd; }提示位置环的无超调策略还可以用梯形速度曲线或S形速度曲线但实现复杂。对于大多数应用简单的降速策略就够用了。5. 调试过程与常见问题排查实录5.1 调试工具与环境搭建调试三环PID光靠看代码是不够的必须有实时数据可视化。我用的是CRT调试软件SecureCRT配合串口输出把电流、速度、位置数据以文本形式发送到电脑用CRT的日志功能记录然后用Excel或Python画图。串口配置波特率1152008数据位1停止位无校验。数据格式$DATA,current,speed,position,target_position#每10ms发送一次CRT接收后保存为CSV文件用Python的matplotlib画图import matplotlib.pyplot as plt import csv data [] with open(data.csv, r) as f: reader csv.reader(f) for row in reader: data.append([float(x) for x in row]) current [d[0] for d in data] speed [d[1] for d in data] position [d[2] for d in data] plt.figure(figsize(12, 8)) plt.subplot(3, 1, 1) plt.plot(current) plt.ylabel(Current (A)) plt.subplot(3, 1, 2) plt.plot(speed) plt.ylabel(Speed (rpm)) plt.subplot(3, 1, 3) plt.plot(position) plt.ylabel(Position (counts)) plt.show()提示串口输出会占用CPU时间如果数据量太大会影响控制周期。建议只在调试时开启正式运行时关闭或降低发送频率。5.2 电流环震荡的排查与解决第一次调试电流环时电流波形震荡严重PWM占空比抖动大。排查过程检查ADC采样用示波器看ADC输入引脚发现PWM开关噪声耦合到了采样电阻上。解决办法在采样电阻两端加RC滤波1kΩ100nF截止频率约1.6kHz滤除20kHz的PWM噪声。检查PID参数Kp太大导致震荡。把Kp从2.0降到0.8震荡消失。检查积分限幅积分项没有限幅导致积分饱和。加上积分限幅后电流响应更平滑。最终电流环的阶跃响应上升时间约0.5ms超调约5%稳态误差小于1%。5.3 速度环低速抖动的排查与解决速度环在低速时100rpm抖动明显高速时正常。排查过程检查编码器测速1ms内编码器计数太少量化误差大。把速度环周期从1ms改成2ms低速抖动有所改善。检查PID参数速度环的D太大放大了编码器噪声。把Kd从0.05降到0.01抖动减小。检查机械传动电机和负载之间的联轴器有间隙低速时导致速度波动。更换弹性联轴器后抖动进一步减小。最终速度环的低速性能100rpm时速度波动小于5%满足大多数应用需求。5.4 位置环超调的排查与解决位置环在定位时超调明显超过目标位置后才慢慢回来。排查过程检查速度限幅速度限幅太大导致接近目标时速度仍然很高。把速度限幅从3000rpm降到2000rpm超调减小。检查降速策略降速阈值太小导致降速太晚。把降速阈值从50个计数改成100个计数超调进一步减小。检查PID参数位置环的Kp太大导致超调。把Kp从8.0降到5.0超调消失。最终位置环的阶跃响应上升时间约80ms无超调稳态误差小于1个计数。5.5 常见问题速查表问题现象可能原因排查方法解决方案电流环震荡Kp太大减小KpKp取临界值的60%电流环稳态误差大Ki太小增大KiKi取Kp的1/10到1/5速度环低速抖动编码器量化误差增加测速周期速度环周期改为2ms速度环高速震荡Kp太大减小KpKp取临界值的50%位置环超调速度限幅太大减小速度限幅速度限幅取额定值的80%位置环稳态误差大Ki太小增大Ki位置环一般不加IPWM无输出定时器配置错误检查TIM1配置使能TIM1和MOE编码器计数方向反A/B相接反交换A/B相或修改编码器模式电流采样漂移运放温漂检查运放供电加LC滤波选低温漂电阻串口无输出波特率不匹配检查串口配置确认波特率115200注意调试时一定要有耐心三环PID的参数是相互影响的改一个环的参数可能会影响其他环。建议从内到外逐环调试每调好一个环再调下一个。6. 实操心得与进阶优化方向6.1 参数整定的经验法则三环PID的参数整定有一些经验法则可以加速调试电流环Kp取电机电阻的倒数乘以PWM周期的倒数Ki取Kp的1/10。比如电机电阻1ΩPWM周期100μsKp≈10000实际调试时从100开始试。速度环Kp取电流环Kp的1/10Ki取速度环Kp的1/20。比如电流环Kp0.8速度环Kp≈0.08实际调试时从0.5开始试。位置环Kp取速度环Kp的1/5Ki一般不加。比如速度环Kp2.0位置环Kp≈0.4实际调试时从1.0开始试。这些经验值只是起点实际参数还要根据电机特性、负载惯量、机械传动等因素调整。6.2 抗积分饱和的实用技巧积分饱和是三环PID调试中最常见的问题之一。当目标值突变时积分项会快速累积导致输出饱和系统响应变慢甚至震荡。解决办法积分限幅限制积分项的最大值和最小值防止无限累积。积分分离当误差较大时暂时关闭积分项只让P和D起作用当误差较小时再开启积分项。反计算抗饱和当输出饱和时把饱和值与实际输出的差值反馈到积分项抵消积分累积。我用的是积分限幅积分分离的组合方案效果很好。代码实现float PID_Calc_AntiWindup(PID_TypeDef *pid, float target, float actual) { float error target - actual; // 积分分离误差大时关闭积分 if (fabs(error) 50) { pid-integral error; } // 积分限幅 if (pid-integral pid-output_max) pid-integral pid-output_max; if (pid-integral pid-output_min) pid-integral pid-output_min; float derivative error - pid-prev_error; pid-prev_error error; float output pid-Kp * error pid-Ki * pid-integral pid-Kd * derivative; // 输出限幅 if (output pid-output_max) output pid-output_max; if (output pid-output_min) output pid-output_min; return output; }6.3 进阶优化前馈控制与自适应PID如果三环PID的性能还不够可以考虑进阶优化前馈控制在速度环或位置环加入前馈项根据目标值的微分直接给出控制量减少反馈环的负担。比如位置环的前馈项是目标速度速度环的前馈项是目标加速度。自适应PID根据系统状态动态调整PID参数。比如低速时增大Kp高速时减小Kp负载大时增大Ki负载小时减小Ki。模糊PID用模糊规则根据误差和误差变化率调整PID参数适合非线性系统。这些进阶方法实现复杂但对于高性能应用是值得的。我目前只用了前馈控制位置环的响应时间从100ms降到了60ms效果明显。6.4 代码组织与可维护性建议最后说几点代码组织的建议模块化把PID算法、电机驱动、编码器读取、串口调试分成独立的.c和.h文件方便复用和移植。参数集中管理把所有PID参数、限幅值、采样周期放在一个头文件里方便修改。调试接口预留串口调参接口可以在运行时修改PID参数不用重新烧录。注释清晰每个函数、每个关键变量都要有注释特别是PID参数的整定过程和依据。我在实际项目中踩过最大的坑是调试时改了参数忘了保存重新烧录后参数又变回默认值导致调试结果无法复现。后来我把所有参数都放在Flash的固定地址上电时读取调试时通过串口写入问题解决。这个项目后续还可以扩展加入CAN总线通信实现多电机同步控制加入OTA升级功能方便远程更新固件加入故障诊断和记录功能方便分析现场问题。这些扩展都需要在现有架构上做增量开发三环PID的核心逻辑不需要大改。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑