TV Light Effect

After seeing Bruce Kingsley’s Arduino-animated HO scale house in the April 2016 issue of Model Railroader, which includes a flickering TV screen in a dark room as one of its many effects, I decided to try writing my own TV-flickering code for the Arduino, and ended up with what I think is a slightly more realistic effect. Using a cool-white LED to simulate the glow of a black and white TV, the code creates the effect by changing the brightness to various levels at various time intervals, within preset ranges, in two different ways.

The majority of the flickers are sudden changes from one level of brightness to another, simulating the light effect of moving images and cutting from one camera to another. The length of time between changes varies each time from between 400-1,000 milliseconds (.4 seconds to 1 second). The levels of brightness are randomly chosen each time, from a range of 25-75, on a scale where 0 is off, and 255 is the brightest level possible.

The flicker effect alone, however, looks a little more like the light of an endlessly flickering flame than the light of a television broadcast. For more realism, I needed to include some softer transitions.

In addition to the hard changes in brightness, the LED fades off, then fades back up, once every 4.5-30 seconds, to simulate softer changes like fade-to-black and cross fade transitions between scenes or commercials. The fade is quick, and the rate of fading changes randomly each time within a range of 2-5 brightness steps per millisecond, and the duration of the dark pause between fading off and coming back on varies each time between 100-800 milliseconds (.1 to .8 seconds).

My first implementation of the TV effect is in the small DPM/Woodland Scenics building I started building in the Winter of 2018-2019, which I included in a larger animation project that changes lights in various rooms over a 24-hour period that can be compressed as needed.

Here’s the Arduino sketch for the TV effect alone:

const int light_TV = 9;
int TV_mode = 1; // 0=off, 1=fade up, 2=on, 3=fade down, 4=dark pause (then loop back to 1)

unsigned long TV_NextFlickerTime = millis(); // The time the TV light level is changed.
unsigned long TV_NextFTBTime = millis(); // The time the TV fades to black.
int TV_brightness;
int TV_minBrightness = 25;
int TV_maxBrightness = 75;

unsigned long TV_lastFadeChangeTime = 0;  // stores the last time brightness was changed in the fade

int TV_maxFadeAmount = 5;
int TV_minFadeAmount = 2;
int TV_fadeAmount = 3; // How many points to fade the LED by on each iteration. This gets changed later.

int TV_minFadeInterval = 3;
int TV_maxFadeInterval = 7;
int TV_fadeInterval = 5; // change interval for TV FTB. This gets changed later.

unsigned long darkPauseStartTime; // used for timing the darkness duration of FTB
int TV_minDarkPauseDuration = 100;
int TV_maxDarkPauseDuration = 800;
int TV_minNextFlickerTime = 400;
int TV_maxNextFlickerTime = 1000;
int TV_minNextFTBTime = 4500; // minimum time between FTB
int TV_maxNextFTBTime = 30000; // maximm time between FTB

void setup()
{
  Serial.begin(9600);
  pinMode(light_TV, OUTPUT);
 
}

void TV_fadeUp() {
    
  if (millis() - TV_lastFadeChangeTime >= TV_fadeInterval) {
    // save the last time you changed the brightness
    TV_lastFadeChangeTime = millis();
    TV_brightness = constrain(TV_brightness+TV_fadeAmount, 0, TV_maxBrightness);
  }
  if (TV_brightness==TV_maxBrightness) {  // advance the mode once fade up is complete
    TV_mode=2; 
    TV_NextFTBTime = millis() + random(TV_minNextFTBTime, TV_maxNextFTBTime);
  } 
  
}

void TV_fadeDown() {
    
  if (millis() - TV_lastFadeChangeTime >= TV_fadeInterval) {
    // save the last time you changed the brightness
    TV_lastFadeChangeTime = millis();
    TV_brightness = constrain(TV_brightness-TV_fadeAmount, 0, 255);
  }
  if (TV_brightness==0) { // change mode to 4 to start a short dark pause
    TV_mode=4;
    darkPauseStartTime = millis();
    } 
}

void TV_flicker () {
  if(millis() > TV_NextFlickerTime) {
    TV_brightness = random(TV_minBrightness, TV_maxBrightness);
    analogWrite(light_TV, TV_brightness);
    TV_NextFlickerTime = millis() + random(TV_minNextFlickerTime, TV_maxNextFlickerTime);
  }
}

void loop()
{

  switch (TV_mode) {
    case 0:
    analogWrite(light_TV, 0);
    break;

    case 1:
    TV_fadeUp();
    analogWrite(light_TV, TV_brightness);
    //Serial.println("mode 1");
    break;

    case 2:
    TV_flicker();
    //Serial.println("mode 2");

    // is it time to FTB?
    if(millis() > TV_NextFTBTime) {
      TV_mode=3;
      TV_fadeAmount = random(TV_minFadeAmount, TV_maxFadeAmount); // Calculate a new fade amount for just the next fade down.
      TV_fadeInterval = random(TV_minFadeInterval, TV_maxFadeInterval); // Calculate a new fade interval for just the next fade down.
      //Serial.println("time for FTB");
     }
    break;

    case 3:
    //Serial.println("mode 3");
    TV_fadeDown();
    analogWrite(light_TV, TV_brightness);
    break;

    case 4:
    if(millis() > darkPauseStartTime + random(TV_minDarkPauseDuration, TV_maxDarkPauseDuration)) {
      TV_mode=1;
      TV_fadeAmount = random(TV_minFadeAmount, TV_maxFadeAmount); // Calculate a new fade amount for just the next fade up.
      TV_fadeInterval = random(TV_minFadeInterval, TV_maxFadeInterval); // Calculate a new fade interval for just the next fade up.
    }
    break;
  }   
 }