Addressing many LEDs with a single Arduino
A fun little side project of mine is Arduino C/MRI, a library that lets you easily connect your Arduino projects up to the JMRI layout control software, by pretending to be a piece of C/MRI hardware. Hence the name.
A common problem when using Arduino C/MRI is dealing with lots of inputs and outputs. As an example, lets wire up a simple non-CTC crossing loop here in New Zealand. It is about as simple as you can get:
Each end consists of:
- A turnout. We'll need 1 digital output to drive that.
- A route indication signal on each leg of the turnout. We'll need an LED for red, and one for green (technically it'd be blue here in NZ). That's 3 pairs of outputs = 6 more.
- A push button to control the turnout. That's 1 digital input.
That's 8 pins right there, doubled for the other end of the loop, makes 16 pins. That's nearly an entire Arduino dedicated to just one piece of track! Naturally we'll be having more than just a single crossing loop on our railway, yet we have no more Arduino pins left. What are we to do?
Expanding outputs
The answer comes in the form of a 74 series logic chip, the 74HC595. This is a serial-in, parallel-out device. We send it the state of each pin using 3 data pins, and it updates each of its 8 pins. So already using 3 pins we're able to drive 8 output pins. But the best part? They can be daisy chained. That means with 3 data pins, we can control an unlimited number of 74HC595 devices! Suddenly our job just got a whole lot easier.
The schematic below demonstrates how one might do this:
Notice how the Q7'
pin is daisy-chained to the next device, while the ST_CP
and SH_CP
pins are shared. Now using 3 data pins we're addressing 16 outputs. Fantastic. What does the code to deal with this look like?
#include <CMRI.h> #define LATCH 8 #define CLOCK 12 #define DATA 11 CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs void setup() { Serial.begin(9600); // make sure this matches your speed set in JMRI pinMode(LATCH, OUTPUT); pinMode(CLOCK, OUTPUT); pinMode(DATA, OUTPUT); } void loop() { // 1: main processing node of cmri library cmri.process(); // 2: update output. Reads bit 0 of T packet and sets the LED to this digitalWrite(LATCH, LOW); shiftOut(DATA, CLOCK, MSBFIRST, cmri.get_byte(0)); digitalWrite(LATCH, HIGH); }
You can see we're using a new method here, cmri.get_byte(n)
. Rather than inspecting a single bit, this returns an entire byte, which we then shift out to the 74HC595 using the shiftOut
method. Toggling the LATCH
pin is how we tell the 74HC595 that we're busy sending it data; it only updates the output pins once we take the LATCH
pin high.
More inputs
That was pretty easy, but what if we have a massive CTC panel and want dozens and dozens of inputs? Or we have gone a little crazy with occupancy detectors? Can we do something similar? Luckily we can, using the CD4021 "8-Stage Static Shift Register". It's just the opposite of what we've seen above.
The schematic is a little messier because of all the pulldown resistors, but you get the idea: 3 data lines to the Arduino.
The code is a little more complex, but only slightly: (note: untested code)
#include <CMRI.h> #include// pins for a 168/368 based Arduino #define SS 10 #define MOSI 11 #define MISO 12 /* not used */ #define CLOCK 13 CMRI cmri; // defaults to a SMINI with address 0. SMINI = 24 inputs, 48 outputs void setup() { Serial.begin(9600); // make sure this matches your speed set in JMRI SPI.begin(); } void loop() { // 1: main processing node of cmri library cmri.process(); // 2: toggle the SS pin digitalWrite(SS, HIGH); delay(1); // wait while data CD4021 loads in data digitalWrite(SS, LOW); // 3: update input status in CMRI, will get sent to PC next time we're asked cmri.set_byte(0, SPI.transfer(0x00 /* dummy output value */)); }
The connections from the above schematic are:
- dataPin -> MISO (12)
- latchPin -> SS (10)
- clockPin -> CLOCK (13)
We're using a new method again here, the cmri.set_byte(n, b)
which sets the given byte to the value read in from the CD4021.
Putting it together
Using a combination of the 74HC595 and CD4021, you should be able to easily address dozens of inputs and outputs from a single Arduino, while using only half a dozen pins. This leaves other pins free for more interesting tasks. Suddenly wiring up your entire goods yard is not only possible, but quite easy.
Comments