STM32定时器中断学习笔记(内部时钟和外部时钟模式2)
这个溢出动作就是在本来设置好的参数上再计数一次,因此我们在设置数据的时候减1就刚好是我们设置好的时间间隔。这个有点像51单片机定时器0中断的三个标志位EA,TR0,TF0,分别是中断使能,定时器0使能,定时器0溢出标志位。这个函数判断了TIMx的中断使能标志位以及中断更新标志位是否纯在即DIER->UIE SR->UIF是否都被置1了。这里有两个中断标志位,一个是中断使能标志位,一个是中断发生指示
注:资料来着B站江协科技
首先看一下定时器配中断置过程

学习中遇到的一些问题:
- STM32外部时钟(ETR)分频设置有个小BUG,目前不知道原因。以江协科技6-1源码把外部时钟的分频系数设置为2或者4,初始化后光耦的第1次遮挡就会使CNT加1,即从0变成1(按照程序要求来说应该是要2次或者4次才对),后续都要2次或者4次遮挡CNT才会加1,。对TIM2的CNT寄存器的观察初始化后是0x0000,遮挡一次后变成 0x0001,后续的都需要2次或者4次才加1,
/*外部时钟配置*/
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_DIV2, TIM_ExtTRGPolarity_NonInverted, 0x0F);
//选择外部时钟模式2,时钟从TIM_ETR引脚输入
//注意TIM2的ETR引脚固定为PA0,无法随意更改
//最后一个滤波器参数加到最大0x0F,可滤除时钟信号抖动
- 当外部时钟中断分频后,它容易受干扰,用万用表的表笔(无源的表笔去碰触也会发生干扰前提是光耦一直挡住了)去测量GPIO的端口电压就会使CNT增加,但是如果不分频它就能不受干扰。
看一个程序是基于内部时钟的定时中断,定时器是Timer2 定时时间是1s,分析每句控制的寄存器是哪些
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
//此分组配置在整个工程中仅需调用一次
//若有多个中断,可以把此代码放在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,定时器开始运行
}
来分析一下用到了哪些寄存器。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE); //开启TIM2的时钟

TIM_InternalClockConfig(TIM2);
该句的意思是禁用了从模式控制寄存器SMCR,选择内部时钟源TIM2。
查看库函数的源代码是
void TIM_InternalClockConfig(TIM_TypeDef* TIMx)
{
/* Check the parameters */
assert_param(IS_TIM_LIST6_PERIPH(TIMx));
/* Disable slave mode to clock the prescaler directly with the internal clock */
TIMx->SMCR &= (uint16_t)(~((uint16_t)TIM_SMCR_SMS));
}
#define TIM_SMCR_SMS =0111 ((uint16_t)0x0007)
取反 (~((uint16_t)TIM_SMCR_SMS)) = 1000 则SMS被置为000
注释说不写该句也可以是因为该寄存器复位默认的值就是0x0000,即TIMx寄存器的SMS的默认值就是0x00(SMCR 的全称通常是 Slave Mode Control Register)


我们看一下这个库函数的说明是/* Disable slave mode to clock the prescaler directly with the internal clock */禁用从模式

当然这里的CR1的CEN不是该语句使能的是后面的语句TIM_Cmd( TIM2, ENABLE);
SMS(slave modle selection)
/*时基单元初始化*/
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_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV2 ;//用于输入捕获的滤波,不影响定时器基础计时,这里写1也是一样


内部时钟这里的设置是不起作用的,
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up; //计数器模式,选择向上计数
由控制寄存器TIM2_CR1的CMS(Center_aligned mode selection)控制 CMS :00



设置定时参数定时1s,
TIM_TimeBaseInitStructure.TIM_Period = 10000-1;
TIM_TimeBaseInitStructure.TIM_Prescaler =7200-1 ;
0x270F = 9999 0x1C1F = 7199
ARR(auto reload vaule)


这里设置都减1的原因是因为硬件逻辑上都是需要+1次才能输出一次数据,以51单片机来说就是寄存器溢出才动作一次。这个溢出动作就是在本来设置好的参数上再计数一次,因此我们在设置数据的时候减1就刚好是我们设置好的时间间隔。
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
这句是高级寄存器设置才会用到的


TIM_ClearFlag(TIM2, TIM_FLAG_Update);
UIF(update interrupt flag)



这个函数的功能就是把UIF位置0
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
DMA(DMA/Interrupt Enable Register)


UIE(updata interrupt enable)
这里有两个中断标志位,一个是中断使能标志位,一个是中断发生指示标志位,要注意区别。
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行

这个是定时器2使能标志位

这个有点像51单片机定时器0中断的三个标志位EA,TR0,TF0,分别是中断使能,定时器0使能,定时器0溢出标志位。这里就是DIER->UIE CR1->CEN SR->UIF这三者都属于TIM2的控制位
后面的部分NVIC配置上一篇有提到就不赘述
主函数部分:
#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更新事件的中断标志位
//中断标志位必须清除
//否则中断将连续不断地触发,导致主程序卡死
}
}
TIM_GetITStatus(TIM2, TIM_IT_Update) == SET
这个函数判断了TIMx的中断使能标志位以及中断更新标志位是否纯在即DIER->UIE SR->UIF是否都被置1了。
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);


大雾看不懂具体逻辑什么意思,不过因该是把TIM2->SR->UIF置零0,至于其它位怎么样了不知道看不懂。按理说其它位因该保持不变的。
这个和51的中断不一样,51的定时器中断标志位TFx,进入中断后会自动硬件置0.这个需要软件置0.
基于外部中断模式2的,定时器TIM2外部中断设置
#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_DIV4, 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外设
TIM2->PSC = 0x00; // 直接操作寄存器:ml-citation{ref="2" data="citationList"}
/*TIM使能*/
TIM_Cmd(TIM2, ENABLE); //使能TIM2,定时器开始运行
}
/**
* 函 数:返回定时器CNT的值
* 参 数:无
* 返 回 值:定时器CNT的值,范围:0~65535
*/
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2); //返回定时器TIM2的CNT
}
/* 定时器中断函数,可以复制到使用它的地方
其它地方前面的都有涉及故略。。。
/*外部时钟配置*/
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_DIV4, TIM_ExtTRGPolarity_NonInverted, 0x0F);

0x02 = 0010
0x0F = 1111



使用GPIO的外部时钟模式2在这里没有设置TS,也没有设置SMS。

至此本篇结束。
更多推荐



所有评论(0)