前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >定时器的PWM输出与输入捕获

定时器的PWM输出与输入捕获

作者头像
飞哥
发布2020-07-10 10:22:44
2.9K0
发布2020-07-10 10:22:44
举报

今天主要讲解的是定时器有关功能——PWM输出和输入捕获实验。

一、定时器简介

STM32F1的定时器非常多,由2个基本定时器(TIM6、TIM7)、4个通用定时器(TIM2-TIM5)和2个高级定时器(TIM1、TIM8)组成。基本定时器的功能最为简单,类似于51单片机内定时器。通用定时器是在基本定时器的基础上扩展而来,增加了输入捕获与输出比较等功能。高级定时器又是在通用定时器基础上扩展而来,增加了可编程死区互补输出、重复计数器、带刹车(断路)功能,这些功能主要针对工业电机控制方面。这里主要介绍通用定时器。

STM32F1的通用定时器TIMx (TIM2-TIM5)具有如下功能:

(1)16 位向上、向下、向上/向下自动装载计数器(TIMx_CNT)。

(2)16 位可编程(可以实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数为 1~65535之间的任意数值。

(3)4个独立通道(TIMx_CH1-4),这些通道可以用来作为:

A.输入捕获

B.输出比较

C. PWM 生成(边缘或中间对齐模式)

D.单脉冲模式输出

(4)可使用外部信号(TIMx_ETR)控制定时器,且可实现多个定时器互连(可以用1个定时器控制另外一个定时器)的同步电路。

(5)发生如下事件时产生中断/DMA请求:

A.更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)

B.触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)

C.输入捕获

D.输出比较

(6)支持针对定位的增量(正交)编码器和霍尔传感器电路

(7)触发输入作为外部时钟或者按周期的电流管理

通用定时器框图如下:

要使用定时器,我们首先对它进行初始化,初始化步骤为:

(1)使能定时器时钟

(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等

代码语言:javascript
复制
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct); 

定时器定时时间计算公式如下:

Tout= ((per)*(psc+1))/Tclk;

其中per为装载值,psc为分频系数,Tclk为系统时钟。从前面讲过的时钟树可以知道,高级定时器是挂在APB2总线上的,基本定时器和通用计时器是挂在APB1总线上的。所以,通用定时器的时钟是来源于ABP1预分频器之后,且如果APB1预分频系数=1,则频率不变,否则频率*2,通常设置APB1预分频器为二分频,因此通用定时器的时钟为72MHz,即Tclk=72MHz,代入算出的Tout单位为微秒。(如果有疑问请查看前面时钟树部分)

(3)设置定时器中断类型,并使能

代码语言:javascript
复制
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

(4)设置定时器中断优先级,使能定时器中断通道

NVIC初始化库函数是NVIC_Init();

(5)开启定时器

代码语言:javascript
复制
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState); 

(6)编写定时器中断服务函数

以上是有关定时器初始化的配置,具体的配置根据需要,这里不多说。

二、PWM输出

1.PWM简介

PWM是 Pulse Width Modulation 的缩写,中文意思就是脉冲宽度调制,简称脉宽调制。它是利用微处理器的数字输出来对模拟电路进行控制的一种非常有效的技术,其控制简单、灵活和动态响应好等优点而成为电力电子技术最广泛应用的控制方式,其应用领域包括测量,通信,功率控制与变换,电动机控制、伺服控制、调光、开关电源,甚至某些音频放大器,因此学习PWM具有十分重要的现实意义。

2.STM32F1 PWM介绍

STM32F1除了基本定时器TIM6和TIM7,其他定时器都可以产生PWM输出。其中高级定时器 TIM1 和 TIM8 可以同时产生多达 7 路的 PWM 输出。而通用定时器也能同时产生多达 4路的 PWM 输出,PWM的输出其实就是对外输出脉宽可调(即占空比调节)的方波信号,信号频率是由自动重装寄存器 ARR 的值决定,占空比由比较寄存器 CCR 的值决定。

