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)显示了电机反转时的转速。

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

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

    3.3.1 任务分析 本任务要求设计一个可实现智能小车电机调速的应用程序,具体要点说明如下. ① 电机驱动部分选用德州仪器(Texas Instruments,TI)公司的 DRV8848 芯片(也可 ...

  2. STM32之增量式编码器电机测速

    STM32之增量式编码器电机测速 编码器 编码器种类 按监测原理分类 光电编码器 霍尔编码器 按输出信号分类 增量式编码器 绝对式编码器 编码器参数 分辨率 精度 最大响应频率 信号输出形式 编码器倍 ...

  3. Arduino智能小车电机控制方向及运动

    为后续的研究先做资料的铺垫,如果错误,欢迎指正 Arduino智能小车--测试篇 Arduino 智能小车-电机控制 delay 延时处理:delay(10000)某个操作运行10秒后再进行其他操作

  4. 做游戏,学编程(C语言)教材《C语言课程设计与游戏开发实践教程》出版了...

    经过半年多的写作.修改.校样.印制,我们的实践教材<C语言课程设计与游戏开发实践教程>终于出版了.这本书可以看成是"做游戏,学编程(C语言)专栏"的详细版本,以下为书中 ...

  5. 智能小车PWM调速原理

    电机驱动电路 智能小车电机的驱动芯片采用L293D.L293D是一款单片集成的高电压.高电流.4通道电机驱动,设计用于连接标准DTL或TTL逻辑电平,驱动电感负载(诸如继电线圈.DC和步进电机)和开关 ...

  6. stm32编码器电机测速(hal库)

    记录一下今天参考别人的代码实现了四个电机的测速. 编码器被广泛应用于电机测速,实现电机闭环控制.所以不论是自己做小车还是后续参加各种比赛,必须要学会编码器测速. 一.参数 编码电机其实就是一个带有编码 ...

  7. LIVE MINI ESP32开发板教程系列(三)drv2605L模块+手机常用振动器实现117种震动效果

    LIVE MINI ESP32开发板教程系列(三)drv2605L模块+手机常用振动器实现117种震动效果 LIVE MINI ESP32引脚图 手机振动器介绍 DRV2605L模块 硬件连线图 DR ...

  8. 寻迹小车 FOLLOWME—— 电机测速及转速控制

    寻迹小车 FOLLOWME-- 之五:电机测速及转速控制 此篇涉及电机的测速和转速控制. 寻迹小车 FollowMe -- 之五:电机测速及转速控制 作者:Hanker 前面已完成了车的主体,控制部分 ...

  9. STM32 CubeMax 编码器电机测速 原理与实现

    编码器电机测速 部分参考:https://blog.csdn.net/lzzzzzzm/article/details/119416134 其他参考部分见图片水印 1. 编码器种类及原理 常见的编码器 ...

最新文章

  1. 在ubuntu 14.04 64bit下配置安装PyQt4(python2.7和python3.4)
  2. pytorch自带网络_【方家之言】一篇长文学懂 pytorch
  3. 编写Dockerfiles的最佳做法
  4. 我在 MySQL 的那些年
  5. 高并发用redis还是mysql_高并发架构系列:Redis缓存和MySQL数据一致性方案详解
  6. Python的turtle库还能绘制这些有趣图形?
  7. Java 数据结构与算法面试 链表
  8. 鸿蒙os系统测评,鲁大师测试鸿蒙OS2:应用恢复率吊打iOS
  9. Android 启动APP时黑屏白屏的三个解决方案
  10. transform子元素,绝对定位失效
  11. AcWing 867. 分解质因数(唯一分解定理)
  12. Centos硬盘IO性能检测命令iostat[转]
  13. php 根据身份证计算年龄
  14. 解密产品经理兼职做猎头,3个月赚十万
  15. Windows系统拦截广告弹窗
  16. 【Java应用】使用Java实现机器学习算法:聚类、分类、预测
  17. VW和VH移动端布局
  18. videojs+hls+rtmp流媒体播放
  19. 在C++中 :: 的三种意思
  20. 中国单反数码相机市场现状动态及前景规模调查报告2022-2028年版

热门文章

  1. github搭建自己的博客网站
  2. 应用程序运行 Error 1706 错误
  3. hydra和medusa使用教程
  4. ORA-01122 ORA-01207问题
  5. java项目-第127期SpringBoot+vue的智慧养老手表管理系统-java毕业设计_计算机毕业设计
  6. 2022-06 CCF
  7. cobbler sync 错误
  8. 系统集成项目需求调研日志
  9. nike air max 1 leopard internationaal meest
  10. hrm项目-day01