software_setup

Tinkering Tuesday – Playmobil Stage – Software

In this part I will explain my final software. It is a compromise between the desired functionality and what is possible with the technology I used.


Past parts of this series:

Tinkering Tuesday – Playmobil Stage – Introduction

Tinkering Tuesday – Playmobil Stage – Playmobil parts
Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings
Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis using Sound Sensor Module

Tinkering Tuesday – Playmobil Stage – Woodwork


Here you can download the full Arduino sketch LEDController2.ino as a zip file.
But let’s start part by part.

Definitions

First the libraries for the stepper motor and the LED rings are included:

#include <Stepper.h>
#include <FastLED.h>

Next, the input pins are defined. The initial sensitivity is not relevant as it is set later in the setup(). The InputSwitch is the toggle switch, low is for the Auduino, high uses the sound sensor board.

//Inputs
#define AuxPin A0
#define MicPin A1
#define SensitivityPin A2
int sensitivity = 400;
#define InputSwitch 2 //LOW=A0/Aux, HIGH=A1/Mic

The next part is defining everything for the disco ball. First of the 4 pins, my motor has 4096 steps. With these information the Stepper library is initialized.
The three variables are used later on for controlling the time the discoball is used.

//DiscoBall
#define motorpin1  7
#define motorpin2  8
#define motorpin3  12
#define motorpin4  13
#define STEPS 4096
Stepper stepper(STEPS, motorpin1, motorpin2, motorpin3, motorpin4);
bool isDiscoTime = false;
unsigned long discoStartTime = 0;
unsigned long discoEndTime = 0;

The definitions for the FastLED library are mostly copy/paste from the NoisePlusPalette sample code. As this uses a matrix I just set it to be 6*8=48 LEDs although I only have 47 connected…

//FastLED
#define LED_PIN     6
const uint8_t kMatrixWidth  = 6;
const uint8_t kMatrixHeight = 8;
const bool    kMatrixSerpentineLayout = true;
CRGB leds[kMatrixWidth * kMatrixHeight];
#define NUM_LEDS (kMatrixWidth * kMatrixHeight)
#define MAX_DIMENSION ((kMatrixWidth>kMatrixHeight) ? kMatrixWidth : kMatrixHeight)
#define BRIGHTNESS  20
#define LED_TYPE    WS2812
#define COLOR_ORDER GRB
//The 16 bit version of LED coordinates
static uint16_t x;
static uint16_t y;
static uint16_t z;
// We're using the x/y dimensions to map to the x/y pixels on the matrix.  We'll
// use the z-axis for "time".  speed determines how fast time moves forward.  Try
// 1 for a very slow moving effect, or 60 for something that ends up looking like
// water.
uint16_t speed = 1; // speed is set dynamically once we've started up
// Scale determines how far apart the pixels in our noise matrix are.  Try
// changing these values around to see how it affects the motion of the display.  The
// higher the value of scale, the more "zoomed out" the noise iwll be.  A value
// of 1 will be so zoomed in, you'll mostly see solid colors.
uint16_t scale = 30; // scale is set dynamically once we've started up
// This is the array that we keep our computed noise values in
uint8_t noise[MAX_DIMENSION][MAX_DIMENSION];
CRGBPalette16 currentPalette( PartyColors_p );
uint8_t       colorLoop = 1;

The defines for the LEDs are rather simple. I have one pin for controlling all the yellow LEDs, one pin for the red ones and two seperate ones for the white spots for the disco ball. If you wonder why I don’t have one for the blue LEDs: I simply can’t control them. I wanted to and everything was soldered for it but I messed up the wiring (I shorted the MOSFET) and so the blue LEDs are always on when there is power connected to the board. Better than always of… I wasn’t able to repair it because I first hot-glued the board in place and then realized my mistake (I was checking each LED but just checked whether it was lighting up…well, it did).
Here there are also start and end time variables as for the stepper motor. I will come to that later.

