引言
在嵌入式开发中,除了对电路进行简单的数字量控制(如【打开/关闭】,也就是“状态”控制),还会进行一些模拟电路控制(也就是“程度”控制)。举个栗子:比如现在控制一盏灯,简单的开关灯,就是对数字量(0、1)的控制;而控制灯的亮度,如把灯调得暗一些,就是对模拟量的控制。PWM 就是用来做“程度”控制的一种技术。
概念
PWM(Pulse Width Modulation,脉宽调制)是一种调制技术,用于控制模拟信号的幅度、频率和相位等特性。PWM 技术通过改变信号的脉冲宽度,来控制信号的平均电压值和电平持续时间,从而实现对模拟信号的控制。
在 PWM 技术中,一个周期的时间被分为若干个等分的时间片,每个时间片内的电平状态由信号的脉冲宽度决定,通常用占空比(Duty Cycle)表示,即高电平时间与一个周期的比值。例如,如果一个周期的时间为 1 秒,高电平时间为 0.5 秒,则占空比为 50%。
图注:定时器工作模式为向上计数,当 CNT<CCRx 时,输出低电平,当 CNT>CCRx 时,输出高电平。当 CNT=ARR 时,重新归零,然后重新向上计数,依次循环。改变 CCRx 的值,就可以改变 PWM 的占空比。改变 ARR 的值,就可以改变 PWM 的输出频率。
举栗:定时器 TIM2 的 APB1 桥频率为 84MHz,PSC = 84 -1,经过预分频器,频率变成了 1MHz(=1/106=0.001ms),ARR 设置为 1000-1,则相当于 1KHz(= 1ms)重载一次,也就意味着 1ms 产生一次中断。①如果想要 0.2s 产生一次中断,则将 ARR 的值设为 200x103-1,如果想要 0.5s 产生一次中断,则将 ARR 的值设为 500x103-1。②如果想要占空比为 50%,则将 CCRx 的值设置为 ARR 的一半。
实操
例程 1:PWM 呼吸灯
步骤
- 打开 STM32CubeMX 软件,按照
STM32CubeMX 通用配置
[1]配置完成后。将 PB0、PB1 引脚分别设置为TIM3_CH3
、TIM3_CH4
。接着配置 TIM3 的 CH3、CH4,如图所示:
配置完成后生成代码。打开 Keil5 MDK 工程文件,按照
Keil5 MDK 通用配置
[1]配置完成后,开始编写代码。打开
main.c
文件。在【main】函数中开启 PWM。1
2
3
4/* USER CODE BEGIN 2 */
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_3);
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_4);
/* USER CODE END 2 */在【main】函数中的【while】循环中写 PWM 控制呼吸灯功能。
1
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/* Infinite loop */
/* USER CODE BEGIN WHILE */
while (1)
{
/** LED0 配置 **/
/* LED0 逐渐熄灭 */
for(uint16_t pwmVal=0; pwmVal<1000; pwmVal++)
{
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, pwmVal);
HAL_Delay(1);
}
/* LED0 逐渐点亮 */
for(uint16_t pwmVal=1000; pwmVal>0; pwmVal--)
{
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_4, pwmVal);
HAL_Delay(1);
}
/** LED1 配置 **/
/* LED1 逐渐熄灭 */
for(uint16_t pwmVal=0; pwmVal<1000; pwmVal++)
{
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, pwmVal);
HAL_Delay(1);
}
/* LED1 逐渐点亮 */
for(uint16_t pwmVal=1000; pwmVal>0; pwmVal--)
{
__HAL_TIM_SetCompare(&htim3, TIM_CHANNEL_3, pwmVal);
HAL_Delay(1);
}
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */编译代码,下载到开发板。例程成功运行。
总结
Q1:pwmVal
变量的值与 LED 灯亮度的关系?
A1:当 pwmVal
的值从 0 到 1000 变化时,PWM 波形的占空比会从 0% 逐渐变化到 100%。具体来说,当 pwmVal 为 0 时,PWM 波形的占空比为 0%,即输出为低电平;当 pwmVal 为 1000 时,PWM 波形的占空比为 100%,即输出为高电平。正点原子阿波罗这款开发板的 LED 等是低电平亮、高电平灭,因此,当 pwmVal
的值增加时,占空比增加,高电平增加,亮度变暗。当 pwmVal
的值减小时,占空比减小,低电平增加,亮度变亮。pwmVal
的值与 LED 灯亮度呈反比关系。
pwmVal | 占空比 | 高电平 | 亮度 |
---|---|---|---|
↑ | ↑ | ↑ | ↓ |
↓ | ↓ | ↓ | ↑ |
Q2:【HAL_Delay】函数的作用?
A2:【HAL_Delay】函数的作用是产生延时,使程序在逐渐增加或降低 LED 亮度的过程中,能够有足够的时间让人眼观察到亮度变化。如果没有 Delay 函数,LED 的亮度会在很短的时间内逐渐变亮或变暗,人眼很难察觉到亮度变化。延时时间越长,越能充分观察到 LED 亮度的变化。但是如果延时时间过长,则会影响程序的响应速度。另外,建议使用【HAL_Delay】作为延时函数而不是另外写一个,因为这个更准确,可以产生 1ms 延时。如下面这个函数,通过循环计数的方式实现延时,即在一个循环中执行一定的操作,直到计数器减为 0,从而产生一定的延时,但是其单位是 CPU 时钟周期或者是毫秒,就不够准确了,所以还是不要用了。
1 | void Delay(unsigned int t) |
例程 2:PWM 舵机
功能实现:①使用 TIM2 中断控制 LED0 的闪烁,用于检测显示程序正在实时运行。②使用 TIM3_CH1 控制 PA6 引脚输出 PWM,控制舵机(SG90 180°)。③使用按键 KEY0 控制 PWM 输出。④使用按键 KEY1 控制 LED1 的亮灭,用于检测显示程序正在实时运行。
步骤
- 打开 STM32CubeMX 软件,按照
STM32CubeMX 通用配置
[1]配置完成后。将 LED 灯引脚 PB0、PB1 引脚设置为GPIO_Output
,将按键引脚 PH2、PH3 引脚设置为GPIO_Input
,将 PA6 引脚设置为TIM3_CH1
。
- 将按键引脚 PH2、PH3 改为上拉(Pull-up)输入。
定时器 TIM3 配置。
180° 舵机周期为 20ms,则 ARR = 20*103-1。
定时器 TIM2 配置。
每 200ms 翻转一次 LED0 电平,ARR = 200*103-1。
使能中断。
配置完成后生成代码。打开 Keil5 MDK 工程文件,按照
Keil5 MDK 通用配置
[1]配置完成后,开始编写代码。分步代码:
① 开启定时器中断和 PWM:打开
main.c
文件。1
2
3
4
5
6
7-----------------------
↓↓↓注:main() 函数内↓↓↓
-----------------------
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2); //开启定时器中断
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); //开启 PWM
/* USER CODE END 2 */② 使用定时器 TIM2 中断控制 LED0 的闪烁:打开
main.c
文件。1
2
3
4
5
6
7
8
9
10-----------------------
↓↓↓注:main() 函数外↓↓↓
-----------------------
/* USER CODE BEGIN 0 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* TIM2--LED0 每 20ms 翻转一次电平 */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
}
/* USER CODE END 0 */③ KEY1 控制 LED1 亮灭:打开
main.c
文件。1
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-----------------------
↓↓↓注:main() 函数外↓↓↓
-----------------------
/* USER CODE BEGIN 0 */
/* 宏定义 KEY0、KEY1 */
void Scan_Keys()
{
/* KEY1--LED1 按下控制亮灭 */
if(KEY1 == 0)
{
HAL_Delay(200); //延时函数去抖动
if(KEY1 == 0)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
while(KEY1 == 0);
}
}
}
/* USER CODE END 0 */
-----------------------
↓↓↓注:main() 函数内↓↓↓
-----------------------
/* USER CODE BEGIN WHILE */
while (1)
{
Scan_Keys();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */④ KEY0 控制 PWM 输出:打开
main.c
文件。1
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-----------------------
↓↓↓注:main() 函数外↓↓↓
-----------------------
/* USER CODE BEGIN 0 */
/* 宏定义 KEY0、KEY1 */
void Scan_Keys()
{
/* KEY0--PWM 按下控制舵机旋转 90° */
if(KEY0 == 0)
{
HAL_Delay(200); //延时函数去抖动
if(KEY0 == 0)
{
/* 运行逻辑:开始舵机在 0° 位置,1500 表示转到 90°,500 表示转到 0°,此处代码的意思是按下 KEY0,舵机由初始位置 0° 旋转到 90°,然后再回到初始位置 0° */
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 1500);
HAL_Delay(1000); // 延时 1s,为了让舵机旋转到 90° 时停一下再旋转
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);
while(KEY0 == 0);
}
}
}
/* USER CODE END 0 */
-----------------------完整代码
1
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
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61-----------------------
↓↓↓注:main() 函数外↓↓↓
-----------------------
/* USER CODE BEGIN 0 */
/* TIM2 中断回调函数:控制 LED0 闪烁 */
void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
{
/* TIM2--LED0 每 20ms 翻转一次电平 */
HAL_GPIO_TogglePin(GPIOB, GPIO_PIN_1);
}
/* 功能函数:扫描按键 */
void Scan_Keys()
{
/* KEY0--PWM 按下控制舵机旋转 90° */
if(KEY0 == 0)
{
HAL_Delay(100); //延时函数去抖动
if(KEY0 == 0)
{
/* 运行逻辑:开始舵机在 0° 位置,1500 表示转到 90°,500 表示转到 0°,此处代码的意思是按下 KEY0,舵机由初始位置 0° 旋转到 90°,然后再回到初始位置 0° */
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 1500);
HAL_Delay(1000); // 延时 1s,为了让舵机旋转到 90° 时停一下再旋转
__HAL_TIM_SET_COMPARE(&htim3, TIM_CHANNEL_1, 500);
while(KEY0 == 0);
}
}
/* KEY1--LED1 按下控制亮灭 */
if(KEY1 == 0)
{
HAL_Delay(200); //延时函数去抖动
if(KEY1 == 0)
{
HAL_GPIO_TogglePin(GPIOB,GPIO_PIN_0);
while(KEY1 == 0);
}
}
}
/* USER CODE END 0 */
-----------------------
↓↓↓注:main() 函数内↓↓↓
-----------------------
/* USER CODE BEGIN 2 */
HAL_TIM_Base_Start_IT(&htim2); //开启定时器中断
HAL_TIM_PWM_Start(&htim3, TIM_CHANNEL_1); //开启 PWM
/* USER CODE END 2 */
/* USER CODE BEGIN WHILE */
while (1)
{
Scan_Keys();
/* USER CODE END WHILE */
/* USER CODE BEGIN 3 */
}
/* USER CODE END 3 */编译代码,下载到开发板。例程成功运行,各功能均已实现。
总结
参考
- 本文标题:PWM 从入门到入土
- 创建时间:2023-04-07 16:02:07
- 本文链接:2023/04/07/055-PWM-从入门到入土/
- 版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!