stm32捕获占空比_嵌协|STM32实现呼吸灯库函数版

  创新放飞梦想,实践铸就未来

fb40f9c1d201a33e8ce34e0c785cb3c6.gif

    小黑上回说到通过STM32 GPIO口加上延时函数实现跑马灯的教程(【跑马灯教程 】),一开始文章的题目是呼吸灯,后来才纠正过来(被锤警告),这回才是真正的呼吸灯!

    由于小黑个人技术知识水平有限文章讲解及表述会存在错误,对大家的学习造成不便还请大家留言指正,公众号管理员会第一时间将留言置顶,避免同学学习不便。

让我们先上结果

4f11fcd8d701de52cb8f54f83a423c40.gif

本文目录 efa81af9b0ce18c4531b28df0aa72f14.png

1.知识理论基础

efa81af9b0ce18c4531b28df0aa72f14.png

2.硬件连接

efa81af9b0ce18c4531b28df0aa72f14.png

3.软件编程

efa81af9b0ce18c4531b28df0aa72f14.png

4.烧写验证

d7eb160726e7ddbe42e5e48795c4ec64.png

一,知识理论基础

什么是呼吸灯:

    顾名思义,就是一个灯。灯的亮度的变化,由亮变暗,从暗变亮,有一个渐变,规律的变化,像是人的呼吸,是灯的呼吸,所以叫呼吸灯。而要让灯可以达到这样的变化,我们要让stm32的IO口上输出一个可调的电平,这时我们就要用到PWM,那什么是PWM呢,我们继续往下看。

什么是定时器:

    讲PWM我们要先认识stm32的定时器,PWM是定时器的功能之一。STM32F103有TIME1和TIME8高级定时器,TIME2~TIME5通用定时器,还有TIME6和TIME7基本定时器。我们要使用的STM32F103C8T6只具有4个定时器,TIME1~TIME4.

e2c77af4ff2f518b0f67ca679c0dfdc4.png

    那么定时器有什么功能呢?定时、输出比较、输入捕获、互补输出,其中,基本定时器就只有定时功能,通用定时器便除了互补输出没有其他都有,而高级定时器便是全都有啦,我们这里用到通用定时器TIM2。

通用定时器具体的功能有:

c602374ed62d096b6169cd9113860483.png

    在这里我们要用到TIM2_CH2的PWM输出功能。

那么什么是PWM呢?

      “脉冲宽度调制(PWM),是英文“Pulse Width Modulation”的缩写,简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术。简单一点,就是对脉冲宽度的控制。”

简单点说就是一个可调的脉冲,控制在一个周期内,控制高电平多长时间,低电平多长时间(占空比),从而实现电平的输出。经常用于舵机、电机控制等。。。

两个重要的概念,频率、占空比:

    频率是指每秒钟信号从高电平到低电平再回到高电平的次数,为一个PWM波周期的倒数。
    占空比是指高电平持续时间比一个周期持续的时间。所以可以通过控制占空比(我们要编程的“数”),来控制输出的等效电压。
   对于方波(pwm输出的就是方形波)的话,频率和占空比就确定了一个波。

91ae1ff2e3a4a3c8244e3be181352b77.png

    为了不至于太难理解,我们不进行深讲,但是建议大家可以去CSDN,百度等等平台进行全面一点的认知,对我们下学期的智能车比赛做基础知识储备。

二,硬件连接

具有定时器功能的引脚:

59e7a01d0304f5446ddf1af3c7f64132.png

934547d32cb37781ee7030cb1379e577.png

1fbe74db2998a1054a139f2c46f37b53.png

db100deaa50a7b0d11c7af7be259361b.png

LED连接:

    我们用到TIM2_CH2,自己实操时可以换一个以达到更好的学习效果。通过图二,我们在默认情况下(即不使用端口映射)TIM2_CH2对应的IO口是PA1,我们将PWM输出极性设置为高,便将LED的正极接到PA1上,负极接GND,(若将输出极性设置成低那就反过来接,将负极接到IO口,,正极接5V)

三,软件编程

    首先我们在工程中HARDWARE文件夹下新建PWM文件夹并新建PWM.c  PWM.h两个文件,导入mdk5,具体操作省略,可以看前边推文。我们将PWM的初始化函数写到PWM.c的文件中函数命名为“TIM2_PWM_Init”(可以随意命名)。

我们先从简单的讲起,PWM.h头文件没什么重点,如下:

#ifndef __PWM_H    #define __PWM_H    #include "sys.h"  //导入头文件   void TIM2_PWM_Init(u16 arr,u16 psc);  //函数声明       #endif

    这里要说的是因为用到了u16 的数据类型定义我们要导入一个头文件“sys.h”(u8,u16,u32都是C语言数据类型,分别代表8位,16位,32位长度的数据类型,这里也可以直接调用"stm32f10x.h")

    接下来是编写PWM.c文件,编写初始化 “void TIM2_PWM_Init(u16 arr,u16 psc);”函数,函数参数为arr重装载值决定pwm的频率周期,psc是时钟预分频数(主要用于计算时间范围为0-65534),这里有一条公式可以计算周期时间Tout= (arr+1)*(psc+1) /Tclk,其中Tclk我们用的TIM2是系统内部APB1时钟倍频来的,(固件库的SystemInit函数里面已经初始化APB1的时钟为2分频,所以APB1的时钟为36M,而从STM32的内部时钟树图得知:当APB1的时钟分频数为1的时候,TIM2~7的时钟为APB1的时钟,而如果APB1的时钟分频数不为1,那么TIM2~7的时钟频率将为APB1时钟的两倍。因此,TIM2的时钟为72M,即 Tclk=72M)

    接下来我们先说说PWM的模式,PWM有两个模式,PWM1和PWM2,PWM1是当我们设定的值比arr值小时输出高电平,PWM2是当我们设定的值比arr值大时输出高电平。如下图就是PWM2模式。

07666db757021deb17f3b63a861f59e0.png

    我们从图出发,可以看到为什么说ARR值决定周期,定时器从0开始计数(这里是向上计数模式,向下计数则相反,也是上边公式为什么要+1),数到ARR时产生溢出(更新)事件(可以从这个地方设置中断,本次用不到中断,不进行讲解),重新回到0 ,这便是一个周期,我们要设置的值便是图中CCRx,这个值会跟ARR进行比较(所以叫输出比较),通过模式设定决定输出高低电平。(为了不至于太难理解请一定结合上图一起看)。我们先看看完整的代码,然后一个一个函数讲PWM.c

#include "PWM.h"void TIM2_PWM_Init(u16 arr,u16 psc){ //结构体变量定义TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;GPIO_InitTypeDef GPIO_InitStruct;TIM_OCInitTypeDef TIM_OCInitStruct;//时钟使能 TIM2 、GPIOA、 AFIO   ①RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2挂载在APB1上的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);  //使能GPIOA、复用功能时钟AFIO//TIM2定时器初始化   ②TIM_TimeBaseInitStruct.TIM_Period=arr;  //重装载值arrTIM_TimeBaseInitStruct.TIM_Prescaler=psc;  //预分频值pscTIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;  //向上计数模式TIM_TimeBaseInitStruct.TIM_ClockDivision=0;  //时钟分割为0 ,TDTS = Tck_timTIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);//TIM2定时器使能TIM_Cmd(TIM2,ENABLE);//TIM2通道2初始化  ③TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;  //PWM模式1TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;  //高电平有效TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable; //输出比较使能TIM_OC2Init(TIM2,&TIM_OCInitStruct);//TIM2通道2预装载寄存器使能TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);//GPIO  PA1初始化   ④GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;    //PA.1GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //50MHz速度GPIO_Init(GPIOA,&GPIO_InitStruct);}

首先我们先总结一下初始化pwm输出的编程步骤:

步骤介绍使能时钟初始化定时器初始化定时器通道初始化GPIO

现在我们一个点一个点的讲解:

    使能时钟,这里 GPIO挂载在APB2总线上,之前文章说过,而我们要用到的TIM2是挂载在APB1上的,所以我们要使能的时钟是RCC_APB1,这里要注意的是通用定时器是挂载在APB1上,高级定时器则是在APB2上。【补充:时钟函数的申明在stm32f10x_rcc.h,这里是上一讲写少了的】

这里我们要写的代码是:

//时钟使能 TIM2 、GPIOA、 AFIO   ①RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //使能TIM2挂载在APB1上的时钟RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);  //使能GPIOA

    初始化定时器,初始化定时器跟初始化GPIO的操作类似,我们先看看要用到的函数【定时器相关函数申明在文件stm32f10x_time.h中】:

a1816f8b1f0c30bd1d752bfaed9050f3.png

    这里函数的两个参数一个是TIMx ,x可以是2,3,4,说明这个初始化函数只适用在通用定时器初始化上,第二个参数是一个结构体变量,里边的成员有:

