1. 项目缘起:为什么需要“双引擎”?

大家好,我是老张,一个在嵌入式视觉和机器人领域折腾了十多年的工程师。今天想和大家聊聊一个特别有意思的实战项目:用STM32做大脑,结合OpenMV和OpenCV搞一个能“眼观六路、全向移动”的智能小车。这可不是简单的玩具车,而是一个能真正理解环境、自主决策的移动机器人平台。

你可能要问,市面上做视觉小车的方案很多,为啥非要搞“双引擎”这么复杂?这得从我踩过的一个坑说起。几年前,我尝试用OpenMV做小车的“眼睛”,它小巧、集成度高,在单片机上跑图像处理确实方便。但很快我就发现,当我想识别一些更复杂的图案,或者想用上一些高级的机器学习模型时,OpenMV的算力就有点捉襟见肘了。反过来,如果全用电脑上的OpenCV来处理,虽然算法强大,但实时性、功耗和体积又成了新问题。

于是,“双引擎”的思路就诞生了。简单说,就是让OpenMV和OpenCV各司其职,优势互补。OpenMV作为前端的“侦察兵”,负责在车上实时采集图像、做一些轻量级的预处理(比如颜色追踪、简单形状识别),然后把关键信息或者原始图像数据传给后台。后台可以是另一块性能更强的板子(比如树莓派),甚至是一台电脑,运行着OpenCV这个“参谋部”,进行更复杂的图像分析、深度学习推理,最后再把决策指令(比如“目标在左前方30厘米处”)发回给STM32这个“指挥官”,控制小车运动。

这种架构的好处是显而易见的。它既保证了小车反应的实时性(OpenMV的快速响应),又拥有了强大的图像分析能力(OpenCV的丰富算法库)。特别适合用在需要复杂视觉判断,但又对延迟有要求的场景,比如物料分拣、跟随特定目标、或者是在复杂地形里自主导航。

2. 硬件搭建:从零开始组装你的“战车”

工欲善其事,必先利其器。硬件是项目的地基,选对部件能让后续开发事半功倍。下面这张清单是我多次项目后总结出的“黄金配置”,兼顾了性能、成本和可玩性。

部件名称 推荐型号/规格 核心作用 选购要点
主控制器 STM32F407VET6 系统大脑,负责逻辑控制、通信调度、运动解算 主频168MHz,内存192KB,外设丰富,性价比极高
视觉传感器1 OpenMV Cam H7 Plus 前端视觉“侦察兵”,实时图像采集与轻处理 H7内核性能更强,支持更高分辨率,带TF卡槽
视觉传感器2 普通USB摄像头 可选,用于连接后台OpenCV进行复杂处理 分辨率720P以上即可,注意驱动兼容性
电机与轮子 直流减速电机 + 麦克纳姆轮 x4 实现全向移动的执行机构 电机需带编码器反馈,麦克纳姆轮尺寸与车架匹配
电机驱动器 TB6612FNG或DRV8833模块 x2 驱动电机,接收PWM信号控制转速和方向 建议双路驱动模块,一个模块驱动两个电机
电源系统 18650锂电池两节串联(7.4V) + 降压模块 为全系统供电 电池需有保护板,降压模块输出5V和3.3V
车体结构 亚克力或铝合金多层小车底盘 承载所有电子部件 结构稳固,有足够的空间安装摄像头和扩展板
通信模块 USB转TTL串口模块 / ESP8266 WiFi模块 连接OpenMV与STM32,或连接后台电脑 串口通信最稳定简单,WiFi用于无线调试和数据传输

组装与接线实战经验:

  1. 车架与电机安装:先把四个麦克纳姆轮安装到电机上,注意轮子的辊子方向!这是全向移动的关键。四个轮的辊子方向需要呈“X”型对称分布。你可以记住一个口诀:左前轮和右后轮的辊子朝右下倾斜,右前轮和左后轮的辊子朝左下倾斜。装反了车就跑不起来了。把电机固定到车架底板上。
  2. 主控与驱动连接:STM32核心板或最小系统板固定在车架二层。将四个电机的线接到两个电机驱动模块上(每个模块驱动两个电机)。然后,将驱动模块的控制引脚(PWMA, AIN1, AIN2, PWMB, BIN1, BIN2)连接到STM32的6个GPIO口(需要支持PWM输出)。同时,把电机的编码器A、B相线也接到STM32的定时器编码器接口引脚,用于后续测速。
  3. 视觉系统安装:将OpenMV摄像头用支架安装在小车前方,确保视野开阔。它的串口TX、RX引脚直接与STM32的串口RX、TX交叉相连。如果你还用了一个USB摄像头给后台电脑,想办法把它也固定好。
  4. 电源布线:这是保证稳定的关键。7.4V电池正负极接入降压模块的输入,降压模块输出5V给电机驱动模块、OpenMV和USB摄像头(如果需要),再通过一个LDO(如AMS1117-3.3)从5V降压到3.3V给STM32供电。务必确保STM32和所有模块共地!

