Starship lighting
Here’s a quick preview of the lighting circuit I have planned for the 1/350th scale Polar Lights Enterprise refit model that I’ve been working on for more than a decade. (Uncertainty about how to light the model led to my glacial progress.) I am planning a comprehensive lighting solution that will power the collision strobes, navigation lights, deflector dish, warp nacelles, floodlights, phasers, photon torpedoes and RCS thrusters. That solution will be powered by an ATtiny85 and an ATMega328.
I’m still working on the code that will replicate the drydock startup sequence, but I’m eager to share what I’ve learned about the Arduino and Tinkercad. So here’s the ATtiny85’s contribution, powering the strobes, nav lights, deflector dish and warp nacelles.
You can see this example in action at Tinkercad by clicking the picture above (you will need to register at Tinkercad). If you’re not familiar with it, Autodesk Tinkercad is a free 3D modeler and circuit simulator. The Circuit module can simulate the open source Arduino Uno microprocessor, letting you test code—called sketches—and circuits. Tinkercad has a number of built-in components like resistors, capacitors, LEDs, breadboards, power supplies and ICs like the 555 timer or the 74HC595 shift register. Unfortunately you’ll quickly wish Tinkercad had other components, like a Darlington array, or more built-in libraries, but it’s still a great playground.
In the frame below, you should be able to see an embedded working circuit (Safari users: it may require using Chrome).
In this example, I’ve programmed the ATtiny85, a stripped down 8-pin version of the Arduino Uno, to control up to eight LEDs on one output pin. The current on any of the five ATtiny output pins is limited to 40 mA—maybe enough to light two LEDs—so I’ve included two NPN transistors to connect the eight navigation lights and four strobes to a separate 5V DC power supply. Tinkercad provides a generic transistor, but I’ll be using a 2n3904 on the final circuit. NOTE: Tinkercad labels its transistor legs as collector, base and emitter, but a 2n3904 is actually emitter, base and collector.
One peculiarity of Tinkercad is that it thinks all LEDs have the same voltage drop, regardless of the color of the LED, so all the resistors in this example are identical values. If you try to reproduce this circuit for real, you’ll need to figure out the correct resistor for each LED. NOTE: I often just use white LEDs and color them with transparent paints.
In this example, pins 2 and 3 of the ATtiny (numbered pins 7 and 2) are used as output pins for the nav lights and strobes. Pins 0 and 1 (numbered pins 5 and 6) are used to light a yellow and blue LED to simulate the deflector dish changing color when changing from impulse to warp. In the final lighting board, I’ll be using a bi-color amber/blue LED, but Tinkercad doesn’t include one. Note that the two LEDs share a single resistor, because only one LED at a time has power.
Pins 0 and 1 are Pulse Width Modulation output pins, so the deflector dish can fade from yellow to blue gradually. In the sketch, this gradual fade is controlled by repeatedly calling the fadeDeflector function based on whether enough time has elapsed and whether the user has pressed the button that switches from impulse to warp. Pin 1 (numbered pin 6) is also connected to a MOSFET that can power a large number of LEDs, like the two LED tape strips in the nacelles (only one is shown here).
There will be a number of timed events in the full lighting board(s) and ordinarily one would include a call to a timer library that would make it easier to call a function. Unfortunately it’s not possible to add additional libraries to Tinkercad and surprisingly a timer is not one of the built-in libraries.
I was, however, able to copy and paste into the sketch the very simple and useful LedFlasher library created by Nick Gammon. Basically I just pasted the class definition and constructor directly into the sketch, instead of simply using an #include call. I was never able to copy and paste a timer library, however.
The last feature of this circuit gradually lights an LED strip when the deflector fades up to blue. Unfortunately Tinkercad doesn’t include an LED tape component, so I’ve created a nine-LED strip here. An LED strip is basically groups of three LEDs wired in series with a resistor, and each group is wired in parallel. So you can you can hack a lot of components that aren’t included in Tinkercad; it’s just not simple.
Pin 4 (numbered pin 6) is declared as an input pin and it’s connected to a momentary button. This button switches from amber/impulse to blue/warp for the deflector.
I’m still waiting for the ATtiny85 and ATMega328 I ordered and I’ll post a video of the working circuit soon.
I also want to credit Trevor at the Ostrich Longneck YouTube channel. I’ve based my sketches and circuits on his videos, and I’m very grateful for his tutorials.
PS I forgot to explain that I really know next to nothing about electronics. I am familiar with coding, but my knowledge of electrical circuits is gained by deciphering the work of others. Almost everything I know I learned from watching YouTube videos. So please don’t expect me to know how to diagnose your efforts to replicate this circuit. One piece of advice is not to use Apple’s Safari browser to run Tinkercad; it runs abysmally slow. Chrome works much closer to real time.
/*
// LedFlasher library
// Author: Nick Gammon
// Date: 23 December 2012
Usage:
LedFlasher ledName (pin, offtime, ontime, initiallyactive); // set parameters
eg.
LedFlasher laserTurrent (5, 1000, 2000, true); // set parameters. pin 5, off for 1000 mS, on for 2000 mS, initially active
laserTurrent.begin (); // initialize
laserTurrent.on (); // turn on
laserTurrent.off (); // turn off
bool isOn = laserTurrent.isOn (); // is it currently on?
laserTurrent.update (); // call in loop function
EXAMPLE CODE
#include <LedFlasher.h>
// set up some LEDs
LedFlasher floodLight (8, 200, 300);
LedFlasher shuttleBayDoors (9, 300, 600);
LedFlasher impuleEngine (10, 900, 100);
LedFlasher strobe (11, 500, 1000);
LedFlasher navigation (12, 1000, 2000);
LedFlasher torpedoes (13, 250, 500);
void setup()
{
floodLight.begin ();
shuttleBayDoors.begin ();
impuleEngine.begin ();
strobe.begin ();
navigation.begin ();
torpedoes.begin ();
} // end of setup
void loop()
{
// update lights
floodLight.update ();
shuttleBayDoors.update ();
impuleEngine.update ();
strobe.update ();
navigation.update ();
torpedoes.update ();
// do other useful stuff here ...
} // end of loop
*/
#pragma once
class LedFlasher
{
public:
// constructor
LedFlasher (const byte pin, const unsigned long timeOff, const unsigned long timeOn, const bool active = true);
void begin ();
void update ();
void on ();
void off ();
bool isOn () const;
private:
const byte pin_;
const unsigned long timeOff_;
const unsigned long timeOn_;
bool active_;
unsigned long currentInterval_; // time till we change state
unsigned long startTime_; // when we last changed state
}; // end of LedFlasher class
// constructor
LedFlasher::LedFlasher (const byte pin, const unsigned long timeOff, const unsigned long timeOn, const bool active) :
pin_ (pin), timeOff_ (timeOff), timeOn_ (timeOn)
{
currentInterval_ = timeOff_;
startTime_ = 0;
active_ = active;
} // end of LedFlasher::LedFlasher
// set pin to output, get current time
void LedFlasher::begin ()
{
pinMode (pin_, OUTPUT);
digitalWrite (pin_, LOW);
startTime_ = millis ();
} // end of LedFlasher::begin
// call from loop to flash the LED
void LedFlasher::update ()
{
// do nothing if not active
if (!active_)
return;
unsigned long now = millis ();
// if time to do something, do it
if (now - startTime_ >= currentInterval_)
{
if (digitalRead (pin_) == LOW)
{
digitalWrite(pin_, HIGH);
currentInterval_ = timeOn_;
}
else
{
digitalWrite(pin_, LOW);
currentInterval_ = timeOff_;
}
startTime_ = now;
} // end of if
} // end of LedFlasher::update
// activate this LED
void LedFlasher::on ()
{
active_ = true;
startTime_ = millis ();
currentInterval_ = timeOff_;
} // end of LedFlasher::on
// deactivate this LED
void LedFlasher::off ()
{
active_ = false;
digitalWrite(pin_, LOW);
} // end of LedFlasher::off
// is it active?
bool LedFlasher::isOn () const
{
return active_;
} // end of LedFlasher::isOn
// Simple Navigation & Strobe light flashing
// Author: Ostrich Longneck
// Date: 1 November 2015
// LedFlasher Library by Nick Gammon, 23 Dec 2012
// Modifications by Jennifer Petkus Jan 11, 2021
// set up the LEDs
LedFlasher strobeLights (3, 900, 100);
LedFlasher navLights (2, 500, 500);
const int impulsePin = 0;
const int warpPin = 1;
const int warpBtnPin = 4;
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 fadeVal = 0;
unsigned long prevWarpTime = 0;
unsigned long warpIncrements = 500 / 255;
bool startUp = 1;
void setup() {
strobeLights.begin ();
navLights.begin ();
pinMode (impulsePin, OUTPUT);
pinMode (warpPin, OUTPUT);
pinMode (warpBtnPin, INPUT);
analogWrite(impulsePin, 0);
analogWrite(warpPin, 0);
} // end of LED setup
void loop() {
strobeLights.update ();
navLights.update ();
if (startUp) {
warpChange = 1;
warpStatus = 1;
fadeUp = 1;
}
if (digitalRead(warpBtnPin) == HIGH) {
if (!warpBtnPressed) {
warpChange = 1;
warpBtnPressed = !warpBtnPressed;
}
} else {
warpBtnPressed = 0;
}
if (warpChange && millis() - prevWarpTime > warpIncrements) {
prevWarpTime = millis();
fadeDeflector();
}
}
void fadeDeflector () {
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
fadeVal--;
if (fadeVal < 0) {
analogWrite(down_pin, 0);
fadeUp = 1;
} else {
analogWrite(down_pin, fadeVal);
}
} else { // fade up
fadeVal++;
if (fadeVal > 255) {
startUp = 0;
analogWrite(up_pin, 255);
warpChange = 0;
fadeUp = 0;
warpStatus = !warpStatus;
} else {
analogWrite(up_pin, fadeVal);
}
}
}