typedef struct{uint16_t TIM_Prescaler;        //预分频值   uint16_t TIM_CounterMode;    //计数模式    uint16_t TIM_Period;          // 重装载值  uint16_t TIM_ClockDivision;     //时间分割  uint8_t TIM_RepetitionCounter;  //重复计数,就是重复溢出多少次才给你来一个溢出中断,如果初始化为0的话,计数器溢出一次,中断一次!} TIM_TimeBaseInitTypeDef;

    其中预分频值跟重装载值上边讲过了,计数模式有向上计数、向下计数、中央对齐模式(中央对齐模式有模式1、2、3),这里我们用到向上计数模式,对于向上计数模式在上边有讲过了,便是从0计数到ARR重装载值,而向下计数的话便是从ARR计数到0。时间分割主要是用于数字滤波器相关,我们在此用不到只要设置为0就好了,重复计数模式,在这里我们用不到,上边注释有稍微讲了一下,可以自行再了解一下。所以这里我们要设置参数如下:

TIM_TimeBaseInitStruct.TIM_Period=arr;  //重装载值arrTIM_TimeBaseInitStruct.TIM_Prescaler=psc;  //预分频值pscTIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;  //向上计数模式TIM_TimeBaseInitStruct.TIM_ClockDivision=0;  //时钟分割为0 ,TDTS = Tck_tim

    ARR值与psc值我们作为参数,在调用时再进行设置。所以完整的初始化函数便是

TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;   //定义结构体变量TIM_TimeBaseInitStruct.TIM_Period=arr;  //重装载值arrTIM_TimeBaseInitStruct.TIM_Prescaler=psc;  //预分频值pscTIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;  //向上计数模式TIM_TimeBaseInitStruct.TIM_ClockDivision=0;  //时钟分割为0 ,TDTS = Tck_tim    TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);

    之后我们要对定时器使能,使用void TIM_Cmd();(省略参数)

7431179cf0419af98542ea06a2500a38.png

同样,这个函数适用于通用定时器,使用比较简单,如下:

TIM_Cmd(TIM2,ENABLE);

    这里第2步就写完了。

    初始化定时器通道,通用定时器有4个通道上边图3有进行讲解,这里我们要用到是通道2即TIM2_CH2;每一个定时器通道都有单独的初始化函数。

c90d778ac31845e05fc71180fc3f669f.png

f87007221dece79d1467157741a1f974.png

  一样是有两个参数,一个是定时器TIMx(同样是只适用通用定时器2 3 4),一个是结构体变量,我们看看结构体变量里的成员。

typedef struct{  uint16_t TIM_OCMode;       //输出模式  uint16_t TIM_OutputState;   //输出比较使能位  uint16_t TIM_OutputNState;  //高级定时器输出比较N状态  uint16_t TIM_Pulse;         //比较值(图9 CCRx)  uint16_t TIM_OCPolarity;      //输出比较极性  uint16_t TIM_OCNPolarity;  //高级定时器输出比较N极性   uint16_t TIM_OCIdleState;   //设置高级定时器空闲状态   uint16_t TIM_OCNIdleState;  //设置高级定时器N空闲状态} TIM_OCInitTypeDef;

    我们用到的是通用定时器所以不用看那些高级定时器才能用的参数,所以这里我们只要设置4个参数就可以了。首先第一个输出模式。

cc6996f8ee00ae791464bb0acf73776a.png

    这里我们用到PWM模式1,PWM模式2上边有讲到,至于其他的模式在此不叫讲述,可以自行百度。

TIM_OCMode= TIM_OCMode_PWM1;

    第二个TIM_OutputState这个是使能位,我们选择使能就好了。

TIM_OutputState=TIM_OutputState_Enable;

    第三个是输出极性,也就是我们要的是高电平有效还是低电平有效,这个跟我们LED引脚连接相关,这里我们选择高电平有效,LED的连接上我们将正极接到GPIO口上;

TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;

    第四个是比较值,我们在后边主函数会用另一个函数直接设置,这个数也就是我们图9CCRx对应的那个值,也可以称之为占空比,这里我们不用设置;

所以我们通道2初始化结构体的参数设置是:

 TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable;

    这里我们还需要通过void TIM_OC2PreloadConfig();(省略参数)这个函数来使能通道2上的预装载寄存器

337eaf4e7d253e6ffffac015e3449c33.png

他有两个参数,一个设置是哪个通用定时器,一个是使能,比较简单,这里直接设置:

TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);

那么我们通道2初始化步骤完整的代码如下:

TIM_OCInitTypeDef TIM_OCInitStruct;  //定义结构体变量//TIM2通道2初始化  ③TIM_OCInitStruct.TIM_OCMode=TIM_OCMode_PWM1;  //PWM模式1TIM_OCInitStruct.TIM_OCPolarity=TIM_OCPolarity_High;  //高电平有效TIM_OCInitStruct.TIM_OutputState=TIM_OutputState_Enable; //输出比较使能TIM_OC2Init(TIM2,&TIM_OCInitStruct);//TIM2通道2预装载寄存器使能TIM_OC2PreloadConfig(TIM2,TIM_OCPreload_Enable);

    GPIO初始化,这里上一篇已经讲过了,不过这里要注意的是我们使用的是复用推挽输出模式,这个是有固定要求的,可以查阅《stm32中文参考手册》

a5b235c78835c8b243254d53d5f5fab7.png

那么GPIO初始化代码如下【补充:GPIO系列函数申明在文件stm32f10x_gpio.h中】:

GPIO_InitTypeDef GPIO_InitStruct;//GPIO  PA1初始化   ④GPIO_InitStruct.GPIO_Mode=GPIO_Mode_AF_PP; //复用推挽输出GPIO_InitStruct.GPIO_Pin=GPIO_Pin_1;    //PA.1GPIO_InitStruct.GPIO_Speed=GPIO_Speed_50MHz; //50MHz速度GPIO_Init(GPIOA,&GPIO_InitStruct);

综上,PWM.c文件中的PWM初始化函数就写好了,我们接着写主函数main.c,先看完整代码:

#include "stm32f10x.h"#include "delay.h"#include "PWM.h" int main(void){  int ledpwm=0;    //定义占空比变量 TIM2_PWM_Init(899,0); //初始化PWM  ARR=899;PSC=0delay_init();     //初始化延时函数 while(1){delay_ms(5);  //稳定pwm波for(ledpwm =0; ledpwm <=255; ledpwm ++)  //从0到255一个个加{TIM_SetCompare2(TIM2, ledpwm);  //设置TIM2_CH2占空比delay_ms(10);       //延时10ms}for(ledpwm =255; ledpwm >=0; ledpwm --)  //从255到0,一个个减{TIM_SetCompare2(TIM2, ledpwm);   //设置TIM2_CH2占空比delay_ms(10);       //延时10ms}}  }

    导入PWM.h头文件,然后初始化pwm,arr=899,psc=0;初始化延时函数,然后通过for循环从0到255计数,这个相信有点C语言基础的都没问题,然后是一个新函数,void TIM_SetCompare2();设置通道2捕获比较寄存器的值。

415e039db5d744347efef3201af1a739.png

    两个参数,一个是哪个通用定时器,一个是比较寄存器的值,比较简单,如下

TIM_SetCompare2(TIM2, ledpwm);

    然后这里为什么是255呢,这个值是可以计算的,LED的最大亮度对应的电压通过占空比计算出对应数值就好了,再大的数值对LED的亮度也就没用了,亮度最大了,还可能烧坏LED。

     Stm32的高电平 是5v 我们设置的ARR值是899,那么最大就是899,假设我们设置的比较值是450,那没就是50%的输出电平也就是2.5v,以此计算。

   完整文件PWM.h  PWM.c  main.c就这三个文件要写,写好了编译烧写就可以了,在自己动手实操一遍后建议换一个定时器和通道再操作一遍,会更加熟练。

四,烧写验证

    话不多说,上图(家里没示波器,我用软件调试来查看输出的波形)

061d3025de08888f0e2a90c5f68c1a9e.png

c426232851db4105927b49d4da30744f.png

614fd975aecadb40086a824889fd83a0.png

可以看到波形从小到大再到小(可以通过图片下边的时间结合波形宽度看出),再看看LED的变化:

4f11fcd8d701de52cb8f54f83a423c40.gif

可以看到LED从亮到暗再到亮,说明我们实验结果完美达标。

 fb107bb2171e9036bc84de77e12645eb.png

写在最后

定时器是单片机中很重要的一项,靠小黑我不全面的知识是完全不能讲完的,而PWM输出是嵌入式协会在第一学期举办的智能车比赛上重要的知识之一,这个实验只是认识一下PWM,这个初始化代码也适用于舵机,电机的pwm输出初始化。

这个方面的知识有很多,为了文章比较易懂,小黑没有全部写出来(掩盖我不懂的事实),想要全面基础的了解一下这个知识还是要到百度,CSDN,博客等平台再全面学习一下才能很好掌握。

小黑的知识还是很不充足的,会有讲解错误不全面的地方,你们可以直接推文下边留言,管理员一看到,如果属实我们会第一时间进行更正。

编写制作不易,点赞,再看,转发给需要的人,三连支持一下(哈哈哈~)

 fb107bb2171e9036bc84de77e12645eb.png

参考资料:《STM32中文参考手册》

《STM32固件库使用手册》

《STM32F1开发指南-库函数版本》

百度、CSDN

f9c5e2cebe0f11d7d73398add2af98dc.png d9aa29a9ede7a4415c981a294dc51c6f.png f9c5e2cebe0f11d7d73398add2af98dc.png

教程结束!

好好学习,天天向上!

——小黑

▼ 更多精彩推荐,请关注我们 ▼ facd125017045a569efd9d0bce6042ae.png扫码关注我们编辑:青蛙  供稿:小黑
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值