目录
[1 PWM信号](#1 PWM信号)
[1.1 三个最基本的量](#1.1 三个最基本的量)
[1.1.1 周期 T(Period)](#1.1.1 周期 T(Period))
[1.1.2脉冲宽度 Th(High Time)](#1.1.2脉冲宽度 Th(High Time))
[1.1.3占空比 D(Duty Cycle)](#1.1.3占空比 D(Duty Cycle))
[1.2 为什么要用 PWM](#1.2 为什么要用 PWM)
[1.3 关键参数对照表](#1.3 关键参数对照表)
[1.4单片机里产生 PWM 的四种套路](#1.4单片机里产生 PWM 的四种套路)
[1.4.1 纯软件延时](#1.4.1 纯软件延时)
[1.4.2 定时器中断计数](#1.4.2 定时器中断计数)
[1.4.3 定时器硬件 PWM 模块](#1.4.3 定时器硬件 PWM 模块)
[1.4.4 专用 PWM IC 或驱动板](#1.4.4 专用 PWM IC 或驱动板)
[1.5 计算示例(AT89C52,12 MHz,定时器 0 模式 1)](#1.5 计算示例(AT89C52,12 MHz,定时器 0 模式 1))
[1.6 常见踩坑提醒](#1.6 常见踩坑提醒)
[1.7 一句话总结](#1.7 一句话总结)
[2 AT89C52单片机实现舵机控制](#2 AT89C52单片机实现舵机控制)
[2.1 舵机专用 PWM(50 Hz)](#2.1 舵机专用 PWM(50 Hz))
[2.2 电路原理图](#2.2 电路原理图)
[2.3 控制程序](#2.3 控制程序)
[3 AT89C52单片机实现直流电机控制](#3 AT89C52单片机实现直流电机控制)
[3.1.3控制逻辑真值表(电机 A 为例)](#3.1.3控制逻辑真值表(电机 A 为例))
[3.1.4 AT89C52 示例代码(定时器 0 产生 1 kHz PWM)](#3.1.4 AT89C52 示例代码(定时器 0 产生 1 kHz PWM))
[3.1.5 常见坑 & 快速排查](#3.1.5 常见坑 & 快速排查)
[3.2 单片机实现直流电机控制电路原理图](#3.2 单片机实现直流电机控制电路原理图)
[3.3 单片机实现直流电机控制控制程序](#3.3 单片机实现直流电机控制控制程序)
**摘要:**本文系统介绍了PWM技术原理及其在舵机和直流电机控制中的应用。PWM通过调节脉冲宽度实现模拟量控制,关键参数包括周期T、高电平时间Th和占空比D。文中详细阐述了AT89C52单片机实现PWM的四种方法,重点展示了50Hz舵机控制(0.5-2.5ms脉宽对应-90°至+90°)和L298N驱动直流电机的具体实现方案,包含完整的电路原理图和C语言程序代码。特别指出应用中需注意的周期选择、驱动能力、电平匹配等常见问题,并提供了参数计算示例和调试建议。
1 PWM信号
PWM(Pulse Width Modulation,脉冲宽度调制)是一种用「数字脉冲」去等效「模拟量」的技术。
1.1 三个最基本的量
1.1.1 周期 T(Period)
一个完整脉冲循环的时间,单位 μs 或 ms。
例:舵机 50 Hz → T = 20 ms。
1.1.2脉冲宽度 Th(High Time)
一个周期内输出高电平的时间。
例:舵机 1.5 ms。
1.1.3占空比 D(Duty Cycle)
D = Th / T × 100 %
例:1.5 ms / 20 ms = 7.5 %。
1.2 为什么要用 PWM
1 数字口只有 0/1,无法直接输出 3.3 V 的一半电压。
2 通过高速开关,让「平均电压」等于目标值:
Vavg = Vhigh × D
例:5 V、占空比 50 % → 等效 2.5 V(电机速度减半、LED 半亮)。
1.3 关键参数对照表
|------------|--------------|----------------------|------------|------------------|
| 应用 | 周期 T | 常用 Th 范围 | 分辨率要求 | 备注 |
| LED 调光 | 1~5 kHz | 0~100 % | 8 bit | 频率太低会闪 |
| 直流电机调速 | 10~20 kHz | 0~100 % | 8~10 bit | 频率>20 kHz 避人耳噪声 |
| 舵机控制 | 20 ms | 0.5~2.5 ms | 1 μs | 50 Hz 固定 |
| 开关电源(Buck) | 50 k~2 MHz | 根据拓扑 | 10~12 bit | MOSFET 驱动 |
1.4单片机里产生 PWM 的四种套路
1.4.1 纯软件延时
for/while + delay → 最简单、最不准、CPU 100 % 占用。
1.4.2 定时器中断计数
每 x μs 进一次中断,累加变量 → 精度高,可做多通道。
1.4.3 定时器硬件 PWM 模块
51 系列没有,AVR/STM32 有 OCR/ARR 寄存器,直接硬件输出。
1.4.4 专用 PWM IC 或驱动板
PCA9685(I²C 16 路)、DRV8833 等,省 IO,精度高。
1.5 计算示例(AT89C52,12 MHz,定时器 0 模式 1)
目标:50 Hz,1 μs 分辨率
计数频率:12 MHz / 12 = 1 MHz → 1 μs/次
20 ms 对应计数:20 000 次
16 位定时器最大 65 536,够用。
步骤:
设 TH0 = (65536-20000)/256,TL0 = ... 先装 20 ms 周期。
再开一个「比较寄存器」存放 Th(如 1500)。
中断里计数到 Th 翻转 IO,到 20 000 再翻转 IO,循环即可。
1.6 常见踩坑提醒
周期太低 → LED/电机肉眼可见抖动或蜂鸣。
Th 精度太差 → 舵机抖动、稳速电机转速漂移。
IO 驱动能力不足 → 加三极管/MOSFET 做缓冲。
舵机高电平 5 V,STM32 单片机 3.3 V 输出时,最好加 NPN 或电平转换。
1.7 一句话总结
PWM = 「固定周期 + 可变高电平」,用数字开关把平均电压/角度/速度「模拟化」。掌握 T、Th、D 三要素,再挑合适的「生成套路」,任何 PWM 场景都能秒拆。
2 AT89C52单片机实现舵机控制
2.1 舵机专用 PWM(50 Hz)
周期:T = 20 ms(50 Hz)
有效高电平:0.5 ms ~ 2.5 ms(有些舵机 0.4 ms ~ 2.6 ms)
角度映射:
0.5 ms → −90°
1.5 ms → 0°
2.5 ms → +90°
分辨率:舵机内部一般 1 µs 对应 0.09°,所以 10 µs 步进肉眼基本看不出抖动。
2.2 电路原理图

如上图所示,AT89C52单片机实现舵机控制电路由AT89C52单片机、晶振及复位电路、舵机、控制开关和示波器组成。通过PWM信号实现舵机转角的控制,不同开关导通情况下P2.5控制信号的占空比不同,从而实现舵机转角的控制。
2.3 控制程序
objectivec
//头文件与为位定义
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar Compare=200;//50HZ 抑芷?0ms
uint ZHK;//PWM信号占空比
sbit k2=P2^0;//-90度
sbit k3=P2^1;//0度
sbit k4=P2^2;//90度
sbit k1=P2^5;//舵机控制信号
//定时器T0产生 100μs 中断
void Timer0_init()//100us
{
TMOD= 0x01; //设置定时器模式
TL0 = 0x9B; //设置定时初值低位
TH0 = 0xFF; //设置定时初值高位 差值表示中断周期
ET0=1;
EA=1;
TR0=1;//启动定时器
}
//定时器延时函数
void Delay(unsigned int ms)
{
unsigned int i;
for (i = 0; i < ms; i++)
{
while (!TF0); // 等待定时器T0溢出
TF0 = 0; // 清除溢出标志
}
}
//定时器T0中断服务函数
void Timer0() interrupt 1
{
TL0 = 0x9B; //设置定时初值低位
TH0 = 0xFF; //设置定时初值高位
}
//舵机转到-90度
void chushi()
{
ZHK=196;
k1=0;
Delay(ZHK);
k1=1;
Delay(Compare-ZHK);
}
//舵机转到0度
void zhongjian()
{
ZHK=186;
k1=0;
Delay(ZHK);
k1=1;
Delay(Compare-ZHK);
}
//舵机转到90度
void zuizhong()
{
ZHK=175;
k1=0;
Delay(ZHK);
k1=1;
Delay(Compare-ZHK);
}
//主函数
void main()
{
Timer0_init();
while(1)
{
if(k2==0)
{
chushi();
}
else if(k3==0)
{
zhongjian();
}
else if(k4==0)
{
zuizhong();
}
}
}
3 AT89C52单片机实现直流电机控制
3.1L298N电机驱动

3.1.1它到底是什么
一块"双H-桥"功率芯片(ST 原装 L298N)+ 外围电路做成的驱动板。
可同时驱动:
两台直流有刷电机,或一台四线两相步进电机。
极限参数:逻辑 5 V 供电,驱动端 5-35 V(建议 ≤24 V 长期工作),连续 2 A/桥,峰值 3 A。
3.1.2引脚速记
Power 区域
VS→ 外接 7-24 V 电机电源
GND→ 与单片机共地
VCC→ 三种用法
① 板上跳线帽插上:内部线性稳压给逻辑 5 V(仅当 VIN ≤12 V 时可用);
② 拔掉跳线帽:由外部单独 5 V 供逻辑;
③ 反向输出:板上稳压 5 V 可引出来给单片机供电(仍要求 VIN ≤12 V)。
Motor 区域
OUT1/OUT2→ 电机 A
OUT3/OUT4→ 电机 B
Logic 区域
IN1/IN2/ENA→ 控制电机 A 的方向与使能
IN3/IN4/ENB→ 控制电机 B
(ENA/ENB 接 PWM 即可调速)
3.1.3控制逻辑真值表(电机 A 为例)
|---------|---------|---------|--------|
| ENA | IN1 | IN2 | 结果 |
| 0 | X | X | 刹车(自由) |
| 1 | 1 | 0 | 正转 |
| 1 | 0 | 1 | 反转 |
| 1 | 1 | 1 | 快速刹车 |
| PWM | 1/0 | 0/1 | 调速+方向 |
3.1.4 AT89C52 示例代码(定时器 0 产生 1 kHz PWM)
objectivec
#include <reg52.h>
sbit IN1 = P1^0;
sbit IN2 = P1^1;
sbit ENA = P1^2; // 接 L298N 的 ENA
bit pwm_out = 0;
unsigned char high = 200; // 占空比 200/255 ≈ 78 %
void Timer0_Init()
{
TMOD &= 0xF0; // 模式 2,8 位自动重装
TH0 = 256-100; // 100*1us = 100 us → 10 kHz
TL0 = 256-100;
ET0 = 1; EA = 1;
TR0 = 1;
}
void Timer0_ISR() interrupt 1
{
static unsigned char cnt = 0;
cnt++;
if(cnt == 0) pwm_out = 1; // 周期起点
if(cnt == high) pwm_out = 0;
ENA = pwm_out;
}
void main()
{
IN1 = 1; IN2 = 0; // 方向:正转
Timer0_Init();
while(1)
{
/* 可在此修改 high 值调速,或改 IN1/IN2 换向 */
}
}
3.1.5 常见坑 & 快速排查
|------------|--------------------------------|
| 现象 | 90 % 原因与对策 |
| 电机不转,指示灯不亮 | 没给 +12V;跳线帽错;共地问题 |
| 电机抖动、咔咔响 | 电源功率不足;PWM 频率太低 |
| 芯片过热、保护 | 电流 >2 A;散热片没装;占空比 100 % 长时间堵转 |
| 逻辑失控 | 单片机 5 V 与驱动板 5 V 没共地;ENA 悬空 |
3.2 单片机实现直流电机控制电路原理图

如上图所示,AT89C52单片机实现直流电机控制电路由AT89C52单片机、L298N电机驱动、开关控制电路、晶振及复位电路、输出电压显示、示波器和直流电机组成。通过软件设置不同占空比的PWM信号实现直流电机转速的控制,再通过L298N电机驱动的驱动可以实现直流电机正转、反转、停止、加速和减速的控制。
3.3 单片机实现直流电机控制控制程序
objectivec
//头文件与为位定义
#include <reg52.h>
#define uint unsigned int
#define uchar unsigned char
uchar Compare=20;//50HZ 一周期20ms
uint ZHK1;//正转占空比
uint ZHK2;//反转占空比
sbit IN1=P1^0;
sbit IN2=P1^1;
sbit ENA=P1^2;
sbit k0=P2^0;//正转
sbit k1=P2^1;//反转
sbit k2=P2^2;//停止
sbit k3=P2^3;//正传加速
sbit k4=P2^4;//正传减速
sbit k5=P2^5;//反转加速
sbit k6=P2^6;//反转减速
//定时器T0产生 1000μs 中断
void Timer0_init()//1000us
{
TMOD= 0x01; //设置定时器模式
TL0 = 0x17; //设置定时初值低位
TH0 = 0xFC; //设置定时初值高位 差值表示中断周期
ET0=1;
EA=1;
TR0=1;//启动定时器
}
//定时器延时函数
void Delay(unsigned int ms)
{
unsigned int i;
for (i = 0; i < ms; i++)
{
while (!TF0); // 等待定时器T0溢出
TF0 = 0; // 清除溢出标志
}
}
//定时器T0中断服务函数
void Timer0() interrupt 1
{
TL0 = 0x17; //设置定时初值低位
TH0 = 0xFC; //设置定时初值高位
}
//电机正转
void DJZHZH()
{
ENA=1;
ZHK1=10;
IN1=1;
IN2=0;
Delay(ZHK1);
IN1=0;
IN2=0;
Delay(Compare-ZHK1);
}
//电机反转
void DJFZH()
{
ENA=1;
ZHK2=10;
IN1=0;
IN2=1;
Delay(ZHK2);
IN1=0;
IN2=0;
Delay(Compare-ZHK2);
}
//停
void DJT()
{
ENA=0;
TR0=0;
IN1=0;
IN2=0;
}
//正转加速
void DJZHZH1()
{
ENA=1;
ZHK1=18;
IN1=1;
IN2=0;
Delay(ZHK1);
IN1=0;
IN2=0;
Delay(Compare-ZHK1);
}
//电机反转加速
void DJFZH1()
{
ENA=1;
ZHK2=18;
IN1=0;
IN2=1;
Delay(ZHK2);
IN1=0;
IN2=0;
Delay(Compare-ZHK2);
}
//电机正传减速
void DJZHZH2()
{
ENA=1;
ZHK1=2;
IN1=1;
IN2=0;
Delay(ZHK1);
IN1=0;
IN2=0;
Delay(Compare-ZHK1);
}
//电机反转减速
void DJFZH2()
{
ENA=1;
ZHK2=2;
IN1=0;
IN2=1;
Delay(ZHK2);
IN1=0;
IN2=0;
Delay(Compare-ZHK2);
}
void main()
{
DJT();
Timer0_init();
while(1)
{
if(k0==0)//正转
{
DJZHZH();
}
else if(k1==0)//反转
{
DJFZH();
}
else if(k2==0) //停止
{
DJT();
}
else if(k3==0)//正转加速
{
DJZHZH1();
}
else if(k5==0)//反转加速
{
DJFZH1();
}
else if(k4==0)//正转减速
{
DJZHZH2();
}
else if(k6==0)//反转减速
{
DJFZH2();
}
}
}