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:

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:

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.

Additional resources

Newer Older

Comments

Tuesday, Oct 1 2013, 7:36 PM Dave (from UK) says...
Just found your C/MRI Arduino library on GitHub and I love it! I have been using JMRI on my garden railway for some years and I have been looking for some way to drive relays. As I have several Arduinos, your library is a perfect solution. Just tried it and it works a treat.<br /> <br /> Thanks.