一、PWM概念
PWM(Pulse Width Modulation,脉宽调制) 是一种通过调节脉冲的占空比(高电平时间占整个周期的比例)来模拟不同电压或功率输出的技术。
-
频率(Frequency):1秒内脉冲重复的次数(单位:Hz)。 频率=1/Ts
-
占空比(Duty Cycle):高电平时间占周期的百分比(范围:0%~100%)。 占空比=Ton/Ts
-
分辨率:占空比的最小调节步长(由定时器位数决定)。
-
精度:占空比变化的步距。
二、51单片机实现PWM
51单片机通常无硬件PWM模块,需通过软件模拟或定时器+IO口实现:
1.纯软件模拟PWM
(占用CPU资源,精度低,频率受限)
#include <reg51.h>
#include "delay.h"
void main(void)
{
unsigned char t;
while(1)
{
for(t=0;t<100;t++)
{
P1 = 0xFF; //P1口为高电平
delay(t); //高电平时间
p1 = 0x00; //P1口为高电平
delay(100-t); //高电平时间,假设周期为100
}
}
}
2.定时器+IO口实现PWM
(精度高,可后台运行,支持多通道)
#include <reg51.h>
#include "delay.h"
void timer0_init(void)
{
TMOD &= 0xF0;
TMOD |= 0x01; //设置定时器模式
TL0 = 0x9C;
TH0 = 0xFF; //设置初值
TF0 = 0; //清除中断标志
TR0 = 1; //开始计时
ET0 = 1; //开启定时中断
EA = 1; //开启总中断
PT0 = 0; //选择优先级
}
unsigned char counter = 0;
unsigned char compare =50; //设置占空比50
void main(void)
{
timer0_init();
while(1)
{
}
}
void timer0_ISR() interrupt 1 //中断函数
{
TL0 = 0x9C;
TH0 = 0xFF; //重装初值
counter ++;
conter %= 100; //当conter为100时清零
if(conter<compre)
{
P1 = 0; //P1口输出低电平
}
else
{
P1 = 1; //P1口输出高电平
}
}
三、PWM应用
1.LED呼吸灯
#include <reg51.h>
#include <intrins.h>
sbit LED = P1^0; // LED连接P1.0
volatile unsigned int PWM_Duty = 0; // 占空比(0~100)
volatile bit Direction = 0; // 呼吸方向:0递增,1递减
// 定时器0初始化(模式2,自动重载)
void Timer0_Init()
{
TMOD &= 0xF0; // 清除T0原有配置
TMOD |= 0x02; // 模式2(8位自动重载)
TH0 = 0x9C; // 重载值,100us中断一次(12MHz晶振)
TL0 = 0x9C; // 初始值
ET0 = 1; // 允许定时器0中断
EA = 1; // 全局中断使能
TR0 = 1; // 启动定时器0
}
// 定时器0中断服务函数
void Timer0_ISR() interrupt 1
{
static unsigned int counter = 0;
counter++;
if (counter >= 100) counter = 0; // PWM周期=100*100us=10ms(100Hz)
LED = (counter < PWM_Duty) ? 1 : 0; // 根据占空比控制LED亮灭
}
// 简单延时函数(12MHz晶振)
void Delay_ms(unsigned int ms)
{
unsigned int i, j;
for (i = 0; i < ms; i++)
for (j = 0; j < 114; j++);
}
void main()
{
Timer0_Init(); // 初始化定时器
while (1)
{
// 调整占空比,实现呼吸效果
if (!Direction) {
PWM_Duty++; // 渐亮
if (PWM_Duty >= 100) Direction = 1;
} else {
PWM_Duty--; // 渐暗
if (PWM_Duty <= 0) Direction = 0;
}
// 控制呼吸速度(约每20ms调整一次)
Delay_ms(20); // 使用简单延时或定时器优化
}
}
2.PWM驱动直流电机
#include <reg51.h>
sbit ENA = P1^0; // PWM使能端
sbit IN1 = P1^1; // 方向控制1
sbit IN2 = P1^2; // 方向控制2
volatile unsigned char PWM_Duty = 0; // 占空比(0-100)
// 定时器0初始化(模式2自动重载,100Hz PWM)
void Timer0_Init() {
TMOD &= 0xF0; // 清除T0配置
TMOD |= 0x02; // 模式2(8位自动重载)
TH0 = 0x9C; // 100us中断(12MHz晶振)
TL0 = 0x9C;
ET0 = 1; // 允许定时器0中断
EA = 1; // 全局中断使能
TR0 = 1; // 启动定时器
}
// 定时器0中断服务函数
void Timer0_ISR() interrupt 1 {
static unsigned char counter = 0;
counter++;
if(counter >= 100) counter = 0;
ENA = (counter < PWM_Duty) ? 1 : 0; // 输出PWM
}
// 设置电机转速和方向
void Motor_Control(unsigned char speed, bit direction)
{
PWM_Duty = speed; // 速度控制(0-100)
if(direction) { // 正转
IN1 = 1;
IN2 = 0;
} else { // 反转
IN1 = 0;
IN2 = 1;
}
}
// 停止电机
void Motor_Stop()
{
IN1 = 0;
IN2 = 0;
PWM_Duty = 0;
}
void main()
{
Timer0_Init(); // 初始化PWM
Motor_Control(70, 1); // 70%速度正转
Delay_ms(3000); // 运行3秒
Motor_Control(50, 0); // 50%速度反转
Delay_ms(2000);
Motor_Stop(); // 停止
while(1);
}
3.PWM控制舵机
舵机通过脉冲宽度调制(PWM)控制旋转角度,其核心参数如下:
工作周期:通常为 20ms(50Hz),部分高速舵机支持10ms(100Hz)。
脉冲宽度范围:
0.5ms → 0°(最小角度)
1.5ms → 90°(中位)
2.5ms → 180°(最大角度)
控制精度:多数舵机分辨率为 1°~2°,高端舵机可达0.5°。
#include <reg51.h>
sbit Servo_PWM = P1^0; // 舵机信号线接P1.0
unsigned int PWM_Width = 1500; // 初始脉冲宽度1.5ms(90°)
// 简易延时函数(12MHz)
void Delay_ms(unsigned int ms)
{
unsigned int i, j;
for(i=0; i<ms; i++)
for(j=0; j<114; j++);
}
// 定时器0初始化(模式1,16位定时器)
void Timer0_Init()
{
TMOD &= 0xF0; // 清除T0配置
TMOD |= 0x01; // 模式1(16位定时器)
TH0 = (65536 - 1000) / 256; // 1ms中断(12MHz晶振)
TL0 = (65536 - 1000) % 256;
ET0 = 1; // 允许定时器0中断
EA = 1; // 全局中断使能
TR0 = 1; // 启动定时器
}
volatile unsigned int count = 0;
void Timer0_ISR() interrupt 1
{
TH0 = (65536 - 1000) / 256; // 重载1ms初值
TL0 = (65536 - 1000) % 256;
count++;
if(count <= PWM_Width / 1000) { // 高电平时间(单位:ms)
Servo_PWM = 1;
} else {
Servo_PWM = 0;
}
if(count >= 20) count = 0; // 周期20ms(50Hz)
}
// 设置舵机角度(0°~180°)
void Set_Servo_Angle(unsigned char angle)
{
if(angle > 180) angle = 180;
PWM_Width = 500 + angle * 11.11; // 公式:0.5ms + angle*(2ms/180)
// 500=0°, 2500=180°(实际值需校准)
}
// 示例:让舵机从0°到180°扫描
void main()
{
Timer0_Init();
while(1) {
for(int i=0; i<=180; i++) {
Set_Servo_Angle(i);
Delay_ms(20); // 每20ms移动1°
}
for(int i=180; i>=0; i--) {
Set_Servo_Angle(i);
Delay_ms(20);
}
}
}