尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
STM32直流电机PID闭环控制实战:从编码器测速到参数整定
STM32直流电机PID控制这个坑我前前后后跳进去爬出来好几回。从最初用开环PWM调速到后来上编码器做闭环再到PID参数现场调得头皮发麻每一步都踩出了经验。这篇就把我基于stm32f103控制板做直流电机转数PID闭环控制的全过程记录下来——硬件怎么搭、测速怎么采、PID怎么写、参数怎么调一次性讲透适合刚接触电机控制、被开环转速不稳折磨过的同学参考。1. 先把控制思路捋清楚为什么要做闭环而不是调PWM就完事1.1 开环调速的最大痛点电压上去转速不一定上去很多人刚玩电机时都会这么干给PWM占空比50%电机转起来占空比加大转速变快改小转速变慢这就算调速了。说实话这思路应对空载玩具电机没问题可一旦带负载问题立刻暴露——电机转速不仅受PWM影响还受负载转矩、电源电压、电机换向磨损等因素干扰。同一占空比空载能转3000转挂个轮子立刻掉到2400转如果你只想固定转速运行开环根本稳不住。直流电机的机械特性里有个关键表达式转速 n ≈ (U - I·R) / (Ce·Φ)。U是电枢电压I是电枢电流R是电枢电阻Ce和Φ和电机本身参数有关。负载变大时I变大I·R压降增加n必然下降。这就是开环调速无法克服的固有缺陷没有转速反馈控制器根本不知道电机现在慢了多少自然也没法补偿。1.2 闭环控制的本质测量-比较-修正的循环闭环控制说白了就是让系统长了眼睛。我用编码器实时测量电机转速和期望目标转速比较算出偏差再用PID算法调整PWM输出直到转速稳定在设定值附近。整个过程像一个不断修正的过程电机转速慢了就加力快了就收力最终让输出紧跟目标。拆到代码层面闭环控制的核心链路是这样的设定目标转速值比如每分钟3000转定时器捕获编码器脉冲换算成当前实际转速计算偏差 e 目标值 - 实际值用PID公式计算控制量得到新的PWM占空比更新PWM输出让电机转速做出调整回到步骤2实时循环其中STM32F103的角色是核心中枢它要处理编码器信号、执行PID运算、输出PWM信号。在F103上这三件事分别对应编码器模式定时器、主循环/定时器中断、PWM输出定时器几乎是为电机控制量身打造的资源布局。2. 硬件选型与电路连接把每一根线都安排明白2.1 STM32F103开发板的选型细节F103家族里我首推STM32F103C8T664脚封装足够用。它内置72MHz主频、3个通用定时器、2个高级定时器、10个以上GPIO端口做单电机闭环控制绰绰有余。如果是双电机差速小车主板也可以考虑STM32F103RCT6多几个定时器和引脚自由度高一些。选型时有个容易忽略的问题开发板上的电源接口和稳压方案。最好确认板载3.3V稳压芯片的输出电流足够因为编码器模块通常需要5V供电但信号线又要和MCU共地电源部分一定不能省我建议用外部5V独立供电驱动MCU板上供电至少也要用AMS1117-3.3这种1A级别的方案。2.2 电机驱动板选型H桥是标配直流电机驱动不能直接接MCU引脚F103的GPIO最多输出几十毫安带不动几百毫安的电机。我常用的方案有几种驱动方案电压范围峰值电流特点L298N5-12V2A经典可靠压降较大DRV88332.7-10.8V1.2A体积小适合小型电机TB6612FNG2.7-13V3.2A效率高MOSFET结构自搭MOSFET H桥任意看MOS管灵活但需要额外电路我这次用的是TB6612FNG它是MOSFET结构导通压降小发热低比较适合12V级别的小型直流电机。接线没太多玄机驱动板的VM接电机供电正极VCC接MCU的3.3V或5V逻辑电源GND和STM32共地AIN1/AIN2、BIN1/BIN2是方向控制引脚PWMA/PWMB接PWM调速信号。方向控制逻辑简单记忆AIN11、AIN20时电机正转AIN10、AIN21时反转都是一样时刹车或滑行具体取决于驱动芯片逻辑。2.3 测速方案怎么选霍尔、编码器、还是测速发电机测速是闭环控制的输入基础测不准PID再好也没用。三种主流方案各有适用场景光电编码器最常见、精度最高输出AB两路正交脉冲通过相位差判断正反转配合STM32定时器的编码器模式SMCR寄存器配置好后自动计数不占CPU运算资源。带霍尔传感器的电机部分电机自带霍尔输出通常输出3路或1路脉冲测速简单但角度分辨率低低速时脉冲稀疏测速刷新慢。测速发电机输出模拟电压和转速成正比需要ADC采集精度尚可但需要额外占用ADC通道。我这边用的是AB相光电编码器常见规格有每转13线、15线或更高。选编码器时注意一个关键参数减速比。如果电机带减速箱比如减速比1:30电机轴转30圈输出轴才转1圈那么测速要按电机轴计算还是按输出轴计算如果应用关注的是输出轴转速要把减速比折算进去。我项目里关注的就是电机轴转速所以直接按编码器线数计数不额外换算减速比。2.4 定时器资源分配PWM、编码器、中断各归其位STM32F103的定时器功能非常强大但引脚复用不是随意定的。我的资源分配如下这个方案可以照抄TIM3主用PWM输出通道CH1映射到PA6PWMA控制电机通道CH2映射到PA7PWMB控制方向TIM4编码器模式输入捕获引脚PB6和PB7接编码器AB相TIM2产生定时中断实现10ms系统控制周期接线顺序要特别注意编码器的VCC接5V、GND共地A相输出接PB6B相输出接PB7。GPIO配置时要注意编码器模式下TIM4的CH1/CH2引脚要配置为输入模式并且启用内部上拉电阻。漏极开路或浮空输入都可能读取到不稳定的跳变导致计数乱跳。2.5 供电拓扑的坑共地是硬原则硬件连接中最容易翻车的不是信号线接错而是电源不共地。电机电源、编码器电源、STM32电源三者如果各自独立供电但GND没有统一连接编码器信号的电平就无法被MCU正确识别轻则转速数据乱跳重则IO口过压损坏。我建议采用单点共地策略电机电源负极、驱动板GND、编码器GND、STM32GND全部接到同一个GND节点。电源部分漆包线尽量粗一些12V电机供电和3.3V逻辑电之间注意滤波加一个100uF电解电容和一个104陶瓷电容并联在电机电源端减少换向火花导致的电压尖峰。3. 转速测量的核心工程实现编码器数据是怎么变成转速的3.1 STM32定时器编码器模式的配置细节F103的定时器编码器模式是个好东西。通常片子里的编码器接口模式支持在TIM1、TIM2、TIM3、TIM4上使用配置它的核心在于TIM的SMCR寄存器设置SMS位为001或011模式。我手写初始化的关键代码片段如下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); // PB6、PB7配置为浮空输入芯片内部无上下拉外部需要上下拉电阻 GPIO_InitStructure.GPIO_Pin GPIO_Pin_6 | GPIO_Pin_7; GPIO_InitStructure.GPIO_Mode GPIO_Mode_IN_FLOATING; GPIO_InitStructure.GPIO_Speed GPIO_Speed_50MHz; GPIO_Init(GPIOB, GPIO_InitStructure); TIM_TimeBaseStructure.TIM_Period 0xFFFF; // 16位计数器最大值 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 0x0; 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 0x0; TIM_ICInit(TIM4, TIM_ICInitStructure); // 设置编码器模式 TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI1, TIM_EncoderMode_TI2, TIM_ICPolarity_Rising); TIM_SetCounter(TIM4, 0); TIM_Cmd(TIM4, ENABLE); }需要注意这里的TIM_Period设置成0xFFFF即计数范围0~65535。如果转速很高、脉冲频率快计数可能会溢出因此要定期比如每个控制周期读取当前计数器值并和上一次的值做差再把计数器清零或做方向判断。如果电机正反转频繁建议读取差值后立即把TIM_SetCounter(TIM4, 0)清零防止累加溢出。3.2 转速换算公式与计算过程有了定时器计数值差转速计算就很简单了关键在于换算公式。设一个控制周期内捕获的脉冲增量为cnt编码器每转输出线数为PPR这里取13线采用4倍频采样那么电机轴转速n单位RPM可以这样算每转脉冲总数 PPR × 4 52 电机每秒钟转数 rps cnt / (控制周期秒数 × 52) 每分钟转数 rpm rps × 60代入我项目里的参数PPR134倍频后每转脉冲数52控制周期10ms也就是0.01秒。例如10ms内计数到26个脉冲那么转速为rps 26 / (0.01 × 52) 50转/秒 rpm 50 × 60 3000转/分实际写代码时为了避免浮点除法消耗太多CPU我会用整数运算处理。比如定义转速计算函数时用乘法和移位代替除法或者直接用浮点运算但关闭FPU不需要F103的软件浮点也足够处理这么低频的计算。有一点必须提醒如果编码器线数不是13而是其他值比如常见的有11线、15线、20线单位换算时要用实际参数替否则转速数据会离谱。3.3 方向判断为什么必须要用正交编码器如果你只需要转速的绝对值那单路脉冲也够用。但电机在PID控制中可能需要反转或制动这时候必须知道当前转动方向。AB相正交编码器输出的两路信号相位差90°STM32编码器模式下通过判断边沿组合就能自动识别方向。正转时计数器递增反转时计数器递减读取到的计数值本身就带有正负号信息。所以初始化时我把TIM_EncoderMode配置为同时捕获TI1和TI2两路这样能实现4倍频测量既提高分辨率又支持方向识别。方向判断结果并不需要自己写代码推算从定时器计数器的增减方向就能看出来。读取到负数时代表反转正数代表正转。3.4 滤波处理实测数据里的毛刺让人头皮发麻编码器信号在电机电刷换向、外界电磁干扰时会出现毛刺导致计数跳变多个值。如果100%信任原始计数数据PID控制器会被这些尖峰带着走导致PWM输出狂跳电机发出嗡嗡声。我实测最明显的现象是稳定运行时突然有一个毛刺让转速从3000跳到2800PID输出瞬间波动电机转速跟着猛抖。解决毛刺我用了两级手段。第一硬件上在编码器信号线和GND之间并联104电容滤掉高频干扰。第二软件上采样十次取平均或者用一阶低通滤波。一阶低通的核心公式是filtered_value alpha × raw_value (1 - alpha) × filtered_valuealpha取值一般在0.1~0.3之间。alpha太大滤波效果弱太小转速跟随变慢。我实验后alpha取0.2比较合适既滤掉了毛刺又保持了足够的动态响应能力。滤波器放在速度计算后面PID输入用的是滤波后的转速值。4. PID算法实现与工程化从公式到代码再从代码到手感4.1 位置式PID和增量式PID怎么选PID控制规律用数学表达就是控制量 Kp × 偏差 Ki × 偏差积分 Kd × 偏差微分。工程上有两种实现方式位置式和增量式它们的区别是输出的物理含义不同各有适用场景。位置式PID直接输出绝对控制量。对电机控制来说控制量就是PWM的比较值比如0~999。它的毛病是积分累积容易过头系统启动时偏差大积分项快速累积控制量可能瞬间爆表产生很大的超调。增量式PID输出的是控制量的增量也就是本次输出和上次输出的差值。公式为ΔU Kp × (e(k) - e(k-1)) Ki × e(k) Kd × (e(k) - 2×e(k-1) e(k-2))然后U(k) U(k-1) ΔU。增量式的好处非常明显没有积分累积过头的风险误动作影响小切换手/自动时冲击小。在电机调速这种执行器有自身惯性、控制量可增减的场合增量式PID是更稳妥的选择。我做这个项目用的就是增量式。4.2 增量式PID的完整代码实现直接上代码这是经过调整的简化版本typedef struct { int16_t target_speed; // 目标转速RPM int16_t current_speed; // 当前实际转速RPM int32_t error; // 当前偏差 int32_t last_error; // 上一次偏差 int32_t prev_error; // 上上次偏差 int16_t kp; // 比例系数 int16_t ki; // 积分系数 int16_t kd; // 微分系数 int32_t pwm_out; // 输出PWM值 int32_t max_pwm; // PWM最大值 int32_t delta_pwm; // PWM增量 } PID_TypeDef; void PID_Calc(PID_TypeDef *pid, int16_t current_speed) { pid-current_speed current_speed; pid-error pid-target_speed - pid-current_speed; // 增量式PID计算 pid-delta_pwm pid-kp * (pid-error - pid-last_error) pid-ki * pid-error pid-kd * (pid-error - 2 * pid-last_error pid-prev_error); // 累加输出 pid-pwm_out pid-delta_pwm; // 限幅处理 if (pid-pwm_out pid-max_pwm) pid-pwm_out pid-max_pwm; if (pid-pwm_out 0) pid-pwm_out 0; // 更新历史偏差 pid-prev_error pid-last_error; pid-last_error pid-error; }代码里两个细节必须强调。一是限幅处理必须做PWM比较值一旦超过定时器ARR设定值输出直接饱和电机全速转PID再算也没用。二是历史偏差的更新顺序prev_error last_error然后last_error error顺序颠倒会让微分计算完全错误那种导数项突变造成的系统抖动排查起来能让人崩溃。4.3 PWM频率与定时器配置的配合PWM输出频率我选择10kHz。这个选择背后有讲究PWM频率太高MOS管开关损耗增大驱动芯片发热频率太低电机电流脉动增大转速噪声变大。电机击穿脉冲宽度调制比较值范围0~1000配合定时器分频10kHz很容易实现。定时器配置核心参数如下void PWM_TIM3_Init(void) { TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure; TIM_OCInitTypeDef TIM_OCInitStructure; // 定时器3频率 72MHz / (711) / (9991) 1kHz这里要算准 // 72MHz时钟预分频71分频得到1MHz周期999则PWM频率1kHz // 如果需要10kHz则预分频后频率10MHz周期999 }我当时是直接用10kHz72MHz主频预分频器值设为0计数周期999得到PWM频率 72MHz / (0 1) / (999 1) 72kHz——这明显不对。正确的做法是先分频再计数。设预分频为7即8分频得到9MHz计数时钟再设ARR为899频率 72MHz / 8 / 900 10kHz。一些开发板库函数封装太深不少人折在这里。小经验PWM比较值最好和PID输出范围一致或者PID输出限幅后统一映射。我习惯把PID最大PWM值设为999这样TIM_SetCompare1(TIM3, pid.pwm_out)一步到位不用额外做百分比转换。4.4 控制周期选择的实际考量PID运算的节奏很重要。控制周期太长系统响应迟钝太短高频噪声和量化误差都会被放大微分项产生很大抖动。我用的是TIM2产生10ms定时中断在中断里完成测速、滤波、PID计算、PWM更新。10ms意味着控制频率100Hz对一般直流电机调速系统来说恰到好处。电机本身是惯性系统机械时间常数通常在几十毫秒到一两百毫秒之间控制器比执行器快5~10倍已经能满足需求。如果追求更好的动态可以尝试1ms到5ms的控制周期但此时一定要考虑中断函数的执行时间10us量级的PID计算完全没有压力F103主频72MHz绰绰有余。中断服务函数的结构大致是这样void TIM2_IRQHandler(void) { if (TIM_GetITStatus(TIM2, TIM_IT_Update) ! RESET) { TIM_ClearITPendingBit(TIM2, TIM_IT_Update); // 读取编码器计数计算转速 int16_t speed Get_Speed_RPM(); // 滤波 speed LowPass_Filter(speed); // PID计算 PID_Calc(pid, speed); // 更新PWM TIM_SetCompare1(TIM3, pid.pwm_out); } }中断里不能做浮点运算的话要特别注意F103是M3内核浮点运算由软件模拟即便是一个简单的除法也要花费较多时间但不至于卡死中断。稳妥起见转速计算用定点数比如把每秒转数乘1000用整数表示PID参数也按整数处理这样中断执行时间就能做到微秒级。5. PID参数整定的实战过程调参调到怀疑人生直到找到手感5.1 第一步先把P值调到一个稳定范围任何调参从纯P开始都是通用的地基。把Ki和Kd先设为0从一个保守的Kp值开始增大观察电机转速响应。我用的方法是目标转速设定为2000RPM把Kp从0.05开始往上加。转速太低、爬升极慢说明Kp太小加大后转速开始有超调出现轻微来回振荡说明Kp已经接近极限。实际观察到比较典型的规律是Kp过小启动时转速上升缓慢长时间达不到目标值Kp合适启动后快速上升到目标值附近超调量在5%~10%以内短时间内稳定Kp过大转速冲到目标值以上很多来回振荡无法收敛甚至出现啸叫我的Kp最终定在0.15左右换算成整数系数就是Kp150因为我的偏差单位是RPMPWM单位是0~999。不同的电机、供电电压、编码器线数会让这个值差很多千万不要照抄别人的参数否则大概率会出问题。5.2 第二步加入积分消除稳态误差纯P控制下电机带负载后会有稳态误差。这是因为要维持一定的PWM输出就必须存在一个偏差让比例项产生的控制量足够维持电机转速。要吃透这个现象误差变小控制量就会减小最终平衡在一个偏差不为零的点。积分项解决的就是这个问题。添加积分时从很小的值开始。Ki设成Kp的1/10到1/5是比较保险的起步范围。如果Ki过大你会观察到转速在目标值附近缓慢上下摆动周期几秒钟那种这不是编码器噪声是积分环频率过低产生振荡。如果Ki太小稳态误差消除速度很慢转速会一直和目标差几十转。我实测下来KiKp/8左右效果不错。加入积分后把目标设定在3000转空载跑转速能稳定在正负10转以内也就是0.3%左右的稳态精度。这个精度对大多数应用场景已经足够。5.3 第三步微分项的谨慎添加微分项的作用是预测偏差变化趋势对偏差变化率进行抑制可以减小超调提高稳定性。但微分项对噪声极其敏感编码器毛刺、转速量化误差都会通过微分项放大成PWM抖动。我建议在这个项目中只加很小很小的Kd甚至完全没有Kd也完全可以跑通。我最终Kd只在0.02左右仅仅用来压制启动时的超调。如果发现加Kd后转速曲线高频抖动说明Kd太大或滤波器不够强优先检查滤波时间常数而不是强行减小Kd。调参过程不是一次到位的。我一般是反复试了十几版参数每调一版都能看到不同的响应曲线最后才能找到全局最优组合。放个经验记录表方便你对照现象可能原因调整建议启动超调很大回不来Kp过大减小Kp长期稳不下来来回震荡Kp过大或Ki过大两者同时减小响应太慢半天到不了目标Kp过小增大Kp带负载后转速回落差几十转Ki过小增大Ki稳态时PWM反复跳电机嗡嗡响Kd过大或滤波不足减小Kd、增强滤波5.4 实际波形的观察与调试技巧PID参数调得好不好光看电机转得稳不稳还不够要观察转速响应曲线。最简单的方式就是串口输出转速值用Excel画波形。我用串口中断把每个控制周期的转速数据发到上位机串口打印函数注意要在中断开销允许的前提下执行否则会拉长中断时间影响实时性。然后我手动把转速从2000改到3000观察响应情况。标准工程师做法是目标值阶跃变化后系统响应要满足三个指标上升时间短、超调量小、稳态误差小。这三个指标常常相互矛盾需要根据应用取舍。我这套系统是风机负载对方超调不敏感优先保证快速响应净允许了8%左右的超调。如果你的应用是机械臂关节超调会撞机就要牺牲上升时间把Kp调小一点、Kd调大一点。串口画波形还有一个好处能清楚看到积分饱和现象。如果转速长时间停在目标值以下PWM已经顶到999了等负载减轻后转速砰的一下冲上天这就是积分饱和。工程解法是积分限幅或变增益我在代码里做了最简单的处理——直接把PID输出限幅在0~999积分项自然受限于输出范围。6. 调试过程中遇到的麻烦和排查思路踩坑实录比教程更值钱6.1 编码器读数跳变、转速数值乱飞现象转速数据忽高忽低就算电机转速很稳定串口打印的值也在几百转范围乱跳。排查步骤先查接线AB相有没有接反并不影响计数但要确认这两路信号和地线之间存在稳定的高/低电平查共地这是老生常谈但也是第一嫌疑在PB6、PB7上分别接示波器看波形如果波形毛刺特别大硬件滤波就要上104电容直接并联到信号和GND之间软件上启用定时器输入滤波TIM_ICFilter可以设置采样频率和滤波长度对窄脉冲干扰很有效6.2 电机启动时电流冲击导致MCU复位现象PWM占空比从0猛地到50%电机带着大负载启动系统瞬间复位。这是典型的电源跌落问题。电机堵转或启动瞬间电流是额定电流的数倍如果供电电源容量不够电压会瞬间跌落到MCU工作电压以下。解决办法驱动板和MCU板分开供电两者只共地电机供电用独立的稳压模块额定电流按电机启动电流的1.5~2倍选择软件上做一个软启动PWM先给一个较低的初始占空比例如10%然后每20ms增加5%让电机电流缓慢上升避免瞬间电流尖峰我后来直接在初始化时加了软启动逻辑一周内再也没有出现过MCU复位问题。软启动代码也就几行的事void Motor_Soft_Start(uint16_t start_duty, uint16_t target_duty, uint16_t step_ms) { for (uint16_t duty start_duty; duty target_duty; duty 5) { TIM_SetCompare1(TIM3, duty); delay_ms(step_ms); } }6.3 电机能转但方向反了且测速为负数如果编码器接反电机正转时计数器递减计算出的速度就是负数PID会把转速当作反向偏差处理PWM输出杂乱。解决方法是交换编码器AB两相接线或者在软件里把读取到的计数值取反两者效果相同。我用软件取反因为方便调整不用再动硬件线。特别提醒一下如果电机正转时编码器计数递减一般也不影响转速绝对值但PID控制时目标值为正、反馈为负系统会以最大PWM输出驱动电机直到堵转或者保护这个坑我印象深刻。务必在PID开始前先验证一下方向一致性。6.4 PID控制量输出有回差导致的停滞不转现象目标转速较低时电机处于转和停的边缘转速波动很大甚至不转。直流电机的死区特性导致PWM输出很低时电机转矩不足以克服静摩擦和死区完全不动PWM稍高时又突然转得飞快。这个区间PID很难稳定控制。解决思路是死区补偿设定一个最小有效PWM值计算出的控制量在这个值以下时直接按最小值输出或者设置一个转速死区偏差很小时不更新PWM。我的做法是在PID输出后加一个约束把0~deadband区间直接垫到deadband值同时设置转速PID不算死区的偏差范围。还有一个思路是用PWM归一化处理让控制量在实际有效范围内工作。具体数值视电机而定我的系统最小值大概8%占空比直接垫到10%。6.5 测速周期和控制周期的同步问题如果测速、PID计算、PWM更新在不同的时间基准里各自运行会导致控制不稳定。比如测速在10ms中断里做但是PWM更新在另一个定时器里做中间可能有几毫秒的随机延迟PID的输出跟不上当前转速。我的建议是单一时间基准所有控制任务都在同一个TIM2中断里按固定顺序执行测速→滤波→PID→PWM输出严格闭环。还有一种情况是串口打印函数耗时太长挤占了中断时间内其他任务的时间。调试时我在串口发送里加了一个FIFO缓冲中断函数只把数据丢进缓冲实际发送在主循环里做这样控制中断的执行时间基本保持不变。7. 系统集成与性能验证能跑起来只是开始稳定可靠才是交付7.1 完整的控制流程图拆解系统进入稳态后每个控制周期内做的事非常规律从TIM4读取计数器值和上次值做差用公式换算成转速RPM套用一阶低通滤波调用PID_Calc传入滤波后的转速PID输出限幅到0~999后写入TIM3比较寄存器把当前转速和目标值存入调试变量供串口输出这套流程用F103掌控CPU占用率极低大部分时间CPU都是空闲的还可以在同一个主循环里做OLED显示、按钮响应、通信处理等额外任务。不过中断服务函数里别做无关紧要的事情专心完成控制闭环就好。7.2 实测数据记录稳不稳拿数字说话我在整个调试过程中记录了一组有代表性的数据。目标转速2000RPM供电电压12V编码器13线4倍频PWM频率10kHzPID参数Kp150、Ki18、Kd2已按整数缩放。稳定运行10秒钟串口输出的转速波动范围最大转速2017RPM最小转速1986RPM平均转速2003RPM波动量±0.85%以内作为对比同一电机做开环PWM调速负载稍微变一点转速波动通常在10%~20%之间。这个数据足以说明闭环控制的必要性开环只是个驱动电路闭环才叫控制系统。再把负载突变测试结果拿出来看看。运行时手动轻捏电机轴模拟突然加载。开环系统转速会掉很多甚至要过好几秒才能适应闭环系统转速掉到1800左右但在不到0.3秒内又能拉回目标值2000。这就是PID闭环控制的真正价值所在。7.3 扩展应用与进阶方向这个项目做完后自然会有很多可以扩展的空间双电机驱动加差速算法扩展成小车底盘换成带霍尔的角度传感器把PID控制从转速环扩展成位置环加上电流采样实现力矩控制或者电流环PID把PID参数做自适应或模糊控制提升复杂负载工况下的鲁棒性F103这个平台虽然有些年头了但在电机控制这个领域依然是绝对的主力。很多人一上来就想上F4、H7其实做电机闭环控制F103的定时器资源、中断响应速度已经足够优秀重点是先把PID控制逻辑吃透把工程问题解决利落这些经验放到哪个平台都是通用的。最后分享一个很实用的小技巧调参时把PID的三个系数定义成一个结构体存储到Flash里每次开机自动加载。这样在现场调试时不用反复编译烧录直接通过串口命令就能在线修改Kp、Ki、Kd省下来的时间会让你非常感谢自己当初多写了这几行代码。我做这个项目时就是靠这个办法把本来要跑一整天的调参工作压缩到了两个小时以内。
RELATED

