记录一下,方便以后翻阅~
主要内容:
1) 三种定时器分类及区别;
2) 通用定时器特点;
3) 通用定时器工作过程;
4) 实验一:定时器中断实验补充知识及部代码解读;
6) 实验二:定时器PWM输出实验补充知识及部分代码解读;
7) 实验三:定时器输入捕获实验补充知识及部分代码解读。
相关实验:
实验一定时器中断实验:通过定时器中断配置,使用定时器3,每隔500ms触发一次中断,后中断服务函数中控制LED实现LED1状态取反;
实验二定时器PWM输出实验:使用定时器3的PWM功能,输出占空比可变的PWM波,用来驱动LED灯,从而达到LED0亮度由暗变亮,又从亮变暗。
实验三定时器输入捕获实验:测量一个信号的脉冲宽度,要求如下图,按键按下后为高电平,获取2~3高电平的持续时间。
官方资料:官方资料:《STM32中文参考手册V10》第14章——通用定时器
1. STM32定时器
STM32F103RC和STM32F103ZE总共最多有8个定时器。
2. 三种定时器区别
3. 通用定时器功能特点
针对STM3 的通用 TIMx (TIM2、TIM3、TIM4 和 TIM5)定时器的特点:
3.1 可来源于位于低速的APB1总线上(APB1);
3.2 16 位向上、向下、向上/向下(也称中心对齐)计数模式,自动装载计数器(TIMx_CNT);
3.3 16 位可编程(可实时修改)预分频器(TIMx_PSC),计数器时钟频率的分频系数 为 1~65535 之间的任意数值。
3.4 4 个独立捕获/比较通道(TIMx_CH1~4),这些通道可以用来作为:
3.4.1 输入捕获;
3.4.2 输出比较;
3.4.3 PWM 生成(边缘或中间对齐模式) ;
3.4.4 单脉冲模式输出。
3.5 可使用外部信号(TIMx_ETR)控制定时器和定时器互连(可用 1 个定时器控制另一个定时器)的同步电路。
3.6 如下事件发生时产生中断/DMA(6个独立的IRQ/DMA请求生成器):
3.6.1 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发);
3.6.2 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数) ;
3.6.3 输入捕获;
3.6.4 输出比较;
3.6.5 支持针对定位的增量(正交)编码器和霍尔传感器电路;
3.6.6 触发输入作为外部时钟或者按周期的电流管理。
3.7 STM32 通用定时器可被用于,测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)等。
3.8 使用定时器预分频器和 RCC 时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。STM32 的每个通用定时器都是完全独立的,没有互相共享的任何资源。
4. 通用定时器的计数模式
通用定时器可以向上计数、向下计数、向上向下双向计数模式:
4.1 向上计数模式:计数器从0计数到自动加载值(TIMx_ARR),然后重新从0开始计数并且产生一个计数器溢出事件;
4.2 向下计数模式:计数器从自动装入的值(TIMx_ARR)开始向下计数到0,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件;
4.3 中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1,产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件,然后再从0开始重新计数。
5. 通用定时器的工作过程
5.1 上图中:
5.1.1 上半部分可称为时钟发生器,主要目的是产生时钟至PSC预分频器;
5.1.2 PSC预分频器、自动重装载寄存器和CNT计数器构成时基单元;
5.1.3 左下部分称为输入捕获部分,对输入信号进行相关操作;
5.1.4 右下部分称为输出比较部分;
5.1.5 剩余的捕获比较寄存器作为剩余的部分。
5.2 针对5.1.1细讲:
5.2.1 计数时钟可来源于RCC的TIMxCLK的内部时钟(主要通过APB1经过倍频获得);
5.2.2 计数时钟可来源于外部引脚TIMx_ETR(主要针对TIM2、TIM3、TIM4),经极性选择、边沿检测、预分频器和滤波后产生时钟信号;
5.2.3 计数时钟可来源于ITR0~3(内部触发输入,即来自于其他定时器),经过选择器后作为时钟来源;
5.2.4 计数时钟可来源于TI1F_ED,TI1FP1,TI1FP2(最终来源于TIMx_CH1~4,外部通道)。
5.3 针对5.1.2细讲:
时钟信号先经过PSC预分频器(即除法运算)产生CK_CNT时钟(计数器最终时钟),然后根据触发控制器配置的计数模式(向上,向下或中心对齐)进行计数,计数到重装载值后,会产生溢出事件,可产生触发中断或DMA请求;
5.4针对5.1.3细讲:
捕获通道引脚上的电平(比如上升沿捕获,边沿检测,可计算脉冲宽度);
5.5 针对5.1.4细讲:
可调整输出高低电平的占空比。
6. 实验一:定时器中断实验知识补充
6.1 内部时钟选择过程
AHB时钟经APB1预分频后产生的时钟供通用定时器TIM2,3,4,5用,注意:如果APB1预分频=1,则1倍输出,否则2倍输出。
举例:
再次强调:除非APB1的分频系数是1,否则通用定时器的时钟等于APB1时钟的2倍。
默认调用SystemInit函数情况下:
SYSCLK=72M;
AHB时钟=72M;//AHB预分频1//
APB1时钟=36M;//APB1预分频2//
所以,通用定时器时钟CK_INT应为APB1时钟的2倍,即2*36M=72M,
最终计数时钟CK_CNT时钟由CK_PSC分频后获得。
6.2 计数模式
6.2.1 向下计数(时钟分频因子=1)
数器从自动装入的值 (TIMx_ARR计数器的值)开始向下计数到0,然后从自动装入的值重新开始并且产生一个计数器向下溢出的事件。
6.2.2 向上计数(时钟分频因子=1)
计数器从0计数到自动加载值(TIMx_ARR计数器的值),然后重新从0开始计数并且产生一个计数器溢出事件。
6.2.3 中央对齐计数模式(时钟分频因子=1 ARR=6)
计数器从0开始计数到自动加载的值(TIMx_ARR寄存器的值−1),产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器下溢事件,然后再从0开始重新计数。
6.3 定时器中断实验相关寄存器
6.3.1 计数器当前值寄存器CNT;
6.3.2 预分频寄存器TIMx_PSC;
6.3.3 自动重装载寄存器(TIMx_ARR);
6.3.4 控制寄存器1(TIMx_CR1);
6.3.5 DMA中断使能寄存器(TIMx_DIER)。
6.4. 定时器中断实验常用库函数
6.4.1 定时器参数初始化:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
举例:
typedef struct
{
uint16_t TIM_Prescaler;
uint16_t TIM_CounterMode;
uint16_t TIM_Period;
uint16_t TIM_ClockDivision;
uint8_t TIM_RepetitionCounter;
} TIM_TimeBaseInitTypeDef;
TIM_TimeBaseStructure.TIM_Period = 4999;
TIM_TimeBaseStructure.TIM_Prescaler =7199;
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
6.4.2 定时器使能函数:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
6.4.3 定时器中断使能函数:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
6.4.4 状态标志位获取和清除:
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
6.5 定时器中断实现步骤
6.5.1 使能定时器时钟:
RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
6.5.2 初始化定时器,配置ARR,PSC:
TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
6.5.3 开启定时器中断,配置NVIC:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
6.5.4 使能定时器:
TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
6.5.5 编写中断服务函数:
void TIMx_IRQHandler(void);
6.6 溢出时间计算公式(有计数时钟、重载值和PSC分频系数共同决定):
Tout(溢出时间)=(ARR+1)(PSC+1)/Tclk
6.7 定时器中断实验部分代码解读
6.7.1 timer.h头文件
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//申明一个定时器3中断初始化函数,入口参数包括16位的arr和psc//
void TIMER3_Interrput_Init(u16 arr, u16 psc);
#endif
6.7.2 timer.c文件
#include "timer.h"
#include "led.h"
//编写定时器3中断初始化函数,入口参数包括16位的arr和psc//
void TIMER3_Interrput_Init(u16 arr, u16 psc)
{
//定义两个结构体//
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
NVIC_InitTypeDef NVIC_InitTypeDef_TIM3Struct;
//使能TIM3时钟//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
//定时器参数初始化,第一个结构体参数初始化//
TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
//*定时器3的中断使能//
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE);
//*定时器3中断使能函数初始化*//
NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannel=TIM3_IRQn;
NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelPreemptionPriority=0;
NVIC_InitTypeDef_TIM3Struct.NVIC_IRQChannelSubPriority=3;
NVIC_Init(&NVIC_InitTypeDef_TIM3Struct);
//使能定时器3//
TIM_Cmd(TIM3,ENABLE);
}
//编写定时器3中断服务函数//
void TIM3_IRQHandler(void)
{
//获取定时器3状态//
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)//判断定时器3状态是否为上升模式//
{
LED1=!LED1; //是的话就翻转LED1//
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);//清除标识//
}
}
6.7.3 main.c文件
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
int main(void)
{
//进行中断优先级分组//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//初始化相关函数//
delay_init();
LED_Init();
TIMER3_Interrput_Init(4999,7199);//500ms触发一次中断//
while(1)
{
}
}
7. 实验二:定时器PWM输出实验知识补充
7.1 PWM模式概念
7.2 PWM工作原理
脉冲宽度调制(PWM——Pulse Width Modulation)简称脉宽调制,是利用微处理器的数字输出来对模拟电路进行控制的一种技术。简单一点,就是对脉冲宽度的控制。
PWM工作逻辑可参考下图理解:
上图中,
ARR为自动重装载寄存器(TIMx_ARR)的位[0:15]设定值,
CCRx为某个捕获比较寄存器(TIMx_CRRx)的位[0:15]设定值,
黑线为计数器寄存器(TIMx_CNT)的位[0:15]实时值,记CNT。
当CNT小于CCRx时,可设输出低电平,反之输出高电平。因此可以认为:脉宽调制(PWM)信号的周期由ARR决定,占空比由CCRx决定。
以通道1为例,寄存器主要包括:
1) 捕获比较(值)寄存器(TIMx_CCRx(x=1,2,3,4)),用来设置比较值;
2) 捕获/比较模式寄存器 1(TIMx_CCMR1),位[4:6],输出比较1模式(OC1M):
在PWM方式下,设置PWM模式1(110),在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为有效电平,否则为无效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为无效电平(OC1REF=0),否则为有效电平(OC1REF=1);
或在PWM方式下,设PWM模式2(111),在向上计数时,一旦TIMx_CNT<TIMx_CCR1时通道1为无效电平,否则为有效电平;在向下计数时,一旦TIMx_CNT>TIMx_CCR1时通道1为有效电平(OC1REF=1),否则为无效电平(OC1REF=0)。
3) 捕获/比较使能寄存器(TIMx_CCER),位1,输入/捕获1输出极性CC1P:
当CC1通道配置为输出时,0:高电平有效,1:低电平有效;
4) 捕获/比较使能寄存器(TIMx_CCER),位0,输入/捕获1输出使能CC1E:
当CC1通道配置为输出时,0:关闭,1:打开。
7.3 PWM模式1 & PWM模式2区别(参考7.2)
针对上述寄存器TIMx_CCMR1的OC1M[2:0]位来分析:
PWM模式1:只要CNT小于CCRx,则为有效电平,反之为无效电平;
PWM模式2:只要CNT小于CCRx,则为无效电平,反之为有效电平。
举例如下图:
7.4 对自动重载的预装载寄存器的理解
自动装载寄存器是预先装载的,写或读自动重装载寄存器将访问预装载寄存器。根据在TIMx_CR1寄存器中的自动装载预装载使能位(ARPE)的设置,预装载寄存器的内容被立即或在每次的更新事件UEV时传送到影子寄存器。
影子寄存器保存的是定时器当前的计数值(或者溢出值),这个值是立即生效的值,这个计数值是从预装载寄存器(ARR)传过来的,但ARR什么时候把计数值传给影子寄存器呢?这儿就有个预装载使能位(ARPE):当ARPE=0的时候,你写入ARR的值马上就传到影子寄存器,也就立即生效当ARPE=1的时候,ARR的值就是直接传过去了,而是等到定时器更新事件发生,才把这个值传到影子寄存器,也就起到一个缓冲作用。
举例说明:
上图(左)所示,当ARPE=1时,自动加载寄存器值从F5改为36时,计数器寄存器值从F0增加至F5时产生更新事件,说明自动加载寄存器值从F5改为36时就已生效;
上图(右)所示,当ARPE=0时,自动加载寄存器值从FF改为36时,计数器寄存器值从31增加至36时产生更新事件,说明自动加载寄存器值从F5改为36时未即使生效,要等下个比较周期生效。
简单的说:
RPE=1,ARR立即生效;
APRE=0,ARR下个比较周期生效。
相关函数:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
7.5 STM32 定时器3输出通道引脚(可参考数据手册)
STM32 定时器输出通道引脚整理
7.6 相关库函数
7.6.1 PWM输出库函数
void TIM_OCxInit(TIM_TypeDef*
TIMx, TIM_OCInitTypeDef* TIM_OCInitStruct);
typedef struct
{
//*主要用到注释的四个参数*//
uint16_t TIM_OCMode; //PWM模式1或者模式2//
uint16_t TIM_OutputState; //输出使能 OR失能//
uint16_t TIM_OutputNState;
uint16_t TIM_Pulse; //比较值,写CCRx
uint16_t TIM_OCPolarity; //比较输出极性//
uint16_t TIM_OCNPolarity;
uint16_t TIM_OCIdleState;
uint16_t TIM_OCNIdleState;
} TIM_OCInitTypeDef;
举例:
TIM_OCInitStructure.TIM_OCMode= TIM_OCMode_PWM2; //PWM模式2//
TIM_OCInitStructure.TIM_OutputState= TIM_OutputState_Enable; //比较输出使能//
TIM_OCInitStructure. TIM_Pulse=100;
TIM_OCInitStructure.TIM_OCPolarity= TIM_OCPolarity_High; //输出极性:TIM输出比较极性高//
TIM_OC2Init(TIM3,&TIM_OCInitStructure); //根据T指定的参数初始化外设TIM3 OC2//
7.6.2 设置比较值函数:
void TIM_SetCompareX(TIM_TypeDef* TIMx, uint16_t Compare2);
7.6.3 使能输出比较预装载:
void TIM_OC2PreloadConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPreload);
7.6.4 使能自动重装载的预装载寄存器允许位:
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
7.7 PWM输出配置步骤(以TIM3_CH2为例):
7.7.1
使能定时器3和相关IO口时钟:
使能定时器3时钟:
void RCC_APB1PeriphClockCmd(uint32_t RCC_APB1Periph, FunctionalState NewState);
使能GPIOB时钟:
void RCC_APB2PeriphClockCmd(uint32_t RCC_APB2Periph, FunctionalState NewState);
7.7.2 初始化IO口为复用功能输出。函数:GPIO_Init();
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF_PP;
7.7.3 开启AFIO时钟(因为要把PB5(对应LED0)作定时器的PWM输出引脚,所以要重映射配置),同时设置重映射。
RCC_APB2PeriphClockCmd(RCC_APB2Periph_AFIO,ENABLE);
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE); //部分重映射,即TIM3_CH2对应PB5引脚//
7.7.4 初始化定时器,ARR,PSC等:
void TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
//TIM_TimeBaseInitTypeStruct结构体包括5个参数://
//uint16_t TIM_Prescaler; 针对预分频器寄存器(TIMx_PSC),值范围在0x0000到0xFFFF之间//
//uint16_t TIM_CounterMode; 计数模式,前面已讲//
//uint16_t TIM_Period; 针对自动重装载寄存器(TIMx_ARR),确定脉宽调制信号的周期,前面已讲//
//uint16_t TIM_ClockDivision; 针对控制寄存器1(TIMx_CR1)的位[8:9],设定时钟分频因子CKD,输入捕获用//
//uint8_t TIM_RepetitionCounter。 重复计数寄存器(TIMx_RCR),只有高级定时器才有//
7.7.5 初始化输出比较参数:
void TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);
7.7.6 使能预装载寄存器:
TIM_OC2PreloadConfig(TIM3,TIM_OCPreload_Enable);
//针对捕获/比较模式寄存器1(TIM3_CCMR1),位[11],设置OC2PE值//
设计preload register和shadow register的好处是,所有真正需要起作用的寄存器(shadow register)可以在同一个时间(发生更新事件时)被更新为所对应的preload register的内容,这样可以保证多个通道的操作能够准确地同步。如果没有shadow register,或者preload register和shadow register是直通的,即软件更新preload register时,同时更新了shadow register,因为软件不可能在一个相同的时刻同时更新多个寄存器,结果造成多个通道的时序不能同步,如果再加上其它因素(例如中断),多个通道的时序关系有可能是不可预知的。
7.7.7 使能定时器:
void TIM_Cmd(TIM3, Enable);
7.7.8 不断改变比较值CCRx,达到不同的占空比效果:
void TIM_SetCompare2(TIM3, uint16_t Compare2); //针对捕获/比较寄存器2(TIM3_CCR2),位[0:15],设置捕获/比较2(CCR2)的值//
7.8 定时器PWM输出实验部分代码解读
7.8.1 timer.h头文件代码解读
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//申明一个定时器3 PWM初始化函数,入口参数包括16位的arr和psc//
void TIMER3_PWM_Init(u16 arr, u16 psc);
#endif
7.8.2 timer.c文件代码解读
#include "timer.h"
#include "led.h"
//编写定时器3 PWM初始化函数,入口参数包括16位的arr和psc//
void TIMER3_PWM_Init(u16 arr, u16 psc)
{
//定义三个结构体//
GPIO_InitTypeDef GPIO_InitTypeStruct;
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitTypeStruct;
TIM_OCInitTypeDef TIM_OCInitTypeStruct;
//使能TIM3时钟,使能GPIOB时钟,使能AFIO时钟//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO,ENABLE);
//初始化GPIOB5为复用推挽输出,50MHz//
GPIO_InitTypeStruct.GPIO_Pin=GPIO_Pin_5;
GPIO_InitTypeStruct.GPIO_Mode=GPIO_Mode_AF_PP;
GPIO_InitTypeStruct.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOB,&GPIO_InitTypeStruct);
//*使能TIM3部分重映射*//
GPIO_PinRemapConfig(GPIO_PartialRemap_TIM3, ENABLE);
//定时器3参数初始化//
TIM_TimeBaseInitTypeStruct.TIM_Period=arr;
TIM_TimeBaseInitTypeStruct.TIM_Prescaler=psc;
TIM_TimeBaseInitTypeStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitTypeStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//*下面这行代码可以不写,值取0x00~0xFF之间,仅对TIM1和TIM8有效*//
TIM_TimeBaseInitTypeStruct.TIM_RepetitionCounter=0x55;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitTypeStruct);
//初始化输出比较参数//
TIM_OCInitTypeStruct.TIM_OCMode=TIM_OCMode_PWM2;
TIM_OCInitTypeStruct.TIM_OutputState=TIM_OutputState_Enable;
TIM_OCInitTypeStruct.TIM_Pulse=100;
TIM_OCInitTypeStruct.TIM_OCPolarity=TIM_OCPolarity_High;
TIM_OC2Init(TIM3,&TIM_OCInitTypeStruct);
//使能预装载寄存器//
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//使能定时器3//
TIM_Cmd(TIM3,ENABLE);
}
7.8.3 main.c文件代码解读
#include "sys.h"
#include "delay.h"
#include "led.h"
#include "timer.h"
int main(void)
{
u16 led0pwmval=0;
u8 dir=1;
delay_init(); //延时函数初始化//
LED_Init(); //LED端口初始化//
TIMER3_PWM_Init(899,0); //不分频,PWM频率=72000000/900=80Khz//
while(1)
{
delay_ms(10); //延迟10ms//
if(dir)led0pwmval++; //若dir为1,则led0pwmval加1//
else led0pwmval--; //若dir不为1,则led0pwmval减1//
if(led0pwmval>300)dir=0; //若led0pwmval大于300,则dir为0//
if(led0pwmval==0)dir=1; //若led0pwmval等于0,则dir为1//
TIM_SetCompare2(TIM3,led0pwmval); //设置定时器3比较值,该比较值先由0升至300,再降至0,不断重复//
}
}
8. 实验三:定时器输入捕获实验知识补充
8.1 如下图,对应是左下部分内容。
上图红框部分细节展开:
以通道1为例:
一句话总结工作过程:通过检测TIMx_CHx上的边沿信号,在边沿信号发生跳变(比如上升沿/下降沿)的时候,将当前定时器的值(TIMx_CNT)存放到对应的捕获/比较寄存器(TIMx_CCRx)里面,完成一次捕获。
具体步骤为:
8.1.1 设置输入捕获滤波器;
涉及相关寄存器:
TIMx_CR1控制寄存器1,[9:8]位CKD时钟分频因子确定fDTS频率;
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[7:4]位的IC1F输入捕获1滤波器,确定采样频率及数字滤波器长度。
8.1.2 设置输入捕获极性;
涉及相关寄存器:
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[1]位CC1P输入/捕获1输出极性,确定选择是IC1还是IC1的反相信号作为触发或捕获信号。
8.1.3 设置输入捕获映射通道;
涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,[1:0]位CC1S捕获/比较1选择,定义通道的方向及输入脚的选择。
8.1.4 设置输入捕获分频器;
涉及相关寄存器:
TIMx_CCMR1捕获/比较寄存器1,输入捕获模式下,[3:2]位IC1PSC输入/捕获1预分频器,定义CC1输入(IC1)的预分频系数;
TIMx_CCER捕获/比较使能寄存器,输入捕获模式下,[0]位CC1E输入/捕获1输出使能,决定计数器的值是否能捕获TIMx_CCR1寄存器。
8.1.5 捕获到有效信号可以开启中断;
涉及相关寄存器:
TIMx_DIER DMA/中断使能寄存器。
8.1.6 最后,看看定时器通道对应引脚TIM5为例。
8.2 相关库函数
8.2.1 输入捕获通道初始化函数:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
typedef struct
{
uint16_t TIM_Channel; //捕获通道1-4//
uint16_t TIM_ICPolarity; //捕获极性//
uint16_t TIM_ICSelection; //映射关系//
uint16_t TIM_ICPrescaler; //分频系数//
uint16_t TIM_ICFilter; //滤波器//
}
TIM_ICInitTypeDef;
举例:
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
8.2.2 通道极性设置独立函数:
void TIM_OCxPolarityConfig(TIM_TypeDef* TIMx, uint16_t TIM_OCPolarity);
8.2.3 获取通道捕获值
uint32_t TIM_GetCapture1(TIM_TypeDef* TIMx);
8.3 输入捕获的一般配置步骤
8.3.1 初始化定时器和通道对应IO的时钟;
8.3.2 初始化IO口,模式为输入,
void GPIO_Init(GPIO_TypeDef* GPIOx, GPIO_InitTypeDef* GPIO_InitStruct);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IPD; //PA0 输入//
8.3.3 初始化定时器,主要设置ARR,PSC的值:
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
8.3.4 初始化输入捕获通道:
void TIM_ICInit(TIM_TypeDef* TIMx, TIM_ICInitTypeDef* TIM_ICInitStruct);
8.3.5 如果要开启捕获中断:
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState)
void NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct);
8.3.6 使能定时器:
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
8.3.7 编写中断服务函数:
void TIMx_IRQHandler(void);
8.4 定时器输入捕获实验部分代码解读
8.4.1 timer.h文件代码解读
#ifndef __TIMER_H
#define __TIMER_H
#include "sys.h"
//申明一个定时器5 输入捕获初始化函数,入口参数包括16位的arr和psc//
void TIM5_Cap_Init(u16 arr,u16 psc);
#endif
8.4.2 timer.c文件代码解读,本代码将顺便解读库函数相关指令对应的寄存器操作。
#include "timer.h"
//编写定时器5 输入捕获初始化函数,入口参数包括16位的arr和psc//
void TIM5_Cap_Init(u16 arr,u16 psc)
{
//申明GPIO,定时器时基,定时器输入捕获和中断参数初始化四个结构体//
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM5_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//**********************************************************//
//使能TIM5时钟,使能GPIOA时钟//
//针对RCC_APB1ENR寄存器,[3]位,TIM5EN设为1,即定时器5时钟开启//
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM5, ENABLE);
//针对RCC_APB2ENR寄存器,[2]位,IOPAEN设为1,即IO端口A时钟开启//
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//**********************************************************//
//初始化GPIOA,PA0,下拉输入模式//
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0; //选择引脚0
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPD; //下拉输入
//按照上述参数初始化GPIOA//
GPIO_Init(GPIOA, &GPIO_InitStructure);
//PA0至0,PA0对应WK_UP按键,按下给1电平,未按给0电平//
GPIO_ResetBits(GPIOA,GPIO_Pin_0);
//**************************************************************//
//初始化定时器5的间基结构//
//针对TIM5_ARR寄存器,[0:15]位,取arr值,设定自动重装载寄存器的数值//
TIM_TimeBaseStructure.TIM_Period = arr;
//针对TIM5_PSC寄存器,[0:15]位,取psc值,设定预分频器的值//
TIM_TimeBaseStructure.TIM_Prescaler =psc;
//针对TIM5_CR1寄存器,[8:9]位,CKD设00,TDTS = Tck_tim//
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//针对TIM5_CR1寄存器,[4]位,DIR设0,向上技术模式;[5:6]位,CMS设00,数器依据方向位(DIR)向上或向下计数//
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//按照上述参数初始化定时器5的时基结构//
TIM_TimeBaseInit(TIM5, &TIM_TimeBaseStructure);
//**************************************************************************************//
//初始化TIM5输入捕获参数//
//选择通道1//
TIM5_ICInitStructure.TIM_Channel = TIM_Channel_1;
//针对TIM5_CCMR1寄存器,输入模式下,[0:1]位,CC1S设为01,即CC1通道配置为输入,IC1映射到TI1上//
TIM5_ICInitStructure.TIM_ICSelection = TIM_ICSelection_DirectTI;
//针对TIM5_CCMR1寄存器,输入模式下,[4:7]位,IC1F设为0000,即IC1F设为无滤波器//
TIM5_ICInitStructure.TIM_ICFilter = 0x00;
//针对TIM5_CCMR1寄存器,输入模式下,[2:3]位,IC1PSC设为00,不分频//
TIM5_ICInitStructure.TIM_ICPrescaler = TIM_ICPSC_DIV1;
//针对TIM5_CCER寄存器,输入模式下,[1]位,CC1E设为0,即上升沿捕获//
TIM5_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//按照上述参数初始化TIM5输入捕获//
TIM_ICInit(TIM5, &TIM5_ICInitStructure);
//*****************************************************************************//
//TIM5中断参数初始化//
NVIC_InitStructure.NVIC_IRQChannel = TIM5_IRQn; //选择TIM5中断//
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //抢占优先级2级//
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0; //从优先级0级//
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能//
NVIC_Init(&NVIC_InitStructure);
//****************************************************************************//
//开启捕获中断//
//针对TIM5_DIER寄存器,[0]位,UIE至1,允许更新中断;[1]位,CC1IE至1,允许捕获/比较1中断//
TIM_ITConfig(TIM5,TIM_IT_Update|TIM_IT_CC1,ENABLE);
//针对TIM5_CR1寄存器,[0]位,CEN至1,定时器5使能//
TIM_Cmd(TIM5,ENABLE );
}
//****************************************************************************//
//下面开始编写定时器5中断服务函数//
u8 TIM5CH1_CAPTURE_STA=0; //定义一个8位的输入捕获状态变量,初始化值为0000 0000//
u16 TIM5CH1_CAPTURE_VAL; //定义一个16位输入捕获变量//
void TIM5_IRQHandler(void)
{
//判断TIM5CH1_CAPTURE_STA的第7位值是否为0,即第7位为捕获完成标志//
//如果TIM5CH1_CAPTURE_STA的第7位值为0,则捕获未完成,if返回1,执行下述括号内代码,否则跳至最后一行代码//
if((TIM5CH1_CAPTURE_STA&0X80)==0)
{
//更新中断函数代码,为计数器溢出时服务//
//判断定时器5的更新中断是否发生//
//针对TIM5_DIER寄存器,[0]位,获取UIE更新中断使能状态值;针对TIM5_SR寄存器,[0]位,获取UIF更新中断标记值//
//当UIE为1,UIF为1,说明定时器5产生更新事件,if返回值为1,执行下述括号内代码,否则跳至捕获中断代码//
if (TIM_GetITStatus(TIM5, TIM_IT_Update) != RESET)
{
//判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即发生更新中断之前,是否已捕获到上升沿了//
//如果TIM5CH1_CAPTURE_STA的第6位值为1,执行下述括号内代码,否则跳至捕获中断代码//
if(TIM5CH1_CAPTURE_STA&0X40)
{
//判断TIM5CH1_CAPTURE_STA的第0~5位值是否为0x3F,即判断溢出次数是否达到上限//
//如果TIM5CH1_CAPTURE_STA的第0~5位值为0x3F,执行下述括号内代码,否则跳至下述else代码//
if((TIM5CH1_CAPTURE_STA&0X3F)==0X3F)
{
//强制输出捕获的最大时间//
//将TIM5CH1_CAPTURE_STA的第7位值至1,将TIM5CH1_CAPTURE_VAL设为0xFFFF//
TIM5CH1_CAPTURE_STA|=0X80;
TIM5CH1_CAPTURE_VAL=0XFFFF;
}
//如果TIM5CH1_CAPTURE_STA的第0~5位值不为0x3F,则执行下述else代码,TIM5CH1_CAPTURE_STA值加1//
else TIM5CH1_CAPTURE_STA++;
}
}
//捕获中断函数代码,表示捕获到上升沿或下降沿//
//判断定时器5的捕获1中断是否发生//
//针对TIM5_DIER寄存器,[1]位,获取CC1IE捕获/比较1使能状态值;针对TIM5_SR寄存器,[1]位,获取CC1IF捕获标记值//
//若捕获1发生捕获事件,即捕获1中断发生,则执行下述括号内代码,否则跳至最后一行代码//
if (TIM_GetITStatus(TIM5, TIM_IT_CC1) != RESET)
{
//判断TIM5CH1_CAPTURE_STA的第6位值是否为1,即判断再此之前是否已捕获到上升沿了//
//若TIM5CH1_CAPTURE_STA的第6位值为1,则之前以捕获到上升沿,本次是下降沿,即完成一次捕获//
if(TIM5CH1_CAPTURE_STA&0X40)
{
//将TIM5CH1_CAPTURE_STA的第7位值至1//
TIM5CH1_CAPTURE_STA|=0X80;
//将TIM_GetCapture1(TIM5)函数的返回值赋给TIM5CH1_CAPTURE_VAL//
//针对TIM5_CCR1,获取[0:15]位的值并传至TIM5CH1_CAPTURE_VAL//
TIM5CH1_CAPTURE_VAL=TIM_GetCapture1(TIM5);
//重新配置极性,为上升沿捕获//
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Rising); //CC1P=0 设置为上升沿捕获//
}
else //本次捕获的是上升沿//
{
TIM5CH1_CAPTURE_STA=0;
TIM5CH1_CAPTURE_VAL=0;
//针对TIM5_CNT计数器寄存器,[0:15]位,全设0//
TIM_SetCounter(TIM5,0);
TIM5CH1_CAPTURE_STA|=0X40; //标记捕获到了上升沿//
TIM_OC1PolarityConfig(TIM5,TIM_ICPolarity_Falling); //CC1P=1 设置为下降沿捕获//
}
}
}
TIM_ClearITPendingBit(TIM5, TIM_IT_CC1|TIM_IT_Update); //清除中断标志位
}
8.4.3 main.c文件代码解读
#include "led.h"
#include "key.h"
#include "timer.h"
#include "sys.h"
#include "delay.h"
#include "usart.h"
//下述两个变量已在timer.c文件中申明过//
extern u8 TIM5CH1_CAPTURE_STA; //输入捕获状态
extern u16 TIM5CH1_CAPTURE_VAL; //输入捕获值
int main(void)
{
u32 temp=0;
//设置NVIC中断分组2,即2位抢占优先级,2位响应优先级//
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
delay_init(); //延时函数初始化
uart_init(115200); //串口初始化为115200
LED_Init(); //LED端口初始化
//初始化TIM5_Cap_Init(u16 arr,u16 psc)函数,arr=0xFFFF,psc=71,即以1Mhz的频率进行计数//
//1Mhz计算来源:下述函数中,有针对TIM5_CR1寄存器[8:9]位的配置,即CKD设00,即CK_INT频率与CK_PSC频率一样//
//CK_PSC频率除以psc+1=72,即获得CK_CNT=1Mhz,为什么+1请参考STM32中文参考手册,14.4.11预分频器TIMx_PSC//
TIM5_Cap_Init(0XFFFF,72-1);
while(1)
{
delay_ms(10); //延时10ms//
//判断TIM5CH1_CAPTURE_STA的第7位值是否为0,0x80即0b1000 0000//
//如果TIM5CH1_CAPTURE_STA的第7位值为0,则if返回1,即成功捕获一次上升沿和一次下降沿,执行下述括号内代码//
if(TIM5CH1_CAPTURE_STA&0X80)
{
//将TIM5CH1_CAPTURE_STA的第[0:5]位数据传至temp中//
temp=TIM5CH1_CAPTURE_STA&0X3F;
temp*=65536; //temp的最小单位对应65536个计数时间总和//
temp+=TIM5CH1_CAPTURE_VAL; //TIM5CH1_CAPTURE_VAL的最小单位对应1个计数时间//
printf("HIGH:%d us\r\n",temp); //打印总的高电平时间//
TIM5CH1_CAPTURE_STA=0; //开启下一次捕获//
}
}
}
8.5 定时器输入捕获实验代码缺点,从timer.c文件中也可以发现,每次捕获的上升沿至下降沿的时间间隔不能大于某个最大值(大概4秒多),否则将强制输出最大时间,并开始新一轮上升沿捕获。
旧知识点:
1)复习如何新建工程模板,可参考STM32学习心得二:新建工程模板;
2)复习基于库函数的初始化函数的一般格式,可参考STM32学习心得三:GPIO实验-基于库函数;
3)复习寄存器地址,可参考STM32学习心得四:GPIO实验-基于寄存器;
4)复习位操作,可参考STM32学习心得五:GPIO实验-基于位操作;
5)复习寄存器地址名称映射,可参考STM32学习心得六:相关C语言学习及寄存器地址名称映射解读;
6)复习时钟系统框图,可参考STM32学习心得七:STM32时钟系统框图解读及相关函数;
7)复习如何对GPIO进行复用及重映射,可参考STM32学习心得十二:端口复用和重映射;
8)复习延迟函数,可参考STM32学习心得九:Systick滴答定时器和延时函数解读;
9)复习ST-LINK仿真器的参数配置,可参考STM32学习心得十:在Keil MDK软件中配置ST-LINK仿真器;
10)复习ST-LINK调试方法,可参考STM32学习心得十一:ST-LINK调试原理+软硬件仿真调试方法;
11)复习中断相关知识,可参考STM32学习心得十三:NVIC中断优先级管理;
12)复习外部中断一般配置,可参考STM32学习心得十五:外部中断实验。