早上好啊,大伙。上一期说完了定时器的原理和模板。这一期就来运用一下定时器来实现一些模块的功能。
对于定时器的一大应用就是PWM波调速,通过PWM调速能够实现很多模块的使用。所以今天这一期我们就来讲讲什么是PWM,讲完之后给大伙用PWM做一个呼吸灯。
什么是PWM
PWM 也就是脉冲宽度调制,是一种通过调节数字信号的脉冲宽度来控制模拟设备的技术。其核心思想是快速切换电源的通断,通过改变高电平(导通)与低电平(关断)的时间比例(即占空比),来模拟不同的平均电压或功率输出。
不知道大家前面有没有学过数码管,用一个寄存器控制多个数码管就是通过快速变换,来实现多个数码管的显示。
再比如说,我们看的动漫也是这样实现的。
PWM的频率
单位时间内(通常为1秒)脉冲信号重复的次数,单位为赫兹(Hz)。
频率 = 1 周期 频率 = \frac{1}{周期} 频率=周期1
PWM的周期
一个完整脉冲循环(从高电平开始到下一个高电平开始)的时间长度,单位为秒(s)。
周期 = 1 频率 周期 = \frac{1}{频率} 周期=频率1
举个栗子
频率 ——
若频率为 1 kHz(1000 Hz),则周期为:
周期 = 1 1000 = 0.001 秒 周期 = \frac{1}{1000} = 0.001秒 周期=10001=0.001秒
周期 ——
若周期为 20 μs(微秒),则频率为:
频率 = 1 T = 1 20 × 1 0 − 6 = 50 kHz \text{频率} = \frac{1}{T} = \frac{1}{20 \times 10^{-6}} = 50\,\text{kHz} 频率=T1=20×10−61=50kHz
PWM的占空比
是一个脉冲周期内,高电平的时间与整个周期时间的比例
单位: % (0%-100%)
表示方式:20%
周期: 一个脉冲信号的时间 , 1s内测周期次数等于频率
脉宽时间: 高电平时间
上图中,脉宽时间占总周期时间的比例,就是占空比。
PWM原理
利用快速开关电源的通断,通过改变高电平持续时间占整个周期的比例(占空比),控制输出到负载的平均电压或功率。
OC(Output Compare)输出比较
输出比较可以通过比较CNT与CCR寄存器值的关系,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形
每个高级定时器和通用定时器都拥有4个输出比较通道
高级定时器的前3个通道额外拥有死区生成和互补输出的功能
PWM(Pulse Width Modulation)脉冲宽度调制
在具有惯性的系统中,可以通过对一系列脉冲的宽度进行调制,来等效地获得所需要的模拟参量,常应用于电机控速等领域
PWM参数:
频率 = 1 / TS
占空比 = TON / TS
分辨率 = 占空比变化步距
输出比较通道(通用)框图
PWM代码实现
第一步、添加上期的 TIM 时钟的代码
这一步就不放代码。
就需要把ARR和PSC调一下,改成我们所想要的就行了。
TIM_TimeBaseInitStructure.TIM_Period = 100 - 1; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 720 - 1; //预分频器,即PSC的值
第二步、确定输出比较通道
每一个输出比较通道都有它对应的引脚口,可以看一下下面的引脚图 ——
例如说,下面 TIM2 的几个 OC 通道。
这里我们就用 TIM2 的 OC1 通道为例。
void PWM_Init(void)
{
Timer_Init(); //导入之前写的TIM时钟定义
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
}
第三步、OC 通道初始化
在OC的初始化里面有很多的参数都是高级定时器里面才会用的,所以我们就挑出其中通用定时器里的参数来进行配置 ——
- TIM_OCMode,输出比较模式。
TIM_OCMode_Timing 定时模式
TIM_OCMode_Active 激活模式
TIM_OCMode_Inactive 非激活模式
TIM_OCMode_Toggle 翻转模式
TIM_OCMode_PWM1 PWM模式1
TIM_OCMode_PWM2 PWM模式2
- TIM_OCPolarity,输出极性。
TIM_OCPolarity_High 高极性
TIM_OCPolarity_Low 低极性
- TIM_OutputState,输出使能。
TIM_OutputState_Disable 失能
TIM_OutputState_Enable 使能
- TIM_Pulse,初始CCR值。
下面看看代码吧 ——
void PWM_Init(void)
{
Timer_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
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);
}
第四步、PWM配置CCR的值
需要注意的是 ——
CCR和ARR共同决定占空比,此函数仅设置CCR的值,并不直接是占空比。
占空比 ( D u t y ) = C C R / ( A R R + 1 ) 占空比(Duty) = CCR / (ARR + 1) 占空比(Duty)=CCR/(ARR+1)
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR2的值
}
OK,到这里我们PWM的配置就完成了。
使用PWM实现呼吸灯
下面我们来看看怎么用刚才实现的PWM来做呼吸灯。
TIM的代码可以看上一期的内容 ——
stm32教程:TIM定时器详解 & TIM时钟计时代码模板
里面的Timer.c就按照上面说的改,这里就不放了。其它代码的完整版在下面。
PWM.c
#include "stm32f10x.h" // Device header
#include "Timer.h"
#include "PWM.h"
void PWM_Init(void)
{
Timer_Init();
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOB的时钟
GPIO_InitTypeDef GPIO_InitStructure;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为复用推挽输出
//受外设控制的引脚,均需要配置为复用模式
TIM_OCInitTypeDef TIM_OCInitStructure; //定义结构体变量
TIM_OCStructInit(&TIM_OCInitStructure); //结构体初始化,若结构体没有完整赋值
//则最好执行此函数,给结构体所有成员都赋一个默认值
//避免结构体初值不确定的问题
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_PWM1; //输出比较模式,选择PWM模式1
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);
}
void PWM_SetCompare1(uint16_t Compare)
{
TIM_SetCompare1(TIM2, Compare); //设置CCR2的值
}
PWM.h
#ifndef __PWM_H
#define __PWM_H
void PWM_Init(void);
void PWM_SetCompare1(uint16_t Compare);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "PWM.h"
uint16_t i = 0;
int main()
{
OLED_Init();//函数定义初始化
PWM_Init();
while(1)
{
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(i); //依次将定时器的CCR寄存器设置为0~100,PWM占空比逐渐增大,LED逐渐变亮
Delay_ms(10); //延时10ms
}
for (i = 0; i <= 100; i++)
{
PWM_SetCompare1(100 - i); //依次将定时器的CCR寄存器设置为100~0,PWM占空比逐渐减小,LED逐渐变暗
Delay_ms(10); //延时10ms
}
}
}
总结
OKK,这一期的内容就这么多,其实还是容易的对吧。
如果需要工程的文件的可以私信我,或者在评论区里留言,我都会提供。