Wiki:知识&笔记 Wiki:知识&笔记
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
首页
  • 学习笔记

    • 《JavaScript教程》笔记
  • 嵌入式

    • STM32
  • 技术文档
归档
GitHub (opens new window)
  • 基础

  • 底层&寄存器

  • HAL库外设

    • HAL库函数
    • HAL结构
    • GPIO各种点亮LED
    • 定时器
      • 定时器工作参数
        • 定时器的三个寄存器
        • 定时器的工作
        • 定时器的中断频率计算
      • 中断优先级
        • NVIC 优先级组
        • 抢占与响应优先级
      • 定时器闪烁LED实践配置
        • CubeMX
        • 工程代码
        • 程序流程
      • 定时器的编码器模式
        • 编码器信号
        • 信号参数
        • 编码器模式计数原理
        • 硬件实现的效果
        • 注意事项
      • 参考
    • PWM控制LED颜色
    • 按键外部中断
    • ADC使用
    • 串口与DMA
    • FLASH 读写
    • IIC通信
    • SPI 通信
    • CAN
  • STM32
  • HAL库外设
2020-08-23
定时器工作参数
定时器的三个寄存器
定时器的工作
定时器的中断频率计算
中断优先级
NVIC 优先级组
抢占与响应优先级
定时器闪烁LED实践配置
CubeMX
工程代码
程序流程
定时器的编码器模式
编码器信号
信号参数
编码器模式计数原理
硬件实现的效果
注意事项
参考

定时器

# 定时器工作参数

# 定时器的三个寄存器

  • 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 的 AHB2 总线的定时器频率为 168 MHz:
根据刚刚学会的计算方法:

可以得出 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 */
}
1
2
3
4
5
6
7
8
9
10

它调用了一个名为HAL_TIM_IRQHandler的函数,这个函数是 HAL 库要对涉及中断的寄存器处理的函数

void HAL_TIM_IRQHandler(TIM_HandleTypeDef *htim)
1
函数名 HAL_TIM_IRQHandler
函数作用 HAL对涉及(定时器)中断的寄存器进行处理
返回值 void
参数 *htim 定时器的句柄指针,如定时器1就输入&htim1,定时器2就输入&htim2

在这个函数对寄存器进行配置之后,程序将会自动调用中断回调函数

_weak void HAL_TIM_PeriodElapsedCallback(TIM_HandleTypeDef *htim)
1

和上面一样,它的参数也是定时器的句柄指针,我们在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();
1
2

看了这些函数的名字就知道了,开启和使能定时器一定要在系统定时器初始化之后,不然会报错的

最后的main.c是这个样子的 (opens new window)

# 程序流程

# 定时器的编码器模式

# 编码器信号

主要是电机等的编码器输出的正交的两个信号A相与B相的信号。编码器每转动固定的位移,就会产生一个脉冲信号通过读取单位时间脉冲信号的数量,便可以达到测速的效果 (),通过对脉冲信号的累加,和编码器的码盘的周长 (转一圈对应距离) 便可以达到计算行走距离的效果 ()

# 信号参数

  • A 脉冲输出

  • B 脉冲输出

  • Z 零点信号

    当编码器旋转到零点时,Z 信号会发出一个脉冲表示现在是零位置 表示编码器转了 1 圈,可用来记录编码器转了多少圈,从而知道运行距离

  • VCC 电源线

  • GND 地线

  • 编码器线数

    编码器的线数 ,是说编码器转一圈输出多少个脉冲,如果一个编码器是500线,说明这个编码器转一圈对应的信号线会输出500个脉冲, A B两相转一圈发出的脉冲数是一样的,不过存在90°相位差 。且线数越高代表能够反映的位置精度越高

# 编码器模式计数原理

tim_mode

这张图几乎就是所有了,但是不是很好看懂,所以接下来我们来解释一下这张图:

大体上编码器模式是三种模式:

  • 仅在 TL1 计数(A 相)
  • 仅在 TL2 计数(B 相)
  • 在 TL1 与 TL2 均计数(A,B 相均计数)

从第二列开始后面的要一行一行的看。具体怎么读这个图呢?以第一行为例:

由于仅在 TL1 计数,所以“高”代表的是 B 相(TL2)为高电平时,此时如果 A 相(TL1)为上升沿,则代表电机在反转,所以此时为向下计数;如果此时A 相(TL1)为下降沿,则代表电机在正转,所以此时为向上计数

向上计数与向下计数

如果我们设置的默认计数模式为UP,则默认模式下的向上计数为从 0 开始向上数数

向下计数则是从目前值值开始向下计数,当目前值为 0 时,则将会从自动重载值开始向下计数

所以“仅在 TL1 计数”这个模式下的所有情况(四种)即为:

tl1_mode
  • TI2(B相)为高电平时:

    • 1时刻:TL1(A相)下降沿, 则向上计数(正转)
    • 2时刻:TL1(A相)上升沿, 则向下计数(反转)
  • TI2(B相)为低电平时:

    • 3时刻:TL1(A相)上升沿, 则向上计数(正转)
    • 4时刻:TL1(A相)下降沿, 则向下计数(反转)

且对于两个相均计数的模式下,一行实际上对应的是分别以 TL1、TL2 为主信号的两种情况。且主信号为谁就只看谁的那两列

例如:假定主信号就是 TL1,所以对应的相对信号是 TL2,那 TL2 的电平状态通过第二列确定后,肯定就要看 TL1 的上升下降沿来判断状态了!

# 硬件实现的效果

code_mode

通过以上解释的原理,我们的编码器模式实现的效果如下,就是可以过滤一些由于噪声引起的毛刺信号,不被计入编码器计数值中

# 注意事项

  • 需要增加测量的精度时,可以采用4倍频方式,即分别在A、B相波形的上升沿和下降沿计数,分辨率可以提高4倍
  • 如果只是测速,不要求方向,那么只需要用单片机随意选择一个信号线就行了,然后定时器边沿触发,检测脉冲计数即可
  • 一般是定时器的通道1和2才能作为编码器输入口,对应编码器输出的两相
  • GPIO配置为配置为上拉输入模式
  • 可以使用强制类型转化为 short 类型将纯向下计数变为负值以表示反转(当然,是当自动重载值为65535时)

# 参考

[1] Robomaster 开发板 C 型教程

[2] 野火《零死角玩转 STM32》

[3] 定时器---正交解码编码器模式详解 (opens new window)

[4] 定时器编码器模式读取脉冲数据 (opens new window)

编辑 (opens new window)
上次更新: 2020/11/18, 13:11:00
GPIO各种点亮LED
PWM控制LED颜色

← GPIO各种点亮LED PWM控制LED颜色→

Theme by Vdoing | Copyright © 2020-2025 Jack :) | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式