Transfer Arduboy sBuffer via SPI to drive an LED matrix

I’m currently working on a project to create an oversized Arduboy with the following features:

  • 128 x 64 LED matrix driven by an SPI Slave
  • Amplified sound

Inspiration came from the following Adafruit post:

Because Evade 2 Doesn’t have USB code, I can’t use Serial. Otherwise, I would have just pumped out the sBuffer data via Serial and used a Pi to drive the LEDs. :confounded:

As a proof of concept, I have a Leonardo board setup as an arduboy and an Uno as a slave.

Using the following diagrams:
[Leonardo]

[Uno]

I wired up the two devices as such (I know i could have just connected the ICSPs together, but i don’t have dual-female jumper cables):

Leonardo         UNO
---------------------------
PB1 [SCK]   - PB5 [SCK]
PB2 [MOSI] - PB3 [MOSI]
PB3 [MISO] - PB4 [MISO]
GND            - GND
 

On the leonardo, i’m just writing a single pixel as such:

  arduboy.drawPixel(0,0, 1);

On the Uno, i’m trying to read the data via an ISR (Largely based off of http://www.gammon.com.au/forum/?id=10892&reply=8#reply8):

#include <SPI.h>
#define HEIGHT 64
#define WIDTH 128

uint8_t sBuffer[WIDTH * HEIGHT  / 8];
//uint8_t sBuffer[1];


// LedControl is used to drive two 8x8 matrices via pins 2,3,4.
#include "LedControl.h"
#define NUM_DEVICES 2
LedControl ledControl=LedControl(4,3,2,NUM_DEVICES);

volatile bool haveData = false;

template <typename T> unsigned int SPI_readAnything(T& value) {
  byte * p = (byte*) &value;
  unsigned int i;
  for (i = 0; i < sizeof(value); i++) {
    *p++ = SPI.transfer(0);
  }
  return i;
}  // end of SPI_readAnything

unsigned int bufferIndex;


unsigned int SPI_readScreenBuffer() {
  //  byte * p = sBuffer;
  unsigned int i;

  if (bufferIndex > 1024) {
    bufferIndex = 0;
  }
  sBuffer[bufferIndex] = (uint8_t)SPDR;  // get first byte
  bufferIndex++;
  haveData = true;

//  sBuffer[0] = (uint8_t)SPDR;  // get first byte
//  for (i = 0; i < sizeof(sBuffer) - 1; i++) {
//    sBuffer[i] = SPI.transfer(0);
//  }
//  Serial.println();
  return i;
}  // e



byte x = 0;
void setup () {
  while (!Serial);
//  Serial.begin (115200);   // debugging
    Serial.begin(250000);
  // have to send on master in, *slave out*
  pinMode(MISO, OUTPUT);
  // turn on SPI in slave mode
  SPCR |= _BV(SPE);

  // now turn on interrupts
  SPI.attachInterrupt();
  Serial.println(F("\nSetup done"));

  //we have to init all devices in a loop
  for(uint8_t address = 0; address < NUM_DEVICES; address++) {
    /*The MAX72XX is in power-saving mode on startup*/
    ledControl.shutdown(address,false);
    /* Set the brightness to a medium values */
    ledControl.setIntensity(address, 8);
    /* and clear the display */
    ledControl.clearDisplay(address);
  }  

}  // end of setup


void loop () {
  if (haveData) {
    haveData = false;
    Serial.println(sBuffer[0]);
    ledControl.setRow(0, 0, sBuffer[0]);

  }
  
  x++;
  if (x > 7) {
    x = 0;
  }
  ledControl.setRow(1, x, 255);
 
  delay(150);
  // ledControl.clearDisplay(0);
  ledControl.clearDisplay(1);

}  // end of loop

// SPI interrupt routine
ISR (SPI_STC_vect) {
  SPI_readScreenBuffer();
  haveData = true;
}  // end of interrupt routine SPI_STC_vect

I’m getting output from Serial.println like the following (where i would expect a single 128 value and the rest to be zeros):

4
4
8
8
8
128
128
128
64
64
64
32
32
32
32
16
16
16

Any help would be greatly appreciated. Once done, I’m going to open source the project, plans, etc.

Thanks :slight_smile:

2 Likes

You might want to start by writing a non-Arduboy sketch for the Leonardo, which just sends simple data to the UNO over SPI, then see if it’s received properly. You could use the Arduino SPI library to send the data.

You could send serial data from the Leonardo to the serial monitor to indicate what’s going on.

Once you get bytes or simple blocks of data to successfully transfer, you could then go back to trying to receive the Arduboy screen data.

The problem may be the receive capabilities of the UNO. We run the Arduboy display at 8MHz. From this conversation:

The maximum speed for Arduino as a slave is F_CPU/4, so it’s 4Mbps. When the Arduino is the master it can work at F_CPU/2, so it’s 8Mbps

1 Like

Have you looked at using @Mr.Blinky’s custom bootloader & serial?

This is not your immediate problem here but FYI there is an extra pin you have to connect to distinguish display controller commands from data and it will have to be handled eventually.

2 Likes

Hi @MLXXXp

The following sketches from this post worked perfectly.
http://www.gammon.com.au/forum/?id=10892&reply=8#reply8

I see that for writes he’s pulling Pin 10 (SS) LOW then HIGH. The the Arduboy lib doesn’t seem to do that at all, but rather writes the contents of sBuffer to SPI upon paint call. – all this is new to me, so i’m very much in “learning mode”.

Also in his his Master example, he also slows down the SPI clock as well.

  SPI.setClockDivider(SPI_CLOCK_DIV8);

I’ll try the same by modifying a local copy of Arduboy 2.

Yes, very true.

I’ll need to emulate invert :(.

Can’t use serial – our game omits USB due to space limitations. This project wasn’t even in our minds when we were developing the game. :frowning:

@MLXXXp advice above is sound, starting with something even simpler should help isolating issues. I don’t believe the UNO will be fast enough to push data to those LEDs at a sufficient refresh rate. As for the controller emulation it’s not too hard to get something basic going, there is some code in simavr to simulate the controller you could build upon.

I believe that’s because on the arduboy the display is always “selected” as slave so all writes go to the display.

But @Mr.Blinky’s bootloader gives you an extra 1K of program space.

@dxb is the boot loader stuff that @Mr.Blinky did similar to what you did to help us squeeze out 1k?

The Arduboy uses Pin 12 to control CS (SS) instead of Pin 10. However, the dispaly always remains selected, so it’s not really necessary.

1 Like

@Mr.Blinky’s bootloader is a replacement bootloader which takes up (I believe) 3k instead of 4k so you get extra space but cannot rely on that feature outside of your dev hardware because end users can’t really be expected to update their bootloader.

EDIT: even with the extra 1k the arduino code USB serial will not fit in progmem with evade2

Given what you know about the innards of Evade 2, is it worth swapping? this is a bit above my head :stuck_out_tongue_winking_eye:

@JayGarcia i’ve Been packing all sorts into Dark & Under and I have the programming skill of a grapefruit.

1 Like

@Mr.Blinky created a reduced size bootloader that gives you and extra 1K of program space regardless of the contents of the sketch. Any Arduboy sketch loaded into an Arduboy with his bootloader installed, and using the matching boards.txt file, will instantly have 1K of space available.

1 Like

I do not think the arduino core USB serial will not fit in progmem with evade2 even with @Mr.Blinky’s bootloader. You need almost 3k of progmem for that.

1 Like

I had my wires completely crossed. Thank you

Ah! Thanks. Back to SPI it is for me. :stuck_out_tongue_winking_eye:

But you could hook up a USB to TTL serial converter to the Leonardo’s RX and TX pins and use Serial1.

I think using SPI and emulating the display controller on a more powerful slave has several advantages over the serial approach anyway:

  • no need to recompile games with custom libs to make them work on your HUGE screen
  • if the game fits the arduboy it will fit your setup
  • nice decoupling: changes to your display will only require changes to the slave SPI device

Agreed, except a more powerfull (faster) slave could be an issue for @JayGarcia.