单片机:定时器/PWM 配置 - 呼吸灯效果

单片机:定时器/PWM 配置 - 呼吸灯效果

1. PWM 原理简介

基本概念

  • PWM(脉冲宽度调制):通过改变脉冲的宽度(占空比)来控制平均电压

  • 占空比​ = 高电平时间 / 周期时间 × 100%

  • 频率​ = 1 / 周期时间

呼吸灯原理

通过逐渐改变PWM的占空比,使LED的亮度从暗到亮再到暗循环变化。

2. 硬件连接

复制代码
LED → 限流电阻 → 单片机PWM引脚
(通常使用具有PWM功能的定时器通道引脚)

3. 51单片机PWM实现(软件模拟)

使用定时器产生PWM

复制代码
#include <REG52.H>
#include <INTRINS.H>

sbit LED = P1^0;  // LED连接到P1.0

unsigned char pwm_duty = 0;  // 占空比 0-255
bit direction = 0;          // 方向标志:0递增,1递减

// 定时器0初始化
void Timer0_Init() {
    TMOD &= 0xF0;  // 设置定时器0模式
    TMOD |= 0x01;  // 定时器0工作模式1,16位定时器
    
    TH0 = 0xFF;    // 定时器0初始值
    TL0 = 0x9C;    // 100us中断一次
    
    ET0 = 1;       // 允许定时器0中断
    EA = 1;        // 开启总中断
    TR0 = 1;       // 启动定时器0
}

// 定时器0中断服务函数
void Timer0_ISR() interrupt 1 {
    static unsigned int pwm_counter = 0;
    static unsigned int breath_counter = 0;
    
    // 重装初值
    TH0 = 0xFF;
    TL0 = 0x9C;
    
    // PWM输出
    if(pwm_counter < pwm_duty) {
        LED = 1;  // 高电平
    } else {
        LED = 0;  // 低电平
    }
    
    pwm_counter++;
    if(pwm_counter >= 255) {  // PWM周期
        pwm_counter = 0;
    }
    
    // 呼吸效果:每10ms调整一次占空比
    breath_counter++;
    if(breath_counter >= 100) {  // 100×100us = 10ms
        breath_counter = 0;
        
        if(direction == 0) {  // 递增
            pwm_duty++;
            if(pwm_duty >= 250) {
                direction = 1;
            }
        } else {  // 递减
            pwm_duty--;
            if(pwm_duty <= 5) {
                direction = 0;
            }
        }
    }
}

void main() {
    Timer0_Init();  // 初始化定时器0
    
    while(1) {
        // 主循环为空,所有工作在中断中完成
    }
}

4. STM32硬件PWM实现(标准库)

使用定时器硬件PWM

复制代码
#include "stm32f10x.h"

// 定时器2,通道1,PA0引脚输出PWM
void PWM_Init(void) {
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;
    
    // 1. 开启时钟
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    
    // 2. 配置GPIO
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  // PA0
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;  // 复用推挽输出
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    
    // 3. 配置定时器时基
    TIM_TimeBaseStructure.TIM_Period = 999;  // ARR自动重装值,PWM频率=72MHz/(999+1)=72kHz
    TIM_TimeBaseStructure.TIM_Prescaler = 71;  // 预分频器,72MHz/(71+1)=1MHz
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;  // 向上计数
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
    
    // 4. 配置PWM输出模式
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;  // PWM模式1
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;  // 输出使能
    TIM_OCInitStructure.TIM_Pulse = 0;  // 初始占空比0
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;  // 高电平有效
    
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);  // 通道1
    TIM_OC1PreloadConfig(TIM2, TIM_OCPreload_Enable);  // 使能预装载
    
    // 5. 启动定时器
    TIM_Cmd(TIM2, ENABLE);
    TIM_CtrlPWMOutputs(TIM2, ENABLE);
}

// 设置PWM占空比
void PWM_SetDuty(TIM_TypeDef* TIMx, uint16_t duty) {
    // duty范围:0-1000
    uint16_t pulse = duty;  // 占空比值
    
    switch((uint32_t)TIMx) {
        case (uint32_t)TIM2:
            TIM_SetCompare1(TIMx, pulse);
            break;
        // 可以添加其他定时器
    }
}

