单片机:定时器/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. 调试技巧
示波器测量
-
连接示波器探头到PWM输出引脚
-
观察波形:
-
频率是否与设定一致
-
占空比是否变化
-
波形是否干净
-
常见问题解决
-
PWM无输出
-
检查引脚配置是否正确
-
确认定时器时钟是否使能
-
检查输出比较通道是否使能
-
-
呼吸灯闪烁
-
增加PWM频率(>100Hz)
-
减小占空比变化步长
-
增加更新频率
-
-
亮度变化不均匀
-
使用对数曲线调整占空比
-
人眼对亮度感知是非线性的
-
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. 优化建议
-
使用DMA:对于多通道PWM,可以使用DMA自动更新占空比
-
查表法:预先计算亮度曲线表,提高效率
-
对数曲线:使用对数曲线使亮度变化更符合人眼感知
-
中断优化:减少中断服务程序中的计算量
通过以上配置,你可以实现平滑的呼吸灯效果。根据具体需求调整PWM频率、占空比变化步长和更新速率,可以获得不同的视觉效果。