Problem with neopixel matrix

Hello.

I'm working on a game project based that uses NeoPixel Matrix 16x16 [knockoff] as a screen.

Most of the time it works, unless the loop starting at //#SHIPS runs.
When that double loop (two for loops traversing a two-dimensional array of color indexes, essentially a bitmap) works, the matrix stops updating (e.g. I change colors in the static border).

The code is quite entangled, but feel free to take a look at the loop.
Using logs I'm sure the data I try to display is fine, here is a sample of the displayMatrix, which corresponds to colors in gameColors:

updateShipPosition xy00

0 2100000000
1 1100000000
2 0000000000
3 0000000000
4 0000000000
5 0000000000
6 0000000000
7 0000000000
8 0000000000
9 0000000000

The code:

#include <Adafruit_GFX.h>
#include <Adafruit_NeoMatrix.h>
#include <Adafruit_NeoPixel.h>
#include <Fonts/TomThumb.h>
#ifndef PSTR
 #define PSTR // Make Arduino Due happy
#endif

#define MTRX_PIN 8
#define BTN_A_PIN 3
#define J_X A1
#define J_Y A0
#define J_SW 7

Adafruit_NeoMatrix matrix = Adafruit_NeoMatrix(16, 16, MTRX_PIN ,
  NEO_MATRIX_TOP + NEO_MATRIX_LEFT +
  NEO_MATRIX_COLUMNS + NEO_MATRIX_ZIGZAG,
  NEO_GRB            + NEO_KHZ800);

const uint16_t colors[7] = {
  // 0 red
  matrix.Color(255, 0, 0),
  // 1 green
  matrix.Color(0, 255, 0),
  // 2 blue
  matrix.Color(0, 0, 255),
  // 3 border
  matrix.Color(100, 120, 100),
  // 4 enemy
  matrix.Color(230, 57, 70),
  // 5 friendly
  matrix.Color(80, 140, 207),
  // 6 margin
  matrix.Color(115, 27, 35),
};

const short CELL_EMPTY = 0;
const short CELL_BORDER = 1;
const short CELL_SHIP = 2;
const short CELL_HIT = 3;
const short CELL_MISS = 5;

short GAME_MODE = 0;
const short GAME_SET_SHIPS = 1;
const short GAME_PLAYER_TURN = 2;
const short GAME_ENEMY_TURN = 3;
const short GAME_END = 4;

const uint16_t gameColors[6] = {
  // 0 empty
  matrix.Color(100, 0, 0),
  // 1 border
  matrix.Color(0, 10, 0),
  // 2 ship
  matrix.Color(80, 140, 207),
  // 3 hit or invalid
  matrix.Color(230, 57, 70),
  // 4 hit or invalid
  matrix.Color(230, 57, 70),
  // 5 miss
  matrix.Color(255, 255, 0),
};

const int OFFSET = 3;
const int JOYSTICK_AXIS_DELAY = 30;

bool btn_a = false;

int j_read_x = 0;
int j_read_y = 0;
int j_read_sw = 0;

int x = 0;
int y = 0;

bool joystickValUpdated = false;
bool shipVertical = false;

bool transition = false;
String text[10] = "";
short textIndex = 0;
// const String texts[5] = {
//   "",
//   "GO!",
//   "FOE",
//   "HIT",
//   "MISS"
// };
const short TEXT_GO = 1;
const short TEXT_FOE = 2;
const short TEXT_HIT = 3;
const short TEXT_MISS = 4;

bool logShipPosition = false;

//// Ships Matrix
// All cells have a number with meaning
// 0 - empty
// 1 - ship border (transparent blue)
// 2 - ship (blue)
// 3 - invalid ship position or hit (red)
// 4 - invalid ship position or hit (red)
short ownShips[10][10] = {};
short enemyShips[10][10] = {};
short ownShots[10][10] = {};
short displayMatrix[10][10] = {};
// temp
short newShipMargin[10][10] = {};
short currentNewShipIndex = 9;
short ownShipsHitPoints[10][2] = {
  {4, 4},
  {3, 3},
  {3, 3},
  {2, 2},
  {2, 2},
  {2, 2},
  {1, 1},
  {1, 1},
  {1, 1},
  {1, 1},
};

bool newShipPositionValid = true;

