5、TIM定时器中断

1、 TIM简介

定时器:可以对输入的时钟进行计数,并在计数值达到设定值时触发中断

STM32在72MHz计数时钟下可以实现最大59.65s的定时。

  1. 16位计数器:执行计数定时的一个寄存器,每来一个时钟,计数器加1

  2. 预分频器:可以对计数器的时钟进行分频,让这个计数更加灵活

  3. 自动重装寄存器:计数的目标值(计多少个时钟申请中断),这些寄存器构成了最核心的部分。

这三部分组成了定时器的时基单元(计数器、自动重装寄存器、预分频器)

不仅具备基本的定时中断功能,而且还包含内外时钟源选择、输入捕获、输出比较、编码器接口、主从触发模式等多种功能

  1. 内外时钟源选择:STM32定时器允许你选择内部时钟源或外部时钟源,以满足不同的时序要求。这使得定时器能够适应不同的时钟环境。
  2. 输入捕获:定时器可以捕获外部事件的时间戳,例如上升沿或下降沿触发。这对于测量脉冲宽度、频率或其他时间相关的应用非常有用。
  3. 输出比较:定时器可以与输出比较功能一起使用,以在特定时间点产生或清除输出信号。这用于生成PWM信号、产生精确的脉冲输出等。
  4. 编码器接口:某些定时器可以配置为编码器接口模式,用于读取旋转编码器的位置或方向信息。这在机械系统中的位置反馈中很有用。
  5. 主从触发模式:定时器可以配置为主从触发模式,其中一个定时器可以触发另一个定时器的启动或停止。这对于精确的时间同步和多个定时器的协同工作非常有用。

根据复杂度和应用场景分为了高级定时器、通用定时器、基本定时器三种类型:

img

STM32F103C8T6定时器资源:TIM1、TIM2、TIM3、TIM4

2、 基本定时器

可以完成定时中断,主模式触发DAC的功能。

DAC功能:数模转换

img

内部时钟(CK_CNT):一般就是系统的主频72MHz,通向时基单元的输入。

时基单元:16位预分频器 + 16位计数器 + 16位自动重装载寄存器。

  1. 预分频器:对输入的72MHz时钟进行预分频,寄存器内存储的值是实际的分频系数减一。写0就是不分频,写1就是2分频,写2就是3分频……

  2. 计数器:对预分频后的计数时钟进行计数,每遇到上升沿就加一。

  3. 自动重装载寄存器:存储计数的最大值,到达此值后触发中断并清零计数器。

折线UI:向上的折线箭头表示该位置会产生中断信号——“更新中断”(由计数值等于自动重装值产生的中断),这个中断信号会通向NVIC

折线U:向下的折线箭头表示该位置会产生事件——“更新事件”,这个更新事件不会触发中断,但可以触发内部其他电路的工作。比如将更新事件映射到触发输出TRGO,然后TRGO直接接到DAC的触发转换引脚上。

主模式触发DAC数模转换功能:

stm32的一大特色就是主从触发模式,可以让内部的硬件在不受程序的控制下自动运行,可以极大地减轻CPU的负担。

驱动DAC正常思路及其问题:

每隔一段时间就产生一个定时中断,手动更新DAC的值。但这样子会频繁的产生中断,会影响主程序的运行和其他中断的响应。

解决方法:

定时器设计了一个主模式,使用主模式可以将定时器的“更新事件”映射到“触发输出TRGO”,然后TRGO直接接到DAC的触发转换引脚上,于是定时器的更新就不需要中断来实现了。整个过程无需软件参与,实现了硬件的自动化。

3、 通用定时器

img

时基单元:PSC预分频器、CNT计数器、自动重装寄存器。通用定时器和高级定时器新增两个功能:向下计数模式、中央对齐计数模式。

  1. 向上计数模式(常用):从0开始累加,到自动重装值触发中断。

  2. 向下计数模式:从自动重装值递减,到0触发中断

  3. 中央对齐计数模式:从0开始累加,到自动重装载值触发中断,然后递减,到0再次触发中断。常用于电机控制的SVPWM算法中。

内外时钟源选择和主从触发模式结构:上面的一大块。下面介绍各种各样的内外时钟源:

定时器中断基本结构:

img

时基单元:中间的粉色部分。

