Skip to content

Final Project Diary

My original idea is to create an animated diorama that plays a light and music show when you stay in front of it.

As a set design for this diorama, I thought about a replica of the Place Ducale, a place in my child town, Charleville-Mézières.

charleville

Spiral development

  1. Make the set with laser cutted facades, printed fountain and RGB leds
  2. Add music
  3. Add the movement detector
  4. Add an interface
  5. Add moving elements

Plan

Week Plan
Principles and Practices Sketch
Computer Aided Design Digital sketches and 3D models
Computer Controlled Cutting First prototype of the facades
3D Scanning and Printing Test print of the fountain with a 3D resin printer
Electronics Design Design a board for the RGB LEDs
Embedded Programming Control the LEDs
Output Devices Test some sound devices
Input Devices Test captors for triggering the diorama
Networking & Communications Handle the communication between the lights and music
Interface & Applications Create an interface to trigger the scenario
Project Development Make it work 🏗
Presentation time Shine ⭐

Principles and Practices

I draw a face view in real size: I think I will laser cut the facades and mold bricks later on to fix them on it. Moreover I could always reuse a brick mold for other dioramas.

I then draw a cut view of the facade to show where I could put the leds. I own several RGB leds, and Stéphane told me we have plenty in the lab, so I will check them too.

Face view Cut view

I finally draw a map and an axonometry (thanks to my brother who study architecture and showed me how) for picturing the all diorama. I still have to clean it and add the possible mechanisms, but I think the next week assignement (computer-aided design) will help me display a better idea of it all. Final project sketch

Computer Aided Design

I found a great site about the history of the fountain and great pictures I could use for modelling.

detail top view

I imported in FreeCAD the top view image of the fountain and designed a first version of it.

I then imported the fountain in Blender and added some fluid simulation. You can read the details of the rendering process on the assignment week. fountain with water

I also designed the houses in FreeCAD.

Computer Controlled Cutting

This week I designed a facade decoration: I’m not entirely happy with them for now but it’s a good start. proto

The prototype came out of the laser cutter a little lopsided, especially on the roof: it’s because of my file export in FreeCAD, I need to fix that. But the engraving is very nice, I will test it on other materials.

result

Electronic Production

This week was not very final project oriented, but I made the two programmers that I will be using during all the Fab Academy course and they were really helpful. I’m so glad I made them as early in the course!

3D Printing & Scanning

This week I added details on my 3D model with the Sculpting mode of Blender.

I then printed a first version of the foutain with the resin printer. You can find out more about this (messy) process in the assignment page.

My failures with the result gave me enough information to change my plans about my final project: I will scale it up to enhance the fountain.

Electronic Design

This week I wanted to design my own board according to my final project and my instructor encouraged me to do so. It would be an ideal exercise to learn KiCAD, and I also wanted to dedicate it to the development of dioramas. So I designed it with RGB leds and the possibility of connecting it to an SD card.

Features I want in my Arduino

  • Power outputs: 3.3V, 5V
  • DAC output to test the sound devices
  • Some RGB leds directly on the board
  • One test led
  • Exposure of the SPI port to be able to plug an SD card via another board
  • The possibility of plugging a mini oled screen (we have one on the lab and it’s very cute) via I2C

The result was too big, but I was very excited about it. But I realized later that a SAMD wouldn’t be the best option for my final project and change to the new generation of ATtiny instead.

Embedded Programming

This week I tested some RGB LEDs library without much success.

Before diving in C code, I wanted to give a try to the Adafruit library. I downloaded it and tested it with a simple code.

#include <Adafruit_NeoPixel.h>

#define PIN 23
#define NUMPIXELS 3

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
  pixels.begin();
}

void loop() {
  pixels.clear();

  for(int i=0; i<NUMPIXELS; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 150, 0));
    pixels.show();

    delay(500);
  }
}

I obtained no errors during compilation.

Sketch uses 10496 bytes (85%) of program storage space. Maximum is 12288 bytes.

And no errors during upload. However… nothing is happening on the board.

