STM32的定时器功能众多,拥有基本定时功能,输出比较功能(如产生PWM波等),输入捕获(测量方波信号),读取正交编码器的波形。
TIM定时器的基本功能是对输入的时钟进行计数,并在计数值达到设定值时触发中断(如主频一般是72MHZ,那么计数值设定为72,就是每1US触发一次中断;计数值设定为72000,就是每1MS触发一次中断。)
STM32定时器拥有16位计数器,预分频器,自动重装寄存器组成的时基单元,在72MHZ计数钟下可以实现最大59.65的定时。TIM不仅具备基本的定时中断功能,而且还包含内外时钟源选择,输入捕获,主从触发模式,输出比较,编码器接口等多种功能,并且分为高级定时器,通用定时器,基本定时器三种类型。
内部时钟(CK_CNT):一般就是系统的主频72MHz,通向时基单元的输入。
时基单元:16位预分频器 + 16位计数器 + 16位自动重装载寄存器。
预分频器:对输入的72MHz时钟进行预分频,寄存器内存储的值是实际的分频系数减一。写0就是不分频,写1就是2分频,写2就是3分频……
计数器:对预分频后的计数时钟进行计数,每遇到上升沿就加一。
自动重装载寄存器:存储计数的最大值,到达此值后触发中断并清零计数器。1。通用定时器框图
- 时基单元:中间的PSC预分频器、自动重装载寄存器、CNT计数器。通用定时器和高级定时器新增两个功能——“向下计数模式”、“中央计数模式”。
- 向上计数模式【常用】:从0开始累加,到自动重装载值触发中断。
- 向下计数模式:从自动重装载值递减,到0触发中断。
- 中央对齐模式:从0开始累加,到自动重装载值触发中断,然后递减,到0再次触发中断。常用于电机控制的SVPWM算法中。
- 内外时钟源选择和主从触发模式结构:上面的一大块。下面介绍各种各样的内外时钟源:
- 内部时钟CK_INT【常用】:通常为72MHz,基本定时器只能选择CK_INT,通用定时器和高级定时器则新增了下面的时钟源。
- 外部TIMx_ETR【常用】:引脚的位置可以参考引脚定义表,如stm32f103c8t6的PA0引脚复用了TIM2_CH1_ETR等。外部输入了方波时钟,然后通过极性选择、滤波等电路进行整形,然后兵分两路,一路ETRF进入触发控制器,紧跟着就可以选择成为时基单元的时钟(外部时钟模式2);一路进入选择器等待成为TRGI。
- TRGI主要用作触发输入来使用,可以触发定时器的从模式,本小节仅用做外部时钟(外部时钟模式1),其他功能后续再讲。
- 外部ITR信号:包括ITR0~ITR4(引脚定义见参考手册“表78 TIMx内部触发连接”),来自其他定时器,实现了定时器的级联。这个ITR信号从上一级定时器的主模式TRGO引脚来(图片右上方)。
- 外部TI1F-ED:来自于输入捕获单元的TIMx_CH1引脚,后缀“ED”意为边沿,也就是说该路时钟的上升沿和下降沿均有效,也就是CH1引脚的边沿。
- 外部TI1FP1:来自CH1引脚时钟。
- 外部TI2FP2:来自CH2引脚时钟。
- 编码器接口:可以读取正交编码器的输出波形,后续会再介绍。
- TRGO引脚:定时器的主模式输出,可以将内部的一些事件映射到TRGO上,相比基本定时器这些事件的范围显然更广。
- 注:最后三种外部时钟用于输入捕获和测频率,后续介绍。
- 输出比较电路:下面右侧的一大堆,总共有4个通道,可以用于输出PWM波形驱动电机。
- 输入捕获电路:下面左侧的一大推,也是有4个通道,可以用于测量输入方波的频率。
- 注:输入捕获电路和输出比较电路不能同时使用,所以共用中间的“捕获/比较寄存器”以及输入/输出的引脚。后续再介绍。
2.高级定时器
重复次数计数器:可以实现每个几个计数周期,才发生一次更新事件和更新中断,相当于自带一级的定时器级联。
DTG(Dead Time Generate):死区生成电路。将输出引脚由原来的一个变为两个互补的输出,可以输出一对互补的有死区的PWM波(防止出现短暂的直通现象),可以驱动三相无刷电机(如四轴飞行器、电动车后轮、电钻等)。
BRK刹车输入:为了给电机驱动提供安全保障,如果外部引脚BKIN(Break IN)产生了刹车信号或者内部时钟失效,这个电路就会自动切断电机的输出,防止意外的发生。
定时中断基本结构
时基单元:中间的粉色部分。
运行控制:控制寄存器的一些位,如启动停止、向上或向下计数等,操作这些寄存器就可以控制时基单元的运行了。
内部时钟模式、外部时钟模式2、外部时钟模式1:外部时钟源选择。这个选择器的输出就是为时基单元提供时钟。
编码器模式:编码器独有的模式,一般用不到。
中断申请控制:由于定时器内部有很多地方要申请中断,“中断申请控制”就用来使能控制这些中断是否使能。比如,中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。
CK_PSC:预分频器的输入时钟,选择内部时钟源就是72MHz。
CNT_EN:计数器使能。高电平计数器正常运行,低电平计数器停止。
CK_CNT:计数器时钟,既是预分频器的时钟输出,也是计数器的时钟输入。
计数器寄存器:对CK_CNT进行自增计数,到达自动重装载值清零。
更新事件:计数器寄存器到达自动重装载值时产生一个脉冲。
下面三行时序体现了预分频计数器的一种缓冲机制:
预分频控制寄存器:供用户读写使用,实时响应用户控制,但并不直接决定分频系数。
预分频缓冲器:也称为影子寄存器,真正起分频作用的寄存器。只有在更新事件到达时,才从“预分频控制寄存器”更新预分频参数。以确保更新事件的稳定性。
预分频计数器:按照预分频参数进行计数,以产生对应的CK_CNT脉冲。
正常的计数器时序没啥好说的,就是根据CK_CNT计数,到达自动重装载值产生中断,所以只需看一下更新自动重装载值的过程:
无预装时序(禁用缓冲寄存器):有溢出问题。若将自动重装载值变小,且此时计数器寄存器已经超过这个新的重装载值,那么计数器寄存器就会一直计数到FFFF才清零。这可能会造成一些问题。
有预装时序(启用缓冲寄存器):比较稳定。只有更新事件来临时才更新自动重装载值。
时钟是所有外设的基础,所以是需要最先配置的。ST公司写好了SystemInit函数来配置时钟树,下面具体介绍:
左侧是时钟产生电路,右侧时钟分配电路,中间的SYSCLK就是72MHz的系统时钟。
时钟产生电路:
四个振荡源:
内部的8MHz高速RC振荡器。
外部的4-16MHz高速石英晶体振荡器:一般8MHz,相比于内部的RC高速振荡器更加稳定。
外部的32.768kHz低速晶振:一般给RTC提供时钟。
内部的40kHz低速RC振荡器:给看门狗提供时钟。
上面的两个高速晶振用于给系统提供时钟,如AGB、APB1、APB2的时钟。SystemInit函数配置时钟的过程:首先启动内部8MHz时钟为系统时钟,然后配置外部8MHz时钟到PLLMUL模块进行9倍频到72MHz,等到这个72MHz时钟稳定后,再将其作为系统时钟。于是就实现了系统时钟从8MHz切换到72MHz。
CSS:监测外部时钟的运行状态,一旦外部时钟失效,就会自动把外部时钟切换成内部时钟,保障系统时钟的运行,防止程序卡死造成事故。如果外部晶振出问题,那么就会导致程序的时钟变为8MHz,也就是比预期的时钟慢了9倍。
时钟分配电路
AHB总线:有预分频器,SysytemInit配置分频系数为1,于是AHB时钟输出就是72MHz。
APB1总线:SysytemInit配置分频系数为2,于是APB1时钟输出就是36MHz。
APB2总线:SysytemInit配置分频系数为1,于是APB2时钟输出就是72MHz。
外设时钟使能:就是库函数RCC_APB2PeriphClockCmd开启的地方,可以控制相应的外设时钟开启。
定时器的时钟:从图中可以看出,按照SystemInit的默认配置,所有的定时器时钟都是72MHz。
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Timer.h"
uint16_t TimerCount = 0;
int main(void){
//配置中断的优先级分组,每个工程只能出现一次!!
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//OLED显示屏初始化
OLED_Init();
OLED_ShowString(1,1,"TIM_Interrupt:");
OLED_ShowNum(2,1,0,5);
//定时器初始化
Timer_Init();
while(1){
OLED_ShowNum(2,1,TimerCount,5);
};
}
//TIM2定时中断后的中断函数
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
TimerCount++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
timer.c
#include "stm32f10x.h" // Device header
// 定时器初始化-TIM2
void Timer_Init(void){
//1.初始化RCC内部时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//2.选择时基单元的时钟
TIM_InternalClockConfig(TIM2);//默认使用内部时钟,也可以不写
//3.配置时基单元
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自动重装器的值10000
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;//PSC预分频的值7200
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//重复计数器的值(高级定时器才有)
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//消除上一行TIM_TimeBaseInit立刻产生更新事件影响
//4.配置中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//5.配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//6.配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
/*
//TIM2定时中断后的中断函数
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
??
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
对外部输入的方波(对射式红外传感器)进行计次,每出现9个方波就自动加一。
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Timer.h"
uint16_t TimerCount = 0;
int main(void){
//配置中断的优先级分组,每个工程只能出现一次!!
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//OLED显示屏初始化
OLED_Init();
OLED_ShowString(1,1,"TimerCount:");
OLED_ShowNum(2,1,0,5);
OLED_ShowString(3,1,"CNT:");
OLED_ShowNum(3,5,0,5);
//定时器初始化
Timer_Init();
while(1){
OLED_ShowNum(2,1,TimerCount,5);
OLED_ShowNum(3,5,TIM_GetCounter(TIM2),5);
};
}
//TIM2定时中断后的中断函数
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
TimerCount++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
timer.c
#include "stm32f10x.h" // Device header
// 定时器初始化-TIM2
void Timer_Init(void){
//1.初始化RCC内部时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
//2.配置GPIO-PA0
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);
//3.选择时基单元的时钟-ETR外部时钟模式2
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_Inverted, 0x0F);
//4.配置时基单元
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_ClearFlag(TIM2,TIM_FLAG_Update);//消除上一行TIM_TimeBaseInit立刻产生更新事件影响
//5.配置中断输出控制
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//6.配置NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2 ;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//7.配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
/*
//TIM2定时中断后的中断函数
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){
??
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
TIM的 OC(Output Compare)输出比较 主要用于输出PWM波形,PWM又是驱动电机的必要条件(智能车、机器人等),所以应用广泛。输出比较功能 可以通过比较 CNT计数器 与 CCR捕获/比较寄存器 的大小,来对输出电平进行置1、置0或翻转的操作,用于输出一定频率和占空比的PWM波形。
- 每个高级定时器和通用定时器都拥有4个输出比较通道。
- 高级定时器的前3个通道额外拥有死区生成和互补输出的功能。
- PWM脉冲宽度调制,在具有惯性的系统中,可以通过一系列脉冲的宽度进行调制,来等效的输出的模拟量(占空比即为高电平所占比例,分辨率为占空比变化精细程度),
- 注:定时中断的频率就是PWM波的频率,只不过占空比的变化范围由自动重装栽植决定
- 左侧输入:CNT和CCR比较的结果。
- ETRF:定时器的小功能,一般不用,无需了解。
- 输出模式控制器:CNT>=CCR1时,输出模式控制器收到信号并输出oc1ref。输出比较的8种模式如下:
- 见stm32参考手册“14.3.7强制输出模式”、“14.3.8输出比较模式”、“14.3.9PWM模式”三节。
- 主模式控制器:可以将oc1ref映射到主模式的TRGO输出上去。
- CC1P:极性选择。选择是否需要将oc1ref的高低电平翻转一下。
- 输出使能电路:由CC1E选择要不要输出。
- OC1:后续通过TIMx_CH1输出到GPIO引脚上。
- PWM频率: Freq = CK_PSC / (PSC + 1) / (ARR + 1)
- PWM占空比: Duty = CCR / (ARR + 1)
- PWM分辨率: Reso = 1 / (ARR + 1)
型号:SG90。
三根输入线:棕色是电源负、红色是电源正、橙色是PWM信号线。注意这个颜色因型号不同可能不同,需要查看说明手册。
输入PWM信号要求:周期为20ms(50Hz),高电平宽度为0.5ms~2.5ms(占空比2.5%~12.5%),脉冲控制精度为2us(0.18°,占空比精度0.01%)。
内部电板的基本思路:根据输入占空比得到期望角度,然后检测当前角度,若当前角度较小则顺时针转;反之则逆时针转,直到与期望角度相同。
- VM:电机电源的正极,范围4.5V~10V。要接一个可以输出大电流的电源,且电压一般与电机的额定电压保持一致。
- VCC:逻辑电平输入端,范围2.7V~5.5V。这个要与控制控制器的电源保持一致,所以采用stm32就是3.3V、采用51单片机就是5V。
- 三个GND:都是电源负极,随便选一个接地即可。
- STBY:Stand by,待机控制引脚。接地,芯片处于待机状态不工作;接逻辑电源VCC,芯片正常工作。
- PWMA、AIN1、AIN2:接在单片机GPIO引脚上,用于控制电机,控制逻辑如上图。PWMA接PWM波,AIN1、AIN2可以任意接普通的GPIO口。
- AO1、AO2:按照控制逻辑,从VM汲取电流来驱动电机。
- PWMB、BIN1、BIN2与BO1、BO2:同上述,控制另一个电机的转动。
注:根据逻辑控制真值表,这里的PWM就是用来等效成一个模拟量。