Arduino WebJoystick Example Hướng Dẫn Điều Khiển Joystick Ảo

Tổng Quan

Ví dụ WebJoystick tạo ra giao diện joystick ảo có thể truy cập từ bất kỳ trình duyệt web nào. Được thiết kế cho Arduino Uno R4 WiFi và nền tảng giáo dục DIYables STEM V4 IoT với khả năng robotics nâng cao, tính năng điều khiển động cơ tích hợp, và tích hợp liền mạch với các module giáo dục robotics. Hoàn hảo để điều khiển robot, xe cộ, hoặc bất kỳ hệ thống nào yêu cầu đầu vào vị trí 2D.

Arduino webjoystick example - hướng dẫn Điều khiển joystick Ảo

Tính Năng

  • Joystick Ảo: Điều khiển joystick tương tác qua giao diện web
  • Tọa Độ Thời Gian Thực: Giá trị X/Y từ -100 đến +100 để điều khiển chính xác
  • Hỗ Trợ Touch & Mouse: Hoạt động trên desktop, tablet và thiết bị di động
  • Auto-Return Có Thể Cấu Hình: Tùy chọn cho joystick quay về giữa khi thả ra
  • Điều Khiển Độ Nhạy: Độ nhạy có thể điều chỉnh để tránh cập nhật quá mức
  • Phản Hồi Trực Quan: Hiển thị vị trí thời gian thực và giá trị tọa độ
  • Giao Tiếp WebSocket: Phản hồi tức thì không cần làm mới trang
  • Vị Trí Trung Tâm: Chỉ báo vị trí trung tâm rõ ràng cho điều khiển trung tính
  • Có Thể Mở Rộng Nền Tảng: Hiện tại được triển khai cho Arduino Uno R4 WiFi, nhưng có thể mở rộng cho các nền tảng phần cứng khác. Xem DIYables_WebApps_ESP32

Phần Cứng Cần Thiết

1×Arduino UNO R4 WiFi
1×Alternatively, DIYables STEM V4 IoT
1×(Tùy chọn) DIYables STEM V4 IoT
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×(Khuyến nghị) Screw Terminal Block Shield for Arduino UNO R4
1×(Khuyến nghị) Breadboard Shield for Arduino UNO R4
1×(Khuyến nghị) Enclosure for Arduino UNO R4
1×(Khuyến nghị) Power Splitter for Arduino UNO R4
1×(Khuyến nghị) Prototyping Base Plate & Breadboard Kit for Arduino UNO

Or you can buy the following kits:

1×DIYables STEM V4 IoT Starter Kit (Arduino included)
1×DIYables Sensor Kit (30 sensors/displays)
1×DIYables Sensor Kit (18 sensors/displays)

Hướng Dẫn Cài Đặt

Các Bước Nhanh

Thực hiện theo các hướng dẫn sau từng bước:

  • Nếu đây là lần đầu tiên bạn sử dụng Arduino Uno R4 WiFi/DIYables STEM V4 IoT, hãy tham khảo hướng dẫn về Arduino UNO R4 - Cài Đặt Phần Mềm.
  • Kết nối bo mạch Arduino Uno R4/DIYables STEM V4 IoT với máy tính của bạn bằng cáp USB.
  • Khởi chạy Arduino IDE trên máy tính của bạn.
  • Chọn bo mạch Arduino Uno R4 phù hợp (ví dụ: Arduino Uno R4 WiFi) và cổng COM.
  • Điều hướng đến biểu tượng Libraries trên thanh bên trái của Arduino IDE.
  • Tìm kiếm "DIYables WebApps", sau đó tìm thư viện DIYables WebApps bởi DIYables
  • Nhấp nút Install để cài đặt thư viện.
Arduino UNO R4 diyables webapps thư viện
  • Bạn sẽ được hỏi về việc cài đặt một số thư viện phụ thuộc khác
  • Nhấp nút Install All để cài đặt tất cả các thư viện phụ thuộc.
Arduino UNO R4 diyables webapps dependency
  • Trên Arduino IDE, vào File Examples DIYables WebApps WebJoystick example, hoặc sao chép đoạn code trên và dán vào editor của Arduino IDE