运行控制:控制寄存器的一些位,如启动停止、向上或向下计数等,操作这些寄存器就可以控制时基单元的运行了。

内部时钟模式、外部时钟模式2、外部时钟模式1:外部时钟源选择。这个选择器的输出就是为时基单元提供时钟。

编码器模式:编码器独有的模式,一般用不到。

中断申请控制:由于定时器内部有很多地方要申请中断,“中断申请控制”就用来使能控制这些中断是否使能。比如,中断信号会先在状态寄存器里置一个中断标志位,这个标志位会通过中断输出控制,到NVIC申请中断。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
/**
* @brief 将指定定时器模块的所有寄存器重置为默认值。
* @param TIMx: 要重置的定时器模块,例如 TIM1、TIM2 等。
* @retval 无
*/
void TIM_DeInit(TIM_TypeDef* TIMx);

/**
* @brief 配置指定定时器模块的时间基准参数。
* @param TIMx: 要配置的定时器模块。
* @param TIM_TimeBaseInitStruct: 包含时间基准配置信息的结构体指针。
* @retval 无
*/
void TIM_TimeBaseInit(TIM_TypeDef* TIMx, TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

/*** 初识化时钟 ***
* @brief 初始化 TIM_TimeBaseInitStruct 结构体为默认值。
* @param TIM_TimeBaseInitStruct: 要初始化的时间基准配置结构体。
* @retval 无
*/
void TIM_TimeBaseStructInit(TIM_TimeBaseInitTypeDef* TIM_TimeBaseInitStruct);

/*** 启用或禁用指定定时器模块 ***
* @brief 启用或禁用指定定时器模块。
* @param TIMx: 要启用或禁用的定时器模块。
* @param NewState: 期望的状态,可以是 ENABLE(启用)或 DISABLE(禁用)。
* @retval 无
*/
void TIM_Cmd(TIM_TypeDef* TIMx, FunctionalState NewState);

/*** 中断功能 ***
* @brief 配置指定定时器模块的中断功能。
* @param TIMx: 要配置的定时器模块。
* @param TIM_IT: 要配置的中断源,例如 TIM_IT_Update。
* @param NewState: 期望的状态,可以是 ENABLE(启用)或 DISABLE(禁用)。
* @retval 无
*/
void TIM_ITConfig(TIM_TypeDef* TIMx, uint16_t TIM_IT, FunctionalState NewState);

/*** 配置为内部时钟 ***
* @brief 配置指定定时器模块的时钟源为内部时钟。
* @param TIMx: 要配置的定时器模块。
* @retval 无
*/
void TIM_InternalClockConfig(TIM_TypeDef* TIMx);

/**
* @brief 配置外部时钟源作为指定定时器模块的时钟输入。
* @param TIMx: 要配置的定时器模块。
* @param TIM_InputTriggerSource: 外部时钟源的触发源,例如 TIM_TS_ITR0。
* @retval 无
*/
void TIM_ITRxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_InputTriggerSource);

/**
* @brief 配置外部时钟源作为指定定时器模块的时钟输入,以及外部时钟源的极性和输入滤波器。
* @param TIMx: 要配置的定时器模块。
* @param TIM_TIxExternalCLKSource: 外部时钟源的触发源,例如 TIM_TIxExternalCLK1Source_TI1。
* @param TIM_ICPolarity: 外部时钟源的极性,可以是 TIM_ICPolarity_Rising(上升沿)或 TIM_ICPolarity_Falling(下降沿)。
* @param ICFilter: 外部时钟源的输入滤波器值。
* @retval 无
*/
void TIM_TIxExternalClockConfig(TIM_TypeDef* TIMx, uint16_t TIM_TIxExternalCLKSource, uint16_t TIM_ICPolarity, uint16_t ICFilter);

/**
* @brief 配置 TIM 为外部时钟模式 1,设置外部触发源的预分频器、极性和输入滤波器。
* @param TIMx: 要配置的定时器模块。
* @param TIM_ExtTRGPrescaler: 外部触发源的预分频器,例如 TIM_ExtTRGPSC_OFF。
* @param TIM_ExtTRGPolarity: 外部触发源的极性,可以是 TIM_ExtTRGPolarity_Inverted(反相)或 TIM_ExtTRGPolarity_NonInverted(非反相)。
* @param ExtTRGFilter: 外部触发源的输入滤波器值。
* @retval 无
*/
void TIM_ETRClockMode1Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

