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 核心模块作用(聚焦右侧输出部分)
  1. 计数器(CNT):从 0 开始向上累加,每接收一个时钟脉冲加 1,直到达到 “自动重载值(ARR)” 后清零,循环往复(与上一篇计时逻辑一致);
  2. 自动重载寄存器(ARR):决定 PWM 周期 T——CNT 从 0 到 ARR 的时间就是一个周期,ARR 越大,周期越长;
  3. 捕获比较寄存器(CCR):决定占空比 —— 以 “PWM 模式 1” 为例:
    • CNT ≤ CCR 时,输出有效电平;
    • CNT > CCR 时,输出无效电平;
    • CCR 值越大,有效电平占比越高(占空比越大);
  4. 多通道支持:图中可见 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 基础配置
  1. 时钟源选择:进入 Connectivity → TIM2,选择 Internal Clock(内部时钟,即 APB1 时钟);
  2. 通道模式配置:将 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)]。
  1. 先确定目标:频率 = 2kHz,时钟频率 = 72MHz,代入公式得:(PSC + 1) × (ARR + 1) = 72MHz / 2kHz = 36000;
  2. 若先取 ARR + 1 = 2000(即 ARR = 1999)—— 这样一个周期内有 2000 个脉冲,级数足够多(2000 级),利于后续亮度细腻调节;
  3. 则 PSC + 1 = 36000 / 2000 = 18,即 PSC = 17;
  4. 若取 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**,我们共同进步!

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