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

Project 1: Dodeca – Part 2

broots ITP, Physical Computing

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);
}
Word Counting I Had A Dream Bot

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