在定时器中最核心的部分是时基单元。图中的“运行控制”就是控制寄存器的一些位,用来启动或停止计数器,配置向上向下计数方式等,操作这些寄存器就能控制时基单元的运行了。关于时钟源选择的部分,在上文都有详细的叙述,这里不再赘述。
计时时间到后,产生的中断信号会先在状态寄存器中置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。这个中断输出控制存在的原因是:定时器模块有很多地方都要申请中断,如果需要该中断,就允许输出;如果不需要这个中断,就禁止输出。简单来说,中断输出控制就是中断输出的允许位。
2.2 时基单元运行时序举例
STM32中,关于时序运行的内容很多,具体请见手册的详细讨论,这里仅举一些时基单元的例子作简要分析。
2.2.1 缓冲(影子)寄存器
STM32在设计之初,为了保证能适用于多种多样的情况,故对时序运行过程中突然手动更改寄存器对时序的影响作了严谨的设计。这里引入缓冲(影子)寄存器,主要目的就是同步,即可以让寄存器设定的某些目标值的变化和更新事件同时发生,防止在运行途中更改造成错误。在定时器结构图中,有些寄存器的画法采用了方框下加阴影的方式,就说明该寄存器不是只有一个寄存器,而是有两个寄存器来形成缓冲机制。实际上,真正使时序电路状态发生更改的都是影子寄存器。
2.2.2 预分频器时序分析
计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
上图描述了当预分频器的分频系数从1变为2时,计数器的时序图。当计数器使能信号CNT_EN变为高电平后的下一个CK_PSC的高电平,定时器时钟CK_CNT接收CK_PSC。且此时预分频器的分频系数为1,PSC = 0,预分频器完成一分频,CK_PSC = CK_CNT。
可以看到,当计数器寄存器的值依次递增达到0xFC后立即跳变为0x00,说明重装载寄存器ARR设计的目标计数值就是0xFC,此时电路产生一个更新事件脉冲信号UEV,并产生中断信号。
在更新事件信号之前在TIMx_PSC中写入新数值,将预分频器的分频系数从1改为2,但是由于缓冲寄存器的存在,CK_CNT不会立即变为CK_PSC / 2,而是在下一次更新中断产生的同时,由预分频缓冲器(影子寄存器)修改分频系数为2,PSC = 1。
由预分频计数器时序可以看到,预分频的分频功能实际上也是通过计数器来实现的。当分频系数变为2后,预分频计数器按0、1、0、1依次计数,每当预分频计数器回到0时,预分频器输出信号CN_CNT输出一个脉冲。
2.2.3 计数器时序分析
- 计数器工作时序图
- 计数器无预装时序图(缓冲机制失效 APRE = 0)
- 计数器有预装时序(缓冲机制有效 APRE = 1)
计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1)
= CK_PSC / (PSC + 1) / (ARR + 1)
2.2.4 RCC时钟树简介
RCC时钟树:在STM32中用来产生和配置时钟,并且把配置好的各个外设都发射到各个外设的系统。 时钟是所有外设运行的基础,所以时钟是最先配置的东西。在程序执行时,在执行主程序之前还会执行一个SystemInit函数(详见:STM32学习笔记(一)丨建立工程丨GPIO 通用输入输出),这个函数的作用就是配置RCC时钟树。
RCC时钟树可以分为两部分:时钟产生电路和时钟分配电路。
1.时钟产生电路
在时钟产生电路,有四个振荡源,分别是内部的8MHz高速RC振荡器、外部的4-16MHz高速晶振振荡器(一般都外接8MHz)、外部的32.768kHz低速晶振振荡器(一般给RTC提供时钟)、内部的40kHz低速RC振荡器(给看门狗WDG提供时钟)。外部的石英振荡器比内部的RC振荡器更加稳定。如果系统非常简单,且不需要过于精确的时钟,就可以使用内部的RC振荡器,这样可以省下外部的晶振电路。
在SystemInit函数中是这样来配置时钟的:首先会启动内部的8MHz高速RC振荡器产生时钟,选择该时钟为系统时钟,暂时以8MHz的内部时钟运行;然后再启动外部时钟,配置外部时钟信号流经如下图所示的电路:
时钟产生电路
在时钟产生电路,有四个振荡源,分别是内部的8MHz高速RC振荡器、外部的4-16MHz高速晶振振荡器(一般都外接8MHz)、外部的32.768kHz低速晶振振荡器(一般给RTC提供时钟)、内部的40kHz低速RC振荡器(给看门狗WDG提供时钟)。外部的石英振荡器比内部的RC振荡器更加稳定。如果系统非常简单,且不需要过于精确的时钟,就可以使用内部的RC振荡器,这样可以省下外部的晶振电路。
在SystemInit函数中是这样来配置时钟的:首先会启动内部的8MHz高速RC振荡器产生时钟,选择该时钟为系统时钟,暂时以8MHz的内部时钟运行;然后再启动外部时钟,配置外部时钟信号流经如下图所示的电路:
2.时钟分配电路
时钟产生电路产生的之中信号SYSCLK(72MHz)首先进入AHB总线,在AHB总线上有一个预分频器,在SystemInit函数配置的默认分频系数为1,所以AHB总线的时钟自然是72MHz。
之后信号进入APB1总线,APB1上同样有预分频器,这里SystemInit默认配置的分频系数为2,输出为36MHz,所以APB1总线的时钟为36MHz。通用定时器和基本定时器是接在APB1上的,但是APB1(APB2同理)连接定时器还有如图所示的以下结构:
通用定时器和基本定时器通过图中APB1下方的支路与APB1连接。由于APB1的预分频系数默认为2,则输出到定时器的时钟频率×2。APB2的预分频器的分频系数默认配置为1,其他其他流程与APB1同理。所以基本定时器,通用定时器,高级定时器的内部基准时钟都是72MHz,这样设计为我们使用定时器带来了方便,不用考虑不同定时器时钟不同的问题了(前提是不乱修改SystemInit函数中的默认配置)。
在时钟输出端口,都有一个与门进行时钟输出控制。控制端外部时钟使能就是程序中 RCC_APB2/1PeriphClockCmd 函数作用的地方。
2.3 定时中断和时钟源选择相关库函数使用
定时器相关的库函数非常多,本节仅对将要使用的库函数和 亿些使用细节 进行说明(即使这样也还是很多)。
- 定时器初始化配置
- 时基单元配置函数
// 恢复定时器缺省配置
void TIM_DeInit(TIM_TypeDef* TIMx);
// 时基单元初始化
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
// 把时基单元初始化函数所用的结构体变量赋一个默认值
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
// 使能计数器(对应定时中断结构图中的“运行控制”功能)
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
// 使能中断输出信号(对应定时中断结构图中的“中断输出控制”功能)
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
- 时基单元的时钟源选择配置函数
// 时基单元的时钟选择相关函数
// 选择内部时钟
TIM_InternalClockConfig(TIM_TypeDef* TIMx);
// 选择ITRx其他定时器的时钟,TIM_InputTriggerSource为选择要接入哪个定时器
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
// 选择TIx捕获通道的时钟,TIM_InputTriggerSource为选择的引脚,TIM_ICPolarity为输入极性选择,ICFilter为滤波配置
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
// 选择ETR外部时钟模式1输入的时钟,TIM_ExtTRGPrescaler为ETR外部时钟预分频器,TIM_ExtTRGPolarity为输入极性选择,ExtTRGFilter为滤波配置
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
uint16_t ExtTRGFilter);
// 选择ETR外部时钟模式1输入的时钟,参数与上一个函数完全相同,且对于ETR外部时钟输入而言,两个函数等效,如果不需要触发输入的功能,则两个函数可以互换
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
// 单独配置ETR外部引脚的预分频器,极性,滤波参数
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,
uint16_t ExtTRGFilter);
2.参数(PSC、ARR等)更改函数(在程序运行过程中修改)
// 预分频值设置,TIM_PSCReloadMode为是否应用输入缓冲功能配置
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);
// 获取定时中断的标志位和清除标志位,使用方法与EXTI相同
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);
- 使用定时器库函数的一些细节
-
选择内部时钟函数:定时器上电后默认选择内部时钟,如果要选择内部时钟,这一句可以省略。
TIM_InternalClockConfig(TIM_TypeDef* TIMx);
-
时基单元初始化函数
TIN_TimeBaseInit
:在配置结构体变量时,会遇到以下几个细节问题
1.TIM_TimeBaseInitStructure.TIM_ClockDivision (采样)时钟分频频率选择
在定时器的外部信号输入引脚一般都有一个滤波器来消除信号的抖动干扰,它的工作原理是:在一个固定的时钟频率f ff下进行采样,如果连续N NN个采样点都是相同的电平,就代表输入信号稳定了,就将采样值输出到下一级电路;如果N NN个采样点不全都相同,就说明信号有抖动,这时保持上一次的输出,或直接输出低电平。 这样就能保证输出信号在一定程度上的滤波。这里的采样频率f ff和采样点数N NN都是滤波器的参数,频率越低,采样点数越多,滤波效果就越好,不过相应的信号延迟就越大。
采样频率f ff的来源可以是内部时钟直接提供,也可以是内部时钟加一个时钟分频而来。 分频是多少,就由参数TIM_ClockDivision决定。可见 TIM_ClockDivision与时基单元的关系并不大,它的可选值可以选择1分频,2分频和4分频。
2.在配置结构体变量时,并没有能直接操作计数器CNT的参数。如果需要,可以采用SetCounter和GetCounter两个函数来操作计数器。
3.TIM_TimeBaseInitStructure.TIM_RepetitionCounter 重复计数寄存器,通过这个参数可以设置重复计数寄存器。但是通用定时器中没有这一个寄存器,故可以直接设置为0。
4.定时时间的计算
参考公式: 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1),注意PSC(TIM_Prescaler)和ARR(TIM_Period)的取值都要在0~65535之间。
5.在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值。预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用。但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次,在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题。
- 外部时钟配置函数
TIM_ETRClockMode2Config
TIM_ExtTRGPrescaler外部时钟预分频器:可以选择外部时钟分频关闭(1分频)、2分频、4分频、8分频。
TIM_ExtTRGPolarity 外部触发的极性:TIM_ExtTRGPolarity_Inverted为反向极性,即低电平和下降沿有效;TIM_ExtTRGPolarity_NonInverted为不反向,即高电平和上升沿有效。
ExtTRGFilter 外部输入滤波器:工作原理与内部时钟的滤波器相似,它的值可以是0x00到0x0F之间的一个值,其决定了采样的f ff和N NN,具体的对应关系在手册中有对应表:
GPIO配置:因为是使用外部接口输入时钟,故在使用该函数之前还需要配置GPIO端口。对于定时器,手册中给的推荐配置是浮空输入。但是浮空输入会导致引脚的输入电平极易受干扰,所以输入信号的功率不小时一般选择上拉或下拉输入。当外部的输入信号功率很小,内部的上拉/下拉电阻(较大)可能会影响到这个输入信号,这时就需要用浮空输入,防止影响外部输入的电平。
2.4 定时器定时中断实例
本次实验要完成的现象是:定义一个 uint16_t 的 Num 变量,使其每秒+1。器件连接图和程序源码如下所示:
Timer.c
#include "stm32f10x.h" // Device header
// 用extern声明变量后,这里的Num就会成为main.c文件中Num的引用
extern uint16_t Num;
void Timer_Init(void)
{
// 用RCC外设时钟控制打开定时器的基准时钟和外设的工作时钟
//这行代码启用TIM2的时钟,允许其运行。在STM32微控制器中,时钟控制是通过RCC(Reset and Clock Control)模块进行配置的。
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// 选择时基单元的时钟源(这里使用内部时钟)
// 定时器上电后默认使用内部时钟,如果使用内部时钟这一步也可以省略
//这行代码配置TIM2的时钟源为内部时钟。TIM2将使用微控制器的内部时钟源。
TIM_InternalClockConfig(TIM2);
// 配置时基单元
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; // 重复计数寄存器,高级定时器才有的模块,这里配置为0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值
// 预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用
// 为了让写入的值立刻起作用,故在函数的最后手动生成了一个更新事件
// 但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次
// 在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 配置中断输出控制,允许中断输出到NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC,在NVIC中打开定时器中断的通道,并分配优先级
// NVIC通道优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 配置NVIC中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 中断通道命令
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 抢占优先级
NVIC_Init(&NVIC_InitStructure);
// 配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
main.c
#include "stm32f10x.h" // Device header
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main()
{
OLED_Init();
Timer_Init();
OLED_ShowString(1, 1, "Num:"); // 显示一个字符串
while(1)
{
OLED_ShowNum(1, 5, Num, 5);
// OLED_ShowNum(2, 5, TIM_GetCounter(TIM2), 5);
}
}
2.5 定时器外部时钟选择
本次实验要完成的现象是:用光敏传感器手动模拟一个外部时钟,定义一个 uint16_t 的 Num 变量,当外部时钟触发10次(预分频之后的脉冲)后Num + 1。器件连接图和程序源码如下所示:
Timer.c
#include "stm32f10x.h" // Device header
// 用extern声明变量后,这里的Num就会成为main.c文件中Num的引用
extern uint16_t Num;
void Timer_Init(void)
{
// 用RCC外设时钟控制打开定时器的基准时钟和外设的工作时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// RCC打开GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
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);
// 选择时基单元的时钟源(使用ETR外部时钟)
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 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 = 2 - 1; // 预分频系数PSC(这里如果PSC设置为0,会连续触发中断,导致主程序不运行,是我的芯片Bug问题)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数寄存器,高级定时器才有的模块,这里配置为0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值
// 预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用
// 为了让写入的值立刻起作用,故在函数的最后手动生成了一个更新事件
// 但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次
// 在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 配置中断输出控制,允许中断输出到NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC,在NVIC中打开定时器中断的通道,并分配优先级
// NVIC通道优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 配置NVIC中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 中断通道命令
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 抢占优先级
NVIC_Init(&NVIC_InitStructure);
// 配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
main.c
#include "stm32f10x.h" // Device header
// 用extern声明变量后,这里的Num就会成为main.c文件中Num的引用
extern uint16_t Num;
void Timer_Init(void)
{
// 用RCC外设时钟控制打开定时器的基准时钟和外设的工作时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
// RCC打开GPIO的时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
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);
// 选择时基单元的时钟源(使用ETR外部时钟)
TIM_ETRClockMode2Config(TIM2, TIM_ExtTRGPSC_OFF, TIM_ExtTRGPolarity_NonInverted, 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 = 2 - 1; // 预分频系数PSC(这里如果PSC设置为0,会连续触发中断,导致主程序不运行,是我的芯片Bug问题)
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0; // 重复计数寄存器,高级定时器才有的模块,这里配置为0
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
// 在TIM_TimeBaseInit函数的最后,会立刻生成一个更新事件,来重新装载预分频器和重复计数器的值
// 预分频器有缓冲寄存器,我们写入的PSC和ARR只有在更新事件时才会起作用
// 为了让写入的值立刻起作用,故在函数的最后手动生成了一个更新事件
// 但是更新事件和更新中断是同时发生的,更新中断会置更新中断标志位,手动生成一个更新事件,就相当于在初始化时立刻进入更新函数执行一次
// 在开启中断之前手动清除一次更新中断标志位,就可以避免刚初始化完成就进入中断函数的问题
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
// 配置中断输出控制,允许中断输出到NVIC
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
// 配置NVIC,在NVIC中打开定时器中断的通道,并分配优先级
// NVIC通道优先级分组
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
// 初始化NVIC
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn; // 配置NVIC中断通道
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE; // 中断通道命令
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2; // 响应优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1; // 抢占优先级
NVIC_Init(&NVIC_InitStructure);
// 配置运行控制
TIM_Cmd(TIM2, ENABLE);
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);
}
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
Num ++;
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
1.先在system中添加文件timer.c和timer.h
//timer.h
#ifndef __TIMER_H
#define __TIMER_H
void Timer_Init(void);
#endif
1.#ifndef 是条件编译指令,它的含义是“如果未定义(not defined)某个宏”,这个宏通常是一个符号常量,一个标识符,或者一个宏定义。
2.__TIMER_H 是一个宏,通常用于避免头文件被多次包含,以防止重复定义。它的名称是根据头文件的名称来定义的,通常采用全大写,使用双下划线作为前缀和后缀。
3.#define 用于定义宏或符号常量。在这个例子中,__TIMER_H 被定义为宏。
4.void Timer_Init(void); 是函数声明,它告诉编译器在某处有一个名为 Timer_Init 的函数,该函数不接受参数,返回类型为 void(即不返回任何值)。
5.最后的 #endif 表示条件编译的结束,它与 #ifndef 配套使用,将条件编译范围结束。
所以,这段代码的作用是:当头文件被包含时,首先检查是否已经定义了 __TIMER_H 这个宏,如果没有定义,那么定义它,并且声明一个名为 Timer_Init 的函数。这种方式可以确保头文件只被包含一次,避免重复定义和编译错误。
用于创建头文件的保护措施。
定时器(Timer)相关的函数
1.void TIM_DeInit(TIM_TypeDef* TIMx);
这个函数用于将指定的定时器模块(TIMx)恢复到其初始状态,即将其配置和状态重置为默认值。
TIM_TypeDef* TIMx 是一个指向定时器类型(TIMx)的指针,通过该指针传递了要初始化的定时器的信息。
通过调用这个函数,可以在程序中重置定时器的配置,以便重新配置或释放定时器资源。
2.void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
这个函数用于初始化定时器的基本参数,包括时钟频率、预分频因子、定时器模式等。
2.1.TIM_TypeDef* TIMx 是一个指向定时器类型(TIMx)的指针,通过该指针传递了要配置的定时器的信息。
2.2.TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct 是一个结构体指针,包含了各种配置参数,如定时器的时钟分频、定时器模式、计数器的自动重载值等。
通过调用这个函数,可以设置定时器的基本参数,以便实现所需的定时和计时功能。
3.void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
这个函数可以把结构体变量赋一个默认值,是一个用于初始化定时器时间基准配置结构体的函数,通常在嵌入式系统中与硬件定时器相关的编程中使用。下面是对这个函数的解释:
1.TIM_TimeBaseStructInit 是一个函数名,它用于初始化定时器时间基准配置结构体。
2.TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct 是一个指向定时器时间基准配置结构体的指针。这个结构体通常包含了各种配置参数,如定时器的时钟分频、定时器模式、计数器的自动重载值等。
3.通过调用这个函数,可以将结构体中的各个成员变量设置为默认值,通常是硬件定时器支持的标准配置。
这个函数的主要作用是方便地初始化定时器配置结构体,以确保在开始使用定时器前,结构体的所有成员都有一个合理的默认值。这有助于防止在配置定时器时遗漏某些参数,从而降低了出错的可能性。
4.void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);
用于使能计数器
1.TIM_Cmd 是函数名,用于启用或禁用指定的定时器。
2.TIM_TypeDef* TIMx 是一个指向定时器类型(TIMx)的指针,通过该指针传递了要配置的定时器的信息。
3.FunctionalState NewState 是一个表示状态的参数,通常是一个枚举类型,用于指示要执行的操作。它可以有两个可能的值,通常是 ENABLE 和 DISABLE,用于启用或禁用定时器。
通过调用这个函数,可以控制定时器的开启或关闭。
5.void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState); 是一个与定时器(Timer)中断相关的函数,通常在嵌入式系统中用于配置和控制定时器中断的功能。下面是对这个函数的解释:
1.TIM_ITConfig 是函数名,用于配置指定定时器的中断功能。
2.TIM_TypeDef* TIMx 是一个指向定时器类型(TIMx)的指针,通过该指针传递了要配置的定时器的信息。
3.uint16_t TIM_IT 是一个表示中断源的参数,通常是一个位掩码或一个组合值,用于指示要配置的中断类型(选择配置那个中断输出)。不同的位代表不同的中断源,通过将这些位设置或清除来控制中断。
4.FunctionalState NewState 是一个表示状态的参数,通常是一个枚举类型,用于指示要执行的操作。它可以有两个可能的值,通常是 ENABLE 和 DISABLE,用于启用或禁用中断。
通过调用这个函数,可以控制定时器的中断功能
time.h
#include "stm32f10x.h" // Device header
void Timer_Init(void)
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
TIM_InternalClockConfig(TIM2);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2, ENABLE);
}
/*
void TIM2_IRQHandler(void)
{
if (TIM_GetITStatus(TIM2, TIM_IT_Update) == SET)
{
TIM_ClearITPendingBit(TIM2, TIM_IT_Update);
}
}
*/
初始化和配置定时器 TIM2
1.RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
这行代码启用了 TIM2 定时器的时钟。
2.TIM_InternalClockConfig(TIM2);:此行配置 TIM2 的时钟源为内部时钟。这意味着 TIM2 的时钟频率将基于微控制器的内部时钟源。
3.TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;:这里创建了一个名为 TIM_TimeBaseInitStructure 的结构体,用于配置 TIM2 的基本定时参数,如分频器、计数模式、周期和预分频器等。
4.TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;:设置时钟分频为 TIM_CKD_DIV1,这意味着时钟不分频。
5.TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;:设置计数模式为向上计数,即计数值从 0 开始递增。
6.TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;:设置计数器的周期值,这里设置为 9999,因为计数器会从 0 开始计数。
7.TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;:设置预分频器的值,这里设置为 7199。预分频器用于降低计数器的时钟频率,以便实现特定的计时效果。
8.TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;:这里设置重复计数器的值为 0,通常不需要使用重复计数器。
9.TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);:将上述配置应用到 TIM2 定时器。
10.TIM_ClearFlag(TIM2, TIM_FLAG_Update);:清除 TIM2 定时器的更新标志。这个标志通常用于指示定时器的溢出事件。
11.TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);:使能 TIM2 定时器的更新中断,这允许在定时器溢出时触发中断。
12.NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);:配置中断优先级分组,通常将中断优先级分为抢占优先级和子优先级。
13.NVIC_InitTypeDef NVIC_InitStructure;:创建一个用于配置中断的结构体 NVIC_InitStructure。
14.NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;:指定中断通道为 TIM2。
15.NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;:启用 TIM2 中断。
16.NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;:设置抢占优先级为 2。
17.NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;:设置子优先级为 1。
18.NVIC_Init(&NVIC_InitStructure);:将上述中断配置应用到 NVIC(嵌套向量中断控制器)。
19.TIM_Cmd(TIM2, ENABLE);:启用 TIM2 定时器,开始定时器的计数。
总之,这段代码用于配置和启用 TIM2 定时器,设置其计数模式、周期、预分频等参数,并启用中断以便在定时器溢出时触发中断处理程序。这对于实现定时操作非常有用,例如生成精确的时间延迟、定时任务等。