//LEDs
#define LEDRedPin 11
#define LEDWhiteRPin 9
#define LEDWhiteLPin 10
#define LEDYellowPin 3
bool isRedOn = false;
unsigned long redStartTime = 0;
unsigned long redEndTime = 0;
bool isYellowOn = false;
unsigned long yellowStartTime = 0;
unsigned long yellowEndTime = 0;

The next section is about the variables for the sound measurements. This is mostly copy/paste from the basic input sketch.

//Input Measuring
bool isAuxInput = false;
long signalInput = 0;
long signalAvgAdd = 0;
long signalAvg = 0;
long signalMax = 0;
long signalMin = 1024;
long signalSpan = 0;
long measureCount = 0;
#define MicSamples 1024
bool isInput = false;
long silenceStart = 0;

setup()

The setup method is also rather simple. It initializes the serial connection (for debug purposes), all the pins and the variables that are set from the switch or potentiometer. Also some other basic initializations are made for the stepper or the LED rings.

void setup() {

delay( 3000 ); // power-up safety delay
Serial.begin(115200);
pinMode(LEDRedPin, OUTPUT);
pinMode(LEDWhiteRPin, OUTPUT);
pinMode(LEDWhiteLPin, OUTPUT);
pinMode(LEDYellowPin, OUTPUT);

pinMode(InputSwitch, INPUT_PULLUP);
if(digitalRead(InputSwitch) == LOW) {
isAuxInput = true;
} else {
isAuxInput = false;
}
//Serial.print("InputMode (LOW=A0/Aux, HIGH=A1/Mic): "); Serial.println(digitalRead(InputSwitch));

pinMode(SensitivityPin, INPUT);
sensitivity = analogRead(SensitivityPin);
Serial.print("Sensitivity: "); Serial.println(sensitivity);

stepper.setSpeed(2);

LEDS.addLeds<LED_TYPE,LED_PIN,COLOR_ORDER>(leds,NUM_LEDS);
LEDS.setBrightness(BRIGHTNESS);
FastLED.setMaxPowerInVoltsAndMilliamps(5,500);

// Initialize LED coordinates to some random values
x = random16();
y = random16();
z = random16();
}

loop()

I have to admit I was a bit lazy in structuring the code and so it evolved more or less in the loop method. First step in every loop is checking the input mode:

//Read Input Mode
if(digitalRead(InputSwitch) == LOW) {
isAuxInput = true;
} else {
isAuxInput = false;
}

Next step is the audio measurement. This only happens if the variable isDiscoTime is not set. The reason for this is the computing power that is required to drive the stepper motor. The Arduino is not really capable of driving the stepper motor and sampling the data. I tried parallelizing it but got measurements which went for several seconds. Even when I reduced my collected samples from 1024 to 256 or something like that it was not quite responsive. So I decided to make disco time a standalone feature.
The audio measurement itself is then also blocking but that is okay with the other functions (which are then a bit slowed down but that doesn’t lead to stuttering). This is also mostly a copy/paste from the basic input sketch.

//Audio Measurement
if(!isDiscoTime) {
for (int i = 0; i < MicSamples; i++) {
if(measureCount > MicSamples) {
signalAvg = signalAvgAdd/MicSamples;
signalSpan = signalMax - signalMin;
//Serial.println(" signalAvg: " + String(signalAvg));
measureCount = 0;
signalAvgAdd = 0;
signalMax = 0;
signalMin = 1024;
} else {

if(isAuxInput) {
signalInput = analogRead(AuxPin);
} else {
signalInput = analogRead(MicPin);
}
//Serial.println(" signalInput: " + String(signalInput));
signalAvgAdd += signalInput;
signalMin = min(signalMin, signalInput);
signalMax = max(signalMax, signalInput);
measureCount++;
}
}
}

Next of I am checking whether there is input at all. For the input from the Auduino I am just using a threshold value of 50 (to be tolerant about background noise). For the sound sensor board I am using the input from the sensitivity potentiometer. The amplitude or signal span is compared to the sensivity (I mapped it to a lower value as it is the span…) and if it is below that threshold for more than 2 seconds I decided that there is no input recognized and all the effects are turned off (see method setAllOff() below). This is also suitable for song parts with lower volumes or breaks. As soon as the signal span is above the threshold there is input.