void fillNewShip() {
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int endX = shipVertical ? x : x - 1 + shipSize;
  int endY = !shipVertical ? y : y - 1 + shipSize;
  if (shipVertical) {
    for (short i = y; i <= endY; i++) {
      ownShips[i][x] = CELL_SHIP;
    }
  } else {
    for (short i = x; i <= endX; i++) {
      ownShips[y][i] = CELL_SHIP;
      Serial.println("fillNewShip");
    }
  }

  currentNewShipIndex++;
  Serial.print("currentNewShipIndex: ");
  Serial.println(currentNewShipIndex);
  if (currentNewShipIndex > 9) {
    GAME_MODE = GAME_PLAYER_TURN;
    transition = true;
    textIndex = TEXT_GO;
    Serial.println("GAME_MODE: GAME_PLAYER_TURN");
    currentNewShipIndex = 0;
  }
}

void rotateShip() {
  shipVertical = !shipVertical;
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int startX = x;
  int startY = y;
  int endX = shipVertical ? startX : startX - 1 + shipSize;
  int endY = !shipVertical ? startY : startY - 1 + shipSize;

  Serial.println("rotateShip; vertical " + (String)shipVertical);
  if (shipVertical) {
    if (endY > 9) {
      y = y - 1;
    }
  } else {
    if (endX > 9) {
      x = x - 1;
    }
  }
  updateShipPosition();
}

// updates position of a ship before it being added
void updateShipPosition() {
  // Serial.println("updateShipPosition index "  + currentNewShipIndex);
  Serial.print("updateShipPosition xy");
  joystickValUpdated = false;
  // Serial.print(currentNewShipIndex);
  Serial.print(x);
  Serial.println(y);

  newShipPositionValid = true;
  short shipSize = ownShipsHitPoints[currentNewShipIndex][0];
  int startX = x;
  int startY = y;
  int endX = shipVertical ? startX : startX - 1 + shipSize;
  int endY = !shipVertical ? startY : startY - 1 + shipSize;

  // clean temp array
  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      newShipMargin[row][col] = CELL_EMPTY;
    }
  }
  if (shipVertical) {
    // vertical
    short lowEdgeX = max(startX - 1, 0);
    short highEdgeX = min(endX + 1, 9);
    short lowEdgeY = max(startY - 1, 0);
    short highEdgeY = min(startY + 1, 9);

    for (short i = lowEdgeY; i <= highEdgeY; i++) {
      newShipMargin[i][lowEdgeX] = CELL_BORDER;
      newShipMargin[i][highEdgeX] = CELL_BORDER;
    }
    newShipMargin[max(startY - 1, 0)][startX] = CELL_BORDER;
    newShipMargin[min(endY + 1, 9)][startX] = CELL_BORDER;

    // ship
    for (short i = startY; i <= endY; i++) {
      newShipMargin[i][startX] = CELL_SHIP;
    }
  } else {
    // horizontal
    short lowEdgeX = max(startX - 1, 0);
    short highEdgeX = min(endX + 1, 9);
    short lowEdgeY = max(startY - 1, 0);
    short highEdgeY = min(startY + 1, 9);

    for (short i = lowEdgeX; i <= highEdgeX; i++) {
      newShipMargin[lowEdgeY][i] = CELL_BORDER;
      newShipMargin[highEdgeY][i] = CELL_BORDER;
    }
    newShipMargin[startY][max(startX - 1, 0)] = CELL_BORDER;
    newShipMargin[startY][min(endX + 1, 9)] = CELL_BORDER;

    // ship
    for (short i = startX; i <= endX; i++) {
      newShipMargin[startY][i] = CELL_SHIP;
    }
  }

  for(short row = 0; row < 10; row++) {
    // Serial.println("");
    for(short col = 0; col < 10; col++) {
      short cell = ownShips[row][col];
      displayMatrix[row][col] = cell + newShipMargin[row][col];
      // Serial.print(displayMatrix[row][col]);
      if (displayMatrix[row][col] > 2) {
        newShipPositionValid = false;
      }
    }
  }

}

void addNewShip() {
  fillNewShip();
  updateShipPosition();
  delay(500);
}

int processCoordinateChange(int prevValue, int sensorValue, bool sizeByAxisMatters) {
  int newValue = prevValue;
  bool sizeMatters = sizeByAxisMatters && GAME_MODE == GAME_SET_SHIPS;

  if (sensorValue > 900) {
    // increase
    joystickValUpdated = true;
    int maxVal = 9;
    if (sizeMatters) {
      // one point doesn't matter
      maxVal = maxVal + 1 - ownShipsHitPoints[currentNewShipIndex][0];
    }
    newValue = min(++newValue, maxVal);
  } else if (sensorValue < 120) {
    // decrease
    joystickValUpdated = true;
    newValue = max(--newValue, 0);
  }
  return newValue;
}

void updateAimPosition() {
  Serial.println("updateAimPosition");
  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      displayMatrix[row][col] = CELL_EMPTY;
    }
  }
  displayMatrix[y][x] = CELL_HIT;
}

