文章目录
- 前言
- 一、时基单元
-
- [1.1 定时器简介](#1.1 定时器简介)
- [1.2 时基单元的基本结构](#1.2 时基单元的基本结构)
- [1.3 上计数、下计数和中心对齐](#1.3 上计数、下计数和中心对齐)
- [1.4 时钟的来源](#1.4 时钟的来源)
- [1.5 寄存器预加载](#1.5 寄存器预加载)
- [1.6 利用时基单元做延迟函数](#1.6 利用时基单元做延迟函数)
- [1.7 关于volatile关键字](#1.7 关于volatile关键字)
- 二、输出比较
-
- [2.1 输出比较简介](#2.1 输出比较简介)
- [2.2 基本工作原理](#2.2 基本工作原理)
- [2.3 8种工作模式](#2.3 8种工作模式)
- [2.4 互补输出](#2.4 互补输出)
- [2.5 极性选择](#2.5 极性选择)
- [2.6 呼吸灯实验](#2.6 呼吸灯实验)
-
- 2.6.1呼吸灯原理
- [2.6.2 初始化IO引脚](#2.6.2 初始化IO引脚)
- [2.6.3 配置时基单元](#2.6.3 配置时基单元)
- [2.6.4 配置输出比较模块](#2.6.4 配置输出比较模块)
- [2.6.5 改变PWM占空比](#2.6.5 改变PWM占空比)
- 总结
- 附录
前言
定时器(Timer) 是单片机中最核心、最常用的外设之一。
把单片机的 CPU(核心) 想象成老板,他负责处理复杂的逻辑、做决策,那么定时器(Timer) 就是老板身边的专业助理,手里拿着一个精准的秒表。
本文对时基单元和输出比较功能做出介绍,作为学习笔记使用,内容参考B站UP"铁头山羊"。
一、时基单元
1.1 定时器简介

在STM32F103C8T6这款单片机中有TIM1-TIM4共4个定时器资源,其中TIM1是高级定时器,拥有通用定时器全部功能,并额外具有重复计数器、死区生成、互补输出、刹车输入等功能。
而TIM2、TIM3、TIM4是通用定时器,拥有基本定时器全部功能,并额外具有内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等功能。

上图是高级定时器的内部结构框图,可以看出它结构非常复杂,功能丰富。
我们将其分为4个部分来看,第一部分为时基单元,第二部分输出比较,第三部分输入捕获,第四个部分从模式控制器。
我们先来学习最基础的时基单元。
1.2 时基单元的基本结构

将时基单元单独拿出来看,分别包含最左边的时钟源,预分频器(PSC),计数器(CNT),自动重装寄存器(ARR),以及重复计数器(RCR).
先来看时钟源,时基单元的实际信号来源于最左边的输入信号。一种来源是时钟树,另一种来自于定时器本身的从模式控制器(从模式控制器暂且不介绍)。
再看预分频器(PSC - Prescaler),标准库的定时器时钟频率一般为72MHz,我们用不到这么高的频率,这时就需要用到预分频器对时钟信号进行降频,它的分频系数是可以设置的,分频系数等于PSC的值+1,范围是0~65535.
接下来是计数器(CNT - Counter),它对经过预分频器处理后的脉冲信号进行计数,左边每来一个脉冲,这个CNT的值就会增加1(上计数时),CNT的计数范围也是0~65535.
往上看是自动重装载寄存器 (ARR - Auto-Reload Register),它是专门来设置定时的时钟周期的。当CNT与ARR的数值相等时,就会发生"溢出"。此时,CNT 会被清零(回到起跑线),同时产生一个更新事件(UEV),通常会触发中断。
最后是重复计数器 (RCR - Repetition Counter) ,它是用来设置重复计数的次数的。如果RCR为0,那么CNT每发生一次溢出,就会产生一个Update事件。但是如果RCR为1的话,那么CNT要溢出两次,才会产生Update事件。
RCR通常用于电机控制,并且只有高级定时器才有这个模块。比如 PWM 波形频率很高(20kHz),如果每周期都进中断 CPU 会累死,用 RCR 可以每隔 N 个周期处理一次,既保证波形连续,又减轻 CPU 负担。

以石英手表为例,手表内部有石英晶振,频率为32.768K,这是手表的时钟来源,频率为32768Hz.
这个频率太高了,我们需要对这个时钟频率进行分频,手表内部有电路降低这个时钟频率,假设我们可以得到1Hz的信号。
1Hz的信号,用来驱动秒针(相当于计数器),秒针会从0开始,每秒跳1格,满60归零(相当于将自动重装寄存器设置为59),这是分针跳一格(相当于计数器的值等于自动重装寄存器的值,触发事件或者中断)。
1.3 上计数、下计数和中心对齐

我们之前说的每来一个脉冲,计数值加1,这种计数方式为上计数。
还有每来一个脉冲,计数值减1,这种为下计数。
最后是中心对齐,首先采用"上计数"从0开始计数到ARR,然后转变成下计数,从ARR计数到0.
一般采用上计数。
1.4 时钟的来源

标准库中我们不做任何配置,SYSCLK的时钟频率为HSE经锁相环的来的时钟频率,为72MHz。各部分外设的时钟频率都被设置为最大的时钟频率。
注意定时器的时钟频率,APB1总线支持的最大频率为36MHz,也就是从HCLK得到的时钟信号会被APB1分频器2分频,此时获得36MHz的是时钟频率。但是TIM前面会有一个倍频器,自动将时钟频率增大,从而TIM得到72MHz的信号。

石英手表计数的最小单位是秒,满一圈也就是60s,秒针归零。
类比手表,定时器的分辨率,也就是最小的记时单位;计数周期,由自动重装寄存器的值决定,决定了计数器数多少个数归零。
我们得到的时钟频率为72MHz,要求的分辨率为1us,即1MHz;周期为1ms,也就是CNT需要计数1000次。
因此,设置PSC的值为71,ARR的值为999,RCR的值为0.
1.5 寄存器预加载

注意,在图中几个可以设置参数的寄存器,有灰色阴影。
这个灰色阴影表示影子寄存器,白色部分为活动寄存器。我们向寄存器写值的时候,值首先被写入到影子寄存器中,不会立即生效。
而是要等到计数器CNT溢出时,产生Update事件,影子寄存器的值才会放入活动寄存器中,然后生效。
这种缓存的机制,我们把它叫做寄存器的预加载。

简单来说,这是 STM32 为了保证定时器在运行过程中修改参数(如周期、占空比)时,波形不会出错而设计的一种"缓存机制"。
假设没有影子寄存器,只有一个寄存器。你在产生一个 PWM 波形,周期是 8(ARR=7)。计数器 CNT 数到了 5 的时候,你突然想改变频率,把 ARR 改成了 3。
没有影子寄存器,这个值直接生效,ARR 瞬间变成了 3。此时 CNT 是 5,已经大于 ARR(3) 了!CNT 无法在这个周期碰到 ARR,它会一直往上数,数到 65535 溢出后才清零 。你的电机本来转得很稳,突然停顿了一下(产生了一个超级长的错误周期),这在控制系统中是致命的。

有影子寄存器,你把 3 写入了预装载寄存器。
定时器硬件还在用影子寄存器里的旧值(7)工作。CNT 继续数:5 -> 7 -> 溢出(更新事件 UEV)。就在溢出的这一瞬间,硬件自动把预装载寄存器的 3 搬运(Copy) 到影子寄存器。下一个周期从 0 数到 3。波形平滑过渡,没有错误。
举例:投影仪换胶片
想象你在用老式投影仪讲课。屏幕上的画面 = 影子寄存器(大家实际看到的)。
你手里的下一张胶片 = 预装载寄存器(你准备好的)。
切换动作 = 更新事件 (Update Event)。
如果你直接在投影仪亮着的时候把胶片抽走换新的,屏幕上会闪瞎眼(波形出错)。
正确的做法是:你先把下一张胶片拿在手里准备好(写入预装载),等当前这张讲完了(周期结束),"咔嚓"一下瞬间切换过去(更新影子寄存器)。
哪些寄存器有"影子"?
PSC (预分频器): 它的影子机制是强制开启的。你改了分频系数,永远是下个周期才生效。
ARR (自动重装载): 可选开启。
CCR (捕获/比较寄存器): 可选开启。
1.6 利用时基单元做延迟函数

我们可以用定时器每1ms产生一次中断,全局变量currentTick+1,那么currentTick反映了当前的时间。
我们怎么配置定时器每1ms产生中断呢?
首先开启TIM2的时钟,得到72MHz的时钟。
然后初始化时基单元,将PSC设置为71,得到1MHz的信号(即1us),将ARR设置为999,RCR设置为0,这样将可以得到1ms的信号。
接下来,使能定时器的中断,选择Update事件。
最后,在中断响应函数里,将currentTick+1,同时清除Update标志位。

中断函数可以如上图实现。
1.7 关于volatile关键字
它的作用是,告诉编译器:"这个变量是易变的,别自作聪明去优化它,每次都要老老实实去内存里读取最新的值。"
在 STM32 开发中,以下三种情况必须使用volatile:
- 中断服务函数与主循环共享的全局变量
凡是一个变量在中断里写,在主循环里读(或者反过来),都必须加volatile
- 映射到硬件寄存器的指针
这是 STM32 库函数底层的核心。单片机的外设寄存器(如 GPIO 的 IDR 输入数据寄存器),它的值是随着外部电压变化而变化的,而不是代码改的。
- 简单的软件延时循环
c
void delay(int count) {
while (count--) {
// 空操作
}
}
无 volatile : 编译器如果开了高等级优化(比如 -O2),它会分析出这个循环除了浪费时间什么都没干,于是直接把这个循环删掉了。延时失效。
有 volatile: 如果把 count 定义为 volatile,或者在循环里访问一个 volatile 变量,编译器就不敢删它了。
小结:
在 STM32 学习中,只需要记住一点:只要这个变量不仅仅是被当前的代码修改,还可能被"中断"或者"硬件外设"偷偷修改,就一定要加上 volatile。
二、输出比较
2.1 输出比较简介

上一节我们介绍了定时器的时基单元,本节我们介绍定时器的输出比较部分。
输出比较(Output Compare, 简称 OC)就是让定时器拿着计数器(CNT)的值,去和你的设定值(CCR)做比较,一旦相等,就自动去修改引脚的电平。
在输出比较电路中,有两个主角:
- CNT (Counter): 那个一直在跑步的计数器(时基单元)
- CCR (Capture/Compare Register): 捕获/比较寄存器。每个通用定时器通常有 4 个通道 (Channels),对应 4 个 CCR 寄存器 (CCR1 ~ CCR4)。这意味着一个定时器可以同时控制 4 盏灯或 4 个电机。
2.2 基本工作原理

硬件电路会一直盯着CNT和CCR ,
当CNT < CCR 时:输出一种电平(比如高电平)。
当 CNT >= CCR 时:输出另一种电平(比如低电平)。
当 CNT == CCR 时:发生跳变。
虽然输出比较有很多种模式,但在实际开发中,95% 的情况我们只用一种模式:PWM 模式(脉冲宽度调制)。
PWM 就是在一段固定的时间内,通过控制"通电时间"和"断电时间"的比例,来等效地获得所需要的模拟参量,常应用于电机控速等领域。
PWM参数:
频率 = 1/Ts,Ts为PWM的周期
占空比 = Ton/Ts,即高电平时间比周期
分辨率 = 占空比变化步距
单片机的VCC为3.3V,如果占空比为0,那么等效输出电压0V.
如果占空比为25%,等效输出电压为0.825V。
如果占空比为75%,等效输出电压为2.475V。
如果占空比为100%,等效输出电压为3.3V。
通过PWM,我们就可以等效输出想要的电压,以此可以调节输出电压,进一步用于调节电机转速。

下面介绍如何设置PWM的占空比
设置ARR为9,那么一个周期就是0~9共10个数,我们设置CCR1为3,那么当CNT小于CCR时,CH1输出高电平;而当CNT大于CCR时,CH1输出低电平。高电平时间与周期之比,即CCR/ARR+1=30%,PWM占空比为30%,调整CCR的大小即可改变占空比。
ARR (自动重装值) 决定了 频率(周期)。CNT 从 0 数到 ARR,这就是一个完整的周期。
CCR (比较值) 决定了 占空比(高电平时间)。
2.3 8种工作模式

上图为输出比较(OC)部分的结构图,有模式选择,互补输出和极性选择等几个部分,下面先来看OC的8种工作模式。

第一类:只看不动
冻结模式 (Frozen),当 CNT = CCR 时,引脚什么都不做,维持原来的状态。
第二类:一次性动作
匹配时置有效电平 (Set Active on Match) ,CNT = CCR 时,引脚瞬间变成高电平。用途,比如想做个"延时启动"装置,定时器启动 10ms 后,把某个开关打开,并且一直开着。
匹配时置无效电平 (Set Inactive on Match),CNT = CCR 时,引脚瞬间变成低电平。用途,"延时关闭"装置,或者作为一次性脉冲的结束。
第三类:波形生成
翻转模式 (Toggle),CNT = CCR 时,引脚电平取反(高变低,低变高)。无论你的 CCR 设多少,它输出的永远是 50% 占空比的方波。
第四类:软件强制控制
强制为无效电平 (Force Inactive) ,不管 CNT 是多少,强制引脚输出 低电平。
强制为有效电平 (Force Active),不管 CNT 是多少,强制引脚输出 高电平。在电机控制中,如果检测到故障,必须立刻把输出拉低停止电机,不用去改复杂的定时器配置,直接切到强制模式即可。
第五类:PWM 模式
这两种模式是互补的。以向上计数为例:
PWM 模式 1 (PWM Mode 1) ------ 标准模式,CNT < CCR 时:输出有效 (高),CNT >= CCR 时:输出无效 (低), CCR 越大,高电平时间越长(占空比越大)。这是最符合直觉的模式。
PWM 模式 2 (PWM Mode 2) ------ 反向模式,刚好和模式 1 相反。CCR 越大,低电平时间越长。
我们学习的重点是PWM模式,大部分情况我们都会用PWM模式,其他模式了解即可。
2.4 互补输出

在 STM32F1 系列中,只有高级定时器(TIM1 和 TIM8) 才拥有互补输出功能。通用的 TIM2~TIM5 是没有的!
互补输出,简单来说,就是一个通道产生两路波形。
正向通道 (CHx)输出高电平时,反向通道 (CHxN) 输出低电平;
正向通道 (CHx)输出低电平时,反向通道 (CHxN) 输出高电平。
那么为什么要有互补输出?我用个反相器芯片不就能把高变成低了吗?为什么要单片机专门搞个功能?
核心原因在于:驱动直流无刷电机(BLDC)或大功率逆变器时,我们需要控制"H桥"电路。互补输出就是为了保证:只要上管开了,下管自动关闭;下管开了,上管自动关闭。

如果H桥的上管和下管同时导通,电源正极直接通过两个开关连到负极,形成短路,就会炸管,电路板冒烟。
仅仅是逻辑取反,还是会有风险。因为 MOS 管不是理想开关,它有关断延迟(结电容)。
STM32 的高级定时器允许你在切换瞬间,插入一段"大家都关 "的时间-死区时间。这段时间大家都关,保证安全。
2.5 极性选择

极性选择(Polarity Selection) 是定时器输出通道上的"最后一道关卡"。
在信号离开 STM32 芯片、跑到引脚上之前,必须经过这个关卡。它的作用非常简单粗暴:决定"有效电平"到底是高电平(3.3V)还是低电平(0V)。
为什么要有极性选择?
这是为了适配不同的硬件电路。举个最常见的例子------点亮LED:
1.电路 A(高电平点亮): LED 正极接 IO 口,负极接 GND。我们需要输出 高电平 让它亮。
2.电路 B(低电平点亮): LED 正极接 3.3V,负极接 IO 口(灌电流驱动)。我们需要输出低电平让它亮。
当从电路A切换到电路B时,使用极性选择,代码里的逻辑完全不用动。只需要把 极性配置(CCxP) 改一下,硬件就会自动帮你把发出的波形"颠倒"过来。
2.6 呼吸灯实验
2.6.1呼吸灯原理

我们让LED在一个周期亮灭,通过在一个周期输出不同的电压,达到呼吸灯的效果。
通过输出PWM信号,利用SPWM原理,达到正弦信号效果。
占空比即正弦信号的幅值,周期为1ms.
2.6.2 初始化IO引脚

用两个LED接定时器1通道1的正向通道和反向通道,观察互补输出的效果。
接下来要确定GPIO引脚如何配置。

查看数据手册,找到TIM1_CH1和TIM1_CH1N通道对应的引脚,我们不使用引脚重映射,那么TIM1_CH1的引脚为PA8,TIM1_CH1N的引脚为PB13 。

查看数据手册,我们使用互补输出,两个引脚,应配置为推挽复用输出。
2.6.3 配置时基单元

之前说过,我们要产生周期为1ms的PWM信号。
首先TIM1获得的时钟信号频率为72MHz,我选择分频为1MHz的信号,则PSC=72-1,得到1MHz(即1us)的时钟信号,再将ARR的值设为1000-1,那么1us*1000=1ms。由此,我们得到周期为1ms的PWM信号。

初始化时基单元,我们需要完成的操作分成4步,首先是开启TIM1的时钟;其次是设置预分频器(PSC)、自动重装载寄存器(ARR)、重复计数器(RCR)的值;再次,将ARR的预装载功能打开(即开启影子寄存器);最后是闭合TIM1的开关。
2.6.4 配置输出比较模块

输出比较部分,我们有两个模块需要配置。
一是初始化输出比较通道1的参数 ,二是闭合MOE的开关 ,注意MOE只有高级定时器才有,我使用STM32F103,只有使用定时器1需要配置MOE。
输出比较通道1的参数:
输出比较模式选择为PWM1;
闭合输出通道的开关;
闭合通道的开关初始化CCR为0,后面会更改CCR的值;
输出极性选择为高;
下面是高级定时器才有的参数
闭合互补引脚的开关;
将互补引脚极性选择为高;
定时器不工作时,将引脚极性置低,这样更安全。
记得还要开启OC1通道的预装载功能,最后闭合MOE的开关。
2.6.5 改变PWM占空比

在主函数种调用TIM_SetCompare1改变CCR1的值。
这里CCR1应为高电平所占时间,所以要将占空比duty*PWM周期。
实验现象为,两个LED交替闪烁。
代码放在附录。
总结
本文对时基单元和输出比较模块进行了介绍,需注意本文主要使用高级定时器1,通用定时器2,3,4的配置略微不同,功能更少。后续将学习输入捕获和从模式。
附录
占空比的计算用到了时间 t ,我用定时器2,加中断的方式获取当前时间。具体方法及实现可以参考我这篇文章。
main函数
c
#include "stm32f10x.h" // Device header
#include "Timer.h"
#include "PWM.h"
#include "math.h"
int main(void)
{
Timer_Init();//初始化定时器2,以获取当前时间
PWM_Init();//初始化定时器1,配置输出比较模式
while(1)
{
float t = GetTick() * 1.0e-3f;//单位转换成s
float duty = 0.5*(sin(2*3.14*t)+1);//占空比
uint16_t ccr1 = duty * 1000;//占空比*PWM周期,即高电压所占时间
TIM_SetCompare1(TIM1,ccr1);//设置CCR1的值,改变占空比
}
}
PWM.c文件
c
#include "PWM.h"
void PWM_Init(void){
//#1.开启TIM1时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE);
//#2.初始化IO引脚,输出比较引脚PA8,互补输出引脚PB13,推挽复用输出
//#2.1初始化引脚PA8
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA,ENABLE);
GPIO_InitTypeDef GPIO_InitStruct = {0};
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOA,&GPIO_InitStruct);
//#2.1初始化引脚PB13
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB,ENABLE);
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;
GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_2MHz;
GPIO_Init(GPIOB,&GPIO_InitStruct);
//#3.初始化时基单元
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct ={0};
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;//时钟分频因子,配置"数字滤波器"的采样频率
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;//计数模式为上计数
TIM_TimeBaseInitStruct.TIM_Prescaler = 72-1;//预分频系数
TIM_TimeBaseInitStruct.TIM_Period = 1000-1;//自动重装载值
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;//重复计数器
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseInitStruct);
//配置ARR寄存器的预加载
TIM_ARRPreloadConfig(TIM1,ENABLE);
//#4.闭合总开关
TIM_Cmd(TIM1,ENABLE);
//#5.配置输出比较的参数
TIM_OCInitTypeDef TIM_OCInitStruct ={0};
TIM_OCInitStruct.TIM_OCMode = TIM_OCMode_PWM1;//输出比较模式
TIM_OCInitStruct.TIM_OutputState = TIM_OutputState_Enable;//这个通道的开关
TIM_OCInitStruct.TIM_Pulse = 0;//CCR比较器,决定了脉冲宽度,会被TIM_SetCompareX()修改
TIM_OCInitStruct.TIM_OCPolarity = TIM_OCPolarity_High;//输出极性为高
//以下参数仅高级定时器
TIM_OCInitStruct.TIM_OutputNState = TIM_OutputNState_Enable;//互补引脚的开关
TIM_OCInitStruct.TIM_OCNPolarity = TIM_OCNPolarity_High;//互补引脚的极性
TIM_OCInitStruct.TIM_OCIdleState = TIM_OCIdleState_Reset;//定时器不工作时,引脚置低
TIM_OCInitStruct.TIM_OCNIdleState = TIM_OCNIdleState_Reset;//定时器不工作时,引脚置低
TIM_OC1Init(TIM1, &TIM_OCInitStruct);//初始化比较通道1
//#6.开启OC1通道的预装载
TIM_OC1PreloadConfig(TIM1,TIM_OCPreload_Enable);
//#7.闭合MOE的开关
TIM_CtrlPWMOutputs(TIM1,ENABLE);//高级定时器才有,只能填1或8
}
PWM.h文件
c
#ifndef __PWM_H
#define __PWM_H
#include "stm32f10x.h" // Device header
void PWM_Init(void);
#endif
Timer.c文件,用于获取当前时间
c
#include "Timer.h"
static volatile uint32_t currentTime;//记录当前时间,单位ms
void Timer_Init(void){
//#1.开启定时器2的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);
//#2.配置时基单元,定时器周期为1ms
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStruct = {0};
TIM_TimeBaseInitStruct.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStruct.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStruct.TIM_Period = 1000-1;
TIM_TimeBaseInitStruct.TIM_Prescaler = 72-1;
TIM_TimeBaseInitStruct.TIM_RepetitionCounter = 0;
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStruct);
//#3.使能定时器的中断
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//#4.配置定时器中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_0);//配置中断优先级分组
NVIC_InitTypeDef NVIC_InitStruct = {0};
NVIC_InitStruct.NVIC_IRQChannel = TIM2_IRQn;
NVIC_InitStruct.NVIC_IRQChannelCmd = ENABLE;
NVIC_InitStruct.NVIC_IRQChannelPreemptionPriority = 0;
NVIC_InitStruct.NVIC_IRQChannelSubPriority = 0;
NVIC_Init(&NVIC_InitStruct);
//#3.闭合时基单元的开关
TIM_Cmd(TIM2, ENABLE);
}
/*
@简介: 获取当前时间,以毫秒为单位
@参数: 无
@返回值: 当前时间,以毫秒为单位
*/
uint32_t GetTick(void){
return currentTime;
}
void TIM2_IRQHandler(void){
if(TIM_GetFlagStatus(TIM2, TIM_FLAG_Update) == SET){
currentTime++;
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除标志位
}
}
Timer.h文件
c
#ifndef __TIMER_H
#define __TIMER_H
#include "stm32f10x.h" // Device header
void Timer_Init(void);
uint32_t GetTick(void);
#endif