Appearance
Arduino UNO R4 WiFi 热点直连控制开发项目指南
1. 项目简介与硬件需求
项目简介
本项目演示如何利用 Arduino UNO R4 WiFi 开发板作为无线接入点(AP 模式),建立独立的局域网。手机或电脑 APP 无需连接外部路由器,即可直接连接该热点,并通过 HTTP 请求实现对板载 LED 等外设的远程控制。该方案适用于无网络环境下的设备调试、便携式控制及快速原型验证。
硬件需求
- 主控板:Arduino UNO R4 WiFi(基于 Renesas RA4M1 微控制器与 ESP32-S3 协处理器)。
- 控制终端:智能手机、平板或电脑(用于连接热点并发送控制指令)。
- 外设(可选):板载 LED 即可满足演示需求,也可外接继电器或电机进行扩展。
- 数据线:USB-C 数据线(用于供电与程序烧录)。
2. 电路连接说明
本示例主要依赖 UNO R4 WiFi 的板载资源,无需复杂的外部接线:
- 板载 LED:UNO R4 WiFi 的板载 LED 默认连接在数字引脚
D13。 - 供电方式:通过 USB-C 接口连接电脑或 5V 电源适配器即可。
- 外部负载(扩展):若需控制外部设备,可将负载正极接
D13(或其他自定义引脚),负极接GND。注意 UNO R4 WiFi 的 GPIO 最大输出电流有限,驱动大功率设备需加装驱动模块。
3. Arduino 代码示例
以下代码配置 UNO R4 WiFi 为 AP 模式,并启动一个简易 HTTP 服务器。当终端访问特定 URL 时,即可控制板载 LED。
wifi_ap_web.cpp
cpp
/*
* WIFI WEB as Server
*/
//#include <WiFiS3.h>
#include "wifi_ap_web.h"
// WiFi AP 配置
const char* ssid = "R4WIFI";
const char* password = "12345678";
// 创建WiFi服务器(端口80)
WiFiServer server(80);
static WiFiClient client;
// LED 状态(由主程序管理)
extern bool ledState;
// WiFi 状态管理
#define WIFI_STATUS_INIT 0
#define WIFI_STATUS_INIT2CONNECTED 1
#define WIFI_STATUS_CONNECTED 3
#define WIFI_STATUS_DISCONNECTED 4
static int eCurrentWifiStatus = WIFI_STATUS_INIT;
// 前向声明
static void sendCorsResponse(WiFiClient& client, bool state);
static void sendJsonResponse(WiFiClient& client, bool state);
static void sendHtmlPage(WiFiClient& client, bool state);
static void sendNotFound(WiFiClient& client);
static void wifiPrintIpaddress();
// ============ WiFi 初始化 ============
void wifiweb_init() {
Serial.println("正在配置AP模式...");
WiFi.beginAP(ssid, password);
Serial.println("等待AP启动");
// 状态机会处理连接状态
}
// ============ 打印 IP 地址 ============
static void wifiPrintIpaddress() {
Serial.println("\n✅ AP模式已启动");
Serial.print("IP地址: ");
Serial.println(WiFi.localIP());
Serial.print("SSID: ");
Serial.println(ssid);
Serial.print("密码: ");
Serial.println(password);
server.begin();
Serial.println("✅ Web服务器已启动");
}
// ============ WiFi 状态机 ============
#if 0
static int getWifiStatus() {
// 简化版:只要 AP 启动就认为连接正常
static bool apStarted = false;
if (!apStarted) {
// 检查 AP 是否已经启动
IPAddress ip = WiFi.localIP();
if (ip != INADDR_NONE && ip != IPAddress(0, 0, 0, 0)) {
apStarted = true;
wifiPrintIpaddress();
eCurrentWifiStatus = WIFI_STATUS_CONNECTED;
}
return eCurrentWifiStatus;
}
// 如果 AP 已启动,直接返回 CONNECTED
eCurrentWifiStatus = WIFI_STATUS_CONNECTED;
return eCurrentWifiStatus;
}
#else
static int getWifiStatus() {
switch(eCurrentWifiStatus) {
case WIFI_STATUS_INIT:
// AP模式:检查IP是否分配了
if (WiFi.localIP() != INADDR_NONE) {
eCurrentWifiStatus = WIFI_STATUS_INIT2CONNECTED;
wifiPrintIpaddress();
}
break;
case WIFI_STATUS_INIT2CONNECTED:
// 如果有客户端连接,或者AP已就绪
if (WiFi.localIP() != INADDR_NONE) {
eCurrentWifiStatus = WIFI_STATUS_CONNECTED;
}
break;
case WIFI_STATUS_CONNECTED:
// 检查AP是否仍然活跃
if (WiFi.localIP() == INADDR_NONE) {
eCurrentWifiStatus = WIFI_STATUS_DISCONNECTED;
}
break;
case WIFI_STATUS_DISCONNECTED:
default:
eCurrentWifiStatus = WIFI_STATUS_INIT;
break;
}
return eCurrentWifiStatus;
}
#endif
// ============ 主处理函数 ============
int wifiweb_getCommand() {
int cmd = 0;
int wifiStatus = getWifiStatus();
if (wifiStatus != WIFI_STATUS_CONNECTED) {
// WiFi 未就绪,返回错误
return (wifiStatus == WIFI_STATUS_INIT2CONNECTED) ? WIFI_CMD_WIFI_CONNECTED : WIFI_CMD_WIFI_ERROR;
}
// 监听客户端连接
client = server.available();
if (client) {
Serial.println("📡 新客户端连接");
// 读取HTTP请求
String request = "";
while (client.connected()) {
if (client.available()) {
char c = client.read();
request += c;
if (c == '\n' && request.endsWith("\r\n\r\n")) {
break;
}
}
}
// 解析请求路径
String path = "";
if (request.length() > 0) {
int firstSpace = request.indexOf(' ');
int secondSpace = request.indexOf(' ', firstSpace + 1);
if (firstSpace > 0 && secondSpace > 0) {
path = request.substring(firstSpace + 1, secondSpace);
}
}
Serial.print("请求路径: ");
Serial.println(path);
// 处理不同路径
if (path == "/H" || path == "/led/on") {
Serial.println("💡 LED 开启");
cmd = WIFI_CMD_ON;
sendCorsResponse(client, true);
}
else if (path == "/L" || path == "/led/off") {
Serial.println("💡 LED 关闭");
cmd = WIFI_CMD_OFF;
sendCorsResponse(client, false);
}
else if (path == "/T" || path == "/led/toggle") {
Serial.println("🔄 LED 翻转");
cmd = WIFI_CMD_TOGGLE;
sendCorsResponse(client, ledState);
}
else if (path == "/status" || path == "/status/") {
Serial.println("Get status");
sendJsonResponse(client, ledState);
}
else if (path == "/" || path == "/index.html") {
sendHtmlPage(client, ledState);
}
else {
sendNotFound(client);
}
delay(10);
client.stop();
Serial.println("🔌 连接已关闭");
}
return cmd;
}
// ============ 响应函数 ============
static void sendCorsResponse(WiFiClient& client, bool state) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/plain");
client.println("Access-Control-Allow-Origin: *");
client.println("Access-Control-Allow-Methods: GET, OPTIONS");
client.println("Access-Control-Allow-Headers: *");
client.println("Connection: close");
client.println();
client.print("LED状态: ");
client.println(state ? "ON" : "OFF");
}
static void sendJsonResponse(WiFiClient& client, bool state) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: application/json");
client.println("Access-Control-Allow-Origin: *");
client.println("Access-Control-Allow-Methods: GET, OPTIONS");
client.println("Access-Control-Allow-Headers: *");
client.println("Connection: close");
client.println();
client.print("{\"status\":\"ok\",\"led\":");
client.print(state ? "true" : "false");
client.print(",\"message\":\"");
client.print(state ? "LED已开启" : "LED已关闭");
client.println("\"}");
}
static void sendHtmlPage(WiFiClient& client, bool state) {
client.println("HTTP/1.1 200 OK");
client.println("Content-Type: text/html");
client.println("Access-Control-Allow-Origin: *");
client.println("Connection: close");
client.println();
client.println("<!DOCTYPE html>");
client.println("<html>");
client.println("<head><meta charset='UTF-8'><title>LED Control</title></head>");
client.println("<body style='text-align:center;font-family:Arial;margin-top:50px;'>");
client.println("<h1>💡 Arduino UNO R4 WiFi</h1>");
client.println("<p>LED状态: <strong>" + String(state ? "ON" : "OFF") + "</strong></p>");
client.println("<button onclick=\"fetch('/H')\" style='padding:15px 30px;background:#4CAF50;color:white;border:none;border-radius:5px;margin:5px;'>开灯</button>");
client.println("<button onclick=\"fetch('/L')\" style='padding:15px 30px;background:#f44336;color:white;border:none;border-radius:5px;margin:5px;'>关灯</button>");
client.println("<button onclick=\"fetch('/T')\" style='padding:15px 30px;background:#2196F3;color:white;border:none;border-radius:5px;margin:5px;'>翻转</button>");
client.println("<br><br><button onclick=\"location.reload()\" style='padding:10px 20px;background:#607D8B;color:white;border:none;border-radius:5px;'>刷新状态</button>");
client.println("</body></html>");
}
static void sendNotFound(WiFiClient& client) {
client.println("HTTP/1.1 404 Not Found");
client.println("Content-Type: text/plain");
client.println("Access-Control-Allow-Origin: *");
client.println("Connection: close");
client.println();
client.println("404 - 路径未找到");
}wifi_ap_web.h
cpp
#ifndef WIFIWEB_H
#define WIFIWEB_H
#include <WiFiS3.h>
#ifdef __cplusplus
extern "C" {
#endif
#define WIFI_CMD_ON 1
#define WIFI_CMD_OFF 2
#define WIFI_CMD_TOGGLE 3
#define WIFI_CMD_WIFI_CONNECTED 10
#define WIFI_CMD_WIFI_ERROR 11
// 函数声明
void wifiweb_init();
int wifiweb_getCommand();
#ifdef __cplusplus
}
#endif
#endifMain.cpp
cpp
#include <U8g2lib.h>
#include <Wire.h>
#include "wifi_ap_web.h"
/*
* display module
*/
U8G2_SSD1306_128X64_NONAME_F_SW_I2C u8g2(U8G2_R0, /* clock=*/ SCL, /* data=*/ SDA, /* reset=*/ U8X8_PIN_NONE);
void display_init()
{
u8g2.begin();
u8g2.enableUTF8Print();
}
void display_task()
{
static int preMs = 0;
int curMs = millis();
if(curMs - preMs < 1000)
{
return;
}
preMs = curMs; // 修正:应该是 preMs = curMs
u8g2.clearBuffer();
u8g2.setFont(u8g2_font_ncenB08_tr);
u8g2.drawStr(0, 10, "Hello, U8g2!");
u8g2.setFont(u8g2_font_wqy12_t_gb2312a);
u8g2.drawUTF8(0, 25, "这是温度照度示例");
u8g2.drawLine(0, 30, 127, 30);
u8g2.drawBox(10, 40, 20, 10);
u8g2.drawCircle(50, 50, 12, U8G2_DRAW_ALL);
u8g2.sendBuffer();
}
/*
* LED module
*/
const int ledPin = LED_BUILTIN;
bool ledState = false; // 这个变量在 wifi_ap_web.cpp 中被 extern 引用
/*
* main loop
*/
void setup() {
Serial.begin(9600);
pinMode(ledPin, OUTPUT);
digitalWrite(ledPin, LOW);
ledState = false;
display_init();
Serial.println("wifi web init ...");
wifiweb_init();
}
void wifiweb_task()
{
int cmd = wifiweb_getCommand();
switch(cmd)
{
case WIFI_CMD_ON: // led on
digitalWrite(ledPin, HIGH);
ledState = true;
break;
case WIFI_CMD_OFF: // led off
digitalWrite(ledPin, LOW);
ledState = false;
break;
case WIFI_CMD_TOGGLE: // led toggle
ledState = !ledState;
digitalWrite(ledPin, ledState ? HIGH : LOW);
break;
case WIFI_CMD_WIFI_CONNECTED: // wifi connected
Serial.println("Wifi Connected !");
break;
default:
break;
}
}
void loop() {
display_task();
wifiweb_task();
delay(10);
}control.html
html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>LED控制(带状态读取)</title>
<style>
body { font-family: Arial; max-width: 500px; margin: 50px auto; text-align: center; }
.led { display: inline-block; width: 30px; height: 30px; border-radius: 50%;
background: #ccc; margin: 20px; transition: 0.3s; }
.led.on { background: #ffeb3b; box-shadow: 0 0 30px #ffeb3b; }
button { padding: 12px 30px; margin: 5px; border: none; border-radius: 5px;
cursor: pointer; font-size: 16px; }
.btn-on { background: #4CAF50; color: white; }
.btn-off { background: #f44336; color: white; }
.btn-toggle { background: #2196F3; color: white; }
.btn-status { background: #607D8B; color: white; }
#log { margin-top: 20px; padding: 10px; background: #f0f0f0; border-radius: 5px; font-size: 14px; }
</style>
</head>
<body>
<h1>💡 LED控制</h1>
<p>Arduino IP: <input type="text" id="ipInput" value="192.168.4.1" style="width:120px;text-align:center;"></p>
<div class="led" id="ledIndicator"></div>
<p>LED状态: <strong id="ledStatus">未知</strong></p>
<div>
<button class="btn-on" onclick="sendCmd('H')">🔆 开灯</button>
<button class="btn-off" onclick="sendCmd('L')">🔅 关灯</button>
<button class="btn-toggle" onclick="sendCmd('T')">🔄 翻转</button>
</div>
<br>
<button class="btn-status" onclick="getStatus()">📡 获取真实状态</button>
<div id="log">就绪</div>
<script>
const ipInput = document.getElementById('ipInput');
const ledIndicator = document.getElementById('ledIndicator');
const ledStatus = document.getElementById('ledStatus');
const logDiv = document.getElementById('log');
function getIP() { return ipInput.value.trim() || '192.168.4.1'; }
function log(msg) {
logDiv.textContent = msg;
console.log(msg);
}
async function sendCmd(cmd) {
const ip = getIP();
const url = `http://${ip}/${cmd}`;
log(`发送: ${cmd}`);
try {
const response = await fetch(url);
if (response.ok) {
// 命令发送成功后,延迟一下再获取状态
setTimeout(getStatus, 200);
} else {
log(`❌ 错误: ${response.status}`);
}
} catch(err) {
log(`❌ 连接失败: ${err.message}`);
}
}
async function getStatus() {
const ip = getIP();
const url = `http://${ip}/status`;
log('📡 获取状态...');
try {
const response = await fetch(url);
if (!response.ok) {
log(`❌ HTTP ${response.status}`);
return;
}
const data = await response.json();
log(`✅ 状态: LED = ${data.led ? '开启' : '关闭'}`);
updateUI(data.led);
} catch(err) {
log(`❌ 获取状态失败: ${err.message}`);
}
}
function updateUI(state) {
if (state) {
ledIndicator.className = 'led on';
ledStatus.textContent = '开启';
ledStatus.style.color = '#4CAF50';
} else {
ledIndicator.className = 'led';
ledStatus.textContent = '关闭';
ledStatus.style.color = '#f44336';
}
}
// 页面加载时自动获取状态
window.onload = function() {
setTimeout(getStatus, 500);
};
</script>
</body>
</html>