D13DESIGN
  • Home
  • Shop
  • Products
    • Pedal PCBs
    • Shirts
    • Stickers
    • Posters
  • Build Docs
  • Layouts
  • Arduino
22nd February, 2021 by Dave

Diorama Lighting 3: Day & Night

Diorama Lighting 3: Day & Night
22nd February, 2021 by Dave

Objectives

This part of the process will not only build out some cool lighting effects but will help to create a backbone for the rest of the code project. I want to build something flexible that means, with a little adjusting, things like exact colour selections, timings, etc. can be set without any major code changes. This work will also define the lighting system “clock” – the loop that will run continually to simulate the different times of day.

So, this code needs to:

  • Be easily adjustable both before and after construction
  • Support fading from night, into day and then back into night via a sunset
  • Provide a 24 hour clock against which we can base all of our events
  • Run fades in such a way that we can combine them with additional effects, such as a magic spell explosion during a sunset

As a teaser, this is the result of where I’ve got to with my night/day cycle:

Getting things defined

Again, I’m using the ShiftPWM library mentioned in Part 2 so I have a few bits of setup to add to my code:

// Clock and data pins are pins from the hardware SPI, you cannot choose them yourself.
// Data pin is MOSI (Uno and earlier: 11, Leonardo: ICSP 4, Mega: 51, Teensy 2.0: 2, Teensy 2.0++: 22) 
// Clock pin is SCK (Uno and earlier: 13, Leonardo: ICSP 3, Mega: 52, Teensy 2.0: 1, Teensy 2.0++: 21)
const int ShiftPWM_latchPin=8;

// If your LED's turn on if the pin is low, set this to true, otherwise set it to false.
const bool ShiftPWM_invertOutputs = false;

// You can enable the option below to shift the PWM phase of each shift register by 8 compared to the previous.
// This will slightly increase the interrupt load, but will prevent all PWM signals from becoming high at the same time
// This will be a bit easier on your power supply, because the current peaks are distributed.
const bool ShiftPWM_balanceLoad = true;

#include <SPI.h>
#include <ShiftPWM.h>

// Set some core properties
unsigned char maxBrightness = 255;
unsigned char pwmFrequency = 75;
unsigned int numRegisters = 2;
unsigned int numOutputs = numRegisters * 8;
unsigned int numRGBLeds = numRegisters * 8 / 3;

void setup() {
  // We'll use the Serial output to give us an indicator of the current time in our diorama, let's set that up here
  while(!Serial){
    delay(100);
  }
  Serial.begin(9600);

  // Sets the number of 8-bit registers that are used.
  ShiftPWM.SetAmountOfRegisters(numRegisters);

 // SetPinGrouping allows flexibility in LED setup. 
 // If your LED's are connected like this: RRRRGGGGBBBBRRRRGGGGBBBB, use SetPinGrouping(4).
  // If your LED's are connected like this: RGBRGBRGBRGBRGBRGBRGBRGB, use SetPinGrouping(1).
  ShiftPWM.SetPinGrouping(1);

  ShiftPWM.Start(pwmFrequency,maxBrightness);
  ShiftPWM.SetAll(0);
}

void loop() {

}

I end the setup function with a simple ShiftPWM call to turn all LEDs off, resetting any previous stored state and giving us something clean and clear to work from.

Setup

We need to define a bunch of core variables we’ll use to drive our lighting scheme – things like how long an “hour” takes to pass, what time the sun should come up, what colour the sky is at night, etc.

To make some of the time calculations a little simpler, we’re going to say that each hour actually has 100 minutes rather than 60. It takes some getting used to but saves us having to count up in 60s.

For example, 10:30AM would be set as “1050”, 9:45PM would be “2175”, etc.

// Remember, we're using 100 intervals per hour rather than 60 so quarter-to-six in the morning is 500 plus a three-quarters of 100 = 575

// Set some variables to use as part of transitions
float currentTime = 500; // the time of day to start from when we plug in
float endOfDay = 2399; // the last minute of the day before we click to the next
float clicksPerMinute = 4; // the number of "loops" to cycle through for each minute of diorama-time
float timeInc = 1 / clicksPerMinute; // an internal variable used for some fade calculations

// What time do things happen?
unsigned int dawnStart = 575; // dawn starts at 5:45AM
unsigned int sunsetStart = 1800; // sunset starts at 6:00PM
unsigned int duskStart = 1950; // dusk starts at 7:30PM

// What colours shall we use for the sky and sun/moon?
// Colours are defined as arrays in the form of {R, G, B}
unsigned int nightSkyRGB[] = {0,0,15}; // the night sky colour
unsigned int daySkyRGB[] = {250,250,180}; // the daytime sky colour
unsigned int sunsetSkyRGB[] = {250,20,20}; // the sky colour during a sunset
unsigned int moonRGB[] = {30,30,120}; // the colour of moonlight
unsigned int sunRGB[] = {220,230,255}; // the colour of sunlight
unsigned int sunsetSunRGB[] = {250,60,30}; // the colour of the sun during a sunset

