Demonstration




Thing used in this project



How It Works


When user access webpage of PHPoC [WiFi] Shield from a web browser on Smart Phone or PC, a WebSocket connection will be created between Arduino and web browser. WebSocket connection allows to real-time exchange data between web browser and Arduino without reloading webpage.

When user rotates the needle on webpage, the rotated angle will be send to Arduino. Arduino convert angle to the equivalent position (unit is pulse), and then use PID algorithm to rotate DC motor the equivalent position.

PID library for Arduino is available here. It also contains auto-tuning function for both position and speed.

Angle to Position Calculation

Assumption:
  • Incremental Encoder Resolution (counts per revolution): ENC_RESOLUTION
  • Gear Ratio: GEAR_RATIO

Signal from Encoder will be decoded by DC Motor Controller. DC Motor Controller acts as Quadrature Decoder, which will quadruple (x4) the number of pulses per revolution.


We have:
  • a revolution <=> 360 degree <=> ENC_RESOLUTION * GEAR_RATIO * 4
  • angle <=> position
=> position = angle * ENC_RESOLUTION * GEAR_RATIO * 4 / 360

The motor I used have:
  • ENC_RESOLUTION = 13
  • GEAR_RATIO = 100
=> position = angle * 13 * 100 * 4 /360

(see line 68 of Arduino code)



Wiring
  • Stack PHPoC Shield or PHPoC WiFi Shield on Arduino
  • Stack DC motor controller PES-2604 on PHPoC Shield or PHPoC WiFi Shield
  • Connect the terminal block of DC motor controller to DC motor and power supply for DC motor as follows:
    • Terminal Block - VM <----> (+) wire - power supply for DC motor
    • Terminal Block - GND <----> (-) wire - power supply for DC motor
    • Terminal Block - M1+ <----> (+) wire - DC motor
    • Terminal Block - M1- <----> (-) wire - DC motor
  • (Optional) Connect the encoder port of DC motor controller to encoder pin of DC motor as follows:
    • Encoder 1 - 5V <----> Vcc - Encoder of DC motor
    • Encoder 1 - GND <----> GND - Encoder of DC motor
    • Encoder 1 - 1A <----> A phase - Encoder of DC motor
    • Encoder 1 - 1B <----> B phase - Encoder of DC motor

    Encoder 1 is located on the upper row of encoder port. More detail is here




Source Code

Arduino Code

This is arduino code, which run in infinite loop.

Code:
#include <Phpoc.h>
#include <PhpocExpansion.h>
#include <Pid.h>

PhpocServer server(80);
DcMotorPID dcPid1(1, 1);
long targetPosition;

void setup(){
    Serial.begin(9600);
    while(!Serial)
        ;

    //Phpoc.begin(PF_LOG_SPI | PF_LOG_NET);
    Phpoc.begin();
    server.beginWebSocket("remote_dc");

    Serial.print("WebSocket server address : ");
    Serial.println(Phpoc.localIP());

    Expansion.begin(460800);

    dcPid1.setPeriod(10000);
    dcPid1.setEncoderPSR(64);
    dcPid1.setDecay(0);
    dcPid1.setFilterFrequency(5000);
    dcPid1.setScaleFactor(5); // depending on motor, should test manually to have the best value
    dcPid1.setIntegralLimit(-2000, 2000);
    dcPid1.begin(PID_POSITION);

    /*----------- check enc pol-------------- */
    int pwm = 0; long pos = 0;
    while(!pos)
    {
        pwm += 10;
        dcPid1.setWidth(pwm);
        pos = dcPid1.getEncoderPosition();
        delay(1);
    }

    if(pos < 0)
        dcPid1.setEncoderPolarity(-1);

    dcPid1.setWidth(0);

    //dcPid1.setGain(24.099171193725, 10.78947680748, 0.0617886);

    /* PID auto-tuning */
    dcPid1.beginTune();
    while(!dcPid1.loopTune())
        ;

    dcPid1.setEncoderPosition(0);

    Serial.print(F("PID TUNED Kp: "));Serial.println(dcPid1.getKp());
    Serial.print(F("PID TUNED Ki: "));Serial.println(dcPid1.getKi());
    Serial.print(F("PID TUNED Kd: "));Serial.println(dcPid1.getKd());
}

