How to shift out 16 bits fast on arduino board

I'm coding a system that need to shift out 16 bits fast from a uint16_t variable to two paired 74HC595 shift registers. I'm running the code on an Arduino (atmega328@16Mhz) and the shiftOut in the Arduino is way too slow. The librarys digitalWrite function is slow as well.

The best idea I came up with is a for loop that loops through all the 16 bits of the variable and writes 1/0 to data pin, set the clock bit high as short as possible and then goes to the next bit of the variable.

Is this the fastest solution and if so, how do I loop through a uint16_t variables bits in c++?

-

If you're looking to use a software data transfer, you don't want to use the functions digitalWrite. They are very slow, this is because they need to translate the pin number through a table to an actual register (PORTx), mask the right bit and change it. All pins in arduino are mapped to numbers, while underneath they can belong to port A, B, C, and even more on the MEGA version of the Arduino.

It's much faster to directly modify the AVR registers. Such as PORTB and a-like. You indeed need to step through each bit. I would create a for loop from 0 to 15 and do some bit shifting and masking.

Because I don't you know pinning configuration I can't give an exact example. However it will probably look very close to this. With 'very close' I mean that this is untested.

void ShiftOut(UI16_t data)
{
// Initialize (you may want to set CLK to low) - as we're toggling later on.

// step from bit 0 to 15
for(UI08_t i = 0; i < 15; i++)
{
// Check the content of this data bit
// Shift data so this bit is LSB, and mask it with 1 so we only look at this bit.
if ((data >> i) & 0x1 == 1)
{
// set data pin high, like PORTB |= 1<<4;
// when pin 4 of portB is your data pin
// Doing an OR will make pin B4 always high
}
else
{
// set data pin low, like PORTB &= ~(1<<4);
// Doing an AND with the inverse means all pins except B4 will be unchanged
}

// Generate clk to 'transfer' the bit:
// This can likely be done by using PORTB ^= 1 << 5; (pin B5 in this example)
// ^= toggle
// Do this TWICE, so CLK goes high/low
}

// as you're using a shift register, you may want to toggle LATCH pin as well..
}

To find out what hardware pin number (don't assume pin 4 is pin B4 or A4!) you need to look at the schematic of Arduino.

I've ran a similar code on a PIC32 (runs at 80MHz). The PIC32 was able to do this at about 1,5 MHz, but a few extra lines of code was running in main() to compute a new output. Nevertheless, it can be done very quickly.

-
Thanks for the detailed answer, I will try the SPI approach first as that one seem faster. I also found more examples for the software approach at bildr.org/2011/02/74hc595 –  Tirithen Oct 19 '11 at 20:47

You need to use the SPI peripheral instead of doing the shifting in software. I've never used an Arduino, but the online reference suggests there is an SPI library you can use. If that doesn't suit your needs, you can look up the SPI peripheral in the ATMega datasheet. SPI peripherals are usually fairly easy to setup and use.

-

This is simple enough with the SPI library.

Connect your shift register input to the MOSI pin (Master Out, Slave In) - Digital pin 11 - and you will have nice fast transfers.

There will be a very slight delay between the upper and lower 8 bits of each word transmitted.

For example:

#include <SPI.h>

void begin()
{
SPI.begin();
}

void loop()
{
unsigned int val;

val = rand()%0xFFFF; // Generate a random 16 bit number

SPI.transfer(val >> 8);  // Output the MSB first
SPI.transder(val & 0xFF); // Followed by the LSB

delay(1000);  // Wait a sec...
}

-
What about the latch pins? This appears to just be toggling the serial data line, but I do't see how anything is getting latched into the register. –  CMP Oct 20 '11 at 21:27
I have never used one of the shift registers in question, so I don't know what is required. Usually you wrap the SPI in a digitalWrite(x,LOW) and digitalWrite(x,HIGH) to toggle a digital IO line of your choice as a "chip select" or "latch toggle" or whatever (obviously reverse the toggle for active high). Use whatever pin you like. –  Majenko Oct 20 '11 at 23:30