I'm putting the "kill switch" into the robot's head so we can turn off its lights during the blackout. And that build is making me aware of process again.
The way to get in trouble is to try to build an entire project at one go. Then when you have problems, you don't know which component of the system they are in, or if they are synergistic between components. And you also risk having a lot of work invested in soldering up fifteen identical circuits the wrong way, or putting the case together only to have magic smoke come out when you first put on the power.
You will always have failures. You will try ideas that don't work. You will run into both errors and unexpected issues that require re-think and re-building. The trick, though, is to have as many of these failures possible happen in such a place and such a way as to waste the least time and the fewest expensive components on the way.
The two basic techniques are modularization, and frequent testing.
A very important concept is proof-of-concept. The idea is that you want to, whenever possible and throughout the project, jury-rig a way to see if the thing will actually work. When you combine this with modularization, instead of trying to test the entire project, you find some element that can be cleanly separated, and make a test for that.
But this is getting way too vague and theoretical here. Let me be concrete with a recent example.
After a lot of waffling over the best way to rig this kill switch, I settled on some cheap 4-channel RF key fobs I have lying around, and a ULN2803 Darlington Array to do the heavy lifting. I already had an Arduino proto-shield wired up for the 4-channel receiver, so even though it offends me to stick an entire Arduino inside the robot just to switch some headlights on and off, doing it this way leverages a pre-built component that is already tested and working.
I'd never used the ULN2803 before. So what I did, is put one together on protoboard and write a quickly sketch to PWM an Arduino pin connected to it.
This is important...I'm not testing the final version software, I'm not testing multiple channels, and it is no-where near the robot. This is just a proof of concept for the ULN2803 LED driver.
That test passed. I drove over to the theater and picked up the robot (there's some other repairs I need to do before the weekend performance anyhow). Clipped the leads to the headlamp and using a bunch of alligator clips stretching from the work bench to the computer table, confirmed that the actual robot's LEDs also light correctly. This is testing that they are wired the way I thought I wired them.
The next incremental test is, still working outside the robot by splicing into the head wiring where it leaves the neck, was to tap the internal power supply and confirm that the Darlington will switch that correctly. At this point I was feeling confident enough to solder up the first part of the Arduino proto-shield and try powering it from the batteries as well. That didn't work -- until I remembered this was a Deicimilla and I had to switch the jumper manually from USB power to external power. And this is why we do it that way; the only changes in the circuit had been soldering on the power lugs and setting up the basic power busses on the proto-shield. So there were many fewer places to look at, and measure with the VOM, before I found my mistake.
Next step was checking the radio circuit Not much point in doing all this wiring if I can't issue the radio commands! I re-purposed some software from my old RF-to-MIDI device, yanked out most of it, deleted the PWM test routine I'd been using to test LEDs, and stuck in a serial monitor. Instead of trying the entire circuit at one go, I was just using the built-in terminal mode of the Aduino IDE to check if the receiver was responding correctly.
It was. I modified the software to make it a toggle framework, and checked that via the serial print to see that the right values were being thrown up. Then I took that center value and built a basic switch/case routine to interpret it.
Notice this isn't even touching the robot at this point. It is just checking to see if the RF receiver is detecting a signal. The mistake would be in soldering down the ULN2803, splicing it into the robot's power supply, writing the eye blink routines, then trying to diagnose the RF link within that environment. The problem might be RF, or it might be a cold solder joint, or an error in the PWM routines.
With the radio working, I strung some long test cables again. Note I'm still splicing into the head directly at this point; I haven't touched the ribbon cable that leads into the robot's trunk. The LEDs all worked, as did my program. I was running out of time, though, otherwise this would have been the time (with the Arduino outside of the robot and easy to access) to write more complex eye routines. After all, I'm using three PWM outputs, one for each color, so all sorts of animations are possible.
I chose, however, to splice both eyes together at the neck. I could always break them out later, but I didn't have time to run them both down the ribbon cable -- even though I had up to eight switching channels available on the ULN2803.
I tested each eye splice before heat-shrinking the connection. Pulled the ribbon out of the base of the robot and soldered it in, and tested with the Arduino still outside. The pink circuit was loose, and I pulled the shield off and re-touched the solder joints. Then, and only then, did I stick the Arduino inside the torso.
Divide and conquer. It's the way engineers work. There's plenty of time to discover all those wonderful synergistic problems when you've got the bugs hammered out of all the individual parts.
I did waste the time this week building "square candies that look round." The final form factor is small candies each sitting on top of an individual micro-servo. It was important to me to have them each behave as individuals, thus there are a lot of arrays in the program tracking individual behavior.
I had some problems in the software. I had to comment out the parts that incremented the servo position over time because that wasn't working. So the prototype version that -- was robust enough to take to the theater and put in the lobby for a test -- had the servos running at full speed. (And only had one candy -- the rest was just duplication and I saw no need to have the other candies for this test).
Turns out the program cycles slowly enough that there was no need to increment servo position fractionally -- which was the way the BlinkM software I'd re-purposed was doing it. So I wrote a new increment/decrement subroutine from scratch that adds an integer (generally between 6 and 12) to the servo position array before updating the PWM with a myServo.write. It is an annoying bit of cludge code and I'm sure there's a better way to do it, but it seems to work now.
Each candy has one of four states (with a status flag) as well as two incremented counters called "Bored" and "Excited" (Well, since these are array variables, actually "cubeBored[select]" but anyhow! The states are rest, searching, and two traverse states.
All of the behavior is triggered by a single Sharp IR proximity detector which was in my parts box. When something gets close enough to the Sharp, the candies go from "rest" to "move to face" -- with a slight randomness to how fast they move so, with luck, when I have all four candies installed they will appear to be moving as individuals.
The call to the Sharp is actually a subroutine, because there is a little noise in those detectors and I am leaving room to write a simple "debounce" snippet there.
When the candies arrive at full face they begin a random panning movement, which lasts until a random number is smaller than the incremented "bored now" counter. Then they enter a "move back to rest" state and we reset.
What I haven't done is built or bought a nice candy box to display them on. But it turns out there is very little room in the lobby and unless I can really impress someone with them they aren't going to get displayed. Ah, well. It was worth it to get more experience in servo subroutine programming.