97. Bearing Witness

“Bearing Witness” 9: Software

Candle flicker
I wanted to achieve a candle effect. So it lit some candles and stared at them for a while. I mean I did some research…

Three things about candle flames:

  1. Flickering = fading rapidly and non-linearly between brightness levels
  2. Flickers usually loosely follow a pattern, around an average brightness
  3. No two flickers are identical

So there’s going to be a lot of calls to the random number generator!

The Arduino has a random number generator. It’s not really random; it’s actually a long fixed string of numbers that appear random – like the decimal places of Pi. You obtain a random number using the call random (minVal, maxVal). So, if you want a random number between 50 and 75 then you would do something like “long RandomNumber = random(50, 76);”. Note that the call sends 76, not 75. This is because the random function includes the minimum value you send it, but excludes the maximum number.

Since the Arduino is just doing some fancy calculations to find its random numbers, it means that every time you start a sketch (or reset the Arduino) you will get exactly the same sequence of numbers. If that’s a problem then you can make the Arduino begin at a different place in the list by using the randomSeed(startingPoint) function. The number you send it is how far down the list to begin. So, something like “randomSeed(123000);”. The recommended approach is to use “analogRead(n)”, where n is an unused input, rather than a fixed number, so that every time you run the sketch you start at a random place. However, I find the number returned is usually quite consistent, and I like to add together two analogReads() and a millis() (milliseconds since the Arduino was last reset).

Each candle has three sets of parameters. Firstly, there are global defaults. These define the starting point for what is considered high (60-75%) and what is low (45-60%) and for how long a flicker is bright/dim.

 DefParam[1] = 60;   // Mean low brightness (%)
 DefParam[2] = 15;   // Max deviation from mean low brightness
 DefParam[3] = 60;   // Mean high brightness (%)
 DefParam[4] = 15;   // Max deviation from mean high brightness
 DefParam[5] = 10;   // Min low time (milliseconds)
 DefParam[6] = 120;  // Max low time (milliseconds)
 DefParam[7] = 20;   // Min high time (milliseconds)
 DefParam[8] = 120;  // Max high time (milliseconds)
 DefParam[9] = 25;   // Mean pattern length (seconds)
 DefParam[10] = 90;  // Mean candle length (seconds)

The program calculates a pattern within these limits. For example, the pattern may be 17ms@52%/126ms@67%. That pattern is repeated (in this case every 17+126=143 milliseconds. But there’s more. Each time the pattern repeats it’s changed slightly, +- a little bit. So the previous example could look something like:


Each time the brightness changes it’s adjusted over a few microseconds so as not to look like a sudden jump. Overall, this makes it look as though the candle is flickering at a fairly constant rate, but also looks natural. Every so often (Mean pattern length) a new pattern is calculated and the pattern is changed.

The flash is a very bright light in the bottom-left corner of the frame. It equates to the source of light of the painted-in shadows. Originally it was intended to be an actual flash, such as 0.5 second on full power. However, it was not clear what had happened whith such a short flash, so I changed it to a pattern of 30-40 flashes, each one slightly shorter than the last. This was also problematic because people just didn’t like the bright flashes. I would say that the point of the flash is to create discomfort, since that is part of the subject matter. Nonetheless, I changed it to a slow fade-in / fade out over a period of 20 seconds.

Random number function [genRan(minVal, maxVal, weight)]
I created a random number function that allowed centre-weighted random numbers to be generated. This function allows a parameter to have a wide range without deviating too far too often. It returns a random number within a chosen range, with the option of weighting towards the mean (average). The chart shows 10,000 random() calls in the range of 1-5. A weighting of 1 is the regular random function, whereas a weighting of 2 shows a bell-shaped curve with an increased probability of returning a 3, and a decrease in returns of 1 and 5. This weighting becomes more pronounced as the weighting value is increased.


// ----------------------------------------------------------------------
// Generate a weighted random number
int genRan(long minVal, long maxVal, long weight)
  long temp = 0;
  maxVal=maxVal*100;    // Use larger numbers to minimise rounding errors

  for (int i = 1; i <= weight; i++)
    temp = temp + random (minVal, maxVal);

  return ((temp / (100L*weight)) + 1);
    // /100 to remove rounding compensation
    // /weight to average multiple values
    // +1 to move the implied abs() up and to include maxVal

Main loop
The program loops around the following:

startLamps();         // Start a new lamp sequence if necessary

for (int i = 1; i <= LampCount; i++)
 endLamp(i);          // End a lamp sequence that's expired
 changePattern(i);    // Start a new pattern if necessary
 changeLoop(i);       // Adjust all calculations for individual lamps
 setPWM(i);           // Adjust each LED's actual brightness to that calculated

USTrigger();          // Send a trigger pulse if it's time for one
distance();           // Check to see how far away nearest object is
flash();              // Do an actual flash

startLamps() checks to see if the timer has expired allowing another candle to be lit. If so then it finds an unlit candle (if any) and sets it to lit as well as setting the flag to trigger a new (first) pattern to be calculated.

The next four functions are performed per lamp. endLamp() checks the timer to see if that candle has reached its end time, marking it as unlit if necessary. changePattern() checks to see if the current pattern has reached their end time, in which case it calculates a new one. changeLoop() calculates the parameters for the next flicker (high/low) if the current one has expired. Finally, setPWM() actually adjusts the candle brightness for this moment.

If enough time has elapsed (half a second) then USTrigger() starts a new ping from the ultrasonic transducer. (When the US module receives the reflection it sends a signal that interrupts the program to tun the echo() function that just sets the EchoReceived flag and notes the time.)

If the EchoReceived flag is set then Distance() calculates the distance to the nearest object using the times that the US pulse was sent and received. If this distance is appropriate (i.e. somebody is standing in front of the artwork) then a flag is set to trigger the flash. [Note: I’m still adjusting this, it may light a candle instead and flash after a while, or some other behaviour.]

Finally, if the DoFlash flag is set then flash() turns off all the candles, resets everything to its “nothing lit” state, and calls the flash routine.

This loop repeats continuously, allowing setPWM() to keep up with the flicker effect.

Voltage test
Finally, there’s one more function. The Arduino checks the supply voltage on power-up, and before each flash, to make sure that the voltage doesn’t exceed 12.5 Volts. If it does, the Arduino turns off all the LEDs and flashes its internal LED. This is important, because the current through the LEDs changes significantly with only a small change of voltage. If the voltage rose significantly (e.g. a 15 Volt power supply is plugged in) the current through the LEDs could damage them.

Download the software
In order to make the work easier to maintain in the long term (by others as well as myself), and to assist anyone who may wish to use similar functionality, I have made the software open source. It may be downloaded here or  here.


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s