锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

STM32应用开发实践教程:智能小车电机测速模块的应用开发

时间:2022-09-01 12:00:00 电容的s参数转换z参数铣床传感器分辨率系列拉线传感器p脉冲位移传感器pc5n位移传感器电子直线位移传感器转换模块0

3.4.1 任务分析
本任务要求设计一个能够实现智能汽车电机测速的应用程序,具体要点如下。
① 以电机为测速对象。
② 支持按键控制和使用 4 功能描述如下:
? Key1 控制电机正转,若电机当前处于停止状态,按下 Key1 如果电机现在处处正转,则使其正转
按下正转或反转状态 Key1 停止;
? Key2 控制电机反转,如果电机目前停止,按下 Key2 如果电机现在处处反转,就会逆转
按下正转或反转状态 Key2 停止;
? Key3 控制电机减速,如果电机制电机减速,使其正向减速;
? Key4 如果电机目前为正转,则控制电机增加转速,正向加速,反之亦然。
③ 系统可以通过串行通信传输当前的电机转速和码盘计数值到上位机的串口调试
助手。
根据本任务的要求,实现电机测速功能有两个技术要点:一是在电机上安装编码器
二是配置 STM32F4 在输入捕获模式下,对编码器的输出脉冲进行计数,
经过换算后得到电机的转速信息。
本任务涉及的知识点包括:
? 编码器的工作原理;
? 直流电机的速度测量方法;
? STM32F4 编码器接口模式的编程配置方法是系列微控制器的定时器。
3.4.2 知识链接
1.编码器
(1)什么是编码器?
编码器(Encoder)它是一种用于运动控制的传感器。采用光电转换或磁电转换的原理
检测物体的机械位置及其变化,将该信息转换为电信号,然后输出并传输到各种运动控制装置
位置。将角位移转换为电信号的编码器称为码盘
称为码尺。
编码器广泛应用于需要准确定位和速度的场合,如机床、机器人、电机反馈系统和
测量和控制设备等。编码器有以下内容 4 个常见的应用场景。
① 角度测量场景
汽车驾驶模拟器采用光电编码器作为方向盘旋转角度的测量传感器;重力测量仪采用光电编码
编码器将转轴连接到重力测量仪中的补偿旋钮轴;扭转角度计用编码器测量扭转角度的变化,如扭转
摆锤冲击实验机利用编码器计算冲击时摆角的变化。
② 长度测量场景
计米器用滚轮周长测量物体长度;拉线位移传感器用卷轮周长测量物体长度;联合
轴直测方法是通过输出脉冲数来测量测量测量器与动力装置的主轴联轴;介质检测方法是
直线位移信息通过直齿条、链轮旋转、同步带轮等介质传递。
③ 速度测量场景
通过连接编码器和仪码器和仪器来测量生产线的线速;角速测量用编码器测量电机,
转轴等转速。
④ 位置测量场景
机床的应用,记忆机床(如钻床等)各坐标点的坐标位置;自动控制的应用,
控制电梯、电梯等在某个位置指定的动作。
(2)编码器的分类
编码器有多种不同的分类方法。
① 按测量方法分类
编码器可分为旋转编码器和直尺编码器。
旋转编码器通过测量被测物体的旋转角度,将被测旋转角度转换为脉冲电信号输出。
通过测量被测物体的直线行程长度,将被测行程长度转换为脉冲电信号输出。
项目 3 智能小车运动控制系统的设计与实现 131
Chapter 3
② 按编码分类
编码器可分为增量编码器、绝对编码器等。
通过检测和统计信号的通断数,增量编码器用光信号扫描分度盘(分度盘与旋转轴连接)
计算旋转角度。
绝对编码器用光信号扫描分度盘上的格雷码刻度盘,确定被测物体的绝对位置,然后检查
格雷码数据被转换为电信号,并以脉冲的形式输出。
图 3-4-1 显示增量编码器和绝对编码器的码盘。

③ 按检测工作原理进行分类
编码器可分为光电编码器、磁电编码器、电感编码器和电容编码器
接下来,介绍两种常用的编码器(光电编码器和磁电编码器)。
光电编码器是一种集光、机、电为一体的数字检测装置,通过光电转换将机器传输到轴上
将机械位移和几何位移转换为脉冲或数字输出。
磁电编码器(又称霍尔编码器)用磁阻或霍尔元件测量磁性材料的角度或位移值
与光电检测相比,磁电检测具有抗震、抗污染等优点。
图 3-4-2 展示带光电编码器的电机和带磁电编码器的电机。

