09fb5b0593402618203486a312617635f75e88fb
Tech-dat/tech-dat.md
| ... | ... | @@ -249,9 +249,9 @@ |
| 249 | 249 | |
| 250 | 250 | ## Tools and Soldering |
| 251 | 251 | |
| 252 | -- [[soldering-dat]] - [[desoldering-dat]] |
|
| 252 | +- [[fab-PCB-soldering-dat]] - [[fab-PCB-desoldering-dat]] |
|
| 253 | 253 | |
| 254 | -- [[PCB-dat]] - [[PCBA-dat]] - [[PCB-design-dat]] - [[PCB-form-dat]] - [[PCB-footprint-dat]] |
|
| 254 | +- [[PCB-dat]] - [[fab-PCBA-dat]] - [[PCB-design-dat]] - [[PCB-form-dat]] - [[PCB-footprint-dat]] |
|
| 255 | 255 | |
| 256 | 256 | - [[fab-tools-dat]] - [[fab-instrument-dat]] - [[fab-workspace-dat]] |
| 257 | 257 | |
| ... | ... | @@ -260,15 +260,15 @@ |
| 260 | 260 | |
| 261 | 261 | ## Mechnicals |
| 262 | 262 | |
| 263 | -- [[mechanics-dat]] - [[CAD-dat]] - [[CNC-dat]] - [[screw-dat]] |
|
| 263 | +- [[fab-tools-mechanical-dat]] - [[CAD-dat]] - [[CNC-dat]] - [[screw-dat]] |
|
| 264 | 264 | |
| 265 | 265 | - [[mechanical-structure-dat]] - [[tube-dat]] - [[tube-bend-dat]] |
| 266 | 266 | |
| 267 | 267 | - [[openpnp-dat]] - [[3d-printer-dat]] - [[pnp-machine-dat]] - [[glue-dat]] - [[heatsink-dat]] |
| 268 | 268 | |
| 269 | -- [[3D-dat]] - [[3d-print-dat]] |
|
| 269 | +- [[3D-dat]] - [[fab-3d-print-dat]] |
|
| 270 | + |
|
| 270 | 271 | |
| 271 | -- [[mechanic-tools-dat]] |
|
| 272 | 272 | |
| 273 | 273 | ## Circuits |
| 274 | 274 |
code-dat/projects/ESP8266-WIFI-motor-1.ino
| ... | ... | @@ -1,149 +0,0 @@ |
| 1 | -#include <ESP8266WiFi.h> |
|
| 2 | -#include <ESP8266WebServer.h> |
|
| 3 | - |
|
| 4 | -// WiFi AP settings (fixed IP) |
|
| 5 | -const char *ssid = "MotorAP"; |
|
| 6 | -const char *password = "motorpass"; // set to "" for open AP |
|
| 7 | -IPAddress apIP(192, 168, 4, 1); |
|
| 8 | -IPAddress netMsk(255, 255, 255, 0); |
|
| 9 | - |
|
| 10 | -// Define pins for motor control |
|
| 11 | -// Motor 1 |
|
| 12 | -const int M1_IN1 = 4; |
|
| 13 | -const int M1_IN2 = 5; |
|
| 14 | -// Motor 2 |
|
| 15 | -const int M2_IN1 = 0; |
|
| 16 | -const int M2_IN2 = 2; |
|
| 17 | - |
|
| 18 | -int motorControl = 50; // 0..100, default mid-point |
|
| 19 | - |
|
| 20 | -ESP8266WebServer server(80); |
|
| 21 | - |
|
| 22 | -void applyMotorControl() |
|
| 23 | -{ |
|
| 24 | - // Deadband: treat 40..60 as stop |
|
| 25 | - if (motorControl > 60) |
|
| 26 | - { |
|
| 27 | - // Forward |
|
| 28 | - int motorSpeed = map(motorControl, 61, 100, 0, 255); |
|
| 29 | - motorSpeed = constrain(motorSpeed, 0, 255); |
|
| 30 | - |
|
| 31 | - analogWrite(M1_IN1, motorSpeed); |
|
| 32 | - digitalWrite(M1_IN2, LOW); |
|
| 33 | - analogWrite(M2_IN1, motorSpeed); |
|
| 34 | - digitalWrite(M2_IN2, LOW); |
|
| 35 | - } |
|
| 36 | - else if (motorControl < 40) |
|
| 37 | - { |
|
| 38 | - // Reverse |
|
| 39 | - int motorSpeed = map(motorControl, 0, 39, 255, 0); |
|
| 40 | - motorSpeed = constrain(motorSpeed, 0, 255); |
|
| 41 | - |
|
| 42 | - digitalWrite(M1_IN1, LOW); |
|
| 43 | - analogWrite(M1_IN2, motorSpeed); |
|
| 44 | - digitalWrite(M2_IN1, LOW); |
|
| 45 | - analogWrite(M2_IN2, motorSpeed); |
|
| 46 | - } |
|
| 47 | - else |
|
| 48 | - { |
|
| 49 | - // Stop motors |
|
| 50 | - digitalWrite(M1_IN1, LOW); |
|
| 51 | - digitalWrite(M1_IN2, LOW); |
|
| 52 | - digitalWrite(M2_IN1, LOW); |
|
| 53 | - digitalWrite(M2_IN2, LOW); |
|
| 54 | - } |
|
| 55 | -} |
|
| 56 | - |
|
| 57 | -String pageRoot() |
|
| 58 | -{ |
|
| 59 | - String html = "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1\">"; |
|
| 60 | - html += "<title>Motor AP Control</title></head><body>"; |
|
| 61 | - html += "<h2>Motor Control (0-100)</h2>"; |
|
| 62 | - html += "<input type=\"range\" id=\"s\" min=\"0\" max=\"100\" value=\"" + String(motorControl) + "\" oninput=\"update(this.value)\"/>"; |
|
| 63 | - html += "<span id=\"v\">" + String(motorControl) + "</span>"; |
|
| 64 | - html += "<script>function update(v){document.getElementById('v').innerText=v;fetch('/set?val='+v);}setInterval(function(){fetch('/status').then(r=>r.text()).then(t=>{document.getElementById('s').value=t;document.getElementById('v').innerText=t;});},1000);</script>"; |
|
| 65 | - html += "</body></html>"; |
|
| 66 | - return html; |
|
| 67 | -} |
|
| 68 | - |
|
| 69 | -void handleRoot() |
|
| 70 | -{ |
|
| 71 | - server.send(200, "text/html", pageRoot()); |
|
| 72 | -} |
|
| 73 | - |
|
| 74 | -void handleSet() |
|
| 75 | -{ |
|
| 76 | - if (!server.hasArg("val")) |
|
| 77 | - { |
|
| 78 | - server.send(400, "text/plain", "missing val"); |
|
| 79 | - return; |
|
| 80 | - } |
|
| 81 | - String v = server.arg("val"); |
|
| 82 | - int val = v.toInt(); |
|
| 83 | - val = constrain(val, 0, 100); |
|
| 84 | - motorControl = val; |
|
| 85 | - applyMotorControl(); |
|
| 86 | - server.send(200, "text/plain", String(motorControl)); |
|
| 87 | -} |
|
| 88 | - |
|
| 89 | -void handleStatus() |
|
| 90 | -{ |
|
| 91 | - server.send(200, "text/plain", String(motorControl)); |
|
| 92 | -} |
|
| 93 | - |
|
| 94 | -void setup() |
|
| 95 | -{ |
|
| 96 | - // Initialize pins |
|
| 97 | - pinMode(M1_IN1, OUTPUT); |
|
| 98 | - pinMode(M1_IN2, OUTPUT); |
|
| 99 | - pinMode(M2_IN1, OUTPUT); |
|
| 100 | - pinMode(M2_IN2, OUTPUT); |
|
| 101 | - |
|
| 102 | - // Initialize motors to off |
|
| 103 | - digitalWrite(M1_IN1, LOW); |
|
| 104 | - digitalWrite(M1_IN2, LOW); |
|
| 105 | - digitalWrite(M2_IN1, LOW); |
|
| 106 | - digitalWrite(M2_IN2, LOW); |
|
| 107 | - |
|
| 108 | - Serial.begin(115200); |
|
| 109 | - delay(100); |
|
| 110 | - |
|
| 111 | - Serial.println("Test..."); |
|
| 112 | - delay(1000); |
|
| 113 | - Serial.println("Test..."); |
|
| 114 | - delay(1000); |
|
| 115 | - Serial.println("Test..."); |
|
| 116 | - delay(1000); |
|
| 117 | - // Configure AP with fixed IP |
|
| 118 | - WiFi.softAPConfig(apIP, apIP, netMsk); |
|
| 119 | - WiFi.softAP(ssid, password); |
|
| 120 | - |
|
| 121 | - IPAddress myIP = WiFi.softAPIP(); |
|
| 122 | - Serial.print("AP IP address: "); |
|
| 123 | - Serial.println(myIP); |
|
| 124 | - |
|
| 125 | - // Configure server routes |
|
| 126 | - server.on("/", handleRoot); |
|
| 127 | - server.on("/set", handleSet); |
|
| 128 | - server.on("/status", handleStatus); |
|
| 129 | - server.begin(); |
|
| 130 | - Serial.println("HTTP server started"); |
|
| 131 | - |
|
| 132 | - // Ensure PWM range 0-255 |
|
| 133 | - analogWriteRange(255); |
|
| 134 | - |
|
| 135 | - // Apply initial motor state |
|
| 136 | - applyMotorControl(); |
|
| 137 | -} |
|
| 138 | - |
|
| 139 | -void loop() |
|
| 140 | -{ |
|
| 141 | - server.handleClient(); |
|
| 142 | - // Optional: keep motor state applied in case other code modifies it |
|
| 143 | - // applyMotorControl(); |
|
| 144 | - delay(10); |
|
| 145 | -} |
|
| 146 | - |
|
| 147 | - |
|
| 148 | - |
|
| 149 | - |
code-dat/projects/ESP8266-WIFI-motor-1/ESP8266-WIFI-motor-1.ino
| ... | ... | @@ -0,0 +1,283 @@ |
| 1 | +#include <ESP8266WiFi.h> |
|
| 2 | +#include <ESP8266WebServer.h> |
|
| 3 | + |
|
| 4 | +// WiFi AP settings (fixed IP) |
|
| 5 | +const char *ssid = "MotorAP"; |
|
| 6 | +const char *password = "motorpass"; // set to "" for open AP |
|
| 7 | +IPAddress apIP(192, 168, 4, 1); |
|
| 8 | +IPAddress netMsk(255, 255, 255, 0); |
|
| 9 | + |
|
| 10 | +// Define pins for motor control |
|
| 11 | +// Motor 1 (Left) |
|
| 12 | +const int M1_IN1 = 4; |
|
| 13 | +const int M1_IN2 = 5; |
|
| 14 | +// Motor 2 (Right) |
|
| 15 | +const int M2_IN1 = 0; |
|
| 16 | +const int M2_IN2 = 2; |
|
| 17 | + |
|
| 18 | +int carSpeed = 200; // 0..255 (Base Speed) |
|
| 19 | +int minSpeed = 100; // Minimum speed to avoid stalling |
|
| 20 | +int turnDiff = 85; // Increased for more "agility" (sharper turns) |
|
| 21 | +int carAction = 0; // 0: stop, 1: forward, 2: backward, 3: left, 4: right |
|
| 22 | +unsigned long lastCommandTime = 0; |
|
| 23 | +const unsigned long SAFETY_TIMEOUT = 300; // Even tighter for high responsiveness |
|
| 24 | + |
|
| 25 | +int batteryS = 3; // Default 3S (11.1V) |
|
| 26 | + |
|
| 27 | +ESP8266WebServer server(80); |
|
| 28 | + |
|
| 29 | +int getCompensatedSpeed(int baseSpeed) { |
|
| 30 | + if (baseSpeed == 0) return 0; |
|
| 31 | + // Simple inverse scaling: |
|
| 32 | + // If it's 3S (11.1V), we scale DOWN the speed compared to 2S (7.4V) |
|
| 33 | + // Scale factor = 7.4 / 11.1 = ~0.66 |
|
| 34 | + if (batteryS == 3) { |
|
| 35 | + return (int)(baseSpeed * 0.66); |
|
| 36 | + } |
|
| 37 | + return baseSpeed; // Use raw speed for 2S |
|
| 38 | +} |
|
| 39 | + |
|
| 40 | +void applyMotorControl() |
|
| 41 | +{ |
|
| 42 | + int leftSpeed = 0; |
|
| 43 | + int rightSpeed = 0; |
|
| 44 | + |
|
| 45 | + int compensatedSpeed = getCompensatedSpeed(carSpeed); |
|
| 46 | + int compensatedTurnDiff = getCompensatedSpeed(turnDiff); |
|
| 47 | + |
|
| 48 | + // Helper to ensure motor gets enough power or is OFF |
|
| 49 | + auto normalizeSpeed = [&](int s) { |
|
| 50 | + if (s <= 0) return 0; |
|
| 51 | + if (s < minSpeed) return minSpeed; |
|
| 52 | + if (s > 255) return 255; |
|
| 53 | + return s; |
|
| 54 | + }; |
|
| 55 | + |
|
| 56 | + switch (carAction) |
|
| 57 | + { |
|
| 58 | + case 0: // Stop |
|
| 59 | + leftSpeed = rightSpeed = 0; |
|
| 60 | + break; |
|
| 61 | + case 1: // Forward |
|
| 62 | + leftSpeed = rightSpeed = compensatedSpeed; |
|
| 63 | + break; |
|
| 64 | + case 2: // Backward |
|
| 65 | + leftSpeed = rightSpeed = -compensatedSpeed; |
|
| 66 | + break; |
|
| 67 | + case 3: // Spin Left |
|
| 68 | + leftSpeed = -compensatedSpeed; |
|
| 69 | + rightSpeed = compensatedSpeed; |
|
| 70 | + break; |
|
| 71 | + case 4: // Spin Right |
|
| 72 | + leftSpeed = compensatedSpeed; |
|
| 73 | + rightSpeed = -compensatedSpeed; |
|
| 74 | + break; |
|
| 75 | + case 5: // Forward Left (Curve) - Swapped |
|
| 76 | + leftSpeed = compensatedSpeed + compensatedTurnDiff; |
|
| 77 | + rightSpeed = compensatedSpeed - compensatedTurnDiff; |
|
| 78 | + break; |
|
| 79 | + case 6: // Forward Right (Curve) - Swapped |
|
| 80 | + leftSpeed = compensatedSpeed - compensatedTurnDiff; |
|
| 81 | + rightSpeed = compensatedSpeed + compensatedTurnDiff; |
|
| 82 | + break; |
|
| 83 | + case 7: // Backward Left (Curve) |
|
| 84 | + leftSpeed = -(compensatedSpeed + compensatedTurnDiff); |
|
| 85 | + rightSpeed = -(compensatedSpeed - compensatedTurnDiff); |
|
| 86 | + break; |
|
| 87 | + case 8: // Backward Right (Curve) |
|
| 88 | + leftSpeed = -(compensatedSpeed - compensatedTurnDiff); |
|
| 89 | + rightSpeed = -(compensatedSpeed + compensatedTurnDiff); |
|
| 90 | + break; |
|
| 91 | + default: |
|
| 92 | + leftSpeed = rightSpeed = 0; |
|
| 93 | + break; |
|
| 94 | + } |
|
| 95 | + |
|
| 96 | + // Apply Left Motor |
|
| 97 | + int absL = abs(leftSpeed); |
|
| 98 | + int finalL = normalizeSpeed(absL); |
|
| 99 | + if (leftSpeed > 0) { |
|
| 100 | + analogWrite(M1_IN1, finalL); |
|
| 101 | + digitalWrite(M1_IN2, LOW); |
|
| 102 | + } else if (leftSpeed < 0) { |
|
| 103 | + digitalWrite(M1_IN1, LOW); |
|
| 104 | + analogWrite(M1_IN2, finalL); |
|
| 105 | + } else { |
|
| 106 | + digitalWrite(M1_IN1, LOW); |
|
| 107 | + digitalWrite(M1_IN2, LOW); |
|
| 108 | + } |
|
| 109 | + |
|
| 110 | + // Apply Right Motor |
|
| 111 | + int absR = abs(rightSpeed); |
|
| 112 | + int finalR = normalizeSpeed(absR); |
|
| 113 | + if (rightSpeed > 0) { |
|
| 114 | + analogWrite(M2_IN1, finalR); |
|
| 115 | + digitalWrite(M2_IN2, LOW); |
|
| 116 | + } else if (rightSpeed < 0) { |
|
| 117 | + digitalWrite(M2_IN1, LOW); |
|
| 118 | + analogWrite(M2_IN2, finalR); |
|
| 119 | + } else { |
|
| 120 | + digitalWrite(M2_IN1, LOW); |
|
| 121 | + digitalWrite(M2_IN2, LOW); |
|
| 122 | + } |
|
| 123 | +} |
|
| 124 | + |
|
| 125 | +String pageRoot() |
|
| 126 | +{ |
|
| 127 | + String html = "<html><head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no\">"; |
|
| 128 | + html += "<style>"; |
|
| 129 | + html += "body { font-family: sans-serif; text-align: center; display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100vh; margin: 0; background: #f0f0f0; }"; |
|
| 130 | + html += ".btn { width: 80px; height: 80px; margin: 10px; border-radius: 15px; border: none; background: #3498db; color: white; font-size: 24px; font-weight: bold; cursor: pointer; user-select: none; -webkit-tap-highlight-color: transparent; box-shadow: 0 4px #2980b9; }"; |
|
| 131 | + html += ".btn:active { background: #2980b9; box-shadow: 0 2px #1c5982; transform: translateY(2px); }"; |
|
| 132 | + html += ".stop-btn { background: #e74c3c; box-shadow: 0 4px #c0392b; }"; |
|
| 133 | + html += ".stop-btn:active { background: #c0392b; box-shadow: 0 2px #962d22; }"; |
|
| 134 | + html += ".row { display: flex; justify-content: center; }"; |
|
| 135 | + html += ".speed-container { margin-top: 20px; width: 80%; }"; |
|
| 136 | + html += "input[type=range] { width: 100%; height: 25px; cursor: pointer; }"; |
|
| 137 | + html += "select { padding: 8px; font-size: 16px; border-radius: 5px; }"; |
|
| 138 | + html += "</style></head><body>"; |
|
| 139 | + html += "<h2>Motor Control</h2>"; |
|
| 140 | + |
|
| 141 | + auto btnHtml = [&](int act, String icon) { |
|
| 142 | + String s_act = String(act); |
|
| 143 | + return " <button class='btn' onmousedown=\"setAction(" + s_act + ", event)\" ontouchstart=\"setAction(" + s_act + ", event)\" " |
|
| 144 | + "onmouseup=\"setAction(0, event)\" onmouseleave=\"setAction(0, event)\" ontouchend=\"setAction(0, event)\" ontouchcancel=\"setAction(0, event)\">" + icon + "</button>"; |
|
| 145 | + }; |
|
| 146 | + |
|
| 147 | + html += "<div class='row'>"; |
|
| 148 | + html += btnHtml(5, "↖"); |
|
| 149 | + html += btnHtml(1, "↑"); |
|
| 150 | + html += btnHtml(6, "↗"); |
|
| 151 | + html += "</div>"; |
|
| 152 | + |
|
| 153 | + html += "<div class='row'>"; |
|
| 154 | + html += btnHtml(3, "←"); |
|
| 155 | + html += " <button class='btn stop-btn' onclick=\"setAction(0, event)\" ontouchstart=\"setAction(0, event)\">OFF</button>"; |
|
| 156 | + html += btnHtml(4, "→"); |
|
| 157 | + html += "</div>"; |
|
| 158 | + |
|
| 159 | + html += "<div class='row'>"; |
|
| 160 | + html += btnHtml(7, "↙"); |
|
| 161 | + html += btnHtml(2, "↓"); |
|
| 162 | + html += btnHtml(8, "↘"); |
|
| 163 | + html += "</div>"; |
|
| 164 | + |
|
| 165 | + html += "<div class='speed-container'>"; |
|
| 166 | + html += " Speed: <span id='v'>" + String(carSpeed) + "</span>"; |
|
| 167 | + html += " <input type='range' id='s' min='100' max='255' value='" + String(carSpeed) + "' oninput='setSpeed(this.value)' />"; |
|
| 168 | + html += "</div>"; |
|
| 169 | + |
|
| 170 | + html += "<div class='speed-container'>"; |
|
| 171 | + html += " Battery Mode: <select onchange=\"setBattery(this.value)\">"; |
|
| 172 | + html += " <option value='2'" + String(batteryS == 2 ? " selected" : "") + ">2S (7.4V)</option>"; |
|
| 173 | + html += " <option value='3'" + String(batteryS == 3 ? " selected" : "") + ">3S (11.1V)</option>"; |
|
| 174 | + html += " </select>"; |
|
| 175 | + html += "</div>"; |
|
| 176 | + |
|
| 177 | + html += "<script>"; |
|
| 178 | + html += "let lastA = -1; let lastS = -1;"; |
|
| 179 | + html += "function setAction(a, e, f) { if(e) { e.preventDefault(); } if(!f && a == lastA && a != 0) return; lastA = a; fetch('/set?action=' + a).catch(e => console.error(e)); }"; |
|
| 180 | + html += "function setSpeed(s) { if(s == lastS) return; lastS = s; document.getElementById('v').innerText = s; fetch('/set?speed=' + s).catch(e => console.error(e)); }"; |
|
| 181 | + html += "function setBattery(b) { fetch('/set?batt=' + b); }"; |
|
| 182 | + html += "setInterval(() => { if(lastA > 0) setAction(lastA, null, true); }, 100);"; // "Pro" agility: 100ms heartbeat |
|
| 183 | + html += "</script></body></html>"; |
|
| 184 | + return html; |
|
| 185 | +} |
|
| 186 | + |
|
| 187 | +void handleRoot() |
|
| 188 | +{ |
|
| 189 | + server.send(200, "text/html", pageRoot()); |
|
| 190 | +} |
|
| 191 | + |
|
| 192 | +void handleSet() |
|
| 193 | +{ |
|
| 194 | + server.client().setNoDelay(true); // Disable Nagle's algorithm for faster response |
|
| 195 | + lastCommandTime = millis(); // Refresh safety watchdog |
|
| 196 | + if (server.hasArg("action")) |
|
| 197 | + { |
|
| 198 | + carAction = server.arg("action").toInt(); |
|
| 199 | + } |
|
| 200 | + if (server.hasArg("speed")) |
|
| 201 | + { |
|
| 202 | + carSpeed = server.arg("speed").toInt(); |
|
| 203 | + carSpeed = constrain(carSpeed, 0, 255); |
|
| 204 | + } |
|
| 205 | + if (server.hasArg("batt")) |
|
| 206 | + { |
|
| 207 | + batteryS = server.arg("batt").toInt(); |
|
| 208 | + } |
|
| 209 | + applyMotorControl(); |
|
| 210 | + server.send(200, "text/plain", "OK"); |
|
| 211 | +} |
|
| 212 | + |
|
| 213 | +void handleStatus() |
|
| 214 | +{ |
|
| 215 | + String status = "Action: " + String(carAction) + ", Speed: " + String(carSpeed) + ", Batt: " + String(batteryS) + "S"; |
|
| 216 | + server.send(200, "text/plain", status); |
|
| 217 | +} |
|
| 218 | + |
|
| 219 | +void setup() |
|
| 220 | +{ |
|
| 221 | + // Initialize pins |
|
| 222 | + pinMode(M1_IN1, OUTPUT); |
|
| 223 | + pinMode(M1_IN2, OUTPUT); |
|
| 224 | + pinMode(M2_IN1, OUTPUT); |
|
| 225 | + pinMode(M2_IN2, OUTPUT); |
|
| 226 | + |
|
| 227 | + // Initialize motors to off |
|
| 228 | + digitalWrite(M1_IN1, LOW); |
|
| 229 | + digitalWrite(M1_IN2, LOW); |
|
| 230 | + digitalWrite(M2_IN1, LOW); |
|
| 231 | + digitalWrite(M2_IN2, LOW); |
|
| 232 | + |
|
| 233 | + // Set PWM frequency for DRV8871 (supports up to 200kHz, but 20kHz is standard for silence and efficiency) |
|
| 234 | + analogWriteFreq(20000); |
|
| 235 | + |
|
| 236 | + Serial.begin(115200); |
|
| 237 | + delay(100); |
|
| 238 | + |
|
| 239 | + Serial.println("Test..."); |
|
| 240 | + delay(1000); |
|
| 241 | + Serial.println("Test..."); |
|
| 242 | + delay(1000); |
|
| 243 | + Serial.println("Test..."); |
|
| 244 | + delay(1000); |
|
| 245 | + // Configure AP with fixed IP |
|
| 246 | + WiFi.softAPConfig(apIP, apIP, netMsk); |
|
| 247 | + WiFi.softAP(ssid, password); |
|
| 248 | + |
|
| 249 | + IPAddress myIP = WiFi.softAPIP(); |
|
| 250 | + Serial.print("AP IP address: "); |
|
| 251 | + Serial.println(myIP); |
|
| 252 | + |
|
| 253 | + // Configure server routes |
|
| 254 | + server.on("/", handleRoot); |
|
| 255 | + server.on("/set", handleSet); |
|
| 256 | + server.on("/status", handleStatus); |
|
| 257 | + server.begin(); |
|
| 258 | + Serial.println("HTTP server started"); |
|
| 259 | + |
|
| 260 | + // Ensure PWM range 0-255 |
|
| 261 | + analogWriteRange(255); |
|
| 262 | + |
|
| 263 | + // Apply initial motor state |
|
| 264 | + applyMotorControl(); |
|
| 265 | +} |
|
| 266 | + |
|
| 267 | +void loop() |
|
| 268 | +{ |
|
| 269 | + server.handleClient(); |
|
| 270 | + |
|
| 271 | + // Safety Watchdog: Stop motors if no command received for > 1 second |
|
| 272 | + if (carAction != 0 && (millis() - lastCommandTime > SAFETY_TIMEOUT)) { |
|
| 273 | + carAction = 0; |
|
| 274 | + applyMotorControl(); |
|
| 275 | + Serial.println("Safety Timeout: Motors stopped"); |
|
| 276 | + } |
|
| 277 | + |
|
| 278 | + delay(10); |
|
| 279 | +} |
|
| 280 | + |
|
| 281 | + |
|
| 282 | + |
|
| 283 | + |
code-dat/projects/ESP8266-WIFI-motor-1/features.md
| ... | ... | @@ -0,0 +1,32 @@ |
| 1 | +# ESP8266 WiFi Motor Chassis Features |
|
| 2 | + |
|
| 3 | +## 1. Directional Control (9-Way) |
|
| 4 | +- **8 Directions**: Full support for Forward, Backward, Left, Right, and all four diagonal directions (Forward-Left, Forward-Right, Backward-Left, Backward-Right). |
|
| 5 | +- **Dedicated Stop**: Immediate halt on button release and a centralized "OFF" emergency stop button. |
|
| 6 | + |
|
| 7 | +## 2. Advanced Motor Driving |
|
| 8 | +- **Differential Steering**: Implements $Base \pm Offset$ logic for smooth, proportional turning during diagonal movement and pivot rotation. |
|
| 9 | +- **Stall Protection**: Enforces a `minSpeed` floor (100 PWM) to ensure motors maintain torque at low speeds. |
|
| 10 | +- **PWM Optimization**: Optimized for high-frequency (20kHz) PWM modulation for smooth speed control. |
|
| 11 | + |
|
| 12 | +## 3. Safety & Reliability |
|
| 13 | +- **Software Watchdog**: A hardware-safe timeout system stops the motors if no command is received within **500ms**, preventing runaway cars in case of WiFi disconnect. |
|
| 14 | +- **Active Heartbeat**: The client browser sends a "keep-alive" signal every **200ms** while any button is held, ensuring seamless motion through minor signal jitters. |
|
| 15 | +- **Interface Sanity**: Prevents "ghost" presses with event `preventDefault()` and stops motors automatically if the browser window loses focus (`blur` event). |
|
| 16 | + |
|
| 17 | +## 4. Power Management |
|
| 18 | +- **Battery Compensation**: Selectable modes for **2S (7.4V)** and **3S (11.1V)** battery packs. |
|
| 19 | +- **Voltage Scaling**: Automatically reduces PWM duty cycle in 3S mode (scaling factor $\approx 0.66$) to match 2S speed levels and protect motors from over-voltage. |
|
| 20 | + |
|
| 21 | +## 5. Web Interface & Connectivity |
|
| 22 | +- **Standalone Hotspot**: Creates its own WiFi Access Point (`MotorAP`) for direct control without an external router. |
|
| 23 | +- **Responsive Web UI**: A mobile-first controller interface with large touch-friendly buttons and a real-time speed slider. |
|
| 24 | +- **Low Latency**: Lightweight HTTP GET handlers for near-instant control responses. |
|
| 25 | + |
|
| 26 | +## 6. Technical Specifications (Default) |
|
| 27 | +- **Controller**: ESP8266 |
|
| 28 | +- **Motor Pins**: |
|
| 29 | + - Left (M1): Pins 4, 5 |
|
| 30 | + - Right (M2): Pins 0, 2 |
|
| 31 | +- **Default Speed**: 200/255 |
|
| 32 | +- **Safety Window**: 500ms |