3D打印ESP8266智能小车:Wi-Fi网页遥控 + L298N驱动

本项目制作了一款由3D打印底盘、ESP8266主控及L298N电机驱动模块组成的无线智能小车。系统采用AP热点模式,用户连接ESP8266后,即可通过网页实时控制小车的运动轨迹。
1
制作底盘以及卡扣




12
小车底盘和固定马达的图纸放在了附件,自己查看并下载。
马达卡扣.stl
76.06KB
底盘-v1.stl
4.55MB
2
组装马达







12345
我们需要把4个TT马达装在底盘下面。每个马达要用2个卡扣穿过底盘来固定位置,最后再用M2乘30的长螺丝拧紧。
3
马达连接L298N







12345
线束由底盘侧边引至上表面。以单侧为例进行接线说明:请将正极与负极导线分别接入对应的OUT接口端子。根据最后一图的接线即可。
4
烧录控制代码

#define ENA 14
#define ENB 12
#define IN_1 15
#define IN_2 13
#define IN_3 2
#define IN_4 0
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
int speedCar = 800;
int speed_Coeff = 3;
const char* ssid = "SmartCar";
ESP8266WebServer server(80);
const char* indexHtml = R"rawliteral(
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no, viewport-fit=cover">
<title>遥控器</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
body {
background: #0f172a;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 20px;
}
.remote {
max-width: 360px;
width: 100%;
background: #1e293b;
border-radius: 48px;
padding: 30px 20px;
box-shadow: 0 20px 40px rgba(0,0,0,0.3);
border: 1px solid #334155;
}
.ip {
text-align: center;
color: #3b82f6;
font-size: 14px;
font-family: monospace;
margin-bottom: 30px;
letter-spacing: 1px;
}
/* 9键方向盘 */
.dpad-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12px;
}
.dpad-btn {
aspect-ratio: 1;
background: #334155;
border: none;
border-radius: 20px;
font-size: 28px;
font-weight: bold;
cursor: pointer;
color: #f1f5f9;
transition: all 0.05s linear;
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
gap: 4px;
}
.dpad-btn .small {
font-size: 11px;
font-weight: normal;
opacity: 0.8;
}
.dpad-btn:active {
transform: scale(0.92);
background: #3b82f6;
}
.stop-btn {
background: #ef4444;
}
.stop-btn:active {
background: #dc2626;
}
/* 底部停止按钮 */
.bottom-stop {
margin-top: 24px;
}
.big-stop {
width: 100%;
background: #ef4444;
border: none;
padding: 16px;
border-radius: 40px;
color: white;
font-size: 18px;
font-weight: bold;
cursor: pointer;
transition: all 0.05s linear;
}
.big-stop:active {
transform: scale(0.97);
background: #dc2626;
}
.status {
text-align: center;
color: #94a3b8;
font-size: 12px;
margin-top: 20px;
}
.status-dot {
display: inline-block;
width: 8px;
height: 8px;
background: #22c55e;
border-radius: 50%;
margin-right: 6px;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.5; }
}
</style>
</head>
<body>
<div class="remote">
<div class="ip" id="ipDisplay">192.168.4.1</div>
<!-- 9键方向盘 -->
<div class="dpad-grid">
<!-- 第一行 -->
<button class="dpad-btn" data-cmd="G" data-continue="true">↖<span class="small">左前</span></button>
<button class="dpad-btn" data-cmd="F" data-continue="true">▲<span class="small">前进</span></button>
<button class="dpad-btn" data-cmd="I" data-continue="true">↗<span class="small">右前</span></button>
<!-- 第二行 -->
<button class="dpad-btn" data-cmd="L" data-continue="true">◀<span class="small">左转</span></button>
<button class="dpad-btn stop-btn" data-cmd="S">●<span class="small">停止</span></button>
<button class="dpad-btn" data-cmd="R" data-continue="true">▶<span class="small">右转</span></button>
<!-- 第三行 -->
<button class="dpad-btn" data-cmd="H" data-continue="true">↙<span class="small">左后</span></button>
<button class="dpad-btn" data-cmd="B" data-continue="true">▼<span class="small">后退</span></button>
<button class="dpad-btn" data-cmd="J" data-continue="true">↘<span class="small">右后</span></button>
</div>
<!-- 底部停止按钮 -->
<div class="bottom-stop">
<button class="big-stop" id="stopBtn">⏹️ 紧急停止</button>
</div>
<div class="status">
<span class="status-dot"></span>
<span id="statusText">待机</span>
</div>
</div>
<script>
let carIP = window.location.hostname;
if (carIP === 'localhost' || carIP === '127.0.0.1' || carIP === '') {
carIP = '192.168.4.1';
}
document.getElementById('ipDisplay').innerText = carIP;
let lastCmd = '';
let activeTimers = {};
function sendCommand(cmd) {
if (lastCmd === cmd && cmd !== 'S') return;
fetch(`http://${carIP}/?State=${cmd}`, { mode: 'no-cors' }).catch(e => {});
lastCmd = cmd;
const names = {
'F': '前进', 'B': '后退', 'L': '左转', 'R': '右转',
'G': '左前', 'I': '右前', 'H': '左后', 'J': '右后', 'S': '待机'
};
document.getElementById('statusText').innerText = names[cmd] || '运行';
}
function startContinue(cmd) {
if (activeTimers[cmd]) return;
sendCommand(cmd);
activeTimers[cmd] = setInterval(() => sendCommand(cmd), 100);
}
function stopContinue(cmd) {
if (activeTimers[cmd]) {
clearInterval(activeTimers[cmd]);
delete activeTimers[cmd];
}
if (cmd !== 'S') sendCommand('S');
}
function bindButton(btn, cmd, isContinue) {
btn.addEventListener('mousedown', (e) => {
e.preventDefault();
if (isContinue) startContinue(cmd);
else sendCommand(cmd);
});
btn.addEventListener('mouseup', () => { if (isContinue) stopContinue(cmd); });
btn.addEventListener('mouseleave', () => { if (isContinue) stopContinue(cmd); });
btn.addEventListener('touchstart', (e) => {
e.preventDefault();
if (isContinue) startContinue(cmd);
else sendCommand(cmd);
});
btn.addEventListener('touchend', () => { if (isContinue) stopContinue(cmd); });
btn.addEventListener('touchcancel', () => { if (isContinue) stopContinue(cmd); });
}
document.querySelectorAll('[data-cmd]').forEach(btn => {
let cmd = btn.getAttribute('data-cmd');
let isContinue = btn.hasAttribute('data-continue');
bindButton(btn, cmd, isContinue);
});
document.getElementById('stopBtn').addEventListener('click', () => {
Object.keys(activeTimers).forEach(cmd => {
if (activeTimers[cmd]) clearInterval(activeTimers[cmd]);
});
activeTimers = {};
sendCommand('S');
});
// 键盘控制
let currentKeyCmd = null;
let keyTimer = null;
document.addEventListener('keydown', (e) => {
const key = e.key.toLowerCase();
const map = {
'arrowup': 'F', 'arrowdown': 'B', 'arrowleft': 'L', 'arrowright': 'R',
'w': 'F', 's': 'B', 'a': 'L', 'd': 'R',
'q': 'G', 'e': 'I', 'z': 'H', 'c': 'J'
};
if (map[key]) {
e.preventDefault();
if (currentKeyCmd === map[key]) return;
if (currentKeyCmd && keyTimer) clearInterval(keyTimer);
currentKeyCmd = map[key];
sendCommand(currentKeyCmd);
keyTimer = setInterval(() => sendCommand(currentKeyCmd), 100);
}
if (key === ' ') { e.preventDefault(); sendCommand('S'); }
});
document.addEventListener('keyup', (e) => {
const key = e.key.toLowerCase();
const mapKeys = ['arrowup','arrowdown','arrowleft','arrowright','w','s','a','d','q','e','z','c'];
if (mapKeys.includes(key)) {
if (keyTimer) clearInterval(keyTimer);
keyTimer = null;
sendCommand('S');
currentKeyCmd = null;
}
});
setTimeout(() => sendCommand('S'), 100);
</script>
</body>
</html>
)rawliteral";
// ==================== 电机控制 ====================
void goAhead() {
digitalWrite(IN_1, LOW); digitalWrite(IN_2, HIGH); analogWrite(ENA, speedCar);
digitalWrite(IN_3, LOW); digitalWrite(IN_4, HIGH); analogWrite(ENB, speedCar);
}
void goBack() {
digitalWrite(IN_1, HIGH); digitalWrite(IN_2, LOW); analogWrite(ENA, speedCar);
digitalWrite(IN_3, HIGH); digitalWrite(IN_4, LOW); analogWrite(ENB, speedCar);
}
void goRight() {
digitalWrite(IN_1, HIGH); digitalWrite(IN_2, LOW); analogWrite(ENA, speedCar);
digitalWrite(IN_3, LOW); digitalWrite(IN_4, HIGH); analogWrite(ENB, speedCar);
}
void goLeft() {
digitalWrite(IN_1, LOW); digitalWrite(IN_2, HIGH); analogWrite(ENA, speedCar);
digitalWrite(IN_3, HIGH); digitalWrite(IN_4, LOW); analogWrite(ENB, speedCar);
}
void goAheadRight() {
digitalWrite(IN_1, LOW); digitalWrite(IN_2, HIGH); analogWrite(ENA, speedCar / speed_Coeff);
digitalWrite(IN_3, LOW); digitalWrite(IN_4, HIGH); analogWrite(ENB, speedCar);
}
void goAheadLeft() {
digitalWrite(IN_1, LOW); digitalWrite(IN_2, HIGH); analogWrite(ENA, speedCar);
digitalWrite(IN_3, LOW); digitalWrite(IN_4, HIGH); analogWrite(ENB, speedCar / speed_Coeff);
}
void goBackRight() {
digitalWrite(IN_1, HIGH); digitalWrite(IN_2, LOW); analogWrite(ENA, speedCar / speed_Coeff);
digitalWrite(IN_3, HIGH); digitalWrite(IN_4, LOW); analogWrite(ENB, speedCar);
}
void goBackLeft() {
digitalWrite(IN_1, HIGH); digitalWrite(IN_2, LOW); analogWrite(ENA, speedCar);
digitalWrite(IN_3, HIGH); digitalWrite(IN_4, LOW); analogWrite(ENB, speedCar / speed_Coeff);
}
void stopRobot() {
digitalWrite(IN_1, LOW); digitalWrite(IN_2, LOW); analogWrite(ENA, 0);
digitalWrite(IN_3, LOW); digitalWrite(IN_4, LOW); analogWrite(ENB, 0);
}
void setSpeed(int speed) {
if (speed >= 400 && speed <= 1023) speedCar = speed;
}
void handleRoot() {
if (server.hasArg("State")) {
String cmd = server.arg("State");
if (cmd == "0") setSpeed(400);
else if (cmd == "1") setSpeed(470);
else if (cmd == "2") setSpeed(540);
else if (cmd == "3") setSpeed(610);
else if (cmd == "4") setSpeed(680);
else if (cmd == "5") setSpeed(750);
else if (cmd == "6") setSpeed(820);
else if (cmd == "7") setSpeed(890);
else if (cmd == "8") setSpeed(960);
else if (cmd == "9") setSpeed(1023);
else if (cmd == "F") goAhead();
else if (cmd == "B") goBack();
else if (cmd == "L") goLeft();
else if (cmd == "R") goRight();
else if (cmd == "I") goAheadRight();
else if (cmd == "G") goAheadLeft();
else if (cmd == "J") goBackRight();
else if (cmd == "H") goBackLeft();
else if (cmd == "S") stopRobot();
}
server.send(200, "text/html", indexHtml);
}
void setup() {
pinMode(ENA, OUTPUT); pinMode(ENB, OUTPUT);
pinMode(IN_1, OUTPUT); pinMode(IN_2, OUTPUT);
pinMode(IN_3, OUTPUT); pinMode(IN_4, OUTPUT);
stopRobot();
Serial.begin(115200);
WiFi.mode(WIFI_AP);
WiFi.softAP(ssid);
server.on("/", handleRoot);
server.begin();
}
void loop() {
server.handleClient();
}
5
ESP8266连接L298N

务必先完成代码烧录,再连接L298N驱动模块!
若插线烧录,可能导致串口通信干扰,造成代码烧录失败。
接线说明
代码烧录完成后,以下是ESP8266与L298N对应关系:
L298N | ESP8266 | 功能说明 |
ENA | D5 | 电机A PWM调速 |
IN1 | D7 | 电机A 方向控制 |
IN2 | D8 | 电机A 方向控制 |
IN3 | D3 | 电机B 方向控制1 |
IN4 | D4 | 电机B 方向控制2 |
ENB | D6 | 电机B PWM调速 |
6
网页控制小车




12
设备重启: 给ESP8266供电后,按下 RST 按键复位模块。
连接热点: 打开手机Wi-Fi列表,搜索并连接名为 SmartCar 的热点。
访问控制台: 连接成功后,打开手机浏览器,在地址栏输入 192.168.4.1 并访问。
操控小车: 进入UI控制页面后,即可通过屏幕上的方向键控制小车运动。
0
0
0
qq空间
微博
复制链接
分享 更多相关项目
猜你喜欢
评论/提问(已发布 0 条)
0