Project 1: Dodeca – Part 2
This week Mai and I made some great progress on our musical Dodecahedron. Feeling pretty confident about the circuit on the breadboard we moved to a more compact arrangement on a soldered board.
We met in person on the floor at ITP to make a second Dodeca so we could both have one at home to code with. Mai was excited to solder for the first time and I shared what I have learned about soldering from Deqing Sun’s course Prototyping Electric Devices. She was a natural of course!
While Mai was constructing the second Dodeca I spent some time on the floor reviewing the Arduino Sketch we had put together. Our sketch makes use of a number of libraries and I added a bunch of comments in the code below to clarify what everything is doing. You can find the current code at the bottom of this post.
The goal of mapping specific facets of the dodeca to notes turned into a much bigger challenge than I expected and I learned a lot about IMU functions in the process. Using an enclosure with numbers on the facets matching the points in our code helped a lot with this.
We ended up using four different libraries to get from the point of reading the IMU sensor to determining which facet has the highest Y value in 3D space. Reading ahead to the IMU labs for physical computing Mai pointed out the “MadgwickAHRS.h” library that translates the sensor readings from the Arduino Nano 33 into Euler Angles (pitch, roll, yaw). Experimenting with this I found that the IMU of the Nano 33 IoT does not have a magnetometer which appeared to introduce a constant drift to the Z-axis, or yaw, readings, using the Madgwick library. Because the facets are mapped in a constant relationship to the sensor, as they are physically attached to one another, I realized that we could abandon the Z-axis/yaw readings all together and calculate rotations using only X-axis/pitch and Y-axis/roll.
After returning the values for roll and pitch from the Madgwick library in radians we can calculate the rotation of the 12 facet midpoints of the dodecahedron in 3D space then check through the midpoint of each facet to determine which one is up!
And our final dodeca with the duration of sound tied to movement:
#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.
#include <BasicLinearAlgebra.h> // This library is required by the Geometry library below.
#include <Geometry.h> // This library uses BasicLinearAlgebra to calculate rotations of the points representing the 12 facets of the dodecahedron in 3D space.
#include "pitches.h" // This tab holds values for pitches of different notes.
/*
Dodeca Instrument
A musical instrument housed in a dodecahedron enclosure that runs on the Arduino Nano 33 IoT.
Dodeca creates notes with tone output based on rotational measurements from the Nano 33 IoT internal IMU sensor.
Made with help from:
IMU lab from Tom Igoe IPC : https://itp.nyu.edu/physcomp/labs/lab-serial-imu-output-to-p5-js/
How to read Arduino Nano IoT 33 IMU: https://desertbot.io/blog/arduino-nano-33-iot-imu
Arduino Floating Point Constants: https://forum.arduino.cc/index.php?topic=527608.0
Geometry Library: https://github.com/tomstewart89/Geometry
Datasheet for LM386: https://www.jameco.com/Jameco/Products/ProdDS/839826.pdf
Basic LM386 Amplifier Circuit: https://www.circuitbasics.com/build-a-great-sounding-audio-amplifier-with-bass-boost-from-the-lm386/
Musical Scales: https://en.wikipedia.org/wiki/Pentatonic_scale
created 5 Oct 2020
by Brandon Roots & Magdalena Claro
*/
// Initialize a Madgwick filter that will be used to calculate the Euler angles pitch, roll, yaw
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;
// Create points representing center of 12 facets of a dodecahedron in 3D space, plus additional 13-24 map to transforms
Point p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16, p17, p18, p19, p20, p21, p22, p23, p24;
// Major pentatonic scale
int notes[] = {
NOTE_AS5, NOTE_GS5, NOTE_FS5, NOTE_DS5, NOTE_CS5, NOTE_AS4, NOTE_GS4, NOTE_FS4, NOTE_DS4, NOTE_CS4, NOTE_AS3, 0
};
// Variabel to track which facet was last facing up
int lastUpwardFacet = 0;
// LED indicator info
int ledPin = 17;
int brightness = 1;
int fadeAmount = 1;
// Movement threshold to make sound
int movementThreshold = 7;
void setup() {
// Initialize serial
Serial.begin(9600);
// Setup LED
pinMode(ledPin, OUTPUT);
// 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!");
// Map values to points in X(), Y() & Z() of dodecahedron with radius of 1 - found these values by making an object in Cinema 4D
p1.X() = 0;
p1.Y() = 79.5;
p1.Z() = 5.8;
p2.X() = 0;
p2.Y() = 30.4;
p2.Z() = -73.7;
p3.X() = 64.5;
p3.Y() = 39.7;
p3.Z() = -24.5;
p4.X() = 46.7;
p4.Y() = 33.9;
p4.Z() = 54.9;
p5.X() = -46.7;
p5.Y() = 33.9;
p5.Z() = 54.9;
p6.X() = -64.5;
p6.Y() = 39.7;
p6.Z() = -24.5;
p7.X() = -46.7;
p7.Y() = 33.9;
p7.Z() = -54.9;
p8.X() = 46.7;
p8.Y() = 33.9;
p8.Z() = -54.9;
p9.X() = 64.5;
p9.Y() = -39.7;
p9.Z() = 24.5;
p10.X() = 0;
p10.Y() = -30.4;
p10.Z() = 73.7;
p11.X() = -64.5;
p11.Y() = -39.7;
p11.Z() = 24.5;
p12.X() = 0;
p12.Y() = -79.5;
p12.Z() = -5.8;
}
void loop() {
// 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.getRollRadians(); // 180 to -180 y leaves x unchanged
pitch = filter.getPitchRadians(); // 90 to -90 x leaves y unchanged
// Calculate rotation of 12 points in dodecahedron
// Rotation represents a rotated coordinate frame (a set of x/y/z axes) expressed with respect to a base frame.
Rotation R1;
// Rotate points using Euler angle data from Madgwick library (pitch, yaw, roll)
R1.FromEulerAngles(pitch, roll, M_PI_4);
// Correct for Arduino Nano orientation in Dodeca
R1.RotateX(-1.55);
// Apply rotations to second set of points
p13 = R1 * p1;
p14 = R1 * p2;
p15 = R1 * p3;
p16 = R1 * p4;
p17 = R1 * p5;
p18 = R1 * p6;
p19 = R1 * p7;
p20 = R1 * p8;
p21 = R1 * p9;
p22 = R1 * p10;
p23 = R1 * p11;
p24 = R1 * p12;
// Check which point+facet has largest Y value and is therefore facing UP
// Create array holding only Y values from points
float pointValuesY[12] = {p13.Y(), p14.Y(), p15.Y(), p16.Y(), p17.Y(), p18.Y(), p19.Y(), p20.Y(), p21.Y(), p22.Y(), p23.Y(), p24.Y()};
// Create array to hold values for point with largest value and that value
float maximumValueY[2] = {pointValuesY[0], 0};
// Loop thorough array to check for largest Y value then store in maximumValueY array
for (int i = 1; i < 12; i++) {
if (pointValuesY[i] > maximumValueY[0]) {
maximumValueY[0] = pointValuesY[i];
maximumValueY[1] = i;
}
}
// Local variable to hold current upwardFacet
int upwardFacet = maximumValueY[1];
// If a new facet is facing up play a tone
if (lastUpwardFacet != upwardFacet) {
// Do some things if a new facet is facing up
Serial.print("Facet ");
Serial.print(upwardFacet+1);
Serial.println(" is facing up");
// Record new upward facet
lastUpwardFacet = upwardFacet;
}
// Make a sound mapped to this point+facet if movement in gX, gY, or gZ sensed...
if (gX > movementThreshold || gY > movementThreshold || gZ > movementThreshold) {
// And not upsidedown...
if (aX < .90){
tone(2, notes[upwardFacet], 500);
// Make LED full brightness
analogWrite(ledPin, 255);
//noTone(8);
}
}
}
// Pulse LED if dodeca not rotated down
if (aX < .90){
// And not moving very much...
if (gX < movementThreshold && gY < movementThreshold && gZ < movementThreshold) {
// Make LED pulse
pulseLED();
}
}else{
// Turn off LED
analogWrite(ledPin, 0);
// Reset fading value to 1
brightness = 2;
}
}
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 ;
}
delay(2);
}