void loop(){
    PhpocClient client = server.available();

    if(client) {
        String data = client.readLine();

        if(data) {
            float targetAngle = data.toFloat();
            targetPosition = (long)(targetAngle / 360.0 * (13 * 100 * 4));
        }
    }

    long position = dcPid1.loop(targetPosition);

    //Serial.print(targetPosition);
    //Serial.print(" ");
    //Serial.println(position);
}



Web User Interface - remote_dc.php

remote_dc.php is a file that contains Web User Interface. It needs to be stored on PHPoC [WiFi] Shield. In order to upload the file to PHPoC [WiFi] Shield, please do the following steps:
  • Copy the below code and save it into remote_dc.php file.
  • Install PHPoC Debugger
  • Connect PHPoC to PHPoC [WiFi] Shield via micro-USB cable according to this instruction
    Note that Arduino must be powered.
  • Upload remote_dc.php file to PHPoC [WiFi] Shield according to this instruction


PHP Code:
<!DOCTYPE html>
<html>
<head>
<title>PHPoC / <?echo system("uname -i")?></title>
<meta name="viewport" content="width=device-width, initial-scale=0.7">
<style>
body { text-align: center; background-color: whiite;}
canvas {
    background: url(plate.png) no-repeat;
    background-size: contain;}
}
</style>
<script>
var MIN_TOUCH_RADIUS = 20;
var MAX_TOUCH_RADIUS = 200;
var CANVAS_WIDTH, CANVAS_HEIGHT;
var PIVOT_X, PIVOT_Y;
var PLATE_WIDTH = 659.0, PLATE_HEIGHT = 569.0; // size of background image
var NEEDLE_WIDTH = 121, NEEDLE_HEIGHT = 492; // size of needle image
var RATIO;
var plateAngle = 0;
var needleImage = new Image();
var clickState = 0;
var lastAngle = 0;
var mouse_xyra = {x:0, y:0, r:0.0, a:0.0};
var ws;

needleImage.src = "needle.png";

