# 帆船專題 ESP32-S
ESP32為系列產品,由上海的中國公司"樂鑫資訊科技(Espressif)"建立和開發,而此系列主打低成本、低功耗,並且整合Wi-Fi和雙模藍芽。有許多Maker透過ESP32的開發板做IoT相關技術的應用,如遠端查看目前的大氣壓力、溫度濕度、倒車雷達更甚至整合在水務上,去監控水的流速及水壓等,讓人類的生活更進步。ESP32板子上有許多針腳其中數字稱為GPIO,另外還有5V、3V3、GND及可以做進階應用Pin(Tx Rx等)。
- VIN3.3V & 5V:主要供給需要使用到電的零件或感測器。
- GND:主要作為接地,使電源有迴路。
- GPIO:主要作為控制零件的開啟或關閉,或者讀取感測器的數值,某些GPIO都有額外的敘述,這些敘述代表這些腳位有不同的功能,例如:只允許輸入,不允許輸出、預設給Esp32s內部記憶體使用...等等功能。

## 腳位

## 伺服馬達測試
```
#include <ESP32Servo.h>
Servo myservo;
void setup() {
myservo.setPeriodHertz(50);
myservo.attach(15, 500, 2500); // GPIO15, min 0.5ms, max 2.5ms
}
void loop() {
for (int pos = 0; pos <= 180; pos++) {
myservo.write(pos);
delay(15);
}
}
```
## 藍芽測試
```
#include <BluetoothSerial.h>
char command;
BluetoothSerial SerialBT;
int led = 2; //LED
void setup() {
Serial.begin(115200); // 建議使用 115200鮑率
pinMode(led, OUTPUT);
SerialBT.begin("ESP32_BT");
}
void loop() {
// put your main code here, to run repeatedly:
if (SerialBT.available()) {
command = SerialBT.read();
if(command == 'F'){
//收到1,開啟LED
digitalWrite(led,HIGH);
Serial.println("收到1");
}
if(command == 'B'){
//收到2,關閉LED
digitalWrite(led,LOW);
Serial.println("收到2");
}
delay(10);
}
```
## 藍牙1
```
#include <BluetoothSerial.h>
#include <ESP32Servo.h> //請先安裝ESP32Servo程式庫
char command;
BluetoothSerial SerialBT;
Servo myservo1,myservo2; //建立一個伺服馬達物件
int led = 2; //LED
int servoPin1 = 15; // 定義信號線接腳 (GPIO 15)
int servoPin2 = 16; // 定義信號線接腳 (GPIO 16)
void setup() {
Serial.begin(115200); // 建議使用 115200鮑率
pinMode(led, OUTPUT);
//Serial.println("ESP32 OK!");
//SG90的脈衝寬度500~2400
myservo1.setPeriodHertz(50); // 設定 PWM 頻率為 50Hz
myservo1.attach(servoPin1, 500, 2500); // 接腳 GPIO15,最小/最大脈衝寬度
myservo2.setPeriodHertz(50); // 設定 PWM 頻率為 50Hz
myservo2.attach(servoPin2, 500, 2500); // 接腳 GPIO16,最小/最大脈衝寬度
SerialBT.begin("ESP32_BT");
}
void loop() {
// put your main code here, to run repeatedly:
//Serial.print("Hello World!"); // 傳送字串
//char value;
if (SerialBT.available()) {
command = SerialBT.read();
if(command == 'F'){
//收到1,開啟LED
digitalWrite(led,HIGH);
myservo1.write(90); //角度0~180
Serial.println("收到1");
}
if(command == 'B'){
//收到2,關閉LED
digitalWrite(led,LOW);
myservo1.write(0); //角度0~180
Serial.println("收到2");
}
if(command == 'L'){
//收到1,開啟LED
digitalWrite(led,HIGH);
myservo2.write(90); //角度0~180
Serial.println("收到1");
}
if(command == 'R'){
//收到2,關閉LED
digitalWrite(led,LOW);
myservo2.write(0); //角度0~180
Serial.println("收到2");
}
}
delay(10);
}
```
## 藍牙2
```
#include <BluetoothSerial.h>
#include <ESP32Servo.h> //請先安裝ESP32Servo程式庫
char command;
BluetoothSerial SerialBT;
Servo myservo1,myservo2; //建立一個伺服馬達物件
int led = 2; //LED
int servoPin1 = 15; // 定義信號線接腳 (GPIO 15)
int servoPin2 = 16; // 定義信號線接腳 (GPIO 16)
int angle1 = 0;
int angle2 = 0;
void setup() {
Serial.begin(115200); // 建議使用 115200鮑率
pinMode(led, OUTPUT);
//Serial.println("ESP32 OK!");
//SG90的脈衝寬度500~2400
myservo1.setPeriodHertz(50); // 設定 PWM 頻率為 50Hz
myservo1.attach(servoPin1, 500, 2500); // 接腳 GPIO15,最小/最大脈衝寬度
myservo2.setPeriodHertz(50); // 設定 PWM 頻率為 50Hz
myservo2.attach(servoPin2, 500, 2500); // 接腳 GPIO16,最小/最大脈衝寬度
myservo1.write(angle1); //角度0
myservo2.write(angle2); //角度0
SerialBT.begin("ESP32_BT");
}
void loop() {
// put your main code here, to run repeatedly:
//Serial.print("Hello World!"); // 傳送字串
//char value;
if (SerialBT.available()) {
command = SerialBT.read();
if(command == 'F'){
//收到1,開啟LED
digitalWrite(led,HIGH);
angle1 += 10;
if (angle1 > 90) {
angle1 = 90;
}
myservo1.write(angle1); //角度
Serial.println("舵增加");
}
if(command == 'B'){
//收到2,關閉LED
digitalWrite(led,LOW);
angle1 -= 10;
if (angle1 < 0) {
angle1 = 0;
}
myservo1.write(angle1); //角度
Serial.println("舵減少");
}
if(command == 'L'){
//收到1,開啟LED
digitalWrite(led,HIGH);
angle2 += 10;
if (angle2 > 90) {
angle2 = 90;
}
myservo2.write(angle2); //角度
Serial.println("帆增加");
}
if(command == 'R'){
//收到2,關閉LED
digitalWrite(led,LOW);
angle2 -= 10;
if (angle2 < 0) {
angle2 = 0;
}
myservo2.write(angle2); //角度
Serial.println("帆減少");
}
}
delay(10);
}
```
## wifi1
```
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
// ========= WiFi 設定 =========
const char* ssid = ""; // 修改為你的 WiFi 名稱
const char* password = ""; // 修改為你的 WiFi 密碼
// ========= 伺服設定 =========
Servo servo1, servo2;
const int servoPin1 = 13; // 第 1 個伺服馬達的腳位
const int servoPin2 = 15; // 第 2 個伺服馬達的腳位
int pos1 = 0; // 初始角度
int pos2 = 0;
// ========= 建立 Web Server =========
WebServer server(80);
// ========= 建立網頁內容 =========
String createHTML(int angle1, int angle2) {
String html = R"rawliteral(
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta charset="utf-8">
<title>ESP32 伺服馬達控制</title>
<style>
html { font-family: sans-serif; text-align: center; }
h1 { margin: 30px auto; }
.slider { width: 80%; }
.container { margin: 20px auto; }
</style>
</head>
<body>
<h1>ESP32 雙伺服馬達控制</h1>
<div class="container">
<h3>伺服1角度: <span id="angle1">)rawliteral" + String(angle1) + R"rawliteral(</span>°</h3>
<input type="range" min="0" max="180" value=")rawliteral" + String(angle1) + R"rawliteral(" class="slider" id="servo1Slider" onchange="updateServo(1, this.value)">
</div>
<div class="container">
<h3>伺服2角度: <span id="angle2">)rawliteral" + String(angle2) + R"rawliteral(</span>°</h3>
<input type="range" min="0" max="180" value=")rawliteral" + String(angle2) + R"rawliteral(" class="slider" id="servo2Slider" onchange="updateServo(2, this.value)">
</div>
<script>
function updateServo(id, pos) {
document.getElementById("angle" + id).innerHTML = pos;
var xhr = new XMLHttpRequest();
xhr.open("GET", "/set?id=" + id + "&value=" + pos, true);
xhr.send();
}
</script>
</body>
</html>
)rawliteral";
return html;
}
// ========= 處理首頁請求 =========
void handleRoot() {
server.send(200, "text/html", createHTML(pos1, pos2));
}
// ========= 處理設定伺服角度請求 =========
void handleServoSet() {
if (server.hasArg("id") && server.hasArg("value")) {
int id = server.arg("id").toInt();
int val = server.arg("value").toInt();
if (id == 1) {
pos1 = val;
servo1.write(pos1);
Serial.printf("伺服1 移動到 %d°\n", pos1);
} else if (id == 2) {
pos2 = val;
servo2.write(pos2);
Serial.printf("伺服2 移動到 %d°\n", pos2);
}
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "錯誤的參數");
}
}
// ========= 初始化 =========
void setup() {
Serial.begin(115200);
servo1.setPeriodHertz(50);
servo2.setPeriodHertz(50);
servo1.attach(servoPin1, 500, 2400);
servo2.attach(servoPin2, 500, 2400);
servo1.write(pos1);
servo2.write(pos2);
WiFi.begin(ssid, password);
Serial.print("連線中");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 連線成功!");
Serial.print("IP 位址: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/set", handleServoSet);
server.begin();
Serial.println("HTTP 伺服器啟動完成");
}
// ========= 主迴圈 =========
void loop() {
server.handleClient();
}
```
## wifi2
```
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
// ========= WiFi 設定 =========
const char* ssid = ""; // 修改為你的 WiFi 名稱
const char* password = ""; // 修改為你的 WiFi 密碼
// ========= 伺服設定 =========
Servo servo1, servo2;
const int servoPin1 = 13; // 第 1 個伺服馬達的腳位
const int servoPin2 = 15; // 第 2 個伺服馬達的腳位
int pos1 = 0; // 初始角度
int pos2 = 0;
// ========= 建立 Web Server =========
WebServer server(80);
// ========= 建立網頁內容 =========
String createHTML(int angle1, int angle2, String ipAddress) {
String html = // 不要只用 R"rawliteral"
"<!DOCTYPE html>\n"
"<html>\n"
"<head>\n"
" <meta name='viewport' content='width=device-width, initial-scale=1'>\n"
" <meta charset='utf-8'>\n"
" <title>ESP32 伺服馬達控制</title>\n"
" <style>\n"
" html { font-family: sans-serif; text-align: center; }\n"
" h1 { margin: 30px auto; }\n"
" .ipinfo { font-size: 1.2em; margin-bottom: 20px; color: #3366BB; }\n"
" .slider { width: 80%; }\n"
" .container { margin: 20px auto; }\n"
" </style>\n"
"</head>\n"
"<body>\n"
" <h1>ESP32 雙伺服馬達控制</h1>\n"
" <div class='ipinfo'>目前裝置 IP 位址:" + ipAddress + "</div>\n"
" <div class='container'>\n"
" <h3>伺服1角度: <span id='angle1'>" + String(angle1) + "</span>°</h3>\n"
" <input type='range' min='0' max='180' value='" + String(angle1) + "' class='slider' id='servo1Slider' onchange='updateServo(1, this.value)'>\n"
" </div>\n"
" <div class='container'>\n"
" <h3>伺服2角度: <span id='angle2'>" + String(angle2) + "</span>°</h3>\n"
" <input type='range' min='0' max='180' value='" + String(angle2) + "' class='slider' id='servo2Slider' onchange='updateServo(2, this.value)'>\n"
" </div>\n"
" <script>\n"
" function updateServo(id, pos) {\n"
" document.getElementById('angle' + id).innerHTML = pos;\n"
" var xhr = new XMLHttpRequest();\n"
" xhr.open('GET', '/set?id=' + id + '&value=' + pos, true);\n"
" xhr.send();\n"
" }\n"
" </script>\n"
"</body>\n"
"</html>\n";
return html;
}
// ========= 處理首頁請求 =========
void handleRoot() {
String ipAddress = WiFi.localIP().toString();
server.send(200, "text/html", createHTML(pos1, pos2, ipAddress));
}
// ========= 處理設定伺服角度請求 =========
void handleServoSet() {
if (server.hasArg("id") && server.hasArg("value")) {
int id = server.arg("id").toInt();
int val = server.arg("value").toInt();
if (id == 1) {
pos1 = val;
servo1.write(pos1);
Serial.printf("伺服1 移動到 %d°\n", pos1);
} else if (id == 2) {
pos2 = val;
servo2.write(pos2);
Serial.printf("伺服2 移動到 %d°\n", pos2);
}
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "錯誤的參數");
}
}
// ========= 初始化 =========
void setup() {
Serial.begin(115200);
servo1.setPeriodHertz(50);
servo2.setPeriodHertz(50);
servo1.attach(servoPin1, 500, 2400);
servo2.attach(servoPin2, 500, 2400);
servo1.write(pos1);
servo2.write(pos2);
WiFi.begin(ssid, password);
Serial.print("連線中");
while (WiFi.status() != WL_CONNECTED) {
delay(500);
Serial.print(".");
}
Serial.println("\nWiFi 連線成功!");
Serial.print("IP 位址: ");
Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/set", handleServoSet);
server.begin();
Serial.println("HTTP 伺服器啟動完成");
}
// ========= 主迴圈 =========
void loop() {
server.handleClient();
}
```
## wifi button
```
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
// ======= WiFi 設定 =======
const char* ssid = ""; // 修改為你的 WiFi 名稱
const char* password = ""; // 修改為你的 WiFi 密碼
// ======= 伺服設定 =======
Servo servoSail, servoRudder;
const int sailPin = 13;
const int rudderPin = 15;
int sailPos = 90; //初始角度
int rudderPos = 90; //初始角度
// ======= Web Server =======
WebServer server(80);
// ======= 建立網頁內容(美化+按鈕)=======
String createHTML(int sailAngle, int rudderAngle, String ipAddress) {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<meta charset='utf-8'><title>ESP32 帆&舵伺服控制</title>";
html += "<style>";
html += "body{background:#f8f8ff; font-family:Tahoma,sans-serif; text-align:center;}";
html += "h1{color:#0066cc;margin:18px;} .ipbox{color:#444;padding:8px;}";
html += ".servo-box{display:inline-block; margin:16px; padding:18px; border-radius:18px; background:#e0ecff; box-shadow:0 3px 10px #bbc8e3;}";
html += ".servo-title{font-size:1.3em; color:#23408c; margin-bottom:15px;} .angle{font-weight:bold; color:#cc4400;}";
html += ".btn-grid{display:grid; grid-template-columns:repeat(5,1fr); gap:7px;}";
html += ".ctrl-btn{border:none;padding:15px 3px; background:#2577cc;color:white; font-size:1em; border-radius:10px; font-weight:bold; cursor:pointer; box-shadow:0 1.5px 6px #8cb3e0;transition:transform .09s;background-image: linear-gradient(#3fb8ff, #2563b7);}";
html += ".ctrl-btn:active{transform:scale(1.09);background-image:linear-gradient(#62ffa8,#2577cc);}";
html += ".ctrl-btn.selected{background:#e69b43;color:#17413d;}";
html += "</style></head><body>";
html += "<h1>ESP32 雙伺服電控系統</h1>";
html += "<div class='ipbox'>目前裝置 IP 位址:<b>" + ipAddress + "</b></div>";
// 帆 (Servo1) 按鈕區加 id
html += "<div class='servo-box'><div class='servo-title'>控制帆</div>";
html += "<div>目前角度:<span class='angle' id='sailAngle'>" + String(sailAngle) + "</span>°</div><br>";
html += "<div class='btn-grid' id='sailBtns'>";
for (int v = 0; v <= 180; v += 45) {
html += String("<button class='ctrl-btn");
if (sailAngle == v) html += " selected";
html += "' onclick='setServo(1," + String(v) + ")'>" + String(v) + "°</button>";
}
html += "</div></div>";
// 舵 (Servo2) 按鈕區加 id
html += "<div class='servo-box'><div class='servo-title'>控制舵</div>";
html += "<div>目前角度:<span class='angle' id='rudderAngle'>" + String(rudderAngle) + "</span>°</div><br>";
html += "<div class='btn-grid' id='rudderBtns'>";
for (int v = 0; v <= 180; v += 45) {
html += String("<button class='ctrl-btn");
if (rudderAngle == v) html += " selected";
html += "' onclick='setServo(2," + String(v) + ")'>" + String(v) + "°</button>";
}
html += "</div></div>";
// JS 修正帆與舵分開高亮
html += "<script>";
html += "function setServo(id, value) {";
html += "fetch('/set?id='+id+'&value='+value).then(()=>{";
html += "if(id==1) {";
html += "document.getElementById('sailAngle').innerHTML=value;";
html += "let btns=document.querySelectorAll('#sailBtns .ctrl-btn');";
html += "btns.forEach((b)=>{b.classList.remove('selected'); if(b.innerText==value+'°'){b.classList.add('selected');}});";
html += "}";
html += "if(id==2) {";
html += "document.getElementById('rudderAngle').innerHTML=value;";
html += "let btns=document.querySelectorAll('#rudderBtns .ctrl-btn');";
html += "btns.forEach((b)=>{b.classList.remove('selected'); if(b.innerText==value+'°'){b.classList.add('selected');}});";
html += "}";
html += "});}";
html += "</script>";
html += "</body></html>";
return html;
}
// ======= 頁面回應 =======
void handleRoot() {
String ip = WiFi.localIP().toString();
server.send(200, "text/html", createHTML(sailPos, rudderPos, ip));
}
void handleServoSet() {
if(server.hasArg("id") && server.hasArg("value")) {
int id = server.arg("id").toInt();
int val = server.arg("value").toInt();
if(id==1){
sailPos = val;
servoSail.write(sailPos);
Serial.printf("帆(Servo1) 移動到 %d°\n", sailPos);
}
else if(id==2){
rudderPos = val;
servoRudder.write(rudderPos);
Serial.printf("舵(Servo2) 移動到 %d°\n", rudderPos);
}
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "參數錯誤");
}
}
// ======= 初始化 =======
void setup() {
Serial.begin(115200);
servoSail.setPeriodHertz(50);
servoRudder.setPeriodHertz(50);
servoSail.attach(sailPin, 500, 2400);
servoRudder.attach(rudderPin, 500, 2400);
servoSail.write(sailPos);
servoRudder.write(rudderPos);
WiFi.begin(ssid, password);
Serial.print("連線中");
while (WiFi.status() != WL_CONNECTED) {
delay(500); Serial.print(".");
}
Serial.println("\nWiFi 已連線");
Serial.print("IP 位址: "); Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/set", handleServoSet);
server.begin();
Serial.println("HTTP 伺服器已啟動");
}
// ======= 主迴圈 =======
void loop() {
server.handleClient();
}
```
## wifi button slider (目前使用版本)
```
#include <WiFi.h>
#include <WebServer.h>
#include <ESP32Servo.h>
// ======= WiFi 設定 =======
const char* ssid = ""; // 修改為你的 WiFi 名稱
const char* password = ""; // 修改為你的 WiFi 密碼
// ======= 伺服設定 =======
Servo servoSail, servoRudder;
const int sailPin = 13;
const int rudderPin = 15;
int sailPos = 90;
int rudderPos = 90;
// ======= Web Server =======
WebServer server(80);
// ======= 響應式網頁內容,上下布局 =======
String createHTML(int sailAngle, int rudderAngle, String ipAddress) {
String html = "<!DOCTYPE html><html><head>";
html += "<meta name='viewport' content='width=device-width, initial-scale=1'>";
html += "<meta charset='utf-8'><title>ESP32 帆&舵伺服控制</title>";
html += "<style>";
html += "body{background:#f8f8ff; font-family:Tahoma,sans-serif; margin:0;}";
html += "h1{color:#0066cc; margin:18px auto 8px; text-align:center;} .ipbox{color:#444; text-align:center;margin-bottom:12px;}";
html += ".container{display:flex;flex-direction:column;max-width:500px;margin:30px auto;gap:30px;}";
html += ".servo-box{padding:18px 12px; border-radius:16px; background:#e0ecff; box-shadow:0 2px 10px #bbc8e3; margin:0 auto;width:94%;}";
html += ".servo-title{font-size:1.2em; color:#23408c; margin-bottom:10px;}";
html += ".angle{font-weight:bold; color:#cc4400;}";
html += ".btn-grid{display:grid; grid-template-columns:repeat(5,1fr); gap:8px; justify-items:center; margin-top:10px;}";
html += ".ctrl-btn{border:none; padding:14px 4px; background:#2577cc;color:white; font-size:1em; border-radius:10px; font-weight:bold; cursor:pointer;";
html += "box-shadow:0 1.5px 6px #8cb3e0;transition:transform .07s;background-image: linear-gradient(#3fb8ff, #2563b7);width:96%;}";
html += ".ctrl-btn:active{transform:scale(1.09);background-image:linear-gradient(#62ffa8,#2577cc);}";
html += ".ctrl-btn.selected{background:#e69b43;color:#17413d;}";
html += ".slider-box{margin-top:16px; display:flex; flex-direction:column;align-items:center;}";
html += ".slider{width:90%;max-width:350px;margin:16px 0;}";
html += "@media(max-width:600px){.container{max-width:99vw}.servo-box{width:99%;}}";
html += "</style></head><body>";
html += "<h1>ESP32 雙伺服電控系統</h1>";
html += "<div class='ipbox'>目前裝置 IP 位址:<b>" + ipAddress + "</b></div>";
html += "<div class='container'>";
// 帆 (Servo1)區-按鈕
html += "<div class='servo-box'><div class='servo-title'>控制帆</div>";
html += "<div>目前角度:<span class='angle' id='sailAngle'>" + String(sailAngle) + "</span>°</div>";
html += "<div class='btn-grid' id='sailBtns'>";
for (int v = 0; v <= 180; v += 45) {
html += String("<button class='ctrl-btn");
if (sailAngle == v) html += " selected";
html += "' onclick='setServo(1," + String(v) + ")'>" + String(v) + "°</button>";
}
html += "</div></div>";
// 舵 (Servo2)區-滑桿
html += "<div class='servo-box'><div class='servo-title'>控制舵</div>";
html += "<div>目前角度:<span class='angle' id='rudderAngle'>" + String(rudderAngle) + "</span>°</div>";
html += "<div class='slider-box'>";
html += "<input type='range' min='0' max='180' value='" + String(rudderAngle) + "' class='slider' id='rudderSlider' oninput='rudderVal.innerText=this.value' onchange='setServo(2,this.value)'>";
html += "<div>選擇角度:<span class='angle' id='rudderVal'>" + String(rudderAngle) + "</span>°</div>";
html += "</div></div>";
html += "</div>";
// JS 操作
html += "<script>";
html += "function setServo(id, value) {";
html += "fetch('/set?id='+id+'&value='+value).then(()=>{";
html += "if(id==1){";
html += "document.getElementById('sailAngle').innerHTML=value;";
html += "let btns=document.querySelectorAll('#sailBtns .ctrl-btn');";
html += "btns.forEach((b)=>{b.classList.remove('selected');if(b.innerText==value+'°'){b.classList.add('selected');}});";
html += "}else{";
html += "document.getElementById('rudderAngle').innerHTML=value;";
html += "document.getElementById('rudderVal').innerText=value;";
html += "document.getElementById('rudderSlider').value=value;";
html += "}";
html += "});}";
html += "</script>";
html += "</body></html>";
return html;
}
// ======= 頁面回應 =======
void handleRoot() {
String ip = WiFi.localIP().toString();
server.send(200, "text/html", createHTML(sailPos, rudderPos, ip));
}
void handleServoSet() {
if (server.hasArg("id") && server.hasArg("value")) {
int id = server.arg("id").toInt();
int val = server.arg("value").toInt();
if (id == 1) {
sailPos = val;
servoSail.write(sailPos);
Serial.printf("帆(Servo1) 移動到 %d°\n", sailPos);
} else if (id == 2) {
rudderPos = val;
servoRudder.write(rudderPos);
Serial.printf("舵(Servo2) 移動到 %d°\n", rudderPos);
}
server.send(200, "text/plain", "OK");
} else {
server.send(400, "text/plain", "參數錯誤");
}
}
// ======= 初始化 =======
void setup() {
Serial.begin(115200);
servoSail.setPeriodHertz(50);
servoRudder.setPeriodHertz(50);
servoSail.attach(sailPin, 500, 2400);
servoRudder.attach(rudderPin, 500, 2400);
servoSail.write(sailPos);
servoRudder.write(rudderPos);
WiFi.begin(ssid, password);
Serial.print("連線中");
while (WiFi.status() != WL_CONNECTED) {
delay(500); Serial.print(".");
}
Serial.println("\nWiFi 已連線");
Serial.print("IP 位址: "); Serial.println(WiFi.localIP());
server.on("/", handleRoot);
server.on("/set", handleServoSet);
server.begin();
Serial.println("HTTP 伺服器已啟動");
}
// ======= 主迴圈 =======
void loop() {
server.handleClient();
}
```