Star Trek: Enterprise Refit 350 Scale Lighting Effects

This sketch will allow you to incorporate audio and lighting effects into the 350 scale Enterprise Refit from Polar Lights.

There are 6 audio sequences with timed visual effects for each sequence corresponding to each of the 6 TOS movies. The sequences are activated by momentary button switches. I've tried to be as descriptive as I can within the sketch to understand what does what.

You will need:
Arduino UNO
Five(5) 7C4H595 Shift Registers
I2C SD Card Reader (4pin)
5w 80ohm Speaker
Optional Audio amplifier circuit
2 Red LEDs w/resistors
1 Yellow LED w/resistor
One Bicolour (Amber/Blue) LED or a separate yellow and blue LED
40 White LEDS w/resistors
Six(6) Momentary Button switches
10k pull-down resistors

These components are everything you need to breadboard the prototype circuit. If you want to install these into the ship, I recommend incorporating a Darlington ULN2308a and several MOSFETs to power larger clusters of LEDs without overloading the Shifter Registers' and Uno's Pin current rating.

This sketch wouldn't have been possible without Ostrich Longneck's Youtube series Arduino for Starships

Here is the link for the required audio files: 350 Refit wav Files - Google Drive

//Libraries required for audio effects 
#include <pcmRF.h>
#include <pcmConfig.h>
#include <TMRpcm.h>

//libraries required for SD card reader
#include <SPI.h>
#include <SD.h>

//libraries required for multiple shift registers
#include <Shifter.h>

//define shifter pins 
#define SER_Pin 2 //SER_IN
#define RCLK_Pin 7  //L_CLOCK
#define SRCLK_Pin 4

//how many registers are in the chain
#define NUM_REGISTERS 5 


// Setup - Global Variables & Effects
  Shifter shifter(SER_Pin, RCLK_Pin, SRCLK_Pin, NUM_REGISTERS); 
  char mychar;
  const int CS_PIN = 10;
  TMRpcm audio;
  
// Warp & Impulse Mode Setup 
  int fadeVal = 0; // variable for fade loop
  int fadeTime = 0; // variable for fade delay loop
  int fadeTo = 0; // variable for length of fade delay
  int maxFade = 255; // set max brightness value for fade-up and fade-down
  int minFade = 0; // set max brightness value for fade-up and fade-down
  int warpMode = 0; // Warp mode: 0=impulse; 1=warp
  int stateChange = 0; // flag to prevent reading button on pin 1 during warp LEDs fade-in & fade-out
  int fadeLength = 18000; // approximate length of time (milliseconds) in which the fade-down fade-up cycle will take place
  int fadeDown = 0; // flag to check if we are currently fading down to 0
  int fadeUp = 0; // flag to check if we are currently fading up to 255
  const int impulsePin = 5; // PWM pin used for impulse (amber LED) 
  const int warpPin = 6; // PWM used for warp (blue LED) 


// Setup - Scene 1 - The Motion Picture 
  const int TMPButton = 14; //TMP Button Pin
  int TMPButtonState = 0; //flag to check if button has been pressed
  const int DockTime [8] = {15000, 13000, 10000, 7500, 7000, 6500, 4000, 2000}; // Timeline in milliseconds between dockyard lights 
  const int DockLed [8] = {8, 9, 10, 11, 12, 13, 14, 15}; //Shift Register Pins used for dockyard sequence
  int DockStart = 1000; // Time before first dockyards starts the sequence (in milliseconds)
  int DockCounter = 0; // Counter used to keep track of floodlight number in sequence
  long DockSeqStart = 0; // Time in milliseconds when flood sequence was initiated
  long DockSeqEnd = 0; // Variable used to check if it's time for the next floodlight to light up
  
  
  const int FloodTime [8] = {1760, 3240, 5080, 6720, 8560, 10200, 30000}; // Timeline in milliseconds between floodlights 
  const int FloodLed [7] = {32, 33, 34, 35, 36, 37, 38};
  int FloodStart = 9360; // Time before first floodlight starts the sequence (in milliseconds)
  int FloodCounter = 0; // Counter used to keep track of floodlight number in sequence
  long FloodSeqStart = 0; // Time in milliseconds when floodlight was initiated
  long FloodSeqEnd = 0; // Variable used to check if it's time for the next floodlight to light up


// Setup - Scene 2 - The Wrath of Khan - Effect 1 
  const int TwokButton = 15; // TWOK Effect
  int TwokButtonState = 0; // flag to check if button has been pressed
  const int PreFixClock [5] = {1000, 1900, 2700, 3400, 19690}; // Timeline in milliseconds between Prefix Code number entry 
  const int PreFix [5] = {0, 1, 2, 3, 4}; //Shift register pins used for Prefix sequence
  int PreFixStart = 18500; // Delay from audio start to firs prefix light
  int PreFixTimer = 0; //Light Number variable 
  unsigned long PreFixSeq = 0; //
  unsigned long PreFixSeqStart = 0; // Time since sequence was started
  const int PhaserLed = 8;
  

// Setup - Scene 3 - The Search For Spock
  const int TSFSButton = 16; //pin used for button
  int TSFSButtonState = 0; //check if the button has been pressed
  const int DestructPin = 1; //Bridge/BCdeck explosion light
  const int TSFSClock [3 ] = {13100 , 26378}; // Timeline in milliseconds between destruct sequence activation
  const int TSFS [3] = {0, 1, 2}; // pins on shifter register for destruct activation sequence
  int TSFSStart = 5573; // Delay for activation of destruct sequence
  int TSFSTimer = 0; //Light Number variable 
  unsigned long TSFSSeq = 0; //
  unsigned long TSFSSeqStart = 0; // Time since sequence was started
  bool Destruct = false; 
  unsigned long BridgeDestruct = 0;
  

// Setup - Scene 4 - The Voyage Home
  const int TVHButton = 17; // pin used for button
  int TVHButtonState = 0; // check button press state
  const int TVHEnd = 30000; // Length of time for Spotlight and 'A'
  int TVHStart = 27500; // delay for start of sequence
  const int TVHLed1 = 32; //
  const int TVHLed2 = 7; //
  int TVHTimer = 0; //Light Number variable 
  unsigned long TVHDelay = 0; //
  unsigned long TVHSeqStart = 0; // Time since sequence was started
  unsigned long TVHOff = 0;
  const long TVHRamp = 30000;


// Setup - Scene 5 - The Final Frontier 
  const int TFFButton = 18; //button for TFF sequence
  int TFFButtonState = 0; // check for presses
  const int warpFail [10] = {HIGH, HIGH, HIGH, HIGH, HIGH, LOW, HIGH, HIGH, HIGH, HIGH};
  const int FloodFail [7] = {50, 200, 400, 600, 800, 1000, 1200};
  int TFFTimer = 0; //Light Number variable 
  unsigned long TFFSeq = 0; //
  unsigned long TFFSeqStart = 0; // Time since sequence was started
  unsigned long FlickerSeqEnd = 0;


// Setup - Scene 6 - The Undiscovered Country
  const int TUCButton = 19; // button for sequence
  int TUCButtonState = 0; // check for presses
  const int TUCLed = 0b0000100; 
  unsigned long TUCStart = 0; // Save the Start time of the Button Press
  long TUCEnd = 30000;
  unsigned long TUCOff = 0;
  int TUC = 3;           // the PWM pin the LED is attached to
  const long TUCRamp = 90000;
  

/*
 *  Shift Register Assignements 
 *    R1 = In the base for TWOK Prefix and TSFS destruct countdown + 'A' light up in Base 
 *    R2 = Dockyard Light Sequence
 *    R3 & R4 = RegistryTop Saucer Explosions
 *    R5 = Floodlight Sequence
 * 
 */




  void setup() 
  {

// Lighting Effects Pins & Setp 
    pinMode (PhaserLed, OUTPUT); // Phaser LED on pin 8
    pinMode (impulsePin, OUTPUT); // Impulse LED on pin 5
    pinMode (warpPin, OUTPUT); // Warp LED on pin 6
    pinMode (DestructPin, OUTPUT); //Bridge & B/C deck explosion LEDs on pin 1
    digitalWrite(PhaserLed, LOW); //Phaser LED on pin 8
    analogWrite(impulsePin, LOW); 
    analogWrite(warpPin, LOW);
    analogWrite(TUC, LOW);
    shifter.clear(); //clears the shifter register on startup
    shifter.write();
    
// Scene Specific Button Pin Setup     
    pinMode (TMPButton, INPUT); // Scene 1 - TMP - Pin 14
    pinMode (TwokButton, INPUT); //Scene 2 - TWOK - Pin 15
    pinMode (TSFSButton, INPUT); //Scene 3 - TSFS - Pin 16  
    pinMode (TVHButton, INPUT); // Scene 4 - TVH - Pin 17 
    pinMode (TFFButton, INPUT); // Scene 5 - TFF - Pin 18 
    pinMode (TUCButton, INPUT); // Scene 6 - TUC - Pin 19   
    

// Audio Setup 
    audio.speakerPin = 9; // Speaker pin on output pin 9
    //Serial.begin(9600);
  //Serial.println("Initializing Card");
  pinMode(CS_PIN, OUTPUT);
  if(!SD.begin(CS_PIN)) {
    return;
  }
  audio.setVolume(5);
  audio.quality(1);
 
  
}  // end of setup

