[TOC]
PWM脉冲宽调点灯
前言
对于灯等来说有很多种方法,前面介绍了一些基础的点灯方法,比如直接点灯,按键控制点灯,按键中断点灯,但都是比较简单的一些方法也很基础,要问我有没有什么高级点的点灯方法,答案是有的,在这我要介绍一种高级点灯的方法就是使用PWM进行点灯。
1.什么是PWM
PWM是脉冲宽度调制,简称脉冲宽调。它利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。
PWM一般用在测量、通讯、功率控制雨转换,电动机控制、调光、开关电源,但是在这我们只研究点灯,点灯才是重中之重。
2.PWM的实现
PWM是基于定时器来进行实现的,但并不是所有的定时器都能实现PWM的,比如说基本定时器就没有办法实现PWM,所以下面的PWM是用通用定时器和高级定时器来进行实现的。
其实PWM的实现是非常的简单,其实和定时器一样,定时器到0或者是到设置的最大值就会触发中断,PWM也是,只不过它并不是到达最大值,也不触发中断,它是到达你设置的那个比较值后就进行翻转电平,例如我的PWM的自动重载值为2000,比较值为1000
当计数到1000的时候PWM就会进行一次电平的翻转,比如你一开始设置的电平是高电平,那等到到比较值的时候就会变成低电平。
3.PWM实现步骤(通用定时器)
前面说了,PWM和定时器一样,所以开启PWM的方式前面和打开定时器的方式一样,但是有一点不同的是PWM要使用到所对应的端口上,所以需要加上一般就是设置端口:
打开定时器时钟
配置端口
- 配置TIM定时器
- 使能TIM外设
按照上面的步骤完成后就得开始配置PWM了,配置的方法如下:
- 配置PWM
- 使能预装寄存器
这样就可以让PWM进行使用了,现在来一个一个的介绍打开的步骤。
3.1 打开定时器的时钟
打开时钟这个是必须要的内容,而在打开时钟的时候需要注意你使用的定时器,如果你使用的是通用定时器,一般都是打开APB1的时钟,而高级定时器一般是APB2,这个需要考虑一下。
如果我这是使用的TIM3的定时器,那么打开的时钟代码如下:
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
如果这里使用TIM1,代码如下:
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
3.2 配置端口
配置端口的步骤和之前配置端口的步骤一致,只不过需要注意一下这个端口的模式需要使用GPIO_Mode_AF_PP
复用推挽输出的方式,因为有些端口是使用重映射到这个端口的,此时这个端口是属于复用功能输出,所以这里需要使用到复用的方式。
那什么时候需要使用到重映射呢?这里需要知道一下,我们现在使用的PWM是PWM的输出模式,所以需要知道开启这个PWM定时器的输出端口。
下面的表说明了通用定时器和高级定时器通道输出所对应的端口:
TIM2定时器 | 未重映射 | 部分重映射 |
---|---|---|
TIM2_CH1 | PA0 | PA15 |
TIM2_CH2 | PA1 | PB3 |
TIM2_CH3 | PA2 | PB10 |
TIM2_CH4 | PA3 | PB11 |
TIM3定时器 | 未重映射 | 部分重映射 | 重映射 |
---|---|---|---|
TIM3_CH1 | PA6 | PB4 | PC6 |
TIM3_CH2 | PA7 | PB5 | PC7 |
TIM3_CH3 | PB0 | PB0 | PC8 |
TIM3_CH4 | PB1 | PB1 | PC9 |
TIM4定时器 | 未重映射 | 重映射 |
---|---|---|
TIM4_CH1 | PB6 | PD12 |
TIM4_CH2 | PB7 | PD13 |
TIM4_CH3 | PB8 | PD14 |
TIM4_CH4 | PB9 | PD15 |
TIM5定时器 | 未重映射 | 重映射 |
---|---|---|
TIM5_CH4 | PA3 | LSI内部时钟连至TIM5_CH4的输入作为校准使用 |
TIM1定时器 | 未重映射 | 部分重映射 | 重映射 |
---|---|---|---|
TIM1_ETR | PA12 | PA12 | PE7 |
TIM1_CH1 | PA8 | PA8 | PE9 |
TIM1_CH2 | PA9 | PA9 | PE11 |
TIM1_CH3 | PA10 | PA10 | PE13 |
TIM1_CH4 | PA11 | PA11 | PE14 |
上面只介绍了部分定时器输出的重映射,更多的大家可以翻阅一下手册,上面的只是一个简单的参考。
知道了定时器输出引脚后我们需要选择定时器然后配置定时器所对应的输出引脚。
这里我使用的是PA6这个引脚,所以使用的是TIM3
这个定时器,配置的代码如下:
GPIO_InitTypeDef GPIO_InitStruct = {
0};
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); // 打开GPIOA的时钟
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP; // 设置复用推挽输出
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
3.3 设置定时器
设置完端口后就可以开始设置定时器了,设置定时器的方法和之前打开基本定时器的方法一样。
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {
0};
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CountterMode_Up; // 设置定时器计数模式为上拉
TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1; // 自动重装值为20000
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1; // 预分系数,72MHz/7200
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0; // 时钟分割
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
3.4 设置PWM
前面设置完TIM3定时器了,现在就是想要配置一下PWM了。
TIM_OCInitTypeDef TIM_OCInitStruct = {
0};
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 打开PWM通道1 因为这里使用的是TIM3的通道一作为PWM
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_ENABLE; // PWM输出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCNPolarity_Low; // 一开始输出的电平,如果为低,那到达比较值后就为高。
TIM_OC1Init(TIM3, &TIM_OCInitStruct); // 让TIM3的通道1和设置的PWM进行绑定
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_ENABLE); //使能TIM3在CCR1上的预重装寄存器
TIM_SetCompare1(TIM3, 10000 - 1); // 设置比较值
TIM_Cmd(TIM3, ENABLE); // 使能TIM3外设
以上就是配置好PWM了,有几个注意点,就是选择的PWM的通道数,比如说我这要让它打开通道二,那在设置的时候需要加上:
TIM_OC2Init(TIM3, &TIM_OCInitStruct); // 让TIM3的通道1和设置的PWM进行绑定
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_ENABLE); //使能TIM3在CCR1上的预重装寄存器
TIM_SetCompare2(TIM3, 10000 - 1); // 设置比较值
这样也设置了通道2的,当然也可以让PWM通道2的重装值为其它的值,那就当设置完通道一后再设置一下PWM,再把设置好的PWM设置通道二。
3.5 完整代码
下面展示一下完整的代码,大家可以拿下来直接使用,但是要注意这个是用TIM3,并且没有重定向的。
void MX_PWMInit(void){
GPIO_InitTypeDef GPIO_InitStruct = {
0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {
0};
TIM_OCInitTypeDef TIM_OCInitStruct = {
0};
// 开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置端口
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_6;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// 配置通用定时器
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 20000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStruct.TIM_ClockDivision = 0;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseInitStruct);
// 配置PWM
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 打开通道1
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_Low;
TIM_OC1Init(TIM3, &TIM_OCInitStruct);
TIM_OC1PreloadConfig(TIM3, TIM_OCPreload_Enable);
TIM_SetCompare1(TIM3, 10000 - 1);
TIM_Cmd(TIM3, ENABLE);
}
4.PWM实现步骤(高级定时器)
其实高级定时器的实现步骤和上面一样,只不过就是想要添加一条语句,为什么不直接在通用定时器上写呢?我也不知道,写这个我都是随性的。
当你设置完PWM后,想要添加下面的语句:
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
因为这里使用的是高级定时器TIM1,包括TIM8都需要添加这一句话,因为这一句话是让MOE主输出使能的,如果没有这一句话,就算你设置好PWM,这个定时器也没有办法进行输出,也就是让TIM1或者TIM8为关闭状态。
完整代码如下,你可以直接拿去使用,但是要注意我这个打开的是TIM1定时器的PWM,而且是通道一,输出的端口是PA8:
void MX_PWMInit(void){
GPIO_InitTypeDef GPIO_InitStruct = {
0};
TIM_TimeBaseInitTypeDef TIM_TimeBaseStruct = {
0};
TIM_OCInitTypeDef TIM_OCInitStruct = {
0};
// 开启时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
// 配置GPIO模式和IO口
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA, &GPIO_InitStruct);
// TIM1通用定时器初始化
// 因为要操作的PA8这个引脚的LED灯,但是只有高级定时器TIM1才可以使用PWM进行控制这个端口
// 所以需要使用打开高级定时器的方法来进行打开
TIM_TimeBaseStruct.TIM_ClockDivision = 0;
TIM_TimeBaseStruct.TIM_CounterMode = TIM_CounterMode_Up; // 向上计数
TIM_TimeBaseStruct.TIM_Period = 20000 - 1;
TIM_TimeBaseStruct.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInit(TIM1, &TIM_TimeBaseStruct);
// PWM初始化
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1; // 设置模式为PWM模式
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable; // 设置比较输出使能
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High; // 设置极性为高
TIM_OC1Init(TIM1, &TIM_OCInitStruct);
TIM_CtrlPWMOutputs(TIM1,ENABLE); //MOE 主输出使能
TIM_OC1PreloadConfig(TIM1, TIM_OCPreload_Enable); // 使能TIM1在CCR1上的预装载寄存器
TIM_ARRPreloadConfig(TIM1, ENABLE); //使能TIMx在ARR上的预装载寄存器
TIM_SetCompare1(TIM1, 10000 - 1);
TIM_Cmd(TIM1, ENABLE);
}
5.开始点灯
如果你上面的步骤都跟上了,那么现在恭喜你你可以点灯了,直接调用上面的初始化函数就可以直接开启点灯了,效果就是亮灭亮灭的。
主函数:
#include "stm32f10x.h"
#include "pwm.h"
int main(void){
MX_PWMInit();
while(1)
{
}
}
这样就可以了,你可以用TIM_SetCompare1(TIMx, value;
函数去修改一下每次的比较值,可以让它亮得久或者亮得短。
你也可以通过修改TIM_OCInitStruct.TIM_OCPolarity
的值来改变一开始的电平。
6.PWM呼吸灯
点灯很容易,但是还是感觉比较低级,那么在这我给大家介绍一下用PWM来点灯的高级点的内容----“PWM呼吸灯”,不知道的可以看完这个教程拿去跑一下就知道了。
呼吸灯其实就是改变每一次PWM的比较值,在感官上感觉就是LED像呼吸一样。
这里需要添加一下滴答定时器来延迟,所以需要添加一下下面的代码:
void delay_us(unsigned int time){
unsigned int temp;
SysTick->LOAD = 9 * time;
SysTick->CTRL = 0x01;
SysTick->VAL = 0;
do{
temp = SysTick->CTRL;
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->VAL = 0;
SysTick->CTRL = 0x00;
}
void delay_ms(unsigned int time){
unsigned int temp;
SysTick->LOAD = 9000 * time;
SysTick->CTRL = 0x01;
SysTick->VAL = 0;
do{
temp = SysTick->CTRL;
}while((temp&0x01)&&(!(temp&(1<<16))));
SysTick->VAL = 0;
SysTick->CTRL = 0x00;
}
这个是之前说过的滴答定时器配置,我就懒得写注释了。
然后PWM需要将分频变成0,因为使用滴答定时器来延迟,所以不需要分频了(你分频会出问题,不要分),然后预装值改为900,其它都不变。
在主函数中的代码就如下:
int main(void){
unsigned char dir = 1; // 方向
unsigned int Duty = 0; // 占空比
MX_PWMInit();
while(1)
{
delay_ms(10);
if (dir == 1){
Duty++;
if (Duty > 300){
dir = 0;
}
}
else{
Duty--;
if (Duty == 0){
dir = 1;
}
}
TIM_SetCompare1(TIM1, Duty);
}
}
上面的是你一开始的电平为:TIM_OCPolarity_Low
。
如果你一开始的电平为:TIM_OCPolarity_Hight
,那么需要使用下面的代码:
int main(void){
unsigned char dir = 0; // 方向
unsigned int Duty = 0; // 占空比
MX_PWMInit();
while(1)
{
delay_ms(10);
if (dir == 0){
Duty++;
if (Duty > 300){
dir = 1;
}
}
else{
Duty--;
if (Duty == 0){
dir = 0;
}
}
TIM_SetCompare1(TIM1, Duty);
}
}