51单片机基础-PWM、频率与占空比

第三十一章 了解 PWM、频率与占空比

1. 导入

PWM(Pulse Width Modulation,脉宽调制)用固定频率的"高低脉冲"来等效控制模拟量(亮度、速度、功率)。核心三要素:

  • 频率 f(Hz)
  • 周期 T(s),与频率互为倒数:T=f1
  • 占空比 D(0~~1 或 0%~~100%),一周期内高电平所占比例

作用直观:

  • 驱动 LED:Vavg≈D⋅Vin,人眼积分效果呈亮度渐变
  • 直流电机:平均电压升降 → 转速可调(频率太低会抖,太高开关损耗大)
  • 有源/无源蜂鸣器:以音频频率开关 → 出声
  • 舵机:严格来说是"脉宽控制"而非固定占空比 PWM,但可用定时策略实现

8051 标准内核无硬件 PWM,常见做法:

  • 软件 PWM(定时器中断 + 比较)
  • 增强型 51(如 STC8)自带 PWM 外设(进阶再述)

2. 基本概念与公式

  • 频率与周期:f=T1
  • 占空比:D=Tthigh
  • 平均电压(慢变化、经负载/RC 滤波):Vavg≈D⋅Vin
  • 分辨率与步距(N 位 PWM):步长 ΔD=2N1

软件 PWM 的资源消耗来自"中断频率":

  • 若将一周期离散成 STEPS 份,每步中断一次,则中断频率 fISR=fPWM×STEPS
  • 例:想要 LED 200 Hz 且 64 级亮度 → fISR=200×64=12.8 kHz(在 11.0592MHz 8051 上可行)
  • 想要 1 kHz 且 256 级 → 256 kHz ISR(在标准 8051 上不可行)

结论:软件 PWM 要在"频率"和"分辨率"间折中;电机驱动高频 PWM(>10kHz)更适合硬件 PWM 或专用驱动。


3. 经典 8051 软件 PWM 方案

3.1 固定频率比较型(多通道 LED 调光)

  • 用定时器周期中断作为"时间片"
  • 设一个递增计数 slot(0...STEPS-1),每通道若 slot < duty 则输出高,否则低
  • 优点:占空比直观、频率稳定;缺点:ISR 频率=频率×步数

下例配置:Fosc=11.0592MHz,目标 f_PWM=200 HzSTEPS=64(≈12.8k 中断/秒,可驱动多通道 LED,肉眼无闪)

c 复制代码
#include <reg52.h>
/* 目标: PWM 200Hz, 64级; Fosc=11.0592MHz
   中断频率 f_ISR = 200*64 = 12800 Hz
   Timer0方式1 16位:
   Tmc = 12/Fosc ≈ 1.085us
   中断间隔 Ts = 1/f_ISR ≈ 78.125us
   计数 = Ts/Tmc ≈ 72 → Reload = 65536-72 = 0xFFB8 (TH0=0xFF, TL0=0xB8) */

sbit PWM1 = P1^0;   // 高电平=点亮(按接法可反相)
sbit PWM2 = P1^1;
sbit PWM3 = P1^2;
sbit PWM4 = P1^3;

#define PWM_STEPS 64

volatile unsigned char slot = 0;       // 0..63
volatile unsigned char d1=0, d2=0, d3=0, d4=0; // 各通道占空(0..64)

void timer0_init_pwm_isr(void){
    TMOD = (TMOD & 0xF0) | 0x01; // T0方式1
    TH0 = 0xFF; TL0 = 0xB8;      // 约 78us
    ET0 = 1; EA = 1; TR0 = 1;
}

void set_duty(unsigned char ch, unsigned char duty){ // duty:0..64
    if(duty > PWM_STEPS) duty = PWM_STEPS;
    switch(ch){
        case 1: d1 = duty; break;
        case 2: d2 = duty; break;
        case 3: d3 = duty; break;
        case 4: d4 = duty; break;
    }
}

void t0_isr(void) interrupt 1 {
    TH0 = 0xFF; TL0 = 0xB8;

    // 比较输出
    PWM1 = (slot < d1) ? 1 : 0;
    PWM2 = (slot < d2) ? 1 : 0;
    PWM3 = (slot < d3) ? 1 : 0;
    PWM4 = (slot < d4) ? 1 : 0;

    // 递增时间片
    slot++;
    if(slot >= PWM_STEPS) slot = 0;
}

/* 简易延时 */
void delay_ms(unsigned int ms){ unsigned int i,j; for(i=0;i<ms;i++) for(j=0;j<125;j++); }