(3)增量光电编码器的工作原理
接下来主要介绍增量光电编码器的工作原理。图3-4-3 表示增量光电编码器的结构分解。

从图 3-4-3 可以看出,电机轴上装有金属码盘(又称分度盘),码盘上刻有透明度
光源和光电元件安装在光格栅的两侧。
当码盘旋转时,当遇到格栅时,光可以通过,当遇到不透明部分时,光被覆盖。光电元件将光
变成电信号,整形电路处理后变成 A 和 B 两路脉冲。
另外,从图 3-4-3 中可以看到 A 和 B 两路脉冲有相位差,一般相差 90°。
(4)编码器的技术指标
在针对图 3-4-3 解释中提到码盘上刻有格栅。码盘上的格栅条数称为编码器的分割
辨率,单位为线,表示码盘的每转脉冲数。
表 3-4-1 所示是电机和编码器的参数。

2.直流电机的速度测量方法
通过学习上一个知识点,编码器是直流电机测速模块的重要组成部分,输出 A
和 B 两路相位差为 90°脉冲可以反馈电机的运行状态,如角位移、角速等。但这两种脉冲
不能直观地呈现相关信息,需要用一些方法收集和处理两个脉冲信号,然后才能得到我
人们想要的信息。
(1)捕获编码器输出脉冲的方法
在数据处理前,应先对编码器的输出脉冲进行计数。在实际应用中,有以下 4 常用的捕获
输出脉冲的方法。
方法①:使用定时器捕获功能,记录 A 和 B 两个脉冲中任何一个上升或下降的次数。
该方法的缺点是无法判断电机的旋转方向,因为它只记录一路脉冲的信息。
方法②:同时使用定时器捕获两个脉冲的上下沿次数,并判断 A 和 B 两路脉冲之
间延进而达到判断电机旋转方向的目的。
方法③:将编码器的输出脉冲与 MCU 外部中断线连接,配置外部中断线的触发方式(上部
脉冲可以在上升或下降后计数。该方法的缺点是无法判断电机的旋转方向。
方法④:使用定时器的编码器接口功能,捕获两个脉冲的上下沿次数,并使用
MCU 判断电机旋转方向的硬件功能。
对上述 4 该方法的优缺点分析如下。
方法 4 最大的优点是,脉冲可以在不占用中断资源的情况下计数,并且可以判断电机的旋转方向;相同
时,利用 MCU 硬件处理减少了程序设计的工作量。因此,如果使用编码器接口功能 MCU
(如 STM32F4 应选择系列微控制器)。
方法①由于缺乏功能(即无法判断电机的旋转方向),其优先级较低,
取而代之的是方法②。但使用方法②如果需要用程序来判断电机的旋转方向,则需要编程
工作量大。
方法③适用于没有编码器接口、输入捕获功能弱的定时器 MCU(如 51 系列微控制器)。
方法③由于其稳定性最低,功能缺失,其优先级最低。
综上所述,在使用中 STM32F407ZGT6 型号的 MCU 时,应选择方法④实现编码器输出
捕获脉冲。
(2)编码器输出脉冲计值与电机速度值之间的转换
编码器输出脉冲捕获完成后,应采用相应的转换方法将其计数值转换为电机的角速
或线速。在工业控制系统中,测量直流电机转速的最典型方法是测频法(M 法)、
测周期法(T 法律)以及上述两者的结合。 M/T 测速法。
M 该方法可以在单位时间内测量脉冲数,并将其转换为信号频率。在测量过程中,如果测量开始
如果初始位置选择不当,就会形成 1 脉冲误差。当速度较低时,单位时间内脉冲数量减少,误差占用
所以 M 该方法适用于高速测量。为了降低测量速度的下限,可以增加编码器线数或
延长尽可能多地收集脉冲,延长测量的单位时间。
T 法可测得两个脉冲之间的时间并换算成信号周期。此法在测量过程中,如果测量的起始位
如果选择不当,就会形成 1 基准时钟的误差。当速度较高时,测量周期较小,误差占比较大
大,所以 T 该方法适用于低速测量。为了提高速度测量的上限,可以减少编码器的脉冲数使用更精细
确次测量的时间值应尽可能大。
M 法与 T 法律各有优缺点。考虑到编码器线数不能无限增加,测量时间不能太长(必须考虑实时)
性),计时单位不能无限小,单凭 M 法或 T 全速范围内的准确测量是无法实现的。这就产生了
M 法与 T 法结合的 M/T 速度测量方法:低速时间测量周期,高速时间测量频率。各研究机构还根据不同的应用进行测量
场合对 M/T 优化测速方法,形成不同形式 M/T 测速法。
3.STM32F4 编码器接口模式系列微控制器定时器
通过学习上一个知识点,可以使用它 STM32F4 系列微控制器定时器的编码器接口模式可
直流电机的速非常方便。本节重点关注编码器接口模式的工作原理和编程配置方法
进行介绍。
STM32F4 在系列微控制器的定时器中,TIM1~TIM5 以及 TIM8 具有编码器接口功能,其他固定
时器没有这个功能。定时器编码器接口的硬件框图如图所示 3-4-4 所示。

