(1)定时器计数功能超级保姆级学习笔记
一、课程核心目标
本次课程聚焦 定时器计数功能 ,围绕 STM32 定时器架构,以 基础定时器 为核心,结合通用定时器,通过框图、时序图深度解析 "时钟分频 → 计数器累加 → 溢出触发更新" 的完整逻辑,掌握预装载、更新事件等关键机制 。
二、基础定时器深度剖析
(一)基础定时器框图拆解(Figure 169 )

基础定时器是 STM32 定时器的 "精简核心",用于简单定时、触发 DAC 等场景,逐模块解析:
模块名称 | 功能与信号流向 | 关联时序 / 作用 |
---|---|---|
时钟输入(TIMxCLK) | 来自 RCC 时钟树,经 Internal clock (CK_INT) 进入触发控制器 |
定时器计数的 "动力源",频率决定计数基准(如 72MHz 系统时钟,可配置分频 ) |
触发控制器(Trigger controller) | 接收 CK_INT,输出 TRGO 可触发 DAC,同时向自动重装载寄存器、计数器发送 Reset, Enable, Count 信号 |
决定定时器 "何时启动、复位",TRGO 是对外同步信号(如触发 DAC 周期性转换 ) |
预分频器(PSC Prescaler) | 对 CK_PSC 分频,输出 CK_CNT 给计数器(公式:CK_CNT = CK_PSC / (PSC + 1) ,PSC 是寄存器值 ) |
调节计数频率,例:72MHz 输入,PSC=71 → CK_CNT=1MHz (实现 1μs 计数周期 ) |
自动重装载寄存器(Auto-reload Register) | 存储计数周期阈值(ARR 值 ),与计数器配合决定 "溢出时机" |
若 ARPE=1 (预装载使能 ),ARR 新值在 更新事件(UEV) 时生效;ARPE=0 则立即生效 |
计数器(CNT COUNTER) | 按 CK_CNT 频率递增(基础定时器默认向上计数 ),达到 ARR 时触发 更新事件(UEV) |
溢出时:1. 置位 Update interrupt flag (UIF) (可触发中断 );2. 触发自动重装载(若 ARPE=1 ) |
(二)计数流程与关键时序(结合图理解 )
以 "72MHz 时钟、PSC=71(CK_CNT=1MHz
)、ARR=999(1ms 周期)" 为例,走一遍完整计数过程:

- 启动计数 :触发控制器收到使能信号(
CEN=1
),CK_CNT
开始驱动CNT
从 0 递增。 - 正常计数 :
CNT
每个CK_CNT
周期 +1,依次经过0→1→2→...→999
。 - 溢出与更新事件 :当
CNT=ARR=999
时,下一个CK_CNT
沿触发 溢出 ,CNT
归零,同时触发:- 更新事件(UEV) :若
ARPE=1
,预装载的ARR
新值(如有修改 )会加载到 "影子寄存器" 生效; - 中断标志(UIF) :置位
Update interrupt flag
,若使能中断则进入中断服务程序。
- 更新事件(UEV) :若
- 周期循环 :
CNT
重新从 0 开始,重复 "计数 → 溢出 → 更新",实现周期性定时。
(三)预装载使能(ARPE)的 "时序魔法"(对比)
ARPE
决定 ARR
新值的生效时机,直接影响计数周期的 "实时性",对比分析:


场景 | ARPE=1 (预装载使能 ) |
ARPE=0 (预装载禁用 ) |
---|---|---|
写入 ARR 时机 |
新值暂存 "预装载寄存器",更新事件(UEV) 时覆盖 "影子寄存器" 生效 | 新值立即写入 "影子寄存器",无需等待更新事件,立即生效 |
时序表现 | 如图 2(Figure 60):计数到 F5 时写入 ARR=36 ,当前周期仍用旧值(F5→00 ),UEV 触发后新值生效 |
如图 3(Figure 59):计数到 36 时写入 ARR=36 ,立即生效,下一周期直接用新值计数 |
适用场景 | 需 "周期无缝切换"(如 PWM 频率切换,避免波形突变 ) | 需 "立即修改周期"(如紧急调整定时,不等待当前周期结束 ) |
(四)更新事件(UEV)与中断(UIF)
- 更新事件(UEV) :计数器溢出时触发,是定时器 "周期结束" 的标志。作用:
- 同步更新
ARR
影子寄存器(若ARPE=1
); - 置位
Update interrupt flag (UIF)
,可触发中断。
- 同步更新
- 更新中断标志(UIF):需在中断服务程序中手动清除(否则持续触发中断 ),用于实现 "定时中断回调"(如 1ms 定时翻转 LED )。
(五)自动重装载时序(ARPE 对比 )
ARPE=1
(延迟生效 ) :写入ARR
新值后,当前周期仍用旧值,溢出触发UEV
后新值生效( F5→36 的切换 )ARPE=0
(立即生效 ) :写入ARR
新值后立即生效,计数到新值时触发溢出(31→36直接切换 )
三、通用定时器扩展(简单关联 )
(一)通用定时器框图概览(TIM9/TIM12 为例 )


