专栏:STM32F103入门教程
环境:Keil‑MDK + STM32F103 标准库3.5版本
一、PWM
PWM (Pulse‑Width Modulation,脉冲宽度调制):周期固定,改变高电平 / 低电平的占空比,等效输出平均电压。

核心概念
周期 T / 频率 f:完整脉冲重复一次的时间;f=1/T
占空比 D:一个周期内,高电平时间 ÷ 整个周期时间 ×100%
占空比 0%:一直低电平 → 等效输出 0V
占空比 50%:高低各一半 → 等效 ½ 电源电压
占空比 100%:一直高电平 → 等效满电源电压
⚠️输出还是方波数字脉冲,不是真正改变电源电压;靠占空比得到等效模拟效果,后级加 LC 滤波可以变成平滑直流。
PWM 基于"面积等效原理",简要理解面积 = 脉冲电平*脉冲宽度,面积越大输出能量越多,脉冲电平固定得出脉冲宽度(占空比)越大输出能量越多。
二、呼吸灯
灯光由暗→亮→暗循环,模拟呼吸效果。
LED 亮度由 PWM 占空比决定:
占空比越大 → LED 平均电压越高,越亮
占空比越小 → LED 越暗
呼吸效果就是:占空比从小到大,再从大到小,循环往复。
LED 本身只有亮 / 灭,PWM 高速开关,人眼视觉残留,感知为亮度渐变。
三、STM32F103 PWM
stm32f103的PWM也属于定时器部份,上一节讲过定时器的基准设置,再增加输出控制功能就可以了。通用定时器有输出PWM的功能,哪个引脚是对应定时器X的哪个通道要查一下数据手册。

这里采用PA1引脚,定时器2的通道2。定时器通常有几个通道。
3.1 接线