void loop() // The main program
  {

// Reset All Effects This is important so that the effects are reset at the end of each sequence   
  shifter.clear(); 
  shifter.write();  
  digitalWrite(PhaserLed, LOW);
  analogWrite(impulsePin, LOW);
  analogWrite(warpPin, LOW);
  analogWrite(TUC, LOW);

 
// Scene 1 = The Motion Picture
/*
 * Scene begins by turning of the drydock lights as shown in the movie, darkening the drydock for the floodlight sequence
 * impulse crystal ramps up
 * At the correct time in the audio, floodlights light up across the ship timed to the movie
 */


  TMPButtonState = digitalRead (TMPButton);
  if (TMPButtonState == HIGH){
     audio.play("TMP.wav");
   shifter.setPin(8, HIGH);
   shifter.setPin(9, HIGH);
   shifter.setPin(10, HIGH);
   shifter.setPin(11, HIGH);
   shifter.setPin(12, HIGH);
   shifter.setPin(13, HIGH);
   shifter.setPin(14, HIGH);
   shifter.setPin(15, HIGH);
   shifter.write(); 
    for (fadeVal = 0; fadeVal <= maxFade; fadeVal +=1) {
      analogWrite (impulsePin, fadeVal);
      for (fadeTime = 0; fadeTime <= fadeTo; fadeTime +=1) {
        fadeTo = fadeLength/8; // used for delay timer in fade loop
      }
    }
  delay(2000);
  
  DockSeqStart = millis(); // Marks the time when the timeline initiates
  for (DockCounter = 7; DockCounter >= 0; DockCounter--) {
   shifter.setPin(DockLed[DockCounter], LOW);
   shifter.write(); 
    DockSeqEnd = DockSeqStart + DockTime [DockCounter];
    while (millis() < DockSeqEnd) {} // Pause between dockyard lights
  }
   delay(15000); 
    FloodSeqEnd = millis() + FloodStart;
  while (millis() < FloodSeqEnd) {} // Initial Floodlight Delay 
  FloodSeqStart = millis(); // Marks the time when the timeline initiates (first floodlight zero-time)
  FloodSeqEnd = FloodSeqStart + FloodTime[1];
  for (FloodCounter = 0; FloodCounter < 7; FloodCounter +=1) {
   shifter.setPin(FloodLed[FloodCounter], HIGH); //activate each floodlight in the correct sequence and time
   shifter.write(); 
    FloodSeqEnd = FloodSeqStart + FloodTime [FloodCounter];
    while (millis() < FloodSeqEnd) {} // Pause between floodlights
  }
  delay(3000); //How long the floodlights stay on for after the sequence finishes
  }
  


// Scene 2 = The Wrath of Khan 
/*
 * Scene begins by activating the Red Alert lights on the base
 * When spock enters the prefix code, leds light in corresponding fashion
 * Phasers fire in sync with audio
 */


TwokButtonState = digitalRead (TwokButton);
  if (TwokButtonState == HIGH){
      audio.play("TWOK.wav");
   shifter.setPin(6, HIGH);
   shifter.write();
   PreFixSeq = millis() + PreFixStart; //Tells the sketch the time at which the sequence should start
   while (millis() < PreFixSeq){}  //Do Nothing Until the sequence start has passed

   PreFixSeqStart = millis(); // Marks time when first leds turns on 

//Lights up the Prefix Leds in time with Spock's entry 
 for(PreFixTimer; PreFixTimer < 5; PreFixTimer += 1){
    shifter.setPin(PreFix[PreFixTimer], HIGH);
    shifter.write();
    PreFixSeq = PreFixSeqStart + PreFixClock [PreFixTimer];
    while (millis() < PreFixSeq) {} // Pauses between Prefix Lightup sequence
      }
   shifter.clear(); 
  shifter.write(); 
   shifter.setPin(6, HIGH);
    shifter.write();
 
 // Fires the phaser in two timed bursts 
 // First phaser blast is 1800ms (phaser on/off pulse lasts 50ms. 1800/50=36. Keep this in mind if you change delay variable for the phaser pulse
 for(int x = 0; x < 36; x++){   
    digitalWrite(PhaserLed, HIGH);
    delay(25); //if you change these values, you will have to recalculate the absolute value for x. 
    digitalWrite(PhaserLed, LOW); 
    delay(25);
  }
    delay(1450);
//Second phaser blast is 2100ms (phaser on/off pulse lasts 50ms. 2100/50=42. Keep this in mind if you change delay variable for the phaser pulse     
  for(int x = 0; x < 42; x++){   
    digitalWrite(PhaserLed, HIGH);
    delay(25);
    digitalWrite(PhaserLed, LOW); 
    delay(25);
  }
  delay(40000);
  }

  
// Scene 3 - The Search For Spock
/*
 * Red Alert lights turn on right away
 * Leds (same as prefix code) turn on timed to Kirk, Scotty, and Chekov's destruct codes
 * When final destruct code is entered, 6 Leds light up and turn off at 10s intervals. Final leds pulses in time with final 10s countdown
 * Bridge explosion LEDs fire
 * Random pattern of explosions on top saucer and registry fire
 */


 TSFSButtonState = digitalRead (TSFSButton);
  if (TSFSButtonState == HIGH){
      audio.play("TSFS.wav");
      Destruct = true; //Signals the sketch to run destruct sequence at correct time
      shifter.setPin(6, HIGH); 
      shifter.write();
   TSFSSeq = millis() + TSFSStart;
   while (millis() < TSFSSeq){}  //Do Nothing Until Correct Audio Time has Elapsed 

   TSFSSeqStart = millis(); // Marks time when first leds turns on 

 for(TSFSTimer; TSFSTimer < 3; TSFSTimer += 1){ //activate the led for each destruct confirmation code
    shifter.setPin(TSFS[TSFSTimer], HIGH);
    shifter.write();
    TSFSSeq = TSFSSeqStart + TSFSClock [TSFSTimer];
    while (millis() < TSFSSeq) {} // Pauses between destruct confirmation Lightup sequence
      }
  }

//Starts the One Minute Self destruct countdown
if(Destruct){      
   shifter.setPin(0, HIGH);
   shifter.setPin(1, HIGH);
   shifter.setPin(2, HIGH);
   shifter.write();
      delay(15139);
   shifter.clear();
   shifter.write();
   for(int SDLed = 0; SDLed <=5; SDLed++){
   shifter.setPin(SDLed, HIGH);
     shifter.write();
     delay(50);}
      delay(4050);
  for(int SDLed = 5; SDLed >=1; SDLed--){
    shifter.setPin(SDLed, LOW);
     shifter.write();
     delay(10000);}
  for(int SDLed = 0; SDLed <10; SDLed++){
    shifter.setPin(0, LOW);
    shifter.write();
    delay(950);
    shifter.setPin(0, HIGH);
    shifter.write();
    delay(50);  
  }

 // Destruct begins with explosion in Bridge & B/C decks  
    digitalWrite(DestructPin, HIGH);
    delay(500);
    digitalWrite(DestructPin, LOW);
    delay(50);
    digitalWrite(DestructPin, HIGH);
    delay(100);
    digitalWrite(DestructPin, LOW);
    delay(100);
    digitalWrite(DestructPin, HIGH);
    delay(150);
    digitalWrite(DestructPin, LOW);
    delay(50);
    digitalWrite(DestructPin, HIGH);
    delay(200);
    digitalWrite(DestructPin, LOW);
    delay(50);

//Destructs continues with random explosions (14 Leds)
for(BridgeDestruct = 0; BridgeDestruct < 300; BridgeDestruct++){
    shifter.setPin(random(16,31), HIGH);
    shifter.write();
    delay(50);
    shifter.clear(); 
    shifter.write(); 
    delay(50); 
    Destruct = false;   
  }

}



// Scene 4 - The Voyage Home
/*
 * When Enterprise A is revealed, registry floodlight turns on and a backlit 'A' lights up on the Base 
 * When Kirk orders the ship to warp, warp effect fades up 
 */
 
  TVHButtonState = digitalRead (TVHButton);
  if (TVHButtonState == HIGH){
      audio.play("TVH.wav");
     TVHDelay = millis() + TVHStart;
 while (millis() < TVHDelay){}  //Do Nothing Until Correct Audio Time has Elapsed   
    TVHSeqStart = millis();
    shifter.setPin(TVHLed1, HIGH); //Turns on registry spotlight
    shifter.setPin(TVHLed2, HIGH); //turs on backlit 'A' on Base 
    shifter.write();
    TVHOff = TVHSeqStart + TVHEnd;
  while(millis() < TVHOff){}
delay(2400);

//Fades up Warp drive 
  for (fadeVal = 0; fadeVal <= maxFade; fadeVal +=1) {
      analogWrite (warpPin, fadeVal);
      for (fadeTime = 0; fadeTime <= fadeTo; fadeTime +=1) {
        fadeTo = TVHRamp/8; // used for delay timer in fade loop 
      }
  }
  delay(7000); 
  }

 

// Scene 5 - The Final Frontier
/*
 * registry floodlight flickers as though malfunctioning during Scotties log entry
 * Red Alert LEDs flicker as though malfunctioning when Red Alert starts 
 * 
 */

  TFFButtonState = digitalRead (TFFButton);
  if (TFFButtonState == HIGH){
      audio.play("TFF.wav");
   shifter.setPin(8, HIGH);
    shifter.write();
    delay(2000);
    FlickerSeqEnd = millis() + 47500; //Marks the time when the flickering should end

//Random flickering of registry flood lights  
   while(millis() < FlickerSeqEnd){
    shifter.setPin(8, LOW);
    shifter.write();
    delay(FloodFail[random(0,6)]);
    shifter.setPin(8, HIGH);
    shifter.write();
    delay(FloodFail[random(0,6)]);
 
 
    }
//Random flickering of Red Alert effect     
    shifter.setPin(8, LOW);
    shifter.write();
    FlickerSeqEnd = millis() + 11238;
    while(millis() < FlickerSeqEnd){
    shifter.setPin(6, HIGH);
    shifter.write();
    delay(FloodFail[random(0,6)]);
    shifter.setPin(6, LOW);
    shifter.write();
    delay(FloodFail[random(0,6)]);
  }
  }

  
// Scene 6 = The Undiscovered Country 

/*
 * Starfield LED ramps up as Kirk orders 'second star to the right'
 * Warp mode activates when order is given 
 * 
 */
 
  TUCButtonState = digitalRead (TUCButton);
  if (TUCButtonState == HIGH){
      audio.play("TUC.wav");

    delay(25496);
// Fades up the LED connected to pin 3 (starfield effect)     
  for (fadeVal = 0; fadeVal <= maxFade; fadeVal +=1) {
      analogWrite (TUC, fadeVal);
      for (fadeTime = 0; fadeTime <= fadeTo; fadeTime +=1) {
        fadeTo = TUCRamp/8; // used for delay timer in fade loop
      }
  }
    delay(5712);
// Fades up the Warp engines     
    for (fadeVal = 0; fadeVal <= maxFade; fadeVal +=1) {
      analogWrite (warpPin, fadeVal);
      for (fadeTime = 0; fadeTime <= fadeTo; fadeTime +=1) {
        fadeTo = TVHRamp/8; // used for delay timer in fade loop 
    
      }
  }
    delay(63000);
// Turns off Warp and starfield effects at end of sequence    
   for (fadeVal = 255; fadeVal >= 0; fadeVal-=1) {
      analogWrite (warpPin, fadeVal);
      analogWrite (TUC, fadeVal);
      for (fadeTime = 0; fadeTime <= fadeTo; fadeTime +=1) {
        fadeTo = TUCRamp/8; // used for delay timer in fade loop 
      }
   }
  
  }
  }  // end of loop