/**
* @brief 配置定时器 TIM 为外部时钟模式 2,设置外部触发源的预分频器、极性和输入滤波器。
* @param TIMx: 要配置的定时器模块。
* @param TIM_ExtTRGPrescaler: 外部触发源的预分频器,例如 TIM_ExtTRGPSC_OFF。
* @param TIM_ExtTRGPolarity: 外部触发源的极性,可以是 TIM_ExtTRGPolarity_Inverted(反相)或 TIM_ExtTRGPolarity_NonInverted(非反相)。
* @param ExtTRGFilter: 外部触发源的输入滤波器值。
* @retval 无
*/
void TIM_ETRClockMode2Config(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);



/**
* @brief 配置定时器 TIM 的外部触发输入 (ETR)。可以配置为外部时钟源或外部触发源。
* @param TIMx: 要配置的定时器模块。
* @param TIM_ExtTRGPrescaler: 外部触发源的预分频器,例如 TIM_ExtTRGPSC_OFF。
* @param TIM_ExtTRGPolarity: 外部触发源的极性,可以是 TIM_ExtTRGPolarity_Inverted(反相)或 TIM_ExtTRGPolarity_NonInverted(非反相)。
* @param ExtTRGFilter: 外部触发源的输入滤波器值。
* @retval 无
*/
void TIM_ETRConfig(TIM_TypeDef* TIMx, uint16_t TIM_ExtTRGPrescaler, uint16_t TIM_ExtTRGPolarity, uint16_t ExtTRGFilter);

/**
* @brief 配置定时器 TIM 的预分频器值,以减小时钟频率。
* @param TIMx: 要配置的定时器模块。
* @param Prescaler: 预分频器的值,用于减小输入时钟频率。
* @param TIM_PSCReloadMode: 指定何时重新加载预分频器值,可以是 TIM_PSCReloadMode_Immediate 或 TIM_PSCReloadMode_Update。
* @retval 无
*/
void TIM_PrescalerConfig(TIM_TypeDef* TIMx, uint16_t Prescaler, uint16_t TIM_PSCReloadMode);

/*** 配置定时器 TIM 的计数模式 ***
* @brief 配置定时器 TIM 的计数模式,例如向上计数、向下计数或中央对齐。
* @param TIMx: 要配置的定时器模块。
* @param TIM_CounterMode: 指定计数模式,可以是 TIM_CounterMode_Up、TIM_CounterMode_Down 或 TIM_CounterMode_CenterAligned1 等。
* @retval 无
*/
void TIM_CounterModeConfig(TIM_TypeDef* TIMx, uint16_t TIM_CounterMode);

/**
* @brief 配置是否启用自动重载寄存器 (ARR) 预加载。
* @param TIMx: 要配置的定时器模块。
* @param NewState: 期望的状态,可以是 ENABLE(启用)或 DISABLE(禁用)。
* @retval 无
*/
void TIM_ARRPreloadConfig(TIM_TypeDef* TIMx, FunctionalState NewState);

/**
* @brief 设置定时器 TIM 的计数器值。
* @param TIMx: 要配置的定时器模块。
* @param Counter: 要设置的计数器值。
* @retval 无
*/
void TIM_SetCounter(TIM_TypeDef* TIMx, uint16_t Counter);

/**
* @brief 获取指定定时器模块的当前计数器值。
* @param TIMx: 要获取计数器值的定时器模块。
* @retval 当前计数器值。
*/
uint16_t TIM_GetCounter(TIM_TypeDef* TIMx);

/**
* @brief 获取指定定时器模块的当前预分频器值。
* @param TIMx: 要获取预分频器值的定时器模块。
* @retval 当前预分频器值。
*/
uint16_t TIM_GetPrescaler(TIM_TypeDef* TIMx);