3.2 PWM控制相关
TIM_TimeBaseInitTypeDef 定时器基准初始化结构体,本次设计的频率是1Khz,也就是周期1ms。
TIM_Prescaler 预分频值设为72,频率变为1Mhz,周期就是1微秒。
TIM_Period 自动重装载寄存器周期1000,1us*1000=1ms。
TIM_OCInitTypeDef 定时器输出比较结构体
c
typedef struct
{
uint16_t TIM_OCMode; /*!< Specifies the TIM mode.
This parameter can be a value of @ref TIM_Output_Compare_and_PWM_modes */
uint16_t TIM_OutputState; /*!< Specifies the TIM Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_state */
uint16_t TIM_OutputNState; /*!< Specifies the TIM complementary Output Compare state.
This parameter can be a value of @ref TIM_Output_Compare_N_state
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_Pulse; /*!< Specifies the pulse value to be loaded into the Capture Compare Register.
This parameter can be a number between 0x0000 and 0xFFFF */
uint16_t TIM_OCPolarity; /*!< Specifies the output polarity.
This parameter can be a value of @ref TIM_Output_Compare_Polarity */
uint16_t TIM_OCNPolarity; /*!< Specifies the complementary output polarity.
This parameter can be a value of @ref TIM_Output_Compare_N_Polarity
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
uint16_t TIM_OCNIdleState; /*!< Specifies the TIM Output Compare pin state during Idle state.
This parameter can be a value of @ref TIM_Output_Compare_N_Idle_State
@note This parameter is valid only for TIM1 and TIM8. */
} TIM_OCInitTypeDef;
重点成员简要解释
TIM_OCMode:输出比较模式
TIM_OCMode_PWM1:PWM 模式 1,向上计数:CNT<CCR 输出有效电平。最常用,做 PWM、呼吸灯
TIM_OCMode_PWM2:PWM 模式 2,向上计数:CNT<CCR 输出无效电平
TIM_OCPolarity:输出极性
TIM_OCPolarity_High:有效电平为高电平TIM_OCPolarity_Low:有效电平为低电平(LED 低电平点亮就选这个)
TIM_Pulse
就是 CCR 寄存器初始值。
例如 ARR=999,TIM_Pulse=300,初始占空比 30%;后续运行中用TIM_SetCompare1()动态修改,不要反复改这个结构体。
TIM_OutputState
TIM_OutputState_Enable / Disable,开启 / 关闭该通道 OC 输出到 IO 引脚。
两个预装载,作用对象不一样
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); // CH2通道CCR预装载
TIM_ARRPreloadConfig(TIM2, ENABLE); // ARR自动重装载预装载
1、TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable)
对象:CCR2 寄存器(通道 2 的比较值,控制占空比)
CCR:决定 PWM 占空比。
开启 OC 预装载:调用TIM_SetCompare2()修改 CCR2,新数值不会立刻生效,等到当前 PWM 周期结束(更新事件)才加载到影子寄存器真正生效。
关闭:修改 CCR2,马上立刻生效,有可能在一个周期中间改变占空比,产生毛刺、抖动。
2、TIM_ARRPreloadConfig(TIM2, ENABLE)
对象:ARR 寄存器(自动重装载,控制 PWM 周期 / 频率)
ARR:定时器计数最大值,决定 PWM 周期(频率)。
开启 ARR 预装载:修改 ARR,新值要等下一次更新事件才生效。
关闭 ARR 预装载:写完 ARR 马上生效。
上面两个不打开对于呼吸灯来说感觉不到,如果是数字电源或电机通常都是打开的
STM32 定时器重装载有两套寄存器:
写入寄存器(程序员操作):CPU 写的是这个;
影子寄存器(真正参与计数):定时器硬件实际在跑的。
预装载开启:CPU 写 → 写入寄存器,等到更新事件,才复制到影子寄存器。
预装载关闭:CPU 写,直接改写影子寄存器,立即生效。
四、工程与程序编写
4.1工程复制
复制TIME2工程文件夹为PWM文件夹,TIME2.uvprojx复制为PWM.uvprojx。
4.2 添加驱动文件
- 直接在原time.c文件里面添加。
time.h
c
#ifndef __TIME_H //防止重复包含,编译报错
#define __TIME_H
#include "stm32f10x.h"
void time2_init(uint16_t arr, uint16_t psc); //time2初始化
void time2_ch2_pwm_init(uint16_t arr, uint16_t psc);
#endif
time.c
c
#include "time.h"
#include "led.h"
//通用定时器中断初始化
void time2_init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef tim_time_base_init_structure; //定时器基准初始化结构体变量
NVIC_InitTypeDef nvic_init_structure; //
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //时钟使能
tim_time_base_init_structure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
tim_time_base_init_structure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
tim_time_base_init_structure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
tim_time_base_init_structure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &tim_time_base_init_structure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE );
nvic_init_structure.NVIC_IRQChannel = TIM2_IRQn; //TIM2中断
nvic_init_structure.NVIC_IRQChannelPreemptionPriority = 0; //抢占优先级0级
nvic_init_structure.NVIC_IRQChannelSubPriority = 0; //从优先级0级
nvic_init_structure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&nvic_init_structure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM2, ENABLE); //使能TIMx外设
}
void TIM2_IRQHandler(void) //TIM2中断
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) != RESET) //检查指定的TIM中断发生与否:TIM 中断源
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update ); //清除TIMx的中断待处理位:TIM 中断源
led_toggle();
}
}
void time2_ch2_pwm_init(uint16_t arr, uint16_t psc)
{
GPIO_InitTypeDef gpio_init_struct; //定义gpio初始化结构体变量
TIM_TimeBaseInitTypeDef tim_time_base_init_structure; //定时器基准初始化结构体变量
TIM_OCInitTypeDef tim_oc_init_structure; //定时器输出比较结构体初始化
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //使能GPIO外设时钟使能
//设置该引脚为复用输出功能,输出TIM1 CH1的PWM脉冲波形
gpio_init_struct.GPIO_Pin = GPIO_Pin_1; //TIM2_CH2
gpio_init_struct.GPIO_Mode = GPIO_Mode_AF_PP; //复用推挽输出
gpio_init_struct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &gpio_init_struct);
tim_time_base_init_structure.TIM_Period = arr; //设置在下一个更新事件装入活动的自动重装载寄存器周期的值
tim_time_base_init_structure.TIM_Prescaler = psc; //设置用来作为TIMx时钟频率除数的预分频值
tim_time_base_init_structure.TIM_ClockDivision = TIM_CKD_DIV1; //设置时钟分割
tim_time_base_init_structure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数模式
TIM_TimeBaseInit(TIM2, &tim_time_base_init_structure); //根据TIM_TimeBaseInitStruct中指定的参数初始化TIMx的时间基数单位
tim_oc_init_structure.TIM_OCMode = TIM_OCMode_PWM1; //选择定时器模式:TIM脉冲宽度调制模式1
tim_oc_init_structure.TIM_OutputState = TIM_OutputState_Enable; //比较输出使能
tim_oc_init_structure.TIM_Pulse = 300; //设置待装入捕获比较寄存器的脉冲值
tim_oc_init_structure.TIM_OCPolarity = TIM_OCPolarity_Low; //输出极性:TIM输出比较极性高 TIM_OCPolarity_Low TIM_OCPolarity_High
TIM_OC2Init(TIM2, &tim_oc_init_structure); //根据TIM_OCInitStruct中指定的参数初始化外设TIMx
TIM_OC2PreloadConfig(TIM2, TIM_OCPreload_Enable); //CH2预装载使能
TIM_ARRPreloadConfig(TIM2, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_Cmd(TIM2, ENABLE); //使能TIM2
}
main.c
c
#include "stm32f10x.h"
#include "led.h"
#include "delay.h"
#include "key.h"
#include "usart.h"
#include "exti.h"
#include "time.h"
#include <stdio.h>
int main(void)
{
int direction = 1;
int pwm_width=0;
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_4);// 设置中断优先级分组4
//led_init();
delay_init();
usart1_init(115200);
//key_init();
//exit_init();
//time2_init(20000-1, 3600-1);
time2_ch2_pwm_init(1000-1,72-1);
while(1)
{
if(direction == 1){
TIM_SetCompare2(TIM2,pwm_width++);//
delay_ms(3);
if(pwm_width > 900){
direction =0;
}
}else{
if(pwm_width >1){
TIM_SetCompare2(TIM2,pwm_width--);
delay_ms(3);
}else{
direction =1;
}
}
}
}
direction 为方向标志,1表示增加亮度,占空比增加。最多可以增加到1000,实测900~1000之间亮度看不出变化。改变占空比后延长3ms的时间。
4.5 编译烧录
编译烧录,可以看到呼吸灯的效果。大家也可以接到其它的定时器通道,修改定时器的频率和周期值 修改延时时间等看效果。
呼吸灯
资料下载
参考资料网盘 :
链接 : https://pan.baidu.com/s/1BrH0JbUY6z7j7gAihTa4Dw
提取码: qnws
本节课完整工程例程网盘:
链接: https://pan.baidu.com/s/1rl6EIqi9m6kQ1JFjuSqINg
提取码: bumc