带编码器的直流减速电机——基于STM32F407

首先,什么是编码器?

编码器是将信号或数据进行编制、转换为可用以通讯、传输和存储的信号形式的设备。在这里,编码器就是能够将电机的转动信息(比如转速、转动角度等)转换为脉冲信号的设备。按照原理可分为(常见的)光电编码器(光学式)和霍尔编码器(磁式)。

接着,编码器的作用以及为什么要用编码器?

如上所述,编码器能够将电机的机械几何位移转化为脉冲信号或数字量。也就是说,有了编码器,我们通过检测编码器输出的脉冲信号,就能获取电机转动角度、转速等相关信息。这样我们不但能定性的控制电机的转向、转速,还能定量的测量。

那么,编码器的原理是什么以及怎么运用呢?

简单来说,就是电机带动码盘转动,码盘的结构使得当电机在转动时会产生A、B两相的脉冲信号,且这两路脉冲信号的相位差为90度(即正交)。如下图:
(图源自网络)
由于A、B信号正交,因此可以根据两个信号哪个先哪个后来判断方向,根据每个信号脉冲数量的多少及每圈电机产生多少脉冲数就可以算出当前行走的距离,如果再加上定时器的话还可以计算出速度。

好了,现在我们的处境是,我们有一个带编码器的直流减速电机,我们知道当电机转动的时候,它会产生A、B两相正交脉冲信号,通过检测脉冲信号我们就可以获取电机的运动状况。
那么,我们通过什么手段来检测脉冲数呢?
其中一种思路是,我们通过定时器的输入捕获或者GPIO引脚的外部中断来检测边沿变化,以此来检测脉冲数。这方法好像没毛病,当电机正常运转时行得通。但是如果电机输出的脉冲信号出现了毛刺呢?这样误差就来了,怎么办?通过软件编写算法来滤去毛刺似乎有点困难,于是我们想到通过硬件来处理这个毛刺。(而STM32正好有硬件编码器,nice!)
这里的脉冲输入是一种特殊的输入捕获情况,因此stm32专门在定时器中提供了编码器模式,可大大简化解析过程。

STM32的编码器接口模式
在该模式下能计算电机输出脉冲信号的个数,且stm32根据其内部机制能够消除毛刺的干扰。

配置过程:
由于编码器接口模式是一种特殊的输入捕获,所以要先配置一下输入捕获(毕竟你要通过捕获边沿来检测脉冲)。在输入捕获过程中,我们把A6、A7复用为TIM3,作为输入捕获的引脚,对电机的A、B相脉冲进行输入捕获。
输入捕获配置完成之后再配置一下编码器模式就可以开始工作了。

于是紧接着我们来看看编码器模式的配置函数TIM_EncoderInterfaceConfig:
STM32官方库函数
其输入参数中,TIMx就是输入捕获时设置的TIM3,polarity就分别是TI1和TI2的捕获极性(TI1是定时器输入通道1,TI2同理),即A、B两相信号的捕获极性(上升沿捕获或者下降沿捕获),重点来说说编码器的模式TIM_EncoderMode:
当模式选为TIM_EncoderMode_TI1时,计数器仅在TI1的边沿处计数
当模式选为TIM_EncoderMode_TI2时,计数器仅在TI2的边沿处计数
当模式选为TIM_EncoderMode_TI12时,计数器在TI1和TI2边沿处均计数
也就是说,模式1,我只以A相的脉冲信号为我的检测信号,TI2我就不管了。(B只是相对A滞后了90度,其它都是一样的,所以检测A就够了)。其它同理。
模式1和模式2显然就没什么差别了,那模式3前者有何不同呢?
这就涉及到网友们所说的编码器软件四倍频技术了:
四倍频
如图,A、B两相正交信号,如果根据模式1或模式2对脉冲信号进行检测,假设我们上升沿捕获一次,那么图中的信号我们只检测到3个上升沿,也就是三个脉冲。但是如果采用模式3,对TI1和TI2检测上升沿,那么同一段时间我们检测到6个上升沿。如果下降沿也检测,那么一共就是12个边沿跳变。每个实际来的脉冲会被检测4次,同样是12/4=3个脉冲,但是这样的话精度大大提高了。
入口参量:(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising);
通过这样配置,我们就完成了编码器软件的初始化。通过以上配置,我们完成了TIM3输入捕获的初始化以及编码器接口模式的相关配置,每来一个跳变沿,计数值变化一次。

至此,为了避免我们忘了初衷,现在我们来回顾一下我们最开始用编码器的初衷,前面我们说要用编码器来干嘛来着?获取电机转动角度、转速。
现在的处境是,我们通过STM32的TIM3的编码器模式,能够测出任意时刻的脉冲值了。现在要解决的就是如何将这个脉冲值转换成我们所要得到的信息(即转动角度、转速)。
首先,根据带编码器的直流减速电机的原理,显然无论电机的转速如何,每转产生的脉冲数是固定的。这里假设电机每转产生260个脉冲(具体数据各位自行查看自己的电机参数啦),那么只要我们用‘电机已产生的脉冲数’除以‘260个/转’,就可以得到电机转了多少圈,一圈即为360度,由此便可将脉冲数和转动角度联系起来。
这里要注意一点,由于我们用的是编码器模式3(也就是TIM_EncoderMode_TI12),我们得到的脉冲数是电机实际产生的脉冲数的四倍。则电机实际产生的脉冲数应为‘得到的计数值’除以4。于是,电机转动圈数为脉冲数除以260再除以4。
转动角度算出来了,转速呢?则是根据计数方向(递增计数或递减计数)来判断的,所以我们接下来的工作就是要搞清楚计数方向和编码器信号的关系。
让我们来看一下STM32的中文参考手册:
中文参考手册
网上几乎在这张表的讲解上都是一带而过,真正能把这张表讲清楚的几乎没有。后来看到一个人在表格上做了类似上图的注释,我才终于搞懂了。
大家还记得我们前面提到的TIM_EncoderInterfaceConfig函数吗,里面有个参量就是选择模式的,对吧。让我们再来回顾一下:
STM32官方库函数
注意到各模式后面的“xx edge depending on xx level”了吗?这就是上面这个表里的第二纵列所提到的“相反信号的电平”。
TIM_EncoderMode_TI1: Counter counts on TI1FP1 edge depending on TI2FP2 level,意思就是当选择模式1时,计数器根据TI2的电平高低来记录TI1的边沿信号。
这样说可能不好理解,我们来举个具体的例子:
中文参考手册
如上图,电机转动时产生A、B两相信号通过TI1、TI2输入到TIM3。
假设我们选择的是模式1,即计数器仅在TI1的边沿处计数。我们观察TI1的上升沿,若此时对应的TI2信号处于低电平(下图红框),于是根据表格我们可以得到计数方向为递增,假设计数器递增时电机正向转动,则可判断此时电机正转。
在这里插入图片描述
在这里插入图片描述
再譬如,假设我们选择模式2,即计数器仅在TI2的边沿处计数。我们观察TI2的下降沿,若此时对应的TI1信号为高电平(下图蓝框),于是根据表格我们可以得到计数方向为递减,假设计数器递减时电机反向转动,则可判断此时电机反转。
在这里插入图片描述
在这里插入图片描述
也就是说,当以TI1为计数信号时,需要根据TI2的电平(也就是第二纵列的“相对信号的电平”)来进行判断是向上计数还是向下计数。TI2也同理。
e.g.如果“仅在TI1计数”,当TI1为“上升”沿(表中第三纵列),此时要根据TI2的电平来做判断。根据下图,可知当TI1为上升沿的时候TI2为低电平,则根据上表可得知计数器为向上计数。这在下图得到了验证。
在这里插入图片描述
再例如,如果“仅在TI2计数”时,当TI2为下降沿时,如果TI1为低电平,则为向上计数(上图左侧);当TI2为下降沿时,如果TI1为高电平,则为向下计数(上图中部);当TI2为上升沿时,如果TI1为高电平,则为向上计数(上图左部);当TI2为上升沿时,如果TI1为低电平,则为向下计数(图中中部);
至此为止,我们便实现了这样的操作:通过使用带编码器的直流减速电机产生正交脉冲,通过STM32的TIM的编码器模式对脉冲进行计数,根据计数方向和编码器信号的关系来判断电机转动方向,利用脉冲计数值来计算电机转动位移。

接下来是代码部分,有些容易让人迷惑的地方(至少是让我迷惑了很久):
输入捕获初始化时Period和Prescaler的作用:
这里的period即为计数器的重装载值,prescaler即为预分频系数。注意在编码器模式时,要把TIM理解为计数器,而不是定时器,这样的话,时钟信号就不是系统内部产生的,而是通过PA6、PA7输入到TIM3的TI1和TI2的。
有了这么个理解,再来看Period和Prescaler就不难理解了。Period就是计数器每一次能检测脉冲的最大值,每来一个脉冲计数值就加一,当计数值超过period就溢出中断。Prescaler就是对电机产生的脉冲信号进行分频的分频系数。比如当分频系数为2时,每当电机产生两个脉冲,stm32才认为接收到一个有效脉冲,计数值才加一。

以下为部分代码

//思路:初始化TIM的编码器模式后,在main函数里死循环不断的读取CNT的值,从而来获得电机的脉冲数数据,以此来计算电机的转速、所转圈数等参数
#include "sys.h"
#include "delay.h"
#include "usart.h"
#include "pwm.h"
#include "encode.h"
#include "exti.h"
#include "key.h"

u32 pwm = 50000;

int main(void)
{
	uint32_t cnt_temp;			//用于暂存TIM的计数值,即TIM检测到的脉冲的数量
	float pulse;				//电机产生的实际脉冲值
	float round;				//电机转的圈数
	
	encoder_init();				//TIM3编码器模式初始化,A6、A7分别作为A相和B相的脉冲输入
	exti_init();				//外部中断,用于通过按键修改数据(比如PWM)
	
	NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);	//设置系统中断优先级分组2
	delay_init(168);  								//初始化延时函数
	uart_init(115200);								//初始化串口波特率为115200
 	TIM4_PWM_Init(50000-1,84-1);					//TIM4初始化,用于产生PWM供给直流减速电机
													//84M/84=1Mhz的计数频率,重装载值50000,所以PWM频率为100000/50000=2hz,即整个周期为500ms
	while(1)
	{
		TIM_SetCompare1(TIM4,pwm);					//设置供给电机的PWM值
		cnt_temp = read_cnt();						//得到脉冲计数值
		pulse = cnt_temp/4.0f;						//由于是TIM_EncoderMode_TI12,所以要四分频,即除以四,得到实际的脉冲值
		round = cnt_temp/4.0f/260.0f;				//假设电机每转产生260个脉冲,则通过该公式可求出电机转了几圈
		printf("cnt_temp:%d\r\n", cnt_temp);		//向串口打印脉冲计数值
		printf("pulse:%f\r\n", pulse);				//向串口打印实际脉冲值
		printf("round:%f\r\n", round);				//向串口打印电机转了几圈
		delay_ms(1000);								//每1s循环更新一次
//		if(TIM3->CR1&1<<4)	printf("forward");
//		else	printf("backward");
	}
}



