Skip to content

第17章 K3机器人

17 第 17 章 课程实验一:K3 机器人

本章通过 K3 教学机器人平台,将前述章节的理论知识(嵌入式编程、电机驱动、PID 控制、传感器融合)落地为可运行的完整系统。学生将完成从底层驱动到上层应用的全栈开发。

17.1 K3 机器人平台概述

17.1.1 硬件架构

K3 是一款面向教学的轮式移动机器人,核心控制器采用 STM32F103,搭配丰富的传感器与执行器:

STM32F103RCT6 WiFi ESP8266 72MHz Cortex M3 05 256KB Flash 48KB SRAM TB6612FNG OLED 128x64 7.4 LDO 3.3V 5V PWM LED x2 机 x2 K3 机器人硬件架构 主控制器 传感器模块 通信模块 电机驱动 显示交互 电源管理 红 外 避 障x4 超 声 波 测 距x1 蓝 牙HC 循 迹 传 感 器x5 串 口USART 编 码 器x2 锂 电 池 直 流 减 速 电 蜂 鸣 器 控 制 状 态 灯x4 电 压 检 测ADC

图 17-1 上图直观呈现了硬件架构的组成要素与数据通路,有助于理解系统整体的工作机理。

17.1.2 软件架构

K3 机器人的软件采用分层架构设计,与第 2 章介绍的设计模式一脉相承:

表 17-1 软件架构

层次 模块 说明
应用层 任务调度器 FreeRTOS 多任务管理,状态机控制
算法层 PID 控制、循迹、避障 第8章 PID 理论的实际应用
驱动层 电机、传感器、OLED 基于 HAL 的外设封装
HAL 层 STM32 HAL 库 CubeMX 自动生成

通过上表的对比可以看出,不同方案在层次、模块、说明等方面各有优劣,实际选型时应结合具体应用场景综合权衡。

17.2 实验一:电机驱动与速度闭环

17.2.1 实验目标

  • 理解 TB6612FNG 双 H 桥驱动原理
  • 实现编码器测速功能
  • 完成速度 PID 闭环控制
  • 验证直线行走精度

17.2.2 驱动原理

TB6612FNG 通过两路 PWM 信号控制两个电机的转速与方向:

PA6 PA7 TIM3C1 PB0 PB1 TIM3C2 3.3V TB6612FNG AO1 AO2 M2+ M2 STBY VCC GND AIN1 AIN2 PWMA M1+ M1 BIN1 BIN2 PWMB BO1 BO2 TB6612FNG 驱动逻辑 STM32 电机 7.4V

图 17-2 上图以框图形式描绘了驱动原理的系统架构,清晰呈现了各模块之间的连接关系与信号流向。

方向控制真值表:

表 17-2

AIN1 AIN2 电机状态
H L 正转
L H 反转
L L 停止(滑行)
H H 制动

上表对驱动原理中各方案的特性进行了横向对比,便于读者根据实际需求选择最合适的技术路线。

17.2.3 编码器测速

使用 STM32 的编码器模式读取光电编码器脉冲:

/* 编码器初始化(TIM2, TIM4 编码器模式) */
void Encoder_Init(void) {
    /* CubeMX 已配置 TIM2/TIM4 为 Encoder Mode */
    HAL_TIM_Encoder_Start(&htim2, TIM_CHANNEL_ALL);
    HAL_TIM_Encoder_Start(&htim4, TIM_CHANNEL_ALL);
}

/* 获取编码器计数值并清零(每 10ms 调用一次) */
int16_t Encoder_Read(TIM_HandleTypeDef *htim) {
    int16_t count = (int16_t)__HAL_TIM_GET_COUNTER(htim);
    __HAL_TIM_SET_COUNTER(htim, 0);
    return count;
}

/* 速度计算(单位:cm/s) */
float Speed_Calculate(int16_t encoder_count) {
    /* 轮径 65mm, 编码器线数 330, 减速比 30:1 */
    /* 每圈脉冲 = 330 * 4 * 30 = 39600(四倍频) */
    float wheel_circumference = 3.14159f * 6.5f;  /* cm */
    float speed = (float)encoder_count / 396.0f * wheel_circumference * 100.0f;
    return speed;  /* cm/s */
}

17.2.4 PID 速度闭环

typedef struct {
    float Kp, Ki, Kd;
    float target;
    float integral;
    float last_error;
    float output;
} PID_t;

PID_t pid_left  = {.Kp = 8.0f, .Ki = 0.5f, .Kd = 1.0f};
PID_t pid_right = {.Kp = 8.0f, .Ki = 0.5f, .Kd = 1.0f};