void shoot() {
  Serial.println("SHOT @ " + (String)x + (String)y);
  return;
  if (ownShots[y][x] == CELL_HIT || ownShots[y][x] == CELL_MISS)  {
    // can't shoot on previously shot
    Serial.println("SHOT NOT EMPTY" + (String)ownShots[y][x]);
    return;
  }
  if (enemyShips[y][x] == CELL_SHIP)  {
    ownShots[y][x] = CELL_HIT;
    transition = true;
    textIndex = TEXT_HIT;
    Serial.println("HIT!");
    // player turn continues
  } else {
    ownShots[y][x] = CELL_MISS;
    transition = true;
    textIndex = TEXT_MISS;
    Serial.println("MISS!");
    // enemy turn
  }

  for(short row = 0; row < 10; row++) {
    for(short col = 0; col < 10; col++) {
      short cell = 0 + ownShots[row][col];
      displayMatrix[row][col] = cell;
    }
  }
}

void joystickPosUpdated() {
  if (GAME_MODE == GAME_SET_SHIPS) {
    updateShipPosition();
  } else {
    updateAimPosition();
  }
}

void joystickPressed() {
  if (GAME_MODE == GAME_SET_SHIPS) {
    if (newShipPositionValid) {
      addNewShip();
    }
  } else if (GAME_MODE == GAME_PLAYER_TURN) {
    shoot();
  }
}

void btnAPressed() {
  Serial.println("BTN A PRESSED");
  if (GAME_MODE == GAME_SET_SHIPS) {
    rotateShip();
  }

}

void btnAReleased() {
  Serial.println("BTN A RELEASED");

}

void setup() {
  Serial.begin(9600);
  matrix.begin();
  matrix.setFont(&TomThumb);
  matrix.setTextWrap(false);
  matrix.setBrightness(3);
  matrix.setTextColor(colors[0]);
  matrix.setCursor(0, 0);
  GAME_MODE = GAME_SET_SHIPS;
  pinMode(J_SW, INPUT_PULLUP);
  pinMode(BTN_A_PIN, INPUT);
  updateShipPosition();

  // test enemy ships
  enemyShips[0][0] = CELL_SHIP;
  enemyShips[3][3] = CELL_SHIP;
  enemyShips[5][5] = CELL_SHIP;

  matrix.fillScreen(0);
}

void loop() {
  if (joystickValUpdated) {
    joystickPosUpdated();
    logShipPosition = true;
  }

  bool btnAVal = digitalRead(BTN_A_PIN);
  if (btnAVal && !btn_a) {
    btn_a = true;
    btnAPressed();
  } else if (!btnAVal && btn_a) {
    btn_a = false;
    btnAReleased();
  }

  j_read_sw = digitalRead(J_SW);
  if (j_read_sw == LOW) {
    joystickPressed();
  } else {
    if (joystickValUpdated) {
      joystickValUpdated = false;
      delay(JOYSTICK_AXIS_DELAY);
    } else {
      j_read_x = analogRead(J_X);
      j_read_y = analogRead(J_Y);
      x = processCoordinateChange(x, j_read_x, !shipVertical);
      y = processCoordinateChange(y, j_read_y, shipVertical);
    }
  }

  // top left red
  matrix.drawPixel(0, 0, colors[0]);
  // bottom right green
  matrix.drawPixel(15, 15, colors[1]);
  // bottom left blue
  matrix.drawPixel(0, 15, colors[2]);

  if (transition == true) {
    Serial.println("TRANSITION");
    if (textIndex > 0) {
      Serial.println("TRANSITION TEXT: " + textIndex);
      matrix.setCursor(3, 8);
      matrix.setTextSize(0);
      matrix.setTextColor(gameColors[2]);
      matrix.print("GO!");
      matrix.show();
      delay(2500);
    }
    transition = false;
  } else {
    // border
    matrix.drawRect(0 + OFFSET - 1, 0 + OFFSET - 1, 12, 12, colors[0]);
    if (logShipPosition) {
      //#SHIPS
      for(int row = 0; row < 10; row++) {
        if (logShipPosition) {
          Serial.println("");
          Serial.print(row);
          Serial.print(" ");
        }
        for(int col = 0; col < 10; col++) {
          int cell = 0 + displayMatrix[row][col];
          matrix.drawPixel(col + OFFSET, row + OFFSET, gameColors[cell]);
          if (logShipPosition) {
            Serial.print(displayMatrix[row][col]);
          }
        }
      }
      logShipPosition = false;
    }
    matrix.show();
  }
}

If anyone has an idea, I'd be very grateful.

