【无标题】

(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=71CK_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 周期)" 为例,走一遍完整计数过程:

  1. 启动计数 :触发控制器收到使能信号(CEN=1 ),CK_CNT 开始驱动 CNT 从 0 递增。
  2. 正常计数CNT 每个 CK_CNT 周期 +1,依次经过 0→1→2→...→999
  3. 溢出与更新事件 :当 CNT=ARR=999 时,下一个 CK_CNT 沿触发 溢出CNT 归零,同时触发:
    • 更新事件(UEV) :若 ARPE=1,预装载的 ARR 新值(如有修改 )会加载到 "影子寄存器" 生效;
    • 中断标志(UIF) :置位 Update interrupt flag,若使能中断则进入中断服务程序。
  4. 周期循环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 切换" 为例,流程:

  1. 初始 PSC=0(分频系数 1 ),CK_CNT=CK_PSC 驱动计数器;
  2. 写入新 PSC=1(分频系数 2 ),若 ARPE=1(预装载使能 ),新分频系数在 更新事件(UEV) 时生效;
  3. UEV 触发后,CK_CNT=CK_PSC/2,计数器按新频率计数,体现 "分频系数延迟生效" 逻辑。

(二)基础计数时序(Figure 55 )

以 "内部时钟分频 1、向上计数" 为例,流程:

  1. CEN 使能后,CK_CNT 驱动计数器从 31 递增;
  2. 计数到 ARR=36 时触发溢出,CNT 归零,同时触发 UEV、置位 UIF
  3. 新周期从 0 开始,重复 "计数 → 溢出",实现周期性定时。

五、知识串联与应用

(一)基础定时器典型应用

  • 定时中断 :配置 PSC=71ARR=999(72MHz 时钟 → 1ms 定时 ),在 UIF 中断中执行周期性任务(数据采集、电机控制 )。
  • 触发 DAC :通过 TRGO 触发 DAC 同步转换,实现 "定时器定时输出波形"(如正弦波周期性更新 )。

(二)通用定时器典型应用

  • 外部脉冲计数 :利用 TIMx_CH 输入滤波、边沿检测,统计电机编码器脉冲(计算转速 )。
  • 输入捕获测频:捕获外部信号上升沿,计算信号周期、频率(如红外信号解码 )。

(2)定时器与延时函数

一、概述

本文档针对基于 SysTick 定时器实现的延时函数(udelaymdelay)和纳秒级时间获取函数(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 = 500tnow = 300,经过周期 = 500 - 300 = 200
  • 溢出(told < tnow
    例:load = 1000told = 100tnow = 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 = 999tnow = 300,则 cnt = 1000 - 300 = 700(已过 700 个周期)。
  • cnt * 1000000 / (load + 1)
    将周期数转换为纳秒(1 毫秒 = 1,000,000 纳秒,1 周期 = 1,000,000/(load+1) 纳秒)。
    例:load = 999cnt = 700,则对应纳秒 = 700 * 1000000 / 1000 = 700,000 纳秒(0.7 毫秒)。

五、总结

  1. 所有函数均依赖 SysTick 定时器 ,核心是通过 LOADVAL 寄存器计算时间。
  2. 延时函数(udelaymdelay)通过累计周期数实现精确等待;system_get_ns 通过组合毫秒数和周期数实现高精度时间获取。
  3. 前提条件:SysTick 需配置为 load + 1 个周期 = 1 毫秒(由时钟频率决定)。

(3)STM32 高精度计时(Timer)

一、核心目标

实现基于 STM32 定时器(SysTick 或 TIM4 二选一 )的 微秒级延时udelay )和 纳秒级系统时间获取system_get_ns ),通过 条件编译 灵活切换定时器,满足不同场景下的高精度计时需求。

二、知识准备

(一)定时器基础

STM32 的定时器(SysTick、TIM4 等 )本质是 "递减计数器"

  1. 从一个 "最大值" 开始(SysTick 用 LOAD 寄存器,TIM4 用 "自动重载值" )
  2. 每个时钟周期减 1,减到 0 后自动重新加载 "最大值",循环计数

(二)条件编译

#ifdef #else #endif 控制代码分支:
4. 定义 #define USE_SYSTICK → 用 SysTick 定时器
5. 没定义 → 用 TIM4 定时器(和 STM32CubeMX 配置对应 )
6.

三、STM32CubeMX 配置(必看!)

(一)配置界面

Pinout & ConfigurationSystem CoreSYS 里:
7. Timebase SourceTIM4
这是告诉 STM32:HAL 库的默认 "时间基准" 用 TIM4 定时器
代码里的 #else 分支,就是为这种配置写的逻辑!
8.

(二)配置的意义

  1. 如果需要更高灵活性(比如 SysTick 被其他功能占用 ),可以手动定义 #define USE_SYSTICK,代码会自动切换到 SysTick 分支。
  2. 两种定时器的逻辑用 条件编译 分开,适配不同需求。
复制代码
### 四、核心代码逐行解析(结合手绘示意图)

你的代码里有 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生成)
  1. #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
   }
关键逻辑
  1. load + 1 的意义

    定时器从 load 减到 0,总共经历 load + 1 个周期(比如 load=100,是 100→99→...→0,共 101 次 )。

    所以 1ms 对应 load + 1 个周期us 微秒就对应 us*(load+1)/1000 个周期。

  2. 溢出处理

    手绘示意图里的 "tnow < told" 就是溢出场景!

    • SysTick 溢出:cnt += told + load + 1 - tnow(补全溢出的周期 )
    • TIM4 溢出:cnt += (load + 1 - told) + tnow(逻辑相同,写法不同 )