通用定时器在基础定时器基础上扩展了 输入捕获、输出比较、PWM 等功能,核心仍围绕 "计数",但触发源、应用场景更丰富:
新增模块 | 功能说明 | 与计数功能的关联 |
---|---|---|
输入滤波与边沿检测 | 对 TIMx_CH1~CH4 输入的外部脉冲滤波、边沿检测(上升沿 / 下降沿 ),输出 TI1FP1 等信号 |
支持 "外部信号计数"(如统计编码器脉冲,计算电机转速 ),替代内部时钟作为计数源 |
捕获 / 比较寄存器(CCR) | 支持 "输入捕获"(记录外部信号触发时刻 )和 "输出比较"(控制 PWM 占空比 ) | 扩展计数功能到 "测频、波形生成",核心仍依赖 "计数器累加 → 匹配 CCR 值触发动作" 逻辑 |
从模式控制器 | 支持 ITR0~ITR3 (其他定时器触发 )、TI1FP1 (通道滤波信号 )作为触发源 |
实现多定时器同步、外部信号触发计数,让计数触发方式更灵活 |
(二)通用定时器的 "多面性"
- 内部时钟计数:与基础定时器类似,用于常规周期性任务(如定时 ADC 采样 )。
- 外部信号计数 :通过
TIMx_CH
输入外部脉冲,经滤波、边沿检测后作为计数时钟,实现 "对外部事件计数"(如红外遥控信号统计 )。
四、关键时序图串联(理解 "何时生效、怎么生效" )
(一)预分频器时序(Figure 53 )
以 "预分频器从 1→2 切换" 为例,流程:
- 初始
PSC=0
(分频系数 1 ),CK_CNT=CK_PSC
驱动计数器; - 写入新
PSC=1
(分频系数 2 ),若ARPE=1
(预装载使能 ),新分频系数在 更新事件(UEV) 时生效; UEV
触发后,CK_CNT=CK_PSC/2
,计数器按新频率计数,体现 "分频系数延迟生效" 逻辑。
(二)基础计数时序(Figure 55 )

以 "内部时钟分频 1、向上计数" 为例,流程:
CEN
使能后,CK_CNT
驱动计数器从 31 递增;- 计数到
ARR=36
时触发溢出,CNT
归零,同时触发UEV
、置位UIF
; - 新周期从 0 开始,重复 "计数 → 溢出",实现周期性定时。
五、知识串联与应用
(一)基础定时器典型应用
- 定时中断 :配置
PSC=71
、ARR=999
(72MHz 时钟 → 1ms 定时 ),在UIF
中断中执行周期性任务(数据采集、电机控制 )。 - 触发 DAC :通过
TRGO
触发 DAC 同步转换,实现 "定时器定时输出波形"(如正弦波周期性更新 )。
(二)通用定时器典型应用
- 外部脉冲计数 :利用
TIMx_CH
输入滤波、边沿检测,统计电机编码器脉冲(计算转速 )。 - 输入捕获测频:捕获外部信号上升沿,计算信号周期、频率(如红外信号解码 )。
(2)定时器与延时函数
一、概述
本文档针对基于 SysTick
定时器实现的延时函数(udelay
、mdelay
)和纳秒级时间获取函数(system_get_ns
)进行整理,核心围绕 ARM Cortex-M 内核的 SysTick 定时器 工作原理及应用展开。
二、SysTick 定时器基础
1. 核心寄存器
LOAD
(重装载寄存器):存储定时器最大计数值,计数到 0 后自动重新加载该值。VAL
(当前值寄存器) :存储定时器当前计数值,递减计数 (从LOAD
减到 0,再重新加载LOAD
循环)。
2. 关键特性
- 计数周期:
LOAD + 1
个计数对应 1 毫秒 (代码隐含配置,由 SysTick 时钟频率决定)。
例:若LOAD = 999
,则999 + 1 = 1000
个计数 = 1 毫秒,每个计数对应 1 微秒。
三、延时函数详解
1. 微秒级延时函数 udelay(int us)

