about:benjie

Random learnings and other thoughts from an unashamed geek

Arduino Pin Speed (Multiplexing)

| Comments

image

I’m working on a new project, I’ve got a 8x8 dual colour dot matrix display (£2.50 delivered from Earthshine Design) and I want to power it from the Arduino. One way of making a chip like that (which has 2x8x8 = 128 LEDs) would be to have a common ground and an additional 128 pins - one for each LED. This, I think you’d agree, would be a nightmare, so instead they’ve basically gone for an 8x16 grid for a total of 24 pins. This raises two main problems:

  1. You can’t turn 2 arbitrary LEDs on at the same time unless they are on the same row/column. (Doing so would actually draw a square of LEDs.)
  2. My Arduino doesn’t have enough digital input/output pins

Point 1 is easily solved - we simply update just one row at a time, letting Persistance Of Vision (POV) do the hard work for us. Point 2 is the subject of this post - multiplexing, combining multiple individual signals into just one signal. I will not be using this dot matrix display in this post, instead I will simply be powering normal LEDs. I wanted to find out if the Arduino is fast enough to multiplex the data through just a few pins in order to power this display. The answer (one of my favourite answers!) is: “Yes, but not without some hacking.”

The Hardware

In order to get a balance between the number of pins we use for this and the speed of the output I’ve decided to use 3 data pins, each controlling 8 bits, for a total of 24 bits (the same as the number of pins on the LED component - no coincidence!) The way that I have implemented it, we also need 2 pins for clocks (one fast, one slow) - read on. The clock pins will be common to all 3 data pins, so for the rest of this post I will only detail one data pin (8 bits). The Hardware (Sorry about the messiness. Click for a larger picture. There is a Roboduino (Arduino compatible) board on the left, the LEDs and resistors should be fairly obvious from the photo, then there are 3 chips. From left to right they are the Darlington array, the SIPO shift register and the flip flop array (see below for all of these)).

Fast clock - Shift Register

We want to send the data through one pin (data line 1, DL1) to an external Serial-In Parallel-Out (SIPO) chip, also known as a shift-register (I’m using a M74HC164B1 octal shift register). This does exactly what it says - it takes a serial (single) input and converts it to parallel (multiple) outputs. However the chip just receives some high signals and some low signals - how should it know how many digital 1s there are in a second’s worth of high signal? This is where the fast clock (FC) comes in. FC just goes from low to high and back again for every bit of data we send through the data lines, and when it changes from low to high the SIPO chip “reads” the value of DL1 and outputs it on it’s first output pin (Pin1). The next time the SIPO chip reads from DL1, it “shifts” the value from Pin1 to the second pin (Pin2), and again stores the value of DL1 (on or off) into Pin1. This continues indefinitely, shifting the data along a pin at a time, filling up all 8 of the SIPO chip’s output pins (and forever overwriting previous data). Here’s a diagram from the datasheet: Shift Register Time goes from right to left (the left shows an earlier time than the right). The data is the second signal (the third can just remain high always) and the 4th signal is the clock pulse. Signals 5-12 show the state of the output pins at each point in time.

Slow Clock - Flip Flop

We don’t want to display this shifting data though, so we use a “flip-flop “Flip-flop (electronics)”)“ (FF) to store the values. A flip flop can be thought of as a Serial-In Serial-Out (SISO) chip in that it takes it’s input and uses that as the output. What’s so special about it then, I hear you ask, isn’t it just a bit of wire? The “special” thing about flip flops is that they “store” this value - recording it when it receives a clock pulse, like the SIPO chip above. We’re going to use the slow clock (SC) for this purpose, because we only want to store the values once all 8 bits have been output from the SIPO chip. (The SC is triggered once every 8 FC pulses.) Doing so we wait for the SIPO chip to correctly represent the 8 bits it has been sent, and then we store these values whilst the next 8 bits are loaded. I’m using a SN74HC273N octal flip flop chip which has 8 individual flip flops in it.

Boosting - Darlington array

Unfortunately the flip flop chip does not supply much current - not enough to power an LED - so we need to use transistors to boost the current. I’ve used a Darlington array to boost the current in my circuit as I had one left over from a previous project (it is a ULN2803A).

Display

I’ve displayed the output of just one of the data pins using 8 standard LEDs, each with a resistor that’s too big (didn’t have one the correct rating).

The Software

To test this, I decided to do some binary counting.

Initial Software

First I implemented my own solution, and when I turned of the debugging delays and optimized as much as I could it resulted in 2 of the LEDs (the ones signifying the most significant bits) visibly flashing, a sad result meaning that there would be visible flickering on the final display if I used this method, especially as I was outputting only 8 bits and not 24 (doing 24 would make it roughly half as fast due to the extra digitalWrite operations).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// The pins I will be using:
int dataPin = 10;
int fastClockPin = 11;
int slowClickPin = 12;
// The data to display:
byte count=0;
void setup() {
  // All the pins are output pins
  pinMode(dataPin,OUTPUT);
  pinMode(fastClockPin, OUTPUT);
  pinMode(slowClockPin, OUTPUT);
  // Reset all pins low
  digitalWrite(dataPin, LOW);
  digitalWrite(fastClockPin, LOW);
  digitalWrite(slowClockPin, LOW);
}
// This will pulse the fast clock pin
void fastClock() {
  digitalWrite(fastClockPin, HIGH);
  digitalWrite(fastClockPin, LOW);
}
// This will pulse the slow clock pin
void slowClock() {
  digitalWrite(slowClockPin, HIGH);
  digitalWrite(slowClockPin, LOW);
}
void loop() {
  // We don't want to edit count, so copy it.
  byte v = count;
  int i;
  // There are 8 bits in a byte, do the following for each bit.
  for (i=0; i<8; i++) {
    // Set the data: if v modulo 2 is 1, send high, otherwise low. (v modulo 2 is the remainder when you divide v by 2)
    digitalWrite(dataPin, v%2 == 1 ? HIGH : LOW);
    // I've written the data, let the SIPO chip know
    fastClock();
    // Shift v 1 bit along, dropping the bit we just checked.
    v = v >> 1;
  }
  // All 8 bits have been output, tell the flip flops to store their current values:
  slowClock();
  // Increase the value of count by one
  ++count;
}

