TrackPacer Part 2 - Connecting Multiple Microcontrollers Using ICSC

This is the second post in a 3 part series detailing the construction of our latest hardware project - TrackPacer. In this post, I'll cover how we implemented ICSC (Inter-Chip Serial Communication) to communicate quickly and reliably between multiple microcontrollers.

Background

led table

The project involved controlling 12,000 LEDs across 400 meters. Given the physical and computational limitations, we chose to spread the task of controlling the LEDs across multiple microcontrollers, and use serial communication between them to pass around information. The final iteration of the prototype involved 10 slave nodes, each controlling 1,200 LEDs, and one master node running the show.

There are multiple different ways to communicate between microcontrollers. You could go the cool wireless route: Bluetooth, ZigBee, WiFi. Or throw some cables in the mix and use any number of established protocols: I2C, SPI, UART, all with their advantages and disadvantages.

The Hardware

We needed communication to be instantaneous and robust across large distances, so we went with a wired UART. In practice for the TrackPacer build, this was 11 Teensy 3.1 microcontrollers communicating through a MAX485 chip by way of a ChainDuino.

These are very handy boards that dress up an Atmega328 with a MAX485 chip and 2 RJ45 ports. Throw a Cat5 cable between two of these and they'll share power with one another, as well as have an open line for Serial communication. 11 of these boards and a quarter mile of sturdy ethernet cable gave us the infrastructure for quick and robust communication.

To understand how these all talk to each other, imagine you and 10 friends standing alongside a tunnel poking your heads inside. Anything you yell into the tunnel is heard by everyone else, and vice versa. There are some finer details including the fact that you can't yell and listen at the same time, and only one person can yell at a time, but that's the gist of it.

The Software

We turned to MajenkoLibraries's open sourced ICSC for giving us a nice framework for communicating between the controllers. This library makes it dead simple to accomplish the following:

  • Establishing yourself in the tunnel with an ID
  • Broadcasting a message to everyone
  • Targeting a message to a specific ID
  • Knowing what to do with an incoming message

With these 4 tasks, we're able to run an organized system. Taking a look at each of these tasks with bits of code, here's what they look like.

Establishing yourself in the tunnel with an ID:

 <code>#include <ICSC.h>

#define NODE 1
ICSC icsc(Serial, NODE);

void setup()
{
 Serial.begin(115200);
 icsc.begin();
}
</code>

Here, we're including the library, initializing an ICSC instance with our defined NODE identifier. Then kicking off all the necessary pieces in the setup function.

Broadcasting a message to everyone:

 <code>icsc.broadcast('P', "ping");
</code>

Calling broadcast(command, message) blasts the message out for everyone to receive.

Targeting a message to a specific ID:

 <code>icsc.send(2, 'P', "ping");
</code>

Instead of broadcasting, you can send(target, command, message) to a specific Node. Any node that registered with that ID will catch the message, all others will simply ignore it.

Knowing what to do with an incoming message:

 <code>#include <ICSC.h>

#define NODE 2
ICSC icsc(Serial, NODE);

void setup()
{
 Serial.begin(115200);
 icsc.begin();
 icsc.registerCommand('P', &ReceivePing);
}

void loop()
{
 icsc.process();
}

void ReceivePing(unsigned char src, char command, unsigned char len, char *data)
{
 if (strcmp(data, "ping") == 0)
 icsc.send(1, 'P', "pong");
}
</code>

Here's the fun part. In order to do anything with incoming messages, you need to register a callback of sorts to the message's command with registerCommand, and define the function with that specific function signature you see here. Then call icsc.process() as fast as you can in your loop in order to receive any inbound messages.

Summary

With the ICSC library and the ChainDuino, we've had no trouble achieving lightning fast, consistent communication between multiple microcontrollers, each of which is physically 40 meters away from each other.

If you've found this interesting, check out the other posts in this series Part 1 - A Nerdy Overview, and Part 3 - Controlling Thousands of LEDs.

Eli uses his mathematics degree from Carnegie Mellon to blur the lines between the digital and physical worlds. He codes for Shure, Volunteers of America, and other clients from our Boulder, CO, office.

More posts by Eli