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