功能
实现指定微秒数(us
)的延时,基于 SysTick 定时器计数。
核心逻辑拆解
void udelay(int us) {
uint32_t told = SysTick->VAL; // 记录起始计数值
uint32_t load = SysTick->LOAD; // 获取重装载值
uint32_t ticks = us * (load + 1) / 1000; // 计算需等待的总周期数
uint32_t cnt = 0; // 累计已过周期数
while (1) {
uint32_t tnow = SysTick->VAL; // 获取当前计数值
if (told >= tnow) {
// 未溢出:直接累加差值
cnt += told - tnow;
} else {
// 溢出:累加"told到0"和"load到tnow"的周期和
cnt += told + load + 1 - tnow;
}
told = tnow; // 更新起始值,用于下一次计算
if (cnt >= ticks) break; // 达到目标周期,退出
}
}
关键公式解析
ticks = us * (load + 1) / 1000
:
因load + 1
个周期 = 1 毫秒,故 1 微秒对应(load + 1)/1000
个周期,us
微秒需us * (load + 1)/1000
个周期。
计数逻辑
- 未溢出(
told >= tnow
) :
例:told = 500
,tnow = 300
,经过周期 =500 - 300 = 200
。 - 溢出(
told < tnow
) :
例:load = 1000
,told = 100
,tnow = 900
(溢出后重新加载),经过周期 =100(100→0) + (1000 + 1 - 900)(1000→900)= 201
。
2. 毫秒级延时函数 mdelay(int ms)
功能
实现指定毫秒数(ms
)的延时,基于 udelay
封装。
核心逻辑
void mdelay(int ms) {
for (int i = 0; i < ms; i++) {
udelay(1000); // 1 毫秒 = 1000 微秒,循环调用 udelay
}
}
四、纳秒级时间获取函数 system_get_ns(void)
功能
返回系统启动到当前的总纳秒数(精度高于 HAL_GetTick()
)。
核心逻辑拆解
uint64_t system_get_ns(void) {
// 1. 获取整数毫秒数并转换为纳秒(1 毫秒 = 1,000,000 纳秒)
uint64_t ns = HAL_GetTick() * 1000000;
// 2. 计算当前毫秒内已过的纳秒数
uint32_t tnow = SysTick->VAL;
uint32_t load = SysTick->LOAD;
uint64_t cnt = load + 1 - tnow; // 当前毫秒内已过的周期数
// 3. 周期数转换为纳秒并累加
ns += cnt * 1000000 / (load + 1);
return ns;
}
关键公式解析
cnt = load + 1 - tnow
:
计算当前毫秒内已流逝的周期数(从当前毫秒起始到现在)。
例:load = 999
,tnow = 300
,则cnt = 1000 - 300 = 700
(已过 700 个周期)。cnt * 1000000 / (load + 1)
:
将周期数转换为纳秒(1 毫秒 = 1,000,000 纳秒,1 周期 = 1,000,000/(load+1) 纳秒)。
例:load = 999
,cnt = 700
,则对应纳秒 =700 * 1000000 / 1000 = 700,000
纳秒(0.7 毫秒)。
五、总结
- 所有函数均依赖 SysTick 定时器 ,核心是通过
LOAD
和VAL
寄存器计算时间。 - 延时函数(
udelay
、mdelay
)通过累计周期数实现精确等待;system_get_ns
通过组合毫秒数和周期数实现高精度时间获取。 - 前提条件:
SysTick
需配置为load + 1
个周期 = 1 毫秒(由时钟频率决定)。
(3)STM32 高精度计时(Timer)
一、核心目标
实现基于 STM32 定时器(SysTick 或 TIM4 二选一 )的 微秒级延时 (udelay
)和 纳秒级系统时间获取 (system_get_ns
),通过 条件编译 灵活切换定时器,满足不同场景下的高精度计时需求。
二、知识准备
(一)定时器基础
STM32 的定时器(SysTick、TIM4 等 )本质是 "递减计数器":
- 从一个 "最大值" 开始(SysTick 用
LOAD
寄存器,TIM4 用 "自动重载值" ) - 每个时钟周期减 1,减到 0 后自动重新加载 "最大值",循环计数
(二)条件编译
用 #ifdef
#else
#endif
控制代码分支:
4. 定义 #define USE_SYSTICK
→ 用 SysTick 定时器
5. 没定义 → 用 TIM4 定时器(和 STM32CubeMX 配置对应 )
6.
三、STM32CubeMX 配置(必看!)
(一)配置界面
在 Pinout & Configuration
→ System Core
→ SYS
里:
7. Timebase Source
选 TIM4
这是告诉 STM32:HAL 库的默认 "时间基准" 用 TIM4 定时器 。
代码里的 #else
分支,就是为这种配置写的逻辑!
8.
(二)配置的意义
- 如果需要更高灵活性(比如 SysTick 被其他功能占用 ),可以手动定义
#define USE_SYSTICK
,代码会自动切换到 SysTick 分支。 - 两种定时器的逻辑用 条件编译 分开,适配不同需求。
### 四、核心代码逐行解析(结合手绘示意图)
你的代码里有 3 个核心函数:`udelay`(微秒延时 )、`mdelay`(毫秒延时 )、`system_get_ns`(纳秒时间获取 )。
下面逐行讲清楚,结合手绘示意图理解!