Verify successful
done in 0.055 seconds
CPU reset.

With the Logic Analyzer we own at the lab, I tried to understand what was happening. I made a simple test with one of my free available pin first, to check I I remembered correctly how to use it.

logic

I connected the free pin 4 and the ground to the logic analyzer, and send a simple blink code to the board. In PulseView, I configured a PWM protocol decoder and observed my LED blinking on the screen.

pwm

After that I searched for a protocol dedicated to RGB leds and configured it. I uploaded the code above (but change the PIN to 4) but observed nothing on PulseView. Another mistery…

rgb

Output Devices

This week I resolved the errors I was having with the Adafruit NeoPixel library !

As I was frustrated by my RGB leds, I tried again to understand why the Neopixel library compiled but didn’t work on my board. It’s then I realized the SAMD11D wasn’t defined in the main file. With little hope I added it…

Arduino/libraries/Adafruit_NeoPixel/Adafruit_NeoPixel.cpp
#elif defined(__SAMD21E17A__) || defined(__SAMD21G18A__) || \
      defined(__SAMD21E18A__) || defined(__SAMD21J18A__) || \
      defined (__SAMD11C14A__) || defined(__SAMD11D14AS__)

I wasn’t expected it to be that simple but my example code actually worked! It was a relief after all this time.

#include <Arduino.h>
#include <Adafruit_NeoPixel.h>

#define PIN 23
#define NUMPIXELS 3

Adafruit_NeoPixel pixels(NUMPIXELS, PIN, NEO_GRB + NEO_KHZ800);

void setup() {
    pixels.begin();
    pixels.clear();
    pixels.show();
}

void loop() {
    pixels.clear();

    for(int i = 0; i < NUMPIXELS; i++) {
        pixels.setPixelColor(i, pixels.Color(119, 245, 135));
        pixels.show();
        delay(500);
    }
}

Input Devices

This week I tried a gesture and color sensor from Adafruit.

When plugging the sensor for the first time, I wasn’t able to detect anything from the example codes. So I uploaded an I2C scanner code to check if the device was recognized.

It worked and detected the device.

-> I2C Scanner
-> Scanning...
-> I2C device found at address 0x39  !
-> done

I had fun reading about this sensor and testing several behaviour. It turns out the color sensor is quite tricky to trigger because when you approach an object from it, it gets dark and has trouble recognizing the color.

Networking & Communications

This week I focused on my final project, and decided which MCU I will be using and how to connect everything.

I will use an ESP32 to be the main board of the project: this way, I will be able to connect an interface later on. This main board will handle the interaction with the project, first with buttons, then with proximity sensor, and finally web interface.

It will send messages to another board via I2C protocol that will contain all functions necessary to control the WS2812B addressable LEDs. I plan to use a ATtiny 412 at first and work with the tinyNeopixel library. I initially wanted to use the FastLED library as it’s very versatile, but my previous tests and the work of previous Fab Academy students (like this one) changed my mind.

In a second spiral, it will also send messages simultaneously to a board dedicated to the sound system, with an associated SD card to store music. I plan to use an ATtiny 3216, but I will need to run some more tests.

graph LR
  A{ESP 32} --->|I2C| B{ATtiny 412};
  B -->|DATA| C[WS2812B];
  D[Interface] --> A;
  A --->|I2C| E{ATtiny 3216};
  E --> F[Speaker];
  G[SD card] --> E;
  C -->|DATA| H[WS2812B];
  H -->|DATA| ...;

I first designed the ATtiny 412 board, and made several mistakes along the way: you can read about them in the dedicated page. I ended up with two boards.

I made some tests to know how many leds I could power without adding another power management. As it turned out, I cannot handle more than 25 leds with this board. It will be enough for the final project, bu it’s something to keep in mind for future reference. It’s also caused by the 3V3 regulator I used to keep consistency between the ESP32 data output and the ATtiny.

As I wrote before, I used the tinyNeoPixel library written by SpenceKonde to program the leds. The idea is to trigger some function thanks to the I2C protocol.