PWM输出比较模式总共有8种,具体由寄存器 CCMRx 的位OCxM[2:0]配置。我们这里只讲解最常用的两种PWM输出模式:PWM1和PWM2,其他几种模式可以参考《STM32F10x中文参考手册》13、14、15定时器章节。PWM1和PWM2这两种模式用法差不多,区别之处就是输出电平的极性不同。

PWM模式根据计数器CNT计数方式,可分为边沿对齐模式和中心对齐模式。

(1)PWM边沿对齐模式

当 TIMx_CR1 寄存器中的 DIR 位为低时执行递增计数,计数器CNT从 0 计数到自动重载值(TIMx_ARR 寄存器的内容),然后重新从 0 开始计数并生成计数器上溢事件。

(2)PWM中心对齐模式

在中心对齐模式下,计数器 CNT 是工作做递增/递减模式下。开始的时候, 计数器CNT 从 0 开始计数到自动重载值减 1(ARR-1),生成计数器上溢事件;然后从自动重载值开始向下计数到 1 并生成计数器下溢事件。之后从 0 开始重新计数。

3.PWM输出配置步骤

(1)使能定时器及端口时钟,并设置引脚复用器映射

代码语言:javascript
复制
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);  
代码语言:javascript
复制
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);

GPIO_PinRemapConfig(GPIO_FullRemap_TIM3,ENABLE);

可选的参数在 stm32f10x_gpio.h 都已经列出来非常详细

代码语言:javascript
复制
GPIO_InitStructure.GPIO_Mode=GPIO_Mode_AF_PP;//复用推挽输出

(2)初始化定时器参数,包含自动重装值,分频系数,计数方式等

代码语言:javascript
复制
void TIM_TimeBaseInit(TIM_TypeDef*TIMx,TIM_TimeBaseInitTypeDef*TIM_TimeBaseInitStruct);

(3)初始化PWM输出参数,包含PWM模式、输出极性,使能等

代码语言:javascript
复制
void TIM_OCxInit(TIM_TypeDef* TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct); 

(4)开启定时器

代码语言:javascript
复制
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

(5)修改TIMx_CCRx的值控制占空比

代码语言:javascript
复制
void TIM_SetCompare1(TIM_TypeDef* TIMx, uint32_t Compare1);

(6)使能TIMx在CCRx上的预装载寄存器

使能输出比较预装载库函数是:

代码语言:javascript
复制
void TIM_OCxPreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);

第一个参数用于选择定时器,第二个参数用于选择使能还是失能输出比较预装载寄存器,可选择为TIM_OCPreload_Enable、TIM_OCPreload_Disable。

(7)使能 TIMx 在 ARR 上的预装载寄存器允许位

使能 TIMx 在 ARR 上的预装载寄存器允许位库函数是:

代码语言:javascript
复制
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

高级定时器要想输出PWM波形,必须要设置一个 MOE 位(TIMx_BDTR的第

15 位),以使能主输出,否则不会输出 PWM。库函数设置的函数为:

三、输入捕获

接下来重点说下输入捕获。

1、输入捕获简介

在定时器中断实验章节中我们介绍了通用定时器具有多种功能,输入捕获就是其中一种。STM32F1除了基本定时器TIM6和TIM7,其他定时器都具有输入捕获功能。输入捕获可以对输入的信号的上升沿,下降沿或者双边沿进行捕获,通常用于测量输入信号的脉宽、测量 PWM 输入信号的频率及占空比。下面我们以输入捕获测量高电平脉宽为例,通过一个简图来介绍输入捕获的工作原理。

CNT计数的次数等于:N*ARR+CCRx2,有了这个计数次数,再乘以 CNT 的计数周期,即可得到 t2-t1 的时间长度,即高电平持续时间。

