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);
}
}

PWM点灯的更多相关文章

  1. Microsemi Libero系列教程(二)——新建点灯工程

    前言 上一篇文章,介绍了Microsemi Libero系列教程(一)-Libero开发环境介绍,下载,安装与注册,作为嵌入式开发中的Hello World,点灯是再也基础不过的实验了,通过点灯实验, ...

  2. STM32之PWM君

    PWM..英语好的人估计又知道这三个大写字母代表哪三个英语单词了.小弟不才,就说中文意思好了:脉冲宽度调制,玩过飞思卡尔的人估计对PWM非常的不陌生吧.电机驱动需要PWM,控制舵机的转向需要PWM,总 ...

  3. [nRF51822] 12、基础实验代码解析大全 · 实验19 - PWM

    一.PWM概述: PWM(Pulse Width Modulation):脉冲宽度调制技术,通过对一系列脉冲的宽度进行调制,来等效地获得所需要波形. PWM 的几个基本概念: 1) 占空比:占空比是指 ...

  4. Windows on Device 项目实践 1 - PWM调光灯制作

    在前一篇文章<Wintel物联网平台-Windows IoT新手入门指南>中,我们讲解了Windows on Device硬件准备和软件开发环境的搭建,以及Hello Blinky项目的演 ...

  5. 用Arduino剖析PWM脉宽调制

    PWM(Pulse Width Modulation)简介 PWM,也就是脉冲宽度调制,用于将一段信号编码为脉冲信号,也就是方波信号.多用于在数字电路中驱动负载随时间变化的电子元件,如LED,电机等. ...

  6. Arduino学习经验(一)之解决舵机库和pwm输出冲突

    一.前言 最近在公司学习Arduino uno ,用它实现小车超声波避障功能.实现的功能很简单,就是在小车前方挂一个超声波模块,当碰到障碍物时,会通过舵机进行摆头,判断两边的距离,进行左右转弯.但是碰 ...

  7. stm8s103 PWM

    stm8s103 PWM的设置不难,但是很多人不注意选项字节这个问题,PWM是IO口的第二功能,要用ST Visual Programmer 修改选项字节. 只需要修改AFR0的功能就可以了

  8. 驱动实现led,pwm和中断基础知识

    2015.4.8星期三 晴天 今天老师讲的内容是内核编写led和pwm驱动,实现花样灯和放歌的功能.理解应用和驱动的对接,最后自己实现了在放歌的时候根据歌曲的节奏亮灭一个小灯,应为两个独立的驱动都已经 ...

  9. STM32中的PWM的频率和占空比的设置

    转于http://blog.csdn.net/liming0931/article/details/8491468 下面的这个是stm32的定时器逻辑图,上来有助于理解:   TIM3的ARR寄存器和 ...

  10. PWM波控制舵机总结

    文章转自:http://www.geek-workshop.com/thread-70-1-1.html 一.关于舵机: 舵机(英文叫Servo):它由直流电机.减速齿轮组.传感器和控制电路组成的一套 ...

随机推荐

  1. VS 输入快捷键propfull

    大家都知道prop.propg输入快捷键. 当前我们需要生成一段包含属性和字段的代码时,可以propfull. propfull在安装了reshaper后,会被隐藏掉.没关系,直接敲propfull, ...

  2. ABC267G Increasing K Times 题解

    做这道题,很有感悟,发篇文. 先给数列从小到大排个序. 接下来设 \(f_{i,j}\) 表示前 \(i\) 个数的排列形成 \(j\) 个上坡的方案数. 接下来考虑转移,分为插入第 \(i\) 个数 ...

  3. python自产调试工具pdb的使用

    python自产调试工具pdb的使用 介绍 调试打印在写代码的时候不可避免 项目越大,调试可能花的时间会越多 print调试可能是最早用的,一段时间内你都会习惯这种方式 一旦成了老鸟,你应该会去用ID ...

  4. 2023-01-07:hyper/docker-registry-web是registry的web界面工具之一。请问部署在k3s中,yaml如何写?

    2023-01-07:hyper/docker-registry-web是registry的web界面工具之一.请问部署在k3s中,yaml如何写? 答案2023-01-07: yaml如下: api ...

  5. 2022-08-27:以下go语言代码输出什么?A:[0];B:panic;C:7;D:不清楚。 package main import ( “fmt“ ) func main() { a

    2022-08-27:以下go语言代码输出什么?A:[0]:B:panic:C:7:D:不清楚. package main import ( "fmt" ) func main() ...

  6. golang调用sdl2,播放yuv视频

    golang调用sdl2,播放yuv视频 win10 x64下测试成功,其他操作系统下不保证成功. 采用的是syscall方式,不是cgo方式. 见地址 代码如下: package main impo ...

  7. 2021-12-18:找到字符串中所有字母异位词。 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引。不考虑答案输出的顺序。 异位词 指由相同字母重排列形成

    2021-12-18:找到字符串中所有字母异位词. 给定两个字符串 s 和 p,找到 s 中所有 p 的 异位词 的子串,返回这些子串的起始索引.不考虑答案输出的顺序. 异位词 指由相同字母重排列形成 ...

  8. Cannot apply DjangoModelPermissionsOrAnonReadOnly on a view that does not set `.queryset` or have a `.get_queryset()` method.

    这个问题是在使用 django REST 做自定义认证的时候出现的 解决方法是 在settings.py 中注释掉这个 REST_FRAMEWORK={ 'DEFAULT_PERMISSION_CLA ...

  9. Error in nextTick: "TypeError: Right-hand side of 'instanceof' is not an object"

    发生这种情况,直接去查看 props 对象是否  类型正确 props 有 大概两种 写法吧, 一种就是对象形 ,一种是数组形 // 对象形props: { show: { type: Boolean ...

  10. UCOS II 源码分析一

    再进行ucos操作系统源码分析前,先对ucos源码文件结构说个简单说明,只有掌握了源码文件结构才能在接下来的源码分析中逐渐感受到会当凌绝顶, 一览众山小,最后的感受就是RTOS也不是很神秘!下面以正点 ...