PWM 驱动 LED 
Author:余生
一、基本原理 
PWM 基本原理 
想象一下,你有一个灯,只能通过 “快速开关” 来控制它的亮度。你开一秒、关一秒,灯看起来就是半亮的。如果你开的时间更短,关的时间更长,灯看起来就更暗。
PWM(Pulse Width Modulation,脉冲宽度调制)就是这个 “快速开关” 的技术。
它是怎么工作的? 
PWM 输出的是一个方波信号,也就是电压在高(比如 3.3V)和低(0V)之间来回切换。
它有两个关键参数:
- 频率(Frequency):每秒开关多少次。 - 比如频率是 1kHz,就是每秒开关 1000 次,人眼根本看不出来闪烁。
- 频率一般固定不变。
 
- 占空比(Duty Cycle****):在一个周期里,“高电平” 占的时间比例。 - 比如占空比 50%:一半时间开,一半时间关 → 灯半亮。
- 占空比 80%:80% 时间开,20% 时间关 → 灯很亮。
- 占空比 20%:灯就比较暗。
 
简单记:占空比越大,平均电压越高,灯越亮(或电机越快)。
举个生活例子 
你用花洒 watering 花,不能调水量,只能 “开一秒、关一秒” 来控制浇水量。
- 开 1 秒、关 1 秒 → 平均浇了一半的水(50% 占空比)
- 开 3 秒、关 1 秒 → 浇得多(75% 占空比)
- 开得越久,花得到的水越多。
PWM 就是这个道理,只不过开关速度非常非常快!
在 STM32 里怎么用? 
STM32 的定时器(Timer)可以生成 PWM 信号。你只需要:
- 设置好频率(比如 1kHz)
- 设置占空比(比如 50%)
- STM32 就会自动从某个引脚输出 PWM 波
比如控制 LED 亮度、电机转速、舵机角度等,都靠它!
二、硬件连接 

三、代码编写 
先新建文件

PWM.h 
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif2
3
4
5
6
7
PWM.c 
#include "stm32f10x.h"  // Device header
void PWM_Init(void) {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
    //        RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
    //        GPIO_PinRemapConfig(GPIO_PartialRemap1_TIM2, ENABLE);
    //        GPIO_PinRemapConfig(GPIO_Remap_SWJ_JTAGDisable, ENABLE);
    GPIO_InitTypeDef GPIO_InitStructure;
    GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
    GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;  // GPIO_Pin_15;
    GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
    GPIO_Init(GPIOA, &GPIO_InitStructure);
    TIM_InternalClockConfig(TIM2);
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
    TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
    TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;     // ARR
    TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1;  // PSC
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
    TIM_OCInitTypeDef TIM_OCInitStructure;
    TIM_OCStructInit(&TIM_OCInitStructure);
    TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
    TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
    TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
    TIM_OCInitStructure.TIM_Pulse = 0;  // CCR
    TIM_OC1Init(TIM2, &TIM_OCInitStructure);
    TIM_Cmd(TIM2, ENABLE);
}
void PWM_SetCompare1(uint16_t Compare) { TIM_SetCompare1(TIM2, Compare); }2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
这个函数是干啥的? 
配置一个定时器(TIM2),让它产生一个 PWM 信号,从 PA0 引脚输出。
这个 PWM 信号可以用来控制一些外部设备,比如调节 LED 的亮度、电机的速度等。
详细解析 
1. 打开时钟电源 
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);2
类比:
“我要用 TIM2 定时器和 GPIOA,请给它们通电!”
- RCC_APB1Periph_TIM2是 TIM2 定时器的时钟
- RCC_APB2Periph_GPIOA是 GPIOA 端口的时钟
2. 配置 GPIOA 的 PA0 为复用推挽输出模式 
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP; // 复用推挽输出
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;       // 使用PA0引脚
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure);2
3
4
5
意思是:
把 PA0 引脚设置为 “复用推挽输出”,专门用来输出 TIM2 产生的 PWM 信号。
复用推挽输出:允许定时器控制该引脚的高低电平。
3. 初始化定时器 TIM2 的基本参数 
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1;   // ARR = 99
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; // PSC = 719
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);2
3
4
5
6
7
计算定时器频率:
- STM32 主频:72MHz
- 分频器(PSC):719 → 72MHz / (719 + 1) = 100kHz
- 自动重装载值(ARR):99 → 100kHz / (99 + 1) = 1kHz
这意味着 定时器每 1ms 溢出一次,也就是 PWM 信号的周期是 1ms。
4. 配置 PWM 模式 
TIM_OCInitTypeDef TIM_OCInitStructure;
TIM_OCStructInit(&TIM_OCInitStructure);
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1;
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStructure.TIM_Pulse = 0;  // CCR = 0
TIM_OC1Init(TIM2, &TIM_OCInitStructure);2
3
4
5
6
7
关键参数解释:
- TIM_OCMode_PWM1:选择 PWM 模式 1(上升沿对齐)
- TIM_OCPolarity_High:输出极性为高电平有效
- TIM_OutputState_Enable:使能输出
- TIM_Pulse = 0:初始占空比为 0%
PWM 模式 1:在每个周期开始时输出低电平,到达 CCR 值时切换为高电平,直到周期结束。
5. 启动定时器 
TIM_Cmd(TIM2, ENABLE);意思是:
“启动定时器 TIM2,开始计数!”
6. 设置占空比的函数 
void PWM_SetCompare1(uint16_t Compare)
{
    TIM_SetCompare1(TIM2, Compare);
}2
3
4
这是一个方便的函数,用来动态调整 PWM 的占空比。
- Compare值决定了高电平持续的时间。
- 最大值为 ARR(99),所以占空比 = Compare / ARR * 100%
例如:
- 如果 Compare = 50,则占空比为 50/99 ≈ 50%
- 如果 Compare = 99,则占空比为 99/99 = 100%
总结 
定时器 + PWM 就像一个自动开关灯的装置:
- 定时器决定灯亮灭的周期(比如 1ms)
- PWM 决定每次亮多久(占空比)
- 通过改变 CCR 值,你可以调整灯亮的时间比例
实际效果 
假设你调用了 PWM_SetCompare1(50); ,那么:
- 周期:1ms(由 ARR=99 决定)
- 占空比:50/99 ≈ 50%
- 实际输出:PA0 引脚上会有 50% 时间是高电平,50% 时间是低电平
如果你把 Compare 改成其他值,比如 25 或 75 ,就能看到不同的占空比效果。
注意 
- 占空比 = CCR / ARR * 100%
- 周期 = (PSC + 1) * (ARR + 1) / fclk,其中fclk是定时器的输入频率(这里是 72MHz)
- 注意:如果想控制 LED 亮度或电机速度,可以通过改变 Compare值来实现平滑调节
如果你想继续做:
- 用按键调节 PWM 占空比
- 结合 ADC 读取传感器值控制 PWM 输出
- 显示当前占空比到 OLED 屏幕上
main.c 
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
#include "stm32f10x.h"  // Device header
uint8_t i;
int main(void) {
    OLED_Init();
    PWM_Init();
    while (1) {
        for (i = 0; i <= 100; i++) {
            PWM_SetCompare1(i);
            Delay_ms(10);
        }
        for (i = 0; i <= 100; i++) {
            PWM_SetCompare1(100 - i);
            Delay_ms(10);
        }
    }
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22