TIM
定时器是功能最强大,内容最复杂的32结构。
- 之前51用过的功能,定时产生中断。
- 输出比较,常用于产生 PWM 波形,驱动电机等。
- 输入捕获,测量方波频率。
- 编码器,读取正交编码器的波形。
最大定时时间:72M/65536/65536=中断频率,中断频率取倒数是最大定时时间。
定时器可以级联,比如 72MHz的最大定时 59.65s,级联一次 * 65536 * 65536.
类型 | 编号 | 总线 | 功能 |
---|---|---|---|
高级定时器 | TIM1、TIM8 | APB2 | 拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能 |
通用定时器 | TIM2、TIM3、TIM4、TIM5 | APB1 | 拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能 |
基本定时器 | TIM6、TIM7 | APB1 | 拥有定时中断、主模式触发DAC的功能 |
从下到上越来越复杂且强大。f103c8t6 有 TM1-4(不同芯片不一样,要查阅手册!),我们主要学习通用定时器。
PSC 对 RCC 分频,比如写入1代表2分频的话,就是输出72MHz/2=36MHz. 实际分频系数=预分频器值+1.
计数器按照时钟频率不断自增,=自动重装载寄存器值时清零并产生中断。UI 是中断,U 是事件。
主动触发 DAC:DAC 中断会很频繁,占用很多 CPU 资源。如果定时器可以自己操纵从设备处理 DAC 就节省许多 CPU 资源。上图中的 TRGO 就是。
在基本计数器基础上,通用寄存器还支持向下计数和中央对齐计数(0-重装值-0-重装值……)
时钟可选内部72MHz时钟或外部时钟。
可以实现定时器级联,参照手册查看哪几个定时器和哪几个级联。自己输出的引脚 CH 也可以作为自己的时钟输入,作用后续展开。
四个输出是输出比较电路,左边是输入捕获电路。
图中有阴影的就是有缓冲寄存器的,缓冲寄存器下面马上讲。
重复次数计数器允许几个周期才触发一次中断。
输出 CH1-3 可以输出互补的波,有用处。为了防止切换时产生直通现象(两个反相电路同时切换,类似x形),通过 DTG 死区生成电路, 切换前产生死区,让上下管全归零再切换。
左下角刹车输入,异常状态时可以终止定时器。
基本结构如下:
运行控制就比如设置向上向下计数。
中断输出控制相当于一个标志位,决定这个中断需不需要。
CK_PSC:预分频器的输入时钟。
CNT_EN:计数器使能,高电平开启。后面预分频器变为1,分频/2了,因此两次上升沿才触发一次。计数器寄存器随之不断自增。
达到自动重装器值后更新事件,计数器归零。
预分频寄存器修改后并不是立刻改变的,而是等触发中断后下一个开始计数周期后才修改预分频缓冲器,预分频缓冲器才能真正控制。
预分频计数器可见分频的原理,比如设置预分频缓冲器=1,也就是实际分频系数为2,那么计数0,1后达到实际分频系数-1,清零并计数器++。
C K _ C N T = C K _ P S C / ( P S C + 1 ) CK\_CNT=CK\_PSC / (PSC+1) CK_CNT=CK_PSC/(PSC+1)
对于计数器来说是这样的:
实际分频系数为2,2次时钟周期才+1,溢出时更新事件和中断标志寄存器提示现在中断了。中断标志寄存器要在中断程序中手动清零。
计数器溢出频率:
C K _ C N T _ O V = C K _ C N T / ( A R R + 1 ) = C K _ P S C / ( P S C + 1 ) / ( A R R + 1 ) CK\_CNT\_OV = CK\_CNT / (ARR + 1) = CK\_PSC / (PSC + 1) / (ARR + 1) CK_CNT_OV=CK_CNT/(ARR+1)=CK_PSC/(PSC+1)/(ARR+1)
arr 是自动重装寄存器值。溢出时间取倒数即可。
arr 也有缓冲寄存器,可以自己选取用不用。
如图,虽然 arr 改成了36,但是也得等这轮中断结束了再更新影子寄存器。
这个最好还是打开,因为如果快加到了arr的值,然后arr缩小了,计数器可能就要一直加加到溢出归零再来一圈才能触发中断之类的小问题。
接下来看看时钟,时钟是所有外设需要的东西。systemInit() 函数里就在配置。
HSI HSE 是内外时钟源,外部更稳定一些。两者都提供系统时钟,一些外设(AHB APB12)就是依靠他俩。
LSE OSC 是 RTC。
LSI RC 是独立看门狗的时钟。
SystemInit() 先启动内部时钟,系统暂时以8MHz运行,再启动外部时钟进入 PLL 锁相环进行倍频,达到稳定 72MHz 后输出。
因此外部时钟电路出问题了,无法正常切换时可能感觉程序跑的慢,是在用8MHz系统内时钟跑。或者CSS时钟安全系统强行把外部电路停了也可能。CSS也参与到了高级控制定时器中的刹车输入电路。
本板APB1预分频系数2,APB2是1.
AHB 预分频器的分频系数直接对PCLK1做除法。定时器2-7则是:“如果预分频系数=1,则频率=36MHz不变;否则频率*2”,所以其内部基准时钟永远是72MHz。APB2上的定时器1,8也是。
所有时钟都连了一个与门,外加一个使能电路。
代码:定时器
流程:
- RCC。
- 设置时钟源。
- 设置时钟源的时基单元(初始化预分频器,自动重装器,计数器模式)。
- 设置中断控制(开启)。
- 配置 nvic,这个用前面学过的 NIVC_Init。
- 设置运行控制。
- 启动计数器。
需要的函数列表:
TimeBaseInit:初始化预分频器,自动重装器,计数器模式。
TimeBaseStructInit:给时基单元赋初值。
Cmd:启动时钟。
ITConfig:开启对应时钟中断。
下面六个是时钟源选择,内部时钟,ITRx其他定时器时钟,TIx捕获通道的时钟,ETR模式1,2的时钟,单独配置ETR时基单元、极性、滤波器等设置的函数。
这五个函数是 Init 后单独修改时基单元的配置的函数(改预分频器,改计数模式,设置是否预装,改计数器值,改自动重装值,。
最后是四个获取清除寄存器的函数。
代码就和exti有一些小的差别。
#include "stm32f10x.h" // Device header
extern uint16_t cnt;
void Timer_Init(void){
//rcc, set timer source, set psc + set arr + set cnt mode(time base unit), set it controller, set nvic it handler, set running control, enable counter
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);//默认也是这个时钟,不写也行
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;//滤波器采样,频率越低,采样点数越多,滤波效果越好。不过延迟也越大。采样频率就是内部时钟和这个分频参数共同作用的结果
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up;
//比如我们想定1s中断一次,CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1),也就是72M / (PSC + 1) / (ARR + 1) = 1
//所以两者赋值可以是10000-1和7200-1,只要两者都在65535以内就行,赋值不唯一
TIM_TimeBaseInitStructure.TIM_Period=10000-1;//arr自动重装器值,72M 计数10000次,耗时 10000/72M s
TIM_TimeBaseInitStructure.TIM_Prescaler=7200-1;//预分频器值,分7200频,即 10000/(72M/7200) s=1s中断一次。
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;//重复计数器值,高级计数器才用
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);//设为更新中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
TIM_Cmd(TIM2,ENABLE);
}
void TIM2_IRQHandler(void){
if(TIM_GetITStatus(TIM2,TIM_IT_Update)==SET){//这里设置为update
cnt++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
这里把cnt变量写在main.c里了,通过extern变量声明“这个变量是在其他文件中的,编译器你去找吧”。其实.h里函数声明也应该加extern,不过可以省略。
启动开发板后,可以看到立刻出发了一次中断,这是为啥呢?TIM_BaseInit最后一句是这样的:
TIMx->EGR = TIM_PSCReloadMode_Immediate;
我们知道自动重装寄存器写入值不是立刻写入的,是等到更新事件的时候才写入缓冲区。那一开始我们初始化自动重装寄存器后,写不进去呀,触发不了中断呀。
因此我们要在 TIM_BaseInit 里手动触发一次更新事件,就是上面这个语句。代价就是上电立刻触发一次中断。更新事件和更新中断是同时发生的。
想去掉这一次中断,可以在 TIM_BaseInit 后面立刻跟一个 “TIM_ClearFlag”。
接下来我们写一个用例,因为TIM2和PA0是一个引脚,所以我们设定TIM2通过外部时钟模式配置,这样我们把红外遮光器接到PA0后,手动挡一次光就相当于模拟了时钟一次周期。
需要做的修改:
- 时钟初始化加上GPIOInit,把内部时钟配置改成外部时钟(模式2)。
void Timer_Init(void){
//rcc, set timer source, set psc + set arr + set cnt mode(time base unit), set it controller, set nvic it handler, set running control, enable counter
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
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);
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,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;
TIM_TimeBaseInitStructure.TIM_Prescaler=1-1;//十次红外检测,定时器++
TIM_TimeBaseInitStructure.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_IT_Update);
TIM_ITConfig(TIM2,TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
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);
TIM_Cmd(TIM2,ENABLE);
}
这样效果就是通过红外传感器来制造clock波形让定时器读取。