系列导读:本系列不讲流水账,只讲思路。AI 时代,思路大于实现——真正的难点在于"为什么这么分层、为什么这么调度、为什么这么算",代码只是思路的载体。每一期围绕一个模块展开,说明逻辑架构与实现思路,关键代码片段作为佐证。


1. 项目是什么

这是一台轮腿式平衡小车,主控是英飞凌 Traveo II 系列 CYT4BB7 双核 Cortex-M7 单片机,搭配逐飞科技 CYT4B 核心板。

它的特别之处在于既有轮又有腿

  • :左右两个轮毂电机,负责驱动与差速转向;
  • :每侧前、后两个舵机组成的四连杆机构,可以调整车身高度,甚至可以跳起来越障。

所以它不只是"会跑的平衡车",而是一个能实现自平衡 + 自主巡航 + 跳跃越障 + 视觉循线的组合体。

轮腿车 = 轮式(速度/转向) × 腿式(姿态/越障)
       = 平衡控制算法 + 运动学逆解 + 路径规划 + 视觉感知

2. 硬件平台一览

模块 型号 作用
主控 CYT4BB7 (Traveo II, 双 M7) 全部算法与调度
IMU 逐飞 IMU660RC(6 轴) 姿态角 pitch/roll/yaw、角速度
摄像头 逐飞 MT9V03X(120×188) 视觉循线、跳跃触发识别
显示屏 IPS200(SPI) 菜单与调试信息
电机 轮毂电机 ×2(UART) 驱动与差速,带转速回读
舵机 四连杆腿部舵机 ×4(PWM) 腿部姿态控制
GPS GNSS 模块(≥16 颗卫星有效) 全局定位、路径打点
无线 LoRa 遥控 手遥 / 路径录制

3. 整体软件架构

这是整个项目最值得讲的地方。代码被清晰地分成了 四层

┌─────────────────────────────────────────────┐
│  中断层 isr.c  (1ms / 10ms 定时调度)         │
├─────────────────────────────────────────────┤
│  应用层 app/  (balance/ik/jump/path/vision) │
├─────────────────────────────────────────────┤
│  驱动层 drivers/ (imu/camera/motor/servo)    │
├─────────────────────────────────────────────┤
│  抽象层 hal/  (uart/pwm/spi/flash)          │
└─────────────────────────────────────────────┘
  • 中断层:把实时性要求高的任务放进 1ms 定时中断里跑(平衡控制必须卡着节拍来);
  • 应用层:纯算法,不碰寄存器,只操作 CarState_t 结构体;
  • 驱动层:封装逐飞库,向上暴露简洁接口(如 imu_driver_read());
  • 抽象层:把寄存器操作包成 hal_uart_init() 这类通用函数。

这样分层的好处:算法和硬件解耦。想换摄像头、换电机,只改驱动层;想改平衡逻辑,只动 app 层。


4. 数据中枢 CarState_t:一辆车的"共享内存"

这是架构里最核心的设计——所有模块之间不直接互调函数传参,而是通过一个全局结构体 CarState_t 交换数据。

typedef struct {
    float pitch, roll, yaw;       /* IMU 姿态 */
    float gyro_y, gyro_z;         /* 角速度 */

    float target_speed;           /* 控制目标:速度 */
    float target_yaw;             /* 控制目标:偏航 */

    int left_motor_out;           /* 电机输出 */
    int right_motor_out;

    Pid_t pid_as, pid_pitch;      /* 各级 PID */
    Servo_t servo_l1, servo_r1;   /* 四个腿部舵机 */

    PathData_t path;              /* 路径数据 */
    PathRecord_t record;          /* 打点记录 */
    uint8_t run_flag, task_flag;  /* 运行/任务状态 */
    ...
} CarState_t;

设计思路:

  1. 一个状态体,处处可见main.c 里定义 CarState_t car,中断、算法、驱动全部读写它;
  2. 天然是"控制环"的接口:传感器把数据写进去,算法从里面读目标、写输出,驱动从里面取输出下发;
  3. 方便调试:所有中间量都在一个结构体里,屏显和串口打印想要什么直接取。