硬件组装完成后,先别急着写代码。用万用表仔细检查所有电源线路有没有短路,电压是否正确。然后单独测试每个模块:给STM32烧录一个简单的LED闪烁程序;手动给电机驱动模块高低电平信号看电机是否正反转;连接OpenMV IDE看能否正常预览图像。这些基础测试能帮你排除80%的硬件故障。

3. 软件环境配置:打通任督二脉

硬件是躯体,软件是灵魂。配置好开发环境,就像给机器人注入了灵魂。我们的软件栈涉及三个部分:STM32嵌入式开发、OpenMV图像编程、以及PC端的OpenCV环境。

3.1 STM32开发环境搭建

我强烈推荐使用 STM32CubeIDE,它是ST官方推出的免费集成开发环境,集成了CubeMX图形化配置工具和调试器,对新手非常友好。

  • 安装与项目创建:去ST官网下载安装。打开后,点击“Start new STM32 project”,选择你的芯片型号(如STM32F407VE)。在接下来的图形化界面(CubeMX)里,你需要进行关键配置:
    • 系统核心(SYS):将Debug改为Serial Wire,这样才能用ST-Link调试。
    • 时钟(RCC):将HSE(高速外部时钟)设置为Crystal/Ceramic Resonator,确保主频能跑到168MHz。
    • GPIO:找到你连接电机驱动和编码器的引脚,设置为对应的功能。例如,驱动PWM引脚设置为TIMx_CHy(定时器通道),方向引脚设置为GPIO_Output。编码器引脚设置为TIMx_CH1TIMx_CH2
    • 定时器(TIM):配置两个定时器用于生成4路PWM(控制4个电机),配置四个定时器用于编码器模式(读取4个电机转速)。
    • 串口(USART):配置一个串口(如USART2)与OpenMV通信,波特率设为115200。
  • 生成代码与基础驱动:配置完成后,点击“Generate Code”。CubeIDE会自动生成包含HAL库的初始化代码。你只需要在main.c/* USER CODE BEGIN 2 *//* USER CODE END 2 */之间添加你的应用逻辑。首先,可以写个测试函数,让四个轮子依次正转、反转,看看硬件连接是否正确。

3.2 OpenMV开发环境搭建

OpenMV的编程在专用的OpenMV IDE中进行,它基于MicroPython,上手极快。

  • 连接与固件:用USB线连接OpenMV Cam到电脑,打开OpenMV IDE。如果第一次使用,IDE可能会提示更新固件,建议更新到最新版本。在IDE左下角可以看到连接的设备型号和串口号。
  • 第一个视觉脚本:新建一个文件,输入以下最简代码测试摄像头和颜色追踪:
import sensor, image, time

# 初始化摄像头
sensor.reset()
sensor.set_pixformat(sensor.RGB565) # 彩色图像
sensor.set_framesize(sensor.QVGA)   # 320x240分辨率
sensor.skip_frames(time = 2000)     # 等待摄像头稳定
sensor.set_auto_gain(False)         # 关闭自动增益,颜色追踪更稳定
sensor.set_auto_whitebal(False)     # 关闭白平衡

# 定义要追踪的颜色的阈值 (这里以红色为例,需要根据实际环境调整)
red_threshold = (30, 100, 15, 127, 15, 127) # (L_min, L_max, A_min, A_max, B_min, B_max) in LAB color space

clock = time.clock()

