定时器编码器模式读取脉冲数据

本小节教你使用 STM32CubeMX 配置定时器的编码器模式,读取电机编码器的脉冲数据,并且利用 Printf 函数把数据发送到电脑端上位机显示出来。

预先了解

电机接口电路原理图
Image 5.9.1 - 电机接口电路原理图

通过上面的电机接口电路原理图可以看到,「小霸王Lite」两轮自平衡小车使用两个通用定时器(TIM2、TIM4)分别捕获两个电机上的编码器的脉冲数据。

STM32F10x 系列 MCU 的所有通用定时器及高级定时器都集成了编码器接口。定时器的两个输入 TI1 和 TI2 分别与增量式编码器的 A 相、B 相相接。当定时器设为编码器模式时,这两个信号的边沿作为计数器的时钟。在这个模式下,计数器依照增量编码器的速度和方向被自动地修改。

具体步骤

接下来,我们以通用定时器 TIM4 配置编码器模式为例,进行具体的讲解。定时器 TIM4 的正交模式与 TIM2 的配置过程是一样的。

进入我们上一小节修改过的 MiaowLabs-Demo 文件夹,找到 MiaowLabs-Demo.ioc 工程文件,双击,打开工程。在左侧 Pinout&Configuration 界面中的 Timers 下拉中点击 TIM4,然后在 TIM4 Mode and Configuration 的 Mode 中将 Combined Channels 选择为 Encoder Mode,即编码器模式。

将 Combined Channels 选择为 Encoder Mode
Image 5.9.2 - 将 Combined Channels 选择为 Encoder Mode

在 Configuration 中选择 Parameter Setting 选项卡,进行基本参数配置。其中,Counter Mode 默认为 Up,即向上计数。Counter Period 设置为 65535,即计数器周期,这是一个 16 位的自动加载寄存器,填写范围为 0~65535。Encoder Mode 设置为 Encoder Mode TI1 and TI2,即两个输入 TI1 和 TI2 都被用来作为增量编码器的接口。Polarity 默认为 Rising Edge,即为捕获上升沿。其他参数默认即可。

值得注意的是,Encoder Mode 设置为 Encoder Mode TI1 and TI2 模式时,AB 两相的上升沿和下降沿都会计数,所以计数值是实际脉冲值的 4 倍,即四倍频。Channel1 和 Channel2 的 Polarity 参数默认是 Rising Edge,意思是在检测到上升沿的时候就触发编码器模式接口捕获 AB 相的值,并不是指只检测 AB 相的上升沿,下降沿还是同样会计数的。

Parameter Settings 选项卡的参数配置
Image 5.9.3 - Parameter Settings 选项卡的参数配置

点击 GENERATE CODE,重新生成代码。

重新生成代码
Image 5.9.4 - 重新生成代码

打开 MDK-ARM 工程,左侧 Application/User 里多个 tim.c 源文件,而且在 main 函数里可以看到多出一个 TIM4 初始化函数: MX_TIM4_Init();

代码截图
Image 5.9.5 - 代码截图

打开 MDK-ARM 工程,按下组合键 Ctrl+N(按住 Ctrl 键再按 N 键),新建一个文件,再按下组合键 Ctrl+S,文件名改为 encoder.c,保存到 MiaowLabs-DEMO 的 Src 文件夹里,接着在 MDK-ARM 工程界面左侧 Project 栏目双击 Application/User 文件夹,把 encoder.c 加进来。

代码截图
Image 5.9.6 - 代码截图

双击 encoder.c 文件,把下面代码敲进去。(尽可能手动敲一遍)

#include "tim.h"//包含tim头文件
#include "encoder.h"

int iTim4Encoder;//存放从TIM4定时器读出来的编码器脉冲

