定时器
# 定时器工作参数
# 定时器的三个寄存器
TIMx_PSC
名称 预分频寄存器 作用 将时钟信号预分频,分频后的频率为定时器自增的频率 记录的值 预分频的比值 减一:0 例如时钟输入的频率为600 Hz,当预分频的设置的值为
100:1
时,定时器自增的频率就是6 Hz为什么形式是
预分频的比值减一:0
呢,因为其内部的值是从0开始的,上面的100:1
储存的就是99:0
TIMx_CNT
名称 计数寄存器 作用 按照预分频后的频率自增 记录的值 自增的次数(到设定值后清空) TIMx_ARR
名称 自动重装载寄存器 作用 记录重装载值,计数器达到此值后自动清空 记录的值 重装载值 减一 为什么形式是
重装载值减一
呢,也是因为其内部的值是从0开始的
# 定时器的工作
由上面的三个寄存器可以知道:寄存器的工作就是按照一定的频率自增,增加到一定值时清空,然后再自增... ...
在清空计数值的同时,会触发一次定时器中断,即定时器更新中断。只要设定好定时器的重载值,就可以保证定时器中断以固定的频率被触发
# 定时器的中断频率计算
符号 | 意义 |
---|---|
需要的中断频率 | |
原始的总线给定时器输入的频率 | |
预分频后定时器的自增频率 | |
TIMx_PSC 内存储的值(比例减一) | |
TIMx_ARR 内存储的值(重装载值减一 ) |
如果用预分频后的频率来表示中断频率,则是:
但是使用原始的总线输入频率则为:
简单记就是:
警告
在配置参数的时候,预分频值与重装载值一定要记得写为减一的形式,避免发生错误
# 中断优先级
为了在有限的寄存器位数中实现更加丰富的中断优先级,NVIC使用了中断分组机制。STM32将先将中断进行分组,然后又将优先级划分为抢占优先级 (Prem priority) 和响应优先级 (Subpriority),抢占优先级和响应优先级的数量均可以通过 NVIC 中 AIRCR 寄存器的 PRIGROUP[8:10] 位进行配置,从而规定了两种优先级对 NVIC_IPRx[7:4] 的划分,根据划分决定两种优先级的数量。
# NVIC 优先级组
在 NVIC_IPRx[7:4] 这4个比特的位置,总共可以有16种中断的配置。但是哪几位决定抢占优先级,哪几位决定响应优先级呢?我们可以根据这四个比特的分配方式将优先级祖分为五种情况,当然,只能使用一组来对工程中的中断们进行优先级划分:
- 第 0 组:所有 4 位用来配置响应优先级。即16 种中断向量具有都不相同的响应优先级
- 第 1 组:最高 1 位用来配置抢占优先级,低 3 位用来配置响应优先级。表示有 2 种级别的抢占优先级(0 级,1 级),有 8 种响应优先级,即在16 种中断向量之中,有 8 种中断,其抢占优先级都为 0 级,而它们的响应优先级分别为0~7,其余 8 种中断向量的抢占优先级则都为 1 级,响应优先级别分别为0~7
- 第 2 组:2 位用来配置抢占优先级,2 位用来配置响应优先级。即4 种抢占优先级,4 种响应优先级
- 第 3 组:高 3 位用来配置抢占优先级,最低 1 位用来配置响应优先级。即有8 种抢占优先级,2 种响应优先级
- 第 4 组:所有 4 位用来配置抢占优先级,即NVIC 配置的16 种中断向量都是只有抢占属性,没有响应属性
# 抢占与响应优先级
可以举这样一个例子来说明二者的工作方式,有这样三个中断 A, B, C:
中断向量 | 抢占优先级 | 响应优先级 |
---|---|---|
A | 0 | 0 |
B | 1 | 0 |
C | 1 | 1 |
当中断 C 正在执行的时候,它能被抢占优先级更高的中断 A 打断,由于 B 和 C 的抢占优先级相同,所以 C 不能被 B 打断。但如果 B 和 C 中断是同时到达的,内核就会首先响应响应优先级别更高的 B 中断
注意
NVIC 能配置的是16 种中断向量,而不是16 个,当工程中有超过 16 个中断向量时,必然有两个以上的中断向量是使用相同的中断种类,而具有相同中断种类的中断向量不能互相嵌套
# 定时器闪烁LED实践配置
# CubeMX
对于 GPIO 的配置不必多说了,对于定时器的配置如下:
首先需要对一个定时器使能,转到 TIM 栏选择一个定时器并将其 Clock Souce
改为 Internal Clock:
可以得出 TIM1_RSC 的预分频倍数与 TIM1_ARR 的重装载数,随后回到定时器配置页面输入这两个值:
最后则要进入 NVIC 页面,将 `TIM1 update interrupt`这个中断允许进行,将 Enable 处打勾即可,也可以在这个页面上对这个中断的分组以及两个优先级进行调整,这里由于只有一个中断所以保持默认: 随后生成代码。# 工程代码
在生成的工程代码中的stm32f4xx_it.c
中,有一个 CubeMX 为我们生成的中断处理函数
void TIM1_UP_TIM10_IRQHandler(void)
{
/* USER CODE BEGIN TIM1_UP_TIM10_IRQn 0 */
/* USER CODE END TIM1_UP_TIM10_IRQn 0 */
HAL_TIM_IRQHandler(&htim1);
/* USER CODE BEGIN TIM1_UP_TIM10_IRQn 1 */
/* USER CODE END TIM1_UP_TIM10_IRQn 1 */
}
2
3
4
5
6
7
8
9
10
它调用了一个名为HAL_TIM_IRQHandler
的函数,这个函数是 HAL 库要对涉及中断的寄存器处理的函数
void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
函数名 | HAL_TIM_IRQHandler |
---|---|
函数作用 | HAL对涉及(定时器)中断的寄存器进行处理 |
返回值 | void |
参数 | *htim 定时器的句柄指针,如定时器1就输入&htim1 ,定时器2就输入&htim2 |
在这个函数对寄存器进行配置之后,程序将会自动调用中断回调函数
_weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
和上面一样,它的参数也是定时器的句柄指针,我们在main.c
中就是需要重新将中断的内容写入这个函数。但是在执行中断操作之前,一般要确定一下,中断信号来源是否正确。做一个来源的判断即可
注意
但是还是缺一点!还是有关键的两个函数:HAL_TIM_Base_Start()
与HAL_TIM_Base_Start_IT()
HAL_TIM_Base_Start()
是仅仅将定时器开启,HAL_TIM_Base_Start_IT()
是将定时器开启且允许中断。而HAL_TIM_Base_Start()
,所以为了使能中断,还需要再main
函数中使用HAL_TIM_Base_Start_IT()
来开启中断
它的参数也是定时器的句柄指针,所以填入&htim1
对 TIM1 中断使能即可
详细的两个函数用法请查看此处
但是,请注意:这两个函数一定不要放在这些函数的前面!
MX_GPIO_Init();
MX_TIMx_Init();
2
看了这些函数的名字就知道了,开启和使能定时器一定要在系统定时器初始化之后,不然会报错的
最后的main.c
是这个样子的 (opens new window)
# 程序流程
# 定时器的编码器模式
# 编码器信号
主要是电机等的编码器输出的正交的两个信号A相与B相的信号。编码器每转动固定的位移,就会产生一个脉冲信号通过读取单位时间脉冲信号的数量,便可以达到测速的效果 (
# 信号参数
A 脉冲输出
B 脉冲输出
Z 零点信号
当编码器旋转到零点时,Z 信号会发出一个脉冲表示现在是零位置 表示编码器转了 1 圈,可用来记录编码器转了多少圈,从而知道运行距离
VCC 电源线
GND 地线
编码器线数
编码器的线数 ,是说编码器转一圈输出多少个脉冲,如果一个编码器是500线,说明这个编码器转一圈对应的信号线会输出500个脉冲, A B两相转一圈发出的脉冲数是一样的,不过存在90°相位差 。且线数越高代表能够反映的位置精度越高
# 编码器模式计数原理
这张图几乎就是所有了,但是不是很好看懂,所以接下来我们来解释一下这张图:
大体上编码器模式是三种模式:
- 仅在 TL1 计数(A 相)
- 仅在 TL2 计数(B 相)
- 在 TL1 与 TL2 均计数(A,B 相均计数)
从第二列开始后面的要一行一行的看。具体怎么读这个图呢?以第一行为例:
由于仅在 TL1 计数,所以“高”代表的是 B 相(TL2)为高电平时,此时如果 A 相(TL1)为上升沿,则代表电机在反转,所以此时为向下计数;如果此时A 相(TL1)为下降沿,则代表电机在正转,所以此时为向上计数
向上计数与向下计数
如果我们设置的默认计数模式为UP
,则默认模式下的向上计数为从 0 开始向上数数
向下计数则是从目前值值开始向下计数,当目前值为 0 时,则将会从自动重载值开始向下计数
所以“仅在 TL1 计数”这个模式下的所有情况(四种)即为:
TI2(B相)为高电平时:
- 1时刻:TL1(A相)下降沿, 则向上计数(正转)
- 2时刻:TL1(A相)上升沿, 则向下计数(反转)
TI2(B相)为低电平时:
- 3时刻:TL1(A相)上升沿, 则向上计数(正转)
- 4时刻:TL1(A相)下降沿, 则向下计数(反转)
且对于两个相均计数的模式下,一行实际上对应的是分别以 TL1、TL2 为主信号的两种情况。且主信号为谁就只看谁的那两列
例如:假定主信号就是 TL1,所以对应的相对信号是 TL2,那 TL2 的电平状态通过第二列确定后,肯定就要看 TL1 的上升下降沿来判断状态了!
# 硬件实现的效果
通过以上解释的原理,我们的编码器模式实现的效果如下,就是可以过滤一些由于噪声引起的毛刺信号,不被计入编码器计数值中
# 注意事项
- 需要增加测量的精度时,可以采用4倍频方式,即分别在A、B相波形的上升沿和下降沿计数,分辨率可以提高4倍
- 如果只是测速,不要求方向,那么只需要用单片机随意选择一个信号线就行了,然后定时器边沿触发,检测脉冲计数即可
- 一般是定时器的通道1和2才能作为编码器输入口,对应编码器输出的两相
- GPIO配置为配置为上拉输入模式
- 可以使用强制类型转化为
short
类型将纯向下计数变为负值以表示反转(当然,是当自动重载值为65535时)
# 参考
[1] Robomaster 开发板 C 型教程
[2] 野火《零死角玩转 STM32》