1.TIM简介
- TIM(Timer)定时器
- 定时器可以对输入的时钟进行计数,并在计数值达到设定值时触发中断
- 16位计数器、预分频器、自动重装寄存器的时基单元,在72MHz计数时钟下可以实现最大59.65s的定时
- 不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能
- 根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型
2.定时器类型
- STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4
2.1 基本定时器
2.1.1 TIM6和TIM7简介
基本定时器TIM6和TIM7各包含一个16位自动装载计数器,由各自的可编程预分频器驱动。
它们可以作为通用定时器提供时间基准,特别地可以为数模转换器(DAC)提供时钟。实际上,它们在芯片内部直接连接到DAC并通过触发输出直接驱动DAC。
这2个定时器是互相独立的,不共享任何资源。
2.1.2 TIM6 和TIM7 的主要特性
TIM6和TIM7定时器的主要功能包括:
● 16位自动重装载累加计数器
● 16位可编程(可实时修改)预分频器,用于对输入的时钟按系数为1~65536之间的任意数值分频
● 触发DAC的同步电路
● 在更新事件(计数器溢出)时产生中断/DMA请求
2.1.3 定时器基本框图
- 预分频器:可以对计数器的时钟进行分频 让这个计数更加灵活 可以对72MHz的计数时钟进行预分频 比如预分频器写0 那就是不分频 或者说是1分频 这时候输出频率=输入频率=72MHz 如果预分频器写1 那就是2分频 输出频率=输入频率/2=36MHz 如果预分频器写2 那就是3分频 输出频率=输入频率/3=24MHz 以此类推 所以预分频器的值与实际的分配系数相差1 即实际分频系数=预分频器的值+1 该预分频器是16位的 所以最大值可以写65535 也就是65536分频 就是对输入的基准频率提前进行一个分频操作
- 计数器:可以对预分频后的计数时钟进行计数 计数时钟每来一个上升沿 计数器的值就加1 计数器也是16位的 里面的值可以从0一直加到65535 如果再加的话 计数器就会回到0重新开始 所以计数器的值在计时过程中会不断地自增运行 当自增运行到目标值时 产生中断 那就完成了定时的任务
- 自动重装寄存器:存储目标的寄存器 是16位的 存的是写入的计数目标 在运行过程中 计数值不断自增 自动重装值是固定的目标 当计数值等于自动重装值时 也就是计时时间到了 那么它就会产生一个中断信号 并且清零计时器 计数器自动开始下一次的计数计时
- 向上的折线箭头:代表这里会产生中断信号 像这种计数值等于自动重装值产生的中断 称作“更新中断” 这个更新中断之后就会通往NVIC 我们再配置好NVIC的定时器通道 那么定时器的更新中断就能够得到CPU的响应了
- 向下的折线箭头:代表会产生一个事件 这里对应的事件叫做“更新事件” 更新事件不会触发中断 但可以触发内部其他电路的工作
- 从基准时钟到预分频器再到计数器 计数器计数自增 同时不断地与自动重装寄存器进行比较 它两值相等时 即计时时间到 这时会产生一个更新中断和更新事件 CPU响应更新中断 就完成了定时中断的任务
2.2 通用定时器
2.2.1 TIMx 简介
通用定时器是一个通过可编程预分频器驱动的16位自动装载计数器构成。
它适用于多种场合,包括测量输入信号的脉冲长度(输入捕获)或者产生输出波形(输出比较和PWM)。
使用定时器预分频器和RCC时钟控制器预分频器,脉冲长度和波形周期可以在几个微秒到几个毫秒间调整。
每个定时器都是完全独立的,没有互相共享任何资源。它们可以一起同步操作。
2.2.2 TIMx 主要功能
通用TIMx (TIM2、TIM3、TIM4和TIM5)定时器功能包括:
● 16位向上、向下、向上/向下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65536之间的任意数值
● 4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 使用外部信号控制定时器和定时器互连的同步电路
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
2.2.3 定时器框图
-
主从触发模式:让内部硬件再不受程序的控制下实现自动运行 若将该模式掌握好 则在某些情况下可以大幅度减轻CPU的负担 用途是在使用DAC时 可能会用DAC输出一段波形 此时需要每隔一段时间来触发一次DAC 让它输出下一个电压点 正常思路是 先设置好一个定时器产生中断 每隔一段时间在中断程序中调用代码手动触发一次DAC转换 然后用DAC输出 但这样会使主程序处于频繁被中断的状态 这会影响主程序的运行和其他中断的响应 所以定时器设计了一个主模式 使用该主模式可以把这个定时器的更新事件映射到这个触发输出的TRGO(Trigger Out)的位置 然后TRGO直接接到DAC的触发转换引脚上 这样定时器的更新就不需要再通过中断来触发DAC转换了 仅需要把更新时间通过主模式映射到TRGO 然后TRGO就会直接去触发DAC 整个过程不需要软件参与 实现了硬件的自动化
- 通用定时器:时钟源不仅可以选择内部的72MHz时钟 还可以选择外部时钟 第一个外部时钟就是来自TIMx_ETR引脚上的外部时钟(可以参考引脚定义表) 表中ETR引脚对应TIM2 在PA0引脚上 可以在ATIM2的ETR引脚也就是PA0上接一个外部方波时钟 接着配置一下内部的极性选择、边沿检测和预分频器电路 再配置一下输入滤波电路(该电路可以对外部时钟进行一定的整形 对输入波形进行滤波) 滤波后的信号兵分两路 上面一路ETRF进入触发控制器 接着就可以选择作为时基单元的时钟 如果想在ETR外部引脚提供时钟 或者想对ETR时钟进行计数 把该定时器当做计数器来用 就可以配置这一路电路 在STM32中 这一路也叫做“外部时钟模式2”
- 除了外部ETR引脚可以提供时钟外 下面一路也可以提供时钟 就是TRGI(Trigger In) 主要是用来触发输入的 这个触发输入可以触发定时器的从模式 (后续会讲到 本小节以出发输入为主) 当TRGI当作外部时钟来使用的时候 这一路叫做“外部时钟模式1” ETR引脚的信号即可以通过上面这一路进来当作时钟 又可以通过下面这一路当作时钟 两种情况对于时钟输入是等价的 只不过下面这一路输入会占用触发输入通道而已 ITR信号这一部分的时钟信号是来自其他定时器的 从右边可以看出 主模式的输出TRGO可以通向其他定时器 通向其他定时器的时候 就接到了其他定时器的ITR引脚上 ITR0-ITR3分别来自其他4个定时器的TRGO输出 通过这一路可以实现定时器的级联 比如先初始化TIM3 然后使用主模式把它的更新事件映射到TRGO上 接着再初始化TIM2 这里选择ITR2 对应的就是TIM3的TRGO 后面再选择时钟为外部时钟模式1 此时TIM3的更新事件就可以驱动TIM2的时基单元 也就实现定时器的级联 这里也可以选择TI1F_ED 这里连接的是输入捕获单元的CH1引脚 也就是从CH1引脚获得时钟 后缀加上一个ED(Edge)就是边沿的意思 也就是通过这一路输入的时钟 上升沿和下降沿均有效 最后这个时钟还能通过TI1FP1和TI2FP2获得 其中TI1FP1连接到CH1引脚的时钟 TI2FP2连接到CH2引脚的时钟 外部时钟模式1的输入可以是ETR引脚、其他定时器、CH1引脚的边沿、CH1引脚和CH2引脚
- ITR和定时器的关系
2.3 高级定时器
2.3.1 TIM1 和TIM8 简介
高级控制定时器(TIM1和TIM8)由一个16位的自动装载计数器组成,它由一个可编程的预分频器驱动。
它适合多种用途,包含测量输入信号的脉冲宽度(输入捕获),或者产生输出波形(输出比较、PWM、嵌入死区时间的互补PWM等)。
使用定时器预分频器和RCC时钟控制预分频器,可以实现脉冲宽度和波形周期从几个微秒到几个毫秒的调节。
高级控制定时器(TIM1和TIM8)和通用定时器(TIMx)是完全独立的,它们不共享任何资源。它们可以同步操作。
2.3.2 TIM1 和TIM8 主要特性
TIM1和TIM8定时器的功能包括:
● 16位向上、向下、向上/下自动装载计数器
● 16位可编程(可以实时修改)预分频器,计数器时钟频率的分频系数为1~65535之间的任意数值
● 多达4个独立通道:
─ 输入捕获
─ 输出比较
─ PWM生成(边缘或中间对齐模式)
─ 单脉冲模式输出
● 死区时间可编程的互补输出
● 使用外部信号控制定时器和定时器互联的同步电路
● 允许在指定数目的计数器周期之后更新定时器寄存器的重复计数器
● 刹车输入信号可以将定时器输出信号置于复位状态或者一个已知状态
● 如下事件发生时产生中断/DMA:
─ 更新:计数器向上溢出/向下溢出,计数器初始化(通过软件或者内部/外部触发)
─ 触发事件(计数器启动、停止、初始化或者由内部/外部触发计数)
─ 输入捕获
─ 输出比较
─ 刹车信号输入
● 支持针对定位的增量(正交)编码器和霍尔传感器电路
● 触发输入作为外部时钟或者按周期的电流管理
2.3.3 定时器框图
- 重复次数计数器:实现每隔几个计数周期才发生一次更新事件和更新中断 原来的结构是每个计数周期完成后就都会发生更新 现在有个计数器在这 每隔几个周期发生更新一次 相当于对输出的更新信号又做了一次分频
3.计数模式
通用、高级定时器可以向上计数、向下计数、向上向下双向计数模式,而基本定时器只有向上计数模式。
1、向上计数模式计数器从0计数到自动加载值(TIMX ARR),然后重新从0开始计数并且产生一个计数器溢出事件
2、向下计数模式。计数器从自动装入的值(TIMX ARR)开始向下计数到,然后从自动装入的值重新开始,并产生一个计数器向下溢出事件
3、中央对齐模式(向上/向下计数):计数器从0开始计数到自动装入的值-1产生一个计数器溢出事件,然后向下计数到1并且产生一个计数器溢出事件,然后再从0开始重新计数。
4.定时器中断基本结构
5.预分频器时序
- 计数器计数频率:CK_CNT = CK_PSC / (PSC + 1)
6.计数器时序
- 计数器溢出频率:CK_CNT_OV = CK_CNT / (ARR + 1) = CK_PSC / (PSC + 1) / (ARR + 1)
- 溢出时间=溢出频率的倒数
7.计数器无预装时序
8.计数器有预装时序
9.RCC时钟树
三种不同的时钟源可被用来驱动系统时钟(SYSCLK):
● HSI振荡器时钟
● HSE振荡器时钟
● PLL时钟
这些设备有以下2种二级时钟源:
● 40kHz低速内部RC,可以用于驱动独立看门狗和通过程序选择驱动RTC。RTC用于从停机/待机模式下自动唤醒系统。
● 32.768kHz低速外部晶体也可用来通过程序选择驱动RTC(RTCCLK)。
当不被使用时,任一个时钟源都可被独立地启动或关闭,由此优化系统功耗。
- 启动内部时钟 选择内部8MHz为系统时钟 暂时以内部8MHz的时钟运行 然后再启动外部时钟 配置外部时钟这一路 进入PLL锁相环进行倍频 8MHz倍频9倍得到72MHz 等到锁相环输出稳定后 选择锁相环输出为系统时钟 这样就把系统时钟8MHz切换为72MHz
- CSS:时钟安全系统 负责切换时钟 可以监测外部时钟的运行状态 一旦外部时钟失效 他就会自动把外部时钟切换回内部时钟 保证系统时钟的运行 防止程序卡死造成事故
10.相关API
10.1 TIM引脚初始化
10.1.1 TIM_DeInit
void TIM_DeInit(TIM_TypeDef* TIMx);
功能:
将TIMx外围寄存器反初始化为其默认复位值
参数:
TIMx: 其中x可以是1到17,以选择TIM外围设备
返回值:
无
10.2 时基单元
10.2.1 TIM_TimeBaseInit
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
功能:
根据 TIM_TimeBaseInitStruct 中指定的参数初始化 TIMx 的时间基数单位
参数:
TIMx:x 可以是 2,3 或者 4,来选择 TIM 外设
TIMTimeBase_InitStruct:指向结构 TIM_TimeBaseInitTypeDef 的指针,包含了TIMx 时间基数单位的配置信息
返回值:
无
//此函数比较重要 用来配置基本结构中粉色部分的时基单元
10.2.2 TIM_TimeBaseStructInit
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);
功能:
用默认值填充每个TIM_TimeBaseInitStruct成员
参数:
TIM_TimeBaseInitStruct:指向TIM_TimeBaseInitTypeDef的指针将被初始化的结构
返回值:
无
10.3 中断输出控制
10.3.1 TIM_ITConfig
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);
功能:
使能或者失能指定的TIM中断。
参数:
TIMx:其中x为1 ~ 17,表示选择TIMx外设
TIM_IT:使能或失能的TIM中断源
NewState: TIM中断的新状态 取值为:ENABLE或DISABLE
返回值:
无
//对应基本结构中绿色部分的中断输出控制
10.4 时钟源选择
10.4.1 TIM_InternalClockConfig
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);
功能:
设置 TIMx 内部时钟
参数:
TIMx:x 可以是 2,3 或者 4,来选择 TIM 外设
返回值:
无
//对应基本结构的时钟选择部分 这里是RCC内部时钟
10.4.2 TIM_ITRxExternalClockConfig
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);
功能:
配置TIMx内部触发器为其他定时器时钟
参数:
TIMx:其中x可以是1、2、3、4、5、9、12或15,用来选择TIM外设
TIM_ITRSource:选择要接入哪个其他的定时器
返回值:
无
//对应基本结构的时钟选择部分 这里是ITRx其他定时器
10.4.3 TIM_TIxExternalClockConfig
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource,
uint16_t TIM_ICPolarity, uint16_t ICFilter);
功能:
选择TIX捕获通道的时钟
参数:
TIMx:其中x可以是1、2、3、4、5、9、12或15,用来选择TIM外设
TIM_TIxExternalCLKSource:选择TIx某个具体的引脚
TIM_ICPolarity:TIx极性 该参数可以是以下值之一:TIM_ICPolarity_Rising(激活高沿或上升沿激活)、TIM_ICPolarity_Falling(激活低沿或下降沿激活)
ICFilter:过滤值 取值范围为0x0 ~ 0xF
返回值:
无
//对应基本结构的时钟选择部分 这里是TIx捕获通道
10.4.4 TIM_ETRClockMode1Config
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
功能:
配置外部时钟模式1
参数:
TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设
TIM_ExtTRGPrescaler:外部触发预分频器
TIM_ExtTRGPolarity:外部触发极性 该参数可以是以下值之一:TIM_ExtTRGPolarity_Inverted:激活低沿或下降沿激活、TIM_ExtTRGPolarity_NonInverted:激活高沿或上升沿激活
ExtTRGFilter:外部触发过滤器 取值范围为0x00 ~ 0x0F
返回值:
无
//对应基本结构的时钟部分 这里是ETR外部时钟经过外部时钟模式1再到时基单元
10.4.5 TIM_ETRClockMode2Config
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler,
uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);
功能:
配置TIMx外部时钟模式2
参数:
TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设
TIM_ExtTRGPrescaler:外部触发预分频器
TIM_ExtTRGPolarity:外部时钟极性
ExtTRGFilter:外部触发滤波器。该参数取值在0x00和0x0F之间
返回值:
无
//对应基本结构的时钟部分 这里是ETR外部时钟经过外部时钟模式2再到时基单元
10.4.6 TIM_ETRConfig
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity,uint16_t ExtTRGFilter);
功能:
配置TIMx外部触发器(ETR)
参数:
TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设
tim_exttrgprecaler:外部触发预分频器
TIM_ExtTRGPolarity:外部触发极性 该参数可以是以下值之一:TIM_ExtTRGPolarity_Inverted:激活低沿或下降沿激活、TIM_ExtTRGPolarity_NonInverted:激活高沿或上升沿激活
ExtTRGFilter:外部触发过滤器 取值范围为0x00 ~ 0x0F
返回值:
无
//单独用来配置ETR引脚的预分频器、极性、滤波器这些参数
10.5 运行控制
10.5.1 TIM_Cmd
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState)
功能:
使能或者失能 TIMx 外设
参数:
TIMx:x 可以是 2,3 或者 4,来选择 TIM 外设
NewState: 外设 TIMx 的新状态
返回值:
无
//对应基本结构橙色单独部分的运行控制 用来使能或失能计数器
10.6 单独写预分频值
10.6.1 TIM_PrescalerConfig
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);
功能:
配置TIMx预调度器
参数:
TIMx:其中x为1 ~ 17,表示选择TIM外设
Prescaler:写入的预分频器的值
TIM_PSCReloadMode:将值写入预分频器的模式 该参数可以是以下值之一:可以是听从安排,在更新事件生效、或者是在写入后手动产生一个更新事件让这个值立刻生效
返回值:
无
10.7 单独写计数器计数模式
10.7.1 TIM_CounterModeConfig
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);
功能:
用来改变计数器的计数模式
参数:
TIMx:其中x可以为1、2、3、4、5或8,选择TIM外设
TIM_CounterMode:要使用的新的计数器计数模式 该参数可以是TIM Up Counting Mode:向上计数模式、TIM Down Counting Mode:向下计数模式、TIM Center Aligned Mode1:中心对齐模型1、TIM Center Aligned Mode2:中心对齐模型2、TIM Center Aligned Mode3:中心对齐模型3
返回值:
无
10.8 自动重装器预装功能配置
10.8.1 TIM_ARRPreloadConfig
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);
功能:
使能或者失能预装模式 使能是有预装 失能是无预装
参数:
TIMx:其中x为1 ~ 17,表示选择TIM外设
NewState: TIMx外设预加载寄存器的新状态 可以是ENABLE或DISABLE
返回值:
无
10.9 其他
void GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);//更新中断标志位
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);//清除标志位
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);//清除更新中断标志位
void TIM_GetCounter(TIM_TypeDef* TIMx);//获取CNT计数器值的变化情况
11. 定时器定时中断
11.1 接线图
11.2 相关代码
Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)//中断初始化函数
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM2的RCC时钟
TIM_InternalClockConfig(TIM2);//时钟选择为RCC内部时钟
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频 这里的选择的是1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置计数模式 这里选择上拉计数模式
TIM_TimeBaseInitStructure.TIM_Period = 10000 - 1;//周期配置 是ARR自动重装值
TIM_TimeBaseInitStructure.TIM_Prescaler = 7200 - 1;//配置预分频器PCS的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//这里是重复计数器 是高级定时器才需要用到 而我们这里是通用定时器 随意直接给其配置0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置时基单元部分
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除挂起标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//配置中断输出控制 第二个参数是更新中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组 这里选择的是分组2(2位抢占2位响应 比较评价)
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//配置中断通道 这里TIM2对应的是TIM2_IQRn
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定NVIC线路的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);//启动定时器(对应运行控制)
}
/*
void TIM2_IRQHandler(void)//中断函数 从启动文件可以找到TIM2对应的是TIM2_IRQHandler
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//更新中断标志位 第二个参数是选择哪个中断标志位 这里选的是更新中断
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
- 定时器外部信号输入引脚 一般会有一个滤波器 滤波器可以滤掉信号的抖动干扰 工作原理是在一个固定的时钟频率f下进行采样 如果连续N个采样点都为相同电平 那就代表输入信号稳定 就把这个采样值输出出去 如果N个采样值不完全相同 那就说明信号有抖动 这时就保持上一次的输出 或者直接输出低电平 这样就能保证输出信号在一定程度上的滤波 这里的采样频率f和采样点数N都是滤波器的参数 频率越低 采样点数越多 滤波效果更好 不过响应的信号延迟就会越大 采样频率f可以由内部时钟直接而来 也可以是由内部时钟加一个时钟分频而来 分频多少由参数ClockDivision决定
- 定时1s 即定时频率为1Hz 但定时频率=72M/(PSC+1)/(ARR+1) 所以我们可以给PSC配置7200-1 给ARR配置10000-1 因为这里配置的是PSC、ARR个体 但其整体在公式中分别+1 所以我们我们在配置其个体时需要减1 等到的就是1Hz=72M/7200/10000(72Mhz = 72000000hz 因为预分频器能对72Mhz的计数时钟进行预分频)
- 预分频给多一点 自动重装给少一点 就是以一个比较高的频率计比较多的数 预分频给少一点 自动重传给多一点 就是以一个比较低的频率计比较少的数
- 调用主函数编译下载后发现Num从1自增 由于TimeBaseInit函数会生成一个更新事件来重新装载预分频器和重装计数器的值 我们知道预分频器有个缓冲寄存器 写的值只有在更新事件时才会起到作用 这里为了让值立刻起作用 在这最后手动生成一个更新事件 这样预分频器的值才有效 但他的副作用就是更新事件和更新中断是同时发生的 更新中断会置更新中断标志位 当我们一旦初始化完毕 更新中断就会立刻进入 所以我们一上电就进入中断 只需在初始化后清除中断标志位即可
Timer.h
#ifndef __Timer_H
#define __Timer_H
void Timer_Init(void);
#endif
main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
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);//获取计数器值的变化情况
}
}
void TIM2_IRQHandler(void)//中断函数 从启动文件可以找到TIM2对应的是TIM2_IRQHandler
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//更新中断标志位 第二个参数是选择哪个中断标志位 这里选的是更新中断
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);//清除标志位
}
}
- 这里有两种方式:第一种是采用定义全局变量 在main.c中定义一个uint16_t Num 此时再到Timer.c中定义一个extern uint16_t Num(extern声明了主函数的Num变量) 便可以使用跨文件的变量 使用extern声明即可 在Timer.c底部的中断函数写Num++就可以实现定时器中断Num自增现象;第二种是将Timer.c中的中断注释掉 将其复制到main.c中 此时只需要在main.c定义一个uint16_t Num即可 对于定时中断而言 中断函数就是为别的文件服务的 中断函数一般放在中断代码模块 但如果其他函数要调用到 我们一般直接注释掉中断模块里的 将其复制到需要用到中断函数的模块 本函数使用的是第二种方法
- 自动重装值配置的是10k 即10k记1s 从开始记到9999然后自动重装 此时定时频率为1Hz 若将自动重装值改成720 则1000记0.1s 从开始记到999然后自动重装 此时定时频率为10Hz 若将预分频值改为720 那就是以原来10倍的计数频率记10000个数 这是定时频率也是10Hz 速度比原来快10倍
现象:OLED屏幕第一行每隔1s加1 从0开始计时 第二行显示计数器 从0-9999 频率为1Hz
12.定时器外部时钟
12.1 接线图
12.2 相关代码
12.2.1 Timer.c
#include "stm32f10x.h" // Device header
void Timer_Init(void)//中断初始化函数
{
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE);//使能TIM2的RCC时钟
TIM_ETRClockMode2Config(TIM2,TIM_ExtTRGPSC_OFF,TIM_ExtTRGPolarity_NonInverted,0x00);//通过ETR引脚的外部时钟模式2配置
//这里不需要分频 所以第二个参数配置OFF
//第三个参数是外部触发的极性
//第四个参数是滤波器配置 这里不需要 所以配置0x00
GPIO_InitTypeDef GPIO_InitStructure;//这里引脚需要用到GPIO 所以进行GPIO初始化
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);
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;//指定时钟分频 这里的选择的是1分频
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;//配置计数模式 这里选择上拉计数模式
TIM_TimeBaseInitStructure.TIM_Period = 10 - 1;//周期配置 是ARR自动重装值
TIM_TimeBaseInitStructure.TIM_Prescaler = 1 - 1;//配置预分频器PCS的值
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;//这里是重复计数器 是高级定时器才需要用到 而我们这里是通用定时器 随意直接给其配置0
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseInitStructure);//配置时基单元部分
TIM_ClearFlag(TIM2,TIM_FLAG_Update);//清除挂起标志位
TIM_ITConfig(TIM2,TIM_IT_Update,ENABLE);//配置中断输出控制 第二个参数是更新中断
NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);//配置优先级分组 这里选择的是分组2(2位抢占2位响应 比较评价)
NVIC_InitTypeDef NVIC_InitStructure;
NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;//配置中断通道 这里TIM2对应的是TIM2_IQRn
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;//指定NVIC线路使能
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;//指定NVIC线路的抢占优先级
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;//指定NVIC线路的响应优先级
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM2,ENABLE);//启动定时器(对应运行控制)
}
uint16_t Timer_GetCounter(void)
{
return TIM_GetCounter(TIM2);//返回当前计数器的值
}
/*
void TIM2_IRQHandler(void)//中断函数 从启动文件可以找到TIM2对应的是TIM2_IRQHandler
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//更新中断标志位 第二个参数是选择哪个中断标志位 这里选的是更新中断
{
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
*/
122.2 Timer.h
#ifndef __Timer_H
#define __Timer_H
void Timer_Init(void);
uint16_t Timer_GetCounter(void);
#endif
12.2.3 main.c
#include "stm32f10x.h" // Device header
#include "Delay.h"
#include "OLED.h"
#include "Timer.h"
uint16_t Num;
int main(void)
{
OLED_Init();
Timer_Init();
OLED_ShowString(1,1,"Num:");
OLED_ShowString(2,1,"CNT:");
while (1)
{
OLED_ShowNum(1,5,Num,5);
OLED_ShowNum(2,5,Timer_GetCounter(),5);
}
}
void TIM2_IRQHandler(void)//中断函数 从启动文件可以找到TIM2对应的是TIM2_IRQHandler
{
if(TIM_GetITStatus(TIM2,TIM_IT_Update) == SET)//更新中断标志位 第二个参数是选择哪个中断标志位 这里选的是更新中断
{
Num++;
TIM_ClearITPendingBit(TIM2,TIM_IT_Update);
}
}
现象:遮挡对射式红外传感器模块 计数加1 从0开始加 计数10次 加到9后清零 更新中断 Num从0加1 以此类推