/* * DIYables WebApp Library - Web Joystick Example * * This example demonstrates the Web Joystick feature: * - Interactive joystick control via web interface * - Real-time X/Y coordinate values (-100 to +100) * - Control pins based on joystick position * * Hardware: Arduino Uno R4 WiFi or DIYables STEM V4 IoT * * Setup: * 1. Update WiFi credentials below * 2. Upload the sketch to your Arduino * 3. Open Serial Monitor to see the IP address * 4. Navigate to http://[IP_ADDRESS]/webjoystick */ #include <DIYablesWebApps.h> // WiFi credentials - UPDATE THESE WITH YOUR NETWORK const char WIFI_SSID[] = "YOUR_WIFI_SSID"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD"; // Create WebApp server and page instances // MEMORY SAFETY FIX: Use static factory to avoid stack object lifetime issues static UnoR4ServerFactory serverFactory; // Static ensures lifetime matches program DIYablesWebAppServer webAppsServer(serverFactory, 80, 81); DIYablesHomePage homePage; // Configure joystick with autoReturn=false and sensitivity=5 (minimum 5% change to trigger updates) DIYablesWebJoystickPage webJoystickPage(false, 5); // Variables to store current joystick values int currentJoystickX = 0; int currentJoystickY = 0; void setup() { Serial.begin(9600); delay(1000); // TODO: initialize your hardware pins here Serial.println("DIYables WebApp - Web Joystick Example"); // Add home and web joystick pages webAppsServer.addApp(&homePage); webAppsServer.addApp(&webJoystickPage); // Optional: Add 404 page for better user experience webAppsServer.setNotFoundPage(DIYablesNotFoundPage()); // Start the WebApp server if (!webAppsServer.begin(WIFI_SSID, WIFI_PASSWORD)) { while (1) { Serial.println("Failed to start WebApp server!"); delay(1000); } } // Set up joystick callback for position changes webJoystickPage.onJoystickValueFromWeb([](int x, int y) { // Store the received values currentJoystickX = x; currentJoystickY = y; // Print joystick position values (-100 to +100) Serial.println("Joystick - X: " + String(x) + ", Y: " + String(y)); // TODO: Add your control logic here based on joystick position // Examples: // - Control motors: if (x > 50) { /* move right */ } // - Control servos: servo.write(map(y, -100, 100, 0, 180)); // - Control LEDs: analogWrite(LED_PIN, map(abs(x), 0, 100, 0, 255)); // - Send commands to other devices via Serial, I2C, SPI, etc. }); // Optional: Handle requests for current joystick values (when web page loads) webJoystickPage.onJoystickValueToWeb([]() { // Send the stored joystick values back to the web client webJoystickPage.sendToWebJoystick(currentJoystickX, currentJoystickY); Serial.println("Web client requested values - Sent to Web: X=" + String(currentJoystickX) + ", Y=" + String(currentJoystickY)); }); // You can change configuration at runtime: // webJoystickPage.setAutoReturn(false); // Disable auto-return // webJoystickPage.setSensitivity(10.0); // Only send updates when joystick moves >10% (less sensitive) } void loop() { // Handle WebApp server communications webAppsServer.loop(); // TODO: Add your main application code here delay(10); }
  • Cấu hình thông tin đăng nhập WiFi trong code bằng cách cập nhật các dòng này:
const char WIFI_SSID[] = "YOUR_WIFI_NETWORK"; const char WIFI_PASSWORD[] = "YOUR_WIFI_PASSWORD";
  • Nhấp nút Upload trên Arduino IDE để upload code lên Arduino UNO R4/DIYables STEM V4 IoT
  • Mở Serial Monitor
  • Kiểm tra kết quả trên Serial Monitor. Nó trông như bên dưới