相关推荐

行波测距仿真全解析:小波分解与卡伦堡变换在配网故障定位中的应用

行波测距仿真全解析:小波分解与卡伦堡变换在配网故障定位中的应用

做配网故障分析和保护原理研究的人,基本都绕不开行波测距这个方向。这几年行波法在电力系统故障定位里被反复提起,但真正上手做过仿真、跑出过稳定波形的朋友都知道,这里面的坑一点都不少。小波分解与重构、卡伦堡变换、行波测距这几个词串在…

📅 2026/10/18 8:22:23
洗衣机里的“智慧大脑”:单片机控制源码与状态机全解析

洗衣机里的“智慧大脑”:单片机控制源码与状态机全解析

前阵子家里那台用了快六年的波轮洗衣机,突然在脱水阶段罢工,嗡嗡响几声就停,面板上的故障码一闪一闪。拆开背部盖板一看,主控板上一颗40脚的单片机芯片几乎承担了所有决策。那一刻我意识到,我们每天都在用的洗衣机&…

📅 2026/10/18 8:22:23
AI论文写作工具有哪些?4类工具实测拆解与避坑指南

AI论文写作工具有哪些?4类工具实测拆解与避坑指南

写论文这件事,说实话,绝大多数人卡住的环节根本不是“写”,而是“从零到一”那段最黑暗的启动期:题目定了却理不清逻辑,文献读了不少但捋不出主线,终于憋出几百字又觉得不像人话。我自己带过不少学生&#…