int GetTim4Encoder(void)//获取TIM4定时器读出来的编码器脉冲
{
    iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先读取脉冲数
    __HAL_TIM_SET_COUNTER(&htim4,0);//再计数器清零
    return iTim4Encoder;//返回脉冲数
代码截图
Image 5.9.7 - 代码截图

再新建一个文件 encoder.h 头文件,把文件保存到 Inc 文件夹。然后,把下面代码敲进去。

#ifndef __ENCODER_H
#define __ENCODER_H

int GetTim4Encoder(void);//声明函数

#endif

打开 main.c 文件,在 main 函数中的 /* USER CODE BEGIN 1 *//* USER CODE END 1 */ 之间定义一个变量:

int iTempTim4Encoder; //临时存放从TIM4编码器接口捕获到的脉冲数据
代码截图
Image 5.9.8 - 代码截图

在主循环里,把以下代码敲进去:

HAL_Delay(5000);//延时5秒
iTempTim4Encoder = GetTim4Encoder();//捕获TIM4脉冲数据
printf("TIM4定时器编码器模式捕获脉冲 = %d \n",iTempTim4Encoder);//把脉冲数据打印出来
HAL_GPIO_TogglePin(LED_GPIO_Port,LED_Pin);//翻转指示灯LED的电平

上面这段加入主循环的代码的意思,是 TIM4 定时器的编码器捕获脉冲,每隔 5 秒累计输出一次脉冲数据,通过串口显示在上位机上。

这里补充一个知识点:M 法测速,就是数固定时间内产生的脉冲数,像这里每隔 5 秒数一次累计起来的脉冲数据,就是用了 M 法测速。

代码还没写完,有一句重要的代码必须要加进去,TIM4 的编码器接口模式才会启用。我们在 main 函数的 /* USER CODE BEGIN 2 *//* USER CODE END 2 */ 之间加入:

HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);//开启TIM4的编码器接口模式

然后在 MDK-ARM 中重新编译代码,把代码烧录进 MiaowLabs-STM32F1-Micro 核心板,将核心板插到「小霸王Lite」两轮自平衡小车底板上,将整辆小车组装好,并用数据线连接核心板和电脑,打开喵呜地面站或其他串口助手软件,我们用手转动 TIM4 对应的电机轮子,就能够看到小车每隔 5 秒就将脉冲数据发送上去。

串口助手截图
Image 5.9.9 - 串口助手截图

我们上面提到,得到的数据是四倍频的数据,「小霸王Lite」上用的电机单相单圈脉冲为 384 个脉冲,四倍频后,即 384 x 4 = 1536 个脉冲。可以从上面看到,当我们用手转动轮子半圈时,得到了 768 个脉冲,当然,我们用手转动轮子是无法精准地转动到理想位置(比如无法精确地转动半圈),会有偏差,但我们只要得到个大概的数据,判断能否正常读取编码器数据就可以了。

串口助手截图
Image 5.9.10 - 串口助手截图

反应快的好奇宝宝在这里可能会提问:在上面的代码中,并没有对数值进行正负判断赋值,脉冲值怎么会出现负数?

如果 TI1 和 TI2 分别接电机的 A 相和 B 相的话,那么,当电机正转的时候,如下图计数器会向上计数,反转的时候会向下计数,但是向下计数并不会出现负的值,依旧是从(0-ARR)计数。

计数方向与编码器信号的关系
Image 5.9.11 - 计数方向与编码器信号的关系

但是为什么出现一个负数呢?计数器的技术范围明明是 0~ARR。让我们回头看获取编码器脉冲的代码:

int GetTim4Encoder(void)//获取TIM4定时器读出来的编码器脉冲
{
    iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4));//先读取脉冲数
    __HAL_TIM_SET_COUNTER(&htim4,0);//再计数器清零
    return iTim4Encoder;//返回脉冲数

注意看,上面的代码 iTim4Encoder = (short)(__HAL_TIM_GET_COUNTER(&htim4)); 使用了强制类型装换,把寄存器的值读出来了之后,转换成了 short 型(2 字节),范围为(-32768-32767),此时当我们把计数器的初始值设置为 0 之后,如果出现反转,它就会从 0 开始向下计数(0,65535,65534,...)但是经过强制类型转换之后就变成了(0,-1,-2,...)。

有些好奇宝宝可能不明白,为什么 65535 会变成 -1 ?此时我们回到 short 的表示范围(-32768-32767),也就是说原来 int 型变量当读出来的值为 32767, 32768, 32769,...,65535,65536,65537... 的时候会因为强制转换成 short 型变量而溢出转换为 32767,-32768,-32767,...,-1,0,1 就这样不断地循环下去。所以电机反转的时候读出的数就是反方向的速度值,不需要用 65535 去减去读出的值再加上负号才可以得到方便观察的值,我们只需要巧妙地运用一个强制类型转换就可以了。