Traceur XY Piloté par Web avec Arduino et Contrôleur de Moteur Pas à Pas
Composants et fournitures
| Arduino UNO | × | 1 |
| PHPoC WiFi Shield 2 pour Arduino | × | 1 |
| Contrôleur de Moteur Pas à Pas PHPoC | × | 2 |
| Traceur XY Makeblock | × | 1 |
À propos de ce projet
Démonstration
Si vous débutez avec Arduino, vous pouvez commencer par :
- Tutoriels Arduino pour débutants.
- Tout sur les moteurs pour Arduino.
Fonctionnement
Lorsqu'un doigt touche la zone de dessin sur la page web, les coordonnées XY du point de contact sont envoyées à l'Arduino. Après mise à l'échelle des coordonnées, l'Arduino commande les deux moteurs pas à pas pour positionner le stylo à ces coordonnées. Pendant le déplacement, l'Arduino envoie en continu la trajectoire du stylo à l'application web, qui la dessine sur le canvas.
Code source
Le code source comprend deux parties : le code Arduino et le code de l'interface utilisateur web.
Code Arduino
Ce code s'exécute en boucle infinie pour :
- Recevoir les commandes de la page web et exécuter la tâche correspondante.
- CMD_MOVE : déplacer le stylo vers une position en contrôlant les deux moteurs pas à pas.
- CMD_PEN_UP : lever le stylo en modifiant l'angle du servomoteur.
- CMD_PEN_DOWN : baisser le stylo en modifiant l'angle du servomoteur.
- Lire en continu la position actuelle du stylo et l'envoyer à la page web.
Interface Utilisateur Web
- Fournir l'interface utilisateur.
- Gérer les événements utilisateur et envoyer les commandes avec coordonnées à l'Arduino.
- Recevoir la trajectoire de l'Arduino et la dessiner sur la page web.
remote_draw.php est le fichier contenant l'interface utilisateur web. Il doit être stocké sur le bouclier PHPoC [WiFi]. Pour téléverser le fichier sur le bouclier PHPoC [WiFi], suivez ces étapes :
- Copiez le code ci-dessous et enregistrez-le dans un fichier nommé
remote_draw.php. - Installez le PHPoC Debugger.
- Connectez le PHPoC au bouclier PHPoC [WiFi] via un câble micro-USB selon ces instructions. Notez que l'Arduino doit être alimenté.
- Téléversez le fichier
remote_draw.phpsur le bouclier PHPoC [WiFi] selon ces instructions.
Procédure
- Configurer les informations réseau pour le bouclier PHPoC ou PHPoC WiFi.
- Installer la bibliothèque PHPoC.
- Installer la bibliothèque PHPoC Expansion.
- Compiler et téléverser le code sur l'Arduino.
- Téléverser l'interface utilisateur web sur le bouclier PHPoC [WiFi].
- Ouvrir le Moniteur Série et copier l'adresse IP du bouclier PHPoC.
- Accéder à l'interface utilisateur web via un navigateur :
https://adresse_ip_du_bouclier/remote_draw.php. - Dessiner via le Web.
Pour acheter des composants électroniques, vous pouvez les commander sur utsource.net.
Le Meilleur Kit de Démarrage Arduino pour Débutant
Voir Le Meilleur Kit Arduino pour Débutants.
Références de Fonctions
Serial.begin()Serial.println()delay()millis()forloopwhileloopif elseloop()setup()String.toInt()String.substring()String.indexOf()String.remove()String.equals()
Code
Code Arduino
#include <Phpoc.h>
#include <PhpocExpansion.h>
#include <Servo.h>
#define MAX_X 55550 // unité : pas
#define MAX_Y 68780 // unité : pas
#define TOUCH_OFFSET 5000 // unité : pas
#define PEN_STATE_UP 0
#define PEN_STATE_DOWN 1
#define CMD_PEN_UP 0
#define CMD_PEN_DOWN 1
#define CMD_MOVE 2
#define STEP_MODE 32
#define SPEED_X_COEF ((long)40 * STEP_MODE)
#define SPEED_Y_COEF ((long)40 * STEP_MODE)
#define SPEED_X_MAX ((long)1500 * STEP_MODE)
#define SPEED_Y_MAX ((long)1500 * STEP_MODE)
#define ACCEL_X_MAX ((long)6000 * STEP_MODE)
#define ACCEL_Y_MAX ((long)6000 * STEP_MODE)
#define STEP_STATE_STOP 0
#define STEP_STATE_LOCK 1
#define RESOLUTION 500
#define MIN_UPDATE_INTERVAL 100 // en millisecondes
PhpocServer server(80);
ExpansionStepper stepX(14);
ExpansionStepper stepY(13);
Servo servo;
long preX = 0;
long preY = 0;
byte penState = PEN_STATE_UP;
bool isUnlockedX = false;
bool isUnlockedY = false;
int forwardDirX = -1; /* direction du traceur XY quand le moteur X avance, selon l'installation, à tester pour déterminer les valeurs */
int forwardDirY = +1; /* direction du traceur XY quand le moteur Y monte, selon l'installation, à tester pour déterminer les valeurs */
unsigned long lastUpdateMillis;
void penUp() {
servo.write(110);
penState = PEN_STATE_UP;
}
void penDown() {
servo.write(180);
penState = PEN_STATE_DOWN;
}
void xyWait() {
while(stepX.getState() > 1 || stepY.getState() > 1);
}
void xyInit() {
penUp();
stepX.setMode(STEP_MODE);
stepX.setVrefStop(4);
stepX.setVrefDrive(15);
stepX.setResonance(120, 250);
stepX.setSpeed(20000);
stepX.setAccel(50000);
stepY.setMode(STEP_MODE);
stepY.setVrefStop(4);
stepY.setVrefDrive(15);
stepY.setResonance(120, 250);
stepY.setSpeed(20000);
stepY.setAccel(50000);
// déplacer le stylo vers (0, 0)
stepX.stepGotoSW(0, -forwardDirX);
stepY.stepGotoSW(0, -forwardDirY);
xyWait();
stepX.setPosition(0);
stepY.setPosition(0);
// décommenter ce bloc pour la première exécution et modifier les valeurs ligne 6, 7 de ce fichier et ligne 33 de index.php selon les valeurs de la console IDE
/*
// vérifier le nombre max de pas
stepX.stepGotoSW(1, forwardDirX);
stepY.stepGotoSW(1, forwardDirY);
xyWait();
// modifier ces valeurs ligne 45 de index.php
Serial.print(F("MAX_X:"));
Serial.println(stepX.getPosition() * forwardDirX);
Serial.print(F("MAX_Y:"));
Serial.println(stepY.getPosition() * forwardDirY);
*/
xyGoto(TOUCH_OFFSET, TOUCH_OFFSET);
xyWait();
stepX.setEioMode(0, EIO_MODE_LOCK);
stepX.setEioMode(1, EIO_MODE_LOCK);
stepY.setEioMode(0, EIO_MODE_LOCK);
stepY.setEioMode(1, EIO_MODE_LOCK);
}
void xyGoto(long x, long y) {
if(x < TOUCH_OFFSET) x = TOUCH_OFFSET;
else if(x > (MAX_X - TOUCH_OFFSET)) x = MAX_X - TOUCH_OFFSET;
if(y < TOUCH_OFFSET) y = TOUCH_OFFSET;
else if(y > (MAX_Y - TOUCH_OFFSET)) y = MAX_Y - TOUCH_OFFSET;
x *= forwardDirX;
y *= forwardDirY;
int32_t deltaX = x - stepX.getPosition();
int32_t deltaY = y - stepY.getPosition();
deltaX = abs(deltaX);
deltaY = abs(deltaY);
long speedX = SPEED_X_COEF * abs(deltaX);
long speedY = SPEED_Y_COEF * abs(deltaY);
if(speedX > speedY) {
if(speedX > SPEED_X_MAX) speedX = SPEED_X_MAX;
double ratio = deltaY / (double)deltaX;
speedY = (long) (ratio * speedX);
} else {
if(speedY > SPEED_Y_MAX) speedY = SPEED_Y_MAX;
double ratio = deltaX / (double)deltaY;
speedX = (long) (ratio * speedY);
}
long accelX;
long accelY;
if(speedX < speedY) {
accelY = ACCEL_Y_MAX;
double ratio = accelY / (double)speedY;
accelX = (long) (ratio * speedX);
} else {
accelX = ACCEL_X_MAX;
double ratio = accelX / (double)speedX;
accelY = (long) (ratio * speedY);
}
if(deltaX != 0) stepX.command(F("goto %ld %lu %lu"), x, speedX, accelX);
if(deltaY != 0) stepY.command(F("goto %ld %lu %lu"), y, speedY, accelY);
}
void xyCheckUpdateToWeb() {
bool isUpdate = false;
unsigned long curMillis = millis();
if((curMillis - lastUpdateMillis) > MIN_UPDATE_INTERVAL) isUpdate = true;
long curX = stepX.getPosition();
long curY = stepY.getPosition();
long deltaX = curX - preX;
long deltaY = curY - preY;
long dist = sqrt(pow(deltaX, 2) + pow(deltaY, 2));
if(dist > RESOLUTION) isUpdate = true;
if(isUpdate == false || dist == 0) return false;
lastUpdateMillis = curMillis;
preX = curX;
preY = curY;
sendPositionToWeb(); // envoyer la position actuelle pour affichage sur le web
}
void sendPositionToWeb() {
char wbuf[20];
long x = stepX.getPosition() * forwardDirX;
long y = stepY.getPosition() * forwardDirY;
String data = String(F("[")) + x + String(F(",")) + y + String(F(",")) + penState + String(F("]\n"));
data.toCharArray(wbuf, data.length() + 1);
server.write(wbuf, data.length());
}
void setup() {
Serial.begin(9600);
while(!Serial);
Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
server.beginWebSocket("xy_plotter");
Serial.print("Adresse du serveur WebSocket : ");
Serial.println(Phpoc.localIP());
Expansion.begin(460800);
servo.attach(8);
/** NOTE : Pour la première exécution :
* - décommenter le dernier bloc dans xyInit()
* - exécuter le code Arduino
* - modifier les valeurs ligne 6, 7 de ce fichier et ligne 33 de index.php selon les valeurs de la console IDE.
**/
xyInit();
lastUpdateMillis = millis();
}
void loop() {
// attendre un nouveau client :
PhpocClient client = server.available();
if(client) {
String data = client.readLine();
if(data) {
byte separatorPos1 = data.indexOf(':');
byte separatorPos2 = data.lastIndexOf(':');
byte cmd = data.substring(0, separatorPos1).toInt();
long x = data.substring(separatorPos1 + 1, separatorPos2).toInt();
long y = data.substring(separatorPos2 + 1).toInt();
switch(cmd) {
case CMD_PEN_DOWN:
xyGoto(x, y);
while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb();
penDown();
break;
case CMD_PEN_UP:
while(stepX.getState() > 1 || stepY.getState() > 1) xyCheckUpdateToWeb();
penUp();
break;
case CMD_MOVE:
xyGoto(x, y);
break;
}
}
}
xyCheckUpdateToWeb();
}
Interface Utilisateur Web (PHP)
<!DOCTYPE html>
<html>
<head>
<title>Arduino - PHPoC Shield - Traceur XY</title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
body { text-align: center; background-color: #33C7F2; }
#canvas { margin-right: auto; margin-left: auto; position: relative; background-color: #FFFFFF; }
canvas { position: absolute; left: 0px; top: 0px; overflow-y: auto; overflow-x: hidden; -webkit-overflow-scrolling: touch; }
#layer1 { z-index: 2; }
#layer2 { z-index: 1; }
#layer3 { z-index: 0; }
</style>
<script>
var PEN_STATE_UP = 0;
var PEN_STATE_DOWN = 1;
var CMD_PEN_UP = 0;
var CMD_PEN_DOWN = 1;
var CMD_MOVE = 2;
var RESOLUTION = 1000;
var MIN_UPDATE_INTERVAL = 100; // en millisecondes
var MAX_X = 55550, MAX_Y = 68780;
var CANVAS_WIDTH = 0, CANVAS_HEIGHT = 0;
var ws = null;
var canvas = null;
var layer1 = null, layer2 = null, layer3 = null;
var ctx1 = null, ctx2 = null, ctx3 = null;
var touchState = 0;
var touchStepX = 0, touchStepY = 0; /* unité : pas */
var touchPixelX = 0, touchPixelY = 0; /* unité : pixel */
var plotterX = 0, plotterY = 0;
var plotterPenState = PEN_STATE_UP;
var lastUpdateMillis;
var buffer = "";
function init() {
canvas = document.getElementById("canvas");
layer1 = document.getElementById("layer1"); /* pour dessiner la position actuelle, reçue du traceur XY */
layer2 = document.getElementById("layer2"); /* pour dessiner la trajectoire du traceur XY, reçue du traceur XY */
layer3 = document.getElementById("layer3"); /* pour dessiner la position de toucher actuelle, entrée par l'utilisateur sur l'écran */
ctx1 = layer1.getContext("2d");
ctx2 = layer2.getContext("2d");
ctx3 = layer3.getContext("2d");
layer1.addEventListener("touchstart", mouse_down);
layer1.addEventListener("touchend", mouse_up);
layer1.addEventListener("touchmove", mouse_move);
layer1.addEventListener("mousedown", mouse_down);
layer1.addEventListener("mouseup", mouse_up);
layer1.addEventListener("mousemove", mouse_move);
canvasResize();
var d = new Date();
lastUpdateMillis = d.getTime();
}
function ws_onmessage(e_msg) {
buffer += e_msg.data;
var pos = buffer.indexOf('\n');
if(pos == -1) return;
var data = buffer.substring(0, pos);
buffer = buffer.substring(pos + 1);
var arr = JSON.parse(data);
plotterX = arr[0];
plotterY = arr[1];
var newPlotterPenState = arr[2];
/* convertir l'unité pas en unité pixel */
plotterX = Math.round(plotterX * CANVAS_WIDTH / MAX_X);
plotterY = -Math.round(plotterY * CANVAS_HEIGHT / MAX_Y);
/* dessiner la position actuelle du traceur */
ctx1.clearRect(0, 0, CANVAS_WIDTH, -CANVAS_HEIGHT);
ctx1.beginPath();
ctx1.arc(plotterX, plotterY, 7, 0, 2*Math.PI);
ctx1.fill();
/* dessiner la trajectoire du traceur seulement quand le stylo est baissé */
if(plotterPenState == PEN_STATE_UP && newPlotterPenState == PEN_STATE_DOWN) ctx2.beginPath();
if(newPlotterPenState == PEN_STATE_DOWN) {
ctx2.lineTo(plotterX, plotterY);
ctx2.stroke();
}
plotterPenState = newPlotterPenState;
}
function ws_onopen() {
document.getElementById("ws_state").innerHTML = "OPEN";
document.getElementById("wc_conn").innerHTML = "Disconnect";
}
function ws_onclose() {
document.getElementById("ws_state").innerHTML = "CLOSED";
document.getElementById("wc_conn").innerHTML = "Connect";
ws.onopen = null;
ws.onclose = null;
ws.onmessage = null;
ws = null;
}
function wc_onclick() {
if(ws == null) {
ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/xy_plotter", "text.phpoc");
document.getElementById("ws_state").innerHTML = "CONNECTING";
ws.onopen = ws_onopen;
ws.onclose = ws_onclose;
ws.onmessage = ws_onmessage;
} else ws.close();
}
function event_handler(event, type) {
// convertir les coordonnées
if(event.targetTouches) {
if(event.targetTouches.length > 1) return false;
touchPixelX = event.targetTouches[0].pageX - canvas.offsetLeft;
touchPixelY = event.targetTouches[0].pageY - canvas.offsetTop - CANVAS_HEIGHT;
} else {
touchPixelX = event.offsetX;
touchPixelY = event.offsetY - CANVAS_HEIGHT;
}
/* convertir de pixel en pas */
var newTouchStepX = Math.round(touchPixelX / CANVAS_WIDTH * MAX_X);
var newTouchStepY = Math.round((-touchPixelY) / CANVAS_HEIGHT * MAX_Y);
if(type == "MOVE") {
/* vérifier la condition de mise à jour pour éviter d'envoyer trop de données au traceur */
var isUpdate = false;
var d = new Date();
var curMillis = d.getTime();
if((curMillis - lastUpdateMillis) > MIN_UPDATE_INTERVAL) {
isUpdate = true;
}
var deltaX = newTouchStepX - touchStepX;
var deltaY = newTouchStepY - touchStepY;
var dist = Math.sqrt( Math.pow(deltaX, 2) + Math.pow(deltaY, 2) );
if(dist > RESOLUTION) isUpdate = true;
if(isUpdate == false) return false;
lastUpdateMillis = curMillis;
}
touchStepX = newTouchStepX;
touchStepY = newTouchStepY;
drawXYline();
return true;
}
function mouse_down() {
event.preventDefault();
event_handler(event, "DOWN");
sendToPlotter(CMD_PEN_DOWN, touchStepX, touchStepY);
touchState = 1;
}
function mouse_up() {
event.preventDefault();
touchState = 0;
sendToPlotter(CMD_PEN_UP, touchStepX, touchStepY);
}
function mouse_move() {
event.preventDefault();
if(touchState == 1) {
if(event_handler(event, "MOVE")) sendToPlotter(CMD_MOVE, touchStepX, touchStepY);
}
}
function sendToPlotter(cmd, x, y) {
if(ws != null && ws.readyState == 1) ws.send(cmd + ":" + touchStepX + ":" + touchStepY + "\r\n");
}
function canvasClear() {
ctx2.clearRect(0, 0, CANVAS_WIDTH, -CANVAS_HEIGHT); /* seulement effacer la trajectoire */
}
function canvasResize() {
var width = Math.round(window.innerWidth*0.95);
var height = Math.round(window.innerHeight*0.95) - 100;
var temp_height = Math.round(width*MAX_Y/MAX_X);
if(temp_height <= height) {
CANVAS_WIDTH = width;
CANVAS_HEIGHT = temp_height;
} else {
CANVAS_WIDTH = height*MAX_X/MAX_Y;
CANVAS_HEIGHT = height;
}
canvas.style.width = CANVAS_WIDTH + "px";
canvas.style.height = CANVAS_HEIGHT + "px";
layer1.width = CANVAS_WIDTH;
layer1.height = CANVAS_HEIGHT;
layer2.width = CANVAS_WIDTH;
layer2.height = CANVAS_HEIGHT;
layer3.width = CANVAS_WIDTH;
layer3.height = CANVAS_HEIGHT;
ctx1.translate(0, CANVAS_HEIGHT);
ctx2.translate(0, CANVAS_HEIGHT);
ctx3.translate(0, CANVAS_HEIGHT);
ctx1.fillStyle = "#00979d";
ctx2.lineWidth = 3;
ctx2.strokeStyle = "black";
ctx3.strokeStyle = "00FFFF";
}
function drawXYline() {
ctx3.clearRect(0, 0, CANVAS_WIDTH, -CANVAS_HEIGHT);
ctx3.beginPath();
ctx3.moveTo(0, touchPixelY);
ctx3.lineTo(CANVAS_WIDTH, touchPixelY);
ctx3.moveTo(touchPixelX, 0);
ctx3.lineTo(touchPixelX, -CANVAS_HEIGHT);
ctx3.stroke();
}
window.onload = init;
</script>
</head>
<body onresize="canvasResize()">
<br>
<div id="canvas">
<canvas id="layer1"></canvas>
<canvas id="layer2"></canvas>
<canvas id="layer3"></canvas>
</div>
<p>WebSocket : <span id="ws_state">null</span></p>
<button id="wc_conn" type="button" onclick="wc_onclick();">Connect</button>
<button type="button" onclick="canvasClear();">Clear</button>
</body>
</html>
Processus de fabrication
- Construire un décodeur DTMF avec un Arduino seul – Aucun CI supplémentaire nécessaire
- Système d'éclairage DMX piloté par web – Contrôleur maître Arduino
- Robot de détection humaine basé sur Arduino Uno : Intégration capteurs étape par étape
- Manette de jeu USB Arduino DIY – Construisez votre propre gamepad haute performance
- SERENA : Système d'Alarme Arduino Mega 2560 Personnalisé avec Écran Tactile TFT LCD
- Véhicule Télécommandé par Android avec Raspberry Pi et Shield Moteur
- Créez un robot suiveur de ligne autonome avec Arduino UNO
- Contrôlez un rover Arduino avec Firmata et une manette Xbox One
- Arduino UNO & Shield WiFi : Affichage température temps réel sur le Web via série