【实验原理】
编码器(encoder)是指将某一形式的信号或数据(通常特指电机转速)通过采集、编制,转换为可以用于传输、储存的信号形式的设备。编码器按采集方式可以分为接触式编码器和非接触式编码器两种;按工作原理可以分为:光电式、磁电式和触点电刷式编码器。光电式编码器通过采集光通过码盘刻孔产生的脉冲信号的频率进行转换,磁电式编码器通过霍尔元件感应电机转动时产生的磁场变化进行转换,触点电刷式编码器直接采集触点在码盘上的位置进行转换;还可以根据其计数模式分为增量式和绝对式编码器。增量式编码器记录通过采集位移信息产生的脉冲信号,近似于“速率”的概念,绝对式编码器记录位置信息,与过程无关,近似于“路程”的概念。
本实验采用的编码器为增量式编码器,增量式编码器通常有两个输出信号,分别为A相和B相(也有的编码器有A、B、Z三相输出,有编码器只有A相输出)。每一个信号都是由方波构成的脉冲序列,A相和B相之间的相位差为90度。单片机在接收到AB相输入后,可以根据收到相位的先后顺序确定电机的转向,根据脉冲序列的频率确定电机的转速,这种解码方式称为正交解码。值得注意的是,脉冲频率与电机转速成正比关系,而其具体比例系数会和码盘孔数、减速箱齿数等有关,而转速与行驶速率也存在一个比例系数,因此通常在使用中,会根据实地测试结果直接确定一个脉冲频率与行驶速率之间的经验系数。
STM32芯片中集成了强大的定时器模块,其工作状态主要有以下几种模式:输入捕获模式、PWM输入模式、强制输出模式、输出比较模式、PWM模式、单脉冲模式、编码器接口模式、调试模式等。在本实验中需要用到的是定时器模块的编码器接口模式。当定时器工作在编码器模式下时,定时器会采集两个指定的引脚输入信号,然后通过检测输入信号的跳变沿,确定输入脉冲的频率及其相位差,从而计算出电机旋转的方向以及速率。编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR。
图1 编码器模式下的计数器操作示意图
值得注意的是,仅仅完成跳变沿检测的功能,普通IO口就能办到。而定时器模块的编码器接口模式相比于普通IO口的优势在于可以抵抗一定的噪音,如图1所示,在输入信号有毛刺的情况下,计数器依然能够正常的工作,普通IO口想要办到类似的功能则需要人为添加一系列代码。
程序流程的简要说明图如图2所示:
图2 程序流程示意图
【实验环境】
硬件设备:
1、双轮自平衡机器人。如图3所示,在平衡车背面中间安装了HC-SR04超声波测距模块。
2、ST-Link下载器(包含USB线与下载线)。如图4所示。
操作系统:
Windows7/8/10,32bit/64bit
软件环境:
Keil 5
【实验步骤】
第一步 配置工程环境
(1)打开已经建立好的工程模板,在新建立的工程模板中已经添加五个文件夹,分别命名为USER、HARDWARE、SYSTEM、CORE、FWLib文件夹,如图5所示。其中USER文件夹存放的是主函数,HARDWARE文件夹存放的是本实验对应的硬件设备函数,SYSTEM存放的是本课程所有实验通用的函数,CORE文件夹存放的是启动文件,FWLib文件夹存放的是底层驱动函数。
图5工程模板对应的文件夹
(2)在HARDWARE文件夹下新建两个文件,分别为encoder.c和encoder.h。分别存放编码器的函数文件和头文件,如图6所示。
图6 在HARDWARE文件夹下建立encoder.c与encoder.h文件
第二步 编写定时器函数,完成定时器的配置和启用
(3)打开程序中的encoder.c文件,首先将encoder.h文件包含进来。其次对Encoder_Init_TIM2 函数进行编写,TIM2采集的是左侧的码盘返回值,选择要使用的时钟,配置引脚,启用编码器模式并配置参数。
#include “encoder.h”
#include “stm32f10x_gpio.h”
/****************************************************************
函数功能:把TIM2初始化为编码器接口模式
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM2(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//使能定时器4的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//使能PA端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
//设定计数器自动重载值
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD;
//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//Reset counter
TIM_SetCounter(TIM2,0);
TIM_Cmd(TIM2, ENABLE);
}
(1) (4)同理,编写Encoder_Init_TIM4函数,TIM4定时器采集的是右侧的编码器返回值。
/****************************************************************
函数功能:把TIM4初始化为编码器接口模式
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM4(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//使能定时器4的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
//使能PB端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOA
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
//设定计数器自动重载值
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD;
//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_SetCounter(TIM4,0);//Reset counter
TIM_Cmd(TIM4, ENABLE);
}
(5)编写编码器计数器读取函数
/*****************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回值:速度值
*****************************************************************/
int Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
case 2: Encoder_TIM= (short)TIM2 -》 CNT; TIM2 -》 CNT=0;break;
case 4: Encoder_TIM= (short)TIM4 -》 CNT; TIM4 -》 CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
(2) 打开encoder.h,编写函数声明
#ifndef __ENCODER_H
#define __ENCODER_H
#include 《sys.h》
#define ENCODER_TIM_PERIOD (u16)(65535) //不可大于65535 因为F103的定时器是16位的
void Encoder_Init_TIM2(void);
void Encoder_Init_TIM4(void);
int Read_Encoder(u8 TIMX);
#endif
第三步 编写main.c文件
(3) (6)将工程编译需要用到的头文件包含进来,并预定义显示函数和全局变量。
#include “encoder.h” //包含编码器函数头文件
#include “sys.h” //包含系统头文件
#include “stm32f10x.h” //包含系统寄存器定义声明的头文件
void oled_show(void);
int Encoder_Left,Encoder_Right; //左右编码器的脉冲计数
(4) (7)在主函数中调用延时函数、显示函数和编码器函数的初始化函数。
int main(void)
{
delay_init(); //延时函数初始化
OLED_Init(); //OLED初始化
Encoder_Init_TIM2(); //编码器接口初始化
Encoder_Init_TIM4(); //编码器接口初始化
(5) (8)定义主循环,在主循环中调用超声波读取函数和显示函数。
while(1)
{
Encoder_Left=Read_Encoder(2); //读取左侧编码器值
Encoder_Right=Read_Encoder(4); //读取右侧编码器值
oled_show(); //显示屏打开
delay_ms(50);
}
(6) (9)编写OLED显示函数
void oled_show(void)
{
//显示右侧编码器返回值
OLED_ShowString(0,10,“Encoder_Right”);
if(Encoder_Right 》=0)OLED_ShowString(20,20,“”),
OLED_ShowNumber(45,20,Encoder_Right,4,12);
else OLED_ShowString(20,20,“-”),
OLED_ShowNumber(45,20,4-Encoder_Right,4,12);
//显示左侧编码器返回值
OLED_ShowString(0,40,“Encoder_Left”);
if(Encoder_Left》=0)OLED_ShowString(10,50,“”),
OLED_ShowNumber(45,50,Encoder_Left,4,12);
else OLED_ShowString(10,50,“-”),
OLED_ShowNumber(45,50,-Encoder_Left,4,12);
//=============刷新======================//
OLED_Refresh_Gram();
}
第四步 编译并下载,观察实验现象
(10)本实验采用仿真器为STLink V2,将仿真器与小车相连,注意正负极不要接反,
(11)编译程序:点击如图8所示的编译按键。
图8Keil编译环境下的编译按键
(12)当编译完成后,如果没有问题,Build Output栏会出现无错误、无警告的提示,如图9所示。
图9编译通过后Build Output栏提示信息
(13)下载程序:点击如图所示的下载按键,程序就会下载到STM32的芯片中。下载按键如图10所示。
图10Keil编译环境下的下载按键
(14)观察实验现象,OLED显示屏上显示出当前电机的转速,用手从不同方向转动电机,观察数值的变化。
【实验原理】
编码器(encoder)是指将某一形式的信号或数据(通常特指电机转速)通过采集、编制,转换为可以用于传输、储存的信号形式的设备。编码器按采集方式可以分为接触式编码器和非接触式编码器两种;按工作原理可以分为:光电式、磁电式和触点电刷式编码器。光电式编码器通过采集光通过码盘刻孔产生的脉冲信号的频率进行转换,磁电式编码器通过霍尔元件感应电机转动时产生的磁场变化进行转换,触点电刷式编码器直接采集触点在码盘上的位置进行转换;还可以根据其计数模式分为增量式和绝对式编码器。增量式编码器记录通过采集位移信息产生的脉冲信号,近似于“速率”的概念,绝对式编码器记录位置信息,与过程无关,近似于“路程”的概念。
本实验采用的编码器为增量式编码器,增量式编码器通常有两个输出信号,分别为A相和B相(也有的编码器有A、B、Z三相输出,有编码器只有A相输出)。每一个信号都是由方波构成的脉冲序列,A相和B相之间的相位差为90度。单片机在接收到AB相输入后,可以根据收到相位的先后顺序确定电机的转向,根据脉冲序列的频率确定电机的转速,这种解码方式称为正交解码。值得注意的是,脉冲频率与电机转速成正比关系,而其具体比例系数会和码盘孔数、减速箱齿数等有关,而转速与行驶速率也存在一个比例系数,因此通常在使用中,会根据实地测试结果直接确定一个脉冲频率与行驶速率之间的经验系数。
STM32芯片中集成了强大的定时器模块,其工作状态主要有以下几种模式:输入捕获模式、PWM输入模式、强制输出模式、输出比较模式、PWM模式、单脉冲模式、编码器接口模式、调试模式等。在本实验中需要用到的是定时器模块的编码器接口模式。当定时器工作在编码器模式下时,定时器会采集两个指定的引脚输入信号,然后通过检测输入信号的跳变沿,确定输入脉冲的频率及其相位差,从而计算出电机旋转的方向以及速率。编码器接口模式基本上相当于使用了一个带有方向选择的外部时钟。这意味着计数器只在0到TIMx_ARR寄存器的自动装载值之间连续计数(根据方向,或是0到ARR计数,或是ARR到0计数)。所以在开始计数之前必须配置TIMx_ARR。
图1 编码器模式下的计数器操作示意图
值得注意的是,仅仅完成跳变沿检测的功能,普通IO口就能办到。而定时器模块的编码器接口模式相比于普通IO口的优势在于可以抵抗一定的噪音,如图1所示,在输入信号有毛刺的情况下,计数器依然能够正常的工作,普通IO口想要办到类似的功能则需要人为添加一系列代码。
程序流程的简要说明图如图2所示:
图2 程序流程示意图
【实验环境】
硬件设备:
1、双轮自平衡机器人。如图3所示,在平衡车背面中间安装了HC-SR04超声波测距模块。
2、ST-Link下载器(包含USB线与下载线)。如图4所示。
操作系统:
Windows7/8/10,32bit/64bit
软件环境:
Keil 5
【实验步骤】
第一步 配置工程环境
(1)打开已经建立好的工程模板,在新建立的工程模板中已经添加五个文件夹,分别命名为USER、HARDWARE、SYSTEM、CORE、FWLib文件夹,如图5所示。其中USER文件夹存放的是主函数,HARDWARE文件夹存放的是本实验对应的硬件设备函数,SYSTEM存放的是本课程所有实验通用的函数,CORE文件夹存放的是启动文件,FWLib文件夹存放的是底层驱动函数。
图5工程模板对应的文件夹
(2)在HARDWARE文件夹下新建两个文件,分别为encoder.c和encoder.h。分别存放编码器的函数文件和头文件,如图6所示。
图6 在HARDWARE文件夹下建立encoder.c与encoder.h文件
第二步 编写定时器函数,完成定时器的配置和启用
(3)打开程序中的encoder.c文件,首先将encoder.h文件包含进来。其次对Encoder_Init_TIM2 函数进行编写,TIM2采集的是左侧的码盘返回值,选择要使用的时钟,配置引脚,启用编码器模式并配置参数。
#include “encoder.h”
#include “stm32f10x_gpio.h”
/****************************************************************
函数功能:把TIM2初始化为编码器接口模式
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM2(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//使能定时器4的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2, ENABLE);
//使能PA端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOA, &GPIO_InitStructure); //初始化GPIOA
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
//设定计数器自动重载值
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD;
//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM2, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM2, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM2, &TIM_ICInitStructure);
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE);
//Reset counter
TIM_SetCounter(TIM2,0);
TIM_Cmd(TIM2, ENABLE);
}
(1) (4)同理,编写Encoder_Init_TIM4函数,TIM4定时器采集的是右侧的编码器返回值。
/****************************************************************
函数功能:把TIM4初始化为编码器接口模式
入口参数:无
返回值:无
****************************************************************/
void Encoder_Init_TIM4(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
//使能定时器4的时钟
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM4, ENABLE);
//使能PB端口时钟
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6|GPIO_Pin_7; //端口配置
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_IN_FLOATING; //浮空输入
GPIO_Init(GPIOB, &GPIO_InitStructure); //初始化GPIOA
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; //预分频器
//设定计数器自动重载值
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD;
//选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//TIM向上计数
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up;
TIM_TimeBaseInit(TIM4, &TIM_TimeBaseStructure);
TIM_EncoderInterfaceConfig(TIM4, TIM_EncoderMode_TI12,
TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 10;
TIM_ICInit(TIM4, &TIM_ICInitStructure);
TIM_ClearFlag(TIM4, TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(TIM4, TIM_IT_Update, ENABLE);
TIM_SetCounter(TIM4,0);//Reset counter
TIM_Cmd(TIM4, ENABLE);
}
(5)编写编码器计数器读取函数
/*****************************************************************
函数功能:单位时间读取编码器计数
入口参数:定时器
返回值:速度值
*****************************************************************/
int Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
case 2: Encoder_TIM= (short)TIM2 -》 CNT; TIM2 -》 CNT=0;break;
case 4: Encoder_TIM= (short)TIM4 -》 CNT; TIM4 -》 CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
(2) 打开encoder.h,编写函数声明
#ifndef __ENCODER_H
#define __ENCODER_H
#include 《sys.h》
#define ENCODER_TIM_PERIOD (u16)(65535) //不可大于65535 因为F103的定时器是16位的
void Encoder_Init_TIM2(void);
void Encoder_Init_TIM4(void);
int Read_Encoder(u8 TIMX);
#endif
第三步 编写main.c文件
(3) (6)将工程编译需要用到的头文件包含进来,并预定义显示函数和全局变量。
#include “encoder.h” //包含编码器函数头文件
#include “sys.h” //包含系统头文件
#include “stm32f10x.h” //包含系统寄存器定义声明的头文件
void oled_show(void);
int Encoder_Left,Encoder_Right; //左右编码器的脉冲计数
(4) (7)在主函数中调用延时函数、显示函数和编码器函数的初始化函数。
int main(void)
{
delay_init(); //延时函数初始化
OLED_Init(); //OLED初始化
Encoder_Init_TIM2(); //编码器接口初始化
Encoder_Init_TIM4(); //编码器接口初始化
(5) (8)定义主循环,在主循环中调用超声波读取函数和显示函数。
while(1)
{
Encoder_Left=Read_Encoder(2); //读取左侧编码器值
Encoder_Right=Read_Encoder(4); //读取右侧编码器值
oled_show(); //显示屏打开
delay_ms(50);
}
(6) (9)编写OLED显示函数
void oled_show(void)
{
//显示右侧编码器返回值
OLED_ShowString(0,10,“Encoder_Right”);
if(Encoder_Right 》=0)OLED_ShowString(20,20,“”),
OLED_ShowNumber(45,20,Encoder_Right,4,12);
else OLED_ShowString(20,20,“-”),
OLED_ShowNumber(45,20,4-Encoder_Right,4,12);
//显示左侧编码器返回值
OLED_ShowString(0,40,“Encoder_Left”);
if(Encoder_Left》=0)OLED_ShowString(10,50,“”),
OLED_ShowNumber(45,50,Encoder_Left,4,12);
else OLED_ShowString(10,50,“-”),
OLED_ShowNumber(45,50,-Encoder_Left,4,12);
//=============刷新======================//
OLED_Refresh_Gram();
}
第四步 编译并下载,观察实验现象
(10)本实验采用仿真器为STLink V2,将仿真器与小车相连,注意正负极不要接反,
(11)编译程序:点击如图8所示的编译按键。
图8Keil编译环境下的编译按键
(12)当编译完成后,如果没有问题,Build Output栏会出现无错误、无警告的提示,如图9所示。
图9编译通过后Build Output栏提示信息
(13)下载程序:点击如图所示的下载按键,程序就会下载到STM32的芯片中。下载按键如图10所示。
图10Keil编译环境下的下载按键
(14)观察实验现象,OLED显示屏上显示出当前电机的转速,用手从不同方向转动电机,观察数值的变化。
举报