简介:
编码器是作为电机反馈的一个重要传感器,可以用于判断电机的转速和运动方向,在ROS机器人中可以为机器人提供一个位置参考。编码器又有增量式编码器、绝对式编码器、混合绝对式编码器等,具体的大家可以百度了解。
本文主要讲解常用的AB相编码器,一般是霍尔或者光电增量式编码器,实现的工具:
starrobot底层开发板、12V 5200ma锂电池、GB37-520减速电机、USB数据线、Keil5
starrobot底层开发板板载了A4950电机驱动器,预留和电机相同线序的XH2.54-6P接口,即插即用。电机转动主要使用到电机线+、电机线-两根线,编码器GND、编码器B相、编码器A相、编码器5V主要用于测速。
STM32的定时器1、2、3、4、5、8是具有编码器模式的,我们只需要按照手册进行配置即可,具体的大家可查阅STM32F4XX中文手册进行了解:
具体代码配置如下:
encoder.cpp
void Encoder::init(void)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseStructure;
TIM_ICInitTypeDef TIM_ICInitStructure;
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB1PeriphClockCmd(ENCODER_TIM_CLK[this->encoder_], ENABLE);
RCC_AHB1PeriphClockCmd(ENCODER_A_PORT_CLK[this->encoder_]|ENCODER_B_PORT_CLK[this->encoder_], ENABLE);
GPIO_InitStructure.GPIO_Pin = ENCODER_A_PIN[this->encoder_];
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(ENCODER_A_PORT[this->encoder_], &GPIO_InitStructure);
GPIO_InitStructure.GPIO_Pin = ENCODER_B_PIN[this->encoder_];
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_100MHz;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_AF;
GPIO_InitStructure.GPIO_OType = GPIO_OType_OD;
GPIO_InitStructure.GPIO_PuPd = GPIO_PuPd_UP;
GPIO_Init(ENCODER_B_PORT[this->encoder_], &GPIO_InitStructure);
GPIO_PinAFConfig(ENCODER_A_PORT[this->encoder_],ENCODER_A_GPIO_PinSource[this->encoder_],ENCODER_GPIO_AF_TIM[this->encoder_]);
GPIO_PinAFConfig(ENCODER_B_PORT[this->encoder_],ENCODER_B_GPIO_PinSource[this->encoder_],ENCODER_GPIO_AF_TIM[this->encoder_]);
TIM_TimeBaseStructInit(&TIM_TimeBaseStructure);
TIM_TimeBaseStructure.TIM_Prescaler = 0x0; // No prescaling //不分频
TIM_TimeBaseStructure.TIM_Period = ENCODER_TIM_PERIOD; //设定计数器自动重装值
TIM_TimeBaseStructure.TIM_ClockDivision = TIM_CKD_DIV1; //选择时钟分频:不分频
TIM_TimeBaseStructure.TIM_CounterMode = TIM_CounterMode_Up; //TIM向上计数
TIM_TimeBaseInit(ENCODER_TIM[this->encoder_], &TIM_TimeBaseStructure); //初始化定时器
TIM_EncoderInterfaceConfig(ENCODER_TIM[this->encoder_], TIM_EncoderMode_TI12, TIM_ICPolarity_Rising, TIM_ICPolarity_Rising);//使用编码器模式3
// TIM_EncoderInterfaceConfig(ENCODER_TIM[this->encoder_], TIM_EncoderMode_TI12, TIM_ICPolarity_Falling, TIM_ICPolarity_Rising);//使用编码器模式3
TIM_ICStructInit(&TIM_ICInitStructure);
TIM_ICInitStructure.TIM_ICFilter = 0;
TIM_ICInit(ENCODER_TIM[this->encoder_], &TIM_ICInitStructure);
TIM_ClearFlag(ENCODER_TIM[this->encoder_], TIM_FLAG_Update);//清除TIM的更新标志位
TIM_ITConfig(ENCODER_TIM[this->encoder_], TIM_IT_Update, ENABLE);
TIM_Cmd(ENCODER_TIM[this->encoder_], ENABLE);
}
配置好编码器驱动后,我们再编写一个定时器的驱动,100ms读取一次编码器的值并把编码器的值转换为rpm值,定时器代码如下:
void BaseBoard_TIM6_Init(u16 arr,u16 psc)
{
TIM_TimeBaseInitTypeDef TIM_TimeBaseInitStructure;
NVIC_InitTypeDef NVIC_InitStructure;
//84MHz
RCC_APB1PeriphClockCmd(RCC_APB1Periph_TIM6,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_TimeBaseInit(TIM6,&TIM_TimeBaseInitStructure);
TIM_ITConfig(TIM6,TIM_IT_Update,ENABLE);
TIM_Cmd(TIM6,ENABLE);
NVIC_InitStructure.NVIC_IRQChannel=TIM6_DAC_IRQn;
NVIC_InitStructure.NVIC_IRQChannelPreemptionPriority=0x01;
NVIC_InitStructure.NVIC_IRQChannelSubPriority=0x03;
NVIC_InitStructure.NVIC_IRQChannelCmd=ENABLE;
NVIC_Init(&NVIC_InitStructure);
}
void TIM6_DAC_IRQHandler(void)
{
int delta_ticks_1;
int delta_ticks_2;
int delta_ticks_3;
int delta_ticks_4;
if(TIM_GetITStatus(TIM6,TIM_IT_Update)==SET)
{
编码器采样周期 100ms
delta_ticks_1= (short)((TIM5 -> CNT) - 0x7fff);
delta_ticks_2= (short)((TIM2 -> CNT) - 0x7fff);
delta_ticks_3= (short)((TIM3 -> CNT) - 0x7fff);
delta_ticks_4= (short)((TIM4 -> CNT) - 0x7fff);
Motor_Rpm.Current_Rpm1 = ((float)(delta_ticks_1 / Time_counts_per_rev)*600.f);
Motor_Rpm.Current_Rpm2 = ((float)(delta_ticks_2 / Time_counts_per_rev)*600.f);
Motor_Rpm.Current_Rpm3 = ((float)(delta_ticks_3 / Time_counts_per_rev)*600.f);
Motor_Rpm.Current_Rpm4 = ((float)(delta_ticks_4 / Time_counts_per_rev)*600.f);
Motor_Rpm.MotorEncoder1 += ((float)(delta_ticks_1 / Time_counts_per_rev));
Motor_Rpm.MotorEncoder2 += ((float)(delta_ticks_2 / Time_counts_per_rev));
Motor_Rpm.MotorEncoder3 += ((float)(delta_ticks_3 / Time_counts_per_rev));
Motor_Rpm.MotorEncoder4 += ((float)(delta_ticks_4 / Time_counts_per_rev));
TIM2->CNT = 0x7fff;
TIM3->CNT = 0x7fff;
TIM4->CNT = 0x7fff;
TIM5->CNT = 0x7fff;
}
TIM_ClearITPendingBit(TIM6,TIM_IT_Update);
}
Motor_Rpm 是一个全局变量的结构体
typedef struct _Moto_
{
float Current_Rpm1;
float Current_Rpm2;
float Current_Rpm3;
float Current_Rpm4;
float MotorEncoder1;
float MotorEncoder2;
float MotorEncoder3;
float MotorEncoder4;
}_Moto_Str;
我们读取这个结构体里面的数据即可得到编码器的数据,为了把编码器的数据通过串口发送给ROS我们还需要定义一个消息类型和节点。
starrobot_msgs::Velocities raw_vel_msg;
ros::Publisher raw_vel_pub("raw_vel", &raw_vel_msg);
raw_vel_msg.encoder_motor1 = Motor_Rpm.MotorEncoder1*wheel_circumference_s*Direction_encoder1_value;
raw_vel_msg.encoder_motor2 = Motor_Rpm.MotorEncoder2*wheel_circumference_s*Direction_encoder2_value;
raw_vel_msg.encoder_motor3 = Motor_Rpm.MotorEncoder3*wheel_circumference_s*Direction_encoder3_value;
raw_vel_msg.encoder_motor4 = Motor_Rpm.MotorEncoder4*wheel_circumference_s*Direction_encoder4_value;
current_vel = kinematics.getVelocities(Motor1_Feedback_,Motor2_Feedback_,Motor3_Feedback_,Motor4_Feedback_);
raw_vel_msg.linear_y = current_vel.linear_y;
raw_vel_msg.linear_x = current_vel.linear_x;
raw_vel_msg.angular_z = current_vel.angular_z;
raw_vel_pub.publish(&raw_vel_msg);
然后就可以在上层收到编码器的数据。
3、总结
编码器数据获取主要使用到STM32 定时器的编码器模式,STM32的定时器在实际的应用中有很大作用,大家可以自己多扩展扩展定时器的其他功能,如果你也在自己动手制作ROS机器人小车的话就扫描一下二维码进群把: