Lab: Two-Way (Duplex) Serial Communication Using An Arduino and P5.js
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);
}






