目录
一:TIM
1:介绍
2:定时器的分类
3:基本定时器
4:通用定时器
5:高级定时器
6:定时器的基本结构
二:定时中断功能
A:定时器定时器中断
1:连接图
编辑
2:步骤
3:函数介绍
4:代码
三:外部时钟功能
A:定时器外部时钟
1:连接图
2:函数介绍
3:外部时钟代码
一:TIM
1:介绍
TIM(Timer)定时器
定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时 (计数器、预分频器、自动重装寄存器构成时基单元)
不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
2:定时器的分类
STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
3:基本定时器
1 : 预分频器+CNT计数器+自动重装载寄存器=时基单元
2 : 基本定时器只能选择内部时钟;他们都为16位的
预分频器 : 可以对72MHZ的计数时钟进行预分频处理; 对输入的基准频率提前进行一个分频的操作
eg : 预分频器写0,那就是不分频,或者说是1分频 ; 输出频率=输入频率=72MHz
预分频器写1,那就是2分频,输出频率(实际分频系数)=输入频率/2=36MHz
预分频器写2,那就是3分频,输出频率(实际分频系数)=输入频率/3= 24MHz
所以预分频器的值和实际的分频系数相差了1; 实际分频系数=预分频器的值+1
计数器 : 计数器可以对预分频后的计数时钟进行计数,预分频器每来一个上升沿计数器就+1
所以计数器的值在计时过程中会不断地自增运行,直到达到目标值(自动重装载寄存器)然后产生中断,然后在重新开始计数
自动重装载寄存器(固定值) : 储存的是我们的计数目标,产生中断的目标值,(当计数器达到目标值就产生中断)
流程: 基准时钟------->预分频器------>计数器<--------->自动重装载计数器
计数器不断自增,会和自动重装载寄存器比较,当两个的值相同时,产生更新中断和更新事件;
cpu会响应更新中断
4:通用定时器
5:高级定时器
6:定时器的基本结构
二:定时中断功能
A:定时器定时器中断
我们使用的是通用定时器TIM2在案列中(内部时钟)
1:连接图
2:步骤
1: 开启时钟 (RCC)
2: 选择时基单元的时钟 (TIM_InternalClockConfig--选择内部时钟)
3: 配置时基单元 (TIM_TimeBaseInit)
4 : 使能更新中断( TIM_ITConfig中断时钟控制)
5: NICV的配置 (见 02: STM32)
6: 启动定时器 (TIM_Cmd)
3:函数介绍
在stm32f10x tim.h文件中的函数-----时钟源选择函数 (选择时基单元的时钟)
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
TIM_InternalClockConfig : 选择内部时钟
TIM_ITRxExternalClockConfig : 选择TIR其他定时器的时钟
TIM_TIxExternalClockConfig : 选择TIx捕获通道的时钟
TIM_ETRClockMode1Config : 选择ETR通过外部时钟模式1输入的时钟
TIM_ETRClockMode2Config : 选择ETR通过外部时钟模式2输入的时钟
TIM_ETRConfig : 单独用来配置ETR引脚的预分频器、极性、滤波器这些参数的
在stm32f10x tim.h文件中的函数-------时基单元函数
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_TimeBaseInit : 时基单元初始化,;TIMX选择某个定时器; TIM_TimeBaseInitStruct:结构体包含了TIM配置的一些参数;
在stm32f10x tim.h文件中的函数-------中断输出控制函数
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
TIM_ITConfig : 使能中断输出信号
在stm32f10x tim.h文件中的函数-------运行控制函数函数
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
TIM_Cmd : 选择启动那个定时器, 选择使能还有失能
在stm32f10x tim.h文件中的函数-------单独修改初始化函数中的重要参数
不能为了某一个参数.直接重新初始化,太关于麻烦,直接更改某一个参数即可
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);
void TIM_SetAutoreload(TIM_TypeDef* TIMx, uint16_t Autoreload);
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);
TIM_PrescalerConfig : 单独写预分频值
TIM_CounterModeConfig : 改变计数器的计数模式
TIM_ARRPreloadConfig : 自动重装器预装功能配置
TIM_SetCounter : 给计数器写入一个值
TIM_SetAutoreload : 给自动重装器写入一个值
TIM_GetCounter : 获取当前计数器的值
TIM_GetPrescaler :获取当前的预分频器的值
在stm32f10x tim.h文件中的函数--其他函数
void TIM_DeInit(TIM_TypeDef* TIMx);
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
TIM_DeInit : 恢复缺省配置
TIM_TimeBaseStructInit : 结构体变量赋一个默认值
4:代码
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Timer.h"
int16_t Num;
extern int16_t Num;
void Timer_init(void){
//第一步是开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//第二步,选择时基单元的时钟 (stm23上电默认使用的是内部时钟,这一行代码可以省略)
TIM_InternalClockConfig(TIM2);
//第三步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
/*计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1
定时频率=72M/(PSC+1)/(ARR+1)
72MHZ=72000KHZ
72000KHZ/7200=10KHZ=10000HZ
T=1/F T=1/10000hz=0.0001s=0.1ms
然后以0.1ms的周期计10 000个数,所以就是1s
*/
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_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //手动清除更新中断标志位
//第五步NICV的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//第六步启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void TIM2_IRQHandler(){
//检查中断标志位
if ( TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
//清除标志位
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
int main(void)
{
OLED_Init();
Timer_init();
OLED_ShowString(1, 1, "Num:");
while (1)
{
OLED_ShowNum(1,5,Num,5);
}
}
为什么要清除中断标志位
响应中断条件是:中断使能和中断标志同时成立
单片机要靠查询中断标志来判断是否要进入中断,如果你不清除中断标志,本次中断退出,单片机又会检测到中断标志,因此重复进入中断
在STM32微控制器中,中断是一种重要的机制,用于响应外部事件或内部条件的变化。当一个中断事件发生时,相应的中断标志位会被置位(1),以表示中断事件已经发生。但是,在处理完中断之后,必须清除中断标志位(0),以确保下一次中断事件的正确触发。
清除中断标志位的主要目的有以下几个方面:
1. 防止重复触发:如果不清除中断标志位,当中断处理程序退出后,如果中断标志位仍然保持置位状态,可能会导致重复触发中断。这样会导致中断处理程序不停地执行,影响系统正常运行。
2. 确保正确的中断优先级:在STM32微控制器中,不同的外设和中断源具有不同的优先级。当多个中断源同时触发时,只有优先级最高的中断源会被处理。如果不清除中断标志位,可能会导致错误的中断源被处理,影响系统的功能和性能。
3. 确保正确的中断嵌套:STM32微控制器支持中断的嵌套执行。当一个高优先级的中断正在执行时,如果有一个更高优先级的中断进来,系统会自动挂起当前中断,转而执行更高优先级的中断。在挂起期间,中断标志位可能会保持置位状态。当更高优先级的中断执行完毕后,必须清除该中断的标志位,以便继续执行之前挂起的中断。
因此,为了确保中断系统的正确运行,必须在中断处理程序中清除相应的中断标志位。这可以通过写入相应的寄存器或调用相应的函数来实现。
时间的计算
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1
定时频率=72M/(PSC+1)/(ARR+1)首先72M进行7200分频,得到是10K的计数频率
在10K频率下,记10000个数,就是1s的时间
Arr是自动重装 psc预分频
方表明最大值是0-65535。-1表示有偏差
频率的单位是Hz; 周期的单位是s;
Hz<KHz<MHz; 都是千进的
s秒,ms毫秒,us微秒,ns纳秒; 都是千进的
72M进行7200分频,得到10KHz的计数频率。
T=1/F 所以T=1/10 000hz = 0.0001s = 0.1ms
然后以0.1ms的周期计10 000个数,所以就是1s
72MHZ=72000KHZ
72000KHZ/7200=10KHZ=10000HZ
T=1/F T=1/10000hz=0.0001s=0.1ms
然后以0.1ms的周期计10 000个数,所以就是1s
三:外部时钟功能
A:定时器外部时钟
1:连接图
2:函数介绍
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
TIM_GetCounter : 它用于获取定时器计数器的当前值
TIM_ETRClockMode2Config : 选择ETR通过外部时钟模式2输入的时钟
3:外部时钟代码
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
extern int16_t Num;
void Timer_init(void){
//第一步是开启时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef A;
A.GPIO_Mode=GPIO_Mode_IPU; //上拉
A.GPIO_Pin=GPIO_Pin_0;
A.GPIO_Speed=GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&A);
//第二步,选择时基单元的时钟 (stm23上电默认使用的是内部时钟,这一行代码可以省略)
//TIM_ExtTRGPolarity_NonInverted 高电平或者上升沿有效
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0f);//选择ETR通过外部时钟模式2输入的时钟
//第三步,配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision=TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数
/*计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1
定时频率=72M/(PSC+1)/(ARR+1)
72MHZ=72000KHZ
72000KHZ/7200=10KHZ=10000HZ
T=1/F T=1/10000hz=0.0001s=0.1ms
然后以0.1ms的周期计10 000个数,所以就是1s
*/
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_ITConfig(TIM2, TIM_IT_Update, ENABLE);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//手动清除更新中断标志位
//第五步NICV的配置
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStructure);
//第六步启动定时器
TIM_Cmd(TIM2,ENABLE);
}
void TIM2_IRQHandler(){
//检查中断标志位
if ( TIM_GetITStatus(TIM2,TIM_IT_Update)==SET)
{
//清除标志位
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
uint16_t Timer_GetCounter(void){
//TIM_GetCounter。它用于获取定时器计数器的当前值。
return TIM_GetCounter(TIM2);
}
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_init();
OLED_ShowString(1, 1, "Num:");
OLED_ShowString(2, 1, "CNT:");
while (1)
{
OLED_ShowNum(1, 5, Num, 5);
OLED_ShowNum(2, 5, Timer_GetCounter(), 5);
}
}
本实验实验了外部时钟(对外式红外传感计数器),当对外式红外传感计数器达到了某个数值是触发定时器