Final Update - She's alive!
I said my next post would be the reveal thread, well... I decide to post info on the programming first. I'm calling it complete, just need to take some photos and video. But here's a sneak peak...
Warning... Nerd content to follow.....
First, some fair warning...
- I'm a professional software engineer.
- I enjoy programming, especially small, new projects; not so much the crusty, old behemoth I work on daily.
- The code for this project is completely over-engineered.
- I'm a nerd...
With that out of the way, here is the new monster I built. If you just want to find the code, I put it on GitHub, the main source file can be found here:
https://github.com/bobm91001/Millennium ... Falcon.cpp. Feel free to do with it what you will. I developed this using PlatformIO in Atom, not the standard Arduino programming environment; I wanted an environment that is more powerful than the standard. I believe the code can be pasted as is into the Arduino editor, with one line at the beginning removed, "#include <Arduino.h>"; however I have not tried that. Also, I included some code below, none of it was actually tested so...
The goals of this effort were two fold. First, learn Arduino programming; I'm a very experienced C++ programmer on Windows, but I know there would be lots of limitations to deal with on the Arduino. Second, I wanted my Falcon to have some "life", not just sit there and glow (OK, it still just sits there and glows and flickers and flashes, but that's a bit better). So I developed a whole "sequencing scheme" that allowed me to "tell a story", albeit a very short one... I think I achieved both goals.
So first, the learning part. If you Google for examples of controlling and flashing LEDs, you usually find some very simple examples. These examples are great for controlling one or two LEDs; my Falcon has 20 LEDs on 6 circuits. Those simple examples just won't cut it. Why? To create a flashing LED, most of these have code that looks something like this:
int LEDpin = 3;
void loop() {
analogWrite(LEDpin, 255); // Turn on LED....
delay(500); // ...for a half second
analogWrite(LEDpin, 0); // Turn off LED....
delay(500); // ...for a half second
}
Great, you've flashed one LED on/off in one second intervals. What if you want two LEDs, one flashing at 3 times a second and the other twice a second? That can't be done with the above code. The problem is the two "delay(500)" calls; that means every time through the loop will take a full second, if I need something else updated more frequently than that, well, I'm out of luck.
So a better way to do this is to remove all delays from your loop() and control the LEDs as a function of time. For example:
int LEDpin=3;
int cycleStartTime=0;
bool LEDisOn = false;
void loop() {
int now = millis(); // Get current time
// See if we reached the end of a 1 second cycle, if so start new cycle
if (now - cycleStartTime > 1000) {
cycleStartTime = now;
}
// If we are in the first half of the cycle,
// turn the LED on if needed
if (now - cycleStartTime < 500 && !LEDisOn) {
analogWrite(LEDpin, 255);
LEDisOn = true;
}
// ... otherwise if in the second half, turn it off if need
else if (now - cycleStartTime > 500 && LEDisOn) {
analogWrite(LEDpin, 0);
LEDisOn = false;
}
}
Hopefully you can imagine how that could be changed to control 2, 3 or more lights.
But as the number of lights, and complexity of the flashing, glowing, flickering increases; the above gets more and more complex to manage. So, I developed some C++ classes to manage my Falcon. But that's getting into some semi-deep C++ programming. I won't go into the details here, that can be found in the source (link above). I will, however give an overview of what I built.
It started with an LED class, basically you make one for each LED circuit you want to control. I have 6 such circuits, the cockpit (1 LED), the headlights (2 LEDS), the landingLights (11 LEDs) and three engine light circuits (2 LEDs each). Each LED can be put into one of several modes: off, on, ramp, sinusoid and flicker. Each of these modes has some parameters to control them, on has the brightness value; ramp has the target brightness and a timespan to get there; sinusoid has a min and max brightness, a period (time for one on/off cycle) and a phase; flicker just has min and max brightness (it randomly updates the brightness between those two values). You can set any LED into any of these mode and then just call it's update(now) function inside your loop() and it will update it's brightness appropriately. Nice.
In order to manage some slightly more complicated behaviors for the engine lights, I created an Engine class that controls the 3 engine LED circuits using 3 LED classes. The Engine has 7 states, each representing different patterns of the 3 LED circuits: off, idling, fullPower, failing (it is the Falcon after all), rampingUp, rampingDown and landing.
Finally, in order to "tell a story", I built a simple state machine that runs all the lights. The state machine just manages the current state of the entire system and transitions between states based on the current time. Each state sets the various LED circuits to a specific mode. The states also have a time duration and a next state. The states are: onGround, PrepareForFlight, InFlight, Landing, and (again since it is the Falcon) FailingStart, Failing, EmergencyShutdown and Restarting. Details of the states are:
OnGround:
- cockpit light ramps to and stay at full brightness
- headlights ramp to and stay off
- after 1 second, landingLights ramp to and stay at full brightness
- engine is set to idling (low, pulsing lights)
- time in state is a random time between 5 and 20 seconds
- next state is randomly chosen between PrepareForFlight and FailingStart
PrepareForFlight:
- after 2 seconds, cockpit dims to a low level
- after 1.4 seconds, headlights ramp to full brightness
- landingLights ramp to off
- engine is set to rampingUp over a 6 second interval
- time in state is 6 seconds
- next state is InFlight
InFlight:
- cockpit light stays dim
- headlights stay full brightness
- landingLights stay off
- engine is set to fullPower, full brightness with some flickering
- time in state is random between 10 and 20 seconds
- next state is Landing
Landing:
- cockpit ramps to nearly full brightness
- after 1.5 seconds landingLights ramp to full brightness
- after 1 second headlights ramp to off
- engine is set to landing, ramps LEDs to idle level over 4 second period
- time in state is 4 seconds
- next state is OnGround
FailingStart:
- all LED modes match PrepareForFlight
- time in state is random between 2 and 4 seconds
- next state is Failing
Failing:
- cockpit starts flickering after .1 to 1.5 seconds
- headlights start flickering after 1 to 2 seconds
- landingLights start flickering after .1 to 2 seconds
- engine is set to failing (various different LED modes)
- time in state is random from 1 to 2 seconds
- next state is EmergencyShutdown
EmergencyShutdown:
- headlights ramp to off
- after .75 seconds, cockpit ramps to off
- landingLights ramp to off
- engine set to rampingDown
- time in state 5 seconds
- next state is Restarting
Restarting:
- headlights stay off
- cockpit ramps to full brightness
- after 2 seconds, landingLights ramp to full brightness
- engine is set to off
- time in state is 4 seconds
- next state is OnGround
Phew! That seems like a lot, but once the structure was in place adding new states and transitions was pretty easy. Over-engineered? Yeah, probably, but it was good practice for future Arduino projects.
Speaking of over-engineering... As you saw many aspects of this have a random component to them (flickering lights, time spent OnGround, InFlight, and FailingStart, and whether the Falcon has a successful PreprareForFlight or switches to a FailingStart). Randomness is controlled via the builtin routine random() which returns a "random" number. Well, if you know anything about computers (and humans), they aren't very good at truly random behavior (I know, you have plenty of friends that contradict that assertion). In fact, you if just start calling random() in your Arduino code, you will always get the same sequence of "random" numbers.... Not very random. However, you can use the function randomSeed() and pass it a starting point in the sequence. Great, just pass it a random number to get a random location in the sequence... Oh, wait. Where do we get a random seed? Well, if you look at most Arduino tutorials you'll find the suggestion to read an unconnected analog input pin which should have some random noise on it, something like:
randomSeed(analogRead(0));
Well, those with even more time and interest have studied this and it turns out that analogRead() on an unconnected pin returns fewer "random" numbers that one would like. Well, there is a lot of work that has gone on to develop "truly" random number sequences on Arduinos, but those efforts are for way more serious applications than running LEDs on a model...
Anyway, that was a long way to get to the fact that I built a somewhat better random seed generator. I don't use any of the analog pins in my project, so I wrote a simple routine that reads all of them and generates one random number using the noisiest last 4 bits of from each pin. It looks like this:
unsigned long generateRandomSeed()
{
unsigned long seed = 0;
for (int i = 0; i < 8; ++i)
{
seed = seed << 4 | (analogRead(i) & 0x0f);
}
return seed;
}
I did a quick test and out of 10,000 calls I got about 5,000 different seed values... Good enough for me!
Congratulations (or condolences?) to those that made it to the end. But that's enough nerding for today. Time to cleanup my workbench, then take some photos and a video for the reveal!
Bob