51单片机L298直流电机PWM调速:原理、Keil工程与Proteus仿真实现
简介这是一套专门用于直流电机控制的PWM调速设计方案采用51单片机作为主控芯片搭配L298驱动芯片完成电机的加速、减速、正转、反转以及停止控制。资源包总共包含21个文件压缩后体积只有116KB内容涵盖C语言程序源码、Keil工程文件、Proteus仿真电路、Hex烧录文件以及3张运行效果截图其中程序源码与仿真工程还分别打包成两个zip压缩包便于独立下载和对照使用。目前已有415人学习浏览适合单片机初学者、相关课程设计以及电子设计竞赛备赛者参考学习。方案中通过独立式键盘输入控制命令单片机定时产生脉宽可调的PWM波形并送入L298驱动芯片从而调整电机的转速与转向同时利用1602液晶显示屏实时显示当前转速整个过程涵盖PWM信号生成、电机驱动、按键交互和数据显示逻辑清晰非常适合用来理解直流电机控制的基本流程。所有源码和仿真工程都已调试完成下载后可以直接用Proteus打开运行也可以根据C语言源程序修改参数进而移植到实际硬件电路中进行功能扩展。1. C51单片机配L298驱动直流电机的第一关先分清占空比和方向逻辑很多人第一次拿51单片机控制L298直流电机时会以为让电机转慢一点就得把控制电压调低结果代码写出来电机要么不动要么直接就全速飞转。真正稳的做法是用PWM调占空比而且要把“方向逻辑”和“速度逻辑”分开处理L298的INA和INB管高电平/低电平组合决定正反转EN管脚上的PWM波形决定平均电压。这个基于AT89C51/AT89C52的直流电机控制工程把按键输入、PWM生成、L298驱动、1602显示全部串起来源文件里既有Keil工程也有Proteus仿真适合应付课程设计和快速搭建实物原型。对做电机控制的工程师来说它也是一个很好用的开环调速参考模板。2. PWM调速原理与L298接口设计为什么占空比决定转速2.1 直流电机调速为什么不直接改模拟电压直流电机的转速和电枢两端平均电压近似成正比但从单片机角度直接输出一个可变的模拟电压很不划算。AT89C51/AT89C52的I/O口只有高电平和低电平想要模拟电压要么外接DAC要么用运放加RC滤波成本和复杂度都上去了。PWM相当于用一个固定周期的数字脉冲去控制平均电压占空比越高等效电压越高转速越快。PWM周期要选得合适。频率太低电机会一顿一顿噪音也大频率太高超过电机换向和电路驱动能力又不一定响应。常见做法是选1kHz到20kHz这个项目用定时器0做1ms周期也就是1kHz基频实际驱动小直流电机手感很稳。计算初值时按12MHz晶振、方式1、12分频来算12MHz除以12得到1MHz即1个机器周期是1微秒1ms需要计数1000次所以初值是65536-100064536换成十六进制就是0xFC18。2.2 L298输入逻辑与四个状态的映射L298内部是H桥结构IN1和IN2控制电流方向ENA使能端控制整个通道是否供电。要转速可调就把PWM信号送到ENA把方向信号送到IN1/IN2。注意即便ENA为高如果IN1和IN2同为高电平也会形成内部刹车这和停止状态不一样。下面是整个调速逻辑的核心状态表。ENAIN1IN2电机行为0任意任意励磁关断自由滑行110正转速度受ENA占空比控制101反转速度受ENA占空比控制111急停/刹车两个下桥同时导通实际接线时把电机两端接到OUT1和OUT2电源正极接L298的VS电机电源逻辑电源接VSS。如果主控板是5V电机是12V也要保证两边共地否则IN1/IN2电平参考点不一致单片机说的高电平在L298看来可能完全不成立。这个共地问题在Proteus仿真里不点破很容易被忽略因为仿真默认所有地是同一个地。2.3 最小控制代码单定时器产生可调PWM在C51里生成PWM不需要复杂外设一个定时器中断配合计数器变量就够了。下面这一段是完整调速的基础后续按键加减速度也是改这个变量。sbit IN1 P1^0; sbit IN2 P1^1; sbit ENA P2^0; unsigned char speed_level 50; // 占空比等级 0~100 unsigned char pwm_counter 0; void Timer0_Init(void) { TMOD 0xF0; // 保留 T1 设置 TMOD | 0x01; // T0 方式116位定时器 TH0 0xFC; // 12MHz 下 1ms 定时初值高字节 TL0 0x18; // 初值低字节补充到 64536 ET0 1; // 打开 T0 中断 EA 1; // 打开总中断 TR0 1; // 启动定时器 } void Timer0_ISR(void) interrupt 1 { TH0 0xFC; // 重新装入初值 TL0 0x18; if (pwm_counter 100) // 周期分成 100 份 pwm_counter 0; if (pwm_counter speed_level) ENA 1; // 前 speed_level 份为高 else ENA 0; // 剩余部分为低 }这段代码的思路是让pwm_counter从0循环到99每个周期100份。speed_level代表高电平持续份数50就是50%占空比。以后加速减速只需要在按键代码里修改speed_level。这种方法比在每个中断里重新计算重装值稳定因为定时器周期始终固定只是高电平时长变化。代码里TMOD先与0xF0清除T0位再或0x01设置方式1这种写法避免了影响T1在多定时器工程里比较规范。3. Keil C51工程逐段拆解按键状态机、双定时器PWM和1602显示3.1 打开工程与源文件组成下载解压后拿着直流电机.uvproj直接双击Keil uVision4/uVision5都能识别这个工程文件。源程序主体是main.c另外那些.lst、.m51、.plg是编译过程产物不用提交留着也不影响。如果打开时提示找不到设备去Keil的Pack Installer里补装Atmel AT89C52芯片包。这里有个容易踩的坑Keil 5在安装C51编译器时如果只选了ARM打开51工程就会报toolchain not installed切到Manage Project Items把Toolchain改成C51 Compiler再指定C51路径即可。3.2 按键输入与运行状态机独立键盘在Proteus仿真里就是几个BUTTON引脚默认高电平按下为低。扫描代码要做去抖否则一次按下可能被当成多次。正常项目里按键处理不会放在主循环while里空转但这个51工程主要是教学用途直接延时去抖是最容易复现的写法。sbit KEY_ACC P3^0; // 加速 sbit KEY_DEC P3^1; // 减速 sbit KEY_FWD P3^2; // 正转 sbit KEY_REV P3^3; // 反转 sbit KEY_EN P3^4; // 启/停 void delay10ms(void) { unsigned char i, j; for (i 0; i 20; i) for (j 0; j 250; j); } void Key_Scan(void) { if (KEY_ACC 0) { delay10ms(); if (KEY_ACC 0) { if (speed_level 90) speed_level 5; while (!KEY_ACC); // 等待松开 } } if (KEY_DEC 0) { delay10ms(); if (KEY_DEC 0) { if (speed_level 10) speed_level - 5; while (!KEY_DEC); } } if (KEY_FWD 0) { delay10ms(); if (KEY_FWD 0) { IN1 1; IN2 0; while (!KEY_FWD); } } if (KEY_REV 0) { delay10ms(); if (KEY_REV 0) { IN1 0; IN2 1; while (!KEY_REV); } } if (KEY_EN 0) { delay10ms(); if (KEY_EN 0) { IN1 0; IN2 0; ENA 0; speed_level 0; while (!KEY_EN); } } }这里把速度范围限制在10到90是为了避免占空比过低时电机因死区电压不转也避免过高时电流冲击。按键后加上等待释放循环虽然会占用主循环但在这种单任务工程里是稳定可靠的。如果是真实产品建议改成检测下降沿加短去抖不等待释放。3.3 1602显示转速与PWM等级同步摘要里说1602要实时显示转速这个工程源码实际上是显示speed_level这个百分比数值也就是PWM占空比等级而不是真实RPM。如果后续要显示真实转速可以在电机轴上装编码器用计数器测速后换算。这里先看显示逻辑把speed_level拆成个位、十位、百位再写进1602。void LCD_WriteSpeed(void) { unsigned char buf[4]; buf[0] speed_level / 100; // 百位 buf[1] speed_level % 100 / 10; // 十位 buf[2] speed_level % 10; // 个位 LCD_WriteChar(0, 7, 0 buf[0]); LCD_WriteChar(0, 8, 0 buf[1]); LCD_WriteChar(0, 9, 0 buf[2]); LCD_WriteChar(0, 10, %); }写入位置是第0行第7列也就是1602屏幕第一行的Speed: 后三位显示。用LCD_WriteChar这个自定义函数的原因是为了避免在C51里使用sprintf后者会引入很重的库代码浪费51单片机本就不宽裕的Flash。如果确实需要格式化输出用指针逐步拼字符会更可控。3.4 编译生成hex文件的必须设置Keil里编译源文件后默认输出hex文件。如果没有生成需要在Options for Target 的 Output 页勾选Create HEX File否则Proteus仿真里加载不了编译结果。勾选后点击Rebuild工程目录下会出现直流电机.hex。那个.uvopt和.bak文件是环境配置和备份不是代码不用理会。3.5 一个容易被忽略的定时器冲突点如果同时用定时器0产生PWM、定时器1做测速主循环里千万不要对TMOD整体赋值比如TMOD 0x11。这种写法会同时覆盖T0和T1的工作方式假如T1已经被设置为计数模式就会瞬间变成定时器模式导致外部脉冲计数异常。规范化写法是在初始化里分别保留不修改的位就像前面第2章的TMOD 0xF0。4. Proteus仿真搭建与L298电机调试从原理图到虚拟示波器4.1 元件清单与引脚连接仿真文件里已经有完整的原理图这里给出从零搭建时需要用的元件和位置逻辑方便自己重画或排查接线。Proteus关键字元件说明接线要点AT89C52主控单片机Program File里加载hex文件Clock Frequency改成12MHzL298双H桥驱动器VS接电机电源正极VSS接5V逻辑电源MOTOR-DC直流电机两个引脚接L298的OUT1、OUT21602 LCD液晶屏RS、RW、E接P2.0~P2.2数据D4~D7接P0.4~P0.7BUTTON按键一端接P3口另一端接GND另一边接10k上拉到VCCCRYSTAL晶振12MHz两个震荡电容对地连接L298时特别注意ENA要接到P2.0上IN1和IN2分别接P1.0和P1.1。如果ENA直接接VCCPWM调速就失效了只剩方向控制这是仿真是最容易出现的“能转不能调速”问题。4.2 加载hex和设置时钟频率双击Proteus原理图里的AT89C52元件在Program File中选择工程目录下编译好的直流电机.hex。还要把Internal Clock改为12MHz这个值必须和Keil里定时器初值计算时用的晶振一致。如果这里保持默认的1MHz定时器1ms中断会变成6ms左右电机转速明显变慢按键加减速也像延迟了一样。4.3 用虚拟示波器检查ENA引脚波形在Proteus左侧工具栏选择“Virtual Instruments”找到模式中的Oscilloscope把探头接到ENA网络。运行时按下加速按键应该能看到波形高电平宽度逐渐变大占空比从10%向90%增加。如果按下按键后波形跳变过快可能是按键去抖时间不够或者主循环里没有等待释放导致一次按下扫描了多次。4.4 仿真失败时的排查顺序电机完全不动先看L298的VS和VSS是否都接电源再看AT89C52是否加载了hex文件。能转但不能调速ENA没有接单片机引脚或者PWM信号没从ENA进。按键按下没反应P3口是否接了上拉电阻Proteus里有些按键模型需要外部上拉才能保证高电平稳定。1602只亮不显示对比度引脚V0悬空导致的接一个10k电位器中心抽头接V0调节到第三行清晰即可。L298发热严重检查电机电源极性或者IN1/IN2是不是出现了同时为高的情况。排查的时候先从电源和共地开始再看信号网络标号是否一一对应最后才考虑代码逻辑。仿真里的问题绝大多数是网络连接错误而不是代码编译错。5. 把开环PWM闭环化的收尾编码器测速与占空比限幅技巧5.1 用外部中断做真实转速测量如果要把占空比显示改成真实电机转速最简洁的路子是给电机加一个测速码盘用光电开关输出脉冲把脉冲送到AT89C52的外部中断INT0引脚。每来一个脉冲进入一次外部中断对计数器加1再用定时器1做固定1秒的时间闸门每秒读一次计数并清零。unsigned long pulse_count 0; unsigned int current_rpm 0; void External0_ISR(void) interrupt 0 { pulse_count; // 码盘每转一圈产生的脉冲会被多次累计 } void Timer1_ISR(void) interrupt 3 { static unsigned char second_count 0; if (second_count 50) // 参数以实际定时器中断周期为准 { second_count 0; // 假设码盘单圈20个脉冲则每分钟转速为 秒计数*60/20 current_rpm (unsigned int)(pulse_count * 3); pulse_count 0; } }这里假设码盘是20线如果改为每圈200线的编码器把修正系数换成pulse_count * 60 / 200。实测时用转速表对比然后微调这个常数。因为这个工程原本是开环PWM控制加上测速后可以用一个最简单的比例控制实际转速低于目标值时提高speed_level高于目标值时降低speed_level。5.2 占空比限幅为什么比简单归零更实用按键减速把speed_level减到0L298输出完全没有PWM信号电机进入励磁关断状态如果此时电机还在转它会靠惯性滑行很久。更实用的急停是“制动”而不是“断电”也就是让IN1和IN2同时为高把电机两端短路利用电机制动效应迅速停下。如果只在代码里把speed_level归零就丢掉了这个刹车特性。5.3 最后的验证方法仿真里跑通后把AT89C52换成AT89C51也不需要改外围因为两个芯片在这个工程里兼容。唯一要重查的是AT89C51的RAM比AT89C52少了128字节如果显示模块用了数组缓冲区要留意内存占用。没有避免不了的问题只有不该省的定时器初值占空比分辨率如果用200份定时器初值和中断里的比较阈值都要同步改否则显示刷新速度会和PWM周期错位导致1602上的数字跳动明显不稳定。本文还有配套的精品资源点击获取