1. 头文件与宏定义
#include "driver_timer.h"
#include "stm32f1xx_hal.h"
#include "cmsis_version.h"
//#define USE_SYSTICK // 取消注释则用SysTick,否则用TIM4
extern TIM_HandleTypeDef htim4; // TIM4的句柄(CubeMX生成)
#define USE_SYSTICK
是 "开关":- 取消注释 → 用 SysTick 实现延时
- 注释掉 → 用 TIM4 实现延时(和 CubeMX 配置对应 )
2. udelay(int us)
:微秒延时(核心!)
void udelay (int us)
{
#ifdef USE_SYSTICK // 分支1:用SysTick
uint32_t told = SysTick->VAL; // 当前计数值(递减)
uint32_t tnow;
uint32_t load = SysTick->LOAD; // 重装载值(最大值)
uint32_t ticks = us * (load + 1) / 1000; // 目标周期数
uint32_t cnt = 0; // 累计经过的周期数
while(1)
{
tnow = SysTick->VAL; // 读当前值
if(told >= tnow)
{
// 情况1:没溢出!正常递减
cnt += told - tnow;
}
else
{
// 情况2:溢出了!从0重新加载了load
cnt += told + load + 1 - tnow;
}
told = tnow; // 更新told,准备下一轮
if(cnt >= ticks) break; // 达到目标周期数
}
#else // 分支2:用TIM4(和CubeMX配置对应)
uint32_t told = __HAL_TIM_GET_COUNTER(&htim4); // 读TIM4当前值
uint32_t tnow;
uint32_t load = __HAL_TIM_GET_AUTORELOAD(&htim4); // TIM4自动重载值
uint32_t ticks = us * (load + 1) / 1000; // 目标周期数
uint32_t cnt = 0; // 累计经过的周期数
while(1)
{
tnow = __HAL_TIM_GET_COUNTER(&htim4); // 读当前值
if(tnow >= told)
{
// 情况1:没溢出!正常递减
cnt += tnow - told;
}
else
{
// 情况2:溢出了!从0重新加载了load
cnt += (load + 1 - told) + tnow;
}
told = tnow; // 更新told,准备下一轮
if(cnt >= ticks) break; // 达到目标周期数
}
#endif
}
关键逻辑
-
load + 1
的意义 :定时器从
load
减到 0,总共经历load + 1
个周期(比如load=100
,是 100→99→...→0,共 101 次 )。所以 1ms 对应
load + 1
个周期 ,us
微秒就对应us*(load+1)/1000
个周期。 -
溢出处理 :
手绘示意图里的 "
tnow < told
" 就是溢出场景!- SysTick 溢出:
cnt += told + load + 1 - tnow
(补全溢出的周期 ) - TIM4 溢出:
cnt += (load + 1 - told) + tnow
(逻辑相同,写法不同 )
- SysTick 溢出:
3. mdelay(int ms)
:毫秒延时(简单封装)
void mdelay(int ms)
{
for(int i = 0; i < ms; i++)
{
udelay(1000); // 1ms = 1000us
}
}
- 直接调用
udelay(1000)
实现 1 毫秒延时,循环ms
次就是ms
毫秒。
4. system_get_ns(void)
:纳秒时间获取
uint64_t system_get_ns(void)
{
#ifdef USE_SYSTICK // 分支1:用SysTick
uint64_t ns = HAL_GetTick(); // 系统启动以来的毫秒数
ns = ns * 1000000; // 转换为纳秒(1ms=1e6ns)
uint32_t tnow = SysTick->VAL; // 当前计数值
uint32_t load = SysTick->LOAD; // 重装载值
uint64_t cnt = load + 1 - tnow; // 当前毫秒内的周期数
// 周期数转纳秒:1ms对应load+1个周期 → 1个周期对应1e6/(load+1) ns
ns += cnt * 1000000 / (load + 1);
return ns;
#else // 分支2:用TIM4
uint64_t ns = HAL_GetTick(); // 系统启动以来的毫秒数
ns = ns * 1000000; // 转换为纳秒
uint32_t tnow = __HAL_TIM_GET_COUNTER(&htim4); // 当前计数值
uint32_t load = __HAL_TIM_GET_AUTORELOAD(&htim4); // 自动重载值
uint64_t cnt = tnow; // 当前毫秒内的周期数
// 周期数转纳秒:1ms对应load+1个周期 → 1个周期对应1e6/(load+1) ns
ns += cnt * 1000000 / (load + 1);
return ns;
#endif
}
- 思路:
- 先用
HAL_GetTick()
拿到 毫秒数,转成纳秒。 - 再用定时器计数值,补上 "当前毫秒内的纳秒数",实现 纳秒级精度。
- 先用
五、总结(复习脉络)
-
配置关联:
- CubeMX 里
Timebase Source = TIM4
→ 代码走#else
分支(用 TIM4 )。 - 定义
USE_SYSTICK
→ 走#ifdef
分支(用 SysTick )。
- CubeMX 里
-
核心逻辑 :
不管用哪种定时器,都是 "累计周期数" ,处理 "溢出场景" 是关键!手绘示意图里的
tnow < told
就是溢出,必须补全周期。 -
函数关系:
udelay
是基础(微秒级 )→mdelay
封装它(毫秒级 )。system_get_ns
结合HAL_GetTick()
和定时器计数值,实现纳秒级时间。
-
实践验证 :
用示波器抓
udelay(100)
的引脚波形,或者打印system_get_ns()
的值,验证延时和计时是否准确。
(4)STM32 定时器输出功能(PWM 核心逻辑)学习笔记
一、核心目标
掌握 STM32 定时器如何通过 ARR、CCR 寄存器 配合,输出 PWM(脉冲宽度调制)信号,理解周期、占空比的配置原理,关联手册、手绘逻辑与代码实现。
二、知识预备
(一)PWM 基础
PWM(脉冲宽度调制)通过控制 "高电平占空比" 模拟 "等效电压",核心参数:
- 周期(T) :PWM 信号一个完整周期的时间,频率
f = 1/T
- 占空比(duty) :高电平时间
T1
与周期T
的比值(duty = T1/T
),决定等效电压(如 50% 占空比可模拟 "中间电压" )
(二)定时器关键寄存器(关联架构图)
- ARR(Auto - Reload Register,自动重装载寄存器 ) :配置 PWM 周期
T
,计数器(CNT)计到 ARR 值后重置,循环计数 - CCR(Capture/Compare Register,捕获比较寄存器 ) :配置占空比,决定 "高电平持续时长
T1
" - CNT(Counter,计数寄存器 ):随定时器时钟递增 / 递减,与 ARR、CCR 比较,控制电平翻转
三、关键逻辑拆解(结合手绘与架构图)
(一)寄存器配置流程
- 设置 ARR(AutoReload) :
配置 PWM 周期T
,比如 ARR 值为999
,则周期由定时器时钟和 ARR 共同决定(需结合分频器,假设分频后时钟为 1MHz,周期T = (ARR + 1)/1MHz
,因计数从 0 到 ARR 共ARR + 1
个时钟 )。 - 设置 CCRn(Capture/Compare Register) :
配置占空比,CCR 值决定 "高电平持续时长"。如 ARR = 999、CCR = 500,则占空比duty = 500/1000 = 50%
(高电平占一半周期 )。 - 设置 "有效电平" :
决定 PWM 高 / 低电平的 "有效触发条件"(如 CNT ≤ CCR 时输出高电平,CNT > CCR 时输出低电平 ),对应手绘 "有效电平:高 / 低" 配置。
(二)PWM 信号生成逻辑
- 计数与电平翻转 :
CNT 从 0 开始递增,当CNT ≤ CCR
时,输出 "有效电平(高)";当CNT > CCR
时,输出 "无效电平(低)"。
CNT 计到 ARR 后重置为 0,循环计数,持续输出 PWM 波形(时序图中,CCR 位置触发电平翻转,ARR 位置重置计数 )。 - 占空比与 CCR 关系 :
CCR 值越大,"高电平持续时长T1
" 越长,占空比越高。如 CCR = 800(ARR = 999 ),占空比800/1000 = 80%
,高电平占大部分周期。
四、时序与电平翻转
(一)时序图解析
- CNT 计数:从 0 递增到 ARR(如 999 ),再重置为 0,循环往复
- 电平翻转点 :
- 当
CNT ≤ CCR
(如 CCR = 500 ),输出 "有效电平(高)" - 当
CNT > CCR
,输出 "无效电平(低)"
- 当
- 周期与占空比体现 :
ARR 决定 "周期终点"(0 → ARR 为一个周期 ),CCR 决定 "高电平终点"(0 → CCR 为高电平时长 ),两者比值即占空比
(二)示例验证(ARR = 999,CCR = 500 )
- 周期
T = (999 + 1)/TIMx_Clock
(假设 TIMx_Clock = 72MHz,分频后为 1MHz,则T = 1ms
) - 占空比
duty = 500/1000 = 50%
,高电平持续0.5ms
,低电平持续0.5ms
,示波器可观测到 "方波"
五、总结
- 寄存器作用 :
ARR 控周期、CCR 控占空比、CNT 实现计数与比较,三者配合生成 PWM 信号 - 配置流程 :
CubeMX 选定时器模式 → 设 ARR(周期 )、CCR(占空比 )→ 代码初始化并启动 PWM - 时序逻辑 :
CNT 递增 → 与 CCR 比较控电平 → 到 ARR 重置,循环生成 PWM ,占空比由CCR/ARR
决定
(5)全彩 LED 控制代码复习笔记
一、整体功能概述
通过 STM32 的定时器 TIM2 输出 PWM 信号,控制全彩 LED 的红(R)、绿(G)、蓝(B)三个通道,实现不同颜色的显示。可初始化全彩 LED 控制、设置指定颜色 ,还能在主循环中动态改变颜色。
二、涉及硬件与原理
(一)全彩 LED 硬件连接
全彩 LED 由红、绿、蓝三个 LED 组成,对应 STM32 引脚:
- 红色(R):PA2,对应 TIM2_CH3
- 绿色(G):PA15,对应 TIM2_CH1
- 蓝色(B):PB3,对应 TIM2_CH2
当引脚为低电平时,对应颜色 LED 发光,通过调整 PWM 占空比(即TIM_OC_InitTypeDef
结构体中Pulse
参数 )调配颜色。
(二)时钟相关(结合时钟树图理解)
系统时钟等经过分频等配置,为 TIM2 提供时钟。比如从时钟树可知,相关分频设置影响最终 TIM2 时钟频率,进而影响 PWM 周期和精度,代码中通过配置定时器参数(如Counter Period
等 )确定 PWM 周期,这里Counter Period
设为 1999 ,结合时钟频率决定了 PWM 周期。
三、代码模块解析
(一)头文件引入
#include "driver_timer.h"
#include "stm32f1xx_hal.h"
#include "cmsis_version.h"
引入定时器驱动、HAL 库(提供定时器 PWM 操作函数 )、CMSIS 版本相关头文件,为后续操作提供函数和类型支持。
(二)外部变量与宏定义
extern TIM_HandleTypeDef htim2;
#define COLOR_LED_R TIM_CHANNEL_3 //红
#define COLOR_LED_G TIM_CHANNEL_1 //绿
#define COLOR_LED_B TIM_CHANNEL_2 //蓝
extern TIM_HandleTypeDef htim2;
:声明外部定义的 TIM2 句柄,用于操作定时器 TIM2 。- 宏定义:将引脚通道与颜色对应,方便代码阅读和维护,明确红、绿、蓝分别对应 TIM2 的哪个通道。
(三)初始化函数color_led_Init
void color_led_Init(void)
{
HAL_TIM_PWM_Start(&htim2, COLOR_LED_R);
HAL_TIM_PWM_Start(&htim2, COLOR_LED_G);
HAL_TIM_PWM_Start(&htim2, COLOR_LED_B);
}
- 功能:调用 HAL 库函数
HAL_TIM_PWM_Start
,启动 TIM2 对应通道(红、绿、蓝)的 PWM 输出,让全彩 LED 初始处于可通过 PWM 控制发光状态。 - 结合硬件:启动后,对应引脚开始输出 PWM 信号,后续可通过改变占空比控制 LED 亮度和颜色。
(四)颜色设置函数colorLed_setColor
void colorLed_setColor(uint32_t color)
{
TIM_OC_InitTypeDef sConfigOC_R = {0};
TIM_OC_InitTypeDef sConfigOC_G = {0};
TIM_OC_InitTypeDef sConfigOC_B = {0};
// 红色通道配置
sConfigOC_R.OCMode = TIM_OCMODE_PWM1;
sConfigOC_R.Pulse = (color >> 16) * 1999 / 255;
sConfigOC_R.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC_R.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_R, COLOR_LED_R) != HAL_OK)
{
Error_Handler();
}
// 绿色通道配置(类似红色,略作说明)
sConfigOC_G.OCMode = TIM_OCMODE_PWM1;
sConfigOC_G.Pulse = ((color >> 8) & 0xff) * 1999 / 255;
sConfigOC_G.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC_G.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_G, COLOR_LED_G) != HAL_OK)
{
Error_Handler();
}
// 蓝色通道配置(类似红色,略作说明)
sConfigOC_B.OCMode = TIM_OCMODE_PWM1;
sConfigOC_B.Pulse = ((color >> 0) & 0xff) * 1999 / 255;
sConfigOC_B.OCPolarity = TIM_OCPOLARITY_LOW;
sConfigOC_B.OCFastMode = TIM_OCFAST_DISABLE;
if (HAL_TIM_PWM_ConfigChannel(&htim2, &sConfigOC_B, COLOR_LED_B) != HAL_OK)
{
Error_Handler();
}
}
- 结构体初始化 :定义三个
TIM_OC_InitTypeDef
结构体,分别用于配置红、绿、蓝通道的 PWM 输出比较参数。 - 参数解析 :
OCMode
:设为TIM_OCMODE_PWM1
,是常用的 PWM 模式,计数器计数小于Pulse
值时,输出有效电平(这里配合OCPolarity
,有效电平为低 )。Pulse
:计算方式为(对应颜色分量)*1999/255
。color
参数格式为0x00RRGGBB
,通过移位操作(如color >> 16
提取红色分量RR
),再映射到 0 - 1999 范围(因为定时器Counter Period
设为 1999 ,占空比 =Pulse
/2000 ),以此控制对应颜色 LED 亮度。OCPolarity
:设为TIM_OCPOLARITY_LOW
,表示有效电平为低电平,与全彩 LED 低电平发光的硬件要求匹配。OCFastMode
:设为TIM_OCFAST_DISABLE
,不使用快速模式。
- 通道配置函数
HAL_TIM_PWM_ConfigChannel
:将配置好的结构体参数应用到 TIM2 对应通道,若配置失败(返回值非HAL_OK
),调用Error_Handler
处理(一般是进入错误死循环等 )。
(五)主函数部分(结合主函数代码理解流程 )
main()
{
color_led_Init();
uint32_t color = 0;
colorLed_setColor(0x00ff0000);
mdelay(1000);
colorLed_setColor(0x00ff00);
mdelay(1000);
colorLed_setColor(0x00ff);
mdelay(1000);
while (1)
{
colorLed_setColor(color);
color += 10000;
mdelay(10);
}
}
- 初始化 :先调用
color_led_Init
初始化全彩 LED 控制,启动各通道 PWM 。 - 初始颜色设置与延时 :依次设置红色(
0x00ff0000
)、绿色(0x00ff00
)、蓝色(0x00ff
),每次设置后延时 1 秒,让 LED 保持对应颜色显示。 - 主循环 :在死循环中,不断改变
color
变量值(每次加 10000 ),调用colorLed_setColor
动态改变全彩 LED 颜色,实现颜色渐变效果,每次改变后延时 10 毫秒,控制渐变速度。