赞
踩
定时器的基本功能:定时器可以在预定的时间间隔内产生周期性的中断。例如,定时器可以被设置为每1ms产生一个中断信号,这常用于创建周期性的服务例程,如操作系统的时钟滴答。
16位计数器:STM32的定时器通常包含一个16位的计数器,意味着它能够计数从0到65535(2^16 - 1)。当计数器从0计数到预设值时,可生成中断或其他事件。
最大计数值:在72MHz的时钟频率下,如果定时器的预分频器(Prescaler)设为72-1(即每72个时钟周期计数一次),则计数器每计数到72000就相当于过去了1秒。这样就可以用来测量时间,或创建延时等。并且最大定时为59.65s。
时钟选择:定时器可以从不同的时钟源中选择,例如内部的主时钟或外部时钟源。
多种工作模式:定时器可以在多种模式下工作,包括简单的定时模式(如计数溢出时产生中断),PWM产生模式(用于调整电压输出,控制电机速度等),输入捕获模式(测量外部事件的时间间隔,如信号的频率)等。
高级控制功能:定时器还可以配置为触发ADC(模数转换器)的启动,或与其他定时器同步等。
类型 | 编号 | 总线 | 功能 |
高级定时器 | TIM1、TIM8 | APB2 | 这类定时器拥有全部的功能,例如高级PWM控制,还支持三相电机的正反变换,复杂的同步控制,以及与其他高级功能的集成,如直接数字控制转换(DCC)。并且额外具有重复计数器、死区生成、互补输出、刹车输入等功能。 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器的全部功能,并且额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主从触发DAC的功能 |
对于STM32F103C8T6,拥有的定时器资源为:TIM1、TIM2、TIM3、TIM4,所以在使用任何外设时,要先查明这个芯片有没有这个功能。
首先下面有三个最重要的寄存器:预分频器,计数器和自动重装载寄存器,这部分是最基本的计数计时电路,也叫做时基单元。预分频器之前连接的是基准计数时钟的输入,基本定时器只能选择内部时钟,所以也就相当于连接的是输入端,也就是内部时钟CK_INT。
预分频器
这里的频率值一般都是系统的主频72MHz,首先预分频器对72MHz的计数时钟进行预分频,就是对输入的基准频率进行一个分频的操作比如这里写0就代表不分频或1分频,这时输出频率等于输入频率 = 72MHz。写1就代表分频(2分频),输出频率=输入频率/2 = 36MHz。如果写2就代表3分频,也就是除以3。所以实际的分频系数比预分频器的值大1,预分频器是16位的,所以最大值为65535,最大分频也就是65536。
计数器
计数器可以对预分频后的计数时钟进行计数,计数时钟每次有一个上升沿,计数器就加1,计数器也是16位的,所以值的范围是0 - 65535。如果加到65535以后,再加一就会回到零重新开始。当计数器的值增加到目标值时,产生中断,就代表完成了定时的任务。
自动重装载寄存器
因为计数器需要一个目标值,所以还需要一个存储目标值的寄存器。这个寄存器也是16位的,会存入技术目标。当计数器的值等于自动重装值时,就代表计时时间到了。这时就会产生中断信号,并重新计数。
UI(Update Interrupt)
这个向上的折线箭头,代表会产生中断信号,像刚才这种计数值等于自动重装值所产生的中断,叫做更新中断,之后会通往NVIC,再配置好NVIC的定时器通道,这时定时器的更新中断就可以得到CPU的响应了。
U
向下的箭头代表会产生一个事件,叫做更新事件,更新事件不会触发中断,但可以触发内部其他电路的工作。
首先中间部分的结构和基本计时器一样, 这里不同的是,通用计时器和高级计时器不仅支持向上计数模式,还支持向下计数模式和中央对齐计数模式。向下计数模式就是从重装值开始,向下自减,减到0以后,回到重装值同时申请中断,依次循环。中央对齐计数模式就是从0开始,先向上自增,计数到重装值,申请中断,然后再向下自减,减到0时再次申请中断,然后依次循环。
在通用定时器中,时钟源不仅可以选择内部的72MHz时钟,还可以选择外部时钟。第一个外部时钟是TIMx_ETR引脚的外部时钟,叫做外部时钟模式2,其中ETR引脚的位置在C8T6中对应PA0引脚。除了ETR,TRGI也可以当作外部时钟,这个叫做外部时钟模式1。
对于下面的部分,右边的是输出比较电路,一共四个通道CH1 - CH4,用于输出PWM波形驱动电机。左边是输入捕获电路,也是四个通道CH1 - CH4,用于测量输入方波的频率。中间的寄存器是输入捕获和输出比较电路共用的,因为输入和输出不能同时使用。
对比通用定时器,主要不同的是右边申请中断的地方,增加了重复次数计数器,这个可以实现每隔几个计数周期才会发生一次更新事件和更新中断。
下面还有对于输出比较模块的升级,DTG是死区生成电路,右边的输出变成了两个互补的输出,可以输出一对互补的PWM波形,这时为了驱动三相无刷电机的。
最下面的刹车输入,这时一个保护机制,如果外部引脚BKIN产生了刹车信号,或者内部时钟失效产生故障,控制电路会自动切断电机的输出。
预分频器时序
这里看一个时序图,当预分频器的参数从1改为2时,特别注意预分频控制寄存器,它的作用是即使参数改变了,但是此时如果计数器只执行到一半, 这个缓冲寄存器可以保证参数的变化不会立刻生效,而是等到本次计数周期完成,产生了更新事件,改变后的参数才会起作用。
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
计数器时序图
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
计数器无预装时序
计数器有预装时序
对于计数器无预装时序和计数器有预装时序,区别在于引入了影子寄存器,目的是为了同步,让值的变化和更新事件同步发生,防止在运行途中更改造成错误。例如在这里如果不使用影子寄存器,F5改成36以后立即生效,但此时计数器的数值已经到了F1, 代表已经超过36,所以F1只能一直增加直到FFFF,回到0以后再重新加到36。
要实现定时器定时中断,要使用的库函数文件为:stm32f10x_tim.h,在这里可以找到定时器TIM需要使用到的函数
第一步:开启RCC时钟,这时定时器的基准时钟和外设的工作时钟就会同时打开。
第二步:选择时基单元的时钟源,对于定时中断,选择内部时钟源。
第三步:配置时基单元,也就是预分频器、计数器和自动重装载器。
第四步:配置输出中断控制,允许更新中断输出到NVIC。
第五步:配置NVIC,在NVIC中打开定时器中断的通道,并分配优先级。
第六步:运行控制,在整个模块配置完成后,还需要使能一下计数器,这样计数器才能开始计数。
首先是Timer.c文件,下面包含代码和详细注释:
- #include "stm32f10x.h"
-
- //定时中断初始化
- void Timer_Init(void)
- {
- //开启时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
-
- //配置时钟源
- TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
-
- //时基单元初始化
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
- TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
- TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1; //计数周期,即ARR的值
- TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
-
- //中断输出配置
- TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
- //TIM_TimeBaseInit函数末尾,手动产生了更新事件
- //若不清除此标志位,则开启中断后,会立刻进入一次中断
- //如果不介意此问题,则不清除此标志位也可
-
- TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
-
- //NVIC中断分组
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- //NVIC配置
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
-
- //TIM使能
- TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
- }
-
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);:
TIM_InternalClockConfig(TIM2);:
定义和初始化一个 TIM_TimeBaseInitTypeDef 类型的结构体变量 TIM_TimeBaseInitStructure,用于配置定时器的时基单元:
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);:
TIM_ClearFlag(TIM2, TIM_FLAG_Update);:
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);:
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); 和 NVIC_Init(&NVIC_InitStructure);:
TIM_Cmd(TIM2, ENABLE);:
接着是Timer.h文件,这部分引用声明一下即可。
- #ifndef __TIMER_H
- #define __TIMER_H
-
- void Timer_Init(void);
-
- #endif
最后是主函数main.c :
- #include "stm32f10x.h"
- #include "Delay.h"
- #include "OLED.h"
- #include "Timer.h"
-
- uint16_t Num; //定义在定时器中断里自增的变量
-
- int main(void)
- {
- //模块初始化
- OLED_Init(); //OLED初始化
- Timer_Init(); //定时中断初始化
-
- //显示静态字符串
- OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
-
- while (1)
- {
- OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
- }
- }
-
- //TIM2中断函数
- void TIM2_IRQHandler(void)
- {
- if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
- {
- Num ++; //Num变量自增,用于测试定时中断
- TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }
定时器外部时钟,刚才是代码示例使用的是内部时钟,这里使用外部时钟,通过一个光电传感器来实现计数功能。
首先是Timer.c 代码:
- #include "stm32f10x.h"
-
- //定时中断初始化
- void Timer_Init(void)
- {
- //开启时钟
- RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
- RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
-
- //GPIO初始化
- GPIO_InitTypeDef GPIO_InitStructure;
- GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IPU;
- GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0;
- GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
- GPIO_Init(GPIOA, &GPIO_InitStructure); //将PA0引脚初始化为上拉输入
-
- //外部时钟配置
- TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
- //选择外部时钟模式2,时钟从TIM_ETR引脚输入
- //注意TIM2的ETR引脚固定为PA0,无法随意更改
- //最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
-
- //时基单元初始化
- TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure; //定义结构体变量
- TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1; //时钟分频,选择不分频,此参数用于配置滤波器时钟,不影响时基单元功能
- TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
- TIM_TimeBaseInitStructure.TIM_Period = 10 - 1; //计数周期,即ARR的值
- TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值
- TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
- TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
-
- //中断输出配置
- TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
- //TIM_TimeBaseInit函数末尾,手动产生了更新事件
- //若不清除此标志位,则开启中断后,会立刻进入一次中断
- //如果不介意此问题,则不清除此标志位也可
-
- TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
-
- //NVIC中断分组
- NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
- //即抢占优先级范围:0~3,响应优先级范围:0~3
- //此分组配置在整个工程中仅需调用一次
- //若有多个中断,可以把此代码放在main函数内,while循环之前
- //若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
-
- //NVIC配置
- NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
- NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
- NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
- NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
- NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
- NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
-
- //TIM使能
- TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
- }
-
- //返回定时器CNT的值
- uint16_t Timer_GetCounter(void)
- {
- return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
- }
-
-
时钟源配置:
GPIO初始化:
外部时钟配置:
时基单元初始化:
中断输出配置:
NVIC中断分组和NVIC配置:
TIM使能:
Timer_GetCounter 函数:
接着是Timer.h 代码,同样只需要声明一下。
- #ifndef __TIMER_H
- #define __TIMER_H
-
- void Timer_Init(void);
- uint16_t Timer_GetCounter(void);
-
- #endif
最后是主函数main.c :
- #include "stm32f10x.h"
- #include "Delay.h"
- #include "OLED.h"
- #include "Timer.h"
-
- uint16_t Num; //定义在定时器中断里自增的变量
-
- int main(void)
- {
- //模块初始化
- OLED_Init(); //OLED初始化
- Timer_Init(); //定时中断初始化
-
- //显示静态字符串
- OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
- OLED_ShowString(2, 1, "CNT:"); //2行1列显示字符串CNT:
-
- while (1)
- {
- OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
- OLED_ShowNum(2, 5, Timer_GetCounter(), 5); //不断刷新显示CNT的值
- }
- }
-
- //TIM2中断函数
- void TIM2_IRQHandler(void)
- {
- if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
- {
- Num ++; //Num变量自增,用于测试定时中断
- TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
- //中断标志位必须清除
- //否则中断将连续不断地触发,导致主程序卡死
- }
- }
完整代码工程文件:
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。