/**
* @brief 检查指定定时器模块标志位是否被设置。
* @param TIMx: 要检查标志位的定时器模块。
* @param TIM_FLAG: 要检查的标志位,例如 TIM_FLAG_Update。
* @retval 标志位的状态,可以是 RESET 或 SET。
*/
FlagStatus TIM_GetFlagStatus(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

/**
* @brief 清除指定定时器模块的标志位。
* @param TIMx: 要清除标志位的定时器模块。
* @param TIM_FLAG: 要清除的标志位,例如 TIM_FLAG_Update。
* @retval 无
*/
void TIM_ClearFlag(TIM_TypeDef* TIMx, uint16_t TIM_FLAG);

/**
* @brief 检查指定定时器模块的中断状态。
* @param TIMx: 要检查中断状态的定时器模块。
* @param TIM_IT: 要检查的中断源,例如 TIM_IT_Update。
* @retval 中断状态,可以是 RESET 或 SET。
*/
ITStatus TIM_GetITStatus(TIM_TypeDef* TIMx, uint16_t TIM_IT);

/**
* @brief 清除指定定时器模块的中断挂起位。
* @param TIMx: 要清除中断挂起位的定时器模块。
* @param TIM_IT: 要清除的中断源,例如 TIM_IT_Update。
* @retval 无
*/
void TIM_ClearITPendingBit(TIM_TypeDef* TIMx, uint16_t TIM_IT);

这些注释提供了对每个函数的功能和参数的清晰解释。如果有任何进一步的疑问,请随时提出。

4、 定时器初始化步骤

  1. 开启RCC内部时钟:首先,您需要启用与所选定时器相关的RCC (Reset and Clock Control) 时钟。这可以通过调用适当的RCC函数来实现,例如 RCC_APB1PeriphClockCmd()RCC_APB2PeriphClockCmd()

  2. 选择时基单元的时钟源:定时器的时基单元需要一个时钟源来进行计数。您可以选择内部时钟源或外部时钟源,具体取决于您的应用。通常,对于定时中断,内部时钟源足够。您可以使用 TIM_InternalClockConfig() 函数来选择内部时钟源。

  3. 配置时基单元:时基单元TIM_TimeBaseInitTypeDefTIM_ClockDivision分频系数、TIM_CounterMode计数方向、TIM_Period自动重装载值、TIM_Prescaler预分频器、TIM_RepetitionCounter重复计数器的值,用于高级定时器

  4. 配置输出中断控制:如果您需要在定时器溢出或到达某个特定值时触发中断,您需要配置输出中断控制。使用 TIM_ITConfig() 函数来启用或禁用定时器的特定中断。

  5. 配置NVIC:在配置输出中断后,您需要在NVIC (Nested Vector Interrupt Controller) 中启用定时器中断通道,并为该中断通道分配一个优先级。使用 NVIC_Init() 函数来完成此操作。

  6. 运行控制:最后,您可以使用 TIM_Cmd() 函数来启动或停止定时器的计数。如果您已正确配置了时基单元、中断和NVIC,则定时器将在满足特定条件时触发中断。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    void Timer_Init(void)
    {
    RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
    // 开启TIM2的时钟
    TIM_InternalClockConfig(TIM2);
    // 配置TIM2的时钟源为内部时钟
    TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
    // 配置TIM2的时间基准参数
    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;
    // 预分频器的值,决定了计数器时钟的频率
    //72M对7200分频,得到的就是10K的计数频率,在10的频率下计一万个数就是1s的时间
    TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 0;
    // 重复计数器的值,用于高级定时器,TIM2为基本定时器,可以忽略
    TIM_TimeBaseInit(TIM2, &TIM_TimeBaseInitStructure);

    TIM_ClearFlag(TIM2, TIM_FLAG_Update);
    // 清除TIM2的更新标志位
    TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
    // 使能TIM2的更新中断

    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);
    // 配置NVIC,设置中断优先级
    NVIC_InitTypeDef NVIC_InitStructure;
    NVIC_InitStructure.NVIC_IRQChannel = TIM2_IRQn;
    // 中断通道为TIM2
    NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
    // 使能中断通道
    NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 2;
    // 抢占优先级
    NVIC_InitStructure.NVIC_IRQChannelSubPriority = 1;
    // 子优先级
    NVIC_Init(&NVIC_InitStructure);

    // 启动TIM2定时器
    TIM_Cmd(TIM2, ENABLE);
    }

    这段代码完成了以下操作:

    1. 打开TIM2的时钟,确保TIM2可以正常工作。
    2. 配置TIM2的时钟源为内部时钟,即使用内部时钟作为计数器的时钟源。
    3. 配置TIM2的时间基准参数,包括时钟分频因子、计数模式、周期和预分频器的值。这些参数决定了TIM2的工作频率和计数周期。
    4. 清除TIM2的更新标志位,以确保不会立即触发中断。
    5. 使能TIM2的更新中断,以便在计数器溢出时触发中断。
    6. 配置NVIC,为TIM2中断通道分配抢占优先级和子优先级。
    7. 启动TIM2定时器,开始计数。

    最终,TIM2将以预定的周期和预分频器配置工作,并在计数器溢出时触发TIM2_IRQn中断,执行与中断相关的处理程序。

