avatar🌌
零醇零醇の小屋

樱花树下,再续前缘

鹤望兰无凋零之时,比翼鸟永世长存

STM32 HAL库入门教程(四):定时器与 PWM 输出(呼吸灯实战)

前面三篇教程里,我们手头的小灯泡只有两种极端的宿命:要么完全亮,要么完全灭。

那如果我想让小灯泡变得像有生命一样,温柔地“一呼一吸”呢? 肯定有聪明的小伙伴会说:“我知道!给它输入一个一半的电压,比如 1.65V,不就半亮了吗?” 想法很棒,但是我们使用的 STM32F103C8T6 引脚只有 0 和 1(0V 和 3.3V),它根本输出不了中间的电压呀喵~

那怎么办呢?这就轮到今天的新魔法登场啦——定时器与 PWM 输出!(。・ω・。)ノ♡


1. 什么是 PWM? ​

听名字又是缩写,别怕别怕,我们用人话来解释: 既然灯泡只有全亮和全灭,那我们只要用“单身二十年的手速”,在一秒钟里把灯疯狂开关上千次!

比如在 1ms 的时间里:

  • 让它亮 0.1ms,灭 0.9ms,我们的眼睛由于“视觉暂留”就会觉得它很暗;
  • 让它亮 0.9ms,灭 0.1ms,我们的眼睛就会觉得它很亮!

这种利用“通电时间的长短”来控制有效功率的技术,就叫做 PWM 啦喵~

这里有两个最核心的概念: 看图: 占空比

  1. “周期 / 频率”:开关一次总共花多长时间。比如一秒钟重复 1000 次,频率就是 1000Hz(1kHz),太慢了眼睛会看到在闪烁,太快了硬件吃不消。
  2. “占空比”:在一个周期里,“高电平亮灯的时间”占整个周期的百分比(0% 就是完全熄灭,100% 就是完全点亮,50% 就是半亮)。

2. 定时器参数与计算公式 ​

那我们怎么用 STM32 的定时器来生成精准的 PWM 呢? 单片机的主频是 72MHz。 定时器里有两个关键小齿轮:

  • “PSC(预分频器)”:把飞快的心跳减速,相当于分频;
  • “ARR(自动重装载值)”:计数器数到多少算一个周期,相当于时钟走一圈的刻度。

计算公式超级简单:

PWM 频率=72MHz(PSC+1)×(ARR+1)\text{PWM 频率} = \frac{72\text{MHz}}{(\text{PSC} + 1) \times (\text{ARR} + 1)}

我们来凑一个最舒服的整数组合:

  • 把 PSC 设成 71(变成 72MHz/72=1MHz72\text{MHz} / 72 = 1\text{MHz},也就是 1 微秒数一下);
  • 把 ARR 设成 999(从 0 数到 999 刚好数了 1000 下,也就是 1000 微秒 = 1ms)。

这样算出来 PWM 频率正好是 1kHz,而且一个周期刚好被切成了 1000 份! 占空比调节范围就是 0 到 1000,比如写 500 就是正好 50% 占空比,简直太治愈了喵~ (=´ω`=)

💡 哎等等,板载的那个 PC13 呢? 很遗憾,PC13 是个没有硬件定时器连通的残疾引脚,根本输出不了硬件 PWM! 所以这次我们要拿一个散装小 LED(或者接个面包板),正极插在 PA0 上,负极接地 GND(还记得第一期说的长脚正短脚负喵~)。


3. CubeMX 工程配置 ​

打开我们熟悉的 CubeMX,选择我们的 STM32F103C8T6,老规矩配好时钟和调试口。

在左侧找到 Timers -> 点击 TIM2:

  • 把 Clock Source 选为 Internal Clock;
  • 把 Channel 1 选为 PWM Generation CH1(你会发现右边芯片的 PA0 自动变绿啦!)。 图1

在下面的参数配置里:

  • Prescaler 填 71;
  • Counter Period 填 999;
  • 再往下拉展开 PWM Generation Channel 1:
    • Mode 选 PWM mode 1;
    • Pulse 填 0(默认占空比为 0,先不亮)。 图2

配置好工程,点击生成代码,打开 Keil 工程! 图3


4. 编写代码让它呼吸起来! ​

写代码环节到啦! 首先,在 main.c 的 while 循环前面,必须先启动定时器的 PWM 功能:

c
/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim2, TIM_CHANNEL_1);
/* USER CODE END 2 */

那怎么在运行中修改占空比呢?HAL 库提供了一个超方便的宏定义:

c
__HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, pwm_value);

只要改变 pwm_value(范围 0 ~ 1000),亮度就会跟着变啦!

现在我们让它一呼一吸起来,在 while(1) 里写上:

c
/* USER CODE BEGIN WHILE */
while (1)
{
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
    // 逐渐变亮(吸气~)
    for (int i = 0; i <= 1000; i += 10)
    {
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
        HAL_Delay(15);
    }
    // 逐渐变暗(呼气~)
    for (int i = 1000; i >= 0; i -= 10)
    {
        __HAL_TIM_SET_COMPARE(&htim2, TIM_CHANNEL_1, i);
        HAL_Delay(15);
    }
}
/* USER CODE END 3 */
图4

下载、烧录,看一眼面包板上的小灯泡! 是不是像睡着的小猫咪一样,极其丝滑温和地一呼一吸啦?(=^・ω・^=)

学会了 PWM 以后不光能做呼吸灯,还能拿去控制舵机转角度、电机调转速,玩法多得很呢!

完结撒花!(=^・ω・^=) ​

快去享受你的第一个呼吸灯吧喵~

STM32 HAL库入门教程(三):外部中断(EXTI)与 NVIC 详解