Note from Zach: I wrote this about 7 months ago. Still catching up on background documentation, so I've pasted the post as-is. A bit wordy, I'm afraid... read on if you're bored.
I've been meaning to write this article for some time to document a few generations of Neuron prototypes. For starters, I'll humbly defer to Andrew to cover the original model; while it didn't have an official version number, you can think of it as v0.1. This first proof of concept was a software simulation, a Ruby script that lived in Sketchup and allowed one to seemingly imbue a wireframe model with life. We'll call that Part One of Neuron Development.
I started working on the project after v0.1 was created and improved a few times: tweaking threshold and falloff coefficients, adding a simple GUI, that sort of thing. We quickly decided that the next version would be completely different, present in the physical world with all of its benefits and constraints. Our experience with the Sketchup plugin helped us understand the need for large numbers of devices; while four Neurons could build a basic self-sustaining loop, eight Neurons made the loop more visible. Sixteen Neurons could be linked into two loops of different sizes; impulses would travel quickly around a small loop, dumping new signals into the larger loop until it was finally saturated with noise. Thirty-two Neurons could build an interconnected web whose function was impossible to predict. Larger numbers allowed one to actually visualize "waves" of data sweeping across vastly complicated networks. We found that some complex models produced rapidly building oscillations, while others couldn't keep a signal moving for more than a few seconds.
At that moment, we realized that we needed to scale up production rapidly: not just for cost reduction and packaging improvement, but to demonstrate the truly fascinating potential of the system. Financial optimization was still important, though. Without even considering production scale-up, just building enough prototypes could be cost prohibitive. With that in mind, we decided to intentionally build several generations of devices, hoping to 'iron out' some initial issues while minimizing iterations and design time in the interest of personal engagement.
v0.2: the first physical Neuron prototype
Our main concern with v0.2 was building enough devices to sustain a loop. Without getting into too much detail--that's for another post that dives into potential levels and such--it's important to know that Neurons can only send signals so fast before recovering for a set period of time. The relationship between the recovery time and the signal propagation rate through a network tells you how many Neurons you need to build a loop that never ends: that is, a pulse that starts moving will continue exciting downstream Neurons and moving around the loop, even upon returning to the originator of the first signal. However, optimizing just for those two values will create a scenario so granular it is difficult to discern signal direction; two Neurons, even sending signals unidirectionally via different physical channels, would appear to alternate. Because of this, and to improve the general look of the system, we decided to construct five devices.
The original Sketchup program used colors to indicate internal potential levels of Neurons: green for ground state, turning red as potential increases, then flashing to blue during the recovery period when the internal level is negative. Using these colors produces an interface simple enough to be interpreted en masse: glancing at a network would tell you activity level and make it easy to identify unintentional loops and epileptic conditions. I'd previously worked with RGB pixels of various sorts; once you had the libraries working right, it was pretty easy to modulate colors and intensities to represent complex conditions. I picked up a handful of Adafruit's breadboard-ready Neopixels; I figured they'd have good libraries that would make them easy to integrate, and they were pretty much plug-and-play. For controllers, we got a set of Sparkfun's Arduino Pro Mini 5vdc boards: pretty much the cheapest official Arduino board we could find, and we figured we could repurpose them for other projects at a later point. These aren't natively USB-capable, so we picked up an FDTI cable too for burning new programs.
We knew this first version would be confined to a breadboard; we could have soldered short jumpers directly to the Pro Mini boards and skipped header altogether, but we didn't want to waste a bunch of Arduinos on a potentially failed design. Once the parts arrived, we soldered on header and crammed five of the assemblies into my 2-row breadboard. Not much room for anything, but fortunately the discrete components supporting the LEDs and microprocessors are limited: a few pull-down resistors for the inputs and a large electrolytic capacitor on the power rail is about it. I also like to put a PCB-mount switch on my breadboard with a little power LED, so I can quickly shut down circuits when I put something in backwards. In the video below, one switch excites the Neuron directly to its right, while the other switch excites all five. They're also connected to each other in a counter-clockwise loop:
I'm not going to go in depth on firmware here: partially because I'm more of a hardware guy, and partially because the code I originally worked out for those Neurons never quite worked right. Andrew put together a version of the program that performed much more reliably, but we ran in to a few problems along the way:
- Bouncy switches. That is to say, buttons that "ring" a bit after they're pressed. Most pushbuttons--espeically the cheap PCB-mounted ones I picked up at the local surplus shop--don't make contact cleanly when they're pressed, instead oscillating rapidly between open and closed over the course of a few milliseconds. Rather than adding a debounce routine into our code, we solved this problem using a simple RC circuit to smooth out the 'low' pulses.
- Variable size. We'd power on a few Neurons and run a test or two, then roughly a minute into the run they'd do weird things. Change color, stop responding, or (more frequently) just turn off. We were using unsigned 8-bit integers to measure milliseconds, and once we got to exactly 65.536s (i.e. 2E8 ms), those variables would overflow and weird things would happen to the loops and routines that depended on them. Bumped up to doubles, which are 16-bit integers capable of counting for quite a few more milliseconds.
- Timing. This would prove to be the ultimate downfall of v0.2: the LEDs, while beautiful and easy to use, were extremely processor intensive. Neopixels are WS2812 LED chips mounted on various types of boards, and the WS2812 is a unique beast: it's actually a tiny white circuit board potted in an epoxy lens, containing an RGB LED chip and a dedicated (tiny) microprocessor. In order to render various colors, you just send that microprocessor 8-bit serial data: it uses an internal PWM module to vary the brightness of the three LED elements as needed based on the data it receives. Trouble is, they're really optimized for larger displays of 20, 50, or even more devices. Since they all run on the same serial bus, and they're intended for use at 30Hz update rates for displays, that serial protocol is fast. As in, 800 kHz timebase fast! That may not seem like much, but the Arduino's ATmega chip runs at 20 MHz... and we were running enough code beyond the NeoPixel firmware that we couldn't get a reliable data connection going to the LED. I think it's a problem that we ultimately could have solved given enough work (probably through better use of interrupts and generally more efficient code), but we decided to move on.
Now that I've made it this far, I think I'll split off the next generation into a separate post. v0.3 goes after the problems listed above, and discovers some new ones along the way.