COM6
Send
DIYables WebApp - Web Joystick Example INFO: Added app / INFO: Added app /web-joystick DIYables WebApp Library Platform: Arduino Uno R4 WiFi Network connected! IP address: 192.168.0.2 HTTP server started on port 80 Configuring WebSocket server callbacks... WebSocket server started on port 81 WebSocket URL: ws://192.168.0.2:81 WebSocket server started on port 81 ========================================== DIYables WebApp Ready! ========================================== 📱 Web Interface: http://192.168.0.2 🔗 WebSocket: ws://192.168.0.2:81 📋 Available Applications: 🏠 Home Page: http://192.168.0.2/ 🕹️ Web Joystick: http://192.168.0.2/web-joystick ==========================================
Autoscroll Show timestamp
Clear output
9600 baud  
Newline  
  • Nếu bạn không thấy gì, hãy khởi động lại bo mạch Arduino.
  • Ghi chú địa chỉ IP được hiển thị, và nhập địa chỉ này vào thanh địa chỉ của trình duyệt web trên smartphone hoặc PC của bạn.
  • Ví dụ: http://192.168.0.2
  • Bạn sẽ thấy trang chủ như hình dưới đây:
Arduino UNO R4 diyables webapp home page with web joystick app
  • Nhấp vào liên kết Web Joystick, bạn sẽ thấy giao diện của ứng dụng Web Joystick như bên dưới:
Arduino UNO R4 diyables webapp web joystick app
  • Hoặc bạn cũng có thể truy cập trực tiếp trang bằng địa chỉ IP theo sau bởi /web-joystick. Ví dụ: http://192.168.0.2/web-joystick
  • Thử điều khiển joystick ảo bằng cách nhấp và kéo trên khu vực joystick và quan sát các giá trị tọa độ X/Y (-100 đến +100) trong Serial Monitor.

Tùy Chỉnh Sáng Tạo - Điều Chỉnh Code Cho Dự Án Của Bạn

2. Cấu Hình Cài Đặt Joystick

Joystick có thể được cấu hình với các tham số khác nhau:

Cấu Hình Cơ Bản

// Tạo joystick với cài đặt mặc định (autoReturn=true, sensitivity=1) DIYablesWebJoystickPage webJoystickPage;

Cấu Hình Nâng Cao

// Tạo joystick với cài đặt tùy chỉnh // autoReturn=false: Joystick giữ nguyên vị trí cuối khi thả ra // sensitivity=5: Chỉ gửi cập nhật khi chuyển động > 5% DIYablesWebJoystickPage webJoystickPage(false, 5);

Cách Sử Dụng Joystick

Điều Khiển Giao Diện Web

Giao diện joystick cung cấp:

  • Bảng Joystick: Khu vực điều khiển hình tròn cho đầu vào touch/mouse
  • Chỉ Báo Vị Trí: Hiển thị vị trí joystick hiện tại
  • Hiển Thị Tọa Độ: Giá trị X/Y thời gian thực (-100 đến +100)
  • Điểm Trung Tâm: Tham chiếu trực quan cho điều khiển trung tính

Vận Hành Joystick

Desktop (Điều Khiển Mouse)

  1. Click và Kéo: Click vào joystick và kéo để di chuyển
  2. Thả: Joystick quay về trung tâm (nếu autoReturn=true)
  3. Click Vị Trí: Click trực tiếp để đặt vị trí joystick

Mobile/Tablet (Điều Khiển Touch)

  1. Touch và Kéo: Chạm vào joystick và kéo ngón tay để di chuyển
  2. Multi-touch: Một ngón tay cho điều khiển chính xác
  3. Thả: Tự động quay về trung tâm (nếu được bật)

Hệ Tọa Độ

Joystick cung cấp tọa độ trong hệ tọa độ Cartesian tiêu chuẩn:

  • Trục X: -100 (hoàn toàn trái) đến +100 (hoàn toàn phải)
  • Trục Y: -100 (hoàn toàn xuống) đến +100 (hoàn toàn lên)
  • Trung Tâm: X=0, Y=0 (vị trí trung tính)
  • Góc: Vị trí chéo cung cấp giá trị X/Y kết hợp

Ví Dụ Lập Trình

Handler Joystick Cơ Bản

