一、STM32F103定时器分类及区别
共有8个定时器,它们是:TIM1~TIM8。STM32的定时器分为基本定时器、通用定时器和高等定时器。
TIM6、TIM7(基本定时器):基本定时器是只能向上计数的16位定时器,基本定时器只能有定时的功能,没有外部IO口,所以没有捕获和比较通道。
TIM2、TIM3、TIM4、TIM5(通用定时器):通用定时器是可以向上计数,也可以向下计数的16位定时器。通用定时器可以定时、输出比较、输入捕捉,每个通用定时器具有4个外部IO口。
TIM1、TIM8(高级定时器):高级定时器是是可以向上计数,也可以向下计数的16位定时器。高等定时器可以定时、输出比较、输入捕捉、还可以输出三相电机互补信号,每个高等定时器有8个外部IO口。
二、通用定时器主要功能
通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意
数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
通用定时器计数模式
向上计数,向下计数,中心对齐
三、通用定时器工作过程
从时钟源产生框可以看到
定时器时钟有四种来源:
● 内部时钟(CK_INT)
● 外部时钟模式1:外部输入脚(TIx) (输入捕获的引脚)
● 外部时钟模式2:外部触发输入(ETR)
● 内部触发输入(ITRx):(定时器级联)使用一个定时器作为另一个定时器的预分频器,如可以配置一个定时器Timer1而作为另一个定时器Timer2的预分频器。
产生CK_PSC时钟,然后在从模式控制器内设置好计数模式(向上向下),再经过预分频器产生CK_CNT,若为CNT计数器向下计数,则当其计数到0时,自动重装载寄存器会重新为CNT计数器装载新值重新递减计数,并产生一个更新事件。
时基单元:
● 计数器寄存器(TIMx_CNT)
● 预分频器寄存器 (TIMx_PSC)
● 自动装载寄存器 (TIMx_ARR)
输入捕获:信号通过捕获通道进入,经滤波,经分频,到捕获比较寄存器。(经输入捕获与输出比较的四个通道其实是一个)
四、时钟源配置(选内部时钟CK_INT)
由以上两张图,我们知道CK_CNT时钟来源于APB1,并且如果APB1预分频系数如果不为1则CK_INT=APB1*2(单位是频率),然后设置好预分频系数CK_PSC为N,得CK_CNT=CK_INT / (N+1)。
CK_INT到底是多少呢?
那以STM32F103RBT6为例,打开system_stm32f10x.c文件找到SystemInit函数,其注释如下:
翻译:设置系统时钟(系统时钟源、PLL倍增器因子、AHB/APBx预分频器和闪存设置)
找到函数,我们为其一一注释:
void SystemInit (void)
{
/* 使能内部高速时钟 */
RCC->CR |= (uint32_t)0x00000001;
/* Reset SW, HPRE, PPRE1, PPRE2, ADCPRE and MCO bits */
#ifndef STM32F10X_CL//没有define这个玩意
RCC->CFGR &= (uint32_t)0xF8FF0000;//这5行是复位个个时钟用的,not define STM32F10X_CL,比如SW设为00即HSI作为系统时钟
#else
RCC->CFGR &= (uint32_t)0xF0FF0000;
#endif /* STM32F10X_CL */
/* Reset HSEON, CSSON and PLLON bits */
RCC->CR &= (uint32_t)0xFEF6FFFF;//把HSE关闭,时钟监测器,PLL关闭。
/* Reset HSEBYP bit */
RCC->CR &= (uint32_t)0xFFFBFFFF;//HSE没有被旁路
/* Reset PLLSRC, PLLXTPRE, PLLMUL and USBPRE/OTGFSPRE bits */
RCC->CFGR &= (uint32_t)0xFF80FFFF;//HSI振荡器时钟经2分频后作为PLL输入时钟
//PREDIV1对输入时钟进行2分频
//PLL倍频系数保留
//PLL VCO时钟(2xPLLCLK)被除以3(必须配置PLL输出为72MHz);
/* 禁用时钟中断寄存器 */
#ifdef STM32F10X_CL
/* Reset PLL2ON and PLL3ON bits */
RCC->CR &= (uint32_t)0xEBFFFFFF;
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x00FF0000;
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#elif defined (STM32F10X_LD_VL) || defined (STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;//禁用时钟中断寄存器
/* Reset CFGR2 register */
RCC->CFGR2 = 0x00000000;
#else
/* Disable all interrupts and clear pending bits */
RCC->CIR = 0x009F0000;
#endif /* STM32F10X_CL */
/* 设置外部SRAM,这个不太懂,参考链接2 */
#if defined (STM32F10X_HD) || (defined STM32F10X_XL) || (defined STM32F10X_HD_VL)
#ifdef DATA_IN_ExtSRAM
SystemInit_ExtMemCtl();
#endif /* DATA_IN_ExtSRAM */
#endif
/* 配置系统时钟 */
SetSysClock();
/*中断向量偏移地址设置,不懂 */
#ifdef VECT_TAB_SRAM
SCB->VTOR = SRAM_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal SRAM. */
#else
SCB->VTOR = FLASH_BASE | VECT_TAB_OFFSET; /* Vector Table Relocation in Internal FLASH. */
#endif
}
[注]: 这里STM32F10X_CL宏定义问题,这玩意没有被定义,参考链接1,链接2.
上头函数SystemInit ()的功能主要是复位与时钟相关的寄存器,并执行SetSysClock()。
#if defined (STM32F10X_LD_VL) || (defined STM32F10X_MD_VL) || (defined STM32F10X_HD_VL)
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
#define SYSCLK_FREQ_24MHz 24000000
#else
/* #define SYSCLK_FREQ_HSE HSE_VALUE */
/* #define SYSCLK_FREQ_24MHz 24000000 */
/* #define SYSCLK_FREQ_36MHz 36000000 */
/* #define SYSCLK_FREQ_48MHz 48000000 */
/* #define SYSCLK_FREQ_56MHz 56000000 */
#define SYSCLK_FREQ_72MHz 72000000
#endif
static void SetSysClock(void)
{
#ifdef SYSCLK_FREQ_HSE
SetSysClockToHSE();
#elif defined SYSCLK_FREQ_24MHz
SetSysClockTo24();
#elif defined SYSCLK_FREQ_36MHz
SetSysClockTo36();
#elif defined SYSCLK_FREQ_48MHz
SetSysClockTo48();
#elif defined SYSCLK_FREQ_56MHz
SetSysClockTo56();
#elif defined SYSCLK_FREQ_72MHz//文件中这个一般高亮
SetSysClockTo72();
#endif
/* If none of the define above is enabled, the HSI is used as System clock
source (default after reset) */
}
由此部分代码可知,默认状态下,在函数SetSysClock()中将会执行函数SetSysClockTo72()。关于这个函数详细信息参考链接3,主要关注一下
- 设置HCLK,HCLK = SYSCLK
- 设置PCLK2,PCLK2 = HCLK
- 设置PCLK1,PCLK1 = HCLK / 2(即APB1预分频系数为2,不为1的哟!!!)
- 设置PLL时钟来源及PLL倍频因数
- 选择PLL作为系统时钟源,即PLLCLK = SYSCLK
到现在终于真相大白:CK_CNT=CK_INT /(N+1)=APB1* 2 /(N+1)=PCLK1 * 2 /(N+1)= HCLK /(N+1)= 72M / (N+1)(这里的N指PSC预分频器的预分频系数)。
五、通用定时器溢出中断配置步骤
void MSTimerInit(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);//定时器时钟
TIM_TimeBaseStructure.TIM_Prescaler = 35999;//设置预分频系数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;//上升沿计数
TIM_TimeBaseStructure.TIM_Period = 0xFFFF; //定时器重装载值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
/*先甭管这个
定义在定时器时钟(CK_INT)频率与数字滤波器(ETR,TIx)
使用的采样频率之间的分频比例,数字滤波器(ETR,TIx)
是为了将ETR进来的分频后的信号滤波,保证通过信号
频率不超过某个限定
*/
TIM_TimeBaseStructure.TIM_RepetitionCounter = 0; //这个高级定时器才用
TIM_DeInit(TIM3);//可有可无,网友说有了可以增加程序健壮性,有待考究
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseStructure);//定时器初始化
TIM_ITConfig( //使能或者失能指定的TIM中断,没中断没必要写
TIM3, //TIM3
TIM_IT_Update ,
ENABLE //使能
);
NVIC_InitStructure.NVIC_IRQChannel = TIM3_IRQn; //TIM3中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0; //先占优先级0级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 3; //从优先级3级
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //IRQ通道被使能
NVIC_Init(&NVIC_InitStructure); //根据NVIC_InitStruct中指定的参数初始化外设NVIC寄存器
TIM_Cmd(TIM3, ENABLE); //使能定时器TIM3
对于上面这段代码,定时器的溢出时间为=(重装载值+1)x(分频系数+1)/72M=(65535+1) x (35999+1) / 72M=65536 x 0.5ms