简介: 编码器是作为电机反馈的一个重要传感器,可以用于判断电机的转速和运动方向,在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机器人小车的话就扫描一下二维码进群把: