adafruit / Adafruit_Protomatter

RGB matrix library for Arduino
57 stars 20 forks source link

Annoying interference on 2xMatrix LED + Matrix Portal S3 using AIO #76

Open damomad12 opened 2 months ago

damomad12 commented 2 months ago

Hi. I’m experiencing an annoying distortion on the Matrix LED displays. It’s like random horizontal lines (or points) appearing & disappearing. Always at the right side of any LEDs being on. It’s like a copy of the LEDs appearing at their right side.

The HW setup is a Matrix Portal S3 and 2 identical LED Matrix Displays 64x64. The MPS3 is directly connected to one of the displays which is chained to the other display.

Arduino board: Adafruit Matrix Portal S3

Arduino IDE version (found in Arduino -> About Arduino menu): 2.3.2

List the steps to reproduce the problem below (if possible attach a sketch or copy the sketch code in too):

I have 3 different devices:

The HW I’m using is all from Adafruit.

Although I noticed (on both devices 1 and 2) just a bit of this distortion about every 15 sec, it was not of importance. But when I configured & connected the MPS3 to the Internet and to AIO, the effect became very visible and quite annoying. This was on both MPS3 +2 chained displays, devices 1 and 2. On device 3, the MPS3 single display, never any distortion.

I’ve been in touch with Adafruit’s support and we´ve tried many things to try to find out what was happening: changed the USB-C cable to support high intensity, although the device is consuming about 0,5A, change ribbon cables, configuration, etc.

Always using the same code, if I disconnect the second display, distortion disappears If I use both 64x64 panels, but with a 64x32 SW setup. No interference!!!
using: Adafruit_Protomatter matrix(64, 4, 1, rgbPins, 4, addrPins, clockPin, latchPin, oePin, false);
If I use both 64x64 panels, but with 64x64 SW setup. Interference appears again
using: Adafruit_Protomatter matrix(128, 4, 1, rgbPins, 5, addrPins, clockPin, latchPin, oePin, false);

Adafruit support conclusion is that the HW is ok: “All hardware looks good. This seems like an issue with the Protomatter library since the problem only occurs when a unified address space is used between two panels.”

They have advised me to put this issue on this GitHub channel.

The effect can be seen on this video: https://youtu.be/DGt_X1TwFok

Thank you in advance.

Dany

The code I’m using is:

//sin rtc
//quitando más cosas innecesarias
/************************** INTERNET Configuration ***********************************/
 
// Internet AIO
// edit the config.h tab and enter your Adafruit IO credentials
// and any additional configuration needed for WiFi, cellular,
// or ethernet clients.
#include "config.h"
/************************** End INTERNET Configuration ***********************************/
 
#include <Adafruit_Protomatter.h>
#include <Adafruit_GFX.h>       // Core graphics library
 
// RGB565 Colour codes
#define ST7735_LIME    0x07FF
#define ST7735_RED     0xF800
 
// CONFIGURA AUTOMÁTICAMENTE ENTRE EL TIPO DE PLACA M4 Y S3
  uint8_t rgbPins[] = {42, 41, 40, 38, 39, 37}; // *****OK**** CAMBIOPANEL COLORES PARA 64X64 NÚMEROS ROJO, H/M AMARAILLO, FÓRMULA AZUL
  uint8_t addrPins[]  = {45, 36, 48, 35, 21};
  uint8_t clockPin =2;
  uint8_t latchPin = 47;
  uint8_t oePin  = 14;
 
// CAMBIOPANEL
// CONFIGURARSEGUNPANEL :
#define NUM_ADDR_PINS 5
 
const int NUMBERMATRIXDISPLAYS=2; // 2=número de paneles LED encadenados uno al lado del otro
 
/********************************
FIN DE LAS CONFIGURACIONES EN FUNCIÓN DEL TIPO DE PLACA Y DIFERENTES PANELES DE MATRICES LED
*********************************/
 
// Constantes y Variables del programa
int   TRUE=1, FALSE=0;
 
int   FIJO=1, HASTASEL=2; // Las fórmulas que aparecerán será sólo de ese nivel o desde el nivel básico hasta la selección actual
int   hastaNivel=HASTASEL; // Inicialización por defecto
int   newNivel=8;   // Variables uso conexión Internet IO ADAFRUIT
int   newFijoHasta=HASTASEL;     // Variables uso conexión Internet IO ADAFRUIT
int   newHMNMgrupo=0, newHMNMveces=0, newHMNMhora=0, newHMNMminuto=0; // Variables uso conexión Internet IO ADAFRUIT para los Mensajes en pantalla
char  newMensaje[121]; // Variables uso conexión Internet IO ADAFRUIT para definir el Mensajes (solo el texto) en pantalla
int   horaMensaje;
int   minutoMensaje;
 
// ¡¡¡ OJO INSERTARLAS TAMBIÉN AL INICIO DEL SETUP() EN LA APERTURA DEL PUERTO SERIE
int   debug=FALSE; // Comprobación de valores, por defecto apagado
 
// CREAR OBJETO PROTOMATTER QUE MANEJA LA MATRIZ LED
Adafruit_Protomatter matrix(64*NUMBERMATRIXDISPLAYS, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins, clockPin, latchPin, oePin, false);
//Adafruit_Protomatter matrix(128, 4, 1, rgbPins, NUM_ADDR_PINS, addrPins, clockPin, latchPin, oePin, false);
 
// *********** Configuración Internet IO ADAFRUIT*********
// set up the feeds & declare variables
// Header
AdafruitIO_Feed *nuevonivel =     io.feed("time-math.cambianivel"); // Para los niveles
AdafruitIO_Feed *nuevofijohasta = io.feed("time-math.fijohasta"); // Para los niveles o Fijo o Hasta
AdafruitIO_Feed *nuevomensaje =   io.feed("time-math.mensaje"); // Mensaje a retransmitir
//
// *************************************************************************
// ******* I N I C I O   C O N F I G U R A C I Ó N   I N I C I A L *********
// *************************************************************************
 
void setup() {
 
  if (debug) {
    Serial.begin(115200);
    // while (!Serial) delay(10); // will pause Zero, Leonardo, etc until serial console opens
    while (!Serial && millis () < 3000) {
      delay(10); // Wait up to 3 seconds to allow the USB console to be opened
    }
  }
 
  //***** INICIALIZAR MATRIX LED ******************
  // Initialize matrix...
  ProtomatterStatus status = matrix.begin();
  if (debug) {
    Serial.print("Protomatter begin() status: ");
    Serial.println((int)status);
  }
  if(status != PROTOMATTER_OK) {
    for(;;);
  }
  //***** FIN INICIALIZAR MATRIX LED ******************
 
  //********* INTERNET AIO ADAFRUIT CONFIG ***********************
  // connect to io.adafruit.com
  //  AIO* setup()
  io.connect();
  //
  // set up a message handler for the count feed.
  // the handleMessage function (defined below)
  // will be called whenever a message is
  // received from adafruit io.
 
  //  AIO*
  nuevonivel->onMessage(handleMessageNN);
  nuevofijohasta->onMessage(handleMessageFH);
  nuevomensaje->onMessage(handleMessageNM);
 
  // wait for a connection
  while(io.status() < AIO_CONNECTED) {
      Serial.print(".");
      delay(500);
  }
  Serial.println();
  // we are connected
 
  nuevonivel->get();
  nuevofijohasta->get();
  nuevomensaje->get();
  //
 
  //********* FIN INTERNET CONFIG *******
 
} // Fin setup() {
 
// *************************************************************************
// *************************************************************************
// **************** I N I C I O   B U C L E   G E N E R A L ****************
// *************************************************************************
// *************************************************************************
 
void loop() {
 
  int hors,mins;
  int dummy;
  boolean reentroEscribepantalla = false;
 
  // ***** MENÚ DE CONFIGURACIÓN *****
 
  // ************** Para arranque Internet conexión con IO ADAFRUIT ****************
  // io.run(); is required for all sketches.
  // it should always be present at the top of your loop
  // function. it keeps the client connected to
  // io.adafruit.com, and processes any incoming data.
  // Serial.println("io.run();");
  //  AIO* loop()
     io.run();
  //
 
  // Fin arranque IO ADAFRUIT
  sleep(100);
  imprimeCaracterHM(0,0); // Imprime los símbolos de Hora y Minuto en la pantalla
  matrix.drawFastVLine(0,7,55,ST7735_RED);      // 1ª Recta vertical
  matrix.show();
 
} // FIN LOOP()
 
// * * * * * * * * * * F U N C I O N E S * * * * * * * * * *
 
void imprimeCaracterHM(int posx, int posy){
 
  // En función de si se trata de 1 panel o 2, como el texto de H o M se dibuja, hay que implementarlo para cada caso
  if ( NUMBERMATRIXDISPLAYS == 1 ) {
    // H
    matrix.drawFastHLine(posx+1,posy+1,3,ST7735_LIME);  // Recta horizontal
    matrix.drawFastVLine(posx,posy,3,ST7735_LIME);      // 1ª Recta vertical
    matrix.drawFastVLine(posx+4,posy,3,ST7735_LIME);    // 2ª Recta vertical
 
    // M
    matrix.drawPixel(posx+1,posy+16+1,ST7735_LIME);      // Punto inicial 1ª Recta oblicua hacia abajo
    matrix.drawPixel(posx+2,posy+16+2,ST7735_LIME);      // Punto final 1ª Recta oblicua hacia abajo
    matrix.drawPixel(posx+3,posy+16+1,ST7735_LIME);      // Punto final 2ª Recta oblicua hacia abajo
    matrix.drawFastVLine(posx,posy+16,3,ST7735_LIME);     // 1ª Recta vertical
    matrix.drawFastVLine(posx+4,posy+16,3,ST7735_LIME);   // 2ª Recta vertical
  }
 
  if ( NUMBERMATRIXDISPLAYS == 2 ) {
   
    // H
    // Reactas laterales
    matrix.drawFastHLine(posx+2,posy+2,6,ST7735_LIME);  // Recta horizontal
    matrix.drawFastHLine(posx+2,posy+3,6,ST7735_LIME);  // Recta horizontal duplicada
 
    matrix.drawFastVLine(posx,posy,6,ST7735_LIME);      // 1ª Recta vertical
    matrix.drawFastVLine(posx+1,posy,6,ST7735_LIME);    // 1ª Recta vertical duplicada
 
    matrix.drawFastVLine(posx+8,posy,6,ST7735_LIME);    // 2ª Recta vertical
    matrix.drawFastVLine(posx+9,posy,6,ST7735_LIME);   // 2ª Recta vertical duplicada
 
    // M
    // Puntos de la V interior
    matrix.drawPixel(posx+2,posy+32+1,ST7735_LIME);      // 1º Punto inicial 1ª Recta oblicua hacia abajo
    matrix.drawPixel(posx+2,posy+32+2,ST7735_LIME);      // 1º Punto inicial 1ª Recta oblicua hacia abajo duplicado paralelo
    matrix.drawPixel(posx+3,posy+32+2,ST7735_LIME);      // 2º Punto inicial 1ª Recta oblicua hacia abajo
    matrix.drawPixel(posx+3,posy+32+3,ST7735_LIME);      // 2º Punto inicial 1ª Recta oblicua hacia abajo duplicado paralelo
    matrix.drawPixel(posx+4,posy+32+3,ST7735_LIME);      // 3º Punto inicial 1ª Recta oblicua hacia abajo
    matrix.drawPixel(posx+4,posy+32+4,ST7735_LIME);      // 3º Punto inicial 1ª Recta oblicua hacia abajo duplicado paralelo
 
    matrix.drawPixel(posx+5,posy+32+4,ST7735_LIME);      // 4º Punto inicial 1ª Recta oblicua hacia arriba
    matrix.drawPixel(posx+5,posy+32+3,ST7735_LIME);      // 4º Punto inicial 1ª Recta oblicua hacia arriba duplicado paralelo
    matrix.drawPixel(posx+6,posy+32+3,ST7735_LIME);      // 5º Punto inicial 1ª Recta oblicua hacia arriba
    matrix.drawPixel(posx+6,posy+32+2,ST7735_LIME);      // 5º Punto inicial 1ª Recta oblicua hacia arriba duplicado paralelo
    matrix.drawPixel(posx+7,posy+32+2,ST7735_LIME);      // 6º Punto inicial 1ª Recta oblicua hacia arriba
    matrix.drawPixel(posx+7,posy+32+1,ST7735_LIME);      // 6º Punto inicial 1ª Recta oblicua hacia arriba duplicado paralelo
 
    // Reactas verticales
    matrix.drawFastVLine(posx,posy+32,6,ST7735_LIME);     // 1ª Recta vertical
    matrix.drawFastVLine(posx+1,posy+32,6,ST7735_LIME);   // 1ª Recta vertical duplicada
    matrix.drawFastVLine(posx+8,posy+32,6,ST7735_LIME);   // 2ª Recta vertical
    matrix.drawFastVLine(posx+9,posy+32,6,ST7735_LIME);   // 2ª Recta vertical duplicada
 
  }
  matrix.show();
}
 
void handleMessageNN(AdafruitIO_Data *data) { // Gestiona el Feed para asignar el Nuevo Nivel
  newNivel=data->toInt();
}
void handleMessageFH(AdafruitIO_Data *data) {  // Gestiona el Feed para asignar el Nuevo Hasta o Fijo para el Nivel
  newFijoHasta=data->toInt();
 }
void handleMessageNM(AdafruitIO_Data *data) { // Gestiona el Feed para asignar el Nuevo Mensaje
  char  *lecturaMensaje; // grupo=1; veces=3; hora=2; minutos=2; texto de mensaje.  Total 4* ";"
  int   i=0;
  int   posPuntoComas=0,puntoComas[5]; // Indica la posición del símbolo punto y coma
 
  lecturaMensaje=data->toChar(); // lecturaMensaje contiene el mensaje completo
 
  while ( lecturaMensaje[i] ) { // Averiguar las posiciomnes de los separadores ";"
    if ( lecturaMensaje[i] == ';') { //Define el separador
      puntoComas[posPuntoComas]=i;
      posPuntoComas++;
    }
    i++; 
  }
 
  // Devuelve los valores de grupo, repeticiones, hora y minuto
  newHMNMgrupo= DevuelveDatoslecturaMensaje(lecturaMensaje,0,puntoComas[0]);
  newHMNMveces= DevuelveDatoslecturaMensaje(lecturaMensaje,puntoComas[0]+1,puntoComas[1]);
  newHMNMhora=  DevuelveDatoslecturaMensaje(lecturaMensaje,puntoComas[1]+1,puntoComas[2]);
  newHMNMminuto=DevuelveDatoslecturaMensaje(lecturaMensaje,puntoComas[2]+1,puntoComas[3]);
 
  for ( i=0 ; i < strlen(lecturaMensaje)-puntoComas[3] ; i++ ) {
    newMensaje[i]=lecturaMensaje[9+1+i]; // Carga la matriz de caracteres en la variable global newMensaje
  }
}
 
int DevuelveDatoslecturaMensaje(char *lecturaMensaje,int desde,int hasta){ // Usado en funcion: handleMessageNM()
  if ( hasta - desde < 2 ) {
    return( int(lecturaMensaje[desde])-48 );
  }
  if ( hasta - desde < 3 ) {
    return( 10*(int(lecturaMensaje[desde])-48) + (int(lecturaMensaje[desde+1])-48) );
  }
  if ( hasta - desde < 4 ) {
    return( 100*(int(lecturaMensaje[desde])-48) + (10*(int(lecturaMensaje[desde+1])-48)) + (int(lecturaMensaje[desde+2])-48) );
  }
  return(0);
}
damomad12 commented 2 months ago

Oops, I accidently closed the issue. Sorry, I've opened it again

dhalbert commented 2 months ago

Forums thread is here: https://forums.adafruit.com/viewtopic.php?t=210086

dhalbert commented 2 months ago

Thank you for all your testing. You mentioned in the forum posts that if wifi is not in use, you don't see the interference. My guess is that wifi activity is interrupting Protomatter's dumping data to the panels. Protomatter uses up a lot of CPU time at interrupt level driving the panels, which have to be driven in real time with precise timing. The more panels, the more time it takes. The wifi activity may be running at a higher priority, or perhaps at some point the wifi activity can no longer be deferred and takes priority. This is just speculation. (I am not necessarily the person who would work on this.)

damomad12 commented 2 months ago

Hi. Thanks for your reply.

As I told at the starting post: "Although I noticed (on both devices 1 and 2) just a bit of this distortion about every 15 sec (more or less), it was not of importance." So there was an ocasional interference from time to time, but not that important.

Ok I understand your guess. Is there anyway I can lower the priority or the wifi activity or Protomatter 's dumping data, so I can find any improvement?

On the other hand, if I tell Protomatter that it's displays width is 64 instead of 128, no interference appears, although the same info is seen on both screens. I suppose that's due to the fact that it takes double time to send info to the 128 screen instead of a 2x64. Could this be right?

If you are not necessarily the person to work on this, will there be anyone else who may help me?

Thank you for your support.

Dany.

th-kwon commented 2 weeks ago

Hello. We're experencing simillar issue from S3 with large matrix. Using sample code from protomatter(doublebuffer_scrolltext) changing some parameter reproduces this issue.

Also this interference is not occuring from MatrixPortal M4 with same code.

Scrolltext from S3 - tearing occurs(0:40) https://youtu.be/IvfrAela4C0 image

Scrolltext from M4 - no tearing https://youtu.be/ZV2n-bKIZ2A

What we're going to do - displaying large gif images and some text message. https://youtu.be/_q8w-LxM-3Q We're looking for using S3 than M4 for larger ram and flash size, but this issue needs to be resolved before using it.

The code below is sample code what we used from video (width 192, height 64, bitdepth 4, text size 2)

/* ----------------------------------------------------------------------
Double-buffering (smooth animation) Protomatter library example.
PLEASE SEE THE "simple" EXAMPLE FOR AN INTRODUCTORY SKETCH.
Comments here pare down many of the basics and focus on the new concepts.

This example is written for a 64x32 matrix but can be adapted to others.
------------------------------------------------------------------------- */

#include <Adafruit_Protomatter.h>
#include <Fonts/FreeSansBold18pt7b.h> // Large friendly font

/* ----------------------------------------------------------------------
The RGB matrix must be wired to VERY SPECIFIC pins, different for each
microcontroller board. This first section sets that up for a number of
supported boards.
------------------------------------------------------------------------- */

#if defined(_VARIANT_MATRIXPORTAL_M4_) // MatrixPortal M4
  uint8_t rgbPins[]  = {7, 8, 9, 10, 11, 12};
  uint8_t addrPins[] = {17, 18, 19, 20, 21};
  uint8_t clockPin   = 14;
  uint8_t latchPin   = 15;
  uint8_t oePin      = 16;
#elif defined(ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3) // MatrixPortal ESP32-S3
  uint8_t rgbPins[]  = {42, 41, 40, 38, 39, 37};
  uint8_t addrPins[] = {45, 36, 48, 35, 21};
  uint8_t clockPin   = 2;
  uint8_t latchPin   = 47;
  uint8_t oePin      = 14;
#elif defined(_VARIANT_FEATHER_M4_) // Feather M4 + RGB Matrix FeatherWing
  uint8_t rgbPins[]  = {6, 5, 9, 11, 10, 12};
  uint8_t addrPins[] = {A5, A4, A3, A2};
  uint8_t clockPin   = 13;
  uint8_t latchPin   = 0;
  uint8_t oePin      = 1;
#elif defined(__SAMD51__) // M4 Metro Variants (Express, AirLift)
  uint8_t rgbPins[]  = {6, 5, 9, 11, 10, 12};
  uint8_t addrPins[] = {A5, A4, A3, A2};
  uint8_t clockPin   = 13;
  uint8_t latchPin   = 0;
  uint8_t oePin      = 1;
#elif defined(_SAMD21_) // Feather M0 variants
  uint8_t rgbPins[]  = {6, 7, 10, 11, 12, 13};
  uint8_t addrPins[] = {0, 1, 2, 3};
  uint8_t clockPin   = SDA;
  uint8_t latchPin   = 4;
  uint8_t oePin      = 5;
