STM32HAL 快速入门(三十八):定时器 PWM 输出实现三色灯控制
摘要:本文介绍了如何使用STM32HAL库通过定时器PWM输出控制三色LED灯。主要内容包括PWM基础概念、定时器PWM输出原理、CubeMX配置步骤及代码实现。重点讲解了有效电平和占空比的概念,分析了定时器计数器(CNT)、自动重载寄存器(ARR)和捕获比较寄存器(CCR)的工作原理。通过CubeMX配置TIM2定时器生成三通道PWM波,并详细说明了关键参数计算方法。最后提供了完整的驱动代码实现
STM32HAL 快速入门(三十八):定时器 PWM 输出实现三色灯控制
一、前言
大家好,这里是 Hello_Embed。上两篇我们用定时器实现了高精度计时,而定时器的另一核心功能 ——PWM 脉冲输出,在嵌入式开发中应用更广:从 LED 亮度调节、电机调速,到舵机角度控制,都离不开 PWM。
本次我们以 “PWM 控制三色灯” 为实战场景,先理解 PWM 的基本概念和定时器输出 PWM 的原理,再通过 CubeMX 配置和代码编写,实现红、绿、蓝三色切换,掌握定时器 PWM 输出的核心流程。后续若需扩展到 “呼吸灯”“混色效果”,只需在此基础上调整占空比即可。
二、主体
2.1 PWM 基础概念:有效电平与占空比
在写代码前,先明确两个核心概念,这是理解 PWM 控制逻辑的前提:
2.1.1 有效电平
有效电平是指 “能让硬件元器件工作的电平”。以本次实验的三色灯为例:当 PA0、PA1、PA2 引脚输出低电平时,对应的红、绿、蓝 LED 会发光(具体需看硬件电路设计),因此 “低电平” 就是该三色灯的有效电平。
2.1.2 占空比
PWM(脉宽调制)是 “高低电平周期性交替的波形”,占空比决定了有效电平在一个周期内的占比,公式如下:占空比 = 有效电平持续时间 / PWM 周期 × 100%
- 若占空比为 100%:一个周期内全是有效电平,LED 最亮;
- 若占空比为 0%:一个周期内全是无效电平,LED 熄灭;
- 若占空比在 0%~100% 之间:LED 亮度随占空比增大而增强(因电平切换频率高于人眼视觉暂留,看不到闪烁,只能感知亮度变化)。
2.2 定时器 PWM 输出原理:从硬件图看核心模块
要生成规律的 PWM 波形,需依赖定时器的 “计数器(CNT)、自动重载寄存器(ARR)、捕获比较寄存器(CCR)”,结合芯片手册的硬件图理解更直观:
2.2.1 核心模块作用(聚焦右侧输出部分)
- 计数器(CNT):从 0 开始向上累加,每接收一个时钟脉冲加 1,直到达到 “自动重载值(ARR)” 后清零,循环往复(与上一篇计时逻辑一致);
- 自动重载寄存器(ARR):决定 PWM 周期 T——CNT 从 0 到 ARR 的时间就是一个周期,ARR 越大,周期越长;
- 捕获比较寄存器(CCR):决定占空比 —— 以 “PWM 模式 1” 为例:
- 当
CNT ≤ CCR时,输出有效电平; - 当
CNT > CCR时,输出无效电平; - CCR 值越大,有效电平占比越高(占空比越大);
- 当
- 多通道支持:图中可见 4 个比较寄存器(CCR1~CCR4),对应 4 个 PWM 输出通道(CH1~CH4),可同时控制 4 个设备(本次用 TIM2 的 CH1~CH3 控制三色灯)。
2.3 CubeMX 配置:TIM2 生成三通道 PWM
本次实验用 TIM2 输出 PWM,控制三色灯的红(PA2)、绿(PA1)、蓝(PA0)引脚,配置步骤需重点关注 “时钟源、通道选择、分频与 ARR 计算”。
2.3.1 基础配置
- 时钟源选择:进入
Connectivity → TIM2,选择 Internal Clock(内部时钟,即 APB1 时钟); - 通道模式配置:将 TIM2 的 Channel 1、Channel 2、Channel 3 均设置为 PWM Generation CHx(PWM 输出模式),截图如下:

2.3.2 关键参数计算:分频值(PSC)与自动重载值(ARR)
PWM 频率需匹配硬件需求:查资料得知,LED 对 PWM 频率的敏感范围在 2kHz 左右(频率过低会有闪烁,过高无意义),因此目标频率设为 2kHz。已知条件:
- TIM2 挂载在 APB1 总线上,系统时钟 72MHz,APB1 Timer 时钟频率 = 72MHz(APB1 预分频为 2 时,Timer 时钟翻倍,此处默认配置为 72MHz);
- 频率公式:PWM 频率 = 时钟频率 / [(PSC + 1) × (ARR + 1)]。
- 先确定目标:频率 = 2kHz,时钟频率 = 72MHz,代入公式得:(PSC + 1) × (ARR + 1) = 72MHz / 2kHz = 36000;
- 若先取 ARR + 1 = 2000(即 ARR = 1999)—— 这样一个周期内有 2000 个脉冲,级数足够多(2000 级),利于后续亮度细腻调节;
- 则 PSC + 1 = 36000 / 2000 = 18,即 PSC = 17;
- 若取 ARR = 99(级数 100),会因级数太少导致亮度变化不明显,因此最终确定:PSC = 17,ARR = 1999。
2.3.3 最终 TIM2 配置
- Prescaler(分频值):17;
- Counter Mode:Up(向上计数);
- Counter Period(ARR 值):1999;
- Polarity(极性):Low(低电平有效,匹配三色灯的有效电平);
其他参数默认,配置截图如下:
2.3.4 GPIO 配置
TIM2 的 Channel 1~3 对应引脚默认是 PA0~PA2(芯片引脚定义),无需手动修改,CubeMX 会自动配置为 “复用推挽输出”。
2.4 代码实现:三色灯驱动与控制
新建 driver_color_led.c 和 driver_color_led.h,实现 “初始化” 和 “颜色设置” 两个核心函数,代码仅添加注释解释关键步骤。
2.4.1 头文件声明与宏定义
// driver_color_led.h
#ifndef __DRIVER_COLOR_LED_H
#define __DRIVER_COLOR_LED_H
#include "stm32f1xx_hal.h"
// 声明 TIM2 句柄(在 CubeMX 生成的 tim.c 中定义)
extern TIM_HandleTypeDef htim2;
// 宏定义:三色灯对应 TIM2 通道(红→CH3,绿→CH2,蓝→CH1)
#define COLOR_LED_R TIM_CHANNEL_3
#define COLOR_LED_G TIM_CHANNEL_2
#define COLOR_LED_B TIM_CHANNEL_1
// 函数声明
void ColorLED_Init(void); // 三色灯初始化(启动 PWM)
void ColorLED_SetColor(uint32_t color); // 设置三色灯颜色(参数格式:0x00RRGGBB)
#endif
2.4.2 三色灯初始化函数(启动 PWM)
// driver_color_led.c
#include "stm32f1xx_hal.h"
#include "driver_color_led.h"
/**
* @brief 三色灯初始化函数
* @note 启动 TIM2 的 CH1~CH3 通道 PWM 输出,需在 MX_TIM2_Init() 后调用
* @param 无
* @retval 无
*/
void ColorLED_Init(void)
{
// 启动 TIM2 通道 3(红灯)的 PWM 输出
HAL_TIM_PWM_Start(&htim2, COLOR_LED_R);
// 启动 TIM2 通道 2(绿灯)的 PWM 输出
HAL_TIM_PWM_Start(&htim2, COLOR_LED_G);
// 启动 TIM2 通道 1(蓝灯)的 PWM 输出
HAL_TIM_PWM_Start(&htim2, COLOR_LED_B);
}
2.4.3 颜色设置函数(配置占空比)
颜色参数格式为 0x00RRGGBB(RR = 红亮度,GG = 绿亮度,BB = 蓝亮度,范围 0x000xFF),需将亮度值(0255)转换为 CCR 值(0~1999),核心是 CCR = 亮度值 × 1999 / 255(因 ARR=1999,总级数 2000)。
/**
* @brief 三色灯颜色设置函数
* @param color:颜色值,格式为 0x00RRGGBB(RR/GG/BB 范围 0x00~0xFF,对应亮度 0~255)
* @retval 无
* @note 通过配置 TIM2 各通道 CCR 值,调整占空比,实现亮度控制
*/
void ColorLED_SetColor(uint32_t color)
{
// 定义 PWM 配置结构体(红、绿、蓝分别配置)
TIM_OC_InitTypeDef sConfigOC_R = {0};
TIM_OC_InitTypeDef sConfigOC_G = {0};
TIM_OC_InitTypeDef sConfigOC_B = {0};
// -------------------------- 红灯配置(TIM2 CH3)--------------------------
sConfigOC_R.OCMode = TIM_OCMODE_PWM1; // PWM 模式 1:CNT ≤ CCR 输出有效电平
// 计算红灯 CCR 值:RR 亮度(0~255)转换为 CCR(0~1999)
sConfigOC_R.Pulse = ((color >> 16) & 0xff) * 1999 / 255;
sConfigOC_R.OCPolarity = TIM_OCPOLARITY_LOW; // 低电平有效(匹配红灯硬件)
sConfigOC_R.OCFastMode = TIM_OCFAST_DISABLE; // 禁用快速模式
// 配置 TIM2 通道 3
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_R, COLOR_LED_R) != HAL_OK)
{
Error_Handler();
}
// -------------------------- 绿灯配置(TIM2 CH2)--------------------------
sConfigOC_G.OCMode = TIM_OCMODE_PWM1;
// 计算绿灯 CCR 值:GG 亮度(0~255)转换为 CCR(0~1999)
sConfigOC_G.Pulse = ((color >> 8) & 0xff) * 1999 / 255;
sConfigOC_G.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC_G.OCFastMode = TIM_OCFAST_DISABLE;
// 配置 TIM2 通道 2
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_G, COLOR_LED_G) != HAL_OK)
{
Error_Handler();
}
// -------------------------- 蓝灯配置(TIM2 CH1)--------------------------
sConfigOC_B.OCMode = TIM_OCMODE_PWM1;
// 计算蓝灯 CCR 值:BB 亮度(0~255)转换为 CCR(0~1999)
sConfigOC_B.Pulse = ((color >> 0) & 0xff) * 1999 / 255;
sConfigOC_B.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC_B.OCFastMode = TIM_OCFAST_DISABLE;
// 配置 TIM2 通道 1
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_B, COLOR_LED_B) != HAL_OK)
{
Error_Handler();
}
}
2.4.5 main 函数调用:实现颜色切换
#include "driver_color_led.h"
#include "driver_timer.h" // 包含 mdelay 函数(上一篇实现的延时)
int main(void)
{
// 1. 初始化 HAL 库和 TIM2(CubeMX 自动生成)
HAL_Init();
MX_TIM2_Init(); // 初始化 TIM2 的 PWM 配置(分频、ARR、通道模式等)
// 2. 初始化三色灯(启动 PWM 输出)
ColorLED_Init();
// 3. 循环切换颜色(红→绿→蓝,每 1000ms 切换一次)
while (1)
{
ColorLED_SetColor(0xFF0000); // 红色:RR=0xFF,GG=0x00,BB=0x00
mdelay(100); // 延时 1000ms(1秒)
ColorLED_SetColor(0x00FF00); // 绿色:RR=0x00,GG=0xFF,BB=0x00
mdelay(100);
ColorLED_SetColor(0x0000FF); // 蓝色:RR=0x00,GG=0x00,BB=0xFF
mdelay(100);
}
}
2.5 测试现象
烧录程序后,三色灯按 “红色→绿色→蓝色” 循环切换,延时为 100ms,可观察到快速闪烁的颜色切换效果。实验截图如下
三、结尾
本次实验通过 TIM2 的 PWM 输出功能,成功实现了三色灯的颜色控制,核心是掌握 “ARR 决定周期、CCR 决定占空比” 的规律,以及 CubeMX 中 PWM 参数的计算与配置。至此,STM32HAL 快速入门系列暂告一段落 —— 我们从 UART、I2C、SPI 通信,到 OLED 显示、SPI Flash 存储,再到定时器计时与 PWM 输出,覆盖了嵌入式开发的核心基础模块。
后续若学习新模块(如 ADC、DAC、中断嵌套等),会继续更新该系列。接下来,我计划做一个简单项目,将本系列学到的模块知识整合应用,进一步巩固实战能力。关注**Hello_Embed**,我们共同进步!
更多推荐



所有评论(0)