{"id":3920,"date":"2021-12-28T14:00:23","date_gmt":"2021-12-28T22:00:23","guid":{"rendered":"https:\/\/www.cloudacm.com\/?p=3920"},"modified":"2021-12-23T19:00:31","modified_gmt":"2021-12-24T03:00:31","slug":"visualizing-data-with-processing","status":"publish","type":"post","link":"https:\/\/www.cloudacm.com\/?p=3920","title":{"rendered":"Visualizing Data with Processing"},"content":{"rendered":"<p>The purpose of this post is to demonstrate how to use Processing to present data in a visual way. The methods used in this post will be based on the work done by M.Furkan Bahat.<\/p>\n<p>Source &#8211; <a href=\"https:\/\/mfurkanbahat.blogspot.com\/2014\/11\/artificial-horizon-and-compass-using.html\">https:\/\/mfurkanbahat.blogspot.com\/2014\/11\/artificial-horizon-and-compass-using.html<\/a><\/p>\n<p>The hardware used in this demonstration will be the following:<br \/>\nGY-521 MPU6050<br \/>\nArduino Uno<br \/>\nESP32-Cam<br \/>\nESP32-USB-Module<br \/>\nPatch wiring<br \/>\nUSB cables<br \/>\nWindows 10 laptop<\/p>\n<p>The MPU6050 is connected to the Arduino Uno using the following pin connections, as shown in the images below.<\/p>\n<p>Red patch wire from VCC to 5V<br \/>\nBlack patch wire from GND to GND<br \/>\nYellow patch wire from SCL to A5<br \/>\nGreen patch wire from SDA to A4<br \/>\nPurple patch wire from INT to Digital Pin 2<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/MPU6050_ArduinoUno.jpg\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-3922\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/MPU6050_ArduinoUno-300x281.jpg\" alt=\"\" width=\"300\" height=\"281\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/MPU6050_ArduinoUno-300x281.jpg 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/MPU6050_ArduinoUno-288x270.jpg 288w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/MPU6050_ArduinoUno.jpg 563w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><br \/>\n<a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/GY-651_MPU6050.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-3923\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/GY-651_MPU6050-285x300.png\" alt=\"\" width=\"285\" height=\"300\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/GY-651_MPU6050-285x300.png 285w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/GY-651_MPU6050-256x270.png 256w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/GY-651_MPU6050.png 426w\" sizes=\"auto, (max-width: 285px) 100vw, 285px\" \/><\/a><br \/>\n<a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-3924\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno-300x171.png\" alt=\"\" width=\"300\" height=\"171\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno-300x171.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno-1024x583.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno-768x437.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno-474x270.png 474w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ArduinoUno.png 1099w\" sizes=\"auto, (max-width: 300px) 100vw, 300px\" \/><\/a><\/p>\n<p>The Arduino code used is shown below, this was sourced from M.Furkan Bahat&#8217;s work. Although the libraries I2Cdev.h and MPU6050_6Axis_MotionApps20.h needed to be installed, sourced here <a href=\"https:\/\/github.com\/jrowberg\/i2cdevlib\">https:\/\/github.com\/jrowberg\/i2cdevlib<\/a>, no additional changes were needed.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/\/ M.Furkan Bahat , November 2014\r\n\/\/ For more information  http:\/\/mfurkanbahat.blogspot.com.tr\/\r\n\r\n\/\/ Source - https:\/\/mfurkanbahat.blogspot.com\/2014\/11\/artificial-horizon-and-compass-using.html\r\n\/\/ ArtificialHorizonCompass.ino\r\n\r\n\/\/ Needed https:\/\/github.com\/jrowberg\/i2cdevlib for I2Cdev.h and MPU6050_6Axis_MotionApps20.h\r\n\r\n#include \"I2Cdev.h\"\r\n#include \"MPU6050_6Axis_MotionApps20.h\"\r\n\r\n#if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE\r\n    #include \"Wire.h\"\r\n#endif\r\n\r\nMPU6050 mpu;\r\n#define OUTPUT_READABLE_YAWPITCHROLL\r\n#define LED_PIN 13 \r\nbool blinkState = false;\r\n\r\n\/\/ MPU control\/status vars\r\nbool dmpReady = false;  \/\/ set true if DMP init was successful\r\nuint8_t mpuIntStatus;   \/\/ holds actual interrupt status byte from MPU\r\nuint8_t devStatus;      \/\/ return status after each device operation (0 = success, !0 = error)\r\nuint16_t packetSize;    \/\/ expected DMP packet size (default is 42 bytes)\r\nuint16_t fifoCount;     \/\/ count of all bytes currently in FIFO\r\nuint8_t fifoBuffer[64]; \/\/ FIFO storage buffer\r\n\r\n\/\/ orientation\/motion vars\r\nQuaternion q;           \/\/ [w, x, y, z]         quaternion container\r\nVectorInt16 aa;         \/\/ [x, y, z]            accel sensor measurements\r\nVectorInt16 aaReal;     \/\/ [x, y, z]            gravity-free accel sensor measurements\r\nVectorInt16 aaWorld;    \/\/ [x, y, z]            world-frame accel sensor measurements\r\nVectorFloat gravity;    \/\/ [x, y, z]            gravity vector\r\nfloat euler[3];         \/\/ [psi, theta, phi]    Euler angle container\r\nfloat ypr[3];           \/\/ [yaw, pitch, roll]   yaw\/pitch\/roll container and gravity vector\r\n\r\n\/\/ packet structure for InvenSense teapot demo\r\nuint8_t teapotPacket[14] = { '$', 0x02, 0,0, 0,0, 0,0, 0,0, 0x00, 0x00, '\\r', '\\n' };\r\n\r\n\/\/ ================================================================\r\n\/\/ ===               INTERRUPT DETECTION ROUTINE                ===\r\n\/\/ ================================================================\r\n\r\nvolatile bool mpuInterrupt = false;     \/\/ indicates whether MPU interrupt pin has gone high\r\nvoid dmpDataReady() {\r\n    mpuInterrupt = true;\r\n}\r\n\r\n\/\/ ================================================================\r\n\/\/ ===                      INITIAL SETUP                       ===\r\n\/\/ ================================================================\r\n\r\nvoid setup() {\r\n    \/\/ join I2C bus (I2Cdev library doesn't do this automatically)\r\n    #if I2CDEV_IMPLEMENTATION == I2CDEV_ARDUINO_WIRE\r\n        Wire.begin();\r\n        TWBR = 24; \/\/ 400kHz I2C clock (200kHz if CPU is 8MHz)\r\n    #elif I2CDEV_IMPLEMENTATION == I2CDEV_BUILTIN_FASTWIRE\r\n        Fastwire::setup(400, true);\r\n    #endif\r\n\r\n    \/\/ initialize serial communication\r\n    \/\/ (115200 chosen because it is required for Teapot Demo output, but it's\r\n    \/\/ really up to you depending on your project)\r\n    Serial.begin(115200);\r\n    while (!Serial); \/\/ wait for Leonardo enumeration, others continue immediately\r\n    mpu.initialize();\r\n    devStatus = mpu.dmpInitialize();\r\n\r\n    \/\/ supply your own gyro offsets here, scaled for min sensitivity\r\n    mpu.setXGyroOffset(220);\r\n    mpu.setYGyroOffset(76);\r\n    mpu.setZGyroOffset(-85);\r\n    mpu.setZAccelOffset(1788); \/\/ 1688 factory default for my test chip\r\n\r\n    \/\/ make sure it worked (returns 0 if so)\r\n    if (devStatus == 0) {\r\n        mpu.setDMPEnabled(true);\r\n\r\n        \/\/ enable Arduino interrupt detection\r\n        \/\/Serial.println(F(\"Enabling interrupt detection (Arduino external interrupt 0)...\"));\r\n        attachInterrupt(0, dmpDataReady, RISING);\r\n        mpuIntStatus = mpu.getIntStatus();\r\n\r\n        \/\/ set our DMP Ready flag so the main loop() function knows it's okay to use it\r\n        \/\/Serial.println(F(\"DMP ready! Waiting for first interrupt...\"));\r\n        dmpReady = true;\r\n\r\n        \/\/ get expected DMP packet size for later comparison\r\n        packetSize = mpu.dmpGetFIFOPacketSize();\r\n    } else {\r\n        \r\n        Serial.print(devStatus);\r\n        Serial.println(F(\")\"));\r\n    }\r\n\r\n    \/\/ configure LED for output\r\n    pinMode(LED_PIN, OUTPUT);\r\n}\r\n\r\n\/\/ ================================================================\r\n\/\/ ===                    MAIN PROGRAM LOOP                     ===\r\n\/\/ ================================================================\r\n\r\nvoid loop() {\r\n    \/\/ if programming failed, don't try to do anything\r\n    if (!dmpReady) return;\r\n\r\n    \/\/ wait for MPU interrupt or extra packet(s) available\r\n    while (!mpuInterrupt &amp;&amp; fifoCount &lt; packetSize) {\r\n \r\n    }\r\n\r\n    \/\/ reset interrupt flag and get INT_STATUS byte\r\n    mpuInterrupt = false;\r\n    mpuIntStatus = mpu.getIntStatus();\r\n\r\n    \/\/ get current FIFO count\r\n    fifoCount = mpu.getFIFOCount();\r\n\r\n    \/\/ check for overflow (this should never happen unless our code is too inefficient)\r\n    if ((mpuIntStatus &amp; 0x10) || fifoCount == 1024) {\r\n        \/\/ reset so we can continue cleanly\r\n        mpu.resetFIFO();\r\n        \/\/Serial.println(F(\"FIFO overflow!\"));\r\n\r\n    \/\/ otherwise, check for DMP data ready interrupt (this should happen frequently)\r\n    } else if (mpuIntStatus &amp; 0x02) {\r\n        \/\/ wait for correct available data length, should be a VERY short wait\r\n        while (fifoCount &lt; packetSize) fifoCount = mpu.getFIFOCount();\r\n\r\n        \/\/ read a packet from FIFO\r\n        mpu.getFIFOBytes(fifoBuffer, packetSize);\r\n        \r\n        \/\/ track FIFO count here in case there is &gt; 1 packet available\r\n        \/\/ (this lets us immediately read more without waiting for an interrupt)\r\n        fifoCount -= packetSize;\r\n\r\n        #ifdef OUTPUT_READABLE_QUATERNION\r\n            \/\/ display quaternion values in easy matrix form: w x y z\r\n            mpu.dmpGetQuaternion(&amp;q, fifoBuffer);\r\n            Serial.print(\"quat\\t\");\r\n            Serial.print(q.w);\r\n            Serial.print(\"\\t\");\r\n            Serial.print(q.x);\r\n            Serial.print(\"\\t\");\r\n            Serial.print(q.y);\r\n            Serial.print(\"\\t\");\r\n            Serial.println(q.z);\r\n        #endif\r\n\r\n        #ifdef OUTPUT_READABLE_EULER\r\n            \/\/ display Euler angles in degrees\r\n            mpu.dmpGetQuaternion(&amp;q, fifoBuffer);\r\n            mpu.dmpGetEuler(euler, &amp;q);\r\n            Serial.print(\"euler\\t\");\r\n            Serial.print(euler[0] * 180\/M_PI);\r\n            Serial.print(\"\\t\");\r\n            Serial.print(euler[1] * 180\/M_PI);\r\n            Serial.print(\"\\t\");\r\n            Serial.println(euler[2] * 180\/M_PI);\r\n        #endif\r\n\r\n        #ifdef OUTPUT_READABLE_YAWPITCHROLL\r\n            \/\/ display Euler angles in degrees\r\n            mpu.dmpGetQuaternion(&amp;q, fifoBuffer);\r\n            mpu.dmpGetGravity(&amp;gravity, &amp;q);\r\n            mpu.dmpGetYawPitchRoll(ypr, &amp;q, &amp;gravity);\r\n            \/\/Serial.print(\"Phi: \");\r\n            Serial.print(ypr[2] * 18\/M_PI);\r\n            \/\/Serial.print(\"\\t theta: \");\r\n            Serial.print(\" \");\r\n            Serial.print(ypr[1] * 180\/M_PI);\r\n            \/\/Serial.print(\"\\t Psi: \");\r\n            Serial.print(\" \");\r\n            Serial.println(ypr[0] * 180\/M_PI);\r\n            \/\/delay(100);\r\n        #endif\r\n\r\n        #ifdef OUTPUT_READABLE_REALACCEL\r\n            \/\/ display real acceleration, adjusted to remove gravity\r\n            mpu.dmpGetQuaternion(&amp;q, fifoBuffer);\r\n            mpu.dmpGetAccel(&amp;aa, fifoBuffer);\r\n            mpu.dmpGetGravity(&amp;gravity, &amp;q);\r\n            mpu.dmpGetLinearAccel(&amp;aaReal, &amp;aa, &amp;gravity);\r\n            Serial.print(\"areal\\t\");\r\n            Serial.print(aaReal.x);\r\n            Serial.print(\"\\t\");\r\n            Serial.print(aaReal.y);\r\n            Serial.print(\"\\t\");\r\n            Serial.println(aaReal.z);\r\n        #endif\r\n\r\n        #ifdef OUTPUT_READABLE_WORLDACCEL\r\n            \/\/ display initial world-frame acceleration, adjusted to remove gravity\r\n            \/\/ and rotated based on known orientation from quaternion\r\n            mpu.dmpGetQuaternion(&amp;q, fifoBuffer);\r\n            mpu.dmpGetAccel(&amp;aa, fifoBuffer);\r\n            mpu.dmpGetGravity(&amp;gravity, &amp;q);\r\n            mpu.dmpGetLinearAccel(&amp;aaReal, &amp;aa, &amp;gravity);\r\n            mpu.dmpGetLinearAccelInWorld(&amp;aaWorld, &amp;aaReal, &amp;q);\r\n            Serial.print(\"aworld\\t\");\r\n            Serial.print(aaWorld.x);\r\n            Serial.print(\"\\t\");\r\n            Serial.print(aaWorld.y);\r\n            Serial.print(\"\\t\");\r\n            Serial.println(aaWorld.z);\r\n        #endif\r\n    \r\n        #ifdef OUTPUT_TEAPOT\r\n            \/\/ display quaternion values in InvenSense Teapot demo format:\r\n            teapotPacket[2] = fifoBuffer[0];\r\n            teapotPacket[3] = fifoBuffer[1];\r\n            teapotPacket[4] = fifoBuffer[4];\r\n            teapotPacket[5] = fifoBuffer[5];\r\n            teapotPacket[6] = fifoBuffer[8];\r\n            teapotPacket[7] = fifoBuffer[9];\r\n            teapotPacket[8] = fifoBuffer[12];\r\n            teapotPacket[9] = fifoBuffer[13];\r\n            Serial.write(teapotPacket, 14);\r\n            teapotPacket[11]++; \/\/ packetCount, loops at 0xFF on purpose\r\n        #endif\r\n\r\n        \/\/ blink LED to indicate activity\r\n        blinkState = !blinkState;\r\n        digitalWrite(LED_PIN, blinkState);\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>The Processing 3 Code used is shown below. Again, this was sourced from M.Furkan Bahat&#8217;s work. The cc.arduino was not installed by default and was required. In addition, these changes were made to the code due to issues with the version of Processing used, which was version 3.5.4.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/\/ size(W, H); Processing didn't like this\r\nsize(1400, 700);<\/pre>\n<p>&nbsp;<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/\/Thanks to Adrian Fernandez\r\n\/\/Communication updates by M.Furkan Bahat November 2014\r\n\/\/For more information http:\/\/mfurkanbahat.blogspot.com.tr\/\r\n\r\n\/\/ Source - https:\/\/mfurkanbahat.blogspot.com\/2014\/11\/artificial-horizon-and-compass-using.html\r\n\/\/ ArtificialHorizonCompass.pde\r\n\r\n\/\/ Works with Arduino Uno and live data\r\n\/\/ Not working with stored data from ESP23-Cam MicroSD log file\r\n\/\/ See - https:\/\/learn.sparkfun.com\/tutorials\/connecting-arduino-to-processing\/all\r\n\r\nimport processing.serial.*;\r\nimport cc.arduino.*;\r\n\r\nint W=1400; \/\/My Laptop's screen width \r\nint H=700;  \/\/My Laptop's screen height \r\nfloat Pitch; \r\nfloat Bank; \r\nfloat Azimuth; \r\nfloat ArtificialHoizonMagnificationFactor=0.7; \r\nfloat CompassMagnificationFactor=0.85; \r\nfloat SpanAngle=120; \r\nint NumberOfScaleMajorDivisions; \r\nint NumberOfScaleMinorDivisions; \r\nPVector v1, v2; \r\n\r\n\r\nSerial port;\r\nfloat Phi;    \/\/Dimensional axis\r\nfloat Theta;\r\nfloat Psi;\r\n\r\nvoid setup() \r\n{ \r\n  \/\/ size(W, H); WRONG - Processing didn't like this\r\n  size(1400, 700); \r\n  rectMode(CENTER); \r\n  smooth(); \r\n  strokeCap(SQUARE);\/\/Optional \r\n  \r\n  println(Serial.list()); \/\/Shows your connected serial ports\r\n  port = new Serial(this, Serial.list()[0], 115200); \r\n  \/\/Up there you should select port which arduino connected and same baud rate.\r\n  port.bufferUntil('\\n'); \r\n}\r\nvoid draw() \r\n{ \r\n  background(0); \r\n  translate(W\/4, H\/2.1);  \r\n  MakeAnglesDependentOnMPU6050(); \r\n  Horizon(); \r\n  rotate(-Bank); \r\n  PitchScale(); \r\n  Axis(); \r\n  rotate(Bank); \r\n  Borders(); \r\n  Plane(); \r\n  ShowAngles(); \r\n  Compass(); \r\n  ShowAzimuth(); \r\n}\r\nvoid serialEvent(Serial port) \/\/Reading the datas by Processing.\r\n{\r\n   String input = port.readStringUntil('\\n');\r\n  if(input != null){\r\n   input = trim(input);\r\n  String[] values = split(input, \" \");\r\n if(values.length == 3){\r\n  float phi = float(values[0]);\r\n  float theta = float(values[1]); \r\n  float psi = float(values[2]); \r\n  print(phi);\r\n  print(theta);\r\n  println(psi);\r\n  Phi = phi;\r\n  Theta = theta;\r\n  Psi = psi;\r\n   }\r\n  }\r\n}\r\nvoid MakeAnglesDependentOnMPU6050() \r\n{ \r\n  Bank =-Phi\/5; \r\n  Pitch=Theta*10; \r\n  Azimuth=Psi;\r\n}\r\nvoid Horizon() \r\n{ \r\n  scale(ArtificialHoizonMagnificationFactor); \r\n  noStroke(); \r\n  fill(0, 180, 255); \r\n  rect(0, -100, 900, 1000); \r\n  fill(95, 55, 40); \r\n  rotate(-Bank); \r\n  rect(0, 400+Pitch, 900, 800); \r\n  rotate(Bank); \r\n  rotate(-PI-PI\/6); \r\n  SpanAngle=120; \r\n  NumberOfScaleMajorDivisions=12; \r\n  NumberOfScaleMinorDivisions=24;  \r\n  CircularScale(); \r\n  rotate(PI+PI\/6); \r\n  rotate(-PI\/6);  \r\n  CircularScale(); \r\n  rotate(PI\/6); \r\n}\r\nvoid ShowAzimuth() \r\n{ \r\n  fill(50); \r\n  noStroke(); \r\n  rect(20, 470, 440, 50); \r\n  int Azimuth1=round(Azimuth); \r\n  textAlign(CORNER); \r\n  textSize(35); \r\n  fill(255); \r\n  text(\"Azimuth:  \"+Azimuth1+\" Deg\", 80, 477, 500, 60); \r\n  textSize(40);\r\n  fill(25,25,150);\r\n  text(\"M.Furkan Bahat\", -350, 477, 500, 60); \r\n}\r\nvoid Compass() \r\n{ \r\n  translate(2*W\/3, 0); \r\n  scale(CompassMagnificationFactor); \r\n  noFill(); \r\n  stroke(100); \r\n  strokeWeight(80); \r\n  ellipse(0, 0, 750, 750); \r\n  strokeWeight(50); \r\n  stroke(50); \r\n  fill(0, 0, 40); \r\n  ellipse(0, 0, 610, 610); \r\n  for (int k=255;k&gt;0;k=k-5) \r\n  { \r\n    noStroke(); \r\n    fill(0, 0, 255-k); \r\n    ellipse(0, 0, 2*k, 2*k); \r\n  } \r\n  strokeWeight(20); \r\n  NumberOfScaleMajorDivisions=18; \r\n  NumberOfScaleMinorDivisions=36;  \r\n  SpanAngle=180; \r\n  CircularScale(); \r\n  rotate(PI); \r\n  SpanAngle=180; \r\n  CircularScale(); \r\n  rotate(-PI); \r\n  fill(255); \r\n  textSize(60); \r\n  textAlign(CENTER); \r\n  text(\"W\", -375, 0, 100, 80); \r\n  text(\"E\", 370, 0, 100, 80); \r\n  text(\"N\", 0, -365, 100, 80); \r\n  text(\"S\", 0, 375, 100, 80); \r\n  textSize(30); \r\n  text(\"COMPASS\", 0, -130, 500, 80); \r\n  rotate(PI\/4); \r\n  textSize(40); \r\n  text(\"NW\", -370, 0, 100, 50); \r\n  text(\"SE\", 365, 0, 100, 50); \r\n  text(\"NE\", 0, -355, 100, 50); \r\n  text(\"SW\", 0, 365, 100, 50); \r\n  rotate(-PI\/4); \r\n  CompassPointer(); \r\n}\r\nvoid CompassPointer() \r\n{ \r\n  rotate(PI+radians(Azimuth));  \r\n  stroke(0); \r\n  strokeWeight(4); \r\n  fill(100, 255, 100); \r\n  triangle(-20, -210, 20, -210, 0, 270); \r\n  triangle(-15, 210, 15, 210, 0, 270); \r\n  ellipse(0, 0, 45, 45);   \r\n  fill(0, 0, 50); \r\n  noStroke(); \r\n  ellipse(0, 0, 10, 10); \r\n  triangle(-20, -213, 20, -213, 0, -190); \r\n  triangle(-15, -215, 15, -215, 0, -200); \r\n  rotate(-PI-radians(Azimuth)); \r\n}\r\nvoid Plane() \r\n{ \r\n  fill(0); \r\n  strokeWeight(1); \r\n  stroke(0, 255, 0); \r\n  triangle(-20, 0, 20, 0, 0, 25); \r\n  rect(110, 0, 140, 20); \r\n  rect(-110, 0, 140, 20); \r\n}\r\nvoid CircularScale() \r\n{ \r\n  float GaugeWidth=800;  \r\n  textSize(GaugeWidth\/30); \r\n  float StrokeWidth=1; \r\n  float an; \r\n  float DivxPhasorCloser; \r\n  float DivxPhasorDistal; \r\n  float DivyPhasorCloser; \r\n  float DivyPhasorDistal; \r\n  strokeWeight(2*StrokeWidth); \r\n  stroke(255);\r\n  float DivCloserPhasorLenght=GaugeWidth\/2-GaugeWidth\/9-StrokeWidth; \r\n  float DivDistalPhasorLenght=GaugeWidth\/2-GaugeWidth\/7.5-StrokeWidth;\r\n  for (int Division=0;Division&lt;NumberOfScaleMinorDivisions+1;Division++) \r\n  { \r\n    an=SpanAngle\/2+Division*SpanAngle\/NumberOfScaleMinorDivisions;  \r\n    DivxPhasorCloser=DivCloserPhasorLenght*cos(radians(an)); \r\n    DivxPhasorDistal=DivDistalPhasorLenght*cos(radians(an)); \r\n    DivyPhasorCloser=DivCloserPhasorLenght*sin(radians(an)); \r\n    DivyPhasorDistal=DivDistalPhasorLenght*sin(radians(an));   \r\n    line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); \r\n  }\r\n  DivCloserPhasorLenght=GaugeWidth\/2-GaugeWidth\/10-StrokeWidth; \r\n  DivDistalPhasorLenght=GaugeWidth\/2-GaugeWidth\/7.4-StrokeWidth;\r\n  for (int Division=0;Division&lt;NumberOfScaleMajorDivisions+1;Division++) \r\n  { \r\n    an=SpanAngle\/2+Division*SpanAngle\/NumberOfScaleMajorDivisions;  \r\n    DivxPhasorCloser=DivCloserPhasorLenght*cos(radians(an)); \r\n    DivxPhasorDistal=DivDistalPhasorLenght*cos(radians(an)); \r\n    DivyPhasorCloser=DivCloserPhasorLenght*sin(radians(an)); \r\n    DivyPhasorDistal=DivDistalPhasorLenght*sin(radians(an)); \r\n    if (Division==NumberOfScaleMajorDivisions\/2|Division==0|Division==NumberOfScaleMajorDivisions) \r\n    { \r\n      strokeWeight(15); \r\n      stroke(0); \r\n      line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); \r\n      strokeWeight(8); \r\n      stroke(100, 255, 100); \r\n      line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); \r\n    } \r\n    else \r\n    { \r\n      strokeWeight(3); \r\n      stroke(255); \r\n      line(DivxPhasorCloser, DivyPhasorCloser, DivxPhasorDistal, DivyPhasorDistal); \r\n    } \r\n  } \r\n}\r\nvoid Axis() \r\n{ \r\n  stroke(255, 0, 0); \r\n  strokeWeight(3); \r\n  line(-115, 0, 115, 0); \r\n  line(0, 280, 0, -280); \r\n  fill(100, 255, 100); \r\n  stroke(0); \r\n  triangle(0, -285, -10, -255, 10, -255); \r\n  triangle(0, 285, -10, 255, 10, 255); \r\n}\r\nvoid ShowAngles() \r\n{ \r\n  textSize(30); \r\n  fill(50); \r\n  noStroke(); \r\n  rect(-150, 400, 280, 40); \r\n  rect(150, 400, 280, 40); \r\n  fill(255); \r\n  Pitch=Pitch\/5; \r\n  int Pitch1=round(Pitch);  \r\n  text(\"Pitch:  \"+Pitch1+\" Deg\", -20, 411, 500, 60); \r\n  text(\"Bank:  \"+Bank*100+\" Deg\", 280, 411, 500, 60); \r\n}\r\nvoid Borders() \r\n{ \r\n  noFill(); \r\n  stroke(0); \r\n  strokeWeight(400); \r\n  rect(0, 0, 1100, 1100); \r\n  strokeWeight(200); \r\n  ellipse(0, 0, 1000, 1000); \r\n  fill(0); \r\n  noStroke(); \r\n  rect(4*W\/5, 0, W, 2*H); \r\n  rect(-4*W\/5, 0, W, 2*H); \r\n}\r\nvoid PitchScale() \r\n{  \r\n  stroke(255); \r\n  fill(255); \r\n  strokeWeight(3); \r\n  textSize(24); \r\n  textAlign(CENTER); \r\n  for (int i=-4;i&lt;5;i++) \r\n  {  \r\n    if ((i==0)==false) \r\n    { \r\n      line(110, 50*i, -110, 50*i); \r\n    }  \r\n    text(\"\"+i*10, 140, 50*i, 100, 30); \r\n    text(\"\"+i*10, -140, 50*i, 100, 30); \r\n  } \r\n  textAlign(CORNER); \r\n  strokeWeight(2); \r\n  for (int i=-9;i&lt;10;i++) \r\n  {  \r\n    if ((i==0)==false) \r\n    {    \r\n      line(25, 25*i, -25, 25*i); \r\n    } \r\n  } \r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>In this configuration, a view live of data with Processing was possible.<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter wp-image-3929 size-large\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass-1024x504.png\" alt=\"\" width=\"640\" height=\"315\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass-1024x504.png 1024w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass-300x148.png 300w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass-768x378.png 768w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass-549x270.png 549w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/Processing_ArtificialHorizonCompass.png 1366w\" sizes=\"auto, (max-width: 640px) 100vw, 640px\" \/><\/a><\/p>\n<p>The next step was to capture a GY-521 data stream to a file. This data stream sample would be used for offline viewing. Putty was used to capture a log of the serial stream. The log file was edited so it only contained valid data, below is a sample.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">0.02 -0.15 0.06\r\n0.02 -0.22 0.11\r\n0.01 -0.30 0.14\r\n0.00 -0.38 0.17\r\n-0.01 -0.45 0.21\r\n-0.01 -0.52 0.24\r\n-0.02 -0.59 0.28\r\n-0.03 -0.66 0.31\r\n-0.03 -0.73 0.35\r\n-0.04 -0.80 0.38\r\n-0.05 -0.87 0.42\r\n-0.05 -0.94 0.45\r\n-0.06 -1.00 0.49\r\n-0.07 -1.07 0.53\r\n-0.07 -1.13 0.56\r\n-0.08 -1.19 0.59\r\n-0.08 -1.25 0.63\r\n-0.09 -1.32 0.66\r\n-0.10 -1.38 0.70\r\n-0.10 -1.44 0.74\r\n-0.11 -1.50 0.77\r\n-0.11 -1.56 0.80\r\n-0.12 -1.61 0.84<\/pre>\n<p>&nbsp;<\/p>\n<p>The ESP32-Cam module will be used to stream log file back to Processing through its serial output. The ESP32-Cam module features an onboard MicroSD storage slot, with the memory card containing the log data in a file. Code was sourced that would serial output the log file line by line with delays between, the purpose being a mock-up of the GY-521 serial data stream generated by the Arduino Uno. Below is the code used.<br \/>\nSource &#8211; <a href=\"https:\/\/arduino.stackexchange.com\/questions\/63468\/reading-text-line-by-line-from-sd\">https:\/\/arduino.stackexchange.com\/questions\/63468\/reading-text-line-by-line-from-sd<\/a><\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"cpp\">\/\/ MicroSD_Line-Read_Ver2.ino\r\n\r\n\/\/ Source - https:\/\/arduino.stackexchange.com\/questions\/63468\/reading-text-line-by-line-from-sd\r\n\r\n#include \"FS.h\"\r\n#include \"SD_MMC.h\" \r\n\r\nvoid setup() {\r\n\r\n  Serial.begin(115200);\r\n  Serial.println(\"SDcard Testing....\");\r\n\r\n   if(!SD_MMC.begin()){\r\n        Serial.println(\"Card Mount Failed\");\r\n        return;\r\n    }\r\n    uint8_t cardType = SD_MMC.cardType();\r\n\r\n    if(cardType == CARD_NONE){\r\n        Serial.println(\"No SD_MMC card attached\");\r\n        return;\r\n    }\r\n\r\n}\r\n\r\n\r\nvoid loop() {\r\n    \r\n  readFile(SD_MMC, \"\/Log.txt\");\r\n    \r\n    \r\n  \/\/ put your main code here, to run repeatedly:\r\n}\r\n\r\n\r\n\r\n\r\n\r\n\/\/Read a file in SD card\r\nvoid readFile(fs::FS &amp;fs, const char * path){\r\n\r\n    File LogFile = fs.open(path);\r\n    \r\n    if(!LogFile){\r\n        Serial.println(\"Failed to open file for reading\");\r\n        return;\r\n    }\r\n\r\n    while(LogFile.available()){\r\n        String line = LogFile.readStringUntil('\\n');\r\n        Serial.println(line);\r\n        delay(30);  \/\/ Mimics the live stream interval from the GY-521 sensor\r\n    }\r\n}\r\n<\/pre>\n<p>&nbsp;<\/p>\n<p>Reading the data from the MicroSD card was successful. The ESP32-Cam flash LED blinked when data was read. A delay was used to mimic the live stream interval from the GY-521 sensor, however this later proved to be inaccurate and at best an approximated guess. Below is that change made to the code.<\/p>\n<pre class=\"EnlighterJSRAW\" data-enlighter-language=\"raw\">delay(30); \/\/ Mimics the live stream interval from the GY-521 sensor<\/pre>\n<p>&nbsp;<\/p>\n<p>The serial data stream to console worked, but Processing did not show any stream data. The ESP32-Cam flash LED did not light, indicating no read functions. There is mention online about interrupts causing issues like this and referenced the following link, source &#8211; <a href=\"https:\/\/playground.arduino.cc\/Main\/DisablingAutoResetOnSerialConnection\/\">https:\/\/playground.arduino.cc\/Main\/DisablingAutoResetOnSerialConnection\/<\/a>. The issue was caused by an interrupt on the ESP32-Cam USB module board. This was corrected by isolating the connections from the ESP32-Cam to the USB module board. Only the following connections were used. Below are images of that configuration.<\/p>\n<p>Black &#8211; GND<br \/>\nRed &#8211; 5V<br \/>\nGreen &#8211; UTX<\/p>\n<p><a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-Cam.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-3932\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-Cam-195x300.png\" alt=\"\" width=\"195\" height=\"300\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-Cam-195x300.png 195w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-Cam-176x270.png 176w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-Cam.png 394w\" sizes=\"auto, (max-width: 195px) 100vw, 195px\" \/><\/a><br \/>\n<a href=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-USB-Module.png\"><img loading=\"lazy\" decoding=\"async\" class=\"aligncenter size-medium wp-image-3933\" src=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-USB-Module-201x300.png\" alt=\"\" width=\"201\" height=\"300\" srcset=\"https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-USB-Module-201x300.png 201w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-USB-Module-181x270.png 181w, https:\/\/www.cloudacm.com\/wp-content\/uploads\/2021\/12\/ESP32-USB-Module.png 435w\" sizes=\"auto, (max-width: 201px) 100vw, 201px\" \/><\/a><\/p>\n<p>Processing then handled the data stream from the ESP32-Cam module the same as it did from the GY-521. There was some other trouble with Processing on system with multiple COM ports. The workaround was to disable those unused COM ports in order for the stream to work. This is something to be aware of with multiple simultaneous Arduino Uno or ESP32-USB-Module connections<\/p>\n<p><iframe loading=\"lazy\" title=\"Processing 3 - Artificial Horizon and Compass\" src=\"https:\/\/player.vimeo.com\/video\/659829699?dnt=1&amp;app_id=122963\" width=\"640\" height=\"315\" frameborder=\"0\" allow=\"autoplay; fullscreen; picture-in-picture; clipboard-write\"><\/iframe><\/p>\n<p>These are some additional observations from the demonstration and expansion. Processing offers much more extensive visualization than the Arduino built in serial plotter. There is value in having playback options for stored data. The data stream playback rate doesn&#8217;t match the original source rate and proved to require a complex remedy. There is also noticeable drift of data values, an example of this is when the bearing does not return to its initial position, even though the sensor has.<\/p>\n<p>Here is a demonstration of using filters for the MPU-6050 to correct value drift.<\/p>\n<p><iframe loading=\"lazy\" title=\"MPU-6050 Data with a Complementary Filter\" width=\"640\" height=\"480\" src=\"https:\/\/www.youtube.com\/embed\/qmd6CVrlHOM?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>This is a link to the data-sheet of the MPU-6050<\/p>\n<p><a href=\"https:\/\/invensense.tdk.com\/wp-content\/uploads\/2015\/02\/MPU-6000-Datasheet1.pdf\">https:\/\/invensense.tdk.com\/wp-content\/uploads\/2015\/02\/MPU-6000-Datasheet1.pdf<\/a><\/p>\n<p>Here is a deeper dive into MPU-6050 programming with the Arduino<\/p>\n<p><iframe loading=\"lazy\" title=\"Ep. 57 Arduino Accelerometer &amp; Gyroscope Tutorial MPU-6050 6DOF Module\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/M9lZ5Qy5S2s?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n<p>In an earlier post, <a href=\"https:\/\/www.cloudacm.com\/?p=3655\">https:\/\/www.cloudacm.com\/?p=3655<\/a>, it was mentioned that software provided as a service has a tendency to cease without warning. Dashware was listed in that group. Although the Dashware software is still available for download as of this writing, <a href=\"http:\/\/www.dashware.net\/dashware-download\/\">http:\/\/www.dashware.net\/dashware-download\/<\/a>, it has been acquired by GoPro and has an extensive terms of use agreement. Fundamentally, the engine that provides the data visualization is FFMPEG, which is largely hidden by the end user interface&#8217;s black box. There are tutorial videos on how to operate the software available here, <a href=\"http:\/\/www.dashware.net\/tutorial-videos\/\">http:\/\/www.dashware.net\/tutorial-videos\/<\/a>. GoPro has acquired other visualization providers, with the addition of ReelSteady to their portfolio. ReelSteady developed video stabilization technology, something that FFMPEG can perform as well. The trend of GoPro appears to be one of reinventing itself away from being just an action camera. Some may argue it&#8217;s too little or too late. Unlike the fate that visited Contour years before, <a href=\"https:\/\/www.geekwire.com\/2013\/contour-cofounder-we-showing-world-do\/\">https:\/\/www.geekwire.com\/2013\/contour-cofounder-we-showing-world-do\/<\/a>, GoPro now competes with a pervasive vast sea of image sensors in mobile devices. Unfortunately, if GoPro were to fail, that failure also encompasses the technology it has acquired.<\/p>\n<p><iframe loading=\"lazy\" title=\"What Happened To GoPro?\" width=\"640\" height=\"360\" src=\"https:\/\/www.youtube.com\/embed\/xzYnAZkayRc?feature=oembed\" frameborder=\"0\" allow=\"accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share\" referrerpolicy=\"strict-origin-when-cross-origin\" allowfullscreen><\/iframe><\/p>\n","protected":false},"excerpt":{"rendered":"<p>The purpose of this post is to demonstrate how to use Processing to present data in a visual way. The methods used in this post will be based on the work done by M.Furkan Bahat. Source &#8211; https:\/\/mfurkanbahat.blogspot.com\/2014\/11\/artificial-horizon-and-compass-using.html The hardware used in this demonstration will be the following: GY-521 MPU6050 Arduino Uno ESP32-Cam ESP32-USB-Module Patch wiring USB cables Windows 10 laptop The MPU6050 is connected to the Arduino Uno using the following pin connections, as shown in the images below&#8230;.<\/p>\n<p class=\"read-more\"><a class=\"btn btn-default\" href=\"https:\/\/www.cloudacm.com\/?p=3920\"> Read More<span class=\"screen-reader-text\">  Read More<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"closed","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[7,12],"tags":[],"class_list":["post-3920","post","type-post","status-publish","format-standard","hentry","category-arduino","category-esp32-cam"],"_links":{"self":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3920","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3920"}],"version-history":[{"count":8,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3920\/revisions"}],"predecessor-version":[{"id":3935,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=\/wp\/v2\/posts\/3920\/revisions\/3935"}],"wp:attachment":[{"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3920"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3920"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.cloudacm.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3920"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}