定时器在STM32中可以说是最复杂的了,麻烦!
定时器分类:基本定时器,通用定时器,高级定时器
基本定时器:拥有定时中断,主模式触发DAC的功能
通用定时器拥有基本定时器全部功能,并有格外的内部时钟源选择,输入捕获,输出比较,编码器接口,主从触发模式
高级定时器:拥有通用定时器全部功能,格外还有重复计数器,死区生成,互补输出,刹车输入等功能
基本定时器:
其中最重要的是时基单元:由预分频器,计数器,自动重装载寄存器三个部分构成时基单元
预分频器的时钟是由基准计数时钟的输入的
基准计数时钟又是从内部时钟CK_INT经过控制器来的(基本定时只能选择内部时钟)
内部时钟CK_INT又是由系统主频TRCC_IM_CLK决定的
预分频器:(16位)
把这个看成一根火腿肠,写0就是切0刀
写1就是切1刀,切一刀就是两节,这里分频也是一样的写0就是不分频,写1就是2分频,依次内推
分频系数可以是0-65535
自动重装载寄存器(16位)
这就是我们要计数到多少所填的值和下面计数器相关联的,我想让它计数到100就重新开始计数那么就填100
计数器(16位)
因为我们输入的时钟实际就是一个方波一样的高低变化着的波形,计数器呢捕获到1个上升沿计数+1,直到计数到我们的自动重装载寄存器所设置的值,那么就会产生一个中断信号(跟新中断)把计数器置0重新开始计时(从0开始持续计数到一定数又清0重新开始这个我们一般叫做向上计数模式)。大概执行过程就是这样子的。
主模式触发DAC:
提供跟新事件把主模式映射到TRGO,然后TRGO就直接触发DAC,这点我也没搞懂下次补上
通用定时器:
我看着都头疼,既然头疼就算了吧
接下来对TIM 常用函数介绍
void TIM_DeInit(TIM_TypeDef* TIMx);
调用该函数会清除TIM的配置和之前讲到的中断一样
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
这个函数配置时基单元的,参数1时选择定时器几TIMx,参数2是一个结构体地址,初始化结构体的成员后把结构体传入进来
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
这个函数是使能定时器的,第一个参数就是选择定时器TIMx,第二个参数就是使能或者不使能
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
这个函数是用来使能中断输出信号的,参数1选择定时器,第二个选择要配置哪个中断输出,第三个参数是能还是不使能
接下来是对时钟的配置
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
选择内部时钟
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
选择ITRx其他定时器的时钟 参数1是定时器几,参数2选择要接入哪个其他的定时器
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
选择TIx捕获通道的时钟,第一个参数:定时器几,第二个参数:选择TIx具体的某个引脚,最后两个参数是极性和滤波器
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
选择ETR1通过外部时钟模式1输入的时钟
参数1:选择定时器几,参数2:可以对外部时钟再做一个分频,参数3和4也是极性和滤波器
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
选择ETR2通过外部时钟模式2输入的时钟,参数和上边哪个ETR1一样的
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
uint16_t ExtTRGFilter);
这个函数用于单独配置ETR的预分频器,极性,滤波器这些
更改计数器模式
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
参数1:定时器几,参数2:选择新的计数器模式
更改预分频置函数
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
这个函数用来单独写预分频值的,第一个参数:定时器几,第而个参数:要写入的预分频值,第三个参数:写入的模式
自动重装器预装功能配置
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);
获取当前预分频的值
标志位的获取和清除
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
检查指定的 TIM 标志位设置与否
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
清除TIM 标志位
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
检查指定的 TIM 中断发生与否
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
清除中断标志位
/*********************************************************************************************************/
代码实现:还是一步步来(通用定时器TIM2挂载到APB1的)
首先从结构框图可以看出TIM1是在APB2总线上的,我们反其道而行之选择APB1上面的TIM2所以我们要初始化APB1的时钟
1.开启时钟
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
}
2.配置时基单元时钟 (我们选择内部时钟)也可以选择其他时钟参考最上面的时钟配置
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置
TIM_InternalClockConfig(TIM2);
}
3.初始化时基单元结构体
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元 先初始化结构体
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
}
4. 选择分频一般是1分频
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//选择分频一般是1分频
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
}
5.计数模式
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//选择分频一般是1分频
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//计数模式 向上计数
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
}
6.配置三个时基单元的重要寄存器,把结构体传入进去
这里补充一下:
赫兹代表的是运动的频率,1s就是1hz代表1秒运动一次,10k赫兹代表1秒运动10000次
注意哈!既然10k赫兹代表1秒运动10000次,那么10k赫兹频率下运动一次是多少呢?
1s=1000 ms=1000000 us;
1000000us/10k赫兹(10000)=100us(也就是运动一次是100us)
我没有刻意去记住那些换算单位,这是我自己配置时候用的方法大家可以参考一下。
再举个例子我如果定时500ms该咋个操作呢?
一样的还是采用改一下计数周期就行了,既然运动一次是100us,我运动5000次不就是500ms吗
我运动10000次不就是1s咯
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//选择分频一般是1分频
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//计数模式
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
//分频为7200也就是分频后是10k赫兹 每间隔100us计数加1
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
//加到10000次后,也就10000*1000us,也就是1s后清零重新开始
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
//RepetitionCounter是重复计数器的值 高级定时器才有
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
}
7.更新中断(去tim.h里面找)
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//选择分频一般是1分频
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//计数模式
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
//RepetitionCounter是重复计数器的值 高级定时器才有
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
}
8.配置NVIC并启动定时器(我上一章EXTI里面有详细讲解如何配置NVIC)
void Time_Init(void)
{
//开启APB1时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//选择时基单元的时钟 注意这里是配置时钟 (我们选择内部时钟)
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct;
//选择分频一般是1分频
TIM_TimeBaseInitStruct.TIM_ClockDivision=TIM_CKD_DIV1;
//计数模式
TIM_TimeBaseInitStruct.TIM_CounterMode=TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period=10000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler=7200-1;
//RepetitionCounter是重复计数器的值 高级定时器才有
TIM_TimeBaseInitStruct.TIM_RepetitionCounter=0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//清除标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//更新中断
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//配置NVIC
//先分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStruct;
NVIC_InitStruct.NVIC_IRQChannel=TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd=ENABLE;
//优先级
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority=1;
NVIC_InitStruct.NVIC_IRQChannelSubPriority=1;
NVIC_Init(&NVIC_InitStruct);
//启动定时器
TIM_Cmd(TIM2,ENABLE);
}
9.去启动文件里面找到中断函数名称
void TIM2_IRQHandler(void)
{
//判断中断标志位是否置为1
if(TIM_GetFlagStatus(TIM2,TIM_FLAG_Update)==1)
{
//清除标志位
TIM_ClearFlag(TIM2,TIM_FLAG_Update);
//填自己要执行的代码
}
}
10.补充
extern :表示该变量外部已经声明过了,是外部变量
下面是实际运行效果
进入中断LED翻转一次(10ms)
每隔500ms进入一次TIM中断