float PID_Update(PID_t *pid, float measured, float dt) {
    float error = pid->target - measured;
    pid->integral += error * dt;

    /* 积分限幅 */
    if (pid->integral > 500.0f)  pid->integral = 500.0f;
    if (pid->integral < -500.0f) pid->integral = -500.0f;

    float derivative = (error - pid->last_error) / dt;
    pid->output = pid->Kp * error + pid->Ki * pid->integral + pid->Kd * derivative;
    pid->last_error = error;

    /* 输出限幅 */
    if (pid->output > 999.0f)  pid->output = 999.0f;
    if (pid->output < -999.0f) pid->output = -999.0f;

    return pid->output;
}

17.3 实验二:多传感器循迹

17.3.1 实验目标

  • 理解红外循迹传感器工作原理
  • 实现 5 路循迹传感器数据融合
  • 基于 PID 实现平滑循迹控制

17.3.2 传感器布局

前 向 S1 S2 S3 S4 S5 K3 机 器 人 底 盘 K3 底部循迹传感器布局(俯视) 左轮 右轮 进 方

图 17-3 该框图展示了传感器布局的核心结构,读者可以从中把握各功能单元的层次划分与协作方式。

17.3.3 加权偏差算法

/* 5 路传感器加权偏差计算 */
float TrackLine_GetError(uint8_t sensor[5]) {
    /* 权值:-2, -1, 0, +1, +2 */
    static const float weight[5] = {-2.0f, -1.0f, 0.0f, 1.0f, 2.0f};
    float weighted_sum = 0;
    int active_count = 0;

    for (int i = 0; i < 5; i++) {
        if (sensor[i]) {  /* 1 = 检测到黑线 */
            weighted_sum += weight[i];
            active_count++;
        }
    }

    if (active_count == 0) {
        return 999.0f;  /* 全部丢线,返回异常值 */
    }

    return weighted_sum / active_count;  /* 偏差范围 [-2, +2] */
}

/* 循迹 PID 控制 */
void TrackLine_Task(void *argument) {
    PID_t track_pid = {.Kp = 30.0f, .Ki = 0.0f, .Kd = 15.0f};
    uint8_t sensor[5];
    float base_speed = 25.0f;  /* cm/s */

    for (;;) {
        Sensor_ReadTrack(sensor);
        float error = TrackLine_GetError(sensor);

        if (error == 999.0f) {
            Motor_Stop();  /* 丢线停车 */
        } else {
            float correction = PID_Update(&track_pid, error, 0.01f);
            pid_left.target  = base_speed + correction;
            pid_right.target = base_speed - correction;
        }

        osDelay(10);
    }
}

17.4 实验三:超声波避障

17.4.1 实验目标

  • 掌握 HC-SR04 超声波传感器驱动
  • 实现基于状态机的避障逻辑
  • 对比反应式避障与规划式避障

17.4.2 核心代码

/* 超声波测距 (HC-SR04) */
float Ultrasonic_GetDistance(void) {
    uint32_t start, end;

    /* 发送 10us 触发脉冲 */
    HAL_GPIO_WritePin(TRIG_GPIO, TRIG_PIN, GPIO_PIN_SET);
    delay_us(10);
    HAL_GPIO_WritePin(TRIG_GPIO, TRIG_PIN, GPIO_PIN_RESET);

    /* 等待 Echo 上升沿 */
    while (!HAL_GPIO_ReadPin(ECHO_GPIO, ECHO_PIN));
    start = __HAL_TIM_GET_COUNTER(&htim1);

    /* 等待 Echo 下降沿 */
    while (HAL_GPIO_ReadPin(ECHO_GPIO, ECHO_PIN));
    end = __HAL_TIM_GET_COUNTER(&htim1);

    /* 计算距离 (cm) */
    return (float)(end - start) * 0.017f;
}

/* 避障状态机 */
typedef enum {
    STATE_FORWARD,
    STATE_TURN_LEFT,
    STATE_TURN_RIGHT,
    STATE_BACKWARD
} Obstacle_State_t;

void Obstacle_Task(void *argument) {
    Obstacle_State_t state = STATE_FORWARD;
    float distance;
    uint32_t turn_tick = 0;

    for (;;) {
        distance = Ultrasonic_GetDistance();

        switch (state) {
            case STATE_FORWARD:
                pid_left.target = 20.0f;
                pid_right.target = 20.0f;
                if (distance < 25.0f) {
                    state = STATE_TURN_LEFT;
                    turn_tick = osKernelGetTickCount();
                }
                break;

            case STATE_TURN_LEFT:
                pid_left.target = -15.0f;
                pid_right.target = 15.0f;
                if (osKernelGetTickCount() - turn_tick > 500) {
                    state = STATE_FORWARD;
                }
                break;

            default:
                state = STATE_FORWARD;
                break;
        }

        osDelay(50);
    }
}

