一、里程计的意义

        在电赛控制题中,小车是比较常见的一种题型,这些题型包括但不限于:循迹,惯导,识别等等。由于视觉部分帧率有限,或者是串口的传输不够及时,处理速度不够快,状态机逻辑不够清晰等等,都可能导致小车跑偏,提前进入下一状态等等问题。

        里程计的意义,就是计算小车从上一次出发开始经过了多少距离,在特点的距离中做对应的事情,如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,然后在状态机里不断读取里程计的计数,如果里程计还没有达到某个值,但是灰度传感器也没有输出,就强制让左右电机差速转弯,直到重新识别到赛道为止。

需要注意,每次在使用里程计之前,务必将里程计清零,否则读取到的里程值就会混乱。

Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