一、前言
STM32定时器分类
STM32103ZET6具备8个定时器TIMx(x = 1,2,...,8)。其中,TIM1和TIM8为高级定时器,TIM2-TIM6为通用定时器,TIM6和TIM7为基本定时器,本文将以TIM3通用定时器为例,分析STM32定时器工作的底层寄存器原理。
STM32通用定时器简介
STM32的通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和 PWM)。
本文DEMO目标
本次DEMO将使用STM32F103ZET6的定时器TIM3实现DS1的翻转(定时器中断),在主函数用DS0的翻转来提示程序正在运行。
二、通用定时器的主要功能
通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为 1~65536 之间的任意 数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
三、时钟树解析
定时器的时钟来源一共有以下4个:
- 内部时钟(CK_INT)
- 外部时钟模式 1:外部输入脚(TIx)
- 外部时钟模式 2:外部触发输入(ETR)
- 内部触发输入(ITRx):使用 A 定时器作为 B 定时器的预分频器(A 为 B 提供时钟)。
这些时钟,具体选择哪个可以通过 TIMx_SMCR寄存器的相关位来设置。这里的 CK_INT时钟是从 APB1 倍频的来的,除非 APB1 的时钟分频数设置为 1,否则通用定时器 TIMx 的时钟是 APB1 时钟的 2 倍,当 APB1 的时钟不分频的时候,通用定时器 TIMx 的时钟就等于 APB1的时钟。
本文选用CK_INT作为定时器时钟来源。
系统时钟为72MHz,AHB不分频,APB1后最大频率为36MHz,故APB1作2分频,故TIM3的时钟是APB1时钟的2倍,即TIM3CLK=72MHz。具体时钟树解析如下所示:
四、寄存器介绍
对通用定时器TIM3的控制主要涉及以下寄存器:
寄存器 | 作用 |
---|---|
TIMx_CR1 | 控制寄存器1 |
TIMx_DIER | DMA/中断使能寄存器 |
TIMx_PSC | 预分频器 |
TIMx_SMCR | 从模式控制寄存器 |
TIMx_CNT | 计数器 |
TIMx_ARR | 自动重装载寄存器 |
TIMx_SR | 状态寄存器 |
下面将对这些寄存器进行一一介绍。
4.1 TIMx_CR1控制寄存器1
《STM32中文手册》对TIMx_CR1寄存器的描述如下:
我们仅需要关注其第7位和第0位,第7位为自动重装载允许位,具体作用见4.6节。第0位为计数器的使能位,该位必须置1,才能让定时器开始计数。
4.2 TIMx_DIER DMA/中断使能寄存器
《STM32中文手册》对TIMx_DIER寄存器的描述如下:
我们仅需关注其第0位,该位为允许更新中断位,该位需要置1,才能允许更新事件所产生的中断。
4.3 TIMx_PSC预分频器
《STM32中文手册》对TIMx_PSC寄存器的描述如下:
该寄存器用于设计时钟分频,然后提供给计数器,作为计数器的时钟,该寄存器中值的范围是0-65535,按照公式fCK_CNT=fCK_PSC/(PSC[15:0]+1)。分频系数的范围为1-65536,按照前文对时钟树的解析,这里的fCK_PSC为72MHz。
4.4 TIMx_SMCR从模式控制寄存器
《STM32中文手册》对TIMx_SMCR寄存器的描述如下:
我们仅需关注该寄存器的位[2:0],即SMS从模式选择,将其设置为000,即关闭从模式,此时如果CEN=1,则预分频器直接由内部时钟驱动(CK_INT)。由于该寄存器的复位值为0X0000,故无需更改此寄存器的值。
4.5 TIMx_CNT计数器
《STM32中文手册》对TIMx_CNT寄存器的描述如下:
该寄存器是定时器的计数器,该寄存器存储了当前定时器的计数值。定时器的超时时间由下式计算:
Time = CNT / fCK_CNT
其中:
Time为超时时间,CNT为TIMx_CNT的计数值,fCK_CNT为定时器预分频频率。
4.6 TIMx_ARR自动重装载寄存器
《STM32中文手册》对TIMx_ARR寄存器的描述如下:
该寄存器包含了将要传送至实际的自动重装载寄存器的数值。该寄存器在物理上实际对应着 2 个寄存器。一个是程序员可以直接操作的,另外一个是程序员看不到的,这个看不到的寄存器在《STM32中文参考手册》里面被叫做影子寄存器。事实上真正起作用的是影子寄存器。根据 TIMx_CR1寄存器中 APRE 位的设置:APRE=0 时,预装载寄存器的内容可以随时传送到影子寄存器,此时 2 者是连通的;而 APRE=1 时,在每一次更新事件(UEV)时,才把预装在寄存器的内容传送到影子寄存器。我们将APRE值设置为0(复位时就是0,不用改)。
4.7 TIMx_SR状态寄存器
《STM32中文手册》对TIMx_SR寄存器的描述如下:
我们只关注其第0位,该位为更新中断标记。当计数器 CNT 被重新初始化的时候,产生更新中断标记,通过这个中断标志位,就可以知道产生中断的类型。当该位为1时,表示中断产生,需要在中断服务函数中用软件对该位清0。
五、程序设计
该DEMO程序主要分为三个部分:定时器3初始化程序,定时器3中断服务程序以及轮询主函数。下面进行一一介绍。
5.1 定时器3初始化程序
该部分程序在HARDWARE/timer.c/TIM3_Int_Init(),主要作用是TIM3时钟使能、设置定时器超时值、允许中断更新、使能定时器3,并注册定时器3的中断,具体代码如下所示:
cpp
//通用定时器3中断初始化
//这里时钟选择为APB1的2倍,而APB1为36M
//arr:自动重装值。
//psc:时钟预分频数
//这里使用的是定时器3!
void TIM3_Int_Init(u16 arr,u16 psc)
{
RCC->APB1ENR|=1<<1; //TIM3时钟使能
TIM3->ARR=arr; //设定计数器自动重装值//刚好1ms
TIM3->PSC=psc; //预分频器7200,得到10Khz的计数时钟
TIM3->DIER|=1<<0; //允许更新中断
TIM3->CR1|=0x01; //使能定时器3
MY_NVIC_Init(1,3,TIM3_IRQn,2);//抢占1,子优先级3,组2
}
5.2 定时器3中断服务程序
该部分程序在HARDWARE/timer.c/TIM3_IRQHandler(),主要作用是在定时器3中断溢出时进行LED1电平翻转,并清除SR寄存器中的中断标志位。具体代码如下所示:
cpp
//定时器3中断服务程序
void TIM3_IRQHandler(void)
{
if(TIM3->SR&0X0001)//溢出中断
{
LED1=!LED1;
}
TIM3->SR&=~(1<<0);//清除中断标志位
}
5.3 轮询主函数
该部分程序在USER/test.c,主要作用是初始化定时器3(设置超时时间为500ms),在主循环函数中不断反转LED0电平,以示程序运行。具体代码如下所示:
cpp
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "timer.h"
int main(void)
{
Stm32_Clock_Init(9); //系统时钟设置
uart_init(72,115200); //串口初始化为115200
delay_init(72); //延时初始化
LED_Init(); //初始化与LED连接的硬件接口
TIM3_Int_Init(4999,7199);//10Khz的计数频率,计数5K次为500ms
while(1)
{
LED0=!LED0;
delay_ms(200);
}
}
六、上机测试
将程序烧录至STM32F103ZET6,可见LED1按照500ms进行翻转(定时器中断),LED0按照200ms闪烁(轮询主函数)。具体效果如下所示:
至此完成通用定时器3中断DEMO!