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);
#endif
2
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