如何在直流电机驱动中加入里程计
文章摘要: 本文介绍了里程计在电赛控制题小车项目中的应用与实现方法。里程计能有效解决因视觉帧率、串口延迟等问题导致的小车跑偏问题,通过精确记录行驶距离来控制特定状态下的操作。实现方法主要基于编码器脉冲计数,详细解析了电机结构体设计和里程计算函数(清零与获取距离)。应用时需在任务开始前清零里程计,通过对比实际行驶距离与预设值来纠正小车轨迹偏差,提高循迹等任务的可靠性。文章提供了完整的代码实现和关键参
一、里程计的意义
在电赛控制题中,小车是比较常见的一种题型,这些题型包括但不限于:循迹,惯导,识别等等。由于视觉部分帧率有限,或者是串口的传输不够及时,处理速度不够快,状态机逻辑不够清晰等等,都可能导致小车跑偏,提前进入下一状态等等问题。
里程计的意义,就是计算小车从上一次出发开始经过了多少距离,在特点的距离中做对应的事情,如2024年电赛H题,小车在循迹中可能会出现车头跑偏导致灰度传感器未识别,进而巡线失败的情况,因此,加入里程计计数,可以在巡线这一段里程中,规避“未识别”的情况,当灰度传感器未识别,而里程计还没有跑完时,就强制让小车转弯,直到重新识别到赛道为止。
二、里程计的实现
我们在进行直流电机控制时,总会要对直流电机进行编码器测速。以霍尔编码器为例,假设电机的减速比为30,霍尔编码器为13线,那么直流电机运行一圈就会输出13*30=390个脉冲,如果你使用的单片机支持编码器模式(如STM32的定时器Encoder模式),那么还可以设置编码器倍频,在四倍频的情况下,电机旋转一圈就会输出4*390=1560个脉冲。
举个例子,这是我现在使用的直流电机结构体,包括直流电机,编码器和pid三个部分:
typedef struct {
int16_t overflow_num;
int32_t last_count;//上一次计数值
int32_t total_count;//总计数值
int32_t distance_start_ticks;//里程计开始时的计数值
float Ts;//控制频率
float speed;//编码器测定速度
float speed_Record[SPEED_RECORD_NUM];
TIM_HandleTypeDef *timEncoder; // 编码器定时器
} encoder_t;
typedef struct{
float kp,ki,kd;//PID参数
float err,last_err;//误差值,上一次误差值
float integral,max_integral;//积分值,最大积分值
float output,max_output;//输出值,最大输出值
float dead_zone;//PID死区
float Ts;
} pid_t ;
typedef struct {
float Target_speed; //rpm
float Target_pos ; //target position cm
int32_t pos_pulse;// 目标脉冲
float pwmCCR;//pwm比较值,用于确定PWM输出的占空比
TIM_HandleTypeDef *PWM_TIM;//PWM定时器
#if MOTOR_DRIVE_MODE
uint32_t pwmChannel; // PWM通道
GPIO_TypeDef *forwardGPIOx; // 正向GPIO
uint16_t forwardGPIOpin; // 正向GPIO引脚
GPIO_TypeDef *reverseGPIOx; // 反向GPIO
uint16_t reverseGPIOpin; // 反向GPIO引脚
#else
uint32_t PWM_Channel_Forward;//正向PWM通道
uint32_t PWM_Channel_Reverse;//反向PWM通道
#endif
encoder_t encoder;//电机编码器结构体
pid_t speed_pid;//速度环PID结构体
pid_t pos_pid;//位置环PID结构体
} motor_t;
在编码器的结构体中,有一个成员变量int32_t distance_start_ticks;
然后是里程计相关的函数,其实也就只有两个
void Odometer_Reset(motor_t* motor) {
motor->encoder.distance_start_ticks = motor->encoder.total_count;
}
float Odometer_GetDistanceCm(motor_t* motor) {
// 1. 计算自复位以来经过的脉冲数
int32_t ticks_since_reset = motor->encoder.total_count - motor->encoder.distance_start_ticks;
// 2. 计算轮子周长 (单位: cm)
// WHEEL_DIAMETER 单位是mm, 除以10得到cm
float circumference_cm = (WHEEL_DIAMETER / 10.0f) * 3.14f;
// 3. 计算行驶距离
// 距离 = (经过的脉冲数 / 每圈总脉冲数) * 每圈的周长
float distance_cm = ((float)ticks_since_reset / PULSE_PER_CYCLE) * circumference_cm;
return distance_cm;
}
Odometer_Reset(motor_t* motor)函数用于将里程清零,也就是把里程计开始的计数值变成编码器的总计数值。
Odometer_GetDistanceCm(motor_t* motor)函数用于返回从里程计清零后,读取到的里程值,单位为cm,用到的宏定义如下:
#define MOTOR_SPEED_RERATIO 30//电机减速比
#define PULSE_PRE_ROUND 13//编码器每圈脉冲数
#define ENCODER_MULTIPLE 4.0//编码器倍频数
#define PULSE_PER_CYCLE (MOTOR_SPEED_RERATIO*PULSE_PRE_ROUND*ENCODER_MULTIPLE) //每圈总脉冲数
#define WHEEL_DIAMETER 66.0f//mm
编码器计数的内容这里就不赘述,也不是本文的重点。
三、里程计的应用
有了以上的里程计实现,我们就可以很快将其应用到工程中,比如,首先计算循迹的里程数。在循迹开始时,调用Odometer_Reset,然后在状态机里不断读取里程计的计数,如果里程计还没有达到某个值,但是灰度传感器也没有输出,就强制让左右电机差速转弯,直到重新识别到赛道为止。
需要注意,每次在使用里程计之前,务必将里程计清零,否则读取到的里程值就会混乱。
更多推荐



所有评论(0)