#elif defined(NRF52_SERIES) // Special nRF52840 FeatherWing pinout
  uint8_t rgbPins[]  = {6, A5, A1, A0, A4, 11};
  uint8_t addrPins[] = {10, 5, 13, 9};
  uint8_t clockPin   = 12;
  uint8_t latchPin   = PIN_SERIAL1_RX;
  uint8_t oePin      = PIN_SERIAL1_TX;
#elif USB_VID == 0x239A && USB_PID == 0x8113 // Feather ESP32-S3 No PSRAM
  // M0/M4/RP2040 Matrix FeatherWing compatible:
  uint8_t rgbPins[]  = {6, 5, 9, 11, 10, 12};
  uint8_t addrPins[] = {A5, A4, A3, A2};
  uint8_t clockPin   = 13; // Must be on same port as rgbPins
  uint8_t latchPin   = RX;
  uint8_t oePin      = TX;
#elif USB_VID == 0x239A && USB_PID == 0x80EB // Feather ESP32-S2
  // M0/M4/RP2040 Matrix FeatherWing compatible:
  uint8_t rgbPins[]  = {6, 5, 9, 11, 10, 12};
  uint8_t addrPins[] = {A5, A4, A3, A2};
  uint8_t clockPin   = 13; // Must be on same port as rgbPins
  uint8_t latchPin   = RX;
  uint8_t oePin      = TX;
#elif defined(ESP32)
  // 'Safe' pins, not overlapping any peripherals:
  // GPIO.out: 4, 12, 13, 14, 15, 21, 27, GPIO.out1: 32, 33
  // Peripheral-overlapping pins, sorted from 'most expendible':
  // 16, 17 (RX, TX)
  // 25, 26 (A0, A1)
  // 18, 5, 9 (MOSI, SCK, MISO)
  // 22, 23 (SCL, SDA)
  uint8_t rgbPins[]  = {4, 12, 13, 14, 15, 21};
  uint8_t addrPins[] = {16, 17, 25, 26};
  uint8_t clockPin   = 27; // Must be on same port as rgbPins
  uint8_t latchPin   = 32;
  uint8_t oePin      = 33;
#elif defined(ARDUINO_TEENSY40)
  uint8_t rgbPins[]  = {15, 16, 17, 20, 21, 22}; // A1-A3, A6-A8, skip SDA,SCL
  uint8_t addrPins[] = {2, 3, 4, 5};
  uint8_t clockPin   = 23; // A9
  uint8_t latchPin   = 6;
  uint8_t oePin      = 9;
#elif defined(ARDUINO_TEENSY41)
  uint8_t rgbPins[]  = {26, 27, 38, 20, 21, 22}; // A12-14, A6-A8
  uint8_t addrPins[] = {2, 3, 4, 5};
  uint8_t clockPin   = 23; // A9
  uint8_t latchPin   = 6;
  uint8_t oePin      = 9;
#elif defined(ARDUINO_ADAFRUIT_FEATHER_RP2040)
  // RP2040 support requires the Earle Philhower board support package;
  // will not compile with the Arduino Mbed OS board package.
  // The following pinout works with the Adafruit Feather RP2040 and
  // original RGB Matrix FeatherWing (M0/M4/RP2040, not nRF version).
  // Pin numbers here are GP## numbers, which may be different than
  // the pins printed on some boards' top silkscreen.
  uint8_t rgbPins[]  = {8, 7, 9, 11, 10, 12};
  uint8_t addrPins[] = {25, 24, 29, 28};
  uint8_t clockPin   = 13;
  uint8_t latchPin   = 1;
  uint8_t oePin      = 0;
#endif

/* ----------------------------------------------------------------------
Matrix initialization is explained EXTENSIVELY in "simple" example sketch!
It's very similar here, but we're passing "true" for the last argument,
enabling double-buffering -- this permits smooth animation by having us
draw in a second "off screen" buffer while the other is being shown.
------------------------------------------------------------------------- */

