拓冰建站拓冰建站
首页 / 资讯中心 / 正文

STM32直流有刷电机PID速度环:从接线到调参完整指南

简介面向STM32直流电机调速开发者的完整工程包以L298N驱动直流有刷电机为基础实现了PID速度环闭环控制可直接导入Keil等IDE进行编译与烧录。包内共657个文件涵盖C源文件、头文件、工程配置文件、编译中间文件以及hex烧录文件等整体大小约18.83MB目录结构清晰便于学习与二次开发。工程代码包含PWM调速信号生成、电机速度反馈检测、PID参数整定、L298N正反转控制及调试接口底层使用标准库与HAL库混合编程适合有一定STM32基础的入门者或需要快速搭建电机控制平台的开发者也适用于嵌入式课程设计与毕业设计参考。已有1024人学习下载对于深入理解电机控制原理、掌握PID算法实际调参过程具有直接参考价值。1. 为什么这些项目总在“调PID”这一步卡住做 STM32 控制直流有刷电机的毕业设计或比赛小车硬件半天就能搭起来一个 L298N、一个编码器电机、几根杜邦线。真正让人熬夜的永远是速度环——电机要么嗡嗡叫、要么转起来忽快忽慢、要么一上电就疯转。问题通常不出在“PID 没学懂”而是出在 PWM 频率、编码器采样节拍、积分限幅这些代码里看不见的细节上。这一篇把 STM32 L298N 直流有刷电机 PID 速度环从接线到调参的完整链路写清楚先讲速度环为什么非做不可再给一套可直接改用的工程代码骨架最后用串口和曲线把参数一个个调出来。适合正准备做 STM32 项目、手上有 L298N 和带霍尔编码器的直流减速电机的人也适合给只知道“PID 三个数”的队友一份能照着操作的检查清单。2. 直流有刷电机的速度环PWM 只是开环PID 才闭环2.1 有刷电机模型和“速度环”到底是什么直流有刷电机的等效模型是一阶惯性加积分电枢电压变化时电流建立转矩转矩让转子加速反电动势又抵消输入电压最终转速稳定在一个平衡点。开环给 50% 占空比电机转速不等于空载转速的一半——负载一变、电源电压一跌、温度一升高转速就跟着掉。速度环做的事情很简单把编码器采到的实际转速和目标转速做差用 PID 算出一个 PWM 占空比增量形成一个负反馈回路。速度环和电流环的区别值得先说明白电流环响应快带宽做到几百赫兹管的是转矩的瞬态速度环带宽通常只有几十赫兹管的是转速平均值。L298N 没有电流采样引脚所以这个项目里只能做单速度环把电流环欠掉的账记在 PID 参数头上——这才是后面调参时 Kp 不敢给太大的根本原因。2.2 位置式 PID 与增量式 PID裸机工程里我选增量式位置式 PID 的表达式是u(k) Kp * e(k) Ki * Σe(i) Kd * (e(k) - e(k-1))输出 u(k) 直接作为 PWM 占空比。它的问题在于积分项 Σe(i) 需要长期保存一旦电机堵转、编码器丢脉冲积分饱和后要好几秒才能退出来表现就是“电机转疯了停下来还要转一会儿”。增量式 PID 不做积分累加而是输出每一次控制动作的增量 Δu(k)Δu(k) Kp * (e(k) - e(k-1)) Ki * e(k) Kd * (e(k) - 2*e(k-1) e(k-2))实际给到 PWM 的是u(k) u(k-1) Δu(k)增量式天然带记忆u(k-1) 就是上一拍的占空比不显式保存积分项想要手动清积分或切换目标转速时直接把 u(k-1) 清零即可。另外增量式天然适合 STM32 的定时器 PWM 输出——只需对 CCR 寄存器做加减不必担心一个巨大的绝对占空比把系统顶到饱和区。这是纯裸机环境下我优先选增量式的理由位置式更多配合上位机做离线仿真用。2.3 采样节拍PID 不是跑得越快越好速度环的 PID 计算和 PWM 输出是两个不同的节拍。常规做法是 PWM 用定时器硬件生成比如 10kHz 到 20kHz而 PID 用固定频率的定时器中断驱动通常取 100Hz 到 1kHz。采样频率和电机机电时间常数要匹配直流减速电机的截止频率一般在 10-50Hz 区间PID 跑到 1kHz 以上并不是浪费但前提是编码器测速要跟得上。编码器测速有两种常见方法M 法测速和 T 法测速。M 法是在固定时间窗口内数编码器脉冲个数高速时分辨率高T 法测两个脉冲之间的时间间隔低速时分辨率高。做速度环时我常用 M 法定时器每 5ms 或 10ms 主动读一次编码器计数然后清零重数。测速周期要放在 PID 运算里考虑——把测速和 PID 都放进同一个定时器中断的同一拍否则采样时序乱掉调出来的参数永远不对。3. STM32 与 L298N 的接线要点电源、PWM 频率、共地和编码器反馈3.1 L298N 引脚逻辑与最小接线图L298N 的使能端ENA/ENB接 PWM输入引脚IN1-IN4接普通 GPIO。逻辑关系固定为IN11、IN20 时电机正转反过来反转ENA 为 0 时无论 IN 状态如何电机都停止。用定时器通道输出 PWM 时把这个关系做成一张表会安全很多IN1IN2ENA(PWM)电机状态100停止1050%正转 50% 占空比0150%反转 50% 占空比11x刹车不建议长期使用L298N 的 IN1/IN2 接普通 GPIOENA 必须接 STM32 的定时器 PWM 输出引脚这个引脚需要有 TIMx_CHx 复用功能。注意 IN 引脚的电平翻转要发生在 PWM 占空比的边界上不要在 PWM 为高电平期间切换方向否则 H 桥直通短路轻则发热重则烧片。代码实现上至少先停 PWM、再切 GPIO、最后重新开 PWM。3.2 双电源策略为什么 12V 电机电源不能碰 3.3V 逻辑电L298N 模块上通常有两个电源端子VMS电机电源和 VSS逻辑电源。常见错误是只接一个电源用板载 5V 稳压输出给 STM32 供电——这在小电流空载测试时能跑但电机堵转时母线电压跌落STM32 直接复位或 flash 写入错乱。我一般的做法是双电源隔离电机侧使用 9-12V 电源给 L298N 的 VMS逻辑侧使用 STM32 板载 3.3VL298N 的 VSS 也接 3.3V模块逻辑电路经过电平转换后可以接受 3.3V 输入实测稳定若你的模块明确要求 5V 逻辑则用 STM32 板载 5V 引脚供电但必须在 VMS 端并联大电容。双电源的核心问题是共地GND 必须全部接在一起否则 IN 引脚的电平参考地和 L298N 的逻辑地不一致电机偶发抖动但无法形成稳定 PWM 控制。另外在 VMS 两端并联一个 470uF 电解电容和一个 104 陶瓷电容这是成本最低的防抖手段。3.3 编码器接口定时器编码器模式还是外部中断计数带霍尔编码器的直流减速电机通常输出 A、B 两相互差 90° 的方波。判断方向和测速有两类做法做法一是把 A、B 相接到 STM32 定时器的 CH1/CH2启用编码器模式比如 TIM4_Encoder_Mode硬件自动根据相位关系加减计数。这是最推荐的方式不占用 CPU计数分辨率高方向自动判。做法二是把单相只用 A 相接到 GPIO 外部中断上升沿计数。这能测速但无法判断方向且中断频率高时 CPU 开销大。从标题的“调式 PID 速度环”目标出发必须用编码器模式。中断里做太多次数统计会让 PID 中断节拍被干扰——这是新手最常见的隐性坑。下面是编码器模式的定时器初始化和读取代码注意编码器模式不算 PWM 输出需要单独用一个定时器/* 使用 TIM4 的编码器模式读取电机 AB 相 */ void Encoder_TIM4_Init(void) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_ICInitTypeDef TIM_ICInitStructure; RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE); RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_Init(GPIOB, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 0xFFFF; TIM_TimeBaseStructure.TIM_Prescaler 0; TIM_TimeBaseStructure.TIM_ClockDivision TIM_CKD_DIV1; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM4, TIM_TimeBaseStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_1; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM4, TIM_ICInitStructure); TIM_ICInitStructure.TIM_Channel TIM_Channel_2; TIM_ICInitStructure.TIM_ICPolarity TIM_ICPolarity_Rising; TIM_ICInitStructure.TIM_ICSelection TIM_ICSelection_DirectTI; TIM_ICInitStructure.TIM_ICPrescaler TIM_ICPSC_DIV1; TIM_ICInitStructure.TIM_ICFilter 0x0F; TIM_ICInit(TIM4, TIM_ICInitStructure); TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); TIM_SetCounter(TIM4, 0); TIM_Cmd(TIM4, ENABLE); } /* 读取编码器计数并清零 */ int16_t Encoder_Get_Count(void) { int16_t count (int16_t)TIM_GetCounter(TIM4); TIM_SetCounter(TIM4, 0); return count; }配置要点有几个TIM_Period 设成 0xFFFF 是为了让 16 位计数器不用频繁处理溢出TIM_EncoderMode_TI12 表示双沿采样四倍频计数对减速比 30:1、单圈 13 线的电机来说每圈计数是 13430 1560TIM_ICFilter 加到 0x0F 是给编码器信号做数字滤波防止抖动造成误计数。返回的 count 是 int16_t因为编码器计数可能为负反转用无符号类型会导致方向判断失效。编码器计数溢出时可能出现跨正负边界的问题这里用 int16_t 强制转换如果采样周期内计数不超过 32767 就是安全的一般 5ms 采样周期内不会出现。4. 速度环代码骨架定时器测速、PID 运算与 PWM 输出的完整实现4.1 定时器分配与 PWM 初始化在 STM32F1 系列中常见做法是 TIM1 或 TIM2 输出 PWM 给 L298N 的 ENATIM4 做编码器计数。注意定时器的通道引脚不能冲突以 stm32f103c8t6 为例TIM1 的 CH1 在 PA8CH2 在 PA9TIM2 的 CH1 在 PA0CH2 在 PA1。L298N 还需要两个普通 GPIO 做 IN1、IN2 方向控制TIM2 的 PA0/PA1 上方刚好可以做事方便布局。PWM 频率选 10kHz 比较合适。F103 的定时器时钟是 72MHzPSC 设为 71ARR 设为 99频率就是 72000000 / (72 * 100) 10kHz。这个频率下有刷电机的电流纹波较小L298N 的开关损耗也不会太大。频率低于 5kHz 时电机会产生明显噪声高于 20kHz 后 L298N 的驱动芯片开关损耗上升容易发热。/* TIM2 输出 PWM 控制 L298N ENAPA0 为 CH1 */ void PWM_TIM2_Init(u16 arr, u16 psc) { GPIO_InitTypeDef GPIO_InitStructure; TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA | RCC_APB2Periph_AFIO, ENABLE); RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); GPIO_InitStructure.GPIO_Pin GPIO_Pin_0; GPIO_InitStructure.GPIO_Mode GPIO_Mode_AF_PP; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOA, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period arr - 1; TIM_TimeBaseStructure.TIM_Prescaler psc - 1; TIM_TimeBaseStructure.TIM_ClockDivision 0; TIM_TimeBaseStructure.TIM_CounterMode TIM_CounterMode_Up; TIM_TimeBaseInit(TIM2, TIM_TimeBaseStructure); TIM_OCInitStructure.TIM_OCMode TIM_OCMode_PWM1; TIM_OCInitStructure.TIM_OutputState TIM_OutputState_Enable; TIM_OCInitStructure.TIM_Pulse 0; TIM_OCInitStructure.TIM_OCPolarity TIM_OCPolarity_High; TIM_OC1Init(TIM2, TIM_OCInitStructure); TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable); TIM_Cmd(TIM2, ENABLE); }调用时 arr100、psc72这样定时器计数 0~99共 100 个节拍每个节拍 1usPWM 周期 100us即 10kHz。需要调整 PWM 占空比时调用TIM_SetCompare1(TIM2, duty)duty 范围 0~99。这里使用 PWM1 模式当 CNT CCR 时输出高电平CCR 从 0 逐渐增大到 99占空比从 0% 逐渐到接近 100%。4.2 增量式 PID 的裸机 C 实现PID 运算要在固定时间节拍里执行通常用一个基础定时器比如 TIM6每 5ms 触发一次更新中断。在这个中断里完成编码器读数、速度计算、PID 运算、限幅和 PWM 更新。写成函数便于在不同工程里复用参数用结构体封装/* 增量式 PID 控制器结构体 */ typedef struct { float Kp; /* 比例系数 */ float Ki; /* 积分系数 */ float Kd; /* 微分系数 */ float target_speed; /* 目标转速单位 rpm */ float current_speed; /* 当前实际转速 */ float error_last; /* 上一次误差 */ float error_before; /* 上上次误差 */ float pid_out; /* PID 输出占空比增量 */ float pid_out_last; /* 上一次 PID 总输出 */ float out_max; /* 输出限幅0~100 */ float out_min; /* 输出下限 */ } IncrementalPID; /* 增量式 PID 核心运算 */ float IncrementalPID_Calc(IncrementalPID *pid, float target, float current) { float error, delta_out, out; error target - current; delta_out pid-Kp * (error - pid-error_last) pid-Ki * error pid-Kd * (error - 2.0f * pid-error_last pid-error_before); pid-error_before pid-error_last; pid-error_last error; out pid-pid_out_last delta_out; /* 输出限幅防止 PWM 占空比越界 */ if (out pid-out_max) out pid-out_max; else if (out pid-out_min) out pid-out_min; pid-pid_out_last out; pid-pid_out out; return out; }这段代码里几个参数要注意Kp 和 Ki 的量纲不一致。Kp 是“每 rpm 误差对应的占空比百分比”Ki 是“每个采样周期对误差累积的占空比百分比”。如果目标转速从 0 突加到 1000rpmKp0.1 时首拍输出就是 10% 占空比感觉温和Kp1 时首拍就是 100%电机会瞬间打满。Kd 项用的是二阶差分作用是抑制误差变化率。编码器测速波动大时Kd 会给 PWM 叠加高频抖动所以裸机速度环里 Kd 通常给得极小甚至为 0先把 PI 调好再加 D。输出限幅不是只限制最终占空比还隐含限制了积分深度。由于增量式不做显式积分累加所以这里的 out 限幅就够了不需要单独的积分限幅。代码中的 pid_out_last 保存了上一拍的输出使系统在目标突变或负载突变时能平滑过渡。这个函数本身没有方向信息。当需要反转时把目标转速设为负值即可。电机方向由 IN1、IN2 决定与 PID 输出的正负无关。PWM 输出的是绝对值方向由 GPIO 控制所以还要在调用处做一次符号分离void Motor_Set_Speed(float speed) { if (speed 0) { GPIO_SetBits(GPIOB, GPIO_Pin_12); /* IN1 1 */ GPIO_ResetBits(GPIOB, GPIO_Pin_13); /* IN2 0 */ TIM_SetCompare1(TIM2, (u16)(speed 99 ? 99 : speed)); } else if (speed 0) { GPIO_ResetBits(GPIOB, GPIO_Pin_12); /* IN1 0 */ GPIO_SetBits(GPIOB, GPIO_Pin_13); /* IN2 1 */ TIM_SetCompare1(TIM2, (u16)(-speed 99 ? 99 : -speed)); } else { /* 目标速度为 0 时直接停止 PWM不要用 IN 引脚刹停 */ TIM_SetCompare1(TIM2, 0); } }很多工程在转速接近 0 时会让 IN1IN21 做刹停L298N 的 datasheet 确实有这种模式但会带来两个问题一是电感和反电动势形成的制动电流直接灌到 H 桥上L298N 发热严重二是刹车解除瞬间的电流冲击会让电源电压产生尖峰。所以速度环输出 0 附近时不要刹停直接让 PWM 为 0让电机自由滑行。如果需要快速停车应在负方向上给一个短时的反向占空比脉冲比如 -20% 持续 50ms。4.3 定时器中断里的完整测速与 PID 执行流程PID 中断函数要做的事情按顺序排列读编码器计数、根据线数和减速比换算转速、调用 PID 计算、设置 PWM占空比。这个顺序不能颠倒因为“先更新测量再计算输出”和“先计算输出再更新测量”都试过后者会让控制系统多一个采样周期的滞后。/* 全局变量目标转速由主循环或串口指令更新 */ volatile int16_t encoder_count; volatile float target_rpm 600.0f; volatile float current_rpm 0.0f; volatile float pid_output 0.0f; IncrementalPID speed_pid; #define PULSE_PER_REV 1560.0f /* 13线 * 4倍频 * 30减速比 */ #define SPEED_SAMPLE_TIME 0.005f /* 5ms 采样周期 */ void TIM6_IRQHandler(void) { if (TIM_GetITStatus(TIM6, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM6, TIM_IT_Update); /* 1. 读取 5ms 内的编码器脉冲数 */ encoder_count Encoder_Get_Count(); /* 2. 换算成转速单位 rpm */ current_rpm (float)encoder_count * 60.0f / (PULSE_PER_REV * SPEED_SAMPLE_TIME); /* 3. PID 计算得到 PWM 占空比百分比 */ pid_output IncrementalPID_Calc(speed_pid, target_rpm, current_rpm); /* 4. 输出到电机 */ Motor_Set_Speed(pid_output); } }转速换算公式的来源不复杂编码器 5ms 内数到 N 个脉冲一秒钟就是 N200 个脉冲一分钟是 N200*60再除以每转脉冲数 PULSE_PER_REV 就是 rpm。这里的 1560 是 13 线编码器、4 倍频、30:1 减速后的数值。如果电机参数不同这个常量要改否则转速显示和 PID 参数全部失真。需要特别提醒一步中断里调用了 Motor_Set_Speed这个函数里有 GPIO 操作和 TIM_SetCompare1如果在 PID 计算前把编码器清零和 PWM 设置放在一个临界区还要考虑中断嵌套问题。不建议在这个中断里加 printf 做调试——串口打印的耗时可能在毫秒级严重拉长中断时间。串口调试应放在主循环里用标志位通知主循环去发送。5. 串口在线调 PID开环自测、临界比例度法与参数限幅5.1 开环自测先确认 PWM 和转速的关系是线性的接上电直接调 PID 是浪费时间的开始必须先做开环测试手动给定一个 PWM 占空比测出稳定转速。分别试 10%、20%、30%、50% 的占空比记录对应的转速值。这里有个严重警告L298N 在占空比低于 10% 左右时电机可能不转因为 H 桥驱动芯片的压降和电机启动转矩问题导致死区。这个死区必须测出来并记下后面 PID 输出限幅时不要让输出长期停留在死区里。常见的做法是写一个临时测试代码主循环里读取串口发来的占空比数字0~100调用 Motor_Set_Speed同时把编码器测得转速通过串口发回上位机。这个测试程序可以直接沿用前面的模块只是 PID 参数全设为 0。开环测试的重点是确认以下几点测试项判定标准方向是否正确给定正占空比编码器计数为正PWM 死区值实测 XX%通常 5-15%转速才稳定起步线性度占空比 20%-80% 区间内转速是否近似线性电源纹波电机高速时 STM32 是否复位或串口乱码方向判断错误是新手第一坑PID 输出为正电机正转但编码器计数为负这样算出的误差就是目标减负值越调越大最终 PWM 打满。如果测试发现方向反了两种解法一种是把编码器 A/B 相对调——硬改硬件最快另一种是软件里给 Encoder_Get_Count 的返回值取负。电源纹波问题的判定标准串口数据乱码、STM32 频繁复位、L298N 上的红色 LED 亮度随转速波动。解决方案已经在第三章说过了VMS 并联大电容、逻辑电平独立供电、共地。如果复位频繁还可以在 STM32 的 VDD 引脚附近加 100nF 去耦电容或者干脆让电机电源远离单片机排线。5.2 用临界比例度法找到 Kp 和 Ki 的起点先给 Kd 和 Ki 设 0只留 Kp。从一个很小的值开始比如 0.05然后在运行状态下逐步增大观察转速响应。临界比例度法的操作是增大 Kp 直到系统产生等幅振荡——转速围绕目标值上下波动且幅度不衰减。这个等幅振荡的 Kp 记为 Kr振荡周期记为 Tr。然后用经验公式算出起始参数Kp 0.45 * KrKi 0.54 * Kr / Tr。这个方法来自 PID 整定的经典经验但要注意的是速度环里 Tr 通常是几百毫秒如果 Ki 算出来太大就减半再试。实际操作时序很重要把目标转速设为电机额定转速的 60% 左右比如 600rpm。先让电机开环转动到接近目标再把 PID 切入闭环。不要从静止直接切闭环——PID 输出会先打满电机猛然起飞容易把目标转速冲过头。切换瞬间的处理是让 pid_out_last 初始化为当前开环 PWM 值这样 PID 从当前工作点开始调整而不是从 0 开始。串口在线调试的格式建议用逗号分列的文本目标转速,实际转速,PID输出,PWM占空比一行一帧。直接用串口助手看数字也能调但配合简单的串口波形工具比如匿名上位机或者 SerialPlot效率会高很多。看到的数据曲线应该长这样转速从目标值下方爬升轻微超调后回落最终稳定在目标转速附近 ±2% 以内。5.3 参数限幅和防积分饱和的裸机处理增量式 PID 虽然没有显式的积分项但 pid_out_last 等效于一个慢速积分器——它记住的是控制量的历史累积。当电机堵转时误差恒定为正PID 输出逐渐爬到限幅值如果此时让电机恢复转动输出需要从限幅值慢慢退回来这个过程本身就等价于积分饱和。处理方式很简单在输出限幅的同时检查是否达到限幅边界if (out pid-out_max || out pid-out_min) { /* 输出饱和冻结 pid_out_last不再让控制量继续累积 */ pid-pid_out_last pid-out_max_target; /* 用一个接近边界的值替代 */ }但上面的写法会让系统在退出饱和时失去“记忆”实际工程中更常见的做法是反算限幅。做法是当计算出的 out 超过限幅时把 delta_out 截断而不是直接截断 out。换句话说如果 out out_max则 out out_max然后反推 pid_out_last out_max——这等价于把超出的增量直接丢弃。由于 pid_out_last 控制下一拍的起点这一步能让系统在脱离饱和时响应更快。限幅值的设置PWM 占空比最大值设为 90% 而不是 100%因为 L298N 在接近全占空比时发热严重而且有刷电机的换向火花也会更剧烈。最小值设为 PWM 死区的实测值通常在 8-15% 之间。这样 PID 输出被限制在工作区间内不会落入非线性死区。6. 三个必调的细节启动防反转、梯形加减速与堵转保护6.1 启动无反转目标值的斜坡发生器直接给一个阶跃目标转速PID 会先输出一个很大的控制量电机转速冲过目标值后PID 又要反向拉回来表现为启动瞬间“咯噔”一下反转或抖动。这个现象的根源是微分环节对阶跃的响应——误差从 0 跳到 600Δe 巨大微分项瞬间爆表。即使 Kd0Ki 项也会在误差大的时候给较大的输出增量导致超调。解决办法不是调参数而是加斜坡发生器目标转速不是直接给 600而是每 10ms 增加 20rpm300ms 后到达目标值。把斜坡发生器放在主循环里或者放在 PID 中断前一级static float ramped_target 0.0f; void Speed_Ramp_Task(void) { static uint32_t last_time 0; uint32_t now HAL_GetTick(); if (now - last_time 10) { if (ramped_target target_rpm) { ramped_target 20.0f; if (ramped_target target_rpm) ramped_target target_rpm; } else if (ramped_target target_rpm) { ramped_target - 20.0f; if (ramped_target target_rpm) ramped_target target_rpm; } pid.target_speed ramped_target; last_time now; } }斜坡的斜率取值比 PID 参数更影响启动质感。斜率太慢显得迟钝太快又回到阶跃的问题。实际调试中从每 10ms 增加 10rpm 起调观察启动时的电流声——没有明显顿挫感就说明斜率合适。如果目标是频繁加减速的应用比如小车走直线斜坡斜率就是加减速的“加速度”这个值要记录到设计文档里。6.2 堵转检测编码器长时间为零时的保护策略PID 速度环有一个天然的危险场景电机堵转。目标转速 600rpm实际转速 0PID 输出限幅在 90%占空比持续最大电机绕组持续通过大电流L298N 的驱动管温度在十几秒内就能升到烫手。堵转检测的逻辑可以直接放在 5ms 中断里也可以放在主循环里逻辑是static uint16_t stall_count 0; /* 每 5ms 执行一次 */ if (encoder_count 0 pwm_output 50) { stall_count; if (stall_count 200) { /* 堵转超过 1 秒停 PWM记录错误码 */ Motor_Set_Speed(0); pid.pid_out_last 0; error_code ERROR_STALL; stall_count 0; } } else { stall_count 0; }这里的判断条件“encoder_count 0 pwm_output 50”能区分堵转和“目标为 0 时正常停止”。阈值 50 表示 PWM 输出已经过半电机仍无脉冲基本可以判定为堵转。堵转后的处理策略取决于应用场景简单做法是直接停车并报错复杂做法是反向转一下再正转尝试。注意堵转后必须清 pid_out_last否则恢复供电时电机会猛地冲一下相当于 PID 从饱和区直接起步冲击电流极大。6.3 速度环调完后的验证清单参数整定完成后验证是否合格可以按下面的清单逐项测试空载启动到 600rpm超调量不应超过 5%即 630rpm调节时间不超过 500ms。在稳定运行中用手轻捏电机轴模拟负载扰动转速跌落不超过 10%恢复时间不超过 300ms。1000rpm 切换到 500rpm下落过程中不应有明显反转或过冲。连续运行 30 分钟L298N 散热片温度不应超过 80℃手能持续触摸约 5 秒。编码器读数在静止时应为 0不应有抖动计数。最后还要复查一个常被忽视的地方共用电源的 ADC 采样。如果 L298N 的电机电源和 STM32 的供电之间有走线耦合PWM 开关噪声会通过地线串扰导致 ADC 采样值波动进而让 PID 的输入噪声变大。验证方法是把 PID 设为 0观察编码器测速曲线——曲线毛刺大优先检查接地而不是继续调 Kd。D 参数能压住表面噪声但掩盖不了硬件的问题。本文还有配套的精品资源点击获取
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门