void main(void){
    PWM1 = PWM2 = PWM3 = PWM4 = 0;
    timer0_init_pwm_isr();

    // 呼吸渐变演示(四路相位错开)
    while(1){
        unsigned char x;
        for(x=0; x<=PWM_STEPS; x++){
            set_duty(1, x);
            set_duty(2, (x+16) % (PWM_STEPS+1));
            set_duty(3, (x+32) % (PWM_STEPS+1));
            set_duty(4, (x+48) % (PWM_STEPS+1));
            delay_ms(20);
        }
    }
}

小贴士:

  • 接法决定"高/低有效"。若 LED 用"灌电流"(P1→LED→电阻→VCC,低电平亮),把输出逻辑取反即可。
  • 多通道越多、STEPS 越大,中断时间越紧;必要时减小 STEPS 或降低频率。

3.2 误差累加(Δ-Σ)"伪 PWM"(低中断率,视觉级调光)

  • 每次系统节拍(如 1ms)做一次:acc += duty; if(acc>=MAX){输出=1; acc-=MAX;} else 输出=0;
  • 平均占空比接近 duty/MAX,但瞬时周期不固定(抖动分散在时间上)
  • 适合 LED 视觉调光;不适合电机等要求固定频率负载

示例(单通道,1ms 节拍,MAX=255):

c 复制代码
#include <reg52.h>
sbit LED = P1^0; // 高电平亮(按接法调)
volatile unsigned char duty = 128; // 0..255
volatile unsigned int  ms = 0;
static unsigned int acc = 0;

void t0_init_1ms(void){ // 11.0592MHz -> TH0/TL0=0xFC66
    TMOD = (TMOD & 0xF0) | 0x01;
    TH0=0xFC; TL0=0x66;
    ET0=1; EA=1; TR0=1;
}
void t0_isr(void) interrupt 1{
    TH0=0xFC; TL0=0x66;
    ms++;
    acc += duty;
    if(acc >= 256){ LED=1; acc -= 256; }
    else          { LED=0; }
}
void delay_ms(unsigned int d){ unsigned int t=ms; while((unsigned int)(ms-t)<d); }

void main(void){
    LED=0; t0_init_1ms();
    while(1){
        unsigned char x;
        for(x=0;x<255;x++){ duty=x; delay_ms(10); }
        for(x=255;x>0;x--){ duty=x; delay_ms(10); }
    }
}

优点:ISR 仅 1kHz,CPU 负担极小;缺点:瞬时闪烁频率不恒定(但人眼通常不敏感)。


4. 常见应用

4.1 LED 调光

  • 推荐 150500 Hz(避免肉眼闪烁),32128 级即可平滑
  • 多路时注意总电流,优先"灌电流"接法或用 ULN2003/三极管

4.2 直流电机调速

  • 频率高于可闻频段(>18~20 kHz)更安静
  • 纯软件 PWM 很难到 20 kHz×高分辨率;建议:
    • 用增强型 51 硬件 PWM,或
    • 用带 PWM 的驱动芯片(如 TB6612、L298N 用外部占空波形),或
    • 牺牲分辨率(如 20 kHz × 8 级仍要 160 kHz ISR,依然吃紧)

4.3 蜂鸣器/有源蜂鸣器"音调"

  • 无源蜂鸣器:直接用定时器翻转引脚得到方波频率 f,非占空比意义上的 PWM
  • 有源蜂鸣器:只需拉低/拉高即可响,PWM 可用于"音量"粗调(有源的不太适合)

示例(Timer1 方式2,自动重装,输出方波到 P3.7):

c 复制代码
#include <reg52.h>
sbit BUZZ = P3^7;  // 连接无源蜂鸣器

/* 设定方波频率f(Hz),自动计算TH1/TL1(方式2 8位自动重装)
   Fosc=11.0592MHz → Tmc=1.085us → T1计数为 256*(12/Fosc)每溢出
   半周期计数 N = (Fosc/12) / (2*f*256),求 TH1=256-N */
void buzzer_set_freq(unsigned int f){
    unsigned int N;
    if(f==0) { TR1=0; BUZZ=0; return; }
    N = (11059200UL/12) / (2UL * f * 256UL); // 近似
    if(N>255) N=255; if(N<1) N=1;
    TH1 = 256 - (unsigned char)N;
    TL1 = TH1;
    TR1 = 1;
}
void t1_isr(void) interrupt 3 { BUZZ = !BUZZ; } // 每溢出翻转一次 → 方波

void buzzer_init(void){
    TMOD = (TMOD & 0x0F) | 0x20; // T1方式2
    ET1 = 1; EA = 1;
}
void delay_ms(unsigned int ms){ unsigned int i,j; for(i=0;i<ms;i++) for(j=0;j<125;j++); }

