定时器定时中断&定时器外部时钟
总线
- 总线分为AHB、APB1 和APB2 ,具体排布如图所示:

- AHB 处于顶层,是核心。
- APB2 和 APB1 则通过特定的桥接器连接到AHB总线上,专门负责管理所有外设.你可以将AHB想象成连接核心城区(CPU、内存)的“高速公路主干道”,而APB2和APB1则是从主干道延伸出去、通往不同功能区域的“城市道路”。
- 这样的设计实现了性能与功耗的平衡:高速组件在AHB上全速运行,而不同类型的外设则根据其速度需求挂载到不同的APB总线上,避免了低速外设拖累整体效率,也便于独立进行电源管理。
- AHB(高级高性能总线),是系统的“高速公路主干道”时钟频率最高,通常等于系统主频(SYSCLK),主要连接的外设是CPU内核、DMA控制器、内存(SRAM/Flash).
- APB1(低速外设总线),是连接普通低速外设的“普通街道”,时钟频率较低,通常是APB2的一半(最高36MHz),主要连接外设是通用定时器(TIM2-TIM5)、USART2/3、I2C、CAN
- APB2(高速外设总线),是连接重要高速外设的“城市快速路”,较高,通常为系统主频或其分频(最高72MHz),主要连接的外设是GPIO端口、ADC、高级定时器(TIM1/TIM8)、SPI1.
定时器概念原理
-
定时器基本框架图

-
定时器框架图相关概念

-
定时器分为三个,由基本定时器、通用定时器和高级定时器。
-
基本定时器(TIM6和TIM7),要接在APB1总线上,拥有定时中断、主模式触发DAC(数字模拟转换器,是将数字信号转换为模拟信号的电路)的功能。其结构图如下:

需要注意的是:
①预分频器(PSC)的分频数是实际想要的分频数-1,比如我想要预分频器将总脉冲信号分为2,则在预分频器的写入的值应为1,预分频器值是用16为二进制数表示,其范围是0~65535.
②自动重载寄存器(ARR)里面要我们规定一个值,在CNT计数器从0自增是不断与其比较,当CNT计数器的值与自动重载寄存器设定的值相同时,自动重载寄存器发出中断信号,然后将CNT计数器清零.
③图中CNT计数器右边的信号为更新中断和更新事件,更新中断将中断信号直接发送到NVIC,当配置好NVIC中断后可以直接响应中断.更新事件不会触发中断,但可以触发STM32内部其他电路的工作.
-
通用定时器(TIM2 TIM3 TIM4 TIM5),连接在APB1总线上,拥有基本定时器的全部功能和其他高级功能.结构图如下:

-
高级定时器(TIM2 TIM3 TIM4 TIM5),连接在APB2总线上,拥有基本定时器的全部功能和其他高级功能.结构图如下:

-
从上面的三种类型定时器的图片可以总结出一张定时器的框架图如下:

-
那么根据框架图可以得出初始化配置定时器的步骤,如图:
定时器定时中断
- 在文件夹System下新建一个Timer.c和一个Timer.h文件.
- 接线图如下:

