在物联网与嵌入式技术快速发展的背景下智能小车作为 robotics 入门与实践的经典项目涵盖了传感器应用、电机控制、自动控制算法及网络交互等多个技术领域目录一、硬件准备与电路连接1.1 核心硬件清单1.2 接线方案表1.3 具体接线图二、安装与使用部分三、系统代码讲解3.1 系统主架构3.2 电机驱动与速度测量3.3 循迹控制3.4 避障控制3.5 Web服务器与控制界面四、项目结果演示4.1 操作流程4.2 视频演示五、DRV8833 电机驱动原理5.1 工作原理5.2 速度控制六、常见问题解答FAQ项目概述本文详细介绍一款基于 ESP32 的智能小车系统实现该系统具备三大核心功能自动循迹通过 5 路红外传感器识别路径实现黑线 / 白线追踪智能避障利用超声波传感器检测障碍物结合舵机扫描实现自主避障Web 远程控制通过 WiFi 构建 Web 服务器提供可视化控制界面与数据监控项目难点及解决方案问题描述多任务并发与实时性解决方案主循环非阻塞设计、使用外部定时器中断测量速度、进行优先级划分一、硬件准备与电路连接1.1 核心硬件清单组件型号 / 规格数量主控芯片ESP32 开发板1电机驱动DRV88332直流电机5~12V 减速电机4循迹模块5 路红外对管传感器1避障模块HC-SR04 超声波传感器1舵机SG90 180° 舵机1测速模块对射式红外计数 20格码盘1电源7.4V 锂电池1小车底盘带安装孔的四轮底盘11.2 接线方案表以下所有引脚定义均直接来源于项目中的config.h文件模块接口/功能ESP32 引脚 (GPIO)对应代码宏定义备注电机驱动 (左前轮)A相 (PWM)25MOTO_LF_A连接至DRV8833的INA1B相 (PWM)26MOTO_LF_B连接至DRV8833的INB1电机驱动 (右前轮)A相 (PWM)13MOTO_RF_A连接至DRV8833的INA2B相 (PWM)12MOTO_RF_B连接至DRV8833的INB2电机驱动 (左后轮)A相 (PWM)32MOTO_LR_A连接至另一DRV8833的INA1B相 (PWM)33MOTO_LR_B连接至另一DRV8833的INB1电机驱动 (右后轮)A相 (PWM)14MOTO_RR_A连接至另一DRV8833的INA2B相 (PWM)27MOTO_RR_B连接至另一DRV8833的INB2测速模块信号输出34PIN_SPEED_SENSOR连接至右后轮测速传感器循迹模块传感器1 (最左)17PIN_TRACE_1数字输入传感器216PIN_TRACE_2数字输入传感器3 (中间)4PIN_TRACE_3数字输入传感器42PIN_TRACE_4数字输入传感器5 (最右)15PIN_TRACE_5数字输入超声波模块Trig (触发)23SR04_TRIG输出Echo (回响)22SR04_ECHO输入舵机信号线21SERVO_PINPWM控制风扇 (可选)控制引脚5FAN_PIN高低电平控制1.3 具体接线图确保电池负极、ESP32的GND、两个DRV8833模块的GND、以及所有传感器模块的GND连接在一起保证所有信号共地在扩展板上直插零知 ESP32主控板图中的⑥注意USB接口朝外及电机驱动模块图中的⑦注意方向。接线所示①电池接口、②循迹模块接口、③超声波模块接口、④测速模块接口、⑤电机接口舵机模块接线为小车扩展板上丝印标有舵机接口的P4引脚上黄色接线对准PWM方向模块所示⑧循迹模块、⑨超声波-舵机模块请注意电机接线顺序如图所示⑤A电机引脚对应A接口其他电机引脚一一对应锂电池接线到小车扩展板时正负极不可反接扩展板上标的丝印接红线-接黑线反接会烧毁扩展板和 ESP32二、安装与使用部分2.1 开源平台-输入智能小车控制系统并搜索-代码下载自动打开2.2 连接-验证-上传2.3 调试-串口监视器三、系统代码讲解深入剖析电机驱动SpeedDrive、避障控制AvoidControl、循迹控制TraceControl、Web服务器WebHandler核心代码文件理解它们是如何协同工作3.1 系统主架构整个系统的入口和调度中心负责所有模块的初始化、AP 模式配置、任务优先级调度、多模式互斥执行是连接各个子模块的桥梁// 关键代码片段setup() 和 loop() void setup() { // ... 初始化串口 CarDrive.begin(); // 初始化电机与测速 Tracer.begin(); // 初始化循迹传感器 Avoider.begin(); // 初始化超声波与舵机 WebSys.begin(AP_SSID, AP_PASSWORD); // 启动AP模式和Web服务器 } void loop() { WebSys.loop(); // 高优先级处理Web请求保证界面响应 CarDrive.loop(); // 后台任务每100ms计算一次速度 // 定时执行模式控制逻辑 if (millis() - lastModeCheck MODE_CHECK_INTERVAL) { SysMode currentMode WebSys.getMode(); // 互斥地执行当前模式 switch (currentMode) { case MODE_TRACE: Avoider.stop(); // 确保避障模式关闭 Tracer.run(); // 运行循迹 break; case MODE_AVOID: Tracer.stop(); // 确保循迹模式关闭 Avoider.run(); // 运行避障 break; case MODE_MANUAL: Tracer.stop(); // 停止所有自动模式 Avoider.stop(); break; } lastModeCheck now; } delay(1); // 微小延时喂狗 }通过millis()和状态机思想实现了多任务的准并行处理switch语句中进入一种自动模式前会先停止另一种这是解决模式冲突的关键确保了系统的行为确定性3.2 电机驱动与速度测量基于测速码盘 外部中断实现脉冲计数通过滤波算法计算出小车的实时移动速度// 关键代码片段中断服务程序 和 速度计算 void IRAM_ATTR SpeedDrive::isrHandler(void* arg) { SpeedDrive* obj (SpeedDrive*)arg; unsigned long now micros(); if (now - obj-lastIsrTime 1000) { // 1ms 硬件去抖 obj-pulseCount; obj-lastIsrTime now; } } void SpeedDrive::loop() { if (now - lastCalcTime SPEED_CALC_INTERVAL) { // 100ms周期 // ... 读取脉冲计数并清零 ... if (raw MIN_PULSE_FOR_SPEED) { // EMA滤波: 新滤波值 α * 新脉冲 (1-α) * 旧滤波值 filteredPulse (EMA_ALPHA * raw) ((1.0 - EMA_ALPHA) * filteredPulse); // 计算速度: 滤波脉冲 * (每秒周期数) * 每脉冲距离 float newSpeed (filteredPulse * (1000.0 / SPEED_CALC_INTERVAL)) * CM_PER_PULSE; // 二次平滑用于显示 smoothedSpeed (0.7 * newSpeed) (0.3 * smoothedSpeed); } else { /* 脉冲过少速度衰减 */} } }isrHandler 是中断服务程序IRAM_ATTR 将其放入RAM中加速执行1ms的去抖动逻辑有效防止了机械抖动3.3 循迹控制采用传感器编码将5个传感器的0/1状态组合看作一个“二进制数”每种组合代表小车相对于黑线的一个特定姿态// 关键代码片段循迹动作决策 void TraceControl::executeAction(const SensorData sensors) { int activeCount sensors.s1 sensors.s2 sensors.s3 sensors.s4 sensors.s5; if (activeCount 4) { // 十字路口或粗线 CarDrive.run(TRACE_SPEED_SLOW, TRACE_SPEED_SLOW); // 减速直行 } else if (activeCount 0) { // 完全丢线 CarDrive.stop(); // 停车等待 } else if (!sensors.s1 !sensors.s2 sensors.s3 !sensors.s4 !sensors.s5) { CarDrive.run(TRACE_SPEED_FAST, TRACE_SPEED_FAST); // 完美居中快速直行 } else if (!sensors.s1 sensors.s2 sensors.s3 !sensors.s4 !sensors.s5) { CarDrive.run(TRACE_SPEED_MEDIUM, TRACE_SPEED_FAST); // 轻微左偏左慢右快 } else if (sensors.s1) { // 严重左偏 CarDrive.run(-TRACE_SPEED_REVERSE, TRACE_SPEED_TURN); // 左轮反转右轮正转原地修正 } // ... 其他更多状态判断 }循迹的核心是差速转向采用查表式逻辑轻微偏航减慢偏离侧轮子速度加速另一侧严重偏航执行更激进的修正一正一反原地转向3.4 避障控制采用了连续扫描 状态机 惯性决策的架构使避障动作更加流畅、自然1连续扫描机制永不阻塞的环境感知// 舵机扫描角度序列右(30°)→中(90°)→左(150°)→中(90°)→循环 const int AvoidControl::SWEEP_ANGLES[4] {30, 90, 150, 90}; void AvoidControl::updateSweep() { unsigned long now millis(); if (now - lastSweepTime AVOID_SWEEP_STEP_MS) return; // 时间未到不扫描 servoWrite(SWEEP_ANGLES[sweepStep]); // 转到目标角度内含动态延时 int d getDistance(); // 测距舵机已到位 // 将距离存入对应的方向变量 int angle SWEEP_ANGLES[sweepStep]; if (angle 45) distRight d; else if (angle 135) distLeft d; else distCenter d; Serial.printf([扫描] 角度%3d° 左%3dcm 中%3dcm 右%3dcm\n, angle, distLeft, distCenter, distRight); sweepStep (sweepStep 1) % 4; // 步进到下一个角度 lastSweepTime now; }AVOID_SWEEP_STEP_MS控制扫描节奏updateSweep() 在每次 run() 中被调用但只有间隔足够时才真正执行舵机转动和测距其余时间立即返回确保主循环流畅2决策算法惯性权重防止摇摆AvoidControl::AvoidState AvoidControl::decideTurnState() { // ... 先判断三方皆堵返回 STATE_BACKING ... // 惯性加成上次方向加10cm权重防止来回摇摆 int scoreLeft distLeft (lastTurnDir -1 ? 10 : 0); int scoreRight distRight (lastTurnDir 1 ? 10 : 0); if (scoreLeft scoreRight) { lastTurnLeft true; lastTurnDir -1; // 记录本次转向方向 } else { lastTurnLeft false; lastTurnDir 1; } return STATE_TURNING; }当左右两侧距离相近时小车容易频繁摇摆。通过给上一次选择的方向额外增加10cm的虚拟“偏好分”使小车倾向于继续沿原方向转弯从而走出平滑弧线3前进状态的分级响应void AvoidControl::handleForward() { int dist distCenter; // 直接使用连续扫描的最新中央距离 if (dist AVOID_DISTANCE_EMERGENCY) { // 20cm以内紧急停车并决策 CarDrive.stop(); currentState decideTurnState(); stateStartTime millis(); } else if (dist AVOID_DISTANCE_SLOW) { // 20~35cm减速区停车决策 CarDrive.stop(); currentState decideTurnState(); stateStartTime millis(); } else if (dist AVOID_DISTANCE_SAFE) { // 35~55cm预警区慢速行驶并微调方向 if (distLeft distRight 20) // 左侧空间大轻微左偏 CarDrive.run(AVOID_SPEED_SLOW - 20, AVOID_SPEED_SLOW); else if (distRight distLeft 20) // 右侧空间大轻微右偏 CarDrive.run(AVOID_SPEED_SLOW, AVOID_SPEED_SLOW - 20); else CarDrive.run(AVOID_SPEED_SLOW, AVOID_SPEED_SLOW); } else { // 55cm安全距离全速前进 CarDrive.run(AVOID_SPEED_NORMAL, AVOID_SPEED_NORMAL); } }侧方距离明显不均衡时通过差速实现柔和的方向修正避免到跟前才急转弯保证小车在进入障碍密集区时先停下来思考而不是盲目冲入各状态之间的转换关系如下图所示3.5 Web服务器与控制界面前后端分离WebHandler 扮演了后端服务器的角色、前端网页通过 fetch API定期向ESP32发起AJAX请求获取最新的速度、风扇状态等JSON数据// 关键代码片段处理前端AJAX请求 void WebHandler::handleData() { float speed CarDrive.getSmoothedSpeed(); // 获取平滑后的速度 updateSpeedHistory(speed); // 更新历史数据用于图表 // 构建JSON响应 String json {; json \speed\: String(speed, 2); json ,\fan\: String(fanState ? true : false); json ,\validSpeeds\:[; // 添加最近5次有效速度 for (int i 0; i VALID_SPEED_COUNT; i) { if (i 0) json ,; json String(validSpeeds[i], 2); } json ]; if (logBuffer.length() 0) { // 添加日志 json ,\log\:\ logBuffer \; logBuffer ; } json }; server.send(200, application/json, json); }前端点击“导出CSV”时会将累积在隐藏textarea中的时间-速度数据保存为.csv文件实现了简易的数据记录功能系统流程图展示了整个系统的软件运行流程和模块间的关系四、项目结果演示4.1 操作流程连接电池后小车扩展板电源灯亮起ESP32开始自建热点ESP32_SmartCar。手机/电脑连接该热点输入密码12345678AP模式下请关闭移动数据网络部分设备会自动切换回4G导致无法访问控制台1访问控制台打开浏览器在地址栏输入192.168.4.1进入智能小车控制台界面2功能测试点击“智能避障”按钮观察小车行为①连续扫描舵机以约2.5秒一个循环4步×400ms持续左右摆动但不会停顿小车依然在前进。②平稳转向接近障碍时小车根据左右空间惯性选择方向流畅转弯无“犹豫不决”现象。③死胡同脱困将小车困在角落它会先后退约0.9秒然后原地掉头朝空旷方向驶出打开浏览器控制台F12 - 网络可以查看/data接口每500ms返回的JSON数据其中包含实时距离和日志3数据导出避障过程中点击“导出CSV”可获得包含时间戳、速度、风扇状态的完整日志便于分析算法表现4.2 视频演示零知ESP32 AP模式智能小车实战演示网页控制、实时测速、自动循迹与避障本视频完整展示了基于零知ESP32的智能小车在AP模式下的操作流程。演示如何连接小车自建热点、通过浏览器访问控制台。三种核心模式手动驾驶的即时响应、自动循迹的精准过弯以及智能避障模式下的自主环境探测与路径决策。实时刷新的速度曲线和数据导出功能也清晰可见五、DRV8833 电机驱动原理DRV8833内部集成了双路H桥驱动器。一个H桥由四个开关MOSFET组成通过控制它们的导通组合可以改变电机两端的电压方向和大小从而控制电机的正转、反转、刹车和滑行5.1 工作原理如原理图所示当INA为高电平PWM、INB为低电平时电流从左向右流过电机正转。反之INA低、INB高电流反向电机反转正转AIN1PWMAIN2LOW、反转AIN1LOWAIN2PWM5.2 速度控制在一个周期内快速切换开关的导通时间即PWM波可以改变电机两端的平均电压从而调节转速PWM的占空比越高平均电压越大电机转得越快H桥电路逻辑六、常见问题解答FAQQ1: 小车在循迹模式下总是冲出赛道应该如何调整A这通常是转向速度过快或响应不够及时。可以尝试降低 config.h 中 TRACE_SPEED_FAST 和 TRACE_SPEED_MEDIUM 的值。检查循迹模块的高度和灵敏度确保它能在黑白线上正确输出高低电平Q2: 避障模式下小车总是撞到障碍物才停下怎么办A首先检查超声波传感器的安装是否牢固、朝向是否正前方。可以在代码中临时添加串口打印输出 getDistance() 的值判断测距是否准确。如果测距准确说明避障的阈值过小可以适当增大 AVOID_DISTANCE_EMERGENCY 和 AVOID_DISTANCE_SLOW 的值Q3: 测速显示始终为0或者数值严重不准确A这通常是硬件或接线问题。请检查测速传感器的VCC和GND是否正确连接、测速传感器的信号线是否连接到正确的GPIO、码盘是否能顺畅地通过传感器的U型槽且遮挡效果明显、在代码中临时添加中断计数打印看是否有脉冲产生。如果没有可能是传感器损坏或接线错误项目资源整合DRV8833 数据手册 DRV8833 Dual H-Bridge Motor Driver datasheet (Rev. E)