Wednesday, May 9, 2018

Incandescent light bulb effect simulated with a microcontroller and LEDs

Last year, I received this marquee sign as a gift:


What do we have?

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



A very nice detail I found are these mock-up light bulbs ,made of 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, allowing all of these leds to turn on simultaneously. It's nice to see it lit, but there is room for a small project.

Room for improvement:

This sign is crying for enhanced simulation, so this project will consist of:

1) Adding a sequence for the bulbs just like the real deal.

and

2) LEDs bright up instantly, and there's no nostalgia in that. Instead, let's emulate the slow paced filament / tungsten / incandescent effect of vintage light bulbs.

This whole project was influenced by real marquee signs such as the one below


some of you may recognize this sign.

Solution:

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

Requires cutting the original connections and rewire them to the PWM pins on the arduino.
The pins you choose NEED to be PWM if you want to simulate the filament effect.

I wired each letter individually and treated the bowling pin as an individual, (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 the excess voltage.

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, you may use 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 atmega 328 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 Process and the aforementioned values are applied:

led_intensity = 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 incandescence 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 complete, by emulating the clicking sound of vintage marquee signs.


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);
}