Missing

Video of the leds

I then designed the main board: what a challenge! I did some research and finally I found the Barduino project, which gave me enough information to launch myself in the design.

I also wanted to be able to program it through USB, so I added a SAMD11C connected directly to RX and TX. I wanted to try that for a long time, I hope it will work as intended!

Warning

When connecting RX and TX, be aware that the SAMD TX must be connected to the ESP32 RX, and RX to TX.

It’s clearly not the best-looking board I made, but it was Friday afternoon and I wanted to mill it before the week-end, so I stick with it.

To program the ESP32 I needed to flash the SAMD11C with my SWD. I followed my own documentation to remember how to do it and uploaded Quentin’s program. After that I just had to select the right port in the Arduino IDE and I was able to program my little monster.

Now it was time to finally network! I wanted to explore the I2C protocol I want to use in my final project.

Sending a signal (Main board)
  Wire.beginTransmission(I2C_ADDRESS);
  Wire.write('R');                      // One byte, but if I need more I can use pointers
  Wire.endTransmission();
Receiving a signal (Student board)
Wire.begin(ADDRESS);
Wire.onReceive(receiveDataWire);

I learned that you need to do as few actions as possible in your receive function, as it plays with interrupts at the same time. Best practice is to change a variable and let the loop() take care of the rest. The variable needs to be volatile and must be defined outside of the loop() or setup() functions.