5、 TIM输出比较原理

输出比较(Output Compare,简称OC)是STM32定时器功能中的一种重要特性,用于生成PWM(脉冲宽度调制)信号或其他需要定时控制的输出信号。下面是关于输出比较的一些要点:

  1. 用途:输出比较主要用于生成PWM信号,其中包括控制信号的占空比和频率。除了PWM之外,它还可以用于产生其他类型的定时控制信号,如脉冲信号、方波信号等。
  2. 比较操作:输出比较的原理是将定时器的计数值(通常是CNT寄存器的值)与比较寄存器(通常是CCR寄存器)的值进行比较。根据比较的结果,可以执行以下操作:
    • 当计数器的值小于CCR寄存器的值时,输出置1。
    • 当计数器的值等于CCR寄存器的值时,输出置0。
    • 当计数器的值大于CCR寄存器的值时,输出保持不变(可以是1或0,取决于配置)。
  3. 通道数量:每个高级定时器和通用定时器都拥有多个输出比较通道,通常有4个。这意味着你可以同时控制多个不同的输出信号。
  4. PWM波形生成:通过适当配置输出比较通道的CCR寄存器值,你可以生成具有不同占空比的PWM波形。这对于驱动电机、LED灯和其他需要精确控制的应用非常有用。
  5. 高级定时器功能:高级定时器(如TIM1和TIM8)的输出比较通道具有额外的功能,包括死区生成和互补输出。死区生成用于防止同一通道上的两个PWM信号同时处于高电平或低电平状态,以防止短路。互补输出用于产生互补的PWM信号,可用于驱动半桥或全桥电路。

CCR:Capture Compare,捕获比较寄存器。

脉冲宽度调制(PWM,Pulse Width Modulation)是一种用于控制模拟信号的技术,通常用于模拟电子电路中,特别是用于控制电机速度、LED亮度和音频信号等应用。以下是一些关于PWM的重要概念:

  1. 频率(Frequency):PWM信号的频率是指PWM周期的倒数,通常以赫兹(Hz)为单位表示。频率决定了PWM波形的周期,即PWM信号从低电平到高电平再到低电平的一个完整周期需要多少时间。频率一般在几千赫兹(kHz)到几十千赫兹(十几kHz或百kHz)之间。

  2. 占空比(Duty Cycle):占空比是PWM信号中高电平的持续时间与一个周期内的总时间之比。它表示了PWM信号中高电平的相对时间。占空比通常以百分比表示,例如,如果PWM的周期是10毫秒,而高电平持续时间是2毫秒,那么占空比为20%。

  3. 分辨率(Resolution):分辨率是指PWM信号占空比变化的精细程度,通常以位数表示。例如,8位分辨率的PWM可以将占空比分成256个不同的级别,每个级别之间的占空比差异是1/256,即约0.39%。分辨率越高,可以表示的占空比级别就越多,PWM信号的精度也就越高。

  4. 定时中断:在STM32定时器中,PWM的频率由定时中断的频率决定。通过调整定时器的预分频器、自动重装载值和计数模式,可以控制定时中断的频率,从而影响PWM信号的频率。

  5. 自动重装载值:自动重装载值(ARR,Auto-Reload Register)决定了PWM信号的周期。它表示PWM信号从低电平到高电平再到低电平的一个完整周期的时间。通过调整ARR的值,可以改变PWM信号的频率。

总之,PWM是一种强大的信号调制技术,用于控制模拟电路中的各种设备。通过调整频率和占空比,可以实现对输出信号的精确控制,以满足不同应用的需求。在STM32等微控制器中,PWM通常由定时器模块生成,可以根据需要进行配置和调整。

6、 通用定时器输出比较通道

img

REF:reference,参考信号

输出比较模式:

img

置有效电平:置高电平

置无效电平:置低电平

PWM模式1:常用向上计数的方式