ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Proteus仿真的51单片机PID电机闭环控制系统设计

基于Proteus仿真的51单片机PID电机闭环控制系统设计 简介这是一份基于51单片机与Proteus仿真实现的PID电机控制系统设计资源面向单片机初学者、嵌入式课程设计及电子竞赛备赛者。系统以51单片机为核心包含电机驱动电路支持按键设定PID参数、启停与方向控制并通过LED指示运行状态、LCD1602屏实时显示相关信息可直观理解PID闭环调速流程。压缩包内含34个文件以C语言源码、Proteus仿真工程、Keil工程文件及Hex烧录文件为主另有仿真截图和说明文档整体仅1.09MB便于快速下载与二次修改。目前已有149人学习资源附带了仿真图与程序源码可帮助读者对照设计框图进行实物验证或毕设/课设拓展。1. 基于单片机Proteus仿真的PID电机控制系统先解决“闭不上环”的问题把“基于单片机proteus仿真的PID电机控制系统设计仿真图、源代码”这个题目拆开看真正难的不是画图而是让仿真里的电机转速闭环。PWM输出再漂亮没有测速反馈PID就是空转。我一般会选 AT89C52 这类51单片机打底Proteus 里元件齐全仿真跑得稳源代码调试也直观应付课程设计和入门级工程验证都够用。下面按仿真图、测速通道、增量式PID代码、示波器验证这条线讲一套能落地的最小系统。需要说明一下项目标题里的 protues 是 Proteus 的常见误拼检索时两个拼写都能用下文统一按正式名称写。2. 从开环PWM到闭环PID被控量、采样周期和控制量的关系2.1 为什么开环PWM不能当“电机控制”直流电机的开环调速最直接的做法就是改变 PWM 占空比占空比高平均电压高转速快占空比低平均电压低转速慢。但 PWM 只解决了“给定电压”的问题没有解决“实际转速到底是多少”的问题。电机的端电压、负载转矩、绕组电阻和工作温度一旦变化同样的占空比得到的转速会明显漂移。闭环控制要盯住的是“被控量”。在被控对象是电机转速时被控量就是每分钟转过的圈数也就是转速。控制器先测量实时转速再与目标转速求差根据误差计算下一步的控制量。误差为正说明转速偏低需要把占空比加大误差为负说明转速偏高需要把占空比收小。这个循环必须在一个固定周期内完成周期太短单片机算不过来周期太长转速会出现明显振荡。2.2 增量式PID算法是把占空比“叠”上去位置式PID是把占空比“设”出来PID 的输出拿过来怎么变成 PWM 占空比常见有两种写法。位置式PID直接计算当前应该输出的占空比// 位置式PID伪代码 output kp * err ki * err_sum kd * (err - err_last);位置式的err_sum是所有历史误差的累加只要前期一直有稳态误差这个累加项就会一直增大程序里一旦忘记做积分限幅占空比很容易被顶到上限这种现象叫积分饱和。增量式PID输出的不是“当前占空比”而是“当前占空比应该在上一次基础上增加多少”// 增量式PID伪代码控制周期内执行一次 int inc kp * (err - err_1) ki * err kd * (err - 2 * err_1 err_2); pid_out inc; if (pid_out 999) pid_out 999; if (pid_out 0) pid_out 0;每次只算增量不需要维护一个无限大的历史误差累加项。即使某次计算出的增量特别大在输出累加后也能被限幅拦下来。这个特性对电机这类惯性负载很重要所以我更倾向在51上用增量式。式中的err_2是上上次误差err_1是上次误差注意在每次计算结束后要把err_1赋给err_2把本次err赋给err_1。若后续做位置环和速度环组成的双闭环可以把两个PID按顺序串起来也就是级联PID控制外环输出作为内环目标值先把这里的单级跑通再往上扩展。如果你希望把工业上常见的PB、Ti、Td三参数转成上面的kp、ki、kd可以用kp 100 / PB、ki kp * T / Ti、kd kp * Td / T换算其中T是控制周期单位要和Ti、Td保持一致。Proteus调参时直接用kp / ki / kd更直观。2.3 Proteus仿真里PID算法需要盯住三个量测速值、占空比、目标值仿真运行起来之后不能只盯着“转速显示是否稳定”这一个结果。我一般会在代码里保留两个实时变量current_speed和pid_out分别对应当前测到的转速和PID计算后的占空比。再用 Proteus 的虚拟示波器看两路信号一路是单片机输出到L298N使能端的PWM波形另一路是编码器反馈脉冲。把这三个量放在一起看才能判断到底是PID收敛了还是被限幅强行压住了。观察对象从哪里看正常表现异常迹象测速值代码变量或串口打印稳定在目标值附近数值跳变快需要检查计数器溢出占空比示波器PWM引脚波形占空比随转速偏差平缓变化频繁撞到0或999说明限幅生效过猛目标值程序内给定与测速值偏差越来越小与测速值差距长期不变可能积分太弱如果示波器上PWM波形一直在满占空比和0占空比之间来回跳先不要继续调参数去检查测速通道的计数是否稳定。测速值本身抖动太大PID会把这些抖动当成真实转速误差来响应最终表现就是仿真发散。这个顺序在Proteus里尤其重要因为仿真不存在传感器噪声但接线和元件配置错误造成的信号毛刺反而更容易误导调试。3. 搭Proteus仿真图元件清单与测速通道接线3.1 元件选取单片机、驱动、测速电机和观察设备Proteus 仿真图不需要把所有电源引脚都画出来但负载回路一定要标清楚。下面这张表是我搭这套系统时会放到画布上的最小元件集元件Proteus 搜索关键词数量在本系统中的作用AT89C52AT89C521运行PID算法并产生PWML298NL298 或 L298D1H桥电机驱动隔离单片机与电机直流电机MOTOR-ENCODER 或 DC MOTOR1被控对象带测速输出更好12MHz晶振CRYSTAL1单片机时钟源30pF电容CAP2晶振负载电容10k电阻RES1复位上拉虚拟示波器OSCILLOSCOPE1观察PWM占空比和测速脉冲虚拟终端VIRTUAL TERMINAL1打印转速、目标值和PID输出元件放好后双击 AT89C52在 Program File 一栏加载 Keil 编译生成的.hex文件Crystal Frequency 填 12MHz。这里最容易出的问题是晶振频率配置和代码里定时器初值不一致导致PWM实际频率和预期差很多。3.2 接线顺序PWM、方向引脚和编码器反馈单片机引脚到 L298N 的接线我习惯固定为先接方向再接使能最后接测速反馈。P2.0接 L298N 的IN1P2.1接IN2P2.3接ENA。正转时把IN1置1、IN2置0反转则反过来。ENA接收单片机送来的PWM信号PWM占空比控制L298N输出到电机的平均电压。L298N 输出端OUT1、OUT2接直流电机两端。电机如果自带编码器输出编码器的A相信号接单片机P3.2/INT0。编码器输出的信号通常是5V方波51的P3.2可直接识别。若使用的是不带测速输出的电机模型可以在Proteus里另外找一个信号发生器把它输出频率设成与转速成比例的信号去模拟编码器脉冲这样PID算法本身仍然可以验证只是转速换算不再来自真实电机模型。3.3 编码器测速通道INT0计数与转速换算测速的基本思路是在固定时间窗口内统计外部脉冲个数再根据编码器每转输出的脉冲数换算成转速。代码里把P3.2配置成下降沿触发的外部中断每来一个脉冲pulse_cnt加1控制周期结束时读取并清零#define PULSE_PER_REV 20 // 编码器每转输出20个脉冲 volatile unsigned int pulse_cnt 0; void Ext0_ISR(void) interrupt 0 { pulse_cnt; // 外部中断计数只做累加 } // 每100ms控制周期结束前在主循环中做一次换算 unsigned int get_rpm(void) { unsigned int rpm; rpm (unsigned long)pulse_cnt * 600UL / PULSE_PER_REV; pulse_cnt 0; // 计数清零进入下一个测量窗口 return rpm; }换算公式里出现常数600是因为测量窗口是0.1秒脉冲频率除以每转脉冲数得到转每秒再乘60得到转每分钟即(pulse_cnt / 0.1) / 20 * 60 pulse_cnt * 600 / 20。如果改了测量窗口这个换算系数要同步调整。这里的中断服务函数只做累加不在中断里做乘除运算是因为外部中断可能随时到来耗时的运算会延长关中断时间造成脉冲丢失。4. 编写PID电机控制系统源代码51的定时器PWM与增量式PID4.1 动态定时器PWM用定时器0做出可调占空比51单片机标准内核没有硬件PWM模块网上常见的“延时翻转IO”办法在主循环里会阻塞测速和PID计算。我一般用动态定时器方案一个定时器中断交替设置引脚高低电平中断服务函数根据当前电平状态和占空比动态重装定时器初值。#include REG52.H sbit PWM_PIN P2^3; sbit MOTOR_IN1 P2^0; sbit MOTOR_IN2 P2^1; #define PWM_PERIOD_CNT 1000 // PWM周期对应的定时器计数个数1kHz volatile unsigned int duty_cycle 500; // 初始占空比 50% static bit pwm_state 0; static unsigned int tcnt 0; void Timer0_ISR(void) interrupt 1 { if (!pwm_state) { PWM_PIN 1; // 低电平结束翻转为高 pwm_state 1; tcnt duty_cycle; // 高电平持续时间 } else { PWM_PIN 0; // 高电平结束翻转为低 pwm_state 0; tcnt PWM_PERIOD_CNT - duty_cycle; // 低电平持续时间 } if (tcnt 0) tcnt 1; // 避免初值异常导致中断卡死 TH0 (unsigned int)(65536 - tcnt) 8; TL0 (unsigned int)(65536 - tcnt) 0xFF; } void pwm_init(void) { TMOD 0x11; // 定时器0和定时器1都工作在16位定时器模式 TH0 0xFC; // 初始计数 1000约1ms TL0 0x18; ET0 1; TR0 1; EA 1; }这套逻辑的核心在于每次中断都在翻转引脚后重新计算到下一次翻转的时间。因为12MHz下51的机器周期是1MHz定时器每计数1次对应1微秒PWM_PERIOD_CNT定为1000时PWM频率就是1kHz占空比分辨率是1/1000。实际运行中duty_cycle被PID输出更新占空比平滑变化不占用主循环时间。4.2 主循环里的增量式PID与输出限幅控制周期我用定时器1做50ms中断一次主循环里每累计2次触发一次PID计算也就是100ms控制周期。这样测速窗口100msPID周期100ms两个时间基准天然对齐。volatile unsigned int current_speed 0; volatile unsigned char time_flag 0; volatile unsigned char ctrl_counter 0; int set_speed 600; // 目标转速单位rpm int pid_out 0; // PID输出范围 0~999 int err_1 0; int err_2 0; float kp 0.80f; float ki 0.05f; float kd 0.02f; void Timer1_ISR(void) interrupt 3 { TH1 (65536 - 50000) / 256; // 50ms TL1 (65536 - 50000) % 256; time_flag 1; } void pid_regulate(void) { int err, inc; err set_speed - (int)current_speed; inc (int)(kp * (err - err_1) ki * err kd * (err - 2 * err_1 err_2)); pid_out inc; if (pid_out 999) pid_out 999; // 占空比上限 if (pid_out 0) pid_out 0; // 占空比下限 duty_cycle (volatile unsigned int)pid_out; err_2 err_1; err_1 err; } void main(void) { unsigned int rpm 0; MOTOR_IN1 1; // 正转方向 MOTOR_IN2 0; IT0 1; // INT0下降沿触发 EX0 1; pwm_init(); TH1 (65536 - 50000) / 256; TL1 (65536 - 50000) % 256; ET1 1; TR1 1; while (1) { if (time_flag) { time_flag 0; if (ctrl_counter 2) // 每两个50ms执行一次 { ctrl_counter 0; rpm get_rpm(); current_speed rpm; pid_regulate(); } } } }这段代码里set_speed和current_speed的单位都是rpmpid_out的单位是千分比占空比。kp取0.8意味着100rpm的转速误差会产生80个单位的占空比增量ki负责消化长期稳态误差kd负责在转速接近目标时抑制超调。如果3个参数都过小系统会长时间达不到目标转速如果ki过大转速会在目标值附近持续振荡。4.3 PID参数初值怎么给Kp、Ki、Kd三组起步值Proteus仿真中不需要上来就做临界比例度法我一般直接按经验区间起步再根据现象微调。控制现象参数调整检查其他环节启动太慢稳定时间过长增大Kp或Ki确认转速换算没有偏小超调大转速冲过目标值很多减小Kp或增大Kd确认PWM频率没有过低目标附近等幅振荡减小Ki略增Kd检查测速脉冲是否抖动仿真发散波形越来越远Kp、Ki同时减小一半查输出是否频繁到999限幅当目标转速从600rpm启动时kp从0.5到1.0之间起步比较安全ki从0.02到0.05起步kd从0.01到0.05起步。如果PID输出的占空比单位改成0到100的百分比三个参数要按比例缩小到原来的1/10很多人在Proteus里直接把0到100的代码套进0到999的限幅结果系统怎么调都不稳。5. 用虚拟示波器和串口验证Proteus PID电机控制系统5.1 先开环测PWM再闭环测转速闭环验证前我强烈建议先把PID断开让duty_cycle固定在一个值比如500。运行仿真后示波器接P2.3看到的应当是1kHz、占空比50%的方波。如果频率不是1kHz优先检查晶振频率和定时器初值。开环能稳住PWM之后再把duty_cycle更新交给PID并把目标转速设到一个较小值比如300rpm。从虚拟终端或代码调试变量里观察转速应当先上升经过少量超调后稳定在300附近。5.2 三个容易误判的现象超调、稳态误差、仿真发散Proteus里观察闭环曲线比实物更容易看到整条动态过程。超调表现为转速越过目标值再回落稳态误差表现为转速停在目标值附近但永远有偏差仿真发散则是一路增大直到撞上限幅。遇到仿真发散第一反应不是继续降Kp而是检查PID的输出限幅和测速换算是否匹配。若限幅上限999对应的是100%占空比而PID输出在900附近长期打顶说明积分项在积累无效误差需要在控制周期结束后判断是否处于限幅状态再决定是否更新积分项。这套仿真图和源代码移植到实物时只需根据真实编码器的每转脉冲数和采样周期重写换算系数PID结构本身不需要改动。本文还有配套的精品资源点击获取
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进