Starship lighting working circuit
In a previous post I’d promised a working example for the basic starship lighting circuit using an ATtiny85. Well here it is, in all its breadboarded glory. Of course the breadboard version doesn’t look as neat as the Tinkercad prototype and it is fragile; bump it and connections come loose. However, I am able to include calls to additional libraries that make programming much easier, and I can now use real components like a bi-color LED rather than two different colored LEDs side-by-side.
Before we look at this working example, I thought I’d go a little more into the design of the full Enterprise refit lighting design. As I mentioned in the previous post, the design will include collision strobes; navigation and formation lights; deflector glow and impulse deflection glow from amber to blue; warp nacelle and magnatomic amplification crystal glow to blue; spotlights; RCS thrusters; phaser and photon torpedo firing; sound effects; and overall internal lighting. Unfortunately powering all that requires more than the pins on an ATMega328 chip if you include button inputs. So I had to offload some of the basic functions onto the ATtiny85.
Programming an ATtiny85, however, is more complicated than programming an Arduino Uno. For convenience, I wrote and tested the program to run on the Arduino Uno and breadboarded the circuit to see if it would work. Then I changed the Uno pin configuration to the ATtiny85, but even though I was careful, I still confused the physical pin layout with the internal pin numbers.
Next, I had to upload the In System Programmer code to the Uno and then burn a bootloader onto the ATtiny85 chip I had bought. The bootloader basically turns the chip into an Arduino. It’s possible there was already a valid bootloader on the chip, but it’s best to make sure. And finally, I had to upload the lighting sketch to the ATtiny and test it on the breadboard. Don’t worry, I’ll address all this in a separate article.
Now the sketch for the working example is different than for the Tinkercad version because I could include the LedFlasher and arduino-timer libraries. I had to download the libraries and then put them in the appropriate Arduino directory or folder, but once installed, they can be referenced by the #include command.
Of course the sketch didn’t work, or rather it worked imperfectly, because I hadn’t used the correct pin numbers. I had to make the changes and upload the corrected sketch, and I finally got it work … once I figured out that I had the pin assignment for the 2n3904 transistors backwards.
Nick Gammon’s LedFlasher library is a quick way to get a blinking LED and I use it for the strobes and nav lights. I use Michael Contrera’s arduino-timer library to repeatedly call the warpDeflFade function, which appropriately fades the deflector from amber to blue. There are several boolean flags that keep track of whether the ship is at impulse or warp and whether to fade down the bi-color LED or fade up. I’ve also added a flag that prevents overlapping presses of the button that switches from impulse to warp.
I should point out some of the less obvious features of the code.
- I am using unsigned long integer variables and constants to hold time values. I think in milliseconds this can hold values up to something like 50 days, so there’s little risk of inconsistent intervals.
- Ordinarily you don’t want to use delays in a sketch without darn good reasons. In this sketch, there’s a delay in setup() that should match the point in the spacedock startup sequence when the deflector begins to glow amber. I will be splitting the startup sequence across the ATtiny and the ATMega and I will want a button on a control panel to restart the sequence whenever I want to show off. This button will be wired to the RESET input of the two chips. The ATMega will cue the music and begin lighting the spotlights in sequence. The delay on the ATtiny’s sketch will light the deflector and begin the strobes and nav lights flashing at the right time. The one second delay is just a placeholder for now.
- I should also mention that looking at the startup sequence, it’s hard to tell exactly when the strobes and nav lights begin flashing. For my own convenience, I’ve decided it’s when the deflector first fades up to amber.
- I assign 2000 / 255 to the variable
warpDeflSteps
. Here I want the fade up/down to last two seconds, so I divide that time in milliseconds by the 255 Pulse Width Modulations increments the Arduino can write to its PWM pins. - I’ve made the timer task warpDeflTask a global variable
- In the warpDeflFade() function, I use the fadeUp boolean to determine whether to fade the deflector/nacelle up or down, and another boolean, warpStatus, that is used to switch which pins are being faded up or down. These booleans keep the function short. Also notice that I cancel the warpDeflTimer’s tasks after cycling through the fade up/down cycle. The task gets reassigned whenever the button is pressed.
/*
Flashing collision strobes and navigation lights;
changing deflector glow from amber to blue;
ramping nacelle glow from dark to blue;
on Enterprise refit model using ATtiny85
Inspired by Ostrich Longneck
LedFlasher library by Nick Gammon
Arduino-timer by Michael Contreras
Jennifer Petkus
January 25, 2021
*/
#include <LedFlasher.h>
#include <arduino-timer.h>
LedFlasher strobeLights (3, 900, 100); // NOTE! Pin numbers relate to input or output numbers, not physical layout of ATtiny
LedFlasher navLights (2, 500, 1000);
// settings for UNO
/*
const int impulsePin = 6;
const int warpPin = 5;
*/
// settings for ATtiny
const int impulsePin = 0;
const int warpPin = 1;
const int warpBtnPin = 4; // connects to button that switches from impulse to warp
bool warpStatus = 0; // 0: at impulse; 1: at warp
bool warpBtnPressed = 0; // 0: deflector not changing; 1: deflector changing
bool warpChange = 0;
bool fadeUp = 0; // prevents deflector fade up until fade down completed
int warpDeflFadeVal = 0;
// first number is how long fade takes, then divided by PWM values
unsigned long warpDeflSteps = 2000 / 255;
// don't make first number too long or steps will be obvious
// adjust this to match drydock spotlight startup sequence
unsigned long startupDelay = 1000;
// startup sequence starts over when ATtiny85 and ATMega328 are reset
void setup() {
strobeLights.begin();
navLights.begin();
pinMode(impulsePin, OUTPUT);
pinMode(warpPin, OUTPUT);
pinMode(warpBtnPin, INPUT);
analogWrite(impulsePin, 0);
analogWrite(warpPin, 0);
warpChange = 1; // will switch to impulse mode
warpStatus = 1; //
fadeUp = 1;
delay(startupDelay);
warpDeflTimer.every(warpDeflSteps, warpDeflFade); // initial fade up to amber
}
void loop() {
warpDeflTimer.tick();
strobeLights.update();
navLights.update();
if (digitalRead(warpBtnPin) == HIGH) {
if (!warpBtnPressed) { // fade starts after finger releases momentary button
warpChange = 1;
warpDeflTimer.every(warpDeflSteps, warpDeflFade);
warpBtnPressed = !warpBtnPressed;
}
} else {
warpBtnPressed = 0;
}
}
void warpDeflFade () {
int down_pin, up_pin;
if (warpStatus) {
down_pin = warpPin;
up_pin = impulsePin;
} else {
down_pin = impulsePin;
up_pin = warpPin;
}
//fade down then up
if (!fadeUp) { // fade down
warpDeflFadeVal--;
if (warpDeflFadeVal < 0) {
analogWrite(down_pin, 0);
fadeUp = 1;
} else {
analogWrite(down_pin, warpDeflFadeVal);
}
} else { // fade up
warpDeflFadeVal++;
if (warpDeflFadeVal > 255) {
warpDeflTimer.cancel();
analogWrite(up_pin, 255);
warpChange = 0;
fadeUp = 0;
warpStatus = !warpStatus;
} else {
analogWrite(up_pin, warpDeflFadeVal);
}
}
}
Finally, I think I should mention that I’ve decided that when at impulse, the deflector glows amber and when at warp it’s blue. Looking at Star Trek: The Motion Picture and The Wrath of Khan, I only see evidence of the amber deflector immediately after leaving spacedock. (In The Search for Spock, it actually glows blue while inside the Earth Spacedock.) Otherwise it’s blue, regardless of whether the ship is at warp. I’ve also decided that the magnatomic impulse cyrstal (towards the front of the nacelles) only glows blue when the ship is at warp. However, I’ve decided that the impulse deflection crystal (aft of the upper saucer) follows the status of deflector.