SparkFun Forums 

Where electronics enthusiasts find answers.

For the discussion of Arduino related topics.
By mark4
#140248
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
Code: Select all
// 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);
Code: Select all
#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!
By Jim_D
#140538
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
By mark4
#140547
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.
By Jim_D
#140710
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.
Code: Select all
// 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
By mark4
#140753
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.
By Jim_D
#140760
mark4 wrote: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
By mark4
#140963
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.
Code: Select all
/*
 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);
}

By Jim_D
#140976
mark4 wrote: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
By Jim_D
#141041
mark4 wrote: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
By Jim_D
#141090
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
Code: Select all
#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);
}
By Jim_D
#141093
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