Serial.println("Updating matrix.");
matrix.show();

Check that the matrix is getting updated when you want it to be.

Thank you, I checked that, the matrix.show() is running every time I expect it to run.

So you are seeing "Updating matrix" on serial monitor but the LEDs do not change"? What are you expecting to change, for example?

I didn't quite get what you meant by this. If the colours are static, they could only change when you upload a new sketch with a different colour specified. Otherwise, they are indeed static, so you couldn't use them to know if the matrix had been updated.

I'm seeing it on serial monitor, but LEDs do not change, that's correct.

I have a line abobe those for loops to draw a rectangle and when I change its colors, it won't update, unless I comment out the loops..
matrix.drawRect(0 + OFFSET - 1, 0 + OFFSET - 1, 12, 12, colors[0]);

I tried to copy your project in the Wokwi simulator, but the NeoPixels stay black. Can you check if my of the buttons is like yours ?
https://wokwi.com/arduino/projects/300458625064763917
I have set the brightness to 255, that is the only change.

The NeoPixels require a data signal that is 0V or 5V. Do you use a ESP32 or Due ? Then you might need a level shifter.

According to the official Arduino reference, the digitalRead() returns a HIGH or a LOW: https://www.arduino.cc/reference/en/language/functions/digital-io/digitalread/.
I prefer for myself to use HIGH/LOW and not true/false or 1/0.

Thank you for trying.

I use Uno, so it provides a 5V signal. As I mentioned the matrix displays almost everything I want, but for that displayMatrix loop.

Ok, I will pay attention, but usually it works with 1/0 as well.

In your demo looks like everything is connected properly, apart maybe from the button, I have a pulldown resistor on breadboard for it.

Anyway I couldn't make it work in a simulator, even by commenting out the matrix loop that I mentioned above.

Today, it always works. But some time in the future, the Arduino language may change and HIGH and LOW will no longer be 1/0. I hope this never happens but I have heard rumours that it might. So it's a good idea to be in the habit of using HIGH/LOW.

1 Like

Yes, I saw that. But my point is, that rectangle is always the same colour, so how do you know it is not being redrawn?

When I change the color and reupload the sketch.

You said that the border does not get updated if the nested for-loops do not run? They don't run if logShipPosition is not true. So you are saying that the border does not get updated if logShipPosition is false? I don't see how.

Re-reading your code again, I noticed this:

const uint16_t colors[7] = {
  // 0 red
  matrix.Color(255, 0, 0),

uint16_t is not large enough to hold the result of matrix.Color(). You should be using uint32_t here and for gameColors, and any other variable holding a value that came from matrixColor().

1 Like

Thank you for the suggestion, but I tried to use colors[0] which is a plain red color, unit16_t should be enough for it.

I meant that after changing the border color, and moving joystick which sets logShipPosition to true, I still saw the border color from the sketch version.

Anyway, I now think the problem is not in the for loops, but rather in the updateShipPosition() function. After I commented it out in the loop, the border and other things started updating.
Will debug it more.

Why?

I expect that color value increases proportionally, so (255, 0, 0) should be much less than (255, 255, 255).

Anyway, I changed those arrays to uint32_t.

You would expect incorrectly. The data is read across all the bytes, it matters not one jot if the actual bits are zero or one. You are probably reading outside the array bounds which is why you see no change.

const uint16_t colors[] = {
  matrix.Color(255, 0, 0), matrix.Color(0, 255, 0), matrix.Color(0, 0, 255) };

void setup() {
  matrix.begin();
  matrix.setTextWrap(false);
  matrix.setBrightness(40);
  matrix.setTextColor(colors[0]);
...

this is from the example sketch "tiletest.ino" of library NeoMatrix . do u mean developer "Lady Ada & Co" make a mistake?

Sorry my bad, I was confusing the normal Neopixel library with the matrix one, the matrix color method must be returning a number.

Here >

Gamma Correction
Because the Adafruit_GFX library was originally designed for LCDs (having limited color fidelity), it handles colors as 16-bit values (rather than the full 24 bits that NeoPixels are capable of). This is not the big loss it might seem. A quirk of human vision makes bright colors less discernible than dim ones. The Adafruit_NeoMatrix library uses gamma correction to select brightness levels that are visually (though not numerically) equidistant. There are 32 levels for red and blue, 64 levels for green.

The Color() function performs the necessary conversion; you don’t need to do any math. It accepts 8-bit red, green and blue values, and returns a gamma-corrected 16-bit color that can then be passed to other drawing functions.

The two alternatives for "HIGH" are 1 and -1. There are no others.

"LOW" will always be zero. :grin: