在暑假期间留校学习STM32,学到了电机霍尔编码器测速的部分,网上资料对于我这种笨笨的人来说组合起来有点烦恼,所以就有了这个讲得比较啰嗦的文章,在我参考了大佬们的方法和听了老师的讲解后我完成了这次实验,在此我把我成功测量的过程记录下来。以便自己以后能复习。
本文章参考了 Chance Z 大佬的文章:(1条消息) 二、读取编码器数值实现电机测速—3、速读_Chance Z的博客-CSDN博客
首先我们要了解霍尔编码器测速的原理,对于测速来说太基础,很多大佬都讲过这里就不多介绍。
本次实验计划是这样的:用一个定时器开启一个固定周期的中断,在中断中利用STM32定时器中的编码器模式(因为我的小车是两轮的这里采用的是两个定时器使用编码器模式),来进行一个对霍尔编码器发出的信号的捕获并保存。然后通过一个串口函数进行计算后发送到电脑上。
我们使用定时器的编码器模式的时候定时器会捕捉两个霍尔传感器发出的信号的上升沿和下降沿,捕获一次计一次数,这样的话霍尔编码器转一圈就会计4个数,也就大佬们说的四倍频。 这样会使我们测量得更加精准。

这里要说明一下,有实物就能发现电机转一圈霍尔传感器转几圈,所以霍尔传感器转一圈和电机转一圈不是同一个概念
首先我们推导下速度计算公式:
每秒速度(目标值)= 一秒内走过的总路程/一秒
= 一秒内的定时器计数值(捕获值)*计一次数所运动的距离
=(一秒内的定时器计数值*2*π*半径)/(基础线数*减速比*4);
计一次数所运动的距离=(2*π*半径)/电机转一圈编码器捕获的次数;
电机转一圈编码器捕获的次数=(基础线数*减速比*4);
设:一秒内的定时器计数值=n
每秒速度=speed
最后得出公式为:

以我的数据来计算:
R=2.25cm
基础线数=7
减速比=50
转一圈脉冲数=7*50*4=1400
计算得出speed=0.00010098*n (m/s)
为了测量的值的准确和读数单片机运算的及时我决定把公式换算成下面这样:
Speed=0.010098*n (cm/s)
定时期中断的周期需要改成10ms。说人话就是每10ms进行一次编码器捕获,而不是1s,计算公式就是这样。
下面为我初始化配置代码(F407vet6)
//每10ms进行一次捕获
void TIM3_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TimeBaseInitstrct;
NVIC_InitTypeDef NVIC_Initstruct;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM3, ENABLE); // ****使能TIM3的时钟
//***** TIM的参数初始化
TimeBaseInitstrct.TIM_ClockDivision=TIM_CKD_DIV1; //直接选1就行
TimeBaseInitstrct.TIM_CounterMode=TIM_CounterMode_Up; //选择计数方式
TimeBaseInitstrct.TIM_Period=arr; //Period是放自动重装载ARR的
TimeBaseInitstrct.TIM_Prescaler=psc; //Prescaler是装预分频系数PSC的
TIM_TimeBaseInit(TIM3, &TimeBaseInitstrct);
//***** 结束
TIM_ITConfig(TIM3,TIM_IT_Update,ENABLE); //使能TIM3的中断(Update为更新中断)
//***** 中断优先级的设置
NVIC_Initstruct.NVIC_IRQChannel=TIM3_IRQn; //这只中断通道
NVIC_Initstruct.NVIC_IRQChannelCmd=ENABLE; //使能
NVIC_Initstruct.NVIC_IRQChannelPreemptionPriority=0x01; //抢占优先级 1
NVIC_Initstruct.NVIC_IRQChannelSubPriority=0x03; //响应优先级 3
NVIC_Init(&NVIC_Initstruct);
//***** 结束
TIM_Cmd(TIM3,ENABLE); //使能TIM3
}
//轮子一号的编码器模式初始化
void Timer1_encoder_Init(u16 arr,u16 psc)
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_TIM1,ENABLE); //TIM1时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOE, ENABLE); //使能PORTF时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_11|GPIO_Pin_9; //光栅尺
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏输出
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_Init(GPIOE,&GPIO_InitStructure); //初始化PD
GPIO_PinAFConfig(GPIOE,GPIO_PinSource9,GPIO_AF_TIM1); //复用为TIM1
GPIO_PinAFConfig(GPIOE,GPIO_PinSource11,GPIO_AF_TIM1); //复用为TIM1
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 0xFFFF 65535
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM1,&TIM_TimeBaseStructure); //初始化TIM2
//编码器模式1 – 根据TI1FP1的电平,计数器在TI2FP2的边沿向上/下计数。
TIM_EncoderInterfaceConfig(TIM1,TIM_EncoderMode_TI1,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //编码器接口模式配置
TIM_ICStructInit(&TIM_ICInitStructure); //默认值赋值
TIM_ICInitStructure.TIM_ICFilter =0x2; //滤波 0x0~0xF
TIM_ICInit(TIM1, &TIM_ICInitStructure); //初始化
TIM_ClearFlag(TIM1, TIM_FLAG_Update);//清除标志位
TIM_ITConfig(TIM1, TIM_IT_Update, ENABLE); //更新中断
TIM_SetCounter(TIM1,0); //计数器清零
TIM_Cmd(TIM1, ENABLE);
}
//轮子二号的编码器模式初始化
void Timer2_encoder_Init(u16 arr,u16 psc) ///左轮
{
GPIO_InitTypeDef GPIO_InitStructure;
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM2,ENABLE); //TIM2时钟使能
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOA, ENABLE); //使能PORTF时钟
GPIO_InitStructure.GPIO_Pin = GPIO_Pin_5|GPIO_Pin_1; //光栅尺
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;//复用功能
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD; //开漏输出
GPIO_InitStructure.GPIO_PuPd= GPIO_PuPd_UP; //上拉
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz; //速度100MHz
GPIO_Init(GPIOA,&GPIO_InitStructure); //初始化PD
GPIO_PinAFConfig(GPIOA,GPIO_PinSource5,GPIO_AF_TIM2); //复用为TIM2
GPIO_PinAFConfig(GPIOA,GPIO_PinSource1,GPIO_AF_TIM2); //复用为TIM2
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler=psc; //定时器分频
TIM_TimeBaseStructure.TIM_Period=arr; //自动重装载值 0xFFFF 65535
TIM_TimeBaseStructure.TIM_ClockDivision=TIM_CKD_DIV1; //时钟分频
TIM_TimeBaseStructure.TIM_CounterMode=TIM_CounterMode_Up; //向上计数模式
TIM_TimeBaseInit(TIM2,&TIM_TimeBaseStructure); //初始化TIM2
//编码器模式1 – 根据TI1FP1的电平,计数器在TI2FP2的边沿向上/下计数。
TIM_EncoderInterfaceConfig(TIM2,TIM_EncoderMode_TI1,TIM_ICPolarity_Rising, TIM_ICPolarity_Rising); //编码器接口模式配置
TIM_ICStructInit(&TIM_ICInitStructure); //默认值赋值
TIM_ICInitStructure.TIM_ICFilter =0x2; //滤波 0x0~0xF
TIM_ICInit(TIM2, &TIM_ICInitStructure); //初始化
TIM_ClearFlag(TIM2, TIM_FLAG_Update);//清除标志位
TIM_ITConfig(TIM2, TIM_IT_Update, ENABLE); //更新中断
TIM_SetCounter(TIM2,0); //计数器清零
TIM_Cmd(TIM2, ENABLE);
}
编码器模式的功能就是在电机正转和反转时会采取不同的计数模式,设正转时为向上计数,那么反转时为向下计数。

下面是我用来读计数值和电机速度的代码:
int _Read_Encoder(u8 TIMX)
{
int Encoder_TIM;
switch(TIMX)
{
case 1: Encoder_TIM= TIM1 -> CNT; TIM1 -> CNT=0;break;
case 2: Encoder_TIM= TIM2 -> CNT; TIM2 -> CNT=0;break;
default: Encoder_TIM=0;
}
return Encoder_TIM;
}
void Get_Motor_Speed(int *ASpeed,int *BSpeed)
{
int AEncoder=_Read_Encoder(1); //读取编码器模式的捕获值
int BEncoder=_Read_Encoder(2);
if(BEncoder>55535) //判断正反转
{
BEncoder-=65535;
}
if(AEncoder>55535)
{
AEncoder-=65535;
}
*BSpeed = BEncoder* 100*0.010098; //速度计算
*ASpeed = AEncoder* 100*0.010098;
}
这段代码的具体思路是将ARR设置为最高,(16位 65535),正转时为正值,反转时因为计数值不能为负数所以就是(65535-CNT),为了更好的显示,也参考了下我所需要测量的速度我觉得当值超过55535时就减去65535,这样就能实现,正转为正值,反转为负值。

串口的发送函数:
void Uart_set(int ASpeedNow,int BSpeedNow) //把速度用串口输出
{
printf("——————————————\r\n");
printf("A=%d cm/s\r\n",ASpeedNow);
printf("B=%d cm/s\r\n",BSpeedNow);
delay_ms(500);
}
//10ms定时器的中断函数
void TIM3_IRQHandler(void) //每10ms进行一次编码器捕捉
{
if(TIM_GetITStatus(TIM3,TIM_IT_Update)==SET)
{
TIM_ClearITPendingBit(TIM3,TIM_IT_Update);
Get_Motor_Speed(&ASpeedNow,&BSpeedNow);
}
}
这就是结果

希望能帮到以后忘记的自己,也希望能帮助你们。