void setup() { // Thiết lập callback joystick cho thay đổi vị trí webJoystickPage.onJoystickValueFromWeb([](int x, int y) { // Lưu trữ các giá trị nhận được currentJoystickX = x; currentJoystickY = y; // In giá trị vị trí joystick Serial.println("Joystick - X: " + String(x) + ", Y: " + String(y)); // Thêm logic điều khiển của bạn ở đây }); }

Ví Dụ Điều Khiển Động Cơ

// Định nghĩa pin cho driver động cơ const int MOTOR_LEFT_PIN1 = 2; const int MOTOR_LEFT_PIN2 = 3; const int MOTOR_RIGHT_PIN1 = 4; const int MOTOR_RIGHT_PIN2 = 5; const int MOTOR_LEFT_PWM = 9; const int MOTOR_RIGHT_PWM = 10; void setup() { // Cấu hình các pin động cơ pinMode(MOTOR_LEFT_PIN1, OUTPUT); pinMode(MOTOR_LEFT_PIN2, OUTPUT); pinMode(MOTOR_RIGHT_PIN1, OUTPUT); pinMode(MOTOR_RIGHT_PIN2, OUTPUT); pinMode(MOTOR_LEFT_PWM, OUTPUT); pinMode(MOTOR_RIGHT_PWM, OUTPUT); webJoystickPage.onJoystickValueFromWeb([](int x, int y) { controlRobot(x, y); }); } void controlRobot(int x, int y) { // Chuyển đổi tọa độ joystick thành tốc độ động cơ int leftSpeed = y + x; // Tiến/lùi + rẽ trái/phải int rightSpeed = y - x; // Tiến/lùi - rẽ trái/phải // Giới hạn tốc độ trong phạm vi hợp lệ leftSpeed = constrain(leftSpeed, -100, 100); rightSpeed = constrain(rightSpeed, -100, 100); // Điều khiển động cơ trái if (leftSpeed > 0) { digitalWrite(MOTOR_LEFT_PIN1, HIGH); digitalWrite(MOTOR_LEFT_PIN2, LOW); analogWrite(MOTOR_LEFT_PWM, map(leftSpeed, 0, 100, 0, 255)); } else if (leftSpeed < 0) { digitalWrite(MOTOR_LEFT_PIN1, LOW); digitalWrite(MOTOR_LEFT_PIN2, HIGH); analogWrite(MOTOR_LEFT_PWM, map(-leftSpeed, 0, 100, 0, 255)); } else { digitalWrite(MOTOR_LEFT_PIN1, LOW); digitalWrite(MOTOR_LEFT_PIN2, LOW); analogWrite(MOTOR_LEFT_PWM, 0); } // Điều khiển động cơ phải if (rightSpeed > 0) { digitalWrite(MOTOR_RIGHT_PIN1, HIGH); digitalWrite(MOTOR_RIGHT_PIN2, LOW); analogWrite(MOTOR_RIGHT_PWM, map(rightSpeed, 0, 100, 0, 255)); } else if (rightSpeed < 0) { digitalWrite(MOTOR_RIGHT_PIN1, LOW); digitalWrite(MOTOR_RIGHT_PIN2, HIGH); analogWrite(MOTOR_RIGHT_PWM, map(-rightSpeed, 0, 100, 0, 255)); } else { digitalWrite(MOTOR_RIGHT_PIN1, LOW); digitalWrite(MOTOR_RIGHT_PIN2, LOW); analogWrite(MOTOR_RIGHT_PWM, 0); } }

Ví Dụ Điều Khiển Servo

#include <Servo.h> Servo panServo; // Điều khiển trục X (trái/phải) Servo tiltServo; // Điều khiển trục Y (lên/xuống) void setup() { // Gắn servo vào các pin panServo.attach(9); tiltServo.attach(10); // Đặt servo về giữa ban đầu panServo.write(90); tiltServo.write(90); webJoystickPage.onJoystickValueFromWeb([](int x, int y) { controlServos(x, y); }); } void controlServos(int x, int y) { // Chuyển đổi tọa độ joystick thành góc servo int panAngle = map(x, -100, 100, 0, 180); // X điều khiển pan (0-180°) int tiltAngle = map(y, -100, 100, 180, 0); // Y điều khiển tilt (đảo ngược) // Di chuyển servo đến vị trí được tính toán panServo.write(panAngle); tiltServo.write(tiltAngle); Serial.println("Pan: " + String(panAngle) + "°, Tilt: " + String(tiltAngle) + "°"); }

Chỉ Báo Vị Trí LED

// Các pin LED để chỉ báo vị trí const int LED_UP = 2; const int LED_DOWN = 3; const int LED_LEFT = 4; const int LED_RIGHT = 5; const int LED_CENTER = 13; void setup() { // Cấu hình các pin LED pinMode(LED_UP, OUTPUT); pinMode(LED_DOWN, OUTPUT); pinMode(LED_LEFT, OUTPUT); pinMode(LED_RIGHT, OUTPUT); pinMode(LED_CENTER, OUTPUT); webJoystickPage.onJoystickValueFromWeb([](int x, int y) { updateLEDIndicators(x, y); }); } void updateLEDIndicators(int x, int y) { // Tắt tất cả LED trước digitalWrite(LED_UP, LOW); digitalWrite(LED_DOWN, LOW); digitalWrite(LED_LEFT, LOW); digitalWrite(LED_RIGHT, LOW); digitalWrite(LED_CENTER, LOW); // Kiểm tra nếu joystick gần trung tâm if (abs(x) < 10 && abs(y) < 10) { digitalWrite(LED_CENTER, HIGH); return; } // Kích hoạt LED dựa trên hướng if (y > 20) digitalWrite(LED_UP, HIGH); if (y < -20) digitalWrite(LED_DOWN, HIGH); if (x > 20) digitalWrite(LED_RIGHT, HIGH); if (x < -20) digitalWrite(LED_LEFT, HIGH); }

Cấu Hình Nâng Cao

Thay Đổi Cấu Hình Runtime

void setup() { // Thiết lập ban đầu với giá trị mặc định webJoystickPage.onJoystickValueFromWeb([](int x, int y) { handleJoystickInput(x, y); }); // Thay đổi cài đặt tại runtime webJoystickPage.setAutoReturn(false); // Tắt auto-return webJoystickPage.setSensitivity(10.0); // Giảm độ nhạy } void handleJoystickInput(int x, int y) { // Xử lý các chế độ khác nhau dựa trên cài đặt hiện tại static bool precisionMode = false; // Chuyển chế độ chính xác với vị trí cực đoan if (abs(x) > 95 && abs(y) > 95) { precisionMode = !precisionMode; if (precisionMode) { webJoystickPage.setSensitivity(1.0); // Độ nhạy cao Serial.println("Chế độ chính xác BẬT"); } else { webJoystickPage.setSensitivity(10.0); // Độ nhạy thấp Serial.println("Chế độ chính xác TẮT"); } } }

Triển Khai Dead Zone

void processJoystickWithDeadZone(int x, int y) { const int DEAD_ZONE = 15; // 15% dead zone xung quanh trung tâm // Áp dụng lọc dead zone int filteredX = (abs(x) < DEAD_ZONE) ? 0 : x; int filteredY = (abs(y) < DEAD_ZONE) ? 0 : y; // Tỷ lệ giá trị ngoài dead zone if (filteredX != 0) { filteredX = map(abs(filteredX), DEAD_ZONE, 100, 0, 100); filteredX = (x < 0) ? -filteredX : filteredX; } if (filteredY != 0) { filteredY = map(abs(filteredY), DEAD_ZONE, 100, 0, 100); filteredY = (y < 0) ? -filteredY : filteredY; } // Sử dụng giá trị đã lọc để điều khiển controlDevice(filteredX, filteredY); }

Ramping Tốc Độ

class SpeedController { private: int targetX = 0, targetY = 0; int currentX = 0, currentY = 0; unsigned long lastUpdate = 0; const int RAMP_RATE = 5; // Thay đổi mỗi chu kỳ cập nhật public: void setTarget(int x, int y) { targetX = x; targetY = y; } void update() { if (millis() - lastUpdate > 20) { // Cập nhật mỗi 20ms // Ramp giá trị X if (currentX < targetX) { currentX = min(currentX + RAMP_RATE, targetX); } else if (currentX > targetX) { currentX = max(currentX - RAMP_RATE, targetX); } // Ramp giá trị Y if (currentY < targetY) { currentY = min(currentY + RAMP_RATE, targetY); } else if (currentY > targetY) { currentY = max(currentY - RAMP_RATE, targetY); } // Áp dụng giá trị ramped applyControlValues(currentX, currentY); lastUpdate = millis(); } } void applyControlValues(int x, int y) { // Logic điều khiển của bạn ở đây với giá trị ramped mượt Serial.println("Ramped - X: " + String(x) + ", Y: " + String(y)); } }; SpeedController speedController; void setup() { webJoystickPage.onJoystickValueFromWeb([](int x, int y) { speedController.setTarget(x, y); }); } void loop() { server.loop(); speedController.update(); // Áp dụng speed ramping }

Ví Dụ Tích Hợp Phần Cứng

Điều Khiển Xe Robot

void setupRobotCar() { // Các pin driver động cơ pinMode(2, OUTPUT); // Hướng động cơ trái 1 pinMode(3, OUTPUT); // Hướng động cơ trái 2 pinMode(4, OUTPUT); // Hướng động cơ phải 1 pinMode(5, OUTPUT); // Hướng động cơ phải 2 pinMode(9, OUTPUT); // PWM động cơ trái pinMode(10, OUTPUT); // PWM động cơ phải webJoystickPage.onJoystickValueFromWeb([](int x, int y) { // Tính toán điều khiển kiểu tank int leftMotor = y + (x / 2); // Tiến/lùi + lái int rightMotor = y - (x / 2); // Tiến/lùi - lái // Giới hạn trong phạm vi hợp lệ leftMotor = constrain(leftMotor, -100, 100); rightMotor = constrain(rightMotor, -100, 100); // Điều khiển động cơ setMotorSpeed(9, 2, 3, leftMotor); // Động cơ trái setMotorSpeed(10, 4, 5, rightMotor); // Động cơ phải }); } void setMotorSpeed(int pwmPin, int dir1Pin, int dir2Pin, int speed) { if (speed > 0) { digitalWrite(dir1Pin, HIGH); digitalWrite(dir2Pin, LOW); analogWrite(pwmPin, map(speed, 0, 100, 0, 255)); } else if (speed < 0) { digitalWrite(dir1Pin, LOW); digitalWrite(dir2Pin, HIGH); analogWrite(pwmPin, map(-speed, 0, 100, 0, 255)); } else { digitalWrite(dir1Pin, LOW); digitalWrite(dir2Pin, LOW); analogWrite(pwmPin, 0); } }

Điều Khiển Gimbal Camera

#include <Servo.h> Servo panServo, tiltServo; int panOffset = 90, tiltOffset = 90; // Vị trí trung tâm void setupCameraGimbal() { panServo.attach(9); tiltServo.attach(10); // Đặt vị trí trung tâm ban đầu panServo.write(panOffset); tiltServo.write(tiltOffset); webJoystickPage.onJoystickValueFromWeb([](int x, int y) { // Tính toán vị trí servo với offset int panPos = panOffset + map(x, -100, 100, -45, 45); // Phạm vi ±45° int tiltPos = tiltOffset + map(y, -100, 100, -30, 30); // Phạm vi ±30° // Giới hạn trong giới hạn servo panPos = constrain(panPos, 0, 180); tiltPos = constrain(tiltPos, 0, 180); // Di chuyển servo mượt mà panServo.write(panPos); tiltServo.write(tiltPos); }); }

Điều Khiển Màu LED RGB

const int RED_PIN = 9; const int GREEN_PIN = 10; const int BLUE_PIN = 11; void setupRGBControl() { pinMode(RED_PIN, OUTPUT); pinMode(GREEN_PIN, OUTPUT); pinMode(BLUE_PIN, OUTPUT); webJoystickPage.onJoystickValueFromWeb([](int x, int y) { // Chuyển đổi vị trí joystick thành giá trị RGB int red = map(abs(x), 0, 100, 0, 255); int green = map(abs(y), 0, 100, 0, 255); int blue = map(abs(x + y), 0, 141, 0, 255); // Khoảng cách chéo // Áp dụng trộn màu theo góc phần tư if (x > 0 && y > 0) { // Phía trên bên phải: Đỏ + Xanh lá = Vàng blue = 0; } else if (x < 0 && y > 0) { // Phía trên bên trái: Xanh lá + Xanh dương = Cyan red = 0; } else if (x < 0 && y < 0) { // Phía dưới bên trái: Xanh dương + Đỏ = Magenta green = 0; } else if (x > 0 && y < 0) { // Phía dưới bên phải: Chỉ đỏ green = blue = 0; } // Đặt giá trị RGB analogWrite(RED_PIN, red); analogWrite(GREEN_PIN, green); analogWrite(BLUE_PIN, blue); }); }