一句话:CarState_t 就是这辆车的"总线"。模块间只通过它说话,谁也不用知道谁存在。


5. 双线程式调度:主循环 vs 定时中断

这是全项目调度设计的关键。系统没有用 RTOS,而是靠 两个 1ms 中断 + 一个主循环 分工:

5.1 1ms 中断:控制实时部分

void pit0_ch0_isr(void) {
    pit_isr_flag_clear(PIT_CH0);
    if (car.imu_init_cnt >= 401) {   /* IMU 稳定后再开控 */
        /* 1. 更新里程/融合定位 */
        fusion_update(&car, ...);
        /* 2. 按 run_flag 执行对应控制 */
        if (car.run_flag == 1) {         /* 自主复现 */
            balance_update(&car);
            motor_driver_set_duty(...);
            servo_driver_control(...);   /* 腿部跟随 */
        }
        else if (car.run_flag == 2) {    /* 遥控模式 */
            ...
        }
    }
}

为什么放中断?平衡控制要求严格的 1ms 周期,中断天然满足定时精度,不依赖主循环忙闲。

5.2 另一个 1ms 中断:任务状态机

void pit0_ch2_isr(void) {
    pit_isr_flag_clear(PIT_CH2);
    if (car.run_flag == 1) {
        path_follow_update(&car);   /* 路径跟踪 → 设置目标速度/目标偏航 */
        if (car.task_flag == 1 && car.jump_flag == 1) {
            jump_manager(&car);     /* 跳跃状态机 */
        }
    }
}

设计意图:把"路径决策"和"实时平衡"分开。平衡环 1ms 一次,路径决策也 1ms 一次但逻辑上更"慢节奏"——它们互不阻塞,实时性都有保证。

5.3 10ms 中断:LoRa 遥控心跳

void pit0_ch1_isr(void) {
    pit_isr_flag_clear(PIT_CH1);
    lora_driver_tick(&car);   /* 遥控超时检测 */
}

遥控数据频率不需要 1ms,10ms 足够,还能省 CPU。

5.4 主循环:非实时任务

int main(void) {
    system_init(&car);
    while (1) {
        imu_driver_read(&car);              /* 读 IMU */
        path_record_gps_update(&car);       /* 收 GPS */
        if (camera_driver_frame_ready()) {  /* 处理一帧图像 */
            image_binarize(image_bin, raw, car.img_threshold);
            image_noise_filter(image_bin);
            track_detect_process(&car, image_bin);
        }
        if (car.run_flag != 1) {            /* 待机时显示菜单 */
            menu_process(&car);
            menu_display(&car);
        }
    }
}

主循环只做慢任务:图像处理(一帧 120×188 的遍历)、GPS 解析、菜单刷新。它们允许几百微秒级的抖动,放主循环最合适。


6. 调度哲学总结

实时性要求:  1ms平衡 > 1ms路径 > 10ms遥控 > 主循环图像
放在哪:      中断 1   →  中断 2  →  中断 3  →  main

这就是嵌入式控制里最常见的"分级调度"思想:

  1. 越紧急、周期越严格的,越靠近中断
  2. 越耗时、越不紧急的,越往主循环沉
  3. 层与层之间用状态体通信,互不依赖。

这套思路其实通用性很强——任何"传感器采集 + 控制 + 人机交互"的小车/机器人项目都可以照搬这个骨架,换成 STM32、ESP32 只是换驱动层而已。


7. 下期预告

架构搭好了,接下来进入第一个核心算法:两轮自平衡的级联 PID 控制

我们会讲清楚:为什么平衡要用"角速度环 + 俯仰角环 + 速度环"三环嵌套,而不是一个 PID 一把梭,以及横滚环、偏航环是怎么在平衡之上叠加的。

欢迎关注,持续连载中。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