Adafruit_Protomatter matrix(
  192,          // Matrix width in pixels
  4,           // Bit depth -- 6 here provides maximum color options
  1, rgbPins,  // # of matrix chains, array of 6 RGB pins for each
  5, addrPins, // # of address pins (height is inferred), array of pins
  clockPin, latchPin, oePin, // Other matrix control pins
  true);       // HERE IS THE MAGIC FOR DOUBLE-BUFFERING!

// Sundry globals used for animation ---------------------------------------

int16_t  textX;        // Current text position (X)
int16_t  textY;        // Current text position (Y)
int16_t  textMin;      // Text pos. (X) when scrolled off left edge
char     str[64];      // Buffer to hold scrolling message text
int16_t  ball[3][4] = {
  {  3,  0,  1,  1 },  // Initial X,Y pos+velocity of 3 bouncy balls
  { 17, 15,  1, -1 },
  { 27,  4, -1,  1 }
};
uint16_t ballcolor[3]; // Colors for bouncy balls (init in setup())

// SETUP - RUNS ONCE AT PROGRAM START --------------------------------------

void setup(void) {
  Serial.begin(9600);

  // Initialize matrix...
  ProtomatterStatus status = matrix.begin();
  Serial.print("Protomatter begin() status: ");
  Serial.println((int)status);
  if(status != PROTOMATTER_OK) {
    // DO NOT CONTINUE if matrix setup encountered an error.
    for(;;);
  }

  // Unlike the "simple" example, we don't do any drawing in setup().
  // But we DO initialize some things we plan to animate...

  // Set up the scrolling message...
  sprintf(str, "Adafruit %dx%d RGB LED Matrix",
    matrix.width(), matrix.height());
  matrix.setFont(&FreeSansBold18pt7b); // Use nice bitmap font
  matrix.setTextWrap(false);           // Allow text off edge
  matrix.setTextSize(2);               // fix for height 64
  matrix.setTextColor(0xFFFF);         // White
  int16_t  x1, y1;
  uint16_t w, h;
  matrix.getTextBounds(str, 0, 0, &x1, &y1, &w, &h); // How big is it?
  textMin = -w; // All text is off left edge when it reaches this point
  textX = matrix.width(); // Start off right edge
  textY = matrix.height() / 2 - (y1 + h / 2); // Center text vertically
  // Note: when making scrolling text like this, the setTextWrap(false)
  // call is REQUIRED (to allow text to go off the edge of the matrix),
  // AND it must be BEFORE the getTextBounds() call (or else that will
  // return the bounds of "wrapped" text).

  // Set up the colors of the bouncy balls.
  ballcolor[0] = matrix.color565(0, 20, 0); // Dark green
  ballcolor[1] = matrix.color565(0, 0, 20); // Dark blue
  ballcolor[2] = matrix.color565(20, 0, 0); // Dark red
}

// LOOP - RUNS REPEATEDLY AFTER SETUP --------------------------------------

void loop(void) {
  // Every frame, we clear the background and draw everything anew.
  // This happens "in the background" with double buffering, that's
  // why you don't see everything flicker. It requires double the RAM,
  // so it's not practical for every situation.

  matrix.fillScreen(0); // Fill background black

  // Draw the big scrolly text
  matrix.setCursor(textX, textY);
  matrix.print(str);

  // Update text position for next frame. If text goes off the
  // left edge, reset its position to be off the right edge.
  if((--textX) < textMin) textX = matrix.width();

  // Draw the three bouncy balls on top of the text...
  for(byte i=0; i<3; i++) {
    // Draw 'ball'
    matrix.fillCircle(ball[i][0], ball[i][1], 5, ballcolor[i]);
    // Update ball's X,Y position for next frame
    ball[i][0] += ball[i][2];
    ball[i][1] += ball[i][3];
    // Bounce off edges
    if((ball[i][0] == 0) || (ball[i][0] == (matrix.width() - 1)))
      ball[i][2] *= -1;
    if((ball[i][1] == 0) || (ball[i][1] == (matrix.height() - 1)))
      ball[i][3] *= -1;
  }

  // AFTER DRAWING, A show() CALL IS REQUIRED TO UPDATE THE MATRIX!

  matrix.show();

  delay(20); // 20 milliseconds = ~50 frames/second
}