📅 2026/10/18 8:17:23
MORE NEWS

更多资讯

📰

AI编程工具范式收敛:TRAE双端合并背后的协议对齐

1. 这不是功能叠加,而是AI编程工具的范式收敛“TRAE 合并双端之后,AI 编程工具正在走向同一个方向”——这句话初看像一句行业观察,细想却是一把解剖刀,精准切开了过去三年AI原生开发工具演进的底层逻辑。我从2021年就开始跟踪这类…

📰

YOLO边缘端部署实战:C#上位机与工业缺陷检测系统架构解析

1. 项目整体思路:为什么把YOLO放到边缘端跑1.1 场景痛点:传统上位机方案卡在哪去年年中接到一条产线的视觉检测改造需求,我当时最开始的想法是沿用过去的套路:工业相机拍照,图像传到工控机的上位机软件里,上…

📰

HTML5响应式简历模板:单文件交付与绿色语义设计

简介:这是一款专为求职者打造的绿色主题HTML5响应式个人简历模板,面向前端初学者与正在准备求职作品集的开发者,解决在线简历缺乏专业性、适配性差及技术展示力不足的问题。资源包共50个文件,包含20张高清图片(用于头像…

📰

从模糊标题到技术落地:以rea为例的项目拆解与选型方法论

1. 从“rea”这个标题说起:一个被低估的万能缩写第一次看到“rea”这个标题的时候,我脑子里蹦出来的第一反应是——这到底是个啥?是某个工具的缩写?某个项目的代号?还是某个技术栈的简称?说实话&#xff0c…

📰

前后端分离餐厅管理系统实战:Flask+Vue+PyCharm全栈开发与踩坑解析

前后端分离做餐厅后台管理系统,这个名字一看就是典型的实战型全栈项目。标题里Flask、Vue、PyCharm、Django挤在一起,信息量不小,大概也说明这个项目经历了一段技术选型的折腾路。作为一个做过不少类似管理系统的开发者,我今天就围…

📰

从召回、排序到重排:X平台For You推荐算法全链路解析

聊算法的人很难不注意到X平台那套“For You”信息流。它是目前社交平台里少有的把推荐系统全链路公开讨论过的案例,从候选生成、排序到重排都值得拆一拆。不管你是做推荐系统的新人,还是已经在信息流业务里摸爬滚打多年,把这条链路的每个环节…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