17.5 实验四:OLED 状态显示

17.5.1 实验目标

  • 掌握 I2C 通信驱动 OLED
  • 实现机器人运行状态实时显示
  • 理解嵌入式 GUI 的基本方法

17.5.2 显示界面设计

K3 Robot v1.0 Mode: Track Batt: 7.2V L:25.3 cm/s R:24.8 cm/s Dist: 42.5cm Err: 0.3 OLED 128x64 显示

图 17-4 上图直观呈现了显示界面设计的组成要素与数据通路,有助于理解系统整体的工作机理。

17.6 综合实验:自主巡航任务

17.6.1 任务描述

综合运用前述实验的所有功能,完成以下自主巡航任务:

  1. 机器人从起点出发,沿黑线循迹前进
  2. 遇到障碍物时切换为避障模式,绕过障碍物后重新寻找黑线
  3. 到达终点后自动停车并通过蜂鸣器提示
  4. 全程在 OLED 上实时显示状态信息

17.6.2 任务调度框架

/* FreeRTOS 任务创建 */
void App_Init(void) {
    /* 高优先级:PID 速度控制(1kHz) */
    osThreadNew(MotorPID_Task, NULL, &(osThreadAttr_t){
        .name = "PID", .priority = osPriorityRealtime, .stack_size = 512
    });

    /* 中优先级:传感器采集(100Hz) */
    osThreadNew(Sensor_Task, NULL, &(osThreadAttr_t){
        .name = "Sensor", .priority = osPriorityHigh, .stack_size = 512
    });

    /* 中优先级:导航决策(50Hz) */
    osThreadNew(Navigation_Task, NULL, &(osThreadAttr_t){
        .name = "Nav", .priority = osPriorityAboveNormal, .stack_size = 1024
    });

    /* 低优先级:显示更新(10Hz) */
    osThreadNew(Display_Task, NULL, &(osThreadAttr_t){
        .name = "OLED", .priority = osPriorityNormal, .stack_size = 512
    });
}

/* 导航决策任务 */
void Navigation_Task(void *argument) {
    typedef enum { NAV_TRACK, NAV_AVOID, NAV_STOP } NavState_t;
    NavState_t state = NAV_TRACK;

    for (;;) {
        switch (state) {
            case NAV_TRACK:
                TrackLine_Control();
                if (Ultrasonic_GetDistance() < 20.0f) {
                    state = NAV_AVOID;
                }
                if (Sensor_AllBlack()) {
                    state = NAV_STOP;  /* 终点检测 */
                }
                break;

            case NAV_AVOID:
                Obstacle_Avoidance();
                if (Sensor_AnyTrack()) {
                    state = NAV_TRACK;  /* 重新找到黑线 */
                }
                break;

            case NAV_STOP:
                Motor_Stop();
                Buzzer_Beep(3);
                osThreadExit();
                break;
        }
        osDelay(20);
    }
}

17.7 本章小结

表 17-3 本章通过 K3 教学机器人平台,将课程前半部分的核心知识串联为一个完整的工程项目:

实验内容 对应章节知识
电机驱动与速度闭环 第5章(定时器PWM)、第6章(电机驱动)、第8章(PID)
多传感器循迹 第3章(GPIO/ADC)、第8章(PID)
超声波避障 第2章(状态机模式)、第3章(GPIO定时器)
OLED 显示 第3章(I2C通信)
综合巡航 第4章(FreeRTOS多任务)、第2章(分层架构)

以上内容归纳了本章小结的关键要素,为后续深入学习和工程实践提供了参考依据。

17.8 本章测验

Quiz results are saved to your browser's local storage and will persist between sessions.

#

1) 在 K3 机器人的速度闭环控制中,编码器的主要作用是?

#

2) K3 机器人 5 路循迹传感器的加权偏差算法中,权值从左到右为 -2, -1, 0, +1, +2。若仅 S4 和 S5 检测到黑线,偏差值为?

#

3) 在 FreeRTOS 任务调度中,为何将 PID 速度控制任务设为最高优先级?

#

4) HC-SR04 超声波传感器测距的原理是?

#

5) K3 机器人综合巡航任务中,从循迹模式切换到避障模式的触发条件是?

Quiz Progress

0 / 0 questions answered (0%)

0 correct