Shift Out speed issue

Hello,

I have written a bit of code to use a string of RGB LEDs that utilize a cascading string of shift registers (DM412 by Silicon Touch Technology). Each shift register takes 6 bytes to control the RGB value of the LED connected to it. It will pass the bits down the line every clock pulse until I send it a strobe code (8 pulses on the data pin). My eventual goal is to have a chase of 300 lights, 2" apart, that will move at the same rate as the wind. The chase will ramp each LED up then down in brightness rather than just blink on and off the LED.

I had to write my own ShiftOut because I needed the clock line to idle high. But other than that, I have been pretty successful in controlling the LED string. The problem I am having is that the Arduino cannot seem to shift out fast enough to approximate more than a gentle breeze. In fact sending a ramping signal to just 20 lights takes 383ms. This translates, I believe, into 150kbits/s. Does this seem slow? This is 100 clock cycles for each bit shifted out.

Thanks for your help. I’ll have to post my code separately…

Here is the code.

/*  This is a speed test of sending a fade waveform (envelope) through a string of x16 LED Dotz
*
*Uses LED X-16 Dotz by Lighting Science which is based on cascading DM412 LED driver chips by
*Silicon Touch Technology.
*Sends 48 bits of RGB data to each Dotz in string, clock held high after last bit
*and internal strobe initiated by pulsing data 8 times
*
*This does not use the arduino native shiftOut command but a written shiftout
*instead that keeps the clock pin high when idle.
*/

#define stepResolution 16        //the number of steps between off-on-off
#define segment_length 20    //the length of the light strings

int offset = 5;      //lower the offset the more Dots simultaneously illuminated, will be related to windspeed

int clockPin = 12;    //PORT B pins 8 - 13
int dataPin1 = 11;    //to shift data out to a light string 
int dataPin2 = 10;
int dataPin3 = 9;
int dataPin4 = 8;

int unsigned long time = 0;

struct Dotz {    //a structure with...
  int value;      //value to output
  int segment;    //segment to output to
};
struct Dotz light[segment_length];  //the array of structs containing values to output and where to send them

void setup()
{
  Serial.begin(19200);
  Serial.println("setup");
  pinMode(clockPin, OUTPUT);
  pinMode(dataPin1, OUTPUT);
  pinMode(dataPin2, OUTPUT);   //not used currently but will be used as part of multiple segment strategy
  
  int segment_number = 0;
  // initialize the light array
    for (int i=0; i < segment_length; i++) {
        light[i].value = 0;        //initialize all values to 0
      light[i].segment = 1;
          }
}

void loop()  {
  int fade_increment = (255 / stepResolution);
  int head_position = 0;    //the leading edge of the waveform
  int old_head = 0;
  
  time = millis();
  //^^^^^^^^^^^^^^^^^^^^^^^^ outside loop ^^^
  for (int i = 0; i < ((segment_length + (stepResolution / 2)) * offset) ; i++) {
        
    //********** inner loop ************** output to to 2 segments ***
    for (int j =0; j < segment_length; j++) {
      if ((j == head_position) and (light[j].value == 0)) {    //if light[j] is the head and not already active, turn it on
        light[j].value = 255;            //light has to be 0 because head_position dwells on one number for a time
      }     
      RGBout(light[j].segment, (light[j].value<<8), (light[j].value<<8), (light[j].value<<8));    //shift it out to the lights
      if (light[j].value > 0) {
        light[j].value = max(light[j].value - fade_increment, 0);  //value decreased by fade increment but not less than 0
      }
    }  //******************************* end - inner loop ***
    sendStrobe();
    old_head = head_position;
    if ((i % offset) == 0) {      //if i is evenly divisible by offset
      head_position++;    //increment head_position to by one (ie. next light in string)
    }
  }  //*********************** end of outside loop *
  time = (millis() - time); // / 1000); 
  Serial.print("time = ");
  Serial.println(time);
}
  
void RGBout (int segment, int r, int g, int b)
      //shifts rgb values out most significant bit first by sending bytes to shiftOut command      
      // " >> " is bitshift operator - moves top 8 bits (high byte) into low byte
{
  int pinMask = 0;
  //Serial.print(segment);
  switch (segment) {
    case 1: pinMask = B00001000;        //data pin 11
            //clockMask = B11101111;  //clock pin 12
            break;
    case 2: pinMask = B00000100;        //data pin 10
            //clockMask = B11011111;  //clock pin 13
            break;
    case 3: pinMask = B00000010;        //data pin 9
            //clockMask = B11011111;  //clock pin
            break;
    case 4: pinMask = B00000001;        //data pin 8
            //clockMask = B11011111;  //clock pin
    default:pinMask = B00000001;        //data pin 8
            //clockMask = B11011111;  //clock pin
  }
  
  shiftOutClkHi(pinMask, clockPin, (r >> 8));    // shift out highbyte - RED 
  shiftOutClkHi(pinMask, clockPin, r);    // shift out lowbyte - RED
   
  shiftOutClkHi(pinMask, clockPin, (g >> 8));  // shift out highbyte - GREEN
  shiftOutClkHi(pinMask, clockPin, g);    // shift out lowbyte - GREEN
   
  shiftOutClkHi(pinMask, clockPin, (b >> 8));  // shift out highbyte - BLUE
  shiftOutClkHi(pinMask, clockPin, b);    // shift out lowbyte - BLUE 
} 

void sendStrobe()
    //sequence to trigger internal strobe of the Dotz (DM412) - 8 pulses on data
{
  for (int j=0; j < 8; j++)  {
   PORTB |= B00001000;    //MUCH faster than digital writes
   PORTB &= B11110111;   //pin 11 toggles hi- low 8 times leaves low - this will have to be changed to toggle the appropriate data pin
  }
}

void shiftOutClkHi(int pinMask, int myClockPin, byte myDataOut) {
 // This shifts 8 bits out MSB first, on the rising edge of the clock,
  //then clock idles high. Bitmasks myDataout by anding the value with a 1
  //by i places then puts that result on myDataPin using Port Manipulation
  
  int clockMask = B11101111;  //bit mask for port write to clock pin
  
        for (int i = 0; i < 8; i++)  {  //compares each bit by masking it w/ all 0s and one 1
                if (myDataOut & (1 << (7 - i))){  //shifts 1 left by 7-i places
                  PORTB |= pinMask;}
                else {PORTB &= ~pinMask;}      
                PORTB &= clockMask;  //fast way to toggle clock low then hi
                PORTB |= ~clockMask;   
      }
}

I had the same experience trying to control a full RGB 8x8 led matrix with 4 8bit shift registers using PWM for color mixing.

I ended up using hardware SPI which is considerably faster.

I have some code for hardware SPI, it’s at home though. I’m using it for a similar project using ShiftBrites, which have the A6281 (though I’m considering switching to the DM413 at some point).

The information is all in the ATmega168 datasheet, they even have some example code. Let’s see, basically it should be like this:

pinmode(13,OUTPUT); // the SPI clock
pinmode(11,OUTPUT); // the SPI data

SPCR = (1<<SPE)|(1<<MSTR); // set up SPI

SPDR = byteyouwannasend; // put byte in SPI register
while(!(SPSR & (1<<SPIF))); // wait until data is all sent

So take a look at page 169 and 170 in the ATmega168 datasheet. The above code idles the clock low, but you can set the CPOL bit to make it idle high. You can set the DORD bit to choose MSB or LSB. By adjusting SPR1 and SPR0 in SPCR and SPI2X in SPSR, you can get clock rates of f/(2,4,8,16,32,64,128). f/2 on the Arduino is 8 MHz, which you probably want to use since the DM412 can handle up to 20MHz. ShiftBrites max at 5MHz so I’m using f/4.

Thanks for the SPI lead. It looks like just the ticket. I wrote something tonight. I seem to hang up in the while SPIF loop because the write collision (WCOL) flag is getting set. I'll have to debug it tomorrow...

It works for a bit then hangs up in the SPIF loop apparently with the write collision flag set and the SPIF unset. I don’t know why. Thought maybe sending ints to spi_transfer was the problem. I forced the char type on everything (all the examples use char in the spi_transfer function - not sure why) but that did not help the problem. Maybe I did that wrong though.

/*  This is a speed test of sending a fade waveform (envelope) through a string of x16 LED Dotz
*
*
*Uses LED X-16 Dotz by Lighting Science which is based on cascading DM412 LED driver chips by
*Silicon Touch Technology.
*Sends 48 bits of RGB PWM data to each Dotz in string, clock held high after last bit
*and internal strobe initiated by pulsing data 8 times
*
*Uses hardware spi to shift out pwm commands to DM412s
*
*/
#define DATAOUT 11//MOSI - Hardware SPI
#define SPICLOCK  13//sck

#define stepResolution 8        //the number of steps between off-on-off
#define segment_length 10    //the length of the light strings

int offset = 5;      //lower the offset the more Dots simultaneously illuminated, will be related to windspeed
byte clr;

int unsigned long time = 0;

int light[segment_length];  //the array of structs containing values to output and where to send them

void setup()
{
  Serial.begin(19200);
  Serial.println("setup");
  pinMode(DATAOUT, OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  
  // SPCR = 01011000
  //interrupt disabled,spi enabled,msb 1st,master,clk high when idle,
  //sample on leading edge of clk,system clock/4 rate (fastest)
  SPCR = (1<<SPE)|(1<<MSTR)|(1<<CPOL);
  clr=SPSR;
  clr=SPDR;
  //SPSR = 00000001
  //double clock speed, thus clock/2
  SPSR = (1<<SPI2X);
  
  int segment_number = 0;
  // initialize the light array
    for (int i=0; i < segment_length; i++) {
        light[i] = 0;        //initialize all values to 0
    }
}


void loop()  {
  int fade_increment = (255 / stepResolution);
  int head_position = 0;    //the leading edge of the waveform
  int old_head = 0;
  
  time = millis();
  //^^^^^^^^^^^^^^^^^^^^^^^^ outside loop ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  for (int i = 0; i < ((segment_length + (stepResolution / 2)) * offset) ; i++) {
        
    //********** inner loop ************** output to to 2 segments ********************************************
    for (int j =0; j < segment_length; j++) {
      if ((j == head_position) and (light[j] == 0)) {    //if light[j] is the head and not already active, turn it on
        light[j] = 255;            //light has to be 0 because head_position dwells on one number for a time
      }
            
      spi_transfer(light[j]);    //shift it out to the lights - R
      spi_transfer(light[j]);    //shift it out to the lights - G
      spi_transfer(light[j]);    //shift it out to the lights - B
      
      if (light[j] > 0) {
        light[j] = max(light[j] - fade_increment, 0);  //value decreased by fade increment but not less than 0
      }
    }  //******************************* end - inner loop ******************************************************
    
    sendStrobe();    //tell the LEDs to latch in values and display
    Serial.println();

    old_head = head_position;
    if ((i % offset) == 0) {      //if i is evenly divisible by offset
      head_position++;    //increment head_position to by one (ie. next light in string)
    }
  }  //*********************** end of outside loop *****************************************************************************
  time = (millis() - time); // / 1000); 
  Serial.print("time = ");
  Serial.println(time);
}

char spi_transfer(volatile byte data)
{
  Serial.print(data, DEC);
  Serial.print(":");
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait for the end of the transmission
  {Serial.print (SPSR, BIN);
  Serial.print(", ");
  };
  return SPDR;                    // return the received byte
}

void sendStrobe()
    //sequence to trigger internal strobe of the Dotz (DM412) - 8 pulses on data
{
  for (int j=0; j < 8; j++)  {
   PORTB |= B00001000;    //MUCH faster than digital writes
   PORTB &= B11110111;   //pin 11 toggles hi- low 8 times leaves low - this will have to be changed to toggle the appropriate data pin
  }
}

Was that serial output junk in your SPI wait loop always there? Seems like it would slow things down a lot. I don't know what else...maybe during your sendStrobe routine, turn off the SPI peripheral since you're manually toggling one of its pins.

Inventor:

I know that you have been focusing on SPI currently, but since you have been watching my thread on improving shiftOut() I figured that you might still be interested in a shiftOut() like solution if the SPI does not work out. I just posted a shiftOutRaw() to my thread that may be up to about 3 times faster than your shiftOutClkHi() function, adapt as appropriate for your clock hi needs. Using the same loop unrolling, you can probably also increase the speed of your sendStrobe() function like this:

void sendStrobe() {
   PORTB |= B00001000;    //MUCH faster than digital writes
   PORTB &= B11110111;   //pin 11 toggles hi- low 8 times leaves low - this will have to be 
   ... 8 times total ...
   PORTB |= B00001000;
   PORTB &= B11110111;
}

While it seems minor, at the speeds you are using, minor improvements such a loop unrolling turn out to be a significant percentage in speed boost.