void main(void){
    buzzer_init();
    // 简单音阶
    const unsigned int notes[] = {262,294,330,349,392,440,494,523};
    unsigned char i;
    while(1){
        for(i=0;i<8;i++){ buzzer_set_freq(notes[i]); delay_ms(300); }
        buzzer_set_freq(0); delay_ms(500);
    }
}

4.4 舵机(50Hz,1~2ms 脉宽)

  • 标准 50Hz(周期 20ms),高电平脉宽 1.02.0ms 对应 0180°
  • 实现要点:每 20ms 输出一次高脉冲,脉宽按角度计算

简洁实现(独立工程使用 Timer0 微秒级计数,阻塞式演示):

c 复制代码
#include <reg52.h>
sbit SERVO = P1^0;

/* T0方式1,1us tick(近似,需12MHz更易匹配;11.0592MHz误差可接受演示) */
void t0_init_1us(void){  // 每次装载使中断间隔≈1us
    TMOD = (TMOD & 0xF0) | 0x01;
    TR0 = 0;
}
void delay_us(unsigned int us){
    while(us--){
        TH0 = 0xFF; TL0 = 0xF0; // 约16计数 → ~1us(粗略,按Fosc微调)
        TF0 = 0; TR0 = 1;
        while(!TF0);
        TR0 = 0;
    }
}
void delay_ms(unsigned int ms){ while(ms--) delay_us(1000); }

/* 角度→脉宽(us):1000~2000 */
unsigned int angle_to_us(unsigned char angle){
    return 1000 + (unsigned int)angle * 1000 / 180;
}

void servo_write(unsigned char angle){
    unsigned int pw = angle_to_us(angle);
    // 周期20ms:先高 pw 微秒,再低 (20000-pw) 微秒
    SERVO = 1; delay_us(pw);
    SERVO = 0; delay_us(20000 - pw);
}

void main(void){
    t0_init_1us();
    while(1){
        unsigned char a;
        for(a=0;a<=180;a+=5)  servo_write(a);
        for(a=180;a>0; a-=5)  servo_write(a);
    }
}

说明:此法为"阻塞式",适合简单演示。工程中建议用定时器中断分时调度 20ms 周期与高电平段。


5. 频率、分辨率与负载建议

  • LED:200500 Hz,32128 级,追求"稳态亮度"即可
  • 电机:>10 kHz 避免啸叫;软件 PWM 难以胜任,尽量用硬件 PWM/驱动器
  • 加热丝/电磁阀:频率可低(几十~几百 Hz),但注意电磁噪声与平均功耗
  • 舵机:固定 50 Hz、脉宽 1~2ms,非"占空比"意义上的普通 PWM

6. 调试与排错

  • 闪烁/抖动明显:频率太低;ISR 间隔抖动;多任务打断太多
  • 串音/毛刺:同口多位同时切换、电源去耦不足;增加串联电阻/缓速;驱动能力不足
  • CPU 占用高:STEPS 过大、通道太多;改用 Δ-Σ、降低分辨率或用硬件 PWM
  • 舵机乱跳:脉宽不稳、周期不准;供电不足(舵机电源须独立稳压与地汇接)

7. 小结

  • 掌握 PWM 三要素与软件实现权衡:fISR=fPWM×STEPS
  • 给出两种软件实现:固定频率比较型(多路 LED 调光)与 Δ-Σ 误差累加(低中断率)
  • 覆盖蜂鸣器方波与舵机脉宽控制的实用范式
  • 对于高频/大功率负载,优先选用硬件 PWM 或外部驱动

相关推荐
逆小舟2 小时前
【STM32】智能排队控制系统
stm32·单片机·嵌入式硬件
清风6666662 小时前
基于单片机的楼道声光人体红外智能控制灯设计
单片机·毕业设计·课程设计·期末大作业
GilgameshJSS2 小时前
STM32H743-ARM例程38-UART-IAP
c语言·arm开发·stm32·单片机·嵌入式硬件
清风6666664 小时前
基于单片机的交流功率测量仪设计与实现
单片机·嵌入式硬件·毕业设计·课程设计·期末大作业
做一道光6 小时前
6、foc控制——IF控制
笔记·单片机·嵌入式硬件·电机控制
Jerry丶Li7 小时前
二十三、STM32的ADC(三)(ADC多通道)
stm32·单片机·嵌入式硬件
d111111111d7 小时前
STM32外设学习--TIM定时器--编码器接口(程序)
笔记·stm32·嵌入式硬件·学习
辰哥单片机设计7 小时前
STM32项目分享:水质检测系统(升级版)
stm32·单片机·嵌入式硬件
straw_hat.10 小时前
32HAL——RTC时钟
stm32·学习