(1)编码器接口硬件框图分析
从图 3-4-4 可以看到中右阴影部分,编码器接口的两个输入分别来自 TI1FP1 和 TI2FP2
信号。TI1FP1 和 TI2FP2 是 TI1 与 TI2 输入滤波器和边缘检测器后的信号。一般在实际应用中
没有滤波和反相,即 TI1FP1=TI1,TI2FP2=TI2。
(2)编码器接口模式的工作原理
编码器接口根据输入情况输入 TI1FP1、TI2FP2 两个信号转换序列产生计数脉冲和方向信号
做法和流程如下:
① 配置 TI1FP1 与 TI1、TI2FP2 与 TI2 映射关系,配置是否相反; ② 配置编码器的计数方式(仅在 TI1 或 TI2 边沿处计数,或者同时在 TI1 和 TI2 处计数);
③ CEN=“1”(位于 TIMx_CR1,使能计数器);
④ 编码器接口判断两个输入信号的相位关系,硬件对 TIMx_CR1 的 DIR 位(该位反映电机
是正转还是反转)进行相应修改;
⑤ 编码器接口根据 DIR 位情况,计数器相应递增(电机正转)或递减(电机反转)计数,
计数器在 0 到 TIMx_ARR 值之间进行连续计数,因此在电机启动前必须先配置 TIMx_ARR 值;
⑥ 任何输入(TI1 或 TI2)发生信号转换时,都会重新计算 DIR 位;
⑦ 计数器会根据增量式编码器的速度和方向自动修改其值,其值始终表示当前编码器的
位置。
表 3-4-2 汇总了计数方向与编码器信号之间的各种可能的组合关系。

 

下面结合一个编码器接口模式下的计数器工作示例(如图3-4-5 所示),对表3-4-2 进行说明。

 

图 3-4-5 说明了计数信号的生成和方向控制,同时也说明了选择双边沿时如何对输入抖动
进行补偿。编码器接口的工作参数配置如下。
 CC1S=“01”(TIMx_CCMR1,TI1FP1 映射到 TI1 上)。
 CC2S=“01”(TIMx_CCMR2,TI1FP2 映射到 TI2 上)。
 CC1P=“0”,CC1NP=“0”,且 IC1F=“0000” (TIMx_CCER,TI1FP1 未反相,TI1FP1=TI1)。
 CC2P=“0”,CC2NP=“0”,且 IC2F=“0000” (TIMx_CCER,TI1FP2 未反相,TI1FP2=TI2)。
 SMS=“011”(TIMx_SMCR,两个输入在上升沿和下降沿均有效)。
 CEN=“1”(TIMx_CR1,使能计数器)。
