ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

STM32直流有刷电机三闭环控制:位置环速度环电流环的级联PID实现

STM32直流有刷电机三闭环控制:位置环速度环电流环的级联PID实现 简介面向嵌入式开发者与电机控制工程师的STM32F4直流有刷电机三闭环控制源码包聚焦位置环、速度环、电流环的级联设计与位置式PID实现采用C语言和HAL库编写可直接在STM32F4平台运行或移植适合正在学习电机控制或需要工程参考的进阶用户。压缩包内共有555个文件以h头文件、c源文件为主辅以o目标文件、s启动文件、icf链接配置文件等整体约20.51MB目录结构清晰、工程链完整。已有401人学习下载。源码涵盖外设初始化、PID控制模块、三闭环控制逻辑、编码器中断处理等关键代码并涉及GPIO、定时器、ADC等配置思路对理解电流采样、速度反馈与位置跟踪的级联控制很有帮助。通过阅读和调试该工程可掌握PID参数整定方法并在此基础上进一步扩展过流保护、过热保护等实用功能。1. 三闭环控制先拆开看位置环、速度环、电流环谁在管什么事直流有刷电机空载转得好好的一加负载转速就掉、位置就偏调一个单环PID怎么调都别扭——这是很多人从单环迈向三闭环的第一个痛点。标题里的「STM32-F4 直流有刷电机-位置环速度环电流环 三闭环控制」本质上就是把三个独立的PID级联起来内环管电流也就是电磁转矩中间环管速度外环管最终位置每一级都在给下一级当执行器。这套架构跑起来电机才算摸到伺服控制的门槛。下面用C语言和HAL库把它拆开讲适合刚把F4跑起来想做高性能位置控制的开发者也适合想系统理解级联PID控制闭环关系的读者。2. 级联PID闭环结构为什么内环要快、外环要慢位置式公式怎么落2.1 三个环路的控制对象、反馈元件与执行周期电流环的控制对象是电枢回路反馈来自电流采样采样电阻或电流传感器经运放放大后进ADC。它要抵消反电动势和负载扰动对电磁转矩的冲击让实际电流快速跟随目标电流。速度环的控制对象是电机加负载的机械系统反馈来自编码器STM32的定时器编码器模式直接读A/B相脉冲换算成转速。位置环反馈同样来自编码器但用的是累计脉冲数而不是频率。三环是串联结构位置环输出目标速度速度环输出目标电流电流环输出PWM占空比。这种结构就是常说的级联PID控制它对时间尺度有一个硬约束内环必须比外环快。电流环执行周期一般在1kHz到20kHz之间速度环取1kHz以内的时基足够位置环几十到几百Hz就够。原因很简单位置环给一个速度指令速度环要在自己的周期内尽量追上电流环又要在更短周期内把电磁转矩顶上这个时间差是级联稳定性的基础。如果在同一个频率跑三个环外环每拍都在改目标内环永远追不上系统表现出来的就是慢性的自激振荡。2.2 位置式PID离散公式与增量式的取舍位置式PID的离散公式是u(k) Kpe(k) Ki∑e(n) Kd*[e(k) - e(k-1)]u(k)是当前拍的控制器输出在电流环里对应PWM比较寄存器值在速度环里对应目标电流在位置环里对应目标速度。三个环路语义不同但PID计算函数完全可以复用只需要把输出限幅和积分限幅改成各自的量纲。位置式PID与增量式PID算法的本质差别在于增量式只输出变化量天然没有积分累积项主控复位或切换模式时冲击小但被限幅卡住时误差会一直累积出现积分饱和位置式直接累加误差积分饱和问题必须靠限幅和抗积分饱和逻辑处理。写代码时我习惯在结构体里同时预留积分累积量、上次误差、输出上限、积分上限把限幅当成PID控制器的一部分而不是留给调用方做。这也是标题点名位置式的直接原因。表格 2-1三环路的反馈信号与PID输出语义环路反馈来源PID输入PID输出典型执行周期电流环ADC采样电枢电流目标电流与实测电流偏差PWM占空比100µs10kHz速度环编码器脉冲频率目标速度与实测速度偏差目标电流1ms1kHz位置环编码器累计脉冲目标位置与实际位置偏差目标速度520ms50200Hz周期取值不是拍脑袋定的。电流环频率受ADC采样时间和PWM定时器影响F4主频168MHz时开到10kHz没有问题速度环更新周期要能覆盖机械谐振频率电流环带宽则必须比速度环高一个数量级否则外环命令电流、内环还在振荡三个环就互相打架。2.3 HAL库写PID的性能边界有5年以上工程经验的人会问HAL库函数调用有开销放在10kHz电流环里行不行我的结论是PID运算放在中断回调里、查询式API换成中断和DMA驱动的接口10kHz完全撑得住。F4内核做整数乘加只要几个周期真正的开销在ADC转换和PWM寄存器更新这两处HAL库都有对应的事件回调比如HAL_ADC_ConvCpltCallback、HAL_TIM_PWM_PulseFinishedCallback把PID塞进回调主循环只跑状态机性能不吃亏。想压到50kHz以上才需要寄存器直操和专用定时器那是另一个话题。3. 用STM32CubeMX生成最小工程PWM通道、编码器模式、ADC采样的配置要点3.1 外设选择与CubeMX里的关键设置F4做有刷电机三闭环外设需求是固定的一路高级定时器TIM1或TIM8输出PWM驱动H桥一路普通定时器开编码器模式接电机编码器的A/B相一路ADC采电流采样电阻加运放或电流传感器再加一个串口打印调试。TIM1的PWM用CH1和CH1N互补输出由死区电路保证上下管不同时导通CH2、CH3留作普通IO或空闲。CubeMX里最容易配错的是编码器模式。TIM4的Encoder Mode选「TI1 and TI2」也就是四倍频位置分辨率最高还要把计数器设为循环模式overflow自动回绕这样正反转越过零点时不会把计数方向搞错。PWM的Pulse初值必须设成0否则上电瞬间电机就会全速转。电流采样如果想让ADC在PWM周期的中点触发可以在TIM1的TRGO上配置ADC触发源这样采到的电流能避开开关边沿毛刺这是H桥电流采样的通用做法。3.2 外设启动顺序与HAL库核心APICubeMX生成代码后在main函数里补充外设启动。下面这段是基于HAL库的启动骨架我一般把采样和编码器先启动最后才放开PWM输出uint16_t adc_buf[8]; /* DMA循环缓冲区 */ volatile float i_phase 0.0f; /* 实测电流在ADC回调更新 */ int main(void) { HAL_Init(); SystemClock_Config(); MX_GPIO_Init(); MX_TIM1_Init(); /* PWM输出 */ MX_TIM4_Init(); /* 编码器 */ MX_ADC1_Init(); /* 电流采样 */ MX_USART2_Init(); /* 调试串口 */ /* 启动顺序先反馈后驱动 */ HAL_TIM_Encoder_Start(htim4, TIM_CHANNEL_ALL); HAL_ADC_Start_DMA(hadc1, (uint32_t *)adc_buf, 8); HAL_TIM_PWM_Start(htim1, TIM_CHANNEL_1); HAL_TIMEx_PWMN_Start(htim1, TIM_CHANNEL_1); while (1) { /* 速度环和位置环在这里跑 */ } }HAL_TIM_Encoder_Start只管让编码器定时器开始计数方向由A/B相相位关系由硬件决定软件不需要判断。要得到速度和位置需要周期性读取TIM4-CNT与上次读到的值做差就是速度把差值累加就是位置。HAL_TIMEx_PWMN_Start启动的是互补通道H桥下管靠它驱动漏掉这一步电机只会上管导通绕组根本不吃电。死区参数在CubeMX的Break and Dead Time配置页里设置。死区设太大占空比有效范围变小设太小开关瞬间上下管直通烧管。先在功率管数据手册找建议值上电后用示波器看Vgs波形确认两路PWM之间有明确间隔后再往上加电流。3.3 ADC采样零偏标定与滤波相位电流反馈是电流环的输入进PID前先做两件事。标零偏电机静止时连续采100次求平均记为offset_zero实测电流等于(adc_value - offset_zero)乘以系数scalescale由采样电阻阻值和运放增益决定。第二件是检查反馈方向给一个固定占空比电流随占空比增加而增加才是正确的。方向反了PID会直接朝饱和方向冲表现是Kp越大越振荡而且换向永远修不回来。滤波上按需选ADC配合DMA连续采集多个点做滑动平均即可但要注意滤波器相位滞后。负载惯量越大滞后越危险速度环容易因此高频抖动。我一般只做一阶低通截止频率至少比电流环带宽高5倍优先保电流环的实时性。4. 位置式PID的C语言实现与三环调用顺序结构体、限幅、抗积分饱和4.1 可复用的PID结构体与核心计算函数位置式PID控制器不关心被控对象一套结构体能同时跑三个环。下面是工程里常用的结构体定义每个字段都有明确量纲含义typedef struct { float Kp; /* 比例系数 */ float Ki; /* 积分系数 */ float Kd; /* 微分系数 */ float ref; /* 目标值 */ float fbk; /* 反馈值 */ float err; /* 当前误差 */ float err_last; /* 上一拍误差 */ float integral; /* 积分累积项 */ float out; /* 控制器输出 */ float out_max; /* 输出限幅 */ float int_max; /* 积分限幅 */ float d_filter; /* 微分低通系数 0~1 */ float d_term_last; /* 上次微分项 */ } PID_TypeDef;核心函数就是标准位置式公式套上积分限幅和输出限幅float PID_Calc(PID_TypeDef *pid, float ref, float fbk) { pid-ref ref; pid-fbk fbk; pid-err ref - fbk; /* 积分累积单独限幅防止积分饱和 */ pid-integral pid-Ki * pid-err; if (pid-integral pid-int_max) pid-integral pid-int_max; if (pid-integral -pid-int_max) pid-integral -pid-int_max; /* 微分项带一阶低通抑制编码器和ADC的高频噪声 */ float d_raw pid-Kd * (pid-err - pid-err_last); float d_term d_raw * pid-d_filter pid-d_term_last * (1.0f - pid-d_filter); pid-out pid-Kp * pid-err pid-integral d_term; if (pid-out pid-out_max) pid-out pid-out_max; if (pid-out -pid-out_max) pid-out -pid-out_max; pid-err_last pid-err; pid-d_term_last d_term; return pid-out; }有三处和教科书公式不一样。第一积分限幅int_max单独存在避免误差大时积分项先冲到饱和导致反向调整时还要等积分慢慢退回来。位置环误差100个脉冲积分按Ki每拍加0.1不加int_max它会一直涨到输出被out_max卡住最终表现就是电机停在半路要好几秒才慢慢回正。第二微分项带低通d_filter取0.8左右能明显减小低速时的电流高频抖动代价是一点点相位滞后。第三输出限幅做成对称值电机要正反转单向限幅会让换向动态变差。4.2 三环的调用节奏中断、定时器、主循环怎么分工级联PID控制的关键不在PID公式在三环的调用频率。我的做法是电流环放在ADC转换完成中断里由PWM定时器触发ADC10kHz左右更新速度环放在1ms定时器中断里读编码器增量、换算转速、算速度PID位置环放到主循环或10ms定时器读累计脉冲、算位置PID。volatile float current_ref 0.0f; /* 速度环输出的电流目标 */ volatile float speed_ref 0.0f; /* 位置环输出的速度目标 */ volatile int32_t enc_cnt 0; /* 编码器累计位置 */ /* ADC转换完成回调电流环约10kHz */ void HAL_ADC_ConvCpltCallback(ADC_HandleTypeDef *hadc) { uint16_t raw adc_buf[last_index]; /* 当前拍采样值last_index由DMA半满/全满中断维护 */ float i_fbk (float)raw * current_scale; /* 标定后的电流 */ uint32_t pwm (uint32_t)PID_Calc(pid_current, current_ref, i_fbk); __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, pwm); } /* 1ms定时器中断速度环 */ void Speed_ISR(void) { int32_t cnt (int32_t)TIM4-CNT; int32_t delta cnt - last_cnt; last_cnt cnt; enc_cnt delta; /* 位置就是脉冲累加 */ /* 四倍频后每转PPR*4个脉冲1ms采样一次 rpm 脉冲增量 * 1000(ms转s) * 60(s转min) / (PPR*4) */ float rpm (float)delta * 60000.0f / (float)(PPR * 4); current_ref PID_Calc(pid_speed, speed_ref, rpm); if (current_ref MAX_CURRENT) current_ref MAX_CURRENT; if (current_ref -MAX_CURRENT) current_ref -MAX_CURRENT; } /* 主循环或10ms任务位置环 */ void Position_Update(void) { speed_ref PID_Calc(pid_position, pos_ref, enc_cnt); if (speed_ref MAX_SPEED_RPM) speed_ref MAX_SPEED_RPM; if (speed_ref -MAX_SPEED_RPM) speed_ref -MAX_SPEED_RPM; }三个关键点。第一last_index要和DMA循环采样的缓冲区下标对齐工程上常用DMA半满/全满中断做ping-pong切换不要在回调里读正在采样的那个字。第二速度环公式里的60000.0f来自三处换算编码器模式四倍频后每转计数PPR*4中断周期1ms要乘1000变成秒再乘60变成每分钟转速方向则由delta的正负自动确定。第三位置环输出必须限幅在电机额定速度以内否则位置误差大时速度环会得到一个大到电流环跟不上的目标速度系统在极限状态来回冲。环间带宽的经验比例是速度环带宽约为电流环的1/5到1/10位置环比速度环再低3到5倍。这个比例不是硬约束但调试顺序是死的先把电流环比稳再闭合速度环最后才碰位置环跳步会让故障的归属变得模糊。4.3 位置式的常见误用积分饱和、微分冲击与符号翻转三闭环项目最常翻车在热身阶段。电机静止时位置误差很大积分项先冲满输出一直是out_max电机全电流往前冲过冲后又要等积分排空才能反向这就是积分饱和的经典表现也是压力调节PID参数经验值里大家反复提的一点。抗饱和除了int_max还有个容易遗漏的边界输出被out_max限幅时积分项不该继续累积。严格的实现要在限幅分支里冻结积分我工程里直接把int_max设得比out_max对应的小保证输出到顶时积分还有余量简单且现场好调。微分冲击和启动方式相关。Ramp斜坡没做时位置环目标从0直接跳到1000第一拍误差突变微分项输出一个巨大的负值电机会猛打方向。d_filter低通能压住一部分配合目标值斜坡发生器才能根治。符号翻转是最隐蔽的一类编码器方向接反或电流反馈极性反了PID无论怎么调都自激振荡判据是把反馈线反接振荡消失就说明是方向问题而不是参数问题。5. 三闭环参数整定顺序与波形判读先内环后外环临界比例法落到代码5.1 电流环整定只调Kp找临界振荡再加Ki消静差电流环被控对象是电枢回路电气时间常数远小于机械时间常数响应很快。整定电流环时先把速度环和位置环旁路速度PID输出直接给固定值电流环的Ki和Kd置0只留下Kp从0开始慢慢加大。用调试器或串口持续观察实测电流波形直到电流出现等幅振荡记下这一拍的Kp再乘0.6作为实际Kp这就是临界比例法也是Ziegler-Nichols法的工程简化版。之后加Ki消除静差静差的表现是给定100mA稳定在95mAKi从小到大加加到稳态误差消失且不出现低频慢漂为止。电流环Ki过大时PWM占空比会周期性摆动电磁转矩在反复横扫机械系统跟着抖。如果电流波形不是贴紧目标值的平滑线而是毛刺很多的锯齿优先查ADC采样窗口和滤波截止频率不急着加Kp。5.2 速度环整定位置环开路观察阶跃响应的过冲与收敛速度环整定要先把位置环的输出断开直接给速度环一个恒定的目标转速。速度环Kp、Ki从零开始调整方法同样是临界比例法。速度环反馈来自编码器低速时脉冲间隔长速度计算有固有滞后Kp开太大会在低速段出现「咔哒咔哒」的抖动。经验参数的起步值可以这样估Kp的量纲是电流每rpm从允许最大电流除以额定转速的1/10起跳比如允许10A、额定3000rpmKp就从0.033往上调。加Ki观察恒速下的稳态误差带载后转速静差最明显。合格的阶跃响应是转速快速爬升出现10%30%过冲1秒内收敛没有持续振荡。转速在目标附近缓慢蠕动说明积分项和负载扰动在拉锯把Ki减半再观察。表格 5-1速度环阶跃响应波形判读现象原因调整方向过冲大、回摆两次以上Kp偏大或积分相位滞后Kp减半Ki适当降低稳态偏差长期不消失Ki偏小Ki增加50%观察5秒低速区间抖动速度环周期过长或Kp过大缩短速度环周期Kp减到抖动消失带载转速跌落明显电流环带宽不够回到电流环加大Kp确认内环先快起来5.3 位置环整定配合Ramp斜坡调过冲最后处理静止保持位置环反馈是编码器累计脉冲没有速度环那种量化噪声Kp和Ki可以开得激进但目标值常是大阶跃必须配合Ramp斜坡发生器一起调。先给一个小阶跃比如转5圈观察运动曲线过冲情况和是否在目标点反复穿越。位置环Ki不能大积分累积会让电机到达目标后继续顶着表现是目标位置附近持续小幅震荡电机发出「嗡嗡」声。三环全闭合后完整运动过程应该是加速段电流环快速响应、速度环平稳爬升接近目标时速度环平滑减速位置环用很小的积分项把位置钉在目标点。静止时电流环会持续给一个很小的占空比抵消摩擦力和重力这是正常的如果看到电流恒定在反向饱和值附近弹不回来说明位置环积分项或误差死区设置有问题要去查这两处。6. 三闭环的启动时序、堵转保护与Ramp斜坡最后三道保险6.1 使能顺序反了会烧H桥三闭环的使能顺序不能随便写。ADC还没完成第一次转换就启动PWM电流环会用残留值输出一个随机占空比此时H桥上下管一旦直通烧的是功率管。我的固定顺序是先启动编码器和ADC等50ms确认位置和电流反馈有效再启动PWM主通道和互补通道最后置速度环和位置环的使能标记。反过来的顺序在调试器里看不出来只有上电瞬间的电流尖峰能说明问题。6.2 堵转检测的两个条件缺一不可堵转是位置控制里最容易被忽略的保护场景位置环还在让电机往目标走机械结构被卡住速度环持续输出满电流命令绕组发热冒烟。检测逻辑用两个条件同时成立实测电流持续超过额定电流60%且编码器速度持续低于阈值的时长超过500ms。此时清零所有环路输出并锁存故障标志只有手动复位才能重新使能。不要只用速度为零一个条件判断堵转启动瞬间速度本来就为零误触发放飞了整个机构的复位流程。#define I_OVERLOAD_LEVEL 0.6f /* 额定电流的60% */ #define SPEED_STALL_RPM 50.0f /* 堵转速度阈值 */ void Fault_Check(void) /* 在1ms速度环中断里调用 */ { static uint32_t stall_time 0; if (fabsf(i_phase) I_OVERLOAD_LEVEL fabsf(rpm) SPEED_STALL_RPM) { if (stall_time 500) /* 500ms 去抖 */ { __HAL_TIM_SET_COMPARE(htim1, TIM_CHANNEL_1, 0); fault_flag 1; } } else { stall_time 0; } }这块逻辑放在速度环ISR里调用。最容易漏的是fabsf反向堵转时电流和速度都是负的丢了绝对值函数反向堵转永远检测不到。6.3 Ramp斜坡给速度环一个梯形的目标位置环输出直接当速度环目标从0突变到3000rpm会让速度环瞬间饱和电流环跟着打满机械冲击很大。我习惯在位置环输出和速度环输入之间插一段斜坡限幅逻辑把目标速度的变化率限制在可配置范围比如500rpm/100ms。实现就是一个增量限幅函数每拍往目标方向最多移动max_step放在位置环更新之后、速度环读取目标之前。这个斜坡同时解决了位置环大阶跃时的微分冲击和堵转保护、启动顺序放在一起正好覆盖了三闭环项目最容易出问题的三个时间点上电时、卡死时、换向时。把这一整套配置和代码放进F4工程再回头看增量式PID算法、寄存器直操和FOC就有了对照的坐标系。本文还有配套的精品资源点击获取
返回列表