三篇文章看懂STM32-TIM定时器(1)超详细介绍及练习源码
纲要
一、定时器基本定时功能,也就是定一个时间,然后让定时器每隔这个时间产生一个中断,来实现每隔一个固定时间执行一段程序的目的,例如做时钟、秒表时都需要一个定时中断的功能
二、定时器的输出比较功能(OC),产生PWM波形,用于驱动电机等设备
三、定时器的输入捕获功能(IC),实现测量方波频率
四、定时器的编码器接口,可以更方便的读取正交编码器的输出波形
定时器配置步骤
①RCC开启时钟,此时基准时钟和整个外设的工作时钟都被打开
②选择时基单元的时钟源(内部时钟模式)
③配置时基单元
④配置输出中断控制,允许更新中断输出到NVIC
⑤配置NVIC,打开中断通道,并分配优先级
⑥配置运行控制,需要对计数器使能
⑦最后再写定时器的中断函数
输出比较PWM配置步骤
①用RCC 把TIM外设与GPIO外设的时钟打开
②配置时基单元
③配置输出比较单元(CCR的值、输出比较模式、极性选择、输出使能)
④配置GPIO,PWM对应的GPIO配置为复用推挽输出
⑤运行控制
输入捕获配置步骤
①RCC开启TIM时钟和GPIO时钟
②GPIO初始化,配置成输入模式,一般为上拉输入/浮空输入
③配置时基单元,让CNT计数器在内部时钟的驱动下自增运行
④配置输入捕获单元(包括滤波器、极性、直连通道)
⑤选择从模式的触发源,选择TI1FP1
⑥选择触发执行的Reset操作
⑦调用TIM_cmd( )启动
定时器需要用到的库函数




-
TIM定时器介绍(基本功能)
-
1)TIM定时器简介
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断(定时触发中断)
16位计数器、预分频器(对计数器时钟进行分频,让计数更加灵活)、自动重装寄存器(计数的目标值,计N个时钟后申请中断)的时基单元,在72MHz计数时钟下可以实现最大59.65s(72M/65536/65536)的定时
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
主模式触发DAC的功能:能让内部的硬件在不受程序的控制下实现自动运行
2))基本定时器
PSC预分频器:实际分配系数=预分频器系数加1
基本定时器的功能和数量较少,一般用于DAC(数模转换)和DMA(直接存储器访问)且只能使用内部时钟
3)通用定时器
4)高级定时器
高级定时器比通用定时器多了关于电机驱动输出功能,目前阶段不需要掌握
5)定时中断基本结构图
6)预分频器时序
预分频控制寄存器和预分频缓冲器:在计数计到一半的时候改变了分频值,这个变化并不会立刻生效,而是会等到本次数周期结束时,产生了更新事件,预分频寄存器的值才会被传递到缓冲寄存器里面去,才会生效
7)计数器时序
计数器无预装时序
计数器有预装时序
8)RCC时钟树
9)定时器定时中断project
代码
Timer.c模块代码
#include "stm32f10x.h" // Device header
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
/*配置时钟源*/
TIM_InternalClockConfig(TIM2); //选择TIM2为内部时钟,若不调用此函数,TIM默认也为内部时钟
/*时基单元初始化*/
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的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
main.c模块代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num; //定义在定时器中断里自增的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Timer_Init(); //定时中断初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
while (1)
{
OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
}
}
/**
* 函 数:TIM2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
{
Num ++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
10)定时器外部时钟project
硬件接线图
代码
Timer.c模块代码
#include "stm32f10x.h" // Device header
/**
* 函 数:定时中断初始化
* 参 数:无
* 返 回 值:无
* 注意事项:此函数配置为外部时钟,定时器相当于计数器
*/
void Timer_Init(void)
{
/*开启时钟*/
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE); //开启GPIOA的时钟
/*GPIO初始化*/
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); //将PA0引脚初始化为上拉输入
/*外部时钟配置*/
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 0x0F);
//选择外部时钟模式2,时钟从TIM_ETR引脚输入
//注意TIM2的ETR引脚固定为PA0,无法随意更改
//最后一个滤波器参数加到最大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; //计数周期,即ARR的值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1; //预分频器,即PSC的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; //重复计数器,高级定时器才会用到
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure); //将结构体变量交给TIM_TimeBaseInit,配置TIM2的时基单元
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //开启TIM2的更新中断
/*NVIC中断分组*/
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2); //配置NVIC为分组2
//即抢占优先级范围:0~3,响应优先级范围:0~3
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在main函数内,while循环之前
//若调用多次配置分组的代码,则后执行的配置会覆盖先执行的配置
/*NVIC配置*/
NVIC_InitTypeDef NVIC_InitStructure; //定义结构体变量
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; //选择配置NVIC的TIM2线
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; //指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; //指定NVIC线路的抢占优先级为2
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; //指定NVIC线路的响应优先级为1
NVIC_Init(&NVIC_InitStructure); //将结构体变量交给NVIC_Init,配置NVIC外设
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:返回定时器CNT的值
* 参 数:无
* 返 回 值:定时器CNT的值,范围:0~65535
*/
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
}
/* 定时器中断函数,可以复制到使用它的地方
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
main.c模块代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num; //定义在定时器中断里自增的变量
int main(void)
{
/*模块初始化*/
OLED_Init(); //OLED初始化
Timer_Init(); //定时中断初始化
/*显示静态字符串*/
OLED_ShowString(1, 1, "Num:"); //1行1列显示字符串Num:
OLED_ShowString(2, 1, "CNT:"); //2行1列显示字符串CNT:
while (1)
{
OLED_ShowNum(1, 5, Num, 5); //不断刷新显示Num变量
OLED_ShowNum(2, 5, Timer_GetCounter(), 5); //不断刷新显示CNT的值
}
}
/**
* 函 数:TIM2中断函数
* 参 数:无
* 返 回 值:无
* 注意事项:此函数为中断函数,无需调用,中断触发后自动执行
* 函数名为预留的指定名称,可以从启动文件复制
* 请确保函数名正确,不能有任何差异,否则中断函数将不能进入
*/
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET) //判断是否是TIM2的更新事件触发的中断
{
Num ++; //Num变量自增,用于测试定时中断
TIM_ClearITPendingBit(TIM2, TIM_IT_Update); //清除TIM2更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
三篇文章看懂STM32-TIM定时器(2)超详细介绍及练习源码
三篇文章看懂STM32-TIM定时器(3)超详细介绍及练习源码
作者:L-22choi