R/G LED Matrix backpack daisy chain

I’m having challenges daisy chaining 2 Red/Green LED Matrices (http://www.sparkfun.com/products/759) together.

I have the following basic code working, and what I’d like to do is extend this to fill two matrices.

It’s my understanding that I need to configure them and specify the number of boards by doing something like the following in setup and extending the loop to count to 128 instead of 64, but of course that’s not working.

If anyone can help me get two matrices working together it would be greatly appreciated. I already have text scrolling across one matrix and am looking forward to doing it across more.

http://vimeo.com/37227434

// Configure boards      
digitalWrite(CHIPSELECT,LOW); // enable the ChipSelect on the backpack
delayMicroseconds(500);
spi_transfer('%'); // % byte
spi_transfer('2'); // num boards
digitalWrite(CHIPSELECT,HIGH); // disable the ChipSelect on the backpack
delayMicroseconds(10);
#define CHIPSELECT 10
#define SPICLOCK  13
#define DATAOUT 11
#define DATAIN 12

char spi_transfer(volatile char data) {
  SPDR = data;
  while (!(SPSR & (1<<SPIF))) {
  };
}

void setup(){

  pinMode(DATAOUT,OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(CHIPSELECT,OUTPUT);
  digitalWrite(CHIPSELECT,HIGH); //disable device
  
  SPCR = B01010001;             //SPI Registers
  SPSR = SPSR & B11111110;      //make sure the speed is 125KHz
  
  /*
  SPCR bits:
   7: SPIEE - enables SPI interrupt when high
   6: SPE - enable SPI bus when high
   5: DORD - LSB first when high, MSB first when low
   4: MSTR - arduino is in master mode when high, slave when low
   3: CPOL - data clock idle when high if 1, idle when low if 0
   2: CPHA - data on falling edge of clock when high, rising edge when low
   1: SPR1 - set speed of SPI bus
   0: SPR0 - set speed of SPI bus (00 is fastest @ 4MHz, 11 is slowest @ 250KHz)
   */

  delay(10);
}

void loop(){

  // Compose frame data
  
  // Draw current frame  
  delay(80);
  digitalWrite(CHIPSELECT,LOW); // enable the ChipSelect on the backpack
  delayMicroseconds(500);
  for (int i=0;i<64;i++) {
    // draw a red, green or orange LED 
    spi_transfer( (i % 3) + 1);
  }
  digitalWrite(CHIPSELECT,HIGH); // disable the ChipSelect on the backpack
  delayMicroseconds(500);
}

Thank you!

Hi Mark,

Did you ever get this working?

I have another matrix on order so I can try to figure out what is failing when it arrives next week.

Where did you see the commanding for number of boards?

I would have thought simply asserting chip select then shifting 128 bytes then deserting chip select would have worked.

Jim

I have not yet got this working. I emailed sparkfun and they sent me the following links to check up on:

http://www.youtube.com/watch?v=tz_HT-FqEOE (check in the description for the github code link)

http://www.sparkfun.com/tutorials/91 (Check the comments-there are example sketches there)

viewtopic.php?p=51465

I had already come across those several times before. I’m hoping to re-review them in the next week and try again.

Hi Mark,

As I suspected, you can just send the 128 bytes to the two daisy chained displays.

The default code on the backpack has a deficiency, however, in that the SPI data buffer and the display buffer are one in the same.

This causes the displays to flash the value of the other display as it is shifted through.

You have to modify the backpack code to separate the two buffers. Dedicate a buffer to the SPI data and copy that data to the display buffer when CSn goes inactive, either through polling or through an interrupt.

Here is some simple flashing code (no character array) that shows the chaining.

It lights one LED and moves through all 128 pixels.

// include SPI.h
#include <SPI.h>

// Connect to "Output SPI" JP4
// DIO 10 = CSn  (JP4-3)
// DIO 11 = MOSI (JP4-2)
// DIO 13 = SCK  (JP4-4)

const int CSn = 10;
const int ColorRED = 1;
const int ColorGREEN = 2;

const int pixels = 128;

void setup()
{
pinMode (CSn, OUTPUT);
SPI.begin();
SPI.setClockDivider(SPI_CLOCK_DIV128);
SPI.setDataMode(SPI_MODE0);
}

void loop()
{
  SetLED(ColorRED);
  delay(150);
  SetLED(ColorGREEN);
  delay(150);
}

void SetLED(int color)
{
  static int pixel = 0;
  
  digitalWrite(CSn,LOW);
  delay(1);
  for (int bytenum = 0; bytenum < pixels; bytenum++)
    if (bytenum == pixel)
      SPI.transfer(color);
    else
      SPI.transfer(0);
  pixel = (pixel + 1) % pixels;
  delay(1);
  digitalWrite(CSn,HIGH);
}

Let me know if you want some help on the backpack code. I’ll have to dig out my AVR programmer and environment.

Jim

Thank you Jim! I look forward to giving this a try in the next few days.

Just so I’m clear, are you saying that I cannot make this work without altering the software on the Backpack itself? In other words, getting 2 displays to work can’t work by uploading code to the Arduino only?

Thanks for all your help.

mark4:
Just so I’m clear, are you saying that I cannot make this work without altering the software on the Backpack itself? In other words, getting 2 displays to work can’t work by uploading code to the Arduino only?

Thanks for all your help.

Kind of.

It will work unmodified, but you are not going to like the display flashing with the shift data.

Give it a try and see for yourself, including your character displays instead of my simple flashing pixel.

Let me know what you think.

Jim

Hi Jim, I had a chance to try your code this afternoon and I’m experiencing exactly what you said I would. I’m glad I’ve got something on two screens now, but now I’ve got this problem of the ghosting.

Here’s a video demo of my scrolling text: http://vimeo.com/38293217

I did that with the SPI library and I’ve got some new test code up and running that doesn’t use the SPI library and it exhibits the same behavior. From what I’ve read, this is because the Backpack is designed to push the 1st screen’s 64 values to the 2nd screen before updating both screens with all 128 values.

I don’t want to wear out my welcome, but any advice is welcome. I’ve been doing a lot of searching, but I haven’t found any tips for overcoming this ghosting yet.

/*
 Test pattern for two Sparkfun R/G LED Matrices daisy-chained together
*/

#define CHIPSELECT 10  // Slave Select
#define SPICLOCK  13   // Slave Clock
#define DATAOUT 11     // MOSI / DI
#define DATAIN 12      // MISO / DO

int frame = 1;

// Used to clear junk from SPI Registers
byte clr;

char spi_transfer(volatile char data){
  SPDR = data;                    // Start the transmission
  while (!(SPSR & (1<<SPIF)))     // Wait the end of the transmission
  { };
}

void setup() {

  pinMode(DATAOUT,OUTPUT);
  pinMode(SPICLOCK,OUTPUT);
  pinMode(CHIPSELECT,OUTPUT);
  digitalWrite(CHIPSELECT,HIGH); //disable device

  // Define the SPI Control Register
  SPCR=(1<<SPE)|(1<<MSTR)|(1<<SPR1)|(1<<SPR0);
  //SPCR = B01010001;

  // Define the SPI Status Register (SPSR)
  SPSR = SPSR & B11111110;
  
  // Clear junk from registers. Not sure if this is necessary.
  clr = SPSR;
  clr = SPDR;  
  
  // Configure for 2 boards
  digitalWrite(CHIPSELECT, LOW);
  delayMicroseconds(500);
  spi_transfer('%');
  spi_transfer(2);
  digitalWrite(CHIPSELECT, HIGH);
  delayMicroseconds(10);
}

void loop() {
  
  digitalWrite(CHIPSELECT,LOW); // enable the ChipSelect on the backpack
  delayMicroseconds(500);
  for (int i=0;i<128;i++){
    if( i == frame )
      spi_transfer(1);
    else
      spi_transfer(0);
  }
  digitalWrite(CHIPSELECT,HIGH); // disable the ChipSelect on the backpack
  delayMicroseconds(500);

  frame++;
  if(frame > 127)
    frame = 0;
  delay(250);
}

mark4:
I don’t want to wear out my welcome, but any advice is welcome. I’ve been doing a lot of searching, but I haven’t found any tips for overcoming this ghosting yet.

Hi Mark,

Do you have the ability to program the backpack AVR?

You’ll need a 2x3 pin AVR programmer (http://www.sparkfun.com/categories/7)

You will also have to add the 6-pin header to the backpacks.

If you can do that, I’ll fix up the backpack code.

You just need to separate the buffer into its two components - a data buffer and a display buffer as I mentioned earlier.

Jim

I think I’ll try to follow Sparkfun’s “Using an Arduino to program an AVR” tutorial at http://www.sparkfun.com/tutorials/200.

I don’t have the LED matrix backpack in front of me. It may be tight to solder on the ISP headers, but I think I can manage it.

Thank you so much, I’m very grateful for your help!

I have some code ready to try in the next day or two. I’ll post again when I have something ready to go.

Jim

mark4:
I think I’ll try to follow Sparkfun’s “Using an Arduino to program an AVR” tutorial at http://www.sparkfun.com/tutorials/200.

I don’t have the LED matrix backpack in front of me. It may be tight to solder on the ISP headers, but I think I can manage it.

Thank you so much, I’m very grateful for your help!

I followed that tutorial and it worked fine, even with an Uno.

To get the headers on I separated the LED from the backpack and soldered it from the back.

Question - there are lots of errors in the backpack documentation.

  1. The input and output SPI headers are backwards as you know.

  2. The red and green LED codes are backwards. Red = 2, Green = 1.

  3. The pixel positions do not follow the documentation, either.

  4. The code they supply is for an earlier revision with some board errors and will not work unmodified on current boards.

Do you want me to fix number 2 or number 3? Not much I can do with number 1.

Number 2 - trivial or I can leave alone for compatibility.

Number 3 - I would need to know where you want pixel 1 and the order.

I have also optimized the code so that the SPI can run faster. I have the code instrumented with some pin level changes on writing to the transmit SPI buffer. I will see how fast it’ll go by looking at the pin outputs on a scope.

Initial tests look good with SPI_CLOCK_DIV8 (2 MHz), no delays on chip select going active, and only 5 us on chip select going inactive.

Jim

This appears to work fine.

The ghosting is gone with the separate data and display buffers.

Lots of cleanup and added comments.

I left the colors alone, you can easily change it in the code.

Let me know if this works for you.

Jim

#include <inttypes.h>
#include <avr/io.h>
#include <avr/interrupt.h>

#define setbit(addr, bit) ((addr) |= (uint8_t) (1 << bit))
#define clrbit(addr, bit) ((addr) &= (uint8_t)~(1 << bit))
#define chkbit(addr, bit) ((addr) &  (uint8_t) (1 << bit))

#define CLK    0
#define CLR    3
#define LATCH  2
#define DATA   1
#define EN     4

#define GREEN   1
#define RED     2
#define YELLOW  3

#define COL_STD 7   // Left to Right
#define COL_REV 0   // Right to Left

// Which way should columns display?
#define COL_DIR COL_STD

//#define DEBUG

volatile uint8_t spiPtr = 0;
volatile uint8_t nextData = 0;

volatile uint8_t image[64];
volatile uint8_t data[64];

// Function prototypes
void ioinit (void);
void inline shiftLine(uint16_t line, uint8_t rowNum);

//===================================================================
// Interrupt service routine that is triggered whenever SPI CSn
// changes state. We only act on the rising edge, at which time
// we copy the SPI data buffer to the display frame buffer.
//
// The old code performed a translation from the frameBufferIndex
// into the frame buffer. This reverses the column data left to
// right. It turns out that code can be simplified  by just 
// inverting the three low bits of frameBufferIndex to do the swap.
//
// Old code:
// image[(((frameBufferIndex))&(~7))+((63-(frameBufferIndex)&63)&7)] = spiTemp;
//
// New code (using old code naming):
// image[frameBufferIndex ^ 7] = spiTemp;
// Note - translation subsequently moved to the matrix update loop
//===================================================================
ISR(PCINT0_vect)
{
    uint8_t i;
    
    if (chkbit(PINB, PINB2)){ // Act if CSn goes inactive (high)
        #if DEBUG        
        setbit(PORTB, PORTB1);    // debug marker
        #endif
        spiPtr = 0;
        for (i = 0; i < 64; i++){
            image[i] = data[i];
        }
    }
    #if DEBUG        
    clrbit(PORTB, PORTB1);    // debug marker on ISR exit
    #endif
}

//===================================================================
// Interrupt service routine for SPI character.
// The transmit SPDR is single buffered - we have to get the data
// loaded before the next SPI clock edge. This is the limiting
// factor for increasing SPI clock rate. We preload the data that
// will be sent on the next interrupt and save it in nextData.
// Then we just load SPDR with nextData and then work on saving
// the received data (double buffered), incrementing the index, and
// preloading nextData again.
//
// The old code did a column flip translation in this ISR. Do not
// do that in the ISR anymore. This should be as lean as possible.
//===================================================================
ISR(SPI_STC_vect) 
{
    if(!(chkbit(PINB, PINB2))){   // Act only if CSn is active (low)
        SPDR = nextData;
        #if DEBUG        
        setbit(PORTB, PORTB0);    // debug marker to show SPDR loaded
        #endif
        data[spiPtr] = SPDR;
        spiPtr = (spiPtr + 1) & 63;
        nextData = data[(spiPtr + 1) & 63];
        #if DEBUG        
        clrbit(PORTB, PORTB0);    // debug marker on ISR exit
        #endif
    }
}

//===================================================================
// Main
//===================================================================
int main (void)
{
    uint8_t  i;
    uint16_t line; 
    uint8_t  row;
    uint8_t  col;

    ioinit();
    
    // Initialize data and display frame buffers.
    for (i = 0; i < 64; i++){
        image[i] = 0;
        data[i]  = 0;
    }

    // Take the 595 shift registers out of reset
    setbit(PORTC, CLR);

    // Enable SPI interrupts and SPI peripheral
    SPCR = (1 << SPE) | (1 << SPIE);

    // Enable pin change interrupt for the SPI CSn pin
    PCICR = 1;  // Enable the PCINT0 interrupt
    PCMSK0 = 4; // Setup PORTB2 to trigger a PCINT0 interrupt

    // Enable interrupts
    sei();

    //===============================================================
    // Loop forever updating the LED matrix. All SPI and copying
    // from the spi data buffer to the display frame buffer is done
    // in the above interrupt service routines.
    //===============================================================
    for (;;){
        for (row = 0; row < 8; row++){
            line = 0;
            for (col = 0; col < 8; col++){
                switch (image[col + (8 * row)]){
                    case GREEN:
                        line |= (0x0001 << (col^COL_DIR)); 
                        break;
                        
                    case RED:    
                        line |= (0x0100 << (col^COL_DIR)); 
                        break;
                        
                    case YELLOW: 
                        line |= (0x0101 << (col^COL_DIR)); 
                        break; 
                }
            } // col
            
        // Now that line has been loaded for the given row,
        // send the data to the 595 shift registers and drive
        // the new row data.
        shiftLine(line, row);
        } // row
    }
    return (0);
}

//===================================================================
// Initialize I/O pins
//===================================================================
void ioinit(void)
{
    DDRB  = 0x13;
    DDRC  = 0x1F;
    DDRD  = 0xFF;
    PORTD = 0x00;
    clrbit(PORTC, CLK);
    clrbit(PORTC, CLR);
    clrbit(PORTC, DATA);
    clrbit(PORTC, LATCH);
    clrbit(PORTC, EN);
}

//===================================================================
// Send row data to the pair of 595 shift registers.
//===================================================================
void inline shiftLine(uint16_t line, uint8_t rowNum)
{
    uint8_t i;
    
    for(i = 0; i < 16; i++){
        clrbit(PORTC, CLK);
        if (line & (1 << i))
            setbit(PORTC, DATA);
        else
            clrbit(PORTC, DATA);
        setbit(PORTC, CLK);
    }
    
    // The new column data has been shifted in, now update the
    // outputs. In order to eliminate ghosting, you have to turn
    // off the drivers between the time the shift data is latched
    // and the row portd is updated. You can either turn off all
    // rows or turn off the 595 outputs with the EN signal. Here
    // we just turn off the row driver by setting all to 0.
    PORTD = 0;              // no rows driving
    setbit(PORTC, LATCH);   // new RG column data is latched
    PORTD = (1 << rowNum);  // new row is driven
    clrbit(PORTC, LATCH);
}

Mark,

I forgot to mention. You might want to change just one backpack so that you have one unmodified to refer to for proper operation. One will ghost, the other won’t. Note that you cannot bump the speed if you do this.

When satisfied, update the remaining backpack(s).

Also, in your code above, remove all of that select code - it is unnecessary.

I believe that was to select a row of backpacks with the custom designed mux board. You do not have a mux present.

Jim

Thanks so much! I’ll give it a try and reply back when I’ve had the chance.

First, I’m sorry for the delayed response, for some reason I didn’t get my usual notification email.

Second, I didn’t realize the LED matrix would pop off the board! That was easy :slight_smile:

Jim_D:
Question - there are lots of errors in the backpack documentation.

  1. The input and output SPI headers are backwards as you know.

  2. The red and green LED codes are backwards. Red = 2, Green = 1.

  3. The pixel positions do not follow the documentation, either.

  4. The code they supply is for an earlier revision with some board errors and will not work unmodified on current boards.

Do you want me to fix number 2 or number 3? Not much I can do with number 1.

Number 2 - trivial or I can leave alone for compatibility.

Number 3 - I would need to know where you want pixel 1 and the order.

  1. I’ll work out the colors one way or another

  2. Right now it’s my understanding that pixels are indexed up from top left to bottom right, moving from left to right, top to bottom. It looks like that’s how your code does it starting on line 137, right?

Jim_D:
Also, in your code above, remove all of that select code - it is unnecessary.

I believe that was to select a row of backpacks with the custom designed mux board. You do not have a mux present.

I’m not sure what the “select code” is that you are referencing.

Can’t wait to try this out. I will keep you posted. Thanks so much. :smiley:

Well, I’m having some trouble getting the firmware uploaded to the board. Here is the output I’m getting from avrdude.

I’m on OS X 10.6.8, Arduino 1.0, Using an Arduino Uno board.

Any help is appreciated. I’m going to keep plugging away at it.

mark[~/Documents/Arduino/LED_matrix_RG_backpack_firmware]# /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avrdude -C /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf -P /dev/tty.usbmodemfa141 -b 19200 -c avrisp -p m328p -v -e -U flash:w:LED_matrix_RG_backpack_firmware.hex

avrdude: Version 5.11, compiled on Sep 2 2011 at 18:52:52

Copyright (c) 2000-2005 Brian Dean, http://www.bdmicro.com/

Copyright (c) 2007-2009 Joerg Wunsch

System wide configuration file is “/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf”

User configuration file is “/Users/mark/.avrduderc”

User configuration file does not exist or is not a regular file, skipping

Using Port : /dev/tty.usbmodemfa141

Using Programmer : avrisp

Overriding Baud Rate : 19200

AVR Part : ATMEGA328P

Chip Erase delay : 9000 us

PAGEL : PD7

BS2 : PC2

RESET disposition : dedicated

RETRY pulse : SCK

serial program mode : yes

parallel program mode : yes

Timeout : 200

StabDelay : 100

CmdexeDelay : 25

SyncLoops : 32

ByteDelay : 0

PollIndex : 3

PollValue : 0x53

Memory Detail :

Block Poll Page Polled

Memory Type Mode Delay Size Indx Paged Size Size #Pages MinW MaxW ReadBack


eeprom 65 20 4 0 no 1024 4 0 3600 3600 0xff 0xff

flash 65 6 128 0 yes 32768 128 256 4500 4500 0xff 0xff

lfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00

hfuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00

efuse 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00

lock 0 0 0 0 no 1 0 0 4500 4500 0x00 0x00

calibration 0 0 0 0 no 1 0 0 0 0 0x00 0x00

signature 0 0 0 0 no 3 0 0 0 0 0x00 0x00

Programmer Type : STK500

Description : Atmel AVR ISP

Hardware Version: 2

Firmware Version: 1.18

Topcard : Unknown

Vtarget : 0.0 V

Varef : 0.0 V

Oscillator : Off

SCK period : 0.1 us

avrdude: AVR device initialized and ready to accept instructions

Reading | ################################################## | 100% 0.12s

avrdude: Device signature = 0x1e950f

avrdude: safemode: lfuse reads as FF

avrdude: safemode: hfuse reads as DF

avrdude: safemode: efuse reads as 1

avrdude: erasing chip

avrdude: reading input file “LED_matrix_RG_backpack_firmware.hex”

avrdude: input file LED_matrix_RG_backpack_firmware.hex auto detected as Intel Hex

avrdude: writing flash (620 bytes):

Writing | | 0% 0.00savrdude: stk500_recv(): programmer is not responding

Changed the ArduinoISP sketch to use a baud rate of 9600.

Changed avrdude command to use 9600:

/Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/bin/avrdude -C /Applications/Arduino.app/Contents/Resources/Java/hardware/tools/avr/etc/avrdude.conf -P /dev/tty.usbmodemfa141 -b 9600 -c avrisp -p m328p -v -e -U flash:w:LED_matrix_RG_backpack_firmware.hex

And it works!

Test with code on one board and NO FLICKER!

Uploading to other boards now…

:smiley:

This is the code from one of your earlier posts that can be removed.

  // Configure for 2 boards
  digitalWrite(CHIPSELECT, LOW);
  delayMicroseconds(500);
  spi_transfer('%');
  spi_transfer(2);
  digitalWrite(CHIPSELECT, HIGH);
  delayMicroseconds(10);

The backpacks have no knowledge of any configuration. You just have to shift in N x 64 bytes, where N is the number of digits.

Jim

Success! Here’s a video: http://vimeo.com/38748809

Thank you so much Jim!

I’ll eventually be posting a full project write up on my site at http://www.markroland.com/project/networked-led-marquee