Brandon Roots
  • INTERACTIVE
  • FILM
  • ABOUT
  • CONTACT
October 28, 2020

Lab: Two-Way (Duplex) Serial Communication Using An Arduino and P5.js

broots ITP, Physical Computing

In this lab I combined a number of capabilities to create a pointing and selecting device (mouse) that uses two-way (duplex) communication.

Here is the circuit I used along with a Fritzing diagram:

Code with some smoothing to the readings:

#include <Arduino_LSM6DS3.h> // This library reads sensor values from the Intertial Measurement Unit (IMU).

  // digital input
  const int buttonPin = 2;

  // buffer to smooth readings
  int xBuffer[] = {0,0,0};
  int yBuffer[] = {0,0,0};
 
void setup() {
  // configure the serial connection:
  Serial.begin(9600);
  // configure the digital input:
  pinMode(buttonPin, INPUT);
  // Initialize the IMU sensor
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (true); // halt program
  }

  Serial.println("IMU initialized!");
}
 
void loop() {

  // Local variables for orientation data from IMU
  float aX, aY, aZ;
  float gX, gY, gZ;
  const char * spacer = ", ";

  // Try to read the IMU sensor
  if (
    IMU.accelerationAvailable() 
    && IMU.gyroscopeAvailable()
  ) {      
    IMU.readAcceleration(aX, aY, aZ);
    IMU.readGyroscope(gX, gY, gZ);

    // adjust the sensor values to be closer to 10bit analog reads
    aX = (aX + 1)*512;
    aY = (aY + 1)*512;

    // do some smoothing to reads
    aX = (xBuffer[0] + xBuffer[1] + xBuffer[2] + aX)/4;
    aY = (yBuffer[0] + yBuffer[1] + yBuffer[2] + aY)/4;

    // save new value to buffer
    xBuffer[2] = xBuffer[1];
    xBuffer[1] = xBuffer[0];
    xBuffer[0] = aX;
    yBuffer[2] = yBuffer[1];
    yBuffer[1] = yBuffer[0];
    yBuffer[0] = aY;

    // print the X rotation:
    Serial.print(aX);
    Serial.print(",");
   
    // print the y rotation:
    Serial.print(aY);
    Serial.print(",");
   
    // read the button:
    int sensorValue = digitalRead(buttonPin);
    // print the results:
    Serial.println(sensorValue);
  }
}

Taking a look at the X and Y axis IMU readings in the serial plotter I noticed there was quite a lot of noise and decided to implement a buffer that averages out the last four readings. Example of the difference here:

Now to use this serial output to control a P5 sketch I shifted to the code running on my computer that the Arduino was connected to. You can see the P5 sketch here.

With this function working I next modified the code for the Arduino to only send data after a “handshake” with the computer. The benefit of this approach is that it will only send data when requested and reduces the chance of a buffer overflow.

Here is the modified Arduino code:

#include <Arduino_LSM6DS3.h> // This library reads sensor values from the Intertial Measurement Unit (IMU).

  // digital input
  const int buttonPin = 2;

  // buffer to smooth readings
  int xBuffer[] = {0,0,0};
  int yBuffer[] = {0,0,0};
 
void setup() {
  // configure the serial connection:
  Serial.begin(9600);

  // Initialize the IMU sensor
  if (!IMU.begin()) {
    //Serial.println("Failed to initialize IMU!");
    while (true); // halt program
  }
  //Serial.println("IMU initialized!");
  
  while (Serial.available() <= 0) {
    Serial.println("hello"); // send a starting message
    delay(300);              // wait 1/3 second
  }
  // configure the digital input:
  pinMode(buttonPin, INPUT);
  
}
 
void loop() {
    
  // Local variables for orientation data from IMU
  float aX, aY, aZ;
  float gX, gY, gZ;
  const char * spacer = ", ";

  // Try to read the IMU sensor
  if (
    IMU.accelerationAvailable() 
    && IMU.gyroscopeAvailable()
  ) {      
    IMU.readAcceleration(aX, aY, aZ);
    IMU.readGyroscope(gX, gY, gZ);

    // adjust the sensor values to be closer to 10bit analog reads
    aX = (aX + 1)*512;
    aY = (aY + 1)*512;

    // do some smoothing to reads
    aX = (xBuffer[0] + xBuffer[1] + xBuffer[2] + aX)/4;
    aY = (yBuffer[0] + yBuffer[1] + yBuffer[2] + aY)/4;

    // save new value to buffer
    xBuffer[2] = xBuffer[1];
    xBuffer[1] = xBuffer[0];
    xBuffer[0] = aX;
    yBuffer[2] = yBuffer[1];
    yBuffer[1] = yBuffer[0];
    yBuffer[0] = aY;

    if (Serial.available() > 0) {
      
      // print the X rotation:
      Serial.print(aX);
      Serial.print(",");
     
      // print the y rotation:
      Serial.print(aY);
      Serial.print(",");
     
      // read the button:
      int sensorValue = digitalRead(buttonPin);
      // print the results:
      Serial.println(sensorValue);
      
    }
  } 
}

I did run into an issue with this arrangement where the “handshake” would cause a stream of values rather than a single set:

Finally I took what I learned in this lab and tried it out with my own sketch, this time using my Dodeca enclosure and mapping the movement to a 3D object in a P5.js sketch. Arduino code below.

#include <Arduino_LSM6DS3.h> // This library reads sensor values from the Intertial Measurement Unit (IMU).
#include "MadgwickAHRS.h" // This library generates Euler angle measures from the IMU.

  // digital input
  const int buttonPin = 2;

  // Initialize a Madgwick filter:
  Madgwick filter;

  // Set sensor's sample rate to 104 Hz to improve accuracy of Euler angle interpretations by Madgwick library
  const float sensorRate = 104.00;

  // buffer to smooth readings
  int xBuffer[] = {0,0,0};
  int yBuffer[] = {0,0,0};

  // LED indicator info
  int ledPin = 17;
  int brightness = 1;
  int fadeAmount = 1;
 
void setup() {
  // configure the serial connection:
  Serial.begin(9600);

  // Initialize the IMU sensor
  if (!IMU.begin()) {
    Serial.println("Failed to initialize IMU!");
    while (true); // halt program
  }
  
  // Start the filter to run at the sample rate
  filter.begin(sensorRate);
  
  Serial.println("IMU initialized!");
  
  // configure the digital input:
  pinMode(buttonPin, INPUT);
  
}
 
void loop() {

  pulseLED();
    
  // Local variables for orientation data from IMU
  float aX, aY, aZ;
  float gX, gY, gZ;
  const char * spacer = ", ";

  // Local variables for orientation from Madgwick library
  float roll, pitch, heading;

  // Try to read the IMU sensor
  if (
    IMU.accelerationAvailable() 
    && IMU.gyroscopeAvailable()
  ) {      
    IMU.readAcceleration(aX, aY, aZ);
    IMU.readGyroscope(gX, gY, gZ);

    // Update the filter, which computes orientation
    filter.updateIMU(gX, gY, gZ, aX, aY, aZ);

    // Print the heading, pitch and roll
    roll = filter.getRoll() + 180; // 180 to -180 y leaves x unchanged
    pitch = filter.getPitch() + 90; // 90 to -90 x leaves y unchanged

    // adjust the sensor values to be closer to 10bit analog reads
    //aX = (aX + 1)*512;
    //aY = (aY + 1)*512;

    // do some smoothing to reads
    pitch = (xBuffer[0] + xBuffer[1] + xBuffer[2] + pitch)/4;
    roll = (yBuffer[0] + yBuffer[1] + yBuffer[2] + roll)/4;

    // save new value to buffer
    xBuffer[2] = xBuffer[1];
    xBuffer[1] = xBuffer[0];
    xBuffer[0] = pitch;
    yBuffer[2] = yBuffer[1];
    yBuffer[1] = yBuffer[0];
    yBuffer[0] = roll;
      
    // print the X rotation:
    Serial.print(pitch);
    Serial.print(",");
   
    // print the y rotation:
    Serial.print(roll);
    Serial.print(",");
   
    // read the button:
    int sensorValue = digitalRead(buttonPin);
    // print the results:
    Serial.println(sensorValue);
      
  } 
}

void pulseLED(){
  // Write brightness to LED
  analogWrite(ledPin, brightness);

  brightness = brightness + fadeAmount;
  // Reverse the direction of the fading at the ends of the fade
  if(brightness == 1 || brightness == 254)
  {
    fadeAmount = -fadeAmount ;
  }   
  //Serial.print("fading LED to ");
  //Serial.println(brightness);
  delay(2);
}

Lab: Serial output from P5.js Markov Chains

Related Posts

Fractal Plant – Foiled by  Registers

Homemade Hardware, ITP, Solar Plant

Fractal Plant – Foiled by Registers

Since receiving the PCBs and successfully soldering the board together I have been trying to rewrite code for the I2C port expander. This has been immensely difficult! The Inkplate Arduino Library makes considerable use of an “Mcp” class, which is written to work with the MCP23017 GPIO expander IC. These chips are quite difficult to […]

“Handling” Playtest Week

Handling, ITP

“Handling” Playtest Week

Last week we attended “Playtest Thursday” on the second floor of 370 Jay St with our games. I came away from the experience with some very specific feedback. Seeing a number of people play the game showed me things I didn’t anticipate. Some folks approached the cabinet and immediately treated it as a touch screen. […]

Fractal Plant – Beta Build

Homemade Hardware, ITP, Solar Plant

Fractal Plant – Beta Build

The boards arrived! Amazingly within an hour of one another. Based on the experience I think that JLCPCB is a better value. With shipping OSHPark was $55.50 for 3 boards. JLCPCB was $26.36 for 10 boards. Aside from a higher cost OSHPark also left sharp bits of tabs around the edges of the boards which […]

Recent Posts

  • Fractal Plant – Foiled by  RegistersFractal Plant – Foiled by Registers
    May 9, 2022
  • “Handling” Playtest Week“Handling” Playtest Week
    May 5, 2022
  • Fractal Plant – Beta BuildFractal Plant – Beta Build
    April 24, 2022
Brandon Roots