2026/9/11 15:40:24

基于51单片机的PID电机转速控制:定时器中断实现闭环调速方案

基于51单片机的PID电机转速控制:定时器中断实现闭环调速方案 简介面向51单片机初学者的PID电机转速控制项目资源基于比例-积分-微分闭环算法实现精确调速适合嵌入式控制系统入门学习。资源包共21个文件压缩后约75KB包含C程序源码、Proteus仿真文件DSN/仿真工程、Keil工程文件、HEX烧录文件及调试辅助文件既可在虚拟环境验证控制效果也能直接下载至实体开发板运行。已有1101人浏览学习内容聚焦核心实现。通过这套资料可掌握定时器采样、误差计算、PID输出调节和PWM驱动电机的完整流程并在仿真中试凑P、I、D参数观察不同增益对响应速度、超调量和稳态误差的影响为后续更复杂的运动控制开发打下基础。1. 基于51单片机的PID电机转速控制一个定时器就能闭环的工程解法刚调好占空比的小直流电机一加载转速就往下掉这是很多51单片机项目的共同痛点。开环PWM只能控电压控不了负载造成的波动。做闭环时又容易误以为PID需要高档单片机其实51单片机的定时器中断加外部中断就足以完成测速、计算、输出这一整套控制循环。本文以常见的带编码器直流减速电机为对象从给51单片机设计测速环节讲起实现增量式PID算法再讨论参数整定和串口验证方法。适合做智能小车、小型调速风扇、课程设计的开发者也适合准备把自动控制原理落到真实硬件上的人。2. 编码器测速与PWM调速先把被控对象和测量端搭对2.1 开环控制的固有缺陷直流电机的转速由电枢电压和负载力矩共同决定。PWM占空比只改变平均电压负载增大时电流增加、转速下降而占空比没有反馈修正。开环系统一旦负载波动转速误差会持续存在。闭环控制需要先解决两个物理量转速反馈和执行输出。在51系统里前者用编码器脉冲换算后者用PWM占空比给定。闭环的四个环节缺一不可设定值、反馈值、控制算法、执行器。先把这个链路搭对PID才有意义。2.2 编码器测速外部中断计数与定时器窗口常见的转速反馈方案是电机轴上装码盘用对射光电传感器或霍尔传感器把旋转变成脉冲。码盘每转一圈输出固定数量脉冲比如20个。测速只需要在固定时间内统计脉冲个数这个任务天然适合51的外部中断把脉冲信号接到INT0引脚编码器输出经滤波整形后触发下降沿中断中断服务函数里只做累加。volatile unsigned int encode_count; // INT0中断里累加 void ExtInt0_ISR(void) interrupt 0 { encode_count; }计数变量必须声明为volatile因为它在中断和主循环之间共享。定时器负责开窗例如每100ms读一次encode_count读完马上清零防止下一次窗口内重复累计。unsigned char timer0_ticks; unsigned int sampled_count; void Timer0_ISR(void) interrupt 1 { TH0 0x3C; // 10ms重装值12MHz晶振12T模式 TL0 0xB0; if (timer0_ticks 10) // 10个10ms 100ms { timer0_ticks 0; ET0 0; // 暂时关闭定时器中断避免读取时被重入 sampled_count encode_count; encode_count 0; ET0 1; } }读取计数前先关闭对应中断源能保证采样窗口的完整性。转速换算公式为转速rpm等于采样脉冲数乘60再除以每转脉冲数和采样时间。采样时间是0.1秒所以60 / 0.1 600代码里直接写count * 600 / PULSE_PER_REV避开浮点运算。2.3 软件PWM1kHz低分辨率也能完成速度闭环51单片机里很多型号如AT89S52、STC89C52没有硬件PWM模块常见做法是用定时器产生软件PWM。我一般会把PWM基频设为100Hz占空比分10级这样定时器1ms中断一次中断开销小电机也能转得平稳。sbit MOTOR_PWM P1^0; volatile unsigned char pwm_level 5; // 0~10对应占空比0%~100% void Timer1_ISR(void) interrupt 3 { static unsigned char pwm_cnt 0; TH1 0xFC; // 1ms重装值12MHz晶振 TL1 0x18; if (pwm_cnt 10) // 10个1ms组成一个PWM周期 pwm_cnt 0; MOTOR_PWM (pwm_cnt pwm_level) ? 1 : 0; }pwm_level为10时占空比100%为5时50%。10级分辨率在速度闭环里足够用PID输出会被映射到这个范围内。如果对噪声和调速细腻度有更高要求建议换带PCA硬件PWM的51型号比如STC15系列硬件PWM分辨率能到8位以上接线和寄存器配置都能大幅简化。2.4 驱动电路与电源去耦单片机IO不能直接驱动电机常见做法是L298N或TB6612驱动芯片低功率项目也可以用三极管搭一个简易驱动。PWM信号和方向信号要分清楚L298N的ENA接PWMIN1/IN2决定正反转TB6612的PWMA接PWMAIN1/AIN2决定方向。驱动方式逻辑电压PWM频率建议适用电流注意事项L298N5V1kHz~5kHz2A以内压降大需要散热TB66123.3V~5V10kHz~100kHz1.2A以内时序与L298N兼容效率高三极管续流二极管5V不推荐高频PWM0.5A以内必须加续流二极管保护电源端记得在电机供电引脚上并一个100uF电解电容和0.1uF瓷片电容否则电刷产生的干扰会通过电源线进入51的复位电路造成程序跑飞。没有实物时可以在Proteus仿真51单片机里先搭出一个带编码器的直流电机模型把测速和PWM逻辑跑通再烧到真实硬件上。3. 增量式PID算法的C51实现中断内完成测速、计算、输出3.1 位置式PID与增量式PID位置式PID输出全量PWM值误差累积在积分项里。一旦输出饱和积分还在继续累积恢复时间变慢需要额外处理抗积分饱和。增量式PID只输出相对上一次输出的增量当前输出等于上一次输出加本次增量。增量式公式为delta_out Kp * (e(k)-e(k-1)) Ki * e(k) Kd * (e(k)-2e(k-1)e(k-2))这个式子没有积分累计项不会出现传统意义上的积分饱和只需要对最终输出做限幅非常契合51单片机的整数运算习惯。3.2 三个参数在51代码里的具体含义Kp是误差变化量的增益Kp越大对误差变化越敏感。Ki是当前误差的直接增益作用是在稳态时持续向目标逼近减小稳态误差。Kd是误差的二阶差分系数用来抑制超调但会把测速的量化噪声放大。代码为了不引入浮点把Kp、Ki、Kd定义成整数系数调参时直接改数值即可。3.3 完整代码框架typedef struct { int setpoint; // 目标转速单位rpm int feedback; // 当前转速单位rpm int err; int last_err; int prev_err; int kp; int ki; int kd; long last_out; // 上一次PWM输出累计值 } MotorPID; long PID_Incremental(MotorPID *pid, int fb) { pid-feedback fb; pid-err pid-setpoint - pid-feedback; long delta (long)pid-kp * (pid-err - pid-last_err) (long)pid-ki * pid-err (long)pid-kd * (pid-err - 2 * pid-last_err pid-prev_err); pid-prev_err pid-last_err; pid-last_err pid-err; return delta; }在100ms控制周期中调用MotorPID speed_pid {0, 0, 0, 0, 0, 20, 1, 5, 0}; #define PULSE_PER_REV 20 void PID_Task(void) { int speed (int)((long)sampled_count * 600 / PULSE_PER_REV); speed_pid.setpoint target_speed; speed_pid.last_out PID_Incremental(speed_pid, speed); if (speed_pid.last_out 0) speed_pid.last_out 0; if (speed_pid.last_out 100) speed_pid.last_out 100; pwm_level (unsigned char)(speed_pid.last_out / 10); // 映射到10级PWM }代码逻辑是先测速、再计算增量、把增量累加到last_out、最后钳位到0到100。pwm_level除以10是因为前面软件PWM只有10级占空比。示例参数Kp20, Ki1, Kd5是一个能起振的起点不同电机需要重新整定。注意delta被声明为long因为16位int在误差大时容易溢出。例如误差100rpmKp50仅P项就有5000再加上Kd的二阶差分项超过32767一点也不奇怪。Keil C51里涉及乘法时建议把其中一个操作数强转成long。3.4 控制周期固定不要放在主循环里PID计算必须固定周期不能放在while(1)里用delay实现。否则计算频率受主循环语句长度影响Kp、Ki的表现会千奇百怪。上面的代码把PID放在T0中断里周期就是100ms。想提高响应速度可以把测速窗口改成50ms但脉冲数减半测速分辨率变差。减速比30以上的直流减速电机额定转速300rpm左右时100ms窗口里有约50个脉冲足够稳定。窗口太小、脉冲太少时测速值会跳变PID反而更难调。4. PID调参实战从临界比例Z-N调参到积分饱和处理4.1 调参前先检查反馈极性和PWM方向一个容易踩的坑误差符号反了PID输出往错误方向跑系统变成正反馈。设定1200rpm当前800rpm误差setpoint - feedback 400增量式输出为正PWM应当增加。如果代码里误写成feedback - setpointPID会进一步减小PWM转速越偏越远。验证方法把Ki0、Kd0Kp设成很小的值给一个低于当前值的设定观察PWM是否减小目标高于当前值时PWM是否增大。方向错了把误差计算反转即可。4.2 开环阶跃先摸清控制量范围不跑PID直接固定pwm_level测开环转速。例如给5级50%占空比测出转速再给7级、9级记录一组比例关系。下面是一份真实项目中常用的开环记录表开环PWM等级实测转速(rpm)5620791591280这张表能告诉你两件事PWM与转速是否接近线性控制量从5到9有4个等级约660rpm变化意味着每级约165rpm。算下来Kp的合理量级应该在几十而不是几百避免上来就给Kp200导致强烈振荡。4.3 Z-N临界比例法调参步骤Ziegler-Nichols临界比例法是给PID调参最常见的起步方法51上也可以直接操作。步骤是设置Ki0、Kd0Kp从0开始缓慢增加。每加一次让电机从静止开始加速观察转速是否出现持续等幅振荡。找到刚好等幅振荡时的Kp记为Ku同时测量振荡周期Tu。按Z-N表计算初始值控制器KpTiTdP0.5Ku--PI0.45KuTu/1.2-PID0.6KuTu/2Tu/8注意Z-N表中的Ti、Td是时间常数而我们代码里的Ki、Kd是直接增益换算关系是Ki Kp * T_control / TiKd Kp * Td / T_control其中T_control是控制周期即100ms。例如测出Ku30Tu1.4s则Kp18Ti0.7sTd0.175s计算得到Ki≈2.6Kd≈31.5。取整后Kp18, Ki3, Kd32。Tu不好用手表测的话可以把速度值通过串口发到电脑在Excel里手动读周期。后面章节会给出串口输出代码。如果系统在调参过程中振荡越来越剧烈立即断电不要等它发散否则驱动芯片容易过热。4.4 增量式PID的输出限幅与变化率限制增量式PID虽然没有传统积分饱和但输出累计值必须限幅。last_out超过100时PWM已经全开再大没有意义。限幅之外还可以给PID增量加变化率限制让电机加减速平滑一些long delta PID_Incremental(speed_pid, speed); if (delta 5) delta 5; if (delta -5) delta -5; speed_pid.last_out delta;这个限制会降低快速响应适合齿轮箱缝隙大、负载有冲击的场景。PWM只有10级时5级的变化率限制意味着一个控制周期最多变半程系统不容易冲过头。4.5 压力调节PID参数经验值不能直接套用到电机网上经常能看到“压力调节PID参数经验值”压力环惯性大Kp通常较小、Ki较大、Kd也较大。而电机转速环机械时间常数小响应快直接套用压力环的参数很容易剧烈振荡。更接近电机调速习惯的经验值是先P后I再D先调P让系统不振荡加上I消除稳态误差最后加D压超调。每次只改一个参数记录波形再改下一个比一次性改三个参数更可控。提示增量为负数时delta可能小于 -5绝对值判断要写成delta 5 || delta -5不要在图省事时只写前半个条件。5. 串口曲线验证与级联PID扩展5.1 用51串口把速度曲线送到上位机调参不能靠感觉。通过串口把目标转速和当前转速发到上位机用串口绘图工具观察曲线是非常高效的调试方式。下面是一个简单的整数发送函数发送范围0到99999的数值void UART_SendInt(int value) { char buf[6]; buf[0] 0 value / 10000 % 10; buf[1] 0 value / 1000 % 10; buf[2] 0 value / 100 % 10; buf[3] 0 value / 10 % 10; buf[4] 0 value % 10; buf[5] \0; UART_SendString(buf); }在PID控制周期里拼帧发送UART_SendString(T:); UART_SendInt(target_speed); UART_SendString( S:); UART_SendInt(speed); UART_SendString(\r\n);注意不能把字符串发送直接写进控制中断因为9600波特率发送一个字符串要耗时几十毫秒会拖垮100ms控制周期。正确做法是在控制中断里把要发的数据写入缓冲区主循环里再发送。如果用带双缓冲的串口中断发送效率更高。5.2 用阶跃响应判断三个指标设定从500rpm阶跃到1000rpm观察曲线得到三个关键指标稳态误差稳定后目标与反馈的差值要求越小越好。稳态误差大优先增加Ki。超调量速度超过1000rpm的最大值。超过20%时增加Kd或减小Kp。调节时间从阶跃开始到进入±5%区间且不再出去的时间。调节时间过长增加Kp。如果曲线呈等幅振荡可能是Kp偏大或者控制周期内测速分辨率不够。把测速窗口改长到200ms重新看曲线可以区分是参数问题还是反馈噪声问题。5.3 从速度环扩展到位置环与级联PID转速跑顺后想让电机转到指定角度可以在速度环外面再加一个位置环。位置环输出作为速度环的目标速度环输出作为PWM占空比这种结构就是级联PID控制。51的资源仍然够用位置环控制周期设为200ms速度环保持100ms。位置环通常只用P控制pos_kp不能太大否则输出目标速度超过电机最大转速内环追不上反而振荡。int position_err position_target - position_now; int speed_target pos_kp * position_err; if (speed_target MOTOR_MAX_RPM) speed_target MOTOR_MAX_RPM; if (speed_target -MOTOR_MAX_RPM) speed_target -MOTOR_MAX_RPM;pos_kp建议先取0.1观察位置响应。位置误差100个码输出10rpm系统反应温和如果位置误差1000个码速度目标100rpm接近不少电机的上限。级联PID调试顺序是先内环后外环内环稳定才能保证外环看到的被控对象是线性的。5.4 最后一个小建议每次只改一个参数改完烧录、观察串口曲线记录一组数据后再改下一个。把整定好的三组参数连同负载情况、供电电压一起做成表格比边调边试可靠得多。对51这种调试工具弱的平台串口曲线就是最廉价的示波器。本文还有配套的精品资源点击获取