sensitivity = analogRead(SensitivityPin);
if((isAuxInput && signalAvg < 50) or (!isAuxInput && signalSpan < map(sensitivity, 0, 1023, 0, 450))) {
if(isInput) {
if(silenceStart == 0) {
silenceStart = millis();
Serial.println("SilenceStart");
}
if(millis()-silenceStart > 2000) {
Serial.println("AllOff");
setAllOff();
isInput = false;
}
}
} else {
if(!isInput) {
isInput = true;
Serial.println("Input recognized" + String(isInput));
} else {
silenceStart = 0;
}
}

The next part is only when there is an input signal and first checks whether the disco ball is off and there was a certain time since the last disco time. The threshold to the next disco time is a random number of milliseconds and allows a disco time only after at least 30 seconds but maximum of 3 minutes. If the threshold has passed then disco time is activated. It is active for a random time between 4 and 15 seconds and also randomly uses only one of the white LEDs or both.

If disco time is activated the stepper motor is turned and checked whether disco time has to be ended. If disco time is not active the LED rings are adjusted (remember, they are not active while the disco ball is turning). These are also the standard methods of the NoisePlusPalette sketch.

The last thing to do if there is input is controlling the yellow and the red LEDs. Those are also set to random values at random intervals. I know, now you are thinking of all the complicated input analysis stuff and how to be sound reactive and such from my last posts. Unfortunately it turned out when putting it all together, the input measuring, the LED rings, the stepper motor and the standard LEDs, together with two different input signals that are very different in behaviour, first the Arduino came to its limits in processing so I started to reduce everything. Then I rebuilt the functions but have to admit in the end the time was the biggest constraint. So I just went with random spotlights. Which due to the different thresholds works quite well!

if(isInput) {
//Disco Ball
if(!isDiscoTime && discoEndTime+random(30000,180000) <= millis()) {
setAllOff();
isDiscoTime = true;
discoStartTime = millis();
discoEndTime = millis() + random(4000,15000);
Serial.println(" Disco Time!!! Start: "+String(discoStartTime)+" End: "+String(discoEndTime));

switch(random(1,4)) {
case 1: //Both LEDs
analogWrite(LEDWhiteRPin ,250);
analogWrite(LEDWhiteLPin ,250);
Serial.println(" Both LEDs");
break;
case 2: //Left LED
analogWrite(LEDWhiteLPin ,250);
Serial.println(" Left LED");
break;
case 3: //Right LED
analogWrite(LEDWhiteRPin ,250);
Serial.println(" Right LED");
break;
}//switch
}//if

if(isDiscoTime) {
//turn stepper motor
stepper.step(1);
if(discoEndTime <= millis()){
isDiscoTime = false;
setAllOff();
Serial.println(" No Disco Time!!! ");
}
} else {

// Periodically choose a new palette, speed, and scale
ChangePaletteAndSettingsPeriodically();
fillnoise8();
mapNoiseToLEDsUsingPalette();
LEDS.show();
}
if(!isRedOn && redEndTime+random(300,7500) <= millis()) {
analogWrite(LEDRedPin, random(50,250));
redStartTime = millis();
isRedOn = true;
redEndTime = redStartTime + random(300, 2000);
} else if (isRedOn && redEndTime < millis()) {
isRedOn = false;
analogWrite(LEDRedPin, 0);
}

if(!isYellowOn && yellowEndTime+random(300,400) <= millis()) {
analogWrite(LEDYellowPin, random(100,250));
yellowStartTime = millis();
isYellowOn = true;
yellowEndTime = yellowStartTime + random(300, 5000);
} else if (isYellowOn && yellowEndTime < millis()) {
isYellowOn = false;
analogWrite(LEDYellowPin, 0);
}
}

setAllOff()