Timer.c代码:
#include "stm32f10x.h" // Device header
//定时器初始化
void Timer_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//启用STM32自带的内部时钟,系统默认配置,可以不写
TIM_InternalClockConfig(TIM2);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 10000-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事 件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
//当接收的脉冲到达自动重装器(ARR)的阈值,就产生更新中断,要提前开启更新中断到NVIC的通路
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//配置NVIC
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure;
//配置TIM2的中断频道,IRQ:中断请求
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
//允许开启NVIC的中断请求
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//设置抢占优先级,中断很少,所以在0~3中给任意数字
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
//设置响应优先级,中断很少,所以在0~3中给任意数字
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
/*
//配置TIM2的启动函数
void TIM2_IRQHandler(void)
{
//检查是否是TIM2更新中断信号进入
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//一次中断,一次清楚,防止程序一直卡在中断里
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
*/
值得注意的是:
①TIM2是连接在APB1总线上的设备,所以在初始化函数时要先开启APB1的时钟.
②STM32内部自带时钟,这个时钟时默认的,写不写开启函数都会默认开启.
③配置时基单元时涉及结构体函数,在该结构体函数有一个配置计数模式的参数TIM_CounterMode,几种模式如图所示:
④配置时基单元时涉及预分频器参数TIM_Prescaler,该参数从0计数,设置为0即不分频.因为时钟的总脉冲是72MHZ,设置为7199,则把一个总脉冲信号(72MHZ)变化为10000HZ.
⑤配置时基单元时涉及自动重装器(ARR)参数TIM_Period,它也是从0开始计数,将其设置为9999HZ正好接收完预分频器变化后的10000HZ脉冲,计数器(向上计数模式,初始为0)在72MHZ的脉冲信号内从0自增到9999,然后清零再次自增到9999.具体关于预分频器和重装计数器的协作可观看该视频:预分频器和重装计数器的协作
⑥配置时基单元时涉及TIM_RepetitionCounter参数,这是高级计数器独有的功能.
⑦配置时基单元后若全部配置完毕(打通了通往NVIC的通道),则函数末尾,会在启动后立即产生了更新事件,至于为什么会这样,可能是因为**为了确保预分频器(PSC)和自动重载寄存器(ARR)的影子寄存器能立即生效,库函数主动触发了一次软件更新事件,这个事件会置位更新中断标志位(UIF).**要清除这次中断,可以使用TIM_ClearFlag(TIM2, TIM_FLAG_Update);函数.
main.c代码:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint8_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Time:");
while(1)
{
OLED_ShowNum(1,6,Num,6);
OLED_ShowNum(2,6,TIM_GetCounter(TIM2),5);
}
}
void TIM2_IRQHandler(void)
{
//检查是否是TIM2更新中断信号进入
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//一次中断,一次清楚,防止程序一直卡在中断里
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
定时器外部时钟
-
不再使用STM32自带的内部时钟,而是使用外部设备(对射式红外传感器)作时钟.
-
与定时器定时中断代码逻辑类似,在文件夹System下新建一个Timer.c和一个Timer.h文件.
-
接线图如下:

Timer.c代码如下:
#include "stm32f10x.h" // Device header
//定时器初始化
void Timer_Init(void)
{
//开启TIM2的时钟,TIM1(高级时钟)对频率要求高,连接在APB2;TIM2、TIM3、TIM4(通用时钟)连接在APB1
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
//初试化GPIOA,因为GPIOA 的0引脚接入了光敏变阻器
GPIO_InitTypeDef GPIO_InitStucture;
GPIO_InitStucture.GPIO_Mode = GPIO_Mode_IPU;
GPIO_InitStucture.GPIO_Pin = GPIO_Pin_0;
GPIO_InitStucture.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIOA,&GPIO_InitStucture);
//启用外部时钟控制
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);
//配置时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
//选择分频数,TIM_CKD_DIV1表示分频数为1,即不分频
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//选择计数器的计数模式,TIM_CounterMode_Up表示向上计数,即从0开始计数,到达预定值清零再次从零开始计数
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
/*设置自动重装器(ARR),因为预分频器设置为将7200个脉冲分频为1个脉冲,那么设置自动重装器为1000-1,
即1000HZ,7200*1000正好等于内部定时器频率72MHZ,此时一个脉冲信号72MHZ全部输入完毕,
正好过去1秒,自动重装器计时器就会清零*/
TIM_TimeBaseInitStructure.TIM_Period = 10-1;
//设置预分频器(PSC),选择预分频数,7200-1表示对72MHZ进行7200次分频,即将7200个脉冲分频为1个脉冲
TIM_TimeBaseInitStructure.TIM_Prescaler = 1-1;
//重复计数器,高级计数器(TIM1)才有,本计数器TIM2用不到
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);
/*中断输出配置*/
TIM_ClearFlag(TIM2, TIM_FLAG_Update); //清除定时器更新标志位
//TIM_TimeBaseInit函数末尾,手动产生了更新事件
//若不清除此标志位,则开启中断后,会立刻进入一次中断
//如果不介意此问题,则不清除此标志位也可
//当接收的脉冲到达自动重装器(ARR)的阈值,就产生更新中断,要提前开启更新中断到NVIC的通路
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);
//配置NVIC
//NVIC分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
//初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure;
//配置TIM2的中断频道,IRQ:中断请求
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
//允许开启NVIC的中断请求
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
//设置抢占优先级,中断很少,所以在0~3中给任意数字
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
//设置响应优先级,中断很少,所以在0~3中给任意数字
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
//启动定时器
TIM_Cmd(TIM2, ENABLE);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
/*
//配置TIM2的启动函数
void TIM2_IRQHandler(void)
{
//检查是否是TIM2更新中断信号进入
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//一次中断,一次清楚,防止程序一直卡在中断里
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
*/
值得注意的是:
①本次使用的是外部时钟,TIM2对应的外部时钟引脚为PA0,具体TIMx与引脚对应关系如图所示:
②既然使用了PA0为引脚,自然要使用GPIO初始化函数来初始化该引脚.
③既然本次使用的是STM32外部时钟,要声明一下开启外部时钟函数:TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x0F);最后一个参数表示滤波器采样频率,我为了使得在对射式红外传感器采得稳定次数,将此采样频率设置为最大.
main.c代码如下:
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint8_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Time:");
OLED_ShowString(2,1,"CNT:");
while(1)
{
OLED_ShowNum(1,5,Num,6);
OLED_ShowNum(2,5,Timer_GetCounter(),5);
}
}
void TIM2_IRQHandler(void)
{
//检查是否是TIM2更新中断信号进入
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)
{
Num++;
//一次中断,一次清楚,防止程序一直卡在中断里
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}


1591

被折叠的 条评论
为什么被折叠?