while(True):
    clock.tick()
    img = sensor.snapshot()         # 抓取一帧图像

    # 在图像中寻找色块
    blobs = img.find_blobs([red_threshold], pixels_threshold=100, area_threshold=100)

    if blobs:
        # 找到最大的色块
        largest_blob = max(blobs, key=lambda b: b.pixels())
        # 在图像上画框
        img.draw_rectangle(largest_blob.rect())
        # 计算并打印色块中心坐标
        img.draw_cross(largest_blob.cx(), largest_blob.cy())
        print("中心坐标: (%d, %d)" % (largest_blob.cx(), largest_blob.cy()))

    # 在IDE中显示帧率
    print(clock.fps())

运行这个脚本,你应该能在IDE右侧看到实时画面,并用一个红色物体测试追踪效果。同时,串行终端会打印出目标中心的坐标和帧率。这就是OpenMV作为“侦察兵”的核心任务:快速定位目标位置。

3.3 PC端OpenCV环境搭建

对于后台处理,我们使用Python版的OpenCV。安装非常简单,在命令行(Windows的CMD或PowerShell,Mac/Linux的终端)里输入:

pip install opencv-python
pip install opencv-contrib-python  # 包含更多扩展模块
pip install numpy                  # OpenCV的依赖
pip install pyserial               # 用于串口通信,接收OpenMV数据

安装完成后,可以写个简单的脚本测试一下,比如打开USB摄像头并显示:

import cv2
cap = cv2.VideoCapture(0) # 0代表第一个摄像头
while True:
    ret, frame = cap.read()
    if not ret:
        break
    cv2.imshow('USB Camera', frame)
    if cv2.waitKey(1) & 0xFF == ord('q'):
        break
cap.release()
cv2.destroyAllWindows()

至此,三大软件环境全部就绪。接下来,我们要让它们“对话”。

4. 双引擎通信与数据流设计

“双引擎”协同工作的核心在于稳定、高效的数据通信。这里我设计了一个简单可靠的串口通信协议,让OpenMV和STM32,以及STM32和后台PC(运行OpenCV)之间能够准确传递信息。

4.1 OpenMV与STM32的通信

OpenMV将处理后的结果(比如目标坐标、颜色标签)通过串口发送给STM32。为了便于解析,我们约定一个简单的数据包格式。例如,发送目标中心坐标(x, y):

$X123,Y156#

$是帧头,XY是数据标识,123156是具体数值,#是帧尾。在OpenMV的脚本中,找到打印坐标的那行print,修改为按照协议格式发送:

# 在OpenMV的循环中
if blobs:
    largest_blob = max(blobs, key=lambda b: b.pixels())
    cx, cy = largest_blob.cx(), largest_blob.cy()
    # 格式化字符串并发送
    data_str = "$X{:03d},Y{:03d}#".format(cx, cy)
    uart.write(data_str) # 假设uart是之前初始化好的串口对象

在STM32端,我们需要编写一个串口中断服务程序来接收和解析这个数据包。思路是:在串口接收中断中,将字符存入缓冲区,并检测帧头$和帧尾#。当收到完整的帧后,在主循环中解析出XY后面的数字,就得到了目标在图像中的坐标。

4.2 STM32与后台PC的通信(可选高级模式)

当需要进行更复杂的识别(如人脸、特定Logo、深度学习模型推理)时,可以让OpenMV直接将原始图像通过串口(速度较慢)或WiFi模块发送给PC,PC上的OpenCV程序处理完后,再将结果指令(如“向左转”、“前进”)通过串口/WiFi发回STM32。

对于图像传输,由于数据量大,通常需要压缩(如JPEG)和分包。OpenMV提供了image.compress()函数。PC端用pyserial接收,再用OpenCV的cv2.imdecode()解码。这是一个进阶玩法,对实时性有影响,但极大地扩展了识别能力。

4.3 数据流整合

整个系统的数据流可以这样理解:

  1. 场景A(轻量任务):红色小球追踪。OpenMV实时识别小球坐标 -> 通过串口发送给STM32 -> STM32根据坐标计算小车运动指令 -> 控制电机。
  2. 场景B(重量任务):识别特定包装盒。OpenMV抓取一帧图像 -> 压缩后通过WiFi发送给PC -> PC运行OpenCV+深度学习模型进行识别分类 -> PC将分类结果和位置通过WiFi/串口发回STM32 -> STM32控制小车和机械臂进行抓取。

在实际项目中,你可以根据任务复杂度动态选择使用单引擎(仅OpenMV)还是双引擎模式。

5. 麦克纳姆轮运动学与核心控制算法

这是项目的精髓所在,也是很多朋友觉得最难的部分。麦克纳姆轮凭什么能横着走、斜着走、原地转圈?理解了它的运动学,控制代码写起来就清晰了。

5.1 运动学原理(尽量说人话)

你可以把麦克纳姆轮想象成普通轮子,但在轮缘上斜着安装了很多小辊子。正是这些辊子,让轮子除了提供前进的力,还能提供一个侧向的力。四个轮子特定角度安装,侧向力相互耦合或抵消,就产生了全向运动的效果。

我们定义小车的运动为三个自由度:前后移动(Vx)、左右平移(Vy)、原地旋转(ω)。每个轮子对这三个运动都有贡献。通过力学分解(这里省略复杂的矩阵推导),我们可以得到四个轮子速度(W1, W2, W3, W4 对应左前、右前、左后、右后)与小车整体运动指令(Vx, Vy, ω)之间的关系,这就是逆运动学公式:

W1 = Vx + Vy + C * ω
W2 = Vx - Vy - C * ω
W3 = Vx - Vy + C * ω
W4 = Vx + Vy - C * ω

其中,C是一个与小车轮距、轴距相关的常数。Vx, Vy, ω是你期望的小车运动速度(单位自定义,如cm/s和rad/s)。公式非常优美对称,计算出的W1~W4就是四个电机应该达到的速度值。

5.2 STM32上的算法实现

在STM32的代码中,我们需要实现一个函数,输入期望的Vx, Vy, ω,输出四个电机的PWM占空比。这里假设我们已经通过编码器测速实现了电机速度的闭环控制(即给定一个速度值,电机就能稳定达到那个速度)。

// 定义小车机械参数
#define WHEEL_RADIUS 3.0f    // 轮子半径,单位厘米
#define WHEEL_BASE 12.0f     // 左右轮距的一半,单位厘米
#define TRACK_WIDTH 12.0f    // 前后轴距的一半,单位厘米
// 常数C,由几何尺寸决定
#define C (WHEEL_BASE + TRACK_WIDTH)

// 麦克纳姆轮逆运动学解算
// vx: 前后速度 (cm/s), vy: 左右速度 (cm/s), omega: 旋转角速度 (rad/s)
void Mecanum_Kinematics(float vx, float vy, float omega, float* wheel_speeds) {
    wheel_speeds[0] = vx + vy + C * omega; // 左前轮 W1
    wheel_speeds[1] = vx - vy - C * omega; // 右前轮 W2
    wheel_speeds[2] = vx - vy + C * omega; // 左后轮 W3
    wheel_speeds[3] = vx + vy - C * omega; // 右后轮 W4

    // 可选:将线速度转换为电机转速 (RPM) 或 PWM值
    // 例如:电机转速 (RPM) = (轮子线速度 / (2 * π * 轮子半径)) * 60
    for(int i=0; i<4; i++) {
        wheel_speeds[i] = (wheel_speeds[i] / (2 * 3.1415926f * WHEEL_RADIUS)) * 60.0f;
    }
}

// 控制小车运动的接口函数
void Chassis_Move(float vx, float vy, float omega) {
    float target_speeds[4];
    Mecanum_Kinematics(vx, vy, omega, target_speeds);

    // 将目标速度设置为四个电机的速度环设定值
    Motor_Set_Target_Speed(MOTOR_FL, target_speeds[0]); // 左前
    Motor_Set_Target_Speed(MOTOR_FR, target_speeds[1]); // 右前
    Motor_Set_Target_Speed(MOTOR_BL, target_speeds[2]); // 左后
    Motor_Set_Target_Speed(MOTOR_BR, target_speeds[3]); // 右后
}

Motor_Set_Target_Speed函数内部会调用你写的PID控制器,根据编码器反馈的实际转速,调整PWM输出,让电机精确达到目标速度。这就是速度闭环控制,它是小车运动平稳、不漂移的关键。

5.3 结合视觉的反馈控制

现在,我们把视觉和运动控制连起来。假设OpenMV识别到目标在图像中心坐标为(cx, cy),图像分辨率是320x240,那么图像中心点是(160, 120)。我们可以计算一个偏差:

error_x = cx - 160;  // 水平方向偏差
error_y = cy - 120;  // 垂直方向偏差(对于固定仰角的摄像头,可映射为距离)

然后,用一个简单的P控制器(比例控制)来生成小车的运动指令:

// 在STM32的主循环中
if (new_vision_data_received) { // 收到新的视觉数据
    float kp = 0.05f; // 比例系数,需要根据实际调试
    float target_vx = 0.0f; // 默认不前后移动
    float target_vy = -kp * error_x; // 横向偏差驱动左右平移
    float target_omega = kp * error_y; // 纵向偏差(或面积)可能驱动旋转或前后移动

    Chassis_Move(target_vx, target_vy, target_omega);
}

这样,当目标在图像左侧(error_x为负),小车就会向右平移(target_vy为正),使目标回到图像中心,实现视觉伺服跟踪。这就是一个完整的“感知-决策-控制”闭环。

6. 实战进阶:双引擎融合案例与源码解析

理论讲完了,我们来点实际的。我设计一个综合案例:让小车自动寻找并移动到场地内的一个红色方块,然后通过OpenCV后台精确识别方块上的数字,并根据数字做出不同动作。

6.1 OpenMV端代码(侦察兵)

OpenMV负责快速定位红色方块区域,并将该区域的图像切片通过串口发送给PC。为了减少数据量,我们只发送感兴趣区域(ROI)。

# OpenMV Script: find_red_square_and_send_roi.py
import sensor, image, time, struct, uio, uos

# 初始化
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.skip_frames(time=2000)
sensor.set_auto_gain(False)
sensor.set_auto_whitebal(False)

# 红色阈值 (需校准)
red_threshold = (30, 80, 40, 80, 10, 70)
uart = uio.UART(3, 115200) # 假设使用P4/P5作为TX/RX

def send_roi_via_uart(img, roi):
    # roi: (x, y, w, h)
    # 1. 发送帧头 'ROIS'
    uart.write(b'ROIS')
    # 2. 发送ROI坐标信息 (4个unsigned short)
    uart.write(struct.pack('<HHHH', roi[0], roi[1], roi[2], roi[3]))
    # 3. 发送ROI图像数据 (RGB565格式)
    roi_img = img.copy(roi=roi).compress(quality=70) # 压缩一下
    uart.write(struct.pack('<I', len(roi_img))) # 发送图像数据长度
    uart.write(roi_img) # 发送图像数据

clock = time.clock()
while(True):
    clock.tick()
    img = sensor.snapshot()

    # 寻找红色色块
    blobs = img.find_blobs([red_threshold], pixels_threshold=500, area_threshold=500, merge=True)
    if blobs:
        largest_blob = max(blobs, key=lambda b: b.area())
        # 在图像上画框
        img.draw_rectangle(largest_blob.rect())
        # 发送ROI信息给PC
        send_roi_via_uart(img, largest_blob.rect())
        # 同时,将方块中心坐标发给STM32控制移动 (简单协议)
        cx, cy = largest_blob.cx(), largest_blob.cy()
        uart.write("$X{:03d},Y{:03d}#".format(cx, cy).encode())
    else:
        # 没找到,发个特殊指令让小车慢速旋转寻找
        uart.write(b"$X999,Y999#")

    print(clock.fps())

6.2 PC端OpenCV代码(参谋部)

PC端用Python接收ROI数据,进行数字识别(这里用简单的模板匹配或轮廓分析举例,实际可用OCR或训练好的CNN模型)。

# PC Script: receive_and_recognize_number.py
import cv2
import numpy as np
import serial
import struct
import threading

# 初始化串口 (连接STM32或OpenMV,取决于接线)
ser = serial.Serial('COM5', 115200, timeout=1) # 请修改为你的串口号

def parse_roi_data(serial_port):
    """解析来自OpenMV的ROI数据包"""
    header = serial_port.read(4)
    if header != b'ROIS':
        return None
    # 读取ROI坐标
    roi_data = serial_port.read(8) # 4个unsigned short, 8字节
    if len(roi_data) < 8:
        return None
    x, y, w, h = struct.unpack('<HHHH', roi_data)
    # 读取图像长度
    img_len_data = serial_port.read(4)
    if len(img_len_data) < 4:
        return None
    img_len = struct.unpack('<I', img_len_data)[0]
    # 读取图像数据
    img_data = serial_port.read(img_len)
    if len(img_data) < img_len:
        return None
    # 解码图像
    nparr = np.frombuffer(img_data, np.uint8)
    roi_img = cv2.imdecode(nparr, cv2.IMREAD_COLOR)
    return (x, y, w, h, roi_img)

def recognize_number(roi_image):
    """在ROI图像中识别数字(示例:简单轮廓法)"""
    gray = cv2.cvtColor(roi_image, cv2.COLOR_BGR2GRAY)
    _, thresh = cv2.threshold(gray, 127, 255, cv2.THRESH_BINARY_INV)
    contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    if contours:
        # 这里简化处理,假设找到的轮廓数量就是数字(仅作示例,实际识别需更复杂算法)
        num = len(contours)
        if 0 <= num <= 9:
            return num
    return -1 # 识别失败

def send_command_to_stm32(command):
    """发送指令给STM32"""
    # 简单协议,例如 'CMD1' 代表数字1对应的动作
    ser.write(command.encode())
    print(f"Sent command: {command}")

# 启动接收线程或循环
try:
    while True:
        result = parse_roi_data(ser)
        if result:
            x, y, w, h, roi_img = result
            cv2.imshow('Received ROI', roi_img)
            number = recognize_number(roi_img)
            if number != -1:
                print(f"Recognized Number: {number}")
                # 根据识别结果发送不同指令
                if number == 1:
                    send_command_to_stm32("CMD1") # 例如:原地转一圈
                elif number == 2:
                    send_command_to_stm32("CMD2") # 例如:后退一段距离
                # ... 其他数字对应动作
            if cv2.waitKey(1) & 0xFF == ord('q'):
                break
except KeyboardInterrupt:
    pass
finally:
    ser.close()
    cv2.destroyAllWindows()

6.3 STM32端代码(指挥官)

STM32同时监听来自OpenMV的坐标数据和来自PC的指令数据,综合决策。

// STM32 Code Snippet (主循环部分)
typedef enum {
    MODE_VISION_TRACKING,
    MODE_EXECUTE_PC_CMD,
    MODE_IDLE
} SystemMode_t;

SystemMode_t current_mode = MODE_VISION_TRACKING;
char pc_command_buffer[10];
bool new_pc_cmd = false;

void USART2_IRQHandler(void) {
    // 串口2中断,假设连接OpenMV
    // ... 解析坐标数据,更新 target_cx, target_cy ...
}

void USART3_IRQHandler(void) {
    // 串口3中断,假设连接PC
    // ... 接收PC指令,存入 pc_command_buffer,设置 new_pc_cmd = true ...
}

int main(void) {
    // 初始化...
    while (1) {
        switch(current_mode) {
            case MODE_VISION_TRACKING:
                if (new_vision_data) {
                    // 根据OpenMV坐标进行P控制移动
                    float vy = -KP_VISION * (target_cx - IMG_CENTER_X);
                    float vx = KP_VISION * (target_cy - IMG_CENTER_Y); // 假设y坐标映射前后距离
                    Chassis_Move(vx, vy, 0);
                    new_vision_data = false;
                }
                // 如果收到PC的强制指令,切换模式
                if (new_pc_cmd) {
                    if (strncmp(pc_command_buffer, "CMD1", 4) == 0) {
                        current_mode = MODE_EXECUTE_PC_CMD;
                        // 执行CMD1对应的动作序列
                    }
                    new_pc_cmd = false;
                }
                break;

            case MODE_EXECUTE_PC_CMD:
                // 执行来自PC的复杂动作序列
                // 例如:Chassis_Move(0, 0, 3.14); // 原地转180度
                // 动作执行完毕后,切换回视觉跟踪模式
                current_mode = MODE_VISION_TRACKING;
                break;

            case MODE_IDLE:
                Chassis_Move(0, 0, 0); // 停止
                break;
        }
    }
}

这个案例展示了“双引擎”的典型工作流:OpenMV快速锁定目标,STM32进行粗略跟踪;同时,OpenMV将关键图像区域发给PC,PC进行精细识别并下达高级指令,STM32融合两者信息做出最终决策。代码虽然简化,但清晰地展示了数据流和控制逻辑。在实际调试中,你需要仔细调整PID参数、通信协议和模式切换的逻辑,确保小车行为平滑、稳定。

Logo

更多推荐