🔬 实验15:定时器——PWM

📌 实验目标
  • 掌握定时器 PWM 输出模式的配置
  • 理解占空比和频率的计算
  • 实现 LED 呼吸灯效果
  • 了解 PWM 在直流电机调速中的应用

一、实验内容

  1. 配置 TIM2 CH2 (PA1) 为 PWM 输出
  2. 设置 PWM 频率 20KHz
  3. 动态改变占空比实现呼吸灯
  4. 使用 PWM 控制蜂鸣器和 LED

二、PWM 基本原理

PWM(脉冲宽度调制)的本质是:定时器计数器 CNT 从 0 递增到自动重装值 ARR,同时与捕获/比较寄存器 CCRx 比较——CNT 小于 CCRx 时输出有效电平,否则输出无效电平。因此:

  • ARR 决定 PWM 周期(频率)
  • CCRx 决定 PWM 占空比(高电平所占比例)
频率与占空比计算
// 定时器输入时钟 72MHz(APB1 分频后翻倍得到)
// 设 PSC = 0, ARR = 71 → 计数周期 = (ARR+1)/(Tclk/(PSC+1))
// PWM 频率 = Tclk / ((PSC+1) * (ARR+1))
//         = 72MHz / (1 * 72) = 1MHz

// 占空比 = CCR / (ARR+1) * 100%
// 例:ARR=9(10 级亮度),CCR=8 → 占空比 80%
📌 本实验硬件映射
  • LED0 → PB5 = TIM3_CH2(默认在 PA7,需部分重映射到 PB5)
  • 蜂鸣器 BEEP → PB8 = TIM4_CH3(默认引脚,无需重映射)

三、示例程序:PWM 呼吸灯 + 蜂鸣器

timer.c — TIM3_CH2(PB5) 与 TIM4_CH3(PB8) PWM 初始化
#include "stm32f10x.h"
#include "timer.h"

//====================================================================
// TIM4_CH3 → PB8 蜂鸣器
// PWM 频率 1KHz,占空比 80%(任务要求)
// 周期 1ms → ARR=9,可分成 10 个等级
//====================================================================
void TIM4_PWM_Init(u16 arr, u16 psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 1. 使能定时器 4 与 GPIOB 时钟
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

    // 2. PB8 复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_8;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 3. 初始化定时器
    TIM_TimeBaseStructure.TIM_Period = arr;          // ARR 决定周期
    TIM_TimeBaseStructure.TIM_Prescaler = psc;       // PSC 决定分频
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);

    // 4. 初始化输出比较(PWM 模式 2,输出使能,极性高)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC3Init(TIM4, &TIM_OCInitStructure);          // CH3

    // 5. 使能预装载寄存器,使能定时器
    TIM_OC3PreloadConfig(TIM4, TIM_OCPreload_Enable);
    TIM_Cmd(TIM4, ENABLE);
}

//====================================================================
// TIM3_CH2 → PB5 LED0(需部分重映射)
// PWM 频率 1KHz,占空比 30%(任务要求)
//====================================================================
void TIM3_PWM_Init(u16 arr, u16 psc)
{
    GPIO_InitTypeDef GPIO_InitStructure;
    TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
    TIM_OCInitTypeDef TIM_OCInitStructure;

    // 1. 使能 TIM3、GPIOB 时钟 + AFIO 时钟(重映射必须)
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);

    // 2. 部分重映射 TIM3_CH2 → PB5
    GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);

    // 3. PB5 复用推挽输出
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_Init(GPIOB, &GPIO_InitStructure);

    // 4. 初始化定时器(周期 1ms)
    TIM_TimeBaseStructure.TIM_Period = arr;
    TIM_TimeBaseStructure.TIM_Prescaler = psc;
    TIM_TimeBaseStructure.TIM_ClockDivision = 0;
    TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);

    // 5. 初始化 PWM 输出(CH2)
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM2;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OC2Init(TIM3, &TIM_OCInitStructure);

    TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
    TIM_Cmd(TIM3, ENABLE);
}
main.c — 主程序与呼吸灯实现
#include "stm32f10x.h"
#include "delay.h"
#include "timer.h"

int main(void)
{
    u16 pwmval = 0;   // 当前占空比
    u8  dir = 1;      // 方向:1 变亮,0 变暗

    delay_init();            // 延时函数初始化
    LED_Init();              // LED 端口初始化
    // TIM4_CH3 → PB8 蜂鸣器,ARR=9 分 10 级,80% 占空比
    TIM4_PWM_Init(9, 0);
    // TIM3_CH2 → PB5 LED,ARR=19,30% 占空比(可在此修改比较值)
    TIM3_PWM_Init(19, 0);
    TIM_SetCompare3(TIM4, 8);   // 蜂鸣器 8/10 = 80%
    TIM_SetCompare2(TIM3, 6);   // LED 6/20 = 30%

    while (1)
    {
        // 呼吸灯:占空比 0→100→0 循环
        if (dir == 1) { pwmval++; if (pwmval > 100) dir = 0; }
        else          { pwmval--; if (pwmval == 0)   dir = 1; }

        // ARR=9 时 10 级亮度,映射到 0~9
        TIM_SetCompare3(TIM4, pwmval * 9 / 100);
        delay_ms(10);
    }
}
💡 这段代码怎么用?
  1. 在工程中加入 timer.c / timer.h,并把固件库的 stm32f10x_tim.cstm32f10x_tim.h 加入编译
  2. 先编译下载观察 LED0(PB5)的亮度、蜂鸣器(PB8)声音
  3. 修改占空比:改 TIM_SetCompare3(TIM4, x) / TIM_SetCompare2(TIM3, x) 的第二个参数即可,这就是「它有什么用」——改变亮度/音量
  4. 呼吸灯核心:在 while(1) 里循环改占空比,配合 delay_ms 控制变化速度
⚠️ 常见错误
  • 忘记使能 AFIO 时钟 → 重映射不生效,PB5 无 PWM 输出
  • 引脚模式必须为 GPIO_Mode_AF_PP(复用推挽),设成普通输出则无波形
  • LED 通常低电平点亮,需结合硬件验证有效极性

四、参考资料

资料文件
PWM PPT定时器-PWM.pptx
实验指导文档实验6 定时器-PWM.docx
代码工程15.pwm-led-beep-2022-a.rar