Abstraction Principle

Looking around at how to optimize this, I soon found that I could use a built in function shiftOut instead of my own method calling digitalWrite for every bit. I updated my code, changing 11 lines of code to just 1. This did speed it up (their binary arithmetic was faster than my method of the time) but not by enough - 1 LED still flashed - there would still be visible flicker. How annoying.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// The pins I will be using:
int dataPin = 10;
int fastClockPin = 11;
int slowClickPin = 12;
// The data to display:
byte count=0;
void setup() {
  // All the pins are output pins
  pinMode(dataPin,OUTPUT);
  pinMode(fastClockPin, OUTPUT);
  pinMode(slowClockPin, OUTPUT);
  // Reset all pins low
  digitalWrite(dataPin, LOW);
  digitalWrite(fastClockPin, LOW);
  digitalWrite(slowClockPin, LOW);
}
// REMOVED: fastClock(), because shiftOut() handles this for us.
// This will pulse the slow clock pin
void slowClock() {
  digitalWrite(slowClockPin, HIGH);
  digitalWrite(slowClockPin, LOW);
}
void loop() {
  // REMOVED: Most of the code here, shiftOut() does it all for us:
  shiftOut(dataPin, fastClockPin, LSBFIRST, count);
  // All 8 bits have been output, tell the flip flops to store their current values:
  slowClock();
  // Increase the value of count by one
  ++count;
}

Digging Deeper

I was a bit disappointed by this, as I knew from the last SoutHACKton meetup that on PICAXE boards you could set multiple pins at the same time. Setting just one pin at a time in Arduino seemed unnecessarily slow. I dug a bit deeper in the extended documentation, and came across the Port Manipulation page:

You may need to be able to turn pins on and off very quickly, meaning within fractions of a microsecond. If you look at the source code in lib/targets/arduino/wiring.c, you will see that digitalRead() and digitalWrite() are each about a dozen or so lines of code, which get compiled into quite a few machine instructions. Each machine instruction requires one clock cycle at 16MHz, which can add up in time-sensitive applications. Direct port access can do the same job in a lot fewer clock cycles.

Huzzah! I can write multiple pins at once, and not only that, I can skip a lot of the clever code that the Arduino libraries come with as I’m happy making assumptions about my program’s operation. Excellent! I had a look at the built in functions, and revised my binary arithmetic so that I could be as optimized as possible, and ran the code…

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// REMOVED: the pins (these are now implicit in the binary masks below rather than explicit as before)
// The data to display:
byte count=0;
// Alternatively: unsigned int count = 0;
void setup() {
  // The pins I am using are output pins: (12,11,10)
  DDRB = B00011100;
  // Reset all pins low:
  PORTB = B00000000;
}
int i;
void loop() {
  // REMOVED: most previous code
  // Loop through the bits of count. (if count is an unsigned int, then use this instead:) for (i=0; i < 16; i++) {
  for (i=0; i < 8; i++) {
    // Turn all clocks off, and set the data pin
    PORTB = B00000000 | (!!(count & (1 << i)) << 2);
    // Leave the data pin as it is, turn on the fast clock pin, and if we're on the last bit also turn on the slow clock pin
    PORTB |= B00001000 | (!!(i == 7) << 4);
    // If we're working with an unsigned int, then change the 7 above to a 15.
  }
  // Increase the value of count by one
  ++count;
}

… All the LEDs appeared on all the time… Either it’s working perfectly or something’s very wrong. Fortunately by adding a delay into the loop, I could see it was the former, and I celebrated! But just how much faster is it? I couldn’t use the Arduinos internal timing, as timing it would cause it to use more processing power and thus slow down… Instead I decided to display the 8 most significant bits of a 16bit number, and see how many of those flickered. All except one flickered (not unexpected) but this meant it was significantly faster than before. I timed the slowest flashing LED - one full cycle (on to off and back on again) of this 1st bit took an average of 2.2 seconds. This meant that one full cycle of the 9th bit (the one we’re bothered about - the slowest of the initial 8 bits) was taking 2.2/256 = 0.0086s, or, in other words, it was running at 116Hz - good enough to not be noticeable by human perception (standard TV is 25-30 Hz). (This also means that the fastest bit is being pulsed at around 15,000Hz.) This is just 8bits, mind, not 24. Adding in the extra bits would not be too taxing on the system though - purely mathematics - as we can set all the pins in the same step. I would expect it to run all 24 bits at around 40-60Hz.

Comments