function init() {
    CANVAS_WIDTH = window.innerWidth - 50;
    CANVAS_HEIGHT = window.innerHeight - 50;

    if(CANVAS_WIDTH < CANVAS_HEIGHT)
        CANVAS_HEIGHT = CANVAS_WIDTH;
    else
        CANVAS_WIDTH = CANVAS_HEIGHT;

    PIVOT_X = CANVAS_WIDTH/2;
    PIVOT_Y = CANVAS_HEIGHT/2;

    RATIO = CANVAS_WIDTH / PLATE_WIDTH;

    NEEDLE_WIDTH = Math.round(NEEDLE_WIDTH * RATIO);
    NEEDLE_HEIGHT = Math.round(NEEDLE_HEIGHT * RATIO);

    var canvas = document.getElementById("canvas");

    canvas.width = CANVAS_WIDTH;
    canvas.height = CANVAS_HEIGHT;

    canvas.addEventListener("touchstart", mouse_down);
    canvas.addEventListener("touchend", mouse_up);
    canvas.addEventListener("touchmove", mouse_move);
    canvas.addEventListener("mousedown", mouse_down);
    canvas.addEventListener("mouseup", mouse_up);
    canvas.addEventListener("mousemove", mouse_move);

    var ctx = canvas.getContext("2d");

    ctx.translate(PIVOT_X, PIVOT_Y);

    rotate_plate(0);

    ws = new WebSocket("ws://<?echo _SERVER("HTTP_HOST")?>/remote_dc", "text.phpoc");
    document.getElementById("ws_state").innerHTML = "CONNECTING";

    ws.onopen  = function(){ document.getElementById("ws_state").innerHTML = "OPEN" };
    ws.onclose = function(){ document.getElementById("ws_state").innerHTML = "CLOSED"};
    ws.onerror = function(){ alert("websocket error " + this.url) };

    ws.onmessage = ws_onmessage;
}
function ws_onmessage(e_msg) {
    e_msg = e_msg || window.event; // MessageEvent

    plateAngle = Number(e_msg.data);
    rotate_plate(plateAngle);
}
function rotate_plate(angle) {
    var canvas = document.getElementById("canvas");
    var ctx = canvas.getContext("2d");

    ctx.clearRect(-PIVOT_X, -PIVOT_Y, CANVAS_WIDTH, CANVAS_HEIGHT);
    ctx.rotate(-angle / 180 * Math.PI);
    ctx.drawImage(needleImage, 0, 0, needleImage.width, needleImage.height, -NEEDLE_WIDTH/2, -NEEDLE_HEIGHT/2, NEEDLE_WIDTH, NEEDLE_HEIGHT);
    ctx.rotate(angle / 180 * Math.PI);

    debug = document.getElementById("debug");
    debug.innerHTML = plateAngle.toFixed(1);
}
function check_update_xyra(event, mouse_xyra) {
    var x, y, r, a;
    var min_r, max_r, width;

    if(event.touches) {
        var touches = event.touches;

        x = (touches[0].pageX - touches[0].target.offsetLeft) - PIVOT_X;
        y = PIVOT_Y - (touches[0].pageY - touches[0].target.offsetTop);
    }
    else {
        x = event.offsetX - PIVOT_X;
        y = PIVOT_Y - event.offsetY;
    }

    /* cartesian to polar coordinate conversion */
    r = Math.sqrt(x * x + y * y);
    a = Math.atan2(y, x);

    mouse_xyra.x = x;
    mouse_xyra.y = y;
    mouse_xyra.r = r;
    mouse_xyra.a = a;

    if((r >= MIN_TOUCH_RADIUS) && (r <= MAX_TOUCH_RADIUS))
        return true;
    else
        return false;
}
function mouse_down() {
    if(event.touches && (event.touches.length > 1))
        clickState = event.touches.length;

    if(clickState > 1)
        return;

    if(check_update_xyra(event, mouse_xyra)) {
        clickState = 1;
        lastAngle = mouse_xyra.a / Math.PI * 180.0;
    }
}
function mouse_up() {
    clickState = 0;
}
function mouse_move() {
    var angle, angleOffset;

    if(event.touches && (event.touches.length > 1))
        clickState = event.touches.length;

    if(clickState != 1)
        return;

    if(!check_update_xyra(event, mouse_xyra)) {
        clickState = 0;
    } else {

        angle = mouse_xyra.a / Math.PI * 180.0;

        if(angle < 0.0)
            angle = angle + 360.0;

        angleOffset = angle - lastAngle;
        lastAngle = angle;

        if(angleOffset > 180.0)
            angleOffset = -360.0 + angleOffset;
        else
        if(angleOffset < -180.0)
            angleOffset = 360 + angleOffset;

        plateAngle += angleOffset;
        rotate_plate(plateAngle);

        if(ws != null & ws.readyState == 1)
            ws.send(plateAngle.toFixed(4) + "\r\n");
    }
    event.preventDefault();
}
window.onload = init;
</script>
</head>

<body>
<h2>

Arduino - DC Motor Position Control<br><br>
<canvas id="canvas"></canvas>
<p>
WebSocket : <span id="ws_state">null</span><br>
Angle : <span id="debug">0</span>
</p>
</h2>
</body>

</html>

Two images used in Web User Interface
  • plate.png
    Click image for larger version  Name:	image_737.png Views:	1 Size:	126.8 KB ID:	1298
  • needle.png
    Click image for larger version  Name:	image_738.png Views:	1 Size:	9.2 KB ID:	1299

These images also need to be uploaded to PHPoC [WiFi] Shield




How To
  • Config network information for PHPoC shield or PHPoC WiFi shield
  • Install PID Controller Library
  • Compile and upload code to Arduino
  • Upload web user interface to PHPoC [WiFi] shield
  • Open Serial Monitor and copy IP address of PHPoC Shield
  • Access Web User Interface via Web Browser: http://ip_address_of_shield/remote_dc.php
  • Control DC motor via Web
    Click image for larger version  Name:	dc_motor_web_ui.png Views:	1 Size:	106.6 KB ID:	1303