PID control with proportional (Kp), integral (Ki) and differential (Kd) coefficient is stated as:
PID controler equation provides the input to the H-bridge. The realisation schematics of the above equation is shown in the following figure:
In .js file, we should add PID control algorithm variables:
// PID Algorithm variablesvar Kp = 0.55; // proportional factorvar Ki = 0.008; // integral factorvar Kd = 0.15; // differential factorvar pwm = 0;var pwmLimit = 254;var err = 0; // variable for second pid implementationvar errSum = 0; // sum of errorsvar dErr = 0; // difference of errorvar lastErr = 0; // to keep the value of previous errorThe function controlAlgorithm () is written as:
function controlAlgorithm () { err = desiredValue - actualValue; // error as difference between desired and actual val. errSum += err; // sum of errors | like integral dErr = err - lastErr; // difference of error pwm = Kp*err+Ki*errSum+Kd*dErr; // PID expression lastErr = err; // save the value of error for next cycle to estimate the derivative if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // determine direction if > 0 if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // determine direction if < 0 board.analogWrite(3, Math.abs(pwm));};This time PID control tends to reach the desired value. The response depends on Kp, Ki and Kd parameters. Example of the response is shown in the following figure:
In the function startControlAlgorithm the values are reset to 0:
function startControlAlgorithm () { if (controlAlgorithmStartedFlag == 0) { // reset parameters pwm = 0; // Reset Pulse Width Modulation value err = 0; // Reset error errSum = 0; // Reset sum of errors as integral dErr = 0; // Reset difference of error lastErr = 0; // Reset value whih keeps the value of previous error to estimate derivative controlAlgorithmStartedFlag = 1; intervalCtrl = setInterval(function(){controlAlgorithm();}, 30); // call the alg. on 30ms console.log("Control algorithm has been started."); }};We should now try to sense the working of the PID. First, we should put desired and actual value to equal in upright position. Then, one should gently (very gently) hold the blue axle and change the desired pot to 15% left or right. If you gently (very gently) try to hold the position upright you will notice, how the motor will struggle to reach the desired value. This corresponds to the last column in the log part - pwm. The control algorithm will push the power to the motor to maximum 255 if needed, just to reach the desired value.
var http = require("http").createServer(handler); // on req - handvar fs = require("fs"); // variable for file system for providing htmlvar firmata = require("firmata");const WebSocket = require('ws'); // for permanent connection between server and clientconst wss = new WebSocket.Server({port: 8888}); // websocket port is 8888wss.broadcast = function broadcast(data) { wss.clients.forEach(function each(client) { if (client.readyState === WebSocket.OPEN) { client.send(data); } });};var messageJSON;var controlAlgorithmStartedFlag = 0; // variable for indicating weather the Alg has benn sta.var intervalCtrl; // var for setInterval in global scopeconsole.log("Starting the code");var board = new firmata.Board("/dev/ttyACM0", function(){ console.log("Connecting to Arduino"); console.log("Enabling analog Pin 0"); board.pinMode(0, board.MODES.ANALOG); // analog pin 0 board.pinMode(1, board.MODES.ANALOG); // analog pin 1 board.pinMode(2, board.MODES.OUTPUT); // direction of DC motor board.pinMode(3, board.MODES.PWM); // PWM of motor i.e. speed of rotation board.pinMode(4, board.MODES.OUTPUT); // direction DC motor});function handler(req, res) { fs.readFile(__dirname + "/example15.html", function (err, data) { if (err) { res.writeHead(500, {"Content-Type": "text/plain"}); return res.end("Error loading html page."); } res.writeHead(200); res.end(data); })}var desiredValue = 0; // desired value varvar actualValue = 0; // variable for actual value (output value)var Kp = 0.55; // proportional factor of PID controllervar Ki = 0.008; // integral factor of PID controllervar Kd = 0.15; // differential factor of PID controllervar factor = 0.3; // proportional factor that determines speed of resonsevar pwm = 0; // set pwm as global variablevar pwmLimit = 254; // to limit value of the pwm that is sent to the motorvar err = 0; // errorvar errSum = 0; // sum of errors as integralvar dErr = 0; // difference of errorvar lastErr = 0; // to keep the value of previous error to estimate derivativehttp.listen(8080); // server will listen on port 8080board.on("ready", function() { board.analogRead(0, function(value){ desiredValue = value; // continuous read of analog pin 0 }); board.analogRead(1, function(value) { actualValue = value; // continuous read of pin A1 }); startControlAlgorithm(); // to start control alg. wss.on('connection', function (ws, req) { // start of wss code messageJSON = {"type": "message", "content": "Srv connected, board OK"}; ws.send(JSON.stringify(messageJSON)); setInterval(sendValues, 40); // on 40ms we send message to client ws.on("message", function (msgString) { // message comes as string -> msgString var msg = JSON.parse(msgString); // string from ws which comes as a string is put to JSON switch(msg.type) { case "startControlAlgorithm": startControlAlgorithm(); break; case "stopControlAlgorithm": stopControlAlgorithm(); break; } }); // end of wss.on code }); // end of sockets.on connection}); // end of board.on(ready)function controlAlgorithm () { err = desiredValue - actualValue; // error as difference between desired and actual val. errSum += err; // sum of errors | like integral dErr = err - lastErr; // difference of error pwm = Kp*err+Ki*errSum+Kd*dErr; // PID expression lastErr = err; // save the value of error for next cycle to estimate the derivative if(pwm > pwmLimit) {pwm = pwmLimit}; // to limit the value for pwm / positive if(pwm < -pwmLimit) {pwm = -pwmLimit}; // to limit the value for pwm / negative if (pwm > 0) {board.digitalWrite(2,1); board.digitalWrite(4,0);}; // determine direction if > 0 if (pwm < 0) {board.digitalWrite(2,0); board.digitalWrite(4,1);}; // determine direction if < 0 board.analogWrite(3, Math.abs(pwm));};function startControlAlgorithm () { if (controlAlgorithmStartedFlag == 0) { // reset parameters pwm = 0; // Reset Pulse Width Modulation value err = 0; // Reset error errSum = 0; // Reset sum of errors as integral dErr = 0; // Reset difference of error lastErr = 0; // Reset value whih keeps the value of previous error to estimate derivative controlAlgorithmStartedFlag = 1; intervalCtrl = setInterval(function(){controlAlgorithm();}, 30); // call the alg. on 30ms console.log("Control algorithm has been started."); }};function stopControlAlgorithm () { clearInterval(intervalCtrl); // clear the interval of control algorihtm board.analogWrite(3, 0); controlAlgorithmStartedFlag = 0; console.log("Control algorithm has been stopped.");};function sendValues () { wss.broadcast(JSON.stringify({"type": "clientReadValues", "desiredValue": desiredValue, "actualValue": actualValue, "error": (desiredValue - actualValue), "pwm": (pwm).toFixed(0)}));};<!DOCTYPE html><meta charset = utf8><html><head><title>Example with potentiometers</title></head> <body onload="load()";> <div><canvas id="canvas1" width ="200" height = "100" style="border: 1px dashed #00c3c3;"></canvas> </div><button id="buttonStartControlAlgorithm" onClick="startControlAlgorithm()">Start Ctrl Alg</button><button id="buttonStopControlAlgorithm" onClick="stopControlAlgorithm()">Stop Ctrl Alg</button> <div id="print1"></div> <br> <script type="text/javascript"> "use strict"; // in order to use clasess var divElement = document.getElementById("print1"); // var for div el.var numberOfLinesBeforeScroll = 10;var linesPrintCounter = 0; var potValue1 = 0; // value of the first potentiometervar x1 = new Array(); // array for x1var y1 = new Array(); // array for y1var potValue2 = 0; // value of second potentiometervar x2 = new Array(); // array for x2 variablevar y2 = new Array(); // array for y2 variablevar canvas1;var ctx1; function log(msg) { var node=document.createElement("tr"); // we create the variable node as the a table row (tr) var textnode=document.createTextNode(linesPrintCounter + " | " + msg); // we create element with the text adding the counter node.appendChild(textnode); // adding text to "node", i.e. table row divElement.insertBefore(node, divElement.childNodes[0]); // inserting into variable node if (linesPrintCounter > numberOfLinesBeforeScroll-1) { // if the lines are more than limit -> start with scroll divElement.removeChild(divElement.childNodes[numberOfLinesBeforeScroll]); // we remove the oldest printout } linesPrintCounter++; // increasing the number of printouts} let ws = new WebSocket("ws://192.168.254.149:8888"); // create socket - connect to itvar messageJSON;function load() { // function that is started, when we open the page canvas1 = document.getElementById("canvas1"); ctx1 = canvas1.getContext("2d"); // initialization of graph with points ctx1.lineWidth = "1"; ctx1.strokeStyle = "#ff0000"; // initializaction of first time series for (var i=0; i<200; i++) { x1[i] = i; // for x values are 0, 1, 2, ... y1[i] = 0; // for y values are 0 }; // initializaion of second graph for (var i=0; i<200; i++) { x2[i] = i; // x2 values are 0, 1, 2, ... y2[i] = 0; // for y2 values are 0 }; };ws.onmessage = function(event) { var msg = JSON.parse(event.data); // string from ws is put to JSON switch(msg.type) { case "message": log(msg.content); // add msg to div break; case "clientReadValues": potValue1 = msg.desiredValue; potValue2 = msg.actualValue; // Draw first graph ***************************************** ctx1.clearRect(0, 0, canvas1.width, canvas1.height); // clear the canvas ctx1.lineWidth = "1"; ctx1.strokeStyle = "#ff0000"; ctx1.beginPath(); // to start drawing new line y1.splice(0, 1); // on the position 0 in the field y1 we erase one value y1[199] = potValue1; // new value is added at the end for (var i=0; i<200; i++) { ctx1.lineTo(x1[i], (100 - (y1[i] / 1023) * 100)); // 0,0 x,y coordinate is in upper left corner }; ctx1.stroke(); // to draw the line // End of draw graph*********************************** // Draw second graph ********************************************** ctx1.lineWidth = "1"; ctx1.strokeStyle = "#00ff00"; ctx1.beginPath(); // to start drawing new line y2.splice(0, 1); // on the position 0 in the field y2 we erase one value y2[199] = potValue2; // new value is added at the end for (i=0; i<200; i++) { ctx1.lineTo(x2[i], (100 - (y2[i] / 1023) * 100)); // 0,0 x,y coordinate is in upper left corner }; ctx1.stroke(); // to draw the line // Draw second graph graph END ************************************ log(potValue1 + "|" + potValue2 + "|" + msg.error + "|" + msg.pwm); break; }};function startControlAlgorithm () { messageJSON = {"type": "startControlAlgorithm"}; ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket }function stopControlAlgorithm () { messageJSON = {"type": "stopControlAlgorithm"}; ws.send(JSON.stringify(messageJSON)); // we have to stringify JSON to send it over websocket } </script></body></html>