This method just deactivates all of the LEDs.

void setAllOff() {
analogWrite(LEDRedPin, 0);
analogWrite(LEDYellowPin, 0);
analogWrite(LEDWhiteRPin, 0);
analogWrite(LEDWhiteLPin, 0);
LEDS.clear();
LEDS.show();
}

FastLED Methods

The methods are also copy/paste from the NoisePlusPalette sketch. Except for the ChangePaletteAndSettingsPeriodically method where I added at least a bit of the input signal to control the LED rings. I adjust the speed of the rings light change with the signal span, see highlighted lines.

////////////////////////////////////////////////////////////////////////
//// FastLED NoisePlusPalette
////////////////////////////////////////////////////////////////////////

// Fill the x/y array of 8-bit noise values using the inoise8 function.
void fillnoise8() {
// If we're runing at a low "speed", some 8-bit artifacts become visible
// from frame-to-frame.  In order to reduce this, we can do some fast data-smoothing.
// The amount of data smoothing we're doing depends on "speed".
uint8_t dataSmoothing = 0;
if( speed < 50) {
dataSmoothing = 200 - (speed * 4);
}

for(int i = 0; i < MAX_DIMENSION; i++) {
int ioffset = scale * i;
for(int j = 0; j < MAX_DIMENSION; j++) {
int joffset = scale * j;

uint8_t data = inoise8(x + ioffset,y + joffset,z);

// The range of the inoise8 function is roughly 16-238.
// These two operations expand those values out to roughly 0..255
// You can comment them out if you want the raw noise data.
data = qsub8(data,16);
data = qadd8(data,scale8(data,39));

if( dataSmoothing ) {
uint8_t olddata = noise[i][j];
uint8_t newdata = scale8( olddata, dataSmoothing) + scale8( data, 256 - dataSmoothing);
data = newdata;
}

noise[i][j] = data;
}
}

z += speed;

// apply slow drift to X and Y, just for visual variation.
x += speed / 8;
y -= speed / 16;
}

void mapNoiseToLEDsUsingPalette()
{
static uint8_t ihue=0;

for(int i = 0; i < kMatrixWidth; i++) {
for(int j = 0; j < kMatrixHeight; j++) {
// We use the value at the (i,j) coordinate in the noise
// array for our brightness, and the flipped value from (j,i)
// for our pixel's index into the color palette.

uint8_t index = noise[j][i];
uint8_t bri =   noise[i][j];

// if this palette is a 'loop', add a slowly-changing base value
if( colorLoop) {
index += ihue;
}

// brighten up, as the color palette itself often contains the
// light/dark dynamic range desired
if( bri > 127 ) {
bri = 255;
} else {
bri = dim8_raw( bri * 2);
}

CRGB color = ColorFromPalette( currentPalette, index, bri);
leds[XY(i,j)] = color;
}
}

ihue+=1;
}

// There are several different palettes of colors demonstrated here.
//
// FastLED provides several 'preset' palettes: RainbowColors_p, RainbowStripeColors_p,
// OceanColors_p, CloudColors_p, LavaColors_p, ForestColors_p, and PartyColors_p.
//
// Additionally, you can manually define your own color palettes, or you can write
// code that creates color palettes on the fly.

// 1 = 5 sec per palette
// 2 = 10 sec per palette
// etc
#define HOLD_PALETTES_X_TIMES_AS_LONG 1