volatile uint8_t ledTriggered = false;
volatile uint8_t ledOff = false;
volatile uint8_t r, g, b;
void receiveDataWire(int numBytes) {
  char c = Wire.read();

  switch (c) {
    case 'R':                   // Red
      ledTriggered = true;
      r = 255;
      g = 0;
      b = 0;    
      break; 
    case 'G':                   // Green
      ledTriggered = true;      
      r = 0;
      g = 255;
      b = 0;   
      break;
    case 'B':                   // Blue
      ledTriggered = true;
      r = 0;
      g = 0;
      b = 255;  
      break;
    case 'O':                   // Off
      ledOff = true;
      break;
}

And in the loop() I can then light the pixels.

    void loop() {
        if (ledTriggered) {
            lightPixel(r,g,b);
            ledTriggered = false;
        }

        if (ledOff) {
            clearPixel();
            ledOff = false;
        }  
    }

I ran rapidly into an issue I didn’t anticipate (yet again, I don’t expect to anticipate much as I’m fairly new to all of this!). As I wanted to test the Wire library, the ATtiny ran out of memory very quickly, even with a simple code. I read the experience of a previous student and tried different libraries (as TinyI2C) to communicate in I2C, but none of them seemed really steady.

I then connected my two board via the Qwiic connectors and tested the all thing, but nothing happened. I used my Logic Analyzer to try to understand what was going on, but I was unable to read any signal on the clock line (SCL). Stephane found an article that suggested to add more informations in the main setup() loop, as the SDA and SCL pins and the clock for example.

#include <Wire.h>

void setup() {
    Wire.begin(21, 22);         // Indication of SDA and SCL pins
    Wire.setClock(100000);      // Clock to 100kHz
}

And then the signal was read by the Logic Analyzer! I almost cried Eureka but I my RGB led did not seem to work, even with the signal now working as expected. Digging the internet (here and here), I found that the interrupts timing of the RGB leds and of the Wire library seemed to be incompatible… There seems to be some solutions but I don’t think I’m able to create an easy fix for now.

During Regional Review I talked about my issue: thanks to the power of distributed education someone had run into the same issue and solved it already! Bas was my saviour this week, and he did a marvelous job talking to his student boards. I immediatly tried his fix, which turns out to be a different version of the tinyNeopixel library, the tinyNeoPixel_Static.

I just had to change the library name and to call out the pinMode(), and it worked! I love being in a distributed network.

#include <Wire.h>

#define I2C_ADDRESS 42

void setup() {
    Wire.begin(21, 22);
    Wire.setClock(100000);
    Serial.begin(115200);
}

void loop() {
    sendDataWire('R');
    delay(1000);

    sendDataWire('G');
    delay(1000);

    sendDataWire('B');
    delay(1000);

    sendDataWire('O');
    delay(1000);
}


void sendDataWire(char c) {
    Wire.beginTransmission(I2C_ADDRESS);
    Wire.write(c);
    Wire.endTransmission();
}
#include <Wire.h>
#include <tinyNeoPixel_Static.h>

#define ADDRESS 42

#define PIN 4           // PA3
#define NUMPIXELS 25    // Do not put more than 25 leds

byte pixels_byte[NUMPIXELS * 3];

tinyNeoPixel pixels = tinyNeoPixel(NUMPIXELS, PIN, NEO_GRB, pixels_byte);

volatile uint8_t ledTriggered = false;
volatile uint8_t ledOff = false;
volatile uint8_t r, g, b;

void setup() {
    pinMode(PIN, OUTPUT);
    pixels.show();

    Wire.begin(ADDRESS);
    Wire.onReceive(receiveDataWire);
}

void loop() {
    if (ledTriggered) {
        lightPixel(r,g,b);
        ledTriggered = false;
    }

    if (ledOff) {
        clearPixel();
        ledOff = false;
    }  
    }

    void receiveDataWire(int numBytes) {
    char c = Wire.read();

    switch (c) {
        case 'R':
        ledTriggered = true;
        r = 255;
        g = 0;
        b = 0;    
        break;  
        case 'G':
        ledTriggered = true;      
        r = 0;
        g = 255;
        b = 0;   
        break;       
        case 'B':
        ledTriggered = true;
        r = 0;
        g = 0;
        b = 255;  
        break;
        case 'O':
        ledOff = true;
        break;
    }
}

void lightPixel(int r, int g, int b) {
    pixels.fill(pixels.Color(r, g, b));
    pixels.show();
}

void clearPixel() {
    pixels.clear();
    pixels.show();
}

Interface & Applications

This week I want to make the interface for my final project: even if it’s a diorama and I want to keep things simple, I would like to have a way to quickly test light effect to work on some of the scenarii I have in mind. So an interface would be a perfect tool to do that: listen at the music and testing live some effects by simply pushing a button on my phone without having to compile every time I make a change: what a dream !

I made a pretty simple interface using W3.CSS.

So I started simple by adding a button to turn off the LEDs.

<button onclick="offButton()">OFF</button>


The button is calling a javascript function that send an http request.

function offButton() {
    var xhttp = new XMLHttpRequest();
    xhttp.open("GET", "off", true);
    xhttp.send();
}

This request is then treated in the C++ code with the help of ASyncWebServer. I then call the I2C function sendCommand() and send a byte to trigger the student board.

server.on("/off", HTTP_GET, [](AsyncWebServerRequest *request) {
    sendCommand('O');
    request -> send(200);
});

The next buttons were a little trickier. I handled the function onclick="command(0)" with integers as arguments because I was unable to treat them server-side.

function command(scenario) {
    var xhttp = new XMLHttpRequest();
    xhttp.open("GET", "/scenario" + "?=" + scenario, true);
    xhttp.send();

    console.log("GET", "/scenario" + "?=" + scenario);
}

The switch() is not capable of handling String values, so as I didn’t have that many effect to trigger, I chose to stay simple and kept integers. I needed to convert them first, because the getParam() function gives a pointer to a String.

  server.on("/scenario", HTTP_GET, [](AsyncWebServerRequest *request) {
    AsyncWebParameter* p = request->getParam(0);
    int param;
    param = p->value().toInt();

    switch (param) {
      case 0:
        sendCommand('S'); // Scenario
        break;
      case 1:
        sendCommand('R'); // Rainbow
        break; 
      case 2:
        sendCommand('Y'); // Binary
        break;      
    }

    request -> send(200);
  });

And I was done with the easy part. For the next, you can trust me if I say I spent more than a day figuring out how to pass correctly the information of the color choice. I first only wanted to trigger specific animations to keep things simple, but I found out that the <input type="color"> in HTML could allow me to send color information directly from the user to the LEDs.

var choose_color = document.getElementById("color_picker");
var hex_code = choose_color.value.split("");
var red = parseInt(hex_code[1] + hex_code[2], 16);
var green = parseInt(hex_code[3] + hex_code[4], 16);
var blue = parseInt(hex_code[5] + hex_code[6], 16);

After that I just had to send a GET request to the server with multiples arguments.

var xhttp = new XMLHttpRequest();
xhttp.open("GET", "/" + effect + "?r=" + red + "&g=" + green + "&b=" + blue, true);
xhttp.send();

On the .ino file, I got the parameters of the request by reading this tutorial and the ESPAsyncWebServer reference. And to send them, I needed to convert them in the right format.

server.on("/on_rgb", HTTP_GET, [](AsyncWebServerRequest *request){
    AsyncWebParameter* r = request->getParam(0);
    AsyncWebParameter* g = request->getParam(1);
    AsyncWebParameter* b = request->getParam(2);

    red = (uint8_t)r->value().toInt();
    green = (uint8_t)g->value().toInt();
    blue = (uint8_t)b->value().toInt();

    sendColor('T', red, green, blue);

    request->send(200);
});

I used a new function sendColor() I wrote to send the information to the student board with the LEDs. It sends four bytes: one char and three uint8_t.

void sendColor(char c, uint8_t r, uint8_t g, uint8_t b) {
    Wire.beginTransmission(I2C_ADDRESS);
    Wire.write(c);
    Wire.write(r);
    Wire.write(g);
    Wire.write(b);
    Wire.endTransmission();
}

In the receiver board, I had to declare my variables right in the beginning of the file, before the setup() function where I call the Wire.onReceive(receiveDataWire);.

uint8_t ri2c;
uint8_t gi2c;
uint8_t bi2c;

On the function receiveDataWire(), I read the first byte and according to it I can know if there are more to read or not. According to these informations, I trigger the corresponding animation. You can read more about those in the section below.

void receiveDataWire(int numBytes) {
    char c = Wire.read();

    switch(c) {
    case 'T':                         // Turn on with RGB values
        scenario = 'T';
        ri2c = Wire.read();
        gi2c = Wire.read();
        bi2c = Wire.read();
        break;
    case 'O':                         // OFF
        scenario = 'O';
        break;
    }
    t = 0;
}

And voilà, I had a working interface.

Reprogramming the NeoPixel

When programming the ATtiny to respond to I2C calls by the ESP32 (you can see it in the Networking week), I ran into an issue: it’s fine for the ATtiny to receive a simple task to do like light all pixels in red, but as soon as I wanted to try more complicated effects as rainbows or blinking, it became deaf to any other calls and got stuck in the light animation. For my final project I want to be able to program any effects I want, and call them in a scenario way.

Clock control I read a serie of articles about how to use millis() to multi-task the MCU and try to be more effective when calling the leds animation. I then modify the structure of my code to avoid using any delay() in it.

When receiving a message from the main board, the ATtiny change a variable called scenario according to the byte received. I then use a switch to trigger an effect. Below you can see the main loop of the program: I use the internal clock of the MCU to count the milliseconds. Each ten milliseconds (an arbitrary interval, it could be longer), I execute a command according to the scenario defined. I then increment t, a variable that allows me to animate the LEDs (I will explain in the next paragraph why).

This way, I can receive another message while the LEDs are doing something, and change the scenario variable on the fly, being always aware of changes triggered by the interface.

void loop() {
  unsigned long currentMillis = millis();           // I check the internal clock

  if (currentMillis - previousMillis > interval) {  // If the clock is beyond the interval predefined, the program changes (or continues)
    previousMillis = currentMillis;                 // I update the time

    switch (scenario) {                             // According to the scenario variable, the LEDs are in  a different animation
      case 'R':                                     // "Rainbow" scenario
        rainbowEffect(t, ALL);                          
        pixels.show();
        tMax = 3000; // 30s                         // tMax is here to turn off the LEDs after 30s of inactivity
        break;
      case 'O':                                     // Off
        turnOff(ALL);
        pixels.show();
        break;
    }

    if (t >= tMax) {
      scenario = 'O';
      t = 0;                                        // Reset the t every time the LEDs turn off for inactivity
    }
    else {
      t++;                                          // t is incremented every 10 milliseconds
    }
  }
}

NeoPixels animations The NeoPixels are a very versatile tools, but sometimes hard to bend to your will. If you read the previous assignments you saw me struggling with them during Embedding programming week, Output week and Networking week.

This time I was determined to not use any delay() and to make them do fun animations. As a reminder, the tinyNeoPixel_Static that I use is based on the Adafruit_NeoPixel API, so I can use the same functions.

It’s important to know that to light the NeoPixel you have to use the show() function. This function appears only in the main loop of my program, and the colors and animationsa are taking care of in the sub-functions.

I first wrote two simple functions to turn on and off the LEDs. The function loops other each pixel and turn them on or off. I added a ledSection argument to decide wich part of the strip should be affected by the command. It will allow me to make asynchronous animations for the scenarii.

Turn off the LEDs
void turnOff(int ledSection[]) {
  /*
   * Turn off a defined Section of LEDs
   */
  for (int i = ledSection[0]; i <= ledSection[1]; i++) {
    pixels.setPixelColor(i, pixels.Color(0, 0, 0));
  }
}
Turn on the LEDs with RGB values as arguments
void turnOnRGB(int ledSection[], int r, int g, int b) {
  /*
   * Turn on a defined section of LEDs with RGB Color
   * R, G, B : 0 to 255
   */
  for (int i = ledSection[0]; i <= ledSection[1]; i++) {
    pixels.setPixelColor(i, pixels.Color(r, g, b));
  }
}

The sections are defined in global with arrays of two numbers: the first and last LED of the section.

int ALL[] = {0, 25};
int SECTION1[] = {0, 15};            
int SECTION2[] = {15, 25};

I also wanted to use the HSV color space for some specific animations, like fading effects. I learned that this space is particular for the NeoPixel and had to remap the values from 0 and 360 to 0 and 65536. I used this Color Picker to convert some values at first.

void turnOnHSV(int ledSection[], int h, int s, int v) {
  /*
   * Turn on a defined section of LEDs with HSV Color
   * H: 0 to 65536
   * S: 0 to 255
   * V: 0 to 255
   */
  int hue = map(h, 0, 360, 0, 65536);                       // Map HSV value
  for (int i = ledSection[0]; i <= ledSection[1]; i++) {
    pixels.setPixelColor(i, pixels.ColorHSV(hue, s, v));
  }
}

I used the t variable to calculate the state of each NeoPixel on the fly and light them accordingly. For example, you can see below in the rainbowEffect that I use t to vary the hue of the LEDs. I made other functions that uses t as well.

void rainbowEffect(int t, int ledSection[]) {
    int hue = t % 360;
    turnOnHSV(ledSection, hue, 255, 255);
}
void blinkEffect(int t, int ledSection[], int r, int g, int b) {
    /*
    * Blink effect (for now hard-coded for 10ms interval)
    */
    if (t % 50 <= 25) {
        turnOnRGB(ledSection, r, g, b);
    }
    else {
        turnOff(ledSection);
    }
}
void circularEffect(int t, int ledSection[], int r, int g, int b) {
    /*
    * Light every LED one by one and start again
    */
    int numberLeds = ledSection[1] - ledSection[0];

    for (int i = ledSection[0]; i <= ledSection[1]; i++) {
            if ((t % numberLeds) + ledSection[0] == i) {
            pixels.setPixelColor(i, pixels.Color(r, g, b));
        }
        else {
            pixels.setPixelColor(i, pixels.Color(0, 0, 0));
        }
    }
}
void fadeEffect(int t, int ledSection[], int hue) {
    /*
    * Varies the brightness value between 0 and 255.
    * Using cosinus and trigonometric circle.
    */
    double angle = (float) (t % 628) / 100;           // Map time to angle
    double valueDouble = (-cos(angle) + 1.0) * 128.0; // Compute cosinus and map value between 0 and 255
    int value = ((int) valueDouble) % (256);          // Transforms value in integer
    turnOnHSV(ledSection, hue, 255, value);           // Turn all defined leds to hue, maximum saturation and value
}

My partner is a computer scientist and we had some fun programming animations with the LEDs over the week-end. She went crazy when she saw that with the ATtiny we couldn’t print any indications, so she had the ideo to monitor the t value thanks to a binary clock. It turned out to be a great help and a beautiful animation, so I kept it in the final code.

void binaryEffect(int t) {
  /*
   * Binary count with 16 leds (2^16-1)
   */
  int tt = t;
  for (int i = 1; i <= 17; i++) {
    if (tt % 2 == 0) {
      pixels.setPixelColor(i, pixels.Color(0,0,0));
    }
    else {
      pixels.setPixelColor(i, pixels.Color(127, 127, 127));
    }
    tt = tt / 2;
  }
}

As I love to make dioramas with scenarii since before Fab Academy, I already had a music in mind to animate it. I found the Swan Lake music really resourceful for a light and music show, and I will try to highlight the music with light effects as well as I can. This is open source music so I know I will not have issues using it in my project. The idea is to make the workflow of creating a scenario for a music easier, so later I will be able to make more scenarii with other musics.

Each scenario will be structured like this one below: every t the pixels are cleared, set to their new color according to the animation, and showed.

void swan(int t) {
  /*
   * Swan Lake music
   */
  pixels.clear();                             // Reinitialize all LEDs

  /*
   * Start of the scenario
   */

  if (effectDuration(t, 0, 30000)) {
    rainbowEffect(t, SECTION1);
  }

  if (effectDuration(t, 0, 30000)) {
    circularEffect(t, SECTION2, 89, 123, 87);
  }

  /*
   * End of the scenario
   */  
  pixels.show();                              // Show the color for each t
}

I created a useful function to give a start and stop time to each animation, using t as always.

bool effectDuration(int t, int tStart, int tStop) {
  /*
   * Implement the effect for a given time
   */
  if ((t >= tStart) && (t < tStop)) {
    return true;
  }
  return false;
}

This way it’s possible to make the LEDs act asynchronously, and light only certain sections of the diorama, or make them do different things at the same time.

Weeks 16 & 17

During these two weeks I started to build the actual box that will contain everything. I also made huge progres with the electronics and the decorations of the facades.

I made the 3D modelization of the box in FreeCAD and all the decorations in Inkscape before laser cutting every part and assembling the box.

Facades

I wanted the facades to show more depth so I did it in three layers, one for the global structure, one for the decorations above and one for the windows underneath.

I painted them and tried some different configurations to evaluate the size of the final box.

Box

I prepared the file in FreeCAD and exported each face one by one in .DXF format to send them to the laser cutter. I prepared room for the LEDs with 25mm of distance to allow the PMMA to spread the light correctly.

I used 6mm MDF planks for the global structure and 3mm PMMA for the scattering effect. To engrave the paving stones I defocused the laser to obtain wider lines.

The assembly is easy for the most part, but the top plank can be tricky to insert. The best techinque I found was to insert the PMMA into the top plank, then insert all of them in the bottom plank.

Tou can see the space for the LEDs on the left picture below. I also painted the box in black to enhance the docrations instead of the container. I like the idea of a black box revealing its secrets after the show.

ATtiny 3216

I decided to make another board to control my LEDs with an ATtiny 3216: it has more RAM than the ATtiny 412 and can normally light more LEDs.. I designed the board in KiCAD and reduced it to its minimal function: receiving I2C commands and lightning the LEDs.

I made a mistake when I misread the best practices from adafruit. I added a capacitor to help the LEDs (just in case, I will never hook up more than 5V), but I forgot to connect the capacitor to the ground… So I corrected my mistake by remplacing the capacitor by a resistor 0 when soldering.

LEDs

I ordered a five meter NeoPixel strip for my final project and quickly tested them with my newly made ATtiny 3216 board. The 300 LEDs on the strip didn’t all light up when powered directly from my computer, but when I use a battery to power them they all light up fine.

I prepared a schematic for the disposition of the LEDs to see how many I would need: turns out I will use 217 LEDs for this project, way much than I expected! I’m reaaly glad to have switch to the ATtiny 3216 for this project.

On Monday morning I started to cut the strips and dispose them on the three planks. I helped myself with my facades to place them accordingly.

And then a very long day started: I spend ten hours soldering, cutting wires, assembling everything, crimping wires to make some nice connections between the planks.

Each strip must be connected by the DATA line and must respect an order to work.

For the 5V and the GND lines however, the strip can be powered only on one side. So I made my life easier and glued two copper lines to distribute the power on the plank.

I also crimped some wires to connect the three planks together without risking to break a connection every time I unmount the box in the following days. I then breaded them to use less space and give it a non-messy look (thank you Nadieh for the idea!).

At the end of the day, I had two planks connected together: before starting the final one I decided to test what I already did. Unfortunately, the LEDs didn’t behave at all as expected. They light up with a very weak red and nothing more. As it was already late, I went home and read tons of forum and guides of Neopixels to try and debug the following morning. I spend a very crappy evening trying to figure out where I went wrong in the day.

On Tuesday morning, I was ready to tackle every problems I could encounter. I tried first my board with a strip of LEDs I used for my previous tests: the same thing happened: I knew there was something wrong with my board. It turns out the ground copper pad wasn’t struck to the board anymore but was somehow detached! I resoldered it quickly and everything went normal again: what a relief.

When plugging the board to the LEDs, I was thunderstrucked to see only one line of LEDs were lighnting up! I made an horrible mistake and forget that the data line had a direction one line out of two… So I spend several hours fixing my mistake, to finally plug everything and make it work.

I used a multimeter every time before testing to make sure no shorts were left on the installation.

Sixteen hours separate theses two pictures below…

All the lights are working well now, and I made sure the connection on the ATtiny won’t break again by adding hot glue to seal the three main pads.

To program them I inserted every one of them in the matrix I prepared.

Fountain

I also launched the resin printing of the foutain in clear resin: I want to install four LEDs underneath it to make more light effects.

ESP32

I finished the main board and everything is working as expected.

Week 18 - Development

Here is my little todo for this final step!

  • Lights for foutain
  • Decoration “curtain”
  • Arcades
  • Roofs
  • Video
  • Slide

Facades: assembly

I finished the roofs and printed the arcades in PLA to give some depth to the scene.

Fountain

I produced a board for the fountain to make it easy on myself for the placement of the LEDs. I took the time to remove the copper of the LEDs’ place because they are not easy to solder properly (as I did the experience in Electronic Design week).

It works so well, I’m really happy to see how this turned out !

Interface customization

I redid the interface to match the color theme of the diorama (not enough in my taste, but it works a little better now).

Photoshoot

We made a photoshoot with the diorama with Stephane (and the help of Pierre for the setup). It was really fun and we reused our FabSlide form machine week!

Later that day I made some changes in the colors to match the theme of La Maison des Ailleurs in Charleville-Mézières: it was the family house of Arthur Rimbaud, and I was deeply inspired by these colors and his work. It is now a museum and a place where artists can gather and create once a year, during Le Printemps des Poètes (Poet’s Spring).

Slide

I redid my final slide in Inkscape with these colors because it worked so well (and I loved the moonlight!)

Video

I also edited my final video using ShotCut, an open source software really easy to use but very complete.

Here is the final version!

June 10th, final presentation

I finally presented my final project ! You can watch it on the video below (at 00:47:36).


Last update: June 27, 2022
Back to top