// Define timings of atmospheric fades - 100 would be equal to an hour
unsigned int dawnFadeRange = 100*clicksPerMinute; // sunrise takes an hour
unsigned int sunsetFadeRange = 100*clicksPerMinute; // sunset takes an hour
unsigned int duskFadeRange = 50*clicksPerMinute; // dusk takes 30 minutes

void setup() {
  ...

We also need a few extra variables so that our script can keep track of what’s going on.

bool isDawnFading = false; // are we currently fading through dawn?
bool isSunsetFading = false; // are we currently fading through sunset?
bool isDuskFading = false; // are we currently fading through dusk?
 
unsigned int fadeCounter = 0; // an internal variable to keep track of how far through a fade we are

// Define which LEDs we'll use for which purpose
unsigned int sunLED = 0; // the first connected LED will be the sun
unsigned int skyLEDs[] = {1,2}; // the next two LEDs will be the sky
unsigned int numSkyLEDs = 2; // this tells the program how many sky LEDs we need to keep updated

void setup() {
  ...

The fade function

Now we have all of the information we need to drive each stage of light in our day – the sky colours, the sun and moon colours, and how long each transition should take. We can build a function that can be called when a fade is taking place that will determine how the LEDs need to set and update them.

void loop() {
   
}

void fade(int type, int startRGB[], int endRGB[], int sunStartRGB[], int sunEndRGB[], int range){

  // type: the type of fade we want 1-dawn, 2-sunset, 3-dusk
  // startRGB: our starting colours in {R, G, B} format
  // endRGB: our end colours in {R, G, B} format
  // sunStartRGB: the start colours for the sun/moon in {R, G, B} format
  // sunEndRGB: the end colours for the sun/moon in {R, G, B} format
  // range: the number of "minutes" to spread this fade over

  // We also need some extra variables to store our calculated colours against before we apply them to the LEDs
  float skyR, skyG, skyB, sunR, sunG, sunB, i;
 
}

Our function will take in a bunch of properties so that we can use the same function for every fade we need.

At the end of our function we need to increment our fade counter (the variable we’re using to time through our fades) and then stop the fade once it’s completed.

void fade(...) {

  ...

  fadeCounter++;

  if(fadeCounter == range){
    switch(type){
    case 1:
      isDawnFading = false;
      break;
    case 2:
      isSunsetFading = false;
      break;
    case 3:
      isDuskFading = false;
      break;
    }
    fadeCounter = 0;
  }
}

Now we need to add the meat of our function – we need to break down our transition from start to end colours into a number of steps, matching our range, work out where we are in that set of steps and then send the right colours to our sky and sun/moon LEDs.

void fade(int type, int startRGB[], int endRGB[], int sunStartRGB[], int sunEndRGB[], int range){

 float skyR, skyG, skyB, sunR, sunG, sunB, i;

 // For each colour we need to know are we going up in number or down, this will determine how we step through the steps

 // SKY
 if(endRGB[0] > startRGB[0]){
   i = float(endRGB[0]-startRGB[0]) / float(range);
 }else{
   i = (float(startRGB[0]-endRGB[0]) / float(range)) * -1;
 }
 skyR = float(startRGB[0]) + i*float(fadeCounter);
 if(endRGB[1] > startRGB[1]){
   i = float(endRGB[1]-startRGB[1]) / float(range);
 }else{
   i = (float(startRGB[1]-endRGB[1]) / float(range)) * -1;
 }
 skyG = float(startRGB[1]) + i*float(fadeCounter);
 if(endRGB[2] > startRGB[2]){
   i = float(endRGB[2]-startRGB[2]) / float(range);
 }else{
   i = (float(startRGB[2]-endRGB[2]) / float(range)) * -1;
 }
 skyB = float(startRGB[2]) + i*float(fadeCounter);

 // SUN
 if(sunEndRGB[0] > sunStartRGB[0]){
   i = float(sunEndRGB[0]-sunStartRGB[0]) / float(range);
 }else{
   i = (float(sunStartRGB[0]-sunEndRGB[0]) / float(range)) * -1;
 }
 sunR = float(sunStartRGB[0]) + i*float(fadeCounter);
 if(sunEndRGB[1] > sunStartRGB[1]){
   i = float(sunEndRGB[1]-sunStartRGB[1]) / float(range);
 }else{
   i = (float(sunStartRGB[1]-sunEndRGB[1]) / float(range)) * -1;
 }
 sunG = float(sunStartRGB[1]) + i*float(fadeCounter);
 if(sunEndRGB[2] > sunStartRGB[2]){
   i = float(sunEndRGB[2]-sunStartRGB[2]) / float(range);
 }else{
   i = (float(sunStartRGB[2]-sunEndRGB[2]) / float(range)) * -1;
 }
 sunB = float(sunStartRGB[2]) + i*float(fadeCounter);

 // Loop through all of our sky LEDs and set them
 for (int a = 0; a < numSkyLEDs; a++) {
   ShiftPWM.SetRGB(skyLEDs[a],int(skyR),int(skyG),int(skyB));
 }

 // Set the sun LED
 ShiftPWM.SetRGB(sunLED,int(sunR),int(sunG),int(sunB));

 // Increment our counter and close the fade if our counter matches our range
  fadeCounter++;
  if(fadeCounter == range){
    switch(type){
    case 1:
      isDawnFading = false;
      break;
    case 2:
      isSunsetFading = false;
      break;
    case 3:
      isDuskFading = false;
      break;
    }
    fadeCounter = 0;
  }
}

The loop

Our loop is now relatively simple. We check if it’s a time of day when we should trigger a fade, and if it is we handle that. We check if we’re at the end of the day and whether we need to click back through midnight. And we put a quick time label into the Serial Monitor so we can see what time it is while we’re developing.

void loop() {
  // Put the "time" into the Serial Monitor
  Serial.println(int(currentTime));

  // Is it time for a fade?
  if(int(currentTime) == dawnStart) isDawnFading = true;
  if(int(currentTime) == sunsetStart) isSunsetFading = true;
  if(int(currentTime) == duskStart) isDuskFading = true;

  //If it's time for a fade let's call the fade function
  // Fade types - 1: dawn, 2: sunset, 3: dusk
  if(isDawnFading) fade(1, nightSkyRGB, daySkyRGB, moonRGB, sunRGB, dawnFadeRange);
  if(isSunsetFading) fade(2, daySkyRGB, sunsetSkyRGB, sunRGB, sunsetSunRGB, sunsetFadeRange);
  if(isDuskFading) fade(3, sunsetSkyRGB, nightSkyRGB, sunsetSunRGB, moonRGB, duskFadeRange);

  // Is it the end of the day yet? If it is, go back to zero, if not we can increment the time
  if(currentTime + timeInc > endOfDay){
    currentTime = 0;
  }else{
    currentTime = currentTime + timeInc;
  }

 // Things were running a bit too quickly so I've added a short 30 millisecond delay - have a play with this if you need to
  delay(30);
}

And that’s about it. We now have a collection of LEDs powered by a single PWM pin that give us a nice night into day, into sunset, into dusk sequence that’s highly configurable using the variables at the start of our file.

All the code

You can find the complete code file for this part of the project on GitHub.

Next up

My kit has now arrived so I have a whole heap of other jobs to do to progress things but I can now start to plan the placement of my LEDs, how many I’ll need and what each of the purposes will be. I’ll use this planning to tailor my code to include all of the LEDs and I’ll start to work on some of the special effects.

Next on the list will be lights for the windows that will transition to candle flickering at dusk before lights out at night.

Previous articleDiorama Lighting 2: Shift RegistersNext article Diorama Lighting 4: Second Thoughts & Code Restructuring

About

I love learning new skills and figuring out to make things. Since my day-job is mostly desk based I like to fill my free time with practical activities – here you’ll find some of the projects I like to work on.

Recent Posts

Diorama Lighting 5: Thunder & Lightning23rd February, 2021
Diorama Lighting 4: Second Thoughts & Code Restructuring23rd February, 2021
Diorama Lighting 3: Day & Night22nd February, 2021

Product categories

  • Pedal PCBs (3)
  • Posters (1)
  • Shirts (5)
  • Stickers (0)
  • Uncategorized (0)

Recent Products

  • Pork Chop Fuzz PCB £4.00
  • Green Scream PCB £4.00
  • Continuum Drive PCB £4.00 – £12.00

Recent layouts

Diorama Lighting 5: Thunder & Lightning23rd February, 2021
Diorama Lighting 4: Second Thoughts & Code Restructuring23rd February, 2021
Diorama Lighting 3: Day & Night22nd February, 2021
Diorama Lighting 2: Shift Registers22nd February, 2021
Diorama Lighting 1: Intro21st February, 2021
Rife Wordpress Theme ♥ Proudly built by Apollo13

About This Sidebar

You can quickly hide this sidebar by removing widgets from the Hidden Sidebar Settings.

Latest Products

  • Pork Chop Fuzz PCB £4.00
  • Green Scream PCB £4.00
  • Continuum Drive PCB £4.00 – £12.00
  • 52 Pedal Project Poster £15.20
  • Guitar Collection Tee Alt £15.00

Basket