下面将讲解一下STM32中的定时器TIM的一些基本操作。这同样也是考试的重点内容之一,比较光自己看一遍印象还是不深刻,写篇博客就会逼自己去弄得更明白一点。
1.软件延时:CPU执行一段“空”程序实现延时
优点:不需要添加额外的硬件设备,简单易实现
缺点:占用CPU时间,精度受中断及CPU主频时钟影响
2.硬件延时电路:采用附加的数字电路(如555单稳态芯片)产生特定的延时。
优点:不占用CPU时间
缺点:改变延时时间需要调整电路参数(电阻、电容值),通用性灵活性差,时间精度受电路参数影响,稳定性差
3.可编程定时器(计数器):
以专用芯片(如Intel8253)或MCU中集成的定时器接口设备(如STM32中的TIM1~8 )来进行定时。
优点:定时器初始化后就独立于CPU工作,不占用CPU时间,时间长短不限,使用灵活精度高。
缺点:增加硬件成本
51单片机采用石英晶体和振荡电路构成的时钟模块,来为系统提供时钟脉冲。此类时钟电路结构简单,输出时钟的频率取决于石英晶体的谐振频率,且只有单一频率时钟输出。受加工工艺等影响,高频石英车成本较高,对于需要频率为72MHz等处理器,常用锁相环技术把低频时钟信号升到一个较高频率。
STM32片内除ARM Cortx - M3内核,还有众多用途各异的外设,既有需要高速时钟的外设,也有需要低速时钟的外设,因此还需要分频器将时钟频率降到一个合适的频率供外设使用。
从图中可以看出STM32的通用定时器分为时基单元、输出比较单元和输入捕获单元三大部分。
时基单元简单来讲就是可以实现计数的功能,以向上计数为例,定时器初始化完成后,被选做计数器时钟源的时钟CS_PSK经过预分频器分频后作为计数脉冲施加到计数器上,计数器的值随着CK_CNT开始从0递增,当计数到自动重加载值(ARR的值)之后,产生计数器向上溢出更新事件,并且开始新一轮的计数。
问题:利用定时器接口TIM3,实现1s定时,利用TIM3定时器中断,在中断服务程序让LED0~1闪烁一次。
下面将讲解STM32模板库中对于的核心代码
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//使能TIM3的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//计数器计数10000次就发生更新事件(因为从0开始需要-1)
TIM_TimeBaseStructure.TIM_Period = 10000-1;
//分频器设置7200(因为从0开始需要-1),那么系统时钟经过分频器就变为10KHz
//再通过计数器计数就是10K / 10K = 1Hz,正好就是1s一次
TIM_TimeBaseStructure.TIM_Prescaler = 7200-1;
//设置TIM3控制寄存器CR1的CKD[2:0] = 00,使tDTS = tCK_INT
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
//向上计数模式
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//初始化TIM3
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
//允许TIM3更新中断
TIM_ITConfig(TIM3, TIM_IT_Update, ENABLE);
//设置TIM3的控制器CR1的CEN位,开启定时器TIM3
TIM_Cmd(TIM3, ENABLE);
由于是使用TIM中断实现,那么中断离不开NVIC,因此还需要配置NVIC以及对于的中断服务程序,这里就不一一列举出来了。
输出比较功能由一个捕获/比较寄存器CCR和计数器CNT以及相应的输出部分组成。
在运行的过程中,如果CNT==CCR1那么执行以下操作:
设置通道输出值:输出比较模式OC1M[2:0] (捕获/比较模式寄存器TIMx_CCMR1中)决定中间信号OC1REF值高有效,OC1REF和输出有效极
性选择位CC1P(捕获/比较使能寄存器TIMx_CCER)决定OC1输出值。
利用TIM3的输出比较功能,产生10Hz的方波,用来控制LED0闪烁。
分析:让TIM3基本定时器工作在0.5s定时状态(20Hz ),再利用TIM3_CH2通道的输出比较功能,在“相等则翻转模”式下,每次间隔0.5s,出现一次翻转,产生10Hz的方波输出。
这里需要调用TIM对应的输出比较模式,因此也需要使用对应的接口驱动函数。但是由于我手上的函数库可能有点老了,这块代码好像对不上,就不详细将其中的函数和参数了。直接讲核心代码好了
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
//使能TIM3的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
//计数器计数500次就发生更新事件(因为从0开始需要-1)
TIM_TimeBaseStructure.TIM_Period = 500-1;
//分频器设置7200(因为从0开始需要-1),那么系统时钟经过分频器就变为10KHz
//再通过计数器计数就是10K / 500 = 20Hz
TIM_TimeBaseStructure.TIM_Prescaler = 7200-1;
//设置TIM3控制寄存器CR1的CKD[2:0] = 00,使tDTS = tCK_INT
TIM_TimeBaseStructure.TIM_ClockDivision = 0x0;
//向上计数模式
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
//初始化TIM3
TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
TIM_OCInitTypeDef TIM_OCInitStructure;
//初始化TIM3_CH2输出翻转模式
TIM_OCInitStructure.TIM_OCMode = TIM_OCMode_Toggle;
//比较输出使能
TIM_OCInitStructure.TIM_OutputState = TIM_OutputState_Enable;
//输出极性,TIM输出比较极性高
TIM_OCInitStructure.TIM_OCPolarity = TIM_OCPolarity_High;
//设置比较输出寄存器TIM3_CCR2的值,每计数达到200次信号翻转一次,定时器输出20Hz,那么方波就为10Hz,(0-499都可以,因为计数器范围是0-499)
TIM_OCInitStructure.TIM_Pulse = 200;
//初始化TIM3_OC2
TIM_OC2Init(TIM3, &TIM_OCInitStructure);
//使能TIM3在CCR2上的预加载寄存器
TIM_OC2PreloadConfig(TIM3, TIM_OCPreload_Enable);
//使能TIM3
TIM_Cmd(TIM3, ENABLE);
输入捕获是指通过对引脚输入信号电平变化的检测,在发现电平变化时,保存计数器的值到输入捕获寄存器中,同时设置输入捕获状态标志位;如果允许中断,则向CPU发出中断请求,进而实现对事件发生间隔时间的测量以及对实时事件的响应。
用途:雷达测速、超声波测距、发动机转速以及车速的测量等。
输入捕获功能由捕获信号输入部分(数字滤波、边沿检测和预分频器),捕获/比较寄存器TIMx_CCRx以及计数器CNT组成。
输入滤波器对TI1输入信号采样滤波得到信号TI1F,经带极性选择的边沿检测器后获得有效沿信号TI1FP1,进而得到输入捕获信号IC1,此信号再经分频得到IC1PS。当一个有效的IC1PS来到后,即触发捕获事件,计数器当前值被锁存到捕获/比较寄存器TIMx_CCR1中,同时通道1捕获/比较标志CC1IF(TIMx_SR寄存器)被置1。
利用TIM2的输入捕获功能,测量任意PWM信号的频率和占空比。
核心代码如下:
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//定义时钟频率并设置ARR到最大值
u32 CK_INT_FREQ = 72000000;
//定义内部时钟源CK_INT频率为72MHz
u32 CK_CNT_FREQ = 1000 * 1000;
//设置预分频比PSC的值
u16 PrescalerValue = (u16)(CK_INT_FREQ/CK_CNT_FREQ-1);
// ARR计数初值65535,测量范围:PWM周期<65536 * 1us = 0.06536s
//PWM频率 > 16.258Hz
u16 ARRValue = 65535;
//使能时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//设置定时器复位模式并选定复位触发信号
TIM_SelectSlaveMode(TIM2, TIM_SlaveMode_Reset);
//将TIM2复位从模式(SMS=100),每次TRIG上跳沿引起TIM2_CNT复位。
//选择TI1FP作为触发信号TRIG
TIM_SelectInputTrigger(TIM2, TIM_TS_TI1FP1);
//配置TIM2定时器
TIM_TimeBaseStructure.TIM_Period = ARRValue;
TIM_TimeBaseStructure.TIM_Prescaler = PrescalerValue;
TIM_TimeBaseStructure.TIM_ClockDivision = 0;
//向上计数模式
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
//设置TIM2的捕获通道IC1
TIM_ICInitStructure.TIM_Channel=TIM_Channel_1;
//TIM2_TI1输入信号不滤波
TIM_ICInitStructure.TIM_ICFilter=0x0;
//IC1选择TI1的边缘极性信号TI1FP1(CC1S=01)
TIM_ICInitStructure.TIM_ICSelection=TIM_ICSelection_DirectTI;
//上升沿触发捕获
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPolarity_Rising;
//IC1信号不分配得到IC1PS
TIM_ICInitStructure.TIM_ICPolarity = TIM_ICPSC_DIV1;
//初始化TIM2的IC1
TIM_PWMIConfig(TIM2, &TIM_ICInitStructure);
//允许IC1捕获中断
TIM_ITConfig(TIM2, TIM_IT_CC1, ENABLE);
//使能IC1输入捕获中断
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
开启TIM2
TIM_Cmd(TIM2, ENABLE);
//配置NVIC
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
//-------------------------------------------------------------------
//NVIC中断服务程序
float PWMDutyResult=0;
float PWMFreqResult=0;
u16 IC1Value=0;
u16 IC2Value=0;
void TIM2_IRQHandler(void) //中断服务程序
{
IC1Value=TIM_GetCapture1(TIM2);
IC2Value=TIM_GetCapture2(TIM2);
if(IC1Value != 0 && IC2Value != 0)
{
PWMDutyResult = (float)(IC2Value+1) * 100 / (IC1Value + 1);
PWMFreqResult = (float)(1000000 / (IC1Value + 1));
//清空中断标志位
TIM_ClearITPendingBit(TIM2, TIM_IT_CC1);
}
}