3. mdelay(int ms):毫秒延时(简单封装)

复制代码
   void mdelay(int ms)
   {
     for(int i = 0; i < ms; i++)
     {
       udelay(1000);  // 1ms = 1000us
     }
   }
  1. 直接调用 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
   }
  1. 思路:
    1. 先用 HAL_GetTick() 拿到 毫秒数,转成纳秒。
    2. 再用定时器计数值,补上 "当前毫秒内的纳秒数",实现 纳秒级精度

五、总结(复习脉络)

  1. 配置关联

    • CubeMX 里 Timebase Source = TIM4 → 代码走 #else 分支(用 TIM4 )。
    • 定义 USE_SYSTICK → 走 #ifdef 分支(用 SysTick )。
  2. 核心逻辑

    不管用哪种定时器,都是 "累计周期数" ,处理 "溢出场景" 是关键!手绘示意图里的 tnow < told 就是溢出,必须补全周期。

  3. 函数关系

    • udelay 是基础(微秒级 )→ mdelay 封装它(毫秒级 )。
    • system_get_ns 结合 HAL_GetTick() 和定时器计数值,实现纳秒级时间。
  4. 实践验证

    用示波器抓 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 比较,控制电平翻转

三、关键逻辑拆解(结合手绘与架构图)

(一)寄存器配置流程

  1. 设置 ARR(AutoReload)
    配置 PWM 周期 T ,比如 ARR 值为 999 ,则周期由定时器时钟和 ARR 共同决定(需结合分频器,假设分频后时钟为 1MHz,周期 T = (ARR + 1)/1MHz ,因计数从 0 到 ARR 共 ARR + 1 个时钟 )。
  2. 设置 CCRn(Capture/Compare Register)
    配置占空比,CCR 值决定 "高电平持续时长"。如 ARR = 999、CCR = 500,则占空比 duty = 500/1000 = 50%(高电平占一半周期 )。
  3. 设置 "有效电平"
    决定 PWM 高 / 低电平的 "有效触发条件"(如 CNT ≤ CCR 时输出高电平,CNT > CCR 时输出低电平 ),对应手绘 "有效电平:高 / 低" 配置。

(二)PWM 信号生成逻辑

  1. 计数与电平翻转
    CNT 从 0 开始递增,当 CNT ≤ CCR 时,输出 "有效电平(高)";当 CNT > CCR 时,输出 "无效电平(低)"。
    CNT 计到 ARR 后重置为 0,循环计数,持续输出 PWM 波形(时序图中,CCR 位置触发电平翻转,ARR 位置重置计数 )。
  2. 占空比与 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 ,示波器可观测到 "方波"

五、总结

  1. 寄存器作用
    ARR 控周期、CCR 控占空比、CNT 实现计数与比较,三者配合生成 PWM 信号
  2. 配置流程
    CubeMX 选定时器模式 → 设 ARR(周期 )、CCR(占空比 )→ 代码初始化并启动 PWM
  3. 时序逻辑
    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();
    }
}
  1. 结构体初始化 :定义三个TIM_OC_InitTypeDef结构体,分别用于配置红、绿、蓝通道的 PWM 输出比较参数。
  2. 参数解析
    • OCMode:设为TIM_OCMODE_PWM1,是常用的 PWM 模式,计数器计数小于Pulse值时,输出有效电平(这里配合OCPolarity,有效电平为低 )。
    • Pulse:计算方式为(对应颜色分量)*1999/255color参数格式为0x00RRGGBB,通过移位操作(如color >> 16提取红色分量RR ),再映射到 0 - 1999 范围(因为定时器Counter Period设为 1999 ,占空比 = Pulse/2000 ),以此控制对应颜色 LED 亮度。
    • OCPolarity:设为TIM_OCPOLARITY_LOW,表示有效电平为低电平,与全彩 LED 低电平发光的硬件要求匹配。
    • OCFastMode:设为TIM_OCFAST_DISABLE,不使用快速模式。
  3. 通道配置函数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 毫秒,控制渐变速度。
相关推荐
嵌入式小李4 小时前
stm32项目(24)——基于STM32的汽车CAN通信系统
stm32·嵌入式硬件·汽车
程序员JerrySUN5 小时前
四级页表通俗讲解与实践(以 64 位 ARM Cortex-A 为例)
java·arm开发·数据库·redis·嵌入式硬件·缓存
优信电子5 小时前
基于STM32F103驱动SI5351 3通道时钟信号发生器输出不同频率信号
单片机·嵌入式
DIY机器人工房5 小时前
开发板RK3568和stm32的异同:
嵌入式硬件·嵌入式·diy机器人工房
酷飞飞16 小时前
库函数版独立按键用位运算方式实现(STC8)
单片机·嵌入式硬件
我怕是好17 小时前
STM32 输入捕获,串口打印,定时器,中断综合运用
stm32·单片机·嵌入式硬件
国科安芯18 小时前
质子试验:守护芯片安全的关键防线
嵌入式硬件·安全·fpga开发·性能优化·硬件架构
zhmc20 小时前
MCU程序的ARM-GCC编译链接
arm开发·单片机·嵌入式硬件
点灯小铭21 小时前
基于STM32单片机的OneNet物联网环境检测系统
stm32·单片机·物联网·毕业设计·课程设计