void ChangePaletteAndSettingsPeriodically()
{
uint8_t secondHand = ((millis() / 1000) / HOLD_PALETTES_X_TIMES_AS_LONG) % 60;
static uint8_t lastSecond = 99;
if(isAuxInput) {
speed = signalSpan/random(10,50);
} else {
speed = signalSpan;
}

if( lastSecond != secondHand) {
lastSecond = secondHand;
if( secondHand ==  0)  { currentPalette = RainbowColors_p;         scale = 30; colorLoop = 1; }
if( secondHand ==  5)  { SetupPurpleAndGreenPalette();             scale = 50; colorLoop = 1; }
if( secondHand == 10)  { SetupBlackAndWhiteStripedPalette();       scale = 30; colorLoop = 1; }
if( secondHand == 15)  { currentPalette = ForestColors_p;          scale =120; colorLoop = 0; }
if( secondHand == 20)  { currentPalette = CloudColors_p;           scale = 30; colorLoop = 0; }
if( secondHand == 25)  { currentPalette = LavaColors_p;            scale = 50; colorLoop = 0; }
if( secondHand == 30)  { currentPalette = OceanColors_p;           scale = 90; colorLoop = 0; }
if( secondHand == 35)  { currentPalette = PartyColors_p;           scale = 30; colorLoop = 1; }
if( secondHand == 40)  { SetupRandomPalette();                     scale = 20; colorLoop = 1; }
if( secondHand == 45)  { SetupRandomPalette();                     scale = 50; colorLoop = 1; }
if( secondHand == 50)  { SetupRandomPalette();                     scale = 90; colorLoop = 1; }
if( secondHand == 55)  { currentPalette = RainbowStripeColors_p;   scale = 20; colorLoop = 1; }
}
}

// This function generates a random palette that's a gradient
// between four different colors.  The first is a dim hue, the second is
// a bright hue, the third is a bright pastel, and the last is
// another bright hue.  This gives some visual bright/dark variation
// which is more interesting than just a gradient of different hues.
void SetupRandomPalette()
{
currentPalette = CRGBPalette16(
CHSV( random8(), 255, 32),
CHSV( random8(), 255, 255),
CHSV( random8(), 128, 255),
CHSV( random8(), 255, 255));
}

// This function sets up a palette of black and white stripes,
// using code.  Since the palette is effectively an array of
// sixteen CRGB colors, the various fill_* functions can be used
// to set them up.
void SetupBlackAndWhiteStripedPalette()
{
// 'black out' all 16 palette entries...
fill_solid( currentPalette, 16, CRGB::Black);
// and set every fourth one to white.
currentPalette[0] = CRGB::White;
currentPalette[4] = CRGB::White;
currentPalette[8] = CRGB::White;
currentPalette[12] = CRGB::White;

}

// This function sets up a palette of purple and green stripes.
void SetupPurpleAndGreenPalette()
{
CRGB purple = CHSV( HUE_PURPLE, 255, 255);
CRGB green  = CHSV( HUE_GREEN, 255, 255);
CRGB black  = CRGB::Black;

currentPalette = CRGBPalette16(
green,  green,  black,  black,
purple, purple, black,  black,
green,  green,  black,  black,
purple, purple, black,  black );
}

//
// Mark's xy coordinate mapping code.  See the XYMatrix for more information on it.
//
uint16_t XY( uint8_t x, uint8_t y)
{
uint16_t i;
if( kMatrixSerpentineLayout == false) {
i = (y * kMatrixWidth) + x;
}
if( kMatrixSerpentineLayout == true) {
if( y & 0x01) {
// Odd rows run backwards
uint8_t reverseX = (kMatrixWidth - 1) - x;
i = (y * kMatrixWidth) + reverseX;
} else {
// Even rows run forwards
i = (y * kMatrixWidth) + x;
}
}
return i;
}
100917_1349_TinkeringTu13.png

Tinkering Tuesday – Playmobil Stage – Woodwork

Planning and assembling the wood and components.


Past parts of this series:

Tinkering Tuesday – Playmobil Stage – Introduction
Tinkering Tuesday – Playmobil Stage – Playmobil parts
Tinkering Tuesday – Playmobil Stage – LED Stage Lights
Tinkering Tuesday – Playmobil Stage – Disco Ball with stepper motor
Tinkering Tuesday – Playmobil Stage – LED Rings
Tinkering Tuesday – Playmobil Stage – Audio Output from microSD with Arduino
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis from WAV input with simple logic, FHT and MSGEQ7
Tinkering Tuesday – Playmobil Stage – Audio Spectrum Analysis using Sound Sensor Module