比如我们要检测这段高电平持续的时间,那么在捕获到上升沿之后,我们立马将计数器的值清零,重新开启计数,直到下一次捕获到下降沿,统计这段时间定时器发生溢出的次数,并且读取出最后一次计数值,相加乘上每次计数的时间,就是整个高电平持续的时间了。

接下来是配置步骤。除了完成前面的定时器初始化之外,还需要

设置通用定时器的输入捕获参数,开启输入捕获功能

代码语言:javascript
复制
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef*TIM_ICInitStruct);

完成了所有配置,开启中断之后,最重要的是中断函数怎么写。

大致思路为:

由于发生定时器溢出(更新中断)和捕获中断都会进入中断函数,所以要在中断函数里通过中断标志来判断是发生了哪种中断,如果发生的是更新中断,并且是在捕获高电平之后,那么需要统计溢出次数和最后一次计数值val;如果发生的是捕获中断,那么需要判断捕获的是高电平还是低电平,因为要测量高电平时间,因此捕获到高电平的时候表示开始捕获,而捕获到低电平的时候表示捕获完成。我们可以定义一个八位二进制的变量sta,最高位置1用来表示捕获完成(捕获到低电平),次高位置1表示开始(或正在)捕获。低六位用来存放溢出次数。

简言之,当前状态为低电平时,次高位置0,当前状态因发生捕获而变为高电平时置1,发生下一次捕获变为低电平时再置0,同时最高位置1表示已经完成了一次捕获。

下面用流程图来表示中断函数实现思路。(订正:右下改为“当前状态为高电平?”)

代码实现如下:

代码语言:javascript
复制
void TIM5_IRQHandler()
{
    if((sta&0x80)!=0x80)//与运算注意加括号
    {
        if(TIM_GetITStatus(TIM5,TIM_IT_Update)==1)
        {
            if(sta&0x40)
            {
                if((sta&0x3f)==0x3f)
                {
                    sta|=0x80;
                    val=0xffff;
                }
                else
                {
                    sta++;
                }
            }
        }
        if(TIM_GetITStatus(TIM5,TIM_IT_CC1))
        {
            if(sta&0x40)
            {
                sta|=0x80;
                val=TIM_GetCapture1(TIM5);
                TIM_OC1PolarityConfig(TIM5,TIM_OCPolarity_High);
            }
            else
            {
                sta=0;
                val=0;
                sta|=0x40;
                TIM_Cmd(TIM5,DISABLE);
                TIM_SetCounter(TIM5,0);
                TIM_OC1PolarityConfig(TIM5,TIM_OCPolarity_Low);
                TIM_Cmd(TIM5,ENABLE);
            }
        }
    }
    TIM_ClearITPendingBit(TIM5,TIM_IT_Update|TIM_IT_CC1);
}

写完了中断函数,接下来写主函数。主函数主要是计算出时间,代码如下,比较简单,就不多说了。

代码语言:javascript
复制
int main()
{
    u32 data=0;
    u8 i=0;
    LED_Init();
    SysTick_Init(72);
    USART1_Init(9600);
    TIM5_CH1_Input_Init(0xffff,71);

    while(1)
    {
        i++;
        if(i%20==0)
        {
            led1=!led1;
        }
        delay_ms(10);

        if(sta&0x80)
        {
            data=sta&0x3f;
            data*=0xffff;
            data+=val;
            printf("高电平时间为:%d\r\n",data);
            sta=0;
        }


    }

}

里面用led灯的闪烁来指示系统是否正常运行。

编程中一些小细节要注意,比如进行进行与运算再判断值时,前面的与运算一定要加括号,否则无法实现

if((sta&0x80)!=0x80)//与运算注意加括号

还有就是变量sta和val在两个源文件中都有使用,但是又不能重复定义,这时可以使用关键字extern来声明变量而不定义变量。有关extern的用法这里讲的比较详细:http://c.biancheng.net/view/404.html。

总结:定时器的功能比较多,除了简单的定时一段时间产生中断之外,还有输出pwm波(占空比可调),输入捕获测量脉宽等等。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-01-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档