//把TIM3理解为一个计数器而不是一个定时器,则没有了时序信号。
//这里TIM3的时钟信号(或者说是计数信号)将由电机编码器输出的脉冲代替,也就是说电机脉冲信号成为TIM3的信号,电机每产生一个脉冲被TIM3检测到,则计数器CNT加一(类比于时序信号时每隔一个时间段计数值加一)
//这样的话,输入捕获的自动重装载值period则影响着脉冲值计数到多少之后就溢出,比如65535的话,则接收到65535个脉冲信号之后计数值置零溢出
//这样的话,输入捕获的预分频系数prescaler的作用是,当我不分频时,来一个电机脉冲信号我计数值就加一,当我二分频时,只有接收到两个脉冲信号我才认为是一个有效脉冲,计数值才加一,简单来说就是计数值总体除以二了
//这样我们就把输入捕获初始化完成了,接下来是编码器模式的初始化
//设为TIM_EncoderMode_TI12模式,即计数器在TI1和TI2上升沿处均计数,再根据设置的极性是TIM_ICPolarity_Rising,也就是在TI1和TI2的上升沿计数器累加(或累减)-->那么到时候要除以二
//这样,编码器的初始化就完成了,接下来我们只要通过函数得出它的计数值,就可以知道电机产生的脉冲数,再根据电机的参数(每转产生多少个脉冲)就可以得到电机转了几圈
#include "encode.h"
#include "sys.h"

//初始化编码器
void encoder_init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;
	TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
	TIM_ICInitTypeDef TIM_ICInitStructure;
	
	RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE);
	RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE);
	
	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
    GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_NOPULL;
	GPIO_InitStructure.GPIO_Pin = GPIO_Pin_6 | GPIO_Pin_7;
	GPIO_Init(GPIOA, &GPIO_InitStructure);

	GPIO_PinAFConfig(GPIOA, GPIO_PinSource6, GPIO_AF_TIM3);    
	GPIO_PinAFConfig(GPIOA, GPIO_PinSource7, GPIO_AF_TIM3);
	
	//Specifies the prescaler value used to divide the TIM clock.
	//也就是说,这里的TIM3的时钟信号其实是由A/B相的频率来决定的,类似于外部时钟,然后分频就是对这个脉冲频率分频,比如二分频就是把两个脉冲记为一个脉冲。
	TIM_TimeBaseStructure.TIM_Prescaler = 1-1;					//这里我们把它设为1,即不分频
	TIM_TimeBaseStructure.TIM_Period = 65535;					//每来一个脉冲信号的上升沿(下面有设置)计数值就累加(或累减),65535则为最大计数值,就溢出了
	TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1;
//	TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //这里按理来说应该不起作用,因为计数方向是受TI1和TI2信号的影响的
	TIM_TimeBaseInit(TIM3, &TIM_TimeBaseStructure);
	
	TIM_ICStructInit(&TIM_ICInitStructure);						//Fills each TIM_ICInitStruct member with its default value
	//相当于:
	//	void TIM_ICStructInit(TIM_ICInitTypeDef* TIM_ICInitStruct)
	//	{
	//	  /* Set the default configuration */
	//	  TIM_ICInitStruct->TIM_Channel = TIM_Channel_1;
	//	  TIM_ICInitStruct->TIM_ICPolarity = TIM_ICPolarity_Rising;
	//	  TIM_ICInitStruct->TIM_ICSelection = TIM_ICSelection_DirectTI;
	//	  TIM_ICInitStruct->TIM_ICPrescaler = TIM_ICPSC_DIV1;
	//	  TIM_ICInitStruct->TIM_ICFilter = 0x00;
	//	}
	TIM_EncoderInterfaceConfig(TIM3, TIM_EncoderMode_TI12, TIM_ICPolarity_Rising ,TIM_ICPolarity_Rising); //配置为编码器模式,计数器在TI1和TI2上升沿处均计数

	TIM_SetCounter(TIM3, 0);		//将脉冲计数值设为零
	TIM_Cmd(TIM3, ENABLE);			//使能TIM3
}

// 读取定时器计数值
uint32_t read_cnt(void)
{
	uint32_t encoder_cnt;
	encoder_cnt = TIM3->CNT;		//读取计数器CNT的值,CNT系uint32_t型的变量
	TIM_SetCounter(TIM3, 0);		//每一次读取完计数值后将计数值清零,重新开始累加脉冲,方便下一次计数
	return encoder_cnt;				//返回的值就是本次读到的计数值
}


猜你喜欢

转载自blog.csdn.net/weixin_44692935/article/details/97419594