When I planned the stage I did not really think about the dimensions it needs. So I just layed out some DIN A4 sized sheets of paper and had a look:

That is DIN A3 and looks quite good already. But I need some space on sides as well (for all of the electronics) so I planned to make a typical stage as can be seen on festivals, e.g. Rock am Ring:

So I initially planned with DIN A3 size for the stage and half a DIN A4 sheet for the sides. I thought about using precut wood like the following that is available in the DIN Ax sizes:

But then I also decided that it is not very cool to cut and merge different sheets to get to the DIN A3 plus DIN A4 size. So I decided I need to get that custom cut at the home improvement store and went with 5mm MDF. That’s when I came up with the following sketch, planning with the 4mm A4 precut sheets and the custom 5mm sheets:

I had a look in the different stores and some only offered 3mm and I also wanted to go with premade laths for the top and bottom of the stage (30 and 50mm in the sketch, at the top I need minimum 22mm to fit the stepper motor which will drive the disco mirror ball). Those are also supplied in different sizes in different stores. I planned to make the top sheet overlap the back and the side sheets, so it has to be wider by the width of the wood I buy.

I decided to make an excel sheet to dynamically calculate the required sizes which include the different material widths.

It calculates the required sizes based on the given dimensions of the stage and the width of the wood. I also added the height of the top and bottom as a variable to adjust to the different sizes of the laths, in this case it was Bauhaus with 24x48mm. I adjusted the inner size of the stage in the excel to 250mm widht because I plan to have a sheet of acrylic glass in the back and Bauhaus offered a precut sheet with a height of 250mm. Additionally the stage size was reduced to 600mm as the laths are 2m long and this way I can cut it into 3 parts and still have 200mm which I can use for the sides.

Unfortunately the wood I initially wanted (MDF, 3mm, black coated) was not available in the Bauhaus. It could have been ordered but 2-3 weeks were too much for me. So I went with 5mm raw MDF and decided to paint it myself.

After I got all the wood cut I layed it out and wondered why it didn’t fit. After several measurements I gave the guy at the wood cutting station the wrong numbers. I read columns D and E instead of E and F… So another trip to the store… But luckily it wasn’t that many sheets that were wrong. So: always check/measure twice before cutting and joining.

In the following picture you can see how I want to layout the acrylic glas and the sides:

In the following pictures you can see the final layout of the stage put together loosely.

Then I went to my little workbench in the cellar and build up the base using mostly my newly acquired Dremel 4000. First drilling and counter sinking all the holes

After that I put it the base together:

The next step was to paint everything:

Then adding all the components. First the servo from the top and the bottom:

Next the rods for the stage lights (I just drilled through everything and the outer panel is holding it in place):

I sanded the acrylic glass, which can be seen in the background here:

All of the other components are going into the left side of the stage, hot-glued in place:

Putting all the LED stage lights and LED rings in place (see the posts for those). I initially planned on using a USB connector for PC cases to power both of the Arduinos. This caused some weird sounds when the servo or especially the LED rings were turned on. I tried several things, also having some capacitors in several places (I wasn’t aware of the principle of decoupling capacitors at that time… now I am at least a little bit…) but nothing worked out. My first tests were done using two separate power sources (240V to USB adaptors from smart phones) and at that time there were no problems, so I had to go with two sepearate power cables. One for the Audio Arduino alone and the other one for all the light components and the other Arduino.

The sound sensor is facing to the front:

All of the outer sheets were fixed with black nails but to be able to reopen the stage to get to the electronics parts (which I already did once to check the speaker cabling because it was not loud enough) I used screws at the left side and glued on black plastic caps to have a better look (you can also see that it is not 100% perfect… I am not the most precise woodworker…):

At the back of the stage there is the push button, a switch, the potentiometer and the two cables (secured with hot glue and a rubber from a beer bottle):

This was the woodworking part. Next up is the final Software. After that I will share some problems, learnings and additional possibilities (that I planned but lacked time).