Wednesday, May 9, 2018

Marquee Sign with incandescent light bulb effect simulated with LEDs

Last year, I received this marquee sign from my kids as a gift on father's day:


What do we have?

A beautiful marquee sign: simply constructed on a real metal frame, printed cardboard with printed rust, painted screw holes and such for aged-tin effect. It looks fairly realistic and pleasant to the eye.



A very nice detail I found is that light bulbs are mock-ups, simply by using a small glass bulb screwed on top of a standard warm white LED:


A small switch on the side closes the circuit delivering 3v from two AA batteries located on the back, but all of these leds turn on simultaneously. It's nice to see it lit, but....

Room for improvement:

This sign is crying for enhanced simulation

1) I want to add a sequence for the bulbs just like the real deal.

and

2) That involves of course emulating the filament effect of vintage light bulbs: remember that slow-paced heating up and then cooling down of tungsten filament light bulbs? I want that because LEDs bright up instantly, and there's no nostalgia in that.

This whole project was influenced by real marquee signs such as this one:


Solution:

This is a relatively simple Arduino project, so let's get it done:

Requires cutting the original connections and rewire them to the PWM pins on the arduino.
This is important if you want to emulate the filament effect.

I wired each letter individually and all of the LEDs on the bowling pin are in parallel.


The original battery holder was also replaced by a holder for two 18650 type batteries. These will deliver 8v but the voltage regulator on the Arduino nano takes care of that.

The light bulb effect:

There is a way of achieving this with an analog circuit (more on that here), but I went for a software solution since the Arduino board was already in.

So here's my gift to you people: How to accurately simulate a lightbulb by means of applied science.

It's not a matter of slowly turning a led on or off in a linear fashion, no: that doesn't look natural nor responds to the way the I-V curve of a light bulb behaves in real life.

If you want a more realistic approach, you should go for an exponential equation that looks like this:



The blue line represents the turning on phase, and the red one when the led shuts down.

To accomplish this, is a matter if following an equation of the form:

Source: 1st order differential equations (https://www.math24.net/learning-curve/)

In this case Lmax is equal to 255 since PWM on the Arduino fluctuates between 0 and 255.
I chose k = 0.02 to shape the curve to my liking, and M=0 (M being the resulting value of L(t) when t=0)

The equation looks like this when coded in C or Arduino and the aforementioned values are applied:

255-((255-0)*exp(-0.02*t));

...where the function exp(x) means e to the power of x.

And voilá, the project is finished and the sign is much more fun to watch now.


This animation above does not reflect the incandescent lamp effect, but you get the idea. Try the code and play with it!


Sound effect bonus:
You may have noticed that the Arduino nano sits on top of a green PCB. This is because I may decide to add a 5v mini relay and have it click every time a bulb turns on or off. Then the effect will be super-romantic.


Code:




/// June 2017Marquee Bowling sign with Tungsten filament lightbulb simulation using LEDS

// Pin number definition follows
// These all need to be PWM pins, so that explains the choice for 3,5,6,9 and 11.

#define B_lamp 3  
#define O_lamp 5
#define W_lamp 6
#define L_lamp 9
#define BOWLINGPIN_lamps 11

#define max_illumination 110  // Up to 255, this defines the maximum illumination you want
// multiple lamps in parallel require an extra for extra current so we need to compensate for that
// otherwise the letters on the sign will glow brighter than the bowling pin
#define max_illumination_for_bowling_pin 255  
#define pausa 1000
#define filament_simulation 1500  // This parameter allows your lamps to turn on an off quicker or slower depending on the type of light bulb and voltage you are simulating.

// the setup function runs once when you press reset or power the board
void setup() 
{
  // Serial.begin(9600);  // This is for debugging purposes, disabled by default. 
  // initialize digital pins
  pinMode(B_lamp, OUTPUT);
  pinMode(O_lamp, OUTPUT);
  pinMode(W_lamp, OUTPUT);
  pinMode(L_lamp, OUTPUT);
  pinMode(BOWLINGPIN_lamps, OUTPUT);
}

// the loop function runs over and over again forever
void loop() 
{
  // Program the sequence you like. This is the one I chose.
  turn_on(B_lamp);
  delay(pausa);
  turn_on(O_lamp);
  delay(pausa);
  turn_on(W_lamp);
  delay(pausa);
  turn_on(L_lamp);
  delay(pausa);
  blink_bowlingpin();
  delay(pausa*3);
  turn_off(B_lamp);
  delay(pausa);
  turn_off(O_lamp);
  delay(pausa);
  turn_off(W_lamp);
  delay(pausa);
  turn_off(L_lamp);
  delay(pausa);
  turn_off(BOWLINGPIN_lamps);
  delay(pausa*2);
}


void turn_on(int lamp)
{
  int maximum = max_illumination; if (lamp==BOWLINGPIN_lamps) maximum = max_illumination_for_bowling_pin;  
  // Now we initiate the slow glow effect
  for(int tungsten=0 ; tungsten <= 200 ; tungsten++)
    {
      float level = 255-((255-0)*exp(-0.02*tungsten));  // This is what really matters: an exponential equation for realistic light bulb simulation, not that linear rubbish.
      level = map(level,0,251,0,maximum);  // Remap the maximum to the desired level.
      analogWrite(lamp, level); // PWM comes to save the day, that's why we analogWrite
      delayMicroseconds(filament_simulation);  // How fast or slow you want your bulbs to be
      //Serial.print(tungsten);Serial.print(";");Serial.println(level);  //This is for debugging only.
    }
}

void turn_off(int lamp)
{
  int maximum = max_illumination; 
  if (lamp==BOWLINGPIN_lamps) maximum = max_illumination_for_bowling_pin; 
  for(int tungsten=0; tungsten <= 200 ; tungsten++)
    {
      float level = 251-(255-((255-0)*exp(-0.02*tungsten)));  // The same curve, in the opposite direction
      level = map(level,0,251,0,maximum);
      analogWrite(lamp, level); 
      delayMicroseconds(filament_simulation/2);
      //Serial.print(tungsten);Serial.print(";");Serial.println(level);
    }
}

// Now some blinking to catch the eye of the distracted driver on the road and get them into our bowling business

void blink_bowlingpin() 
{
  for (int blink=0; blink <3 ;blink++)
  {
    turn_on(BOWLINGPIN_lamps);
    delay(pausa/2);
    turn_off(BOWLINGPIN_lamps);
    delay(pausa/2);
  }
  turn_on(BOWLINGPIN_lamps);
}