对图 3-4-5 中各标号处的工作情况分析如下。
编号①处:在 TI1 上升沿,查看相对信号 TI2 的电平为低,查表 3-4-2,计数器递增。
编号②处:在 TI2 上升沿,查看相对信号 TI1 的电平为高,查表 3-4-2,计数器递增。
编号③处:在 TI1 下降沿,查看相对信号 TI2 的电平为高,查表 3-4-2,计数器递增。
编号④处:在 TI2 下降沿,查看相对信号 TI1 的电平为低,查表 3-4-2,计数器递增。
编号⑤处:在 TI1 上升沿,查看相对信号 TI2 的电平为低,查表 3-4-2,计数器递增。
编号⑥处:在 TI1 下降沿,查看相对信号 TI2 的电平为低,查表 3-4-2,计数器递减。
编号⑦处:在 TI1 上升沿,查看相对信号 TI2 的电平为低,查表 3-4-2,计数器递增。
编号⑧处:在 TI1 下降沿,查看相对信号 TI2 的电平为低,查表 3-4-2,计数器递减。

(3)编码器接口模式的编程配置步骤
掌握了定时器编码器接口的硬件框图及其工作原理等内容以后,我们可以开始学习如何对定
时器的编码器接口模式进行编程配置,使之对输入的 TI1FP1、TI2FP2 两个信号转换序列进行计
数与方向判断。本任务使用测频率法(M 法),即测量单位时间内的脉冲数后换算成频率,具体
的编程配置步骤如下。
① 配置编码器接口输入通道 GPIO 工作模式
以 TIM3 为例,配置其编码器输入通道(TI1 和 TI2)的 GPIO 端口(PC6 和 PC7)为复用
功能。具体配置方法可参考 PWM 信号输出通道 GPIO 端口的配置。
② 配置定时器的基本工作参数
本步骤通过调用 TIM_TimeBaseInit()函数配置“定时器基本初始化结构体(TIM_Time-
BaseInitTypeDef)”的各成员变量来完成,具体参考任务 3.1 的相关内容。
本步骤主要对定时器预分频器的分频因子和自动重装载寄存器值进行配置,一般配置为不分
频,自动重装载值为 65535,配置示例如下:
TIM_TimeBaseStructure.TIM_Period = 65535; // 配置 TIMx_ARR 值
TIM_TimeBaseStructure.TIM_Prescaler = 0;  // 配置预分频器的分频因子
③ 配置定时器的从模式为“编码器模式”,并配置各工作参数
配置从模式控制寄存器(TIMx_SMCR)的 SMS 位段为“011”,可使定时器工作在“编码
器模式 3”,并在 TI1FP1 和 TI2FP2 的边沿处都计数。编码器接口的其他工作参数可参照图 3-4-5
的工作示例进行配置,这是实际应用中最常用的一种配置。
本项配置涉及较多寄存器,但STM32F4标准外设库也提供了库函数TIM_EncoderInterfaceConfig()
来完成定时器“编码器模式”的配置,该库函数的原型定义如下:
void TIM_EncoderInterfaceConfig(TIM_TypeDef* TIMx, uint16_t TIM_EncoderMode,
uint16_t TIM_IC1Polarity, uint16_t TIM_IC2Polarity);
使用实例:
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);
上述实例中,参数“TIM_EncoderMode_TI12”配置编码器接口在 TI1 和 TI2 的边沿处均计
数,参数“TIM_ICPolarity_Rising”配置 TI1FP1 和 TI2FP2 未反相。另外,该库函数还配置了
TI1FP1 与 TI1、TI2FP2 与 TI2 的映射关系,具体实现方式可参考 STM32F4 标准外设库源代码。
④ 编写计数值处理与速度换算函数
以定时器 3 工作在编码器接口模式为例,使用 M 法测量单位时间内的脉冲数的具体实现流
程如下:
 先采集一次定时器 3 的计数值;
 经过一定的时间间隔(由定时器 6 更新中断实现)后,再采集一次定时器 3 的计数值;
 将本次计数值与上次计数值相减,得到差值;
 由于计数值存在溢出的可能,如递增计数至 65535 或递减计数至 0,因此需要对差值进
行处理(分递增计数和递减计数两种情况);
 根据直流电机的减速比、编码器分辨率等参数将“计数值”换算成“电机的转速”。
⑤ 配置另一个定时器限定采样的时间间隔
通过学习电机测速方法,我们知道 M 法需要测量单位时间内的脉冲数。当定时器被配置工
作在编码器接口模式下时,相当于使用了带有方向选择的外部时钟,此时定时器无法实现基本的
定时功能。因此必须配置另一个定时器以实现定时功能,限定采样的时间间隔。如使用定时器 6,
配置它工作使用内部时钟,计数器工作在递增计数模式,并使能更新中断。具体配置方法与 3.1
节相同,此处不再赘述。
⑥ 编写另一个定时器的中断服务函数
在中断服务函数中,调用步骤④的计数值处理与速度换算函数,实现计算单位时间内的电机
转速的功能。 

