零知派——ESP32 智能小车控制系统(AP模式配置+网页控制)
在物联网与嵌入式技术快速发展的背景下,智能小车作为 robotics 入门与实践的经典项目,涵盖了传感器应用、电机控制、自动控制算法及网络交互等多个技术领域
目录
项目概述
本文详细介绍一款基于 ESP32 的智能小车系统实现,该系统具备三大核心功能:
- 自动循迹:通过 5 路红外传感器识别路径,实现黑线 / 白线追踪
- 智能避障:利用超声波传感器检测障碍物,结合舵机扫描实现自主避障
- Web 远程控制:通过 WiFi 构建 Web 服务器,提供可视化控制界面与数据监控
项目难点及解决方案
问题描述:多任务并发与实时性
解决方案:主循环非阻塞设计、使用外部定时器中断测量速度、进行优先级划分
一、硬件准备与电路连接
1.1 核心硬件清单
| 组件 | 型号 / 规格 | 数量 |
|---|---|---|
| 主控芯片 | ESP32 开发板 | 1 |
| 电机驱动 | DRV8833 | 2 |
| 直流电机 | 5~12V 减速电机 | 4 |
| 循迹模块 | 5 路红外对管传感器 | 1 |
| 避障模块 | HC-SR04 超声波传感器 | 1 |
| 舵机 | SG90 180° 舵机 | 1 |
| 测速模块 | 对射式红外计数 + 20格码盘 | 1 |
| 电源 | 7.4V 锂电池 | 1 |
| 小车底盘 | 带安装孔的四轮底盘 | 1 |
1.2 接线方案表
以下所有引脚定义均直接来源于项目中的
config.h文件
| 模块 | 接口/功能 | ESP32 引脚 (GPIO) | 对应代码宏定义 | 备注 |
|---|---|---|---|---|
| 电机驱动 (左前轮) | A相 (PWM) | 25 | MOTO_LF_A |
连接至DRV8833的INA1 |
| B相 (PWM) | 26 | MOTO_LF_B |
连接至DRV8833的INB1 | |
| 电机驱动 (右前轮) | A相 (PWM) | 13 | MOTO_RF_A |
连接至DRV8833的INA2 |
| B相 (PWM) | 12 | MOTO_RF_B |
连接至DRV8833的INB2 | |
| 电机驱动 (左后轮) | A相 (PWM) | 32 | MOTO_LR_A |
连接至另一DRV8833的INA1 |
| B相 (PWM) | 33 | MOTO_LR_B |
连接至另一DRV8833的INB1 | |
| 电机驱动 (右后轮) | A相 (PWM) | 14 | MOTO_RR_A |
连接至另一DRV8833的INA2 |
| B相 (PWM) | 27 | MOTO_RR_B |
连接至另一DRV8833的INB2 | |
| 测速模块 | 信号输出 | 34 | PIN_SPEED_SENSOR |
连接至右后轮测速传感器 |
| 循迹模块 | 传感器1 (最左) | 17 | PIN_TRACE_1 |
数字输入 |
| 传感器2 | 16 | PIN_TRACE_2 |
数字输入 | |
| 传感器3 (中间) | 4 | PIN_TRACE_3 |
数字输入 | |
| 传感器4 | 2 | PIN_TRACE_4 |
数字输入 | |
| 传感器5 (最右) | 15 | PIN_TRACE_5 |
数字输入 | |
| 超声波模块 | Trig (触发) | 23 | SR04_TRIG |
输出 |
| Echo (回响) | 22 | SR04_ECHO |
输入 | |
| 舵机 | 信号线 | 21 | SERVO_PIN |
PWM控制 |
| 风扇 (可选) | 控制引脚 | 5 | FAN_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。手机/电脑连接该热点,输入密码12345678
AP模式下请关闭移动数据网络,部分设备会自动切换回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高,电流反向,电机反转

正转:AIN1=PWM,AIN2=LOW、反转:AIN1=LOW,AIN2=PWM
5.2 速度控制
在一个周期内快速切换开关的导通时间(即PWM波),可以改变电机两端的平均电压,从而调节转速
PWM的占空比越高,平均电压越大,电机转得越快
H桥电路逻辑

六、常见问题解答(FAQ)
Q1: 小车在循迹模式下总是冲出赛道,应该如何调整?
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)
更多推荐

所有评论(0)