三亩地 三亩地SAN MU DI · CODE DIARY
ARTICLE DETAIL

日记详情

真实记录编程学习的某一天,欢迎挑你感兴趣的翻一翻。

ESP32智能小车开发:从循迹避障到多传感器融合与任务调度

ESP32智能小车开发:从循迹避障到多传感器融合与任务调度

1. 先搞清楚 ESP32 智能小车到底能做什么,以及你需要准备什么

如果你手头有一块 ESP32 开发板,想把它变成一个能自己跑、能躲开障碍物、还能跟着你走的智能小车,那这个项目正合适。它不是一个简单的遥控车,而是集成了循迹(按黑线走)、避障(自动躲开障碍)和跟随(比如跟随一个移动的物体或人)三种核心功能的综合实践平台。对于想深入嵌入式、机器人控制或者物联网应用的人来说,这是一个绝佳的练手项目,能把传感器、电机控制、算法逻辑和 ESP32 的强大功能(如 Wi-Fi、蓝牙)串起来。

最值得关注的点不是功能列表有多长,而是如何让这三个功能在有限的硬件资源上稳定、协调地工作。很多人一开始会把代码堆在一起,导致小车行为混乱,或者某个功能占用太多 CPU 时间,影响其他任务的实时性。所以,这个项目的关键价值在于理解多传感器数据融合基于优先级的任务调度

在动手前,你需要准备好以下核心硬件和软件环境,这是项目能跑起来的基础:

硬件清单:

  • 主控:ESP32 开发板(如 ESP32-DevKitC、NodeMCU-32S 等)。这是小车的大脑。
  • 小车底盘:包括电机、车轮、电机驱动模块(常用 L298N 或 TB6612FNG)。底盘结构要稳固。
  • 电源:建议使用两路供电。一路(如 7.4V 锂电池)给电机驱动供电,另一路(通过降压模块降到 5V/3.3V)给 ESP32 和传感器供电,避免电机启动时的电压波动导致 ESP32 重启。
  • 循迹模块:常用的是红外对管式循迹传感器,有三路、五路等。五路传感器能提供更精确的路径信息,判断弯道、十字路口更准确。
  • 避障模块:常用红外或超声波测距模块(如 HC-SR04)。红外简单便宜但受环境光影响,超声波更稳定但响应稍慢。可以根据需要选择一种或组合使用。
  • 跟随模块:这通常不是单一模块。一种常见方案是使用超声波模块测距来实现“距离跟随”,或者使用摄像头模块(如 OV2640)结合图像识别算法,但后者对 ESP32 算力要求高。入门更推荐超声波方案。
  • 连接线、杜邦线、螺丝等

软件环境:

  • 开发平台:Arduino IDE 或 PlatformIO(基于 VSCode)。对于 ESP32 项目,PlatformIO 在库管理和项目结构上更有优势,我后续示例会基于它。
  • ESP32 开发板支持包:在 Arduino IDE 中需通过开发板管理器安装;在 PlatformIO 中创建项目时选择 ESP32 平台即可。
  • 必要的库:根据你使用的电机驱动、传感器型号,可能需要安装对应的库,例如用于超声波传感器的NewPing库,或者用于 PWM 电机控制的ESP32Servo库(虽然叫 Servo,但可用于生成电机 PWM 信号)。

注意:在焊接或接线前,务必确认所有模块的电压等级。ESP32 的 GPIO 口通常是 3.3V 电平,而一些传感器或驱动模块可能是 5V 电平。直接连接可能导致 ESP32 损坏或传感器工作不正常,必要时需使用电平转换模块。

2. 从零搭建硬件平台与基础驱动

硬件连接是第一步,也是最容易出错的一步。建议按照“电源 -> 主控 -> 执行器 -> 传感器”的顺序来搭建。

2.1 电源系统与电机驱动连接

电源系统的稳定是整个项目的基础。强烈建议采用双电源方案:

  1. 电机电源:锂电池(如 7.4V)正负极接入电机驱动模块(如 L298N)的电源输入端子。
  2. 控制电源:从电机驱动模块的+5V输出(如果支持且稳定)引出,或者使用独立的 5V 降压模块,为 ESP32 和所有传感器供电。确保共地(GND),即电机驱动的 GND、ESP32 的 GND、所有传感器的 GND 最终都要连接在一起。

以 TB6612FNG 驱动两个直流电机为例,连接方式如下:

  • VM:接电机电源正极(如 7.4V)。
  • VCC:接逻辑电源(5V),给芯片内部逻辑供电,可接 ESP32 的 5V 或外部 5V。
  • GND:接电源负极,并与 ESP32 GND 相连。
  • PWMA/AIN1/AIN2:接 ESP32 的 GPIO 引脚,用于控制电机 A 的 PWM 速度和方向。
  • PWMB/BIN1/BIN2:接 ESP32 的 GPIO 引脚,用于控制电机 B。
  • STBY:接高电平(如 3.3V)使能芯片。

2.2 ESP32 与传感器接线

将传感器信号线连接到 ESP32 的 GPIO 口。务必在代码中记录好你使用的引脚号,后续编程就基于这些定义。

一个典型的引脚分配示例如下(请根据你的实际模块调整):

  • 电机驱动
    • 左电机 PWM: GPIO 16
    • 左电机方向1: GPIO 17
    • 左电机方向2: GPIO 5
    • 右电机 PWM: GPIO 18
    • 右电机方向1: GPIO 19
    • 右电机方向2: GPIO 21
  • 五路循迹传感器(假设输出数字信号,检测到黑线为低电平):
    • 最左: GPIO 36 (仅输入,注意 ESP32 有些引脚只能输入)
    • 左: GPIO 39
    • 中: GPIO 34
    • 右: GPIO 35
    • 最右: GPIO 32
  • 超声波避障模块(HC-SR04):
    • Trig: GPIO 25
    • Echo: GPIO 26
  • 用于跟随的超声波模块(可与避障共用或另设一个):
    • Trig: GPIO 27
    • Echo: GPIO 14

2.3 编写基础电机控制函数

在软件层面,首先要封装好最底层的电机控制函数。这能让上层逻辑(循迹、避障算法)更清晰。

// motor_control.h 或直接在主程序中定义 #define LEFT_MOTOR_PWM 16 #define LEFT_MOTOR_IN1 17 #define LEFT_MOTOR_IN2 5 #define RIGHT_MOTOR_PWM 18 #define RIGHT_MOTOR_IN1 19 #define RIGHT_MOTOR_IN2 21 // 初始化电机控制引脚 void motor_init() { pinMode(LEFT_MOTOR_PWM, OUTPUT); pinMode(LEFT_MOTOR_IN1, OUTPUT); pinMode(LEFT_MOTOR_IN2, OUTPUT); pinMode(RIGHT_MOTOR_PWM, OUTPUT); pinMode(RIGHT_MOTOR_IN1, OUTPUT); pinMode(RIGHT_MOTOR_IN2, OUTPUT); // 初始状态设为停止 digitalWrite(LEFT_MOTOR_IN1, LOW); digitalWrite(LEFT_MOTOR_IN2, LOW); digitalWrite(RIGHT_MOTOR_IN1, LOW); digitalWrite(RIGHT_MOTOR_IN2, LOW); analogWrite(LEFT_MOTOR_PWM, 0); analogWrite(RIGHT_MOTOR_PWM, 0); } // 控制单个电机速度与方向,speed范围通常为0-255 void set_motor(int in1, int in2, int pwm_pin, int speed) { bool direction = (speed >= 0); speed = abs(speed); if(speed > 255) speed = 255; digitalWrite(in1, direction ? HIGH : LOW); digitalWrite(in2, direction ? LOW : HIGH); analogWrite(pwm_pin, speed); } // 封装小车动作:前进、后退、左转、右转、停止 void car_forward(int speed) { set_motor(LEFT_MOTOR_IN1, LEFT_MOTOR_IN2, LEFT_MOTOR_PWM, speed); set_motor(RIGHT_MOTOR_IN1, RIGHT_MOTOR_IN2, RIGHT_MOTOR_PWM, speed); } void car_turn_left(int speed, int bias) { set_motor(LEFT_MOTOR_IN1, LEFT_MOTOR_IN2, LEFT_MOTOR_PWM, speed - bias); set_motor(RIGHT_MOTOR_IN1, RIGHT_MOTOR_IN2, RIGHT_MOTOR_PWM, speed + bias); } // ... 其他动作函数

写好这些基础函数后,先在setup()里调用motor_init(),然后在loop()里写几行测试代码(如让小车前进2秒,左转1秒),确保电机响应正确。这是后续所有复杂功能的基石。

3. 分步实现核心功能:循迹、避障与跟随

不要试图一次性把三个功能的代码全写进去。应该逐个实现、单独测试,最后再考虑如何整合。我建议的测试顺序是:循迹 -> 避障 -> 跟随。

3.1 循迹功能实现与调参

循迹的本质是根据地面反射的红外光强度,判断小车相对于黑线的位置。五路传感器会返回一个5位的二进制状态(例如0b00100表示只有中间传感器检测到黑线)。

核心逻辑(PID 控制简化版):

  1. 读取传感器状态:将5个GPIO的输入读入一个变量。
  2. 位置偏差计算:给每个传感器位置赋予一个权重(如最左=-2,左=-1,中=0,右=1,最右=2),根据检测到黑线的传感器计算加权平均,得到偏差error
  3. 控制输出:根据error计算左右轮的速度差。可以使用简单的比例控制(P控制):left_speed = base_speed - Kp * error; right_speed = base_speed + Kp * errorKp是比例系数,需要实测调整。
  4. 边界处理:当只有最左或最右传感器检测到线时,说明偏差极大,可能需要原地转弯。
// 定义循迹传感器引脚 #define TRACK_LEFTMOST 36 #define TRACK_LEFT 39 #define TRACK_MID 34 #define TRACK_RIGHT 35 #define TRACK_RIGHTMOST 32 int track_sensor_pins[5] = {TRACK_LEFTMOST, TRACK_LEFT, TRACK_MID, TRACK_RIGHT, TRACK_RIGHTMOST}; int sensor_weights[5] = {-2, -1, 0, 1, 2}; // 位置权重 void track_init() { for(int i=0; i<5; i++) { pinMode(track_sensor_pins[i], INPUT); } } int get_track_error() { int sensor_values = 0; int sum = 0, count = 0; for(int i=0; i<5; i++) { // 假设检测到黑线为 LOW if(digitalRead(track_sensor_pins[i]) == LOW) { sum += sensor_weights[i]; count++; } } if(count == 0) { // 没有检测到线,可能是脱线,返回一个极大或极小的值触发搜索行为 return 999; } return sum; // 或返回 sum/count 得到平均位置 } void track_follow(int base_speed, float Kp) { int error = get_track_error(); int speed_diff = (int)(Kp * error); // 限制速度差在合理范围 speed_diff = constrain(speed_diff, -base_speed, base_speed); int left_speed = base_speed - speed_diff; int right_speed = base_speed + speed_diff; // 调用电机控制函数 set_motor(LEFT_MOTOR_IN1, LEFT_MOTOR_IN2, LEFT_MOTOR_PWM, left_speed); set_motor(RIGHT_MOTOR_IN1, RIGHT_MOTOR_IN2, RIGHT_MOTOR_PWM, right_speed); }

调参要点

  • 先让base_speed小一点(如 100),Kp从 10 开始试。
  • 观察小车在直道是否摆动过大(Kp太大)或反应迟钝(Kp太小)。
  • 在弯道观察能否顺利通过,会不会冲出轨道。
  • get_track_error()函数中对于“脱线”(count==0)情况的处理策略很重要,常见做法是让小车沿原方向转圈搜索,或者根据上一次的偏差方向继续转弯。

3.2 避障功能实现与优先级

避障功能需要更高的优先级,因为遇到障碍时必须立即停止或转向,否则会撞上。通常采用中断高频轮询的方式。

超声波测距实现:

#include <NewPing.h> #define OBSTACLE_TRIG_PIN 25 #define OBSTACLE_ECHO_PIN 26 #define MAX_DISTANCE 200 // 最大检测距离,单位厘米 NewPing sonar_obstacle(OBSTACLE_TRIG_PIN, OBSTACLE_ECHO_PIN, MAX_DISTANCE); bool check_obstacle(int safe_distance_cm) { unsigned int distance_cm = sonar_obstacle.ping_cm(); if(distance_cm == 0) { // 0 表示超出量程或检测失败,通常视为安全 return false; } return (distance_cm < safe_distance_cm); }

避障策略: 最简单的策略是“检测到障碍就停,然后转向”。但更好的策略是融入小车的当前状态。例如:

  1. 在循迹模式下,如果前方有障碍,先停车。
  2. 尝试轻微左转,检测左侧距离;再尝试轻微右转,检测右侧距离。
  3. 选择距离更远的一侧,执行一个定幅度的转向,绕过障碍。
  4. 绕过障碍后,重新尝试寻找黑线(这需要循迹算法能处理“脱线”状态)。

优先级处理:在loop()函数中,避障检查应该放在最前面。

void loop() { // 1. 最高优先级:安全避障 if(check_obstacle(15)) { // 安全距离15cm car_stop(); avoid_obstacle_routine(); // 执行避障子程序,例如转向 delay(100); return; // 本次循环结束,下次循环继续检查 } // 2. 执行主要任务(如循迹或跟随) if(mode == MODE_TRACK) { track_follow(150, 15.0); // 调用循迹函数 } else if(mode == MODE_FOLLOW) { follow_routine(); // 调用跟随函数 } delay(20); // 主循环延迟 }

3.3 跟随功能实现(以超声波距离跟随为例)

跟随功能可以理解为一种特殊的“循迹”,只不过跟踪的目标不是地面的黑线,而是前方物体的距离。我们使用另一个超声波模块(或复用避障模块,但需注意扫描频率)来测量与目标的距离。

核心逻辑

  1. 设定一个“理想跟随距离”(如 30cm)和一个“容忍范围”(如 ±5cm)。
  2. 持续测量实际距离。
  3. 如果实际距离大于理想距离+容忍上限,则小车前进。
  4. 如果实际距离小于理想距离-容忍下限,则小车后退。
  5. 如果距离在容忍范围内,则小车停止。
  6. 为了平滑运动,可以引入简单的 P 控制,根据距离偏差成比例地调整速度。
#define FOLLOW_TRIG_PIN 27 #define FOLLOW_ECHO_PIN 14 #define TARGET_DISTANCE_CM 30 #define DISTANCE_TOLERANCE 5 NewPing sonar_follow(FOLLOW_TRIG_PIN, FOLLOW_ECHO_PIN, MAX_DISTANCE); void follow_by_distance() { unsigned int dist = sonar_follow.ping_cm(); if(dist == 0) return; // 无效测量 int error = dist - TARGET_DISTANCE_CM; if(abs(error) < DISTANCE_TOLERANCE) { car_stop(); } else { // 比例控制,Kp_follow 需要调试 int speed = constrain(abs(error) * Kp_follow, 0, 200); if(error > 0) { // 距离太远,前进 car_forward(speed); } else { // 距离太近,后退 car_backward(speed); } } }

注意事项

  • 超声波模块有最小测距盲区(通常 2-5cm),目标太近会测不准。
  • 跟随运动可能会比较“生硬”(突然加速/减速),可以通过对误差进行滤波(如移动平均)或使用 PID 控制来改善平滑性。
  • 在开放空间进行跟随测试,确保有足够的安全距离让小车移动。

4. 功能整合、状态机与系统优化

当三个独立功能都能稳定工作后,最大的挑战来了:如何让它们协同工作而不冲突?你不能让小车一边循迹一边又因为检测到前方有人而执行跟随,也不能在避障转向时还死死抓着黑线不放。这就需要引入状态机的概念。

4.1 设计小车行为状态机

我们可以为小车定义几个主要状态:

  • STATE_IDLE:空闲/停止状态。
  • STATE_TRACKING:循迹状态。
  • STATE_AVOIDING:避障状态(高优先级,可中断其他状态)。
  • STATE_FOLLOWING:跟随状态。
  • STATE_LOST_LINE:脱线搜索状态。

状态转移规则示例:

  1. 默认启动进入STATE_IDLE
  2. 收到“开始循迹”指令(如通过蓝牙)进入STATE_TRACKING
  3. STATE_TRACKING中,若check_obstacle()返回真,则立即切换到STATE_AVOIDING,执行避障动作。避障完成后,可根据策略返回STATE_TRACKING或进入STATE_LOST_LINE寻线。
  4. 收到“开始跟随”指令进入STATE_FOLLOWING。在此状态下,避障依然有效。
  5. 在任何移动状态中,如果循迹传感器完全检测不到黑线超过一定时间,则进入STATE_LOST_LINE,执行原地旋转或沿边搜索直到重新检测到线。

代码框架如下:

enum CarState { STATE_IDLE, STATE_TRACKING, STATE_AVOIDING, STATE_FOLLOWING, STATE_LOST_LINE }; CarState current_state = STATE_IDLE; unsigned long lost_line_timer = 0; #define LOST_LINE_TIMEOUT 1000 // 脱线1秒后触发搜索 void loop() { // 全局安全监测:避障优先级最高 if(check_obstacle(10)) { // 更近的紧急距离 if(current_state != STATE_AVOIDING) { // 记录前一个状态,以便避障后恢复 previous_state = current_state; } current_state = STATE_AVOIDING; } // 根据当前状态执行相应任务 switch(current_state) { case STATE_IDLE: car_stop(); break; case STATE_TRACKING: track_follow(150, 15.0); // 检查是否脱线 if(get_track_error() == 999) { // 假设999表示脱线 if(lost_line_timer == 0) lost_line_timer = millis(); if(millis() - lost_line_timer > LOST_LINE_TIMEOUT) { current_state = STATE_LOST_LINE; } } else { lost_line_timer = 0; // 检测到线,清零计时器 } break; case STATE_AVOIDING: execute_avoidance(); // 执行一套避障动作,完成后自动切换状态 // 例如:后退 -> 左转90度 -> 前进一段 -> 切回 previous_state break; case STATE_FOLLOWING: follow_by_distance(); break; case STATE_LOST_LINE: search_for_line(); // 执行寻线策略 // 如果找到线,切换回 STATE_TRACKING break; } // 这里可以添加通过串口、蓝牙接收指令切换状态的代码 handle_control_command(); delay(20); }

4.2 资源优化与稳定性提升

当功能增多,loop()循环时间变长,可能会影响传感器读取的实时性。以下是一些优化点:

  1. 非阻塞式延时:避免使用delay()进行长时间等待,尤其是在避障或寻线搜索动作中。使用millis()进行时间管理。
    unsigned long previousMillis = 0; const long interval = 100; // 100ms执行一次 void loop() { unsigned long currentMillis = millis(); if (currentMillis - previousMillis >= interval) { previousMillis = currentMillis; // 执行需要定时运行的任务,如超声波测距 perform_sonar_measurement(); } // 其他非定时任务可以继续执行 // ... }
  2. 传感器数据滤波:超声波测距、红外传感器可能会受到噪声干扰。可以采用中值滤波移动平均滤波来平滑数据。
    // 简单的移动平均滤波示例(用于超声波距离) #define FILTER_SIZE 5 int distance_buffer[FILTER_SIZE] = {0}; int buffer_index = 0; int get_filtered_distance() { distance_buffer[buffer_index] = sonar_obstacle.ping_cm(); buffer_index = (buffer_index + 1) % FILTER_SIZE; long sum = 0; for(int i=0; i<FILTER_SIZE; i++) { sum += distance_buffer[i]; } return sum / FILTER_SIZE; }
  3. 电源管理:在电机启动、停止瞬间,电源电压可能会有较大波动。确保 ESP32 的供电电路有足够大的电容(如 100uF 以上)进行缓冲,或者使用独立的稳压模块。
  4. 调试信息输出:通过串口打印关键变量(如传感器值、计算出的误差、当前状态),是调试不可或缺的手段。但注意在最终版本中减少打印频率,以免占用过多 CPU 时间。

4.3 通过无线通信扩展控制

ESP32 内置 Wi-Fi 和蓝牙,这为小车增添了无限可能。你可以:

  • 蓝牙控制:开发一个简单的手机 APP(用 MIT App Inventor 或 Android Studio),通过蓝牙串口协议向 ESP32 发送指令,如切换模式(循迹/跟随/遥控)、调整速度、获取传感器数据等。
  • Wi-Fi 视频图传:如果你加装了摄像头模块(如 ESP32-CAM),可以搭建一个视频流服务器,在手机或电脑浏览器上实时查看小车视角,实现第一人称视角(FPV)控制或监控。
  • OTA 升级:通过 Wi-Fi 实现空中固件升级,无需每次都连接 USB 线。

添加蓝牙控制的基础代码片段:

#include "BluetoothSerial.h" BluetoothSerial SerialBT; void setup() { Serial.begin(115200); SerialBT.begin("ESP32_SmartCar"); // 蓝牙设备名称 } void loop() { // 处理蓝牙指令 if (SerialBT.available()) { char cmd = SerialBT.read(); switch(cmd) { case 'F': current_state = STATE_TRACKING; break; case 'G': current_state = STATE_FOLLOWING; break; case 'S': current_state = STATE_IDLE; break; // ... 其他指令 } } // ... 原有的主循环逻辑 }

5. 常见问题排查与进阶思路

即使按照步骤搭建,也难免会遇到问题。下面是一个从现象到原因的排查链路,你可以按顺序检查。

5.1 小车完全不动或动作异常

  1. 检查电源
    • 用万用表测量给 ESP32 供电的电压是否稳定在 3.3V/5V。
    • 电机启动时,观察 ESP32 是否重启(串口监视器看启动信息)。如果重启,说明电机电源干扰了控制电源,必须加强电源隔离或滤波。
  2. 检查电机驱动接线
    • 确认电机驱动模块的使能引脚(如STBY)已接高电平。
    • 确认 ESP32 的控制引脚是否与代码定义一致。
    • 手动给电机驱动模块的输入引脚高低电平,看电机是否转动,以排除驱动模块或电机本身故障。
  3. 检查代码
    • 确认motor_init()setup()中被调用。
    • loop()最开始添加一个简单的测试动作(如car_forward(100); delay(1000); car_stop(); delay(1000);),看是否能周期性运动。
    • 使用串口打印各个控制引脚的电平状态,确认输出是否符合预期。

5.2 循迹不稳定(冲出轨道或摇摆)

  1. 检查传感器
    • 将每个传感器单独对准白纸和黑线,观察其输出信号(通过串口打印或 LED 指示)是否变化明显、稳定。可能需要调整传感器距地面的高度。
    • 检查环境光是否太强,干扰了红外传感器。可以尝试遮挡环境光或使用带调制功能的红外传感器。
  2. 调整参数
    • Kp:这是主要调整对象。摆动大就减小Kp,反应慢、过弯困难就增大Kp
    • base_speed:速度越快越难控制,初期调参时请用较低速度。
    • 传感器权重:如果小车总是偏向一侧,可以微调权重值。
  3. 检查机械结构
    • 确保小车左右轮子直径一致,且与地面接触良好,没有打滑。
    • 确保传感器安装平整,距离地面高度一致。

5.3 避障功能误触发或不触发

  1. 超声波模块测试
    • 单独写一个测试程序,连续读取超声波距离并打印,观察其读数是否稳定、是否符合实际距离。
    • 检查模块的VccGNDTrigEcho连接是否正确牢固。
  2. 调整安全距离
    • 如果小车总是提前很远就刹车,说明安全距离设得太大。如果经常撞上,说明安全距离设得太小或测量周期太长。
    • 考虑超声波模块的检测角度,侧面或斜向的障碍物可能检测不到。
  3. 代码逻辑
    • 确认避障检查的优先级最高,且检查频率足够高(建议每 50-100ms 一次)。
    • 在避障动作execute_avoidance()中,确保有足够的转向角度和前进距离来真正绕过障碍,而不是原地转一下又撞上去。

5.4 跟随功能不灵敏或振荡

  1. 超声波跟随的局限性
    • 超声波只能测量距离,无法区分目标是墙还是人。在跟随人时,如果人突然转向侧面,小车可能会丢失目标。
    • 测距频率和响应速度要匹配。如果loop()太慢,小车动作就会滞后。
  2. 改善策略
    • 对距离值进行滤波(如前所述),减少噪声引起的误动。
    • 引入死区控制:在目标距离附近设置一个“死区”,在此区域内小车不动,只有超出死区才响应,可以减少小车的频繁启停振荡。
    • 尝试使用PD 控制:在比例控制的基础上加入微分项,可以预测变化趋势,使跟随动作更平滑。

5.5 功能切换混乱或卡死

  1. 状态机逻辑错误
    • 仔细检查每个状态切换的条件是否清晰、互斥。
    • 确保没有某个状态执行后无法跳出(比如STATE_LOST_LINE中如果永远找不到线,需要有超时处理并切换到安全状态如STATE_IDLE)。
  2. 资源冲突
    • 确保不同功能使用的定时器、中断、硬件资源(如某个 GPIO 既用于循迹又用于 LED 显示)没有冲突。
    • 如果使用了蓝牙或 Wi-Fi,注意它们的任务可能会占用较多 CPU 时间和内存,影响主循环速度。可以考虑使用 FreeRTOS 创建独立任务来管理无线通信。

进阶思路: 当基础功能稳定后,你可以尝试:

  • 多传感器融合避障:结合超声波和红外,提高障碍物检测的可靠性。
  • PID 控制器:在循迹和跟随中实现完整的 PID 控制,而不仅仅是 P 控制,使运动更平滑精准。
  • SLAM 与路径规划:这是更高级的领域。为小车配备激光雷达或深度摄像头,在未知环境中构建地图并自主规划路径,这通常需要更强的处理器(如树莓派)与 ESP32 协同工作。
  • 物联网集成:将小车传感器数据(温度、距离、图像)通过 Wi-Fi 上传到云平台(如阿里云、ThingsBoard),实现远程监控和数据分析。

这个项目的魅力在于,从简单的 GPIO 控制到复杂的多任务调度,从有线调试到无线控制,每一个环节都有可以深入挖掘和优化的地方。我建议的实践路径是:先确保单个功能在静态环境下稳定,再考虑动态环境下的干扰,最后实现多个功能的有机协同。调试过程中,串口打印是你的眼睛,耐心和逻辑是你的工具。

← 返回列表