☰
STM32F103三环PID控制直流有刷电机实战:从硬件选型到参数整定
2026/9/29 19:27:52 网站建设 项目流程

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,位置环跑在1kHz,CPU占用率不到30%。
  • 生态成熟:F103的教程、例程、社区资源极其丰富,遇到问题容易找到参考。标准库和HAL库都有大量现成代码可以借鉴。
  • 外设够用:需要的高级定时器(TIM1)生成互补PWM带死区,需要ADC采集电流,需要编码器接口读位置和速度,需要USART做调试输出,F103全都有。

当然,如果你的应用需要更复杂的算法(比如无感FOC、自适应控制),或者需要更高的PWM频率(比如20kHz以上),那F103可能就不够了,得考虑F4或G4系列。但对于有刷电机的三环PID,F103是性价比最高的选择。

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/PA7(TIM3_CH1/CH2)。所以引脚分配一定要提前规划好,避免外设冲突。

提示:STM32的引脚复用功能很多,分配时先列出所有需要的外设,再查数据手册的复用表,确保没有冲突。特别是ADC和定时器通道,经常打架。

3.2 H桥驱动电路的设计要点

H桥驱动是直流有刷电机控制的核心。我用的是DRV8871,它内部集成了H桥MOSFET、电流检测、过流保护、过热保护,外围电路简单,只需要几个电容和电阻。但有几个细节要注意:

  • PWM频率选择:DRV8871支持最高100kHz的PWM频率。我选的是20kHz,高于人耳听觉范围,避免电机发出啸叫。同时20kHz对于F103的72MHz主频来说,ARR值可以设得比较合理(72000000/20000=3600),分辨率足够。
  • 死区时间: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。

我最终的电流环参数:Kp=0.8,Ki=0.1,Kd=0。电流环的响应时间约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太大会导致速度抖动。

我最终的速度环参数:Kp=2.0,Ki=0.05,Kd=0.01。速度环的响应时间约10ms,超调小于5%。

抗扰设计:速度环的输出(电流指令)要限幅,防止电流过大烧毁电机。我设置的电流限幅是±1.5A,对应电机额定电流的75%。

4.4 位置环的实现与无超调策略

位置环是最外环,输入是目标位置,反馈是编码器累计计数,输出是速度指令。位置环的响应最慢,但精度要求最高。

位置环的PID参数整定:

  • 位置环一般只用P:位置环的积分项容易导致超调,除非有稳态位置误差,否则不加I。
  • D可以加:位置环的D可以抑制超调,但会放大编码器噪声。如果编码器分辨率高,可以加少量D。
  • P从小往大调:逐渐增大P,直到位置响应开始震荡,取临界值的40%作为P。

我最终的位置环参数:Kp=5.0,Ki=0,Kd=0.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画图。

串口配置:波特率115200,8数据位,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位置环一般不加I
PWM无输出定时器配置错误检查TIM1配置使能TIM1和MOE
编码器计数方向反A/B相接反交换A/B相或修改编码器模式
电流采样漂移运放温漂检查运放供电加LC滤波,选低温漂电阻
串口无输出波特率不匹配检查串口配置确认波特率115200

注意:调试时一定要有耐心,三环PID的参数是相互影响的,改一个环的参数可能会影响其他环。建议从内到外逐环调试,每调好一个环再调下一个。

6. 实操心得与进阶优化方向

6.1 参数整定的经验法则

三环PID的参数整定有一些经验法则可以加速调试:

  • 电流环:Kp取电机电阻的倒数乘以PWM周期的倒数,Ki取Kp的1/10。比如电机电阻1Ω,PWM周期100μs,Kp≈10000,实际调试时从100开始试。
  • 速度环:Kp取电流环Kp的1/10,Ki取速度环Kp的1/20。比如电流环Kp=0.8,速度环Kp≈0.08,实际调试时从0.5开始试。
  • 位置环:Kp取速度环Kp的1/5,Ki一般不加。比如速度环Kp=2.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的核心逻辑不需要大改。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询