3.4.3 任务实施
1.硬件接线
一般带编码器的电机外部接线如图 3-4-6 所示。

从图 3-4-6 中可以看到,直流电机共有 6 根外部接线,其上安装的编码器可输出 A、B 两
相脉冲,另外 4 根接线分别是:电机电源+、电机电源-、编码器电源和编码器地线。
编制表 3-4-3 所示的电机测速模块硬件接线表,并将直流电机(编码器)、DRV8848 电机
驱动和 STM32F4 系列微控制器相连。

 2.电机测速模块的程序流程图
图 3-4-7 是电机测速模块的程序流程,图 3-4-7(a)为主程序流程,图 3-4-7(b)为
TIM6 定时器中断程序流程。

3.编写定时器 3 的编码器接口功能配置程序
复制一份任务 3.3 的工程,并将其重命名为“task3.4_Timer_Encoder_MotorSpeed”。在
“TIMER”文件夹下新建“timer3.c”和“timer3.h”两个文件,将它们加入工程中,并配置头文
件包含路径。
本配置步骤包含 3 个子流程:
 配置编码器接口输入通道 GPIO 工作模式;
 配置定时器的基本工作参数;
 配置定时器的从模式为“编码器模式”,并配置各工作参数。
在“timer3.c”文件中输入以下代码:

#include "timer3.h"
#include "usart.h"
/*  注意 :lastCount 应使用静态变量 */
static int16_t lastCount = 0;
/**
* @brief Timer3 编码器接口输入通道相关 GPIO 模式配置
* @param None
* @retval None
*/
void TIM3_GPIO_Config(void)
{
GPIO_InitTypeDef GPIO_InitStructure;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOC,ENABLE);
/* PC6|PC7  复用为 TIM3 编码器接口 AB 相输入 */
GPIO_PinAFConfig(GPIOC,GPIO_PinSource6,GPIO_AF_TIM3);
GPIO_PinAFConfig(GPIOC,GPIO_PinSource7,GPIO_AF_TIM3);
/* TI1FP1:PC6 | TI2FP2:PC7 */
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;// 复用功能
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
GPIO_Init(GPIOC,&GPIO_InitStructure);
}
/**
* @brief Timer3 编码器接口功能初始化
* @param arr: 自动重载寄存器值, PSC: 预分频器分频系数
* @retval None
*/
void TIM3_Encoder_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3,ENABLE);
TIM_TimeBaseInitStructure.TIM_Period = arr;
TIM_TimeBaseInitStructure.TIM_Prescaler = psc;
TIM_TimeBaseInitStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInitStructure.TIM_ClockDivision = TIM_CKD_DIV1;
TIM_TimeBaseInitStructure.TIM_RepetitionCounter = 1;
TIM_TimeBaseInit(TIM3,&TIM_TimeBaseInitStructure);
/*  配置 TIM3 编码器接口的工作参数 */
TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising,
TIM_ICPolarity_Rising);
TIM_SetCounter(TIM3, 0);  //TIM3 计数值清零
TIM_Cmd(TIM3,ENABLE);  // 启动 TIM3 定时器
}

 4.编写计数值处理与速度换算函数
在“timer3.c”文件中继续输入以下代码:

static float motorSpeed = 0.0;
volatile uint16_t curCount = 0; // 用于获取编码器当前计数值
volatile int32_t realValue = 0; // 用于存放差值
/**
* @brief 换算电机转速
* @param TIMx:  定时器编号,如 TIM3
* @retval  电机转速 ( 单位: r/s)
*/
float Cal_Motor_Speed(TIM_TypeDef* TIMx)
{
motorSpeed = 0.0;
curCount = TIMx->CNT;  // 获取编码器计数值
realValue = curCount - lastCount;  // 计算差值
/*  计数值溢出处理 */
if(realValue >= MAX_COUNT) // 计数值上溢
{
realValue -= ENCODER_TIM_PERIOD;
}
else if(realValue < -MAX_COUNT) // 计数值下溢
{
realValue += ENCODER_TIM_PERIOD;
}
printf(" 当前值为 : %d\r\n",curCount);
printf(" 上次值为 : %d\r\n",lastCount);
printf(" 捕获值为 : %d\r\n",realValue);
lastCount = curCount; 
motorSpeed = (float)realValue/4/2/80;
printf(" 电机转速为 : %0.2f(r/s)\r\n", motorSpeed);
printf("***********************\r\n");
return motorSpeed;
}
/**
* @brief TIMx Counter 与上次计数值清零
* @param TIMx:  定时器编号,如 :TIM3
* @retval None
*/
void TIM_Clear_Counter(TIM_TypeDef* TIMx)
{
lastCount = 0; // 上次编码器计数值清零
TIM_SetCounter(TIMx, 0);  //TIMx 计数值清零
}

 上述代码段的第 11~22 行对计数值的上溢和下溢进行处理,第 27 行将计数值换算为电机
转速。由于在 TI1 和 TI2 的上升沿和下降沿均捕获信号,相当于对计数值进行了“四倍频”处理,
因此在最终换算时要除以 4。另外,第 27 行中的“2”代表码盘上的栅格为 2,“80”代表该电
机的减速比为 1:80,最终的 motorSpeed 值为电机经减速器输出后的转速,单位为“r/s”。
在“timer3.h”文件中输入以下代码:

#ifndef __TIMER3_H
#define __TIMER3_H
#include "sys.h"
#define MAX_COUNT 10000
#define ENCODER_TIM_PERIOD 65535
void TIM3_GPIO_Config(void);
void TIM3_Encoder_Init(uint16_t arr, uint16_t psc);
float Cal_Motor_Speed(TIM_TypeDef* TIMx);
void TIM_Clear_Counter(TIM_TypeDef* TIMx);
#endif

5.配置另一个定时器限定采样的时间间隔并编写定时器中断服务函数
在“timer6.c”文件中输入以下代码:

#include "timer6.h"
#include "timer3.h"
#include "usart.h"
/**
* @brief TIM6 定时中断功能初始化
* @param arr:  自动重装载值, PSC:  定时器预分频值
* @retval None
*/
void TIM6_Int_Init(uint16_t arr, uint16_t psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,ENABLE);// 使能 TIM6 时钟
TIM_TimeBaseInitStructure.TIM_Period = arr; // 自动重装载值
TIM_TimeBaseInitStructure.TIM_Prescaler = psc; // 定时器预分频值
TIM_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure); //TIM6 初始化
TIM_ClearITPendingBit(TIM6, TIM_IT_Update); // 清除更新中断请求位
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE); // 允许定时器 6 更新中断
NVIC_InitStructure.NVIC_IRQChannel = TIM6_DAC_IRQn;// 定时器 6 中断
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority = 0x01;// 抢占优先级 1
NVIC_InitStructure.NVIC_IRQChannelSubPriority = 0x03;// 子优先级 3
NVIC_InitStructure.NVIC_IRQChannelCmd = ENABLE;
NVIC_Init(&NVIC_InitStructure);
TIM_Cmd(TIM6,DISABLE); // 暂时不使能定时器 6
}
/**
* @brief TIM6 定时器中断服务函数
* @param None
* @retval None
*/
void TIM6_DAC_IRQHandler(void)
{
if(TIM_GetITStatus(TIM6,TIM_IT_Update) == SET) // 若发生更新中断
{
Cal_Motor_Speed(TIM3);
}
TIM_ClearITPendingBit(TIM6,TIM_IT_Update); // 清除更新中断标志位
}

TIM6 用作限定采样的时间间隔。为了确保每次采样时间的准确性,TIM6 配置完毕后暂时不
使能。特别注意:上述代码段的第 21 行与第 22 行代码的先后顺序不能错,第 30 行代码暂时不
使能 TIM6。
上述代码段的第42 行代码用于调用计数值处理与速度换算函数,本行代码每隔 1s 执行一次。
6.编写 main()函数
在“main.c”文件中输入以下代码:

#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "led.h"
#include "key.h"
#include "exti.h"
#include "timer1.h"
#include "timer3.h"
#include "timer6.h"
#include "motor.h"
static void Key_Process(void);
uint8_t keyValue = 0;
int16_t initSpeed = 40, newSpeed = 40;
MOTOR_StateTypeDef Motor_State = MOTOR_STOP;
int main(void)
{
delay_init(168); // 延时函数初始化
LED_Init(); //LED 端口初始化
Key_Init(); // 按键初始化
EXTIx_Init();  // 外部中断初始化
USART1_Init(115200);  // 串口 1 初始化
TIM6_Int_Init(10000-1,8400-1); // 定时器 6 定时中断 (1s) 初始化
TIM1_GPIO_Config(); // 定时器 1 输出通道 GPIO 初始化
TIM1_PWM_Init(100-1,12-1); // 定时器 1PWM 输出功能初始化
TIM3_GPIO_Config();
TIM3_Encoder_Init(65536-1,0);
printf("System Started!\r\n");
while(1)
{
Key_Process();
LED1 = ~LED1;
delay_ms(50);
}
}
/**
* @brief 按键处理流程
* @param None
* @retval None
*/
static void Key_Process(void)
{
/* Key1_ 上键按下,电机正转,小车前进 */
if(keyValue == KEY_U_PRESS)
{
if(Motor_State == MOTOR_STOP)
{
Car_Forward(initSpeed); // 电机正转,小车前进
Motor_State = MOTOR_FORWARD;
TIM_Clear_Counter(TIM3); // 清 lastCount 与 TIM3 的 Counter
TIM_Cmd(TIM6,ENABLE); // 开启定时器 6
}
else if(Motor_State == MOTOR_FORWARD ||
Motor_State == MOTOR_BACKUP)
{
Car_Stop();
newSpeed = 40;
Motor_State = MOTOR_STOP;
TIM_Cmd(TIM6,DISABLE);  // 关闭定时器 6
TIM_SetCounter(TIM6, 0); // 清除定时器 6 计数值
}
keyValue = 0;
}
/* Key2_ 下键按下,电机反转,小车后退 */
if(keyValue == KEY_D_PRESS)
{
if(Motor_State == MOTOR_STOP)
{
Car_Backup(initSpeed);  // 电机反转,小车后退
Motor_State = MOTOR_BACKUP;
TIM_Clear_Counter(TIM3); // 清 lastCount 与 TIM3 的 Counter
TIM_Cmd(TIM6,ENABLE); // 开启定时器 6
}
else if(Motor_State == MOTOR_FORWARD || \
Motor_State == MOTOR_BACKUP)
{
Car_Stop();
newSpeed = 40;
Motor_State = MOTOR_STOP;
TIM_Cmd(TIM6,DISABLE);  // 关闭定时器 6
TIM_SetCounter(TIM6, 0); // 清除定时器 6 计数值
}
keyValue = 0;
}
/* Key3_ 左键按下,减小占空比 */
if(keyValue == KEY_L_PRESS)
{
if(Motor_State == MOTOR_FORWARD)
{
newSpeed -= 5;
if(newSpeed < 0) newSpeed = 0;
Car_Forward(newSpeed);
}
else if(Motor_State == MOTOR_BACKUP)
{
newSpeed -= 5;
if(newSpeed < 0) newSpeed = 0;
Car_Backup(newSpeed);
}
keyValue = 0;
}
/* Key4_ 右键按下,增加占空比 */
if(keyValue == KEY_R_PRESS)
{
if(Motor_State == MOTOR_FORWARD)
{
newSpeed += 5;
if(newSpeed >= 100) newSpeed = 100;
Car_Forward(newSpeed);
}
else if(Motor_State == MOTOR_BACKUP)
{
newSpeed += 5;
if(newSpeed >= 100) newSpeed = 100;
Car_Backup(newSpeed);
}
keyValue = 0;
}
}

编写 Key_Process()函数时,应特别注意“电机启动”与“电机停止”的程序流程,即上述
代码段的第 52~55 行、第 60~64 行、第 73~76 行和第 81~85 行。
7.观察试验现象
应用程序编译无误后,下载至开发板运行,打开上位机的串口调试助手,可观察到图 3-4-8
所示的电机转速显示试验现象。

图 3-4-8(a)显示了电机正转时的转速,图 3-4-8(b)显示了电机反转时的转速。 

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章