// 呼吸灯效果
void Breath_LED(void) {
    static uint16_t duty = 0;
    static uint8_t direction = 0;  // 0:增加,1:减少
    
    if(direction == 0) {
        duty += 5;
        if(duty >= 1000) {
            duty = 1000;
            direction = 1;
        }
    } else {
        duty -= 5;
        if(duty <= 0) {
            duty = 0;
            direction = 0;
        }
    }
    
    PWM_SetDuty(TIM2, duty);
}

// 延时函数
void Delay_ms(uint32_t ms) {
    uint32_t i, j;
    for(i = 0; i < ms; i++) {
        for(j = 0; j < 7200; j++);
    }
}

int main(void) {
    PWM_Init();  // 初始化PWM
    
    while(1) {
        Breath_LED();  // 更新呼吸灯占空比
        Delay_ms(10);  // 延时10ms
    }
}

5. STM32高级PWM实现(HAL库)

复制代码
#include "stm32f1xx_hal.h"

TIM_HandleTypeDef htim2;
TIM_OC_InitTypeDef sConfigOC;

void SystemClock_Config(void);
static void MX_GPIO_Init(void);
static void MX_TIM2_Init(void);

int main(void) {
    HAL_Init();
    SystemClock_Config();
    MX_GPIO_Init();
    MX_TIM2_Init();
    
    uint16_t pwmVal = 0;
    uint8_t dir = 1;  // 1:增加,0:减少
    
    // 启动PWM
    HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
    
    while(1) {
        if(dir) {
            pwmVal += 5;
            if(pwmVal >= 1000) {
                pwmVal = 1000;
                dir = 0;
            }
        } else {
            pwmVal -= 5;
            if(pwmVal <= 0) {
                pwmVal = 0;
                dir = 1;
            }
        }
        
        // 更新占空比
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwmVal);
        HAL_Delay(10);
    }
}

void MX_TIM2_Init(void) {
    TIM_ClockConfigTypeDef sClockSourceConfig = {0};
    TIM_MasterConfigTypeDef sMasterConfig = {0};
    
    htim2.Instance = TIM2;
    htim2.Init.Prescaler = 71;  // 预分频
    htim2.Init.CounterMode = TIM_COUNTERMODE_UP;
    htim2.Init.Period = 999;  // 自动重装值
    htim2.Init.ClockDivision = TIM_CLOCKDIVISION_DIV1;
    HAL_TIM_Base_Init(&htim2);
    
    sClockSourceConfig.ClockSource = TIM_CLOCKSOURCE_INTERNAL;
    HAL_TIM_ConfigClockSource(&htim2, &sClockSourceConfig);
    
    HAL_TIM_PWM_Init(&htim2);
    
    sConfigOC.OCMode = TIM_OCMODE_PWM1;
    sConfigOC.Pulse = 0;
    sConfigOC.OCPolarity = TIM_OCPOLARITY_HIGH;
    sConfigOC.OCFastMode = TIM_OCFAST_DISABLE;
    HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC, TIM_CHANNEL_1);
    
    sMasterConfig.MasterOutputTrigger = TIM_TRGO_RESET;
    sMasterConfig.MasterSlaveMode = TIM_MASTERSLAVEMODE_DISABLE;
    HAL_TIMEx_MasterConfigSynchronization(&htim2, &sMasterConfig);
}

6. ESP32 Arduino PWM实现

复制代码
// 适用于ESP32开发板
const int ledPin = 2;  // GPIO2,ESP32内置LED
const int freq = 5000;  // PWM频率5kHz
const int ledChannel = 0;  // 使用PWM通道0
const int resolution = 8;  // 8位分辨率,占空比0-255

void setup() {
    // 配置PWM
    ledcSetup(ledChannel, freq, resolution);
    // 将PWM通道绑定到GPIO
    ledcAttachPin(ledPin, ledChannel);
}

void loop() {
    // 呼吸灯效果
    for(int dutyCycle = 0; dutyCycle <= 255; dutyCycle++) {
        ledcWrite(ledChannel, dutyCycle);
        delay(10);
    }
    
    for(int dutyCycle = 255; dutyCycle >= 0; dutyCycle--) {
        ledcWrite(ledChannel, dutyCycle);
        delay(10);
    }
}

7. 关键参数计算

PWM频率计算

  • STM32:PWM频率 = 定时器时钟 / ((ARR+1) × (PSC+1))

  • 51单片机:PWM频率 = 1 / (中断周期 × 计数器最大值)

呼吸灯平滑度控制

  • 占空比变化步长:控制亮度变化速度

  • 更新频率:控制变化平滑度

  • 建议值:

    • 步长:1-10

    • 更新间隔:5-20ms

    • 分辨率:8位(256级)或10位(1024级)

8. 调试技巧

示波器测量

  1. 连接示波器探头到PWM输出引脚

  2. 观察波形:

    • 频率是否与设定一致

    • 占空比是否变化

    • 波形是否干净

常见问题解决

  1. PWM无输出

    • 检查引脚配置是否正确

    • 确认定时器时钟是否使能

    • 检查输出比较通道是否使能

  2. 呼吸灯闪烁

    • 增加PWM频率(>100Hz)

    • 减小占空比变化步长

    • 增加更新频率

  3. 亮度变化不均匀

    • 使用对数曲线调整占空比

    • 人眼对亮度感知是非线性的

9. 进阶应用

多通道PWM

复制代码
// 控制RGB三色LED
void RGB_Breathing(void) {
    static uint16_t r_val = 0, g_val = 0, b_val = 0;
    static uint8_t r_dir = 1, g_dir = 1, b_dir = 1;
    
    // 分别控制三个通道
    // ... 类似上面的呼吸灯代码
}

使用定时器中断更新PWM

复制代码
// 在定时器中断中更新占空比
void TIM3_IRQHandler(void) {
    if(TIM_GetITStatus(TIM3, TIM_IT_Update) != RESET) {
        // 更新PWM占空比
        Breath_LED();
        TIM_ClearITPendingBit(TIM3, TIM_IT_Update);
    }
}

10. 优化建议

  1. 使用DMA:对于多通道PWM,可以使用DMA自动更新占空比

  2. 查表法:预先计算亮度曲线表,提高效率

  3. 对数曲线:使用对数曲线使亮度变化更符合人眼感知

  4. 中断优化:减少中断服务程序中的计算量

通过以上配置,你可以实现平滑的呼吸灯效果。根据具体需求调整PWM频率、占空比变化步长和更新速率,可以获得不同的视觉效果。

相关推荐
WeeJot嵌入式1 小时前
【GPIO】按键控制小灯
单片机·嵌入式硬件·mongodb
水云桐程序员1 小时前
单片机:新建第一个工程,点亮LED
单片机·嵌入式硬件
华芯微特SYNWIT1 小时前
SWM221 Cortex-M0系列MCU环境配置
单片机·嵌入式硬件
普中科技1 小时前
【普中 51-Ai8051 开发攻略】-- 第 12 章 LED 点阵实验-显示字符
单片机·嵌入式硬件·开发板·led点阵屏·普中科技·ai8051u·aicube
进击的小头2 小时前
第11篇:TI DSP芯片中断系统详解:PIE架构、配置实战与实时性优化
单片机·嵌入式硬件
2401_832635583 小时前
Spring Data MongoDB 最佳实践:如何构建高效数据访问层
java·mongodb·spring
Hello_Embed4 小时前
嵌入式上位机开发入门(二十四):Paho MQTT 嵌入式客户端源码分析
网络·单片机·网络协议·tcp/ip·嵌入式
yrx02030713 小时前
串口空闲中断+DMA接收+环形缓冲区 && 串口DMA发送+环形缓冲区
stm32·单片机
LCG元14 小时前
STM32实战:基于STM32F103的4G模块(EC20)HTTP通信
stm32·嵌入式硬件·http