在嵌入式开发领域,WiFi 技术的应用越来越广泛,而 ESP8266 正是一款强大且易于使用的 WiFi 模块。它不仅价格亲民,而且功能丰富,能够轻松实现设备联网、数据传输以及远程控制等功能。无论是物联网(IoT)项目、智能家居,还是创意小玩意儿,ESP8266 都能为你提供强大的支持。
本教程是我基于ESP8266实现的简单Web服务器,并通过浏览器与其交互。这个项目不仅展示了 ESP8266 的联网能力,还结合了 HTML、CSS 和 JavaScript 等前端技术,实现简易的嵌入式开发与Web开发的结合。
一、硬件需求
1.ESP-01S模块
ESP-01S 是一款性价比极高的 WiFi 模块,基于 ESP8266 芯片,内置 Tensilica L106 32 位微处理器,主频可达 80 MHz,甚至可超频至 160 MHz。它支持 802.11 b/g/n 协议,能够轻松连接到家庭或办公室的 WiFi 网络,提供稳定的无线通信能力。
ESP-01S 支持多种开发环境,包括 Arduino IDE、NodeMCU 和 MicroPython,开发者可以根据自己的喜好选择合适的工具。此外,ESP-01S 拥有强大的社区支持,网上有大量的开源项目、教程和解决方案,帮助开发者快速上手和解决问题。
ESP-01S 支持多种低功耗模式,包括 Modem-sleep 和 Deep-sleep,非常适合电池供电的物联网设备。通过合理配置低功耗模式,可以显著延长设备的续航时间,满足远程传感器、智能家居等场景的需求。
引脚图及接线如下:
脚序 | 名称 | 功能 |
1 | GND | 接地即可 |
2 | IO2 | GPIO2/UART1_TXD |
3 | IO0 | GPIO0,外部拉高即为下载模式;悬空或外部拉高即为运行模式 |
4 | RX | UART0_RXD/GPIO3 |
5 | TX | UART0_TXD/GPIO1 |
6 | EN | 芯片使能,高电平有效 |
7 | RST | 复位引脚 |
8 | 3V3 | 3.3V供电;外部供电电源输出电流建议在500mA以上 |
2.USB转TTL模块
将 USB 转 TTL 模块 (CH340C)与 ESP-01S 的对应引脚相连(注意:TX 接 RX,RX 接 TX,千万不要接反!供电电压为 3.3V,切勿接成 5V,否则可能损坏模块)。连接完成后,即可实现电脑与 ESP-01S 的通信,并完成代码的烧写。
二、软件需求
编辑与下载代码可以使用 Arduino IDE 完成。我使用的是 2.3.4 版本,只要是 1.8 版本以上 的 Arduino IDE 均可支持。
1.下载 Arduino IDE
访问 Arduino 官网:Software | Arduino。在下图指定位置选择适合你操作系统的版本(我使用的是 Windows 版本)。
2.配置 Arduino IDE
打开 Arduino IDE,点击 文件
-> 首选项
,在 附加开发板管理器网址
中添加以下链接:
http://arduino.esp8266.com/stable/package_esp8266com_index.json
点击 工具
-> 开发板
-> 开发板管理器
,搜索 esp8266
并安装最新版本。
点击 工具
-> 开发板
,选择 Generic ESP8266 Module
。
接下来配置工具栏内的内容,跟着下图我配置好的内容更改即可。
其中的port指的是端口,这跟据你的USB转TTL模块接的电脑哪个端口而定,具体可以打开电脑的设备管理器查找对应端口。我电脑上连的是COM3,如下图。
三、代码内容
1.引入库与宏定义
引入ESP8266 WiFi库,用于实现WiFi连接和网络通信;定义GPIO0引脚,用于烧录模式控制。
#include <ESP8266WiFi.h>
#define RELAY 0
2.WIFI配置
配置与你电脑现在连接的同一个WiFi,并创建一个TC 服务器,监听 80 端口(HTTP默认端口)。记得将代码中111改成WiFi名称,将222改成该WiFi的密码。
const char *ssid = "111";
const char *password = "222";
WiFiServer server(80);
3.setup( )函数
初始化串口通信后,配置 GPIO0 为输出模式并置高电平,接着连接 WiFi 并在连接过程中打印状态,连接成功后启动 TCP 服务器并打印本地 IP 地址。
void setup()
{
Serial.begin(115200);
pinMode(RELAY, OUTPUT); // Set GPIO0 to output mode.
digitalWrite(RELAY, HIGH); // Set the GPIO0 output level to high.
/* Connect to the WiFi network. */
Serial.println("");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { // If the connection to WiFi fails, retry every 500 milliseconds.
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
/* Start the Server. */
server.begin();
Serial.println("Server started.");
/* Output of the IP address. */
Serial.print("Use this URL to connect: ");
Serial.print("http://");
Serial.print(WiFi.localIP());
Serial.println("/");
}
4.loop( )函数
检查客户端连接,等待并读取其发送的 HTTP 请求首行,然后清空缓冲区以备后续通信。
void loop()
{
/* Check whether a user has established a connection. */
WiFiClient client = server.available();
if (!client)
return;
/* Wait until the user sends data. */
Serial.println("new client.");
while (!client.available())
delay(1);
/* Reading the first line of the request. */
String request = client.readStringUntil('\r');
Serial.println(request);
client.flush();
5.生成并返回 HTML 页面
返回HTTP成功响应,生成带15x15棋盘的 HTML 页面,定义样式并通过 onclick
实现落子功能。
/* Return the answer. */
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("");
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("<meta charset='UTF-8'>"); // 添加字符编码声明
client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
client.println("<head><title>ESP8266 五子棋游戏</title></head>");
client.println("<style>");
client.println("* { margin: 0; padding: 0; box-sizing: border-box; }");
client.println("body { font-family: 'Arial', sans-serif; background-color: #f0f0f0; color: #333; display: flex; justify-content: center; align-items: center; height: 100vh; }");
client.println(".container { text-align: center; }");
client.println(".board { display: grid; grid-template-columns: repeat(15, 30px); grid-template-rows: repeat(15, 30px); gap: 1px; margin: 20px auto; }");
client.println(".cell { width: 30px; height: 30px; background-color: #fff; border: 1px solid #ccc; display: flex; justify-content: center; align-items: center; cursor: pointer; }");
client.println(".cell.black { background-color: #000; }");
client.println(".cell.white { background-color: #fff; border: 1px solid #000; }");
client.println(".title { font-size: 2rem; margin-bottom: 20px; }");
client.println(".button { display: inline-block; padding: 10px 20px; font-size: 1rem; color: #fff; background-color: #333; text-decoration: none; border-radius: 5px; transition: all 0.3s ease; }");
client.println(".button:hover { background-color: #555; }");
client.println(".footer { margin-top: 30px; font-size: 1rem; }");
client.println(".footer .bold-text { font-weight: bold; }");
client.println(".message { font-size: 1.5rem; margin: 20px 0; color: red; }");
client.println("</style>");
client.println("<body>");
client.println("<div class='container'>");
client.println("<div class='title'>Eddie Wang五子棋</div>");
client.println("<div class='message' id='message'></div>");
client.println("<div class='board'>");
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
client.println("<div class='cell' id='cell_" + String(i) + "_" + String(j) + "' onclick='makeMove(" + String(i) + ", " + String(j) + ")'></div>");
}
}
client.println("</div>");
client.println("<a href=\"/RESET\" class='button'>重置游戏</a>");
client.println("<div class='footer'>");
client.println("<p>Created by <span class='bold-text'>Eddie Wang</span>.</p>");
client.println("</div>");
client.println("</div>");
client.println("</body>");
client.println("</html>");
client.println("");
}
6.JavaScript逻辑
处理玩家落子逻辑,切换玩家并检查是否形成五子连珠以判定胜利。
let currentPlayer = 'black';
let gameOver = false;
const boardSize = 15;
const board = Array.from({ length: boardSize }, () => Array(boardSize).fill(null));
function makeMove(row, col) {
if (gameOver || board[row][col]) return;
const cell = document.getElementById('cell_' + row + '_' + col);
cell.classList.add(currentPlayer);
board[row][col] = currentPlayer;
if (checkWin(row, col)) {
document.getElementById('message').innerText = `玩家 ${currentPlayer.toUpperCase()} 胜利!`;
gameOver = true;
return;
}
currentPlayer = currentPlayer === 'black' ? 'white' : 'black';
}
function checkWin(row, col) {
const directions = [
[1, 0], // 水平
[0, 1], // 垂直
[1, 1], // 对角线(右下)
[1, -1] // 对角线(左下)
];
for (const [dx, dy] of directions) {
let count = 1;
for (let i = 1; i <= 4; i++) {
const x = row + dx * i;
const y = col + dy * i;
if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] !== currentPlayer) break;
count++;
}
for (let i = 1; i <= 4; i++) {
const x = row - dx * i;
const y = col - dy * i;
if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] !== currentPlayer) break;
count++;
}
if (count >= 5) return true;
}
return false;
}
7.完整代码
#include <ESP8266WiFi.h>
#define RELAY 0
const char *ssid = "111";
const char *password = "222";
WiFiServer server(80);
void setup()
{
Serial.begin(115200);
pinMode(RELAY, OUTPUT); // Set GPIO0 to output mode.
digitalWrite(RELAY, HIGH); // Set the GPIO0 output level to high.
/* Connect to the WiFi network. */
Serial.println("");
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED) { // If the connection to WiFi fails, retry every 500 milliseconds.
delay(500);
Serial.print(".");
}
Serial.println("");
Serial.println("WiFi connected.");
/* Start the Server. */
server.begin();
Serial.println("Server started.");
/* Output of the IP address. */
Serial.print("Use this URL to connect: ");
Serial.print("http://");
Serial.print(WiFi.localIP());
Serial.println("/");
}
void loop()
{
/* Check whether a user has established a connection. */
WiFiClient client = server.available();
if (!client)
return;
/* Wait until the user sends data. */
Serial.println("new client.");
while (!client.available())
delay(1);
/* Reading the first line of the request. */
String request = client.readStringUntil('\r');
Serial.println(request);
client.flush();
/* Return the answer. */
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("");
client.println("<!DOCTYPE HTML>");
client.println("<html>");
client.println("<meta charset='UTF-8'>"); // 添加字符编码声明
client.println("<meta name='viewport' content='width=device-width, initial-scale=1'>");
client.println("<head><title>ESP8266 五子棋</title></head>");
client.println("<style>");
client.println("* { margin: 0; padding: 0; box-sizing: border-box; }");
client.println("body { font-family: 'Arial', sans-serif; background-color: #f0f0f0; color: #333; display: flex; justify-content: center; align-items: center; height: 100vh; }");
client.println(".container { text-align: center; }");
client.println(".board { display: grid; grid-template-columns: repeat(15, 30px); grid-template-rows: repeat(15, 30px); gap: 1px; margin: 20px auto; }");
client.println(".cell { width: 30px; height: 30px; background-color: #fff; border: 1px solid #ccc; display: flex; justify-content: center; align-items: center; cursor: pointer; }");
client.println(".cell.black { background-color: #000; }");
client.println(".cell.white { background-color: #fff; border: 1px solid #000; }");
client.println(".title { font-size: 2rem; margin-bottom: 20px; }");
client.println(".button { display: inline-block; padding: 10px 20px; font-size: 1rem; color: #fff; background-color: #333; text-decoration: none; border-radius: 5px; transition: all 0.3s ease; }");
client.println(".button:hover { background-color: #555; }");
client.println(".footer { margin-top: 30px; font-size: 1rem; }");
client.println(".footer .bold-text { font-weight: bold; }");
client.println(".message { font-size: 1.5rem; margin: 20px 0; color: red; }");
client.println("</style>");
client.println("<body>");
client.println("<div class='container'>");
client.println("<div class='title'>Eddie Wang五子棋</div>");
client.println("<div class='message' id='message'></div>");
client.println("<div class='board'>");
for (int i = 0; i < 15; i++) {
for (int j = 0; j < 15; j++) {
client.println("<div class='cell' id='cell_" + String(i) + "_" + String(j) + "' onclick='makeMove(" + String(i) + ", " + String(j) + ")'></div>");
}
}
client.println("</div>");
client.println("<a href=\"/RESET\" class='button'>重置游戏</a>");
client.println("<div class='footer'>");
client.println("<p>Created by <span class='bold-text'>Eddie Wang</span>.</p>");
client.println("</div>");
client.println("</div>");
client.println("<script>");
client.println("let currentPlayer = 'black';");
client.println("let gameOver = false;");
client.println("const boardSize = 15;");
client.println("const board = Array.from({ length: boardSize }, () => Array(boardSize).fill(null));");
client.println("function makeMove(row, col) {");
client.println(" if (gameOver || board[row][col]) return;");
client.println(" const cell = document.getElementById('cell_' + row + '_' + col);");
client.println(" cell.classList.add(currentPlayer);");
client.println(" board[row][col] = currentPlayer;");
client.println(" if (checkWin(row, col)) {");
client.println(" document.getElementById('message').innerText = `玩家 ${currentPlayer.toUpperCase()} 胜利!`;");
client.println(" gameOver = true;");
client.println(" return;");
client.println(" }");
client.println(" currentPlayer = currentPlayer === 'black' ? 'white' : 'black';");
client.println("}");
client.println("function checkWin(row, col) {");
client.println(" const directions = [");
client.println(" [1, 0], // 水平");
client.println(" [0, 1], // 垂直");
client.println(" [1, 1], // 对角线(右下)");
client.println(" [1, -1] // 对角线(左下)");
client.println(" ];");
client.println(" for (const [dx, dy] of directions) {");
client.println(" let count = 1;");
client.println(" for (let i = 1; i <= 4; i++) {");
client.println(" const x = row + dx * i;");
client.println(" const y = col + dy * i;");
client.println(" if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] !== currentPlayer) break;");
client.println(" count++;");
client.println(" }");
client.println(" for (let i = 1; i <= 4; i++) {");
client.println(" const x = row - dx * i;");
client.println(" const y = col - dy * i;");
client.println(" if (x < 0 || x >= boardSize || y < 0 || y >= boardSize || board[x][y] !== currentPlayer) break;");
client.println(" count++;");
client.println(" }");
client.println(" if (count >= 5) return true;");
client.println(" }");
client.println(" return false;");
client.println("}");
client.println("</script>");
client.println("</body>");
client.println("</html>");
client.println("");
}
四、硬件配置与烧录
1.引脚具体接线
ESP-01S引脚 | 接线去处 |
3V3 | 3.3V |
RST | 不接即可 |
EN | 3.3V |
TX | TTL上的RXD |
RX | TTL上的TXD |
IO0 | GND |
IO2 | 不接即可 |
GND | GND |
我的实物接线如下图。其中我3V3借助STM32F103系统板来供电。
2. 烧录
确保接入电脑,电脑端口识别到CH340了后,即可点击烧录。
烧录结束后的输出提示,代表烧录完成。
接下来,打开 Arduino IDE 的串口监视器(Serial Monitor),这一步至关重要!!!
确保所有引脚连接稳固,没有松动或断开。然后,将 ESP-01S 上连接 IO0
的杜邦线拔下,并快速插入 RST
引脚后立即拔出(即对 ESP-01S 进行复位操作)。此时,仔细观察串口监视器的输出,最后一行显示的网址即为 ESP-01S 的 IP 地址,通过该地址即可在浏览器中访问五子棋游戏页面。
下图为复位后串口监视器发出的消息,方框即为IP地址。
只要确保手机或电脑与ESP01S连接的是同一个WIFI就可通过浏览器访问该IP地址,进入五子棋页面。下图是电脑浏览器进入的界面。
还有手机进入的界面以及操作视频。 每次操作落子,串口监视器都会有消息接收。
Eddie66