目录
前言
使用平台:STM32F407ZET6
使用数据手册:STM32F407数据手册.pdf
使用参考手册:STM32F4xx参考手册(中文).pdf
使用cortex-M手册:Cortex M3与M4权威指南.pdf
定时器的作用一般是为了使用定时功能和中断功能(洗衣机、微波炉、电风扇、智能空调......),当然在STM32中也可以利用定时器产生周期性的脉冲信号来控制不同的外设(电机的转速、舵机的角度、灯光的亮度........),所以掌握STM32中的定时器对于项目开发是很有必要的。
定时器有多种,常用的外设定时器有基本定时器、通用定时器与高级定时器。 对于STM32F407微处理器而言,内部一共集成了14 个定时器,其中有2个基本定时器(TIM6和TIM7)、10个通用定时器(TIM2~TIM5、TIM9~TIM14)、2个高级定时器(TIM1和TIM8)。其中通用定时器TIM2和TIM5为32位定时器,其他为16位定时器,当然,定时器位数越大,定时时间越久。2的32次方--4 294 967 296,2的16次方65 536。

基本定时器概念
本文使用的是基本定时器,基本定时器具有16位 的自动重载计数器(TIM_Period)与16位 的预分频器(TIM_Prescaler)。并且自动重载计数器只能递增计数( TIM_CounterMode_Up**),发生更新事件(** TIM_IT_Update**)会生成中断/DMA请求(计数器上溢)。**
定时时间
定时时间需要三个参数决定,定时器的时钟频率、预分频器的值与自动重载计数器的值。

这里我提供一下我写的自动计算填的值代码(这个只匹配16位的定时器哦,32位的不一定适用哦):
.\main.exe 84 1 8400这样使用即可,第一个参数84是定时器的频率以MHZ为单位,第二个参数是需要定时的时间,这里以秒为单位,第三个参数可以选择填自己确定的预分配值或者自动重载值即可。这里可以得到匹配的自动重载值或者预分频值(在正式填的时候,不要忘记减1)
cpp
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
/**
* @brief 获得分频系数或者计数器
*
* @param argc
* @param argv
* @return int
*/
int main(int argc, char const *argv[])
{
if (argc != 4) // 需要两个参数:时间 和 预分频
return -1;
int clock = atoi(argv[1])*1000000; // 84 MHz 定时器时钟
float tim_sec = atof(argv[2]); // 期望的定时秒数
int prescaler = atoi(argv[3]); // 预分频值
// 计算 ARR(自动重装载值)
int result = (int)(((float)tim_sec * clock) / ((float)prescaler));
if (result > 0 && result < 65536)
printf("%d\n", result);
else
printf("Out of range\n");
return 0;
}
定时器时钟确定
我的板子芯片是STM32F407ZET6,最大支持的频率是168MHz,通过锁相环分频器(PLLCLOCK)可以将外部高速时钟(HSE)的8MHz转化为168MHz,这里我们通过时钟树来分析。


我们的定时器的频率不是单纯的像总线一样有84MHz或者42MHz限制,这里面的关键就是。

如果设置了板子的频率为168MHz,并且设置了非1的的分频系数,那么定时器的时钟频率是总线的频率的两倍。
倍频锁相环被正确配置为168MHz
如果在标准库中设置倍频锁相环被正确配置为168MHz。我们其实只要修改两个地方即可,其一外部晶振的正确频率,其一PLL_M

对应代码处为:
我后面会写一篇文章专门介绍为什么这样就可以设置系统时钟为168MHz。可以期待一波。
1.system_stm32f4xx.c文件修改PLL_M值8MHz晶振即为8b

2.stm32f4xx.h文件修改HSE_VALUE,8MHZ晶振即为8000000

定时器的库函数操作
代码

本质使用了若干函数
cpp
RCC_APBxPeriphClockCmd(RCC_APBxPeriph_TIMx, ENABLE)
TIM_TimeBaseInit(TIMx, &TIM_TimeBaseInitStruct)
NVIC_Init(NVIC_InitTypeDef* NVIC_InitStruct)
TIM_ITConfig(TIMx, TIM_IT_Update)
TIM_Cmd(ENABLE)
这里我使用了基本定时器的TIM7,通过定时器的定时中断操作,把led翻转。
cpp
void TimerBaseInit(void)
{
// 开启定时器时钟
/* 开启TIM6与TIM7基本定时器时钟 84MHz*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM7, ENABLE);
// 为每个定时器赋值
/* 基本定时器TIM7 800ms 84MHz*/
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 42000 - 1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 1600 - 1;
TIM_TimeBaseInit(TIM7, &TIM_TimeBaseInitStruct);
/* TIM7 */
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel = TIM7_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0xf;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
TIM_ITConfig(TIM7, TIM_IT_Update, ENABLE);
TIM_Cmd(TIM7, ENABLE);
}
定时中断函数:
cpp
void TIM7_IRQHandler(void)
{
if (TIM_GetITStatus(TIM7, TIM_IT_Update))
{
TIM_ClearITPendingBit(TIM7, TIM_IT_Update);
GPIO_ToggleBits(GPIOF, GPIO_Pin_10);
}
}
定时器的寄存器操作
代码

寄存器初始化:
cpp
void TimerBaseRegisterInit(void)
{
//开启定时器时钟
RCC->APB1ENR |= (0b11<<4); //TIM6、TIM7
RCC->APB2ENR |= (0b11<<16); //TIM9、TIM10
//配置定时器
/* 配置定时器前先关闭定时器,即关闭计数器 */
TIM6->CR1 &= ~(1 <<0 );
/* 配置定时器参数 */
TIM6->PSC = 10000-1; //分频值 10000
TIM6->ARR = 8400 - 1; // 对应分频器10000
TIM6->CNT = 0; // 清0计数器,个人认为自动重装初值应该从0开始
TIM6->CR1 |= (0b10000100);
TIM6->DIER |= (0b1);
//配置NVIC
NVIC->ISER[1] = 1u << (TIM6_DAC_IRQn %32);
//配置完成定时器,开启计数器
TIM6->CR1 |= (1 << 0);
}
定时中断函数:
cpp
void TIM7_IRQHandler(void)
{
if (TIM7->SR == 1)
{
TIM7->SR = 0;
GPIO_ToggleBits(GPIOF, GPIO_Pin_10);
}
}
寄存器
这里提供基本定时器使用的寄存器:
启动定时器时钟寄存器:

控制寄存器CR1:

寄存器配置寄存器,搭配使用

中断使能寄存器

状态寄存器

配置NVIC使能中断寄存器

