ESP32 S3 UNO - LED Matrix

This ESP32 S3 Uno LED matrix tutorial shows you how to connect a MAX7219 dot matrix display to the ESP32 S3 Uno form board and control it with Arduino code. You will drive an 8x8 LED matrix module or a 32x8 LED matrix display, and show text, numbers and scrolling messages on it. The same code also works for 16x8 or 64x8 sizes after one small change.

In this tutorial, you will:

  1. Wire an 8x8 and a 32x8 FC-16 LED matrix to the ESP32 S3 Uno
  2. Install the MD_Parola and MD_MAX72XX libraries in Arduino IDE
  3. Show left, center, right and inverted text and numbers on the dot matrix display
  4. Make a long message scroll across the LED display
  5. Change the code for other matrix sizes
ESP32 S3 Uno LED matrix

This page is about external LED matrix modules. The ESP32 S3 Uno has no built-in LED matrix, so you need one of the modules listed below.

Hardware Preparation

1×ESP32 S3 Uno-form Board
1×USB Cable Type-A to Type-C (for USB-A PC)
1×USB Cable Type-C to Type-C (for USB-C PC)
1×FC-16 LED Matrix 32x8
1×FC-16 LED Matrix 8x8
1×Jumper Wires
1×DC Power Jack
1×5V Power Adapter

Or you can buy the following kits:

1×DIYables Sensor Kit (18 sensors/displays)
Disclosure: Some of the links provided in this section are Amazon affiliate links. We may receive a commission for any purchases made through these links at no additional cost to you.
Additionally, some of these links are for products from our own brand, DIYables .

Overview of LED Matrix

LED Matrix display

There are many kinds of LED matrix, but the one most makers use is the matrix driven by the MAX7219 chip. It is cheap, easy to chain, and has good libraries, so it is a great match for the ESP32 S3 Uno.

Blocks

The basic unit of this display is called a block. One block is a grid of 8x8 LEDs (64 LEDs in total), and one MAX7219 chip controls it. Blocks come in two main styles: the generic module and the FC-16 module. The code must know which style you have.

Chaining blocks

A display can be a single block, or many blocks linked in a chain. You can buy ready-made displays with 4 or 8 blocks already joined, or you can buy single blocks and connect them yourself to get the size you want. Either way, you tell your ESP32 S3 Uno code how many blocks are in the chain.

Pinout

LED Matrix Pinout

Each block has two groups of pins: an input group that takes data in, and an output group that passes data to the next block. Knowing the two groups makes chaining easy.

Input pins

VCC goes to 5V and GND goes to GND. DIN is the data pin, and it connects to the SPI MOSI pin of the ESP32 S3 Uno. CS (Chip Select) can go to any digital pin. CLK is the clock pin, and it connects to the SPI clock (SCK) pin.

Output pins

VCC, GND, CS and CLK connect to the same pins on the next block. DOUT (Data Out) connects to the DIN pin of the next block.

ESP32 S3 Uno Pinout

The image below shows the pinout of the ESP32 S3 Uno form board. Use it to find the Uno header pins (D9, D11, D13…) and their GPIO numbers while you wire the LED matrix.

ESP32 S3 Uno pinout diagram

Wiring Diagram

How you wire the display depends on how many blocks it has and how they are joined. In every case, only the input group of the first block connects to the ESP32 S3 Uno.

Single block (8x8)

Connect the input pin group to the ESP32 S3 Uno and leave the output pin group free.

The wiring diagram between ESP32 S3 Uno 8x8 LED matrix FC-16

This image is created using Fritzing. Click to enlarge image

The wiring diagram between ESP32 S3 Uno 8x8 LED matrix generic

This image is created using Fritzing. Click to enlarge image

Ready-made multi-block display (32x8)

Connect the input pin group to the ESP32 S3 Uno and leave the output pin group free.

The wiring diagram between ESP32 S3 Uno LED matrix display

This image is created using Fritzing. Click to enlarge image

Display built from single blocks

  1. Connect the input pin group of the first block to the ESP32 S3 Uno.
  2. Connect the output pin group of each block to the input pin group of the next block.
  3. Leave the output pin group of the last block free.
ESP32 S3 Uno 32x8 LED matrix wiring FC-16 diagram

This image is created using Fritzing. Click to enlarge image

ESP32 S3 Uno 32x8 LED matrix wiring generic diagram

This image is created using Fritzing. Click to enlarge image

LED Matrix Pin ESP32 S3 Uno Pin
VCC 5V (external 5V supply)
GND GND
DIN D11 (GPIO11, SPI MOSI)
CS D9 (GPIO46)
CLK D13 (GPIO12, SPI SCK)

Power

The display can draw up to 1 A at full brightness. That is too much for the 5V pin of the ESP32 S3 Uno, so use an external 5V power supply instead. One 5V adapter can power both the LED matrix and the ESP32 S3 Uno. Always connect the GND of the supply, the matrix and the board together.

SPI pins

The display talks to the ESP32 S3 Uno over SPI. DIN goes to D11 (GPIO11) and CLK goes to D13 (GPIO12), which are the default SPI MOSI and SCK pins, so the library needs no pin change. CS can be any free digital pin; this tutorial uses D9 (GPIO46).

WARNING

D9 (GPIO46) is a boot strapping pin on the ESP32 S3 Uno. The board reads it at power-up to pick the boot mode. The CS line of the matrix is normally fine here, but if your board does not boot or will not upload code while the matrix is connected, unplug the CS wire, upload, and plug it back in. You can also move CS to another free pin and change CS_PIN in the code.

WARNING

The ESP32 S3 Uno uses 3.3V logic, and its pins are NOT 5V tolerant. The signals here only go from the board to the matrix, so the board is safe. A MAX7219 powered at 5V usually accepts 3.3V signals, but if the display shows random dots or stays blank, add a 3.3V to 5V logic level shifter on DIN, CS and CLK.

How To Program For LED Matrix

Driving a MAX7219 matrix by hand takes a lot of code. The MD_Parola and MD_MAX72XX libraries do the hard part for you, so your sketch only needs a few lines. Here is what each part of the setup looks like.

  1. Include the two libraries.
#include <MD_Parola.h> #include <MD_MAX72xx.h>
  1. Pick your hardware type: GENERIC_HW or FC16_HW.
#define HARDWARE_TYPE MD_MAX72XX::FC16_HW
  1. Set the number of blocks. A 4-in-1 display has 4 blocks.
#define MAX_DEVICES 4
  1. Set the ESP32 S3 Uno pin that drives the CS pin. Here it is D9, which is GPIO46. Always write the GPIO number in code.
#define CS_PIN 46
  1. Create an MD_Parola object for the display.
MD_Parola ledMatrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES);
  1. Start the display in setup().
void setup() { ledMatrix.begin(); // Initialize the LED matrix ledMatrix.setIntensity(0); // Set the display brightness (range 0-15) ledMatrix.displayClear(); // Clear the display screen }

After that, you can show text, numbers and effects. The next sections show full examples.

ESP32 S3 Uno - LED Matrix Code

This sketch is made for a 32x8 FC-16 display with four blocks. It shows "Left", "Center" and "Right" with matching alignment, then inverted text, then a number, two seconds each. For an 8x8, 16x8 or 64x8 display, just change MAX_DEVICES to 1, 2 or 8.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-led-matrix */ #include <MD_Parola.h> #include <MD_MAX72xx.h> #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 // 4 blocks #define CS_PIN 46 // The ESP32 S3 Uno pin (D9) connected to the CS pin of LED matrix // create an instance of the MD_Parola class MD_Parola ledMatrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); void setup() { ledMatrix.begin(); // initialize the object ledMatrix.setIntensity(15); // set the brightness of the LED matrix display (from 0 to 15) ledMatrix.displayClear(); // clear LED matrix display } void loop() { ledMatrix.setTextAlignment(PA_LEFT); ledMatrix.print("Left"); // display text delay(2000); ledMatrix.setTextAlignment(PA_CENTER); ledMatrix.print("Center"); // display text delay(2000); ledMatrix.setTextAlignment(PA_RIGHT); ledMatrix.print("Right"); // display text delay(2000); ledMatrix.setTextAlignment(PA_CENTER); ledMatrix.setInvert(true); ledMatrix.print("Invert"); // display text inverted delay(2000); ledMatrix.setInvert(false); ledMatrix.print(1234); // display number delay(2000); }

Detailed Instructions

  1. New to the ESP32 S3 Uno? Follow ESP32 S3 Uno - Getting Started first.
  2. Wire it up as shown in the diagram.
  3. Connect the board to your computer with a USB Type-C cable.
  4. Open Arduino IDE, choose the ESP32S3 Dev Module board and the correct COM port.
  5. Open the Library Manager by clicking the Libraries icon on the left bar.
  6. Install MD_Parola: search for "MD_Parola", find the MD_Parola library and click Install.
  • Search for MD_Parola created by majicDesigns and click the Install button.
∞
Newbiely | Arduino IDE 2.3.8
──
☐
✕
File
Edit
Sketch
Tools
Help
ESP32S3 Dev Module
Library Manager
Type:
All
Topic:
All
MD_Parola by majicDesigns
Implemented using the MD_MAX72xx library for hardware control. Provides functions to simplify the implementation of text special effects on the LED matrix. More info
3.7.2
INSTALL
Newbiely.ino
···
1 void setup() {
Output
Serial Monitor
Ln 1, Col 1
ESP32S3 Dev Module on COM15
1
  1. Install the dependency: when asked to install MD_MAX72XX too, click Install All.
  2. Copy the code above and paste it into Arduino IDE.
  3. Upload the code by clicking the Upload button.
  4. Watch the display cycle through the text and the number.
  5. Tip: if the letters look mirrored or upside down, change FC16_HW to GENERIC_HW (or the other way round) and upload again.

ESP32 S3 Uno LED Matrix Code – Scrolling Text

A long message does not fit on a 32x8 display at once. The scrolling effect fixes that: the text slides across the screen from right to left, so you can show a message of any length.

This sketch scrolls "Hello, DIYables" across the display. The displayAnimate() call moves the text one step each time loop() runs, and displayReset() starts the scroll again when it reaches the end.

/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-led-matrix */ #include <MD_Parola.h> #include <MD_MAX72xx.h> #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 // 4 blocks #define CS_PIN 46 // The ESP32 S3 Uno pin (D9) connected to the CS pin of LED matrix // create an instance of the MD_Parola class MD_Parola ledMatrix = MD_Parola(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); void setup() { ledMatrix.begin(); // initialize the object ledMatrix.setIntensity(15); // set the brightness of the LED matrix display (from 0 to 15) ledMatrix.displayClear(); // clear led matrix display ledMatrix.displayScroll("Hello, DIYables", PA_CENTER, PA_SCROLL_LEFT, 100); } void loop() { if (ledMatrix.displayAnimate()) { ledMatrix.displayReset(); } }

For more text effects, see the MD_Parola Library Reference.

Video Tutorial

Watch the video below to see this ESP32 S3 Uno project step by step.

The demo in the video is a falling-blocks game on a 32x8 FC-16 display. Three buttons move the piece left, move it right, and rotate it (hold the rotate button for a fast drop). A buzzer plays sounds for moves, cleared lines and game over. The buttons use the internal pull-up resistors.

Part ESP32 S3 Uno Pin
LED matrix CS D9 (GPIO46)
LED matrix DIN D11 (GPIO11)
LED matrix CLK D13 (GPIO12)
Left button D2 (GPIO18)
Right button D3 (GPIO17)
Rotate button D4 (GPIO19)
Buzzer D6 (GPIO3)
/* * This ESP32 S3 Uno code was developed by newbiely.com * * This ESP32 S3 Uno code is made available for public use without any restriction * * For comprehensive instructions and wiring diagrams, please visit: * https://newbiely.com/tutorials/esp32-s3-uno/esp32-s3-uno-led-matrix */ #include <MD_MAX72xx.h> #include <SPI.h> // ============================================================ // MAX7219 // ============================================================ #define HARDWARE_TYPE MD_MAX72XX::FC16_HW #define MAX_DEVICES 4 #define CS_PIN 46 // D9 on the ESP32 S3 Uno MD_MAX72XX mx = MD_MAX72XX(HARDWARE_TYPE, CS_PIN, MAX_DEVICES); // ============================================================ // BUTTONS // ============================================================ #define BTN_LEFT 18 // D2 #define BTN_RIGHT 17 // D3 #define BTN_ROTATE 19 // D4 #define BUZZER_PIN 3 // D6 // ============================================================ // GAME BOARD // ============================================================ #define BOARD_W 8 #define BOARD_H 32 bool board[BOARD_H][BOARD_W]; // ============================================================ // TIMING // ============================================================ unsigned long lastFallTime = 0; const unsigned long NORMAL_FALL = 700; const unsigned long SOFT_DROP_FALL = 80; #define LONG_PRESS_TIME 400 // ============================================================ // SCORE // ============================================================ unsigned long score = 0; unsigned int lines = 0; unsigned int level = 1; unsigned long highScore = 0; // ============================================================ // GAME STATE // ============================================================ bool gameOver = false; bool gameStarted = false; // ============================================================ // TETROMINO DEFINITIONS // ============================================================ struct Point { int x; int y; }; struct Tetromino { Point block[4]; uint8_t color; }; Tetromino pieces[7] = { // I { {{0,0}, {1,0}, {2,0}, {3,0}}, 0 }, // O { {{0,0}, {1,0}, {0,1}, {1,1}}, 1 }, // T { {{1,0}, {0,1}, {1,1}, {2,1}}, 2 }, // L { {{0,0}, {0,1}, {0,2}, {1,2}}, 3 }, // J { {{1,0}, {1,1}, {1,2}, {0,2}}, 4 }, // S { {{1,0}, {2,0}, {0,1}, {1,1}}, 5 }, // Z { {{0,0}, {1,0}, {1,1}, {2,1}}, 6 } }; // ============================================================ // CURRENT PIECE // ============================================================ int currentPiece; int currentX; int currentY; int rotationState = 0; // ============================================================ // BUTTON STATE // ============================================================ bool lastLeftState = HIGH; bool lastRightState = HIGH; bool lastRotateState = HIGH; unsigned long rotatePressTime = 0; bool softDrop = false; bool rotateHandled = false; // ============================================================ // DISPLAY COORDINATE MAPPING // ============================================================ void setPixel(int x, int y, bool state) { if (x < 0 || x >= BOARD_W || y < 0 || y >= BOARD_H) { return; } // Reverse vertical direction int physicalColumn = (BOARD_H - 1) - y; int physicalRow = x; mx.setPoint( physicalRow, physicalColumn, state ); } // ============================================================ // CLEAR DISPLAY // ============================================================ void clearDisplay() { mx.clear(); } // ============================================================ // DRAW BOARD // ============================================================ void drawBoard() { clearDisplay(); // Draw fixed blocks for (int y = 0; y < BOARD_H; y++) { for (int x = 0; x < BOARD_W; x++) { if (board[y][x]) { setPixel( x, y, true ); } } } // Draw falling piece drawCurrentPiece(); } // ============================================================ // GET ROTATED BLOCK POSITION // ============================================================ Point getRotatedPoint( Point p, int rotation ) { Point r; switch (rotation % 4) { case 0: r.x = p.x; r.y = p.y; break; case 1: r.x = -p.y; r.y = p.x; break; case 2: r.x = -p.x; r.y = -p.y; break; case 3: r.x = p.y; r.y = -p.x; break; } return r; } // ============================================================ // CHECK COLLISION // ============================================================ bool checkCollision( int pieceX, int pieceY, int rotation ) { for (int i = 0; i < 4; i++) { Point p = getRotatedPoint( pieces[currentPiece].block[i], rotation ); int x = pieceX + p.x; int y = pieceY + p.y; // Left / Right wall if (x < 0 || x >= BOARD_W) { return true; } // Bottom if (y >= BOARD_H) { return true; } // Existing block if (y >= 0 && board[y][x]) { return true; } } return false; } // ============================================================ // DRAW CURRENT PIECE // ============================================================ void drawCurrentPiece() { for (int i = 0; i < 4; i++) { Point p = getRotatedPoint( pieces[currentPiece].block[i], rotationState ); int x = currentX + p.x; int y = currentY + p.y; if (x >= 0 && x < BOARD_W && y >= 0 && y < BOARD_H) { setPixel( x, y, true ); } } } // ============================================================ // RANDOM PIECE // ============================================================ void spawnPiece() { currentPiece = random(0, 7); rotationState = 0; currentX = 3; currentY = 0; // I piece if (currentPiece == 0) { currentX = 2; } // Game over if piece cannot spawn if (checkCollision( currentX, currentY, rotationState)) { gameOver = true; if (score > highScore) { highScore = score; } soundGameOver(); } } // ============================================================ // LOCK PIECE // ============================================================ void lockPiece() { for (int i = 0; i < 4; i++) { Point p = getRotatedPoint( pieces[currentPiece].block[i], rotationState ); int x = currentX + p.x; int y = currentY + p.y; if (x >= 0 && x < BOARD_W && y >= 0 && y < BOARD_H) { board[y][x] = true; } } soundLock(); // Check completed lines clearLines(); // New piece spawnPiece(); lastFallTime = millis(); } // ============================================================ // CLEAR LINES // ============================================================ void clearLines() { int cleared = 0; for (int y = BOARD_H - 1; y >= 0; y--) { bool full = true; for (int x = 0; x < BOARD_W; x++) { if (!board[y][x]) { full = false; break; } } if (full) { cleared++; // Move rows down for (int yy = y; yy > 0; yy--) { for (int x = 0; x < BOARD_W; x++) { board[yy][x] = board[yy - 1][x]; } } // Clear top row for (int x = 0; x < BOARD_W; x++) { board[0][x] = false; } // Check same row again y++; } } // Scoring if (cleared > 0) { lines += cleared; switch (cleared) { case 1: score += 100 * level; break; case 2: score += 300 * level; break; case 3: score += 500 * level; break; case 4: score += 800 * level; break; } // Increase level every 10 lines level = (lines / 10) + 1; soundLineClear(cleared); } } // ============================================================ // MOVE LEFT // ============================================================ void moveLeft() { if (gameOver) { return; } if (!checkCollision( currentX - 1, currentY, rotationState)) { currentX--; drawBoard(); soundLeft(); } } // ============================================================ // MOVE RIGHT // ============================================================ void moveRight() { if (gameOver) { return; } if (!checkCollision( currentX + 1, currentY, rotationState)) { currentX++; drawBoard(); soundRight(); } } // ============================================================ // ROTATE // ============================================================ void rotatePiece() { if (gameOver) { return; } int newRotation = (rotationState + 1) % 4; // Normal rotation if (!checkCollision( currentX, currentY, newRotation)) { rotationState = newRotation; drawBoard(); return; } // Wall kick LEFT if (!checkCollision( currentX - 1, currentY, newRotation)) { currentX--; rotationState = newRotation; drawBoard(); return; } // Wall kick RIGHT if (!checkCollision( currentX + 1, currentY, newRotation)) { currentX++; rotationState = newRotation; drawBoard(); return; } } // ============================================================ // MOVE DOWN // ============================================================ bool moveDown() { if (gameOver) { return false; } if (!checkCollision( currentX, currentY + 1, rotationState)) { currentY++; return true; } // Cannot move further lockPiece(); return false; } // ============================================================ // SOFT DROP // ============================================================ void handleSoftDrop() { if (gameOver) { return; } static unsigned long lastSoftDrop = 0; if (millis() - lastSoftDrop >= SOFT_DROP_FALL) { lastSoftDrop = millis(); if (moveDown()) { score += 1; } drawBoard(); } } // ============================================================ // NORMAL FALL // ============================================================ void handleNormalFall() { if (gameOver) { return; } unsigned long fallSpeed; fallSpeed = NORMAL_FALL - ((level - 1) * 50); if (fallSpeed < 100) { fallSpeed = 100; } if (millis() - lastFallTime >= fallSpeed) { lastFallTime = millis(); moveDown(); drawBoard(); } } // ============================================================ // BUTTON HANDLING // ============================================================ void handleButtons() { bool leftState = digitalRead(BTN_LEFT); bool rightState = digitalRead(BTN_RIGHT); bool rotateState = digitalRead(BTN_ROTATE); // ========================================================== // LEFT // ========================================================== if (leftState == LOW && lastLeftState == HIGH) { moveLeft(); } // ========================================================== // RIGHT // ========================================================== if (rightState == LOW && lastRightState == HIGH) { moveRight(); } // ========================================================== // ROTATE BUTTON PRESSED // ========================================================== if (rotateState == LOW && lastRotateState == HIGH) { rotatePressTime = millis(); rotateHandled = false; } // ========================================================== // ROTATE BUTTON HELD // ========================================================== if (rotateState == LOW) { if (!rotateHandled && millis() - rotatePressTime >= LONG_PRESS_TIME) { rotateHandled = true; softDrop = true; } } // ========================================================== // ROTATE BUTTON RELEASED // ========================================================== if (rotateState == HIGH && lastRotateState == LOW) { unsigned long pressDuration = millis() - rotatePressTime; // Short press = ROTATE if (pressDuration < LONG_PRESS_TIME && !rotateHandled) { rotatePiece(); soundRotate(); } // Stop soft drop softDrop = false; } lastLeftState = leftState; lastRightState = rightState; lastRotateState = rotateState; } // ============================================================ // START MUSIC // ============================================================ // // Arcade-style startup melody. // // No continuous tone. // Each note ends before the next note starts. // ============================================================ void playStartMusic() { clearDisplay(); // ---------------------------------------------------------- // Intro LED sweep // ---------------------------------------------------------- for (int y = 0; y < BOARD_H; y++) { for (int x = 0; x < BOARD_W; x++) { setPixel( x, y, true ); } // Melody notes during sweep if (y == 0) { tone(BUZZER_PIN, 523, 100); } if (y == 4) { tone(BUZZER_PIN, 659, 100); } if (y == 8) { tone(BUZZER_PIN, 784, 100); } if (y == 12) { tone(BUZZER_PIN, 1047, 150); } if (y == 18) { tone(BUZZER_PIN, 784, 100); } if (y == 22) { tone(BUZZER_PIN, 1047, 100); } if (y == 26) { tone(BUZZER_PIN, 1319, 180); } delay(18); } delay(100); clearDisplay(); // ---------------------------------------------------------- // READY melody // ---------------------------------------------------------- tone(BUZZER_PIN, 784, 120); delay(150); tone(BUZZER_PIN, 988, 120); delay(150); tone(BUZZER_PIN, 1175, 120); delay(150); tone(BUZZER_PIN, 1568, 250); delay(300); clearDisplay(); noTone(BUZZER_PIN); } // ============================================================ // LEFT SOUND // ============================================================ void soundLeft() { tone( BUZZER_PIN, 650, 70 ); } // ============================================================ // RIGHT SOUND // ============================================================ void soundRight() { tone( BUZZER_PIN, 850, 70 ); } // ============================================================ // ROTATE SOUND // ============================================================ void soundLock() { tone(BUZZER_PIN, 450, 120); delay(130); tone(BUZZER_PIN, 300, 100); } // ============================================================ // ROTATE SOUND // ============================================================ void soundRotate() { tone( BUZZER_PIN, 1200, 100 ); } // ============================================================ // LINE CLEAR SOUND // ============================================================ void soundLineClear(int count) { if (count == 1) { tone( BUZZER_PIN, 1000, 120 ); delay(150); tone( BUZZER_PIN, 1400, 160 ); } else if (count == 2) { tone( BUZZER_PIN, 1000, 100 ); delay(120); tone( BUZZER_PIN, 1300, 100 ); delay(120); tone( BUZZER_PIN, 1700, 180 ); } else { tone( BUZZER_PIN, 1200, 100 ); delay(120); tone( BUZZER_PIN, 1500, 100 ); delay(120); tone( BUZZER_PIN, 1800, 100 ); delay(120); tone( BUZZER_PIN, 2200, 250 ); } } // ============================================================ // GAME OVER SOUND // ============================================================ void soundGameOver() { tone( BUZZER_PIN, 700, 180 ); delay(210); tone( BUZZER_PIN, 500, 180 ); delay(210); tone( BUZZER_PIN, 350, 220 ); delay(250); tone( BUZZER_PIN, 220, 450 ); delay(500); noTone(BUZZER_PIN); } // ============================================================ // GAME OVER ANIMATION // ============================================================ void gameOverAnimation() { for (int i = 0; i < 4; i++) { drawBoard(); delay(250); clearDisplay(); delay(250); } } // ============================================================ // RESET GAME // ============================================================ void resetGame() { for (int y = 0; y < BOARD_H; y++) { for (int x = 0; x < BOARD_W; x++) { board[y][x] = false; } } score = 0; lines = 0; level = 1; gameOver = false; spawnPiece(); lastFallTime = millis(); drawBoard(); } // ============================================================ // SETUP // ============================================================ void setup() { Serial.begin(115200); // Buttons pinMode( BTN_LEFT, INPUT_PULLUP ); pinMode( BTN_RIGHT, INPUT_PULLUP ); pinMode( BTN_ROTATE, INPUT_PULLUP ); // Buzzer pinMode( BUZZER_PIN, OUTPUT ); // MAX7219 mx.begin(); mx.control( MD_MAX72XX::INTENSITY, 5 ); mx.control( MD_MAX72XX::UPDATE, true ); mx.clear(); // Random seed randomSeed( analogRead(2) // A0 = GPIO2, used only as noise for the random seed ); // ========================================================== // START MUSIC // ========================================================== playStartMusic(); // ========================================================== // START GAME // ========================================================== resetGame(); gameStarted = true; } // ============================================================ // MAIN LOOP // ============================================================ void loop() { if (!gameStarted) { return; } // ========================================================== // GAME OVER // ========================================================== if (gameOver) { gameOverAnimation(); delay(500); // Restart resetGame(); return; } // ========================================================== // INPUT // ========================================================== handleButtons(); // ========================================================== // SOFT DROP // ========================================================== if (softDrop) { handleSoftDrop(); } else { handleNormalFall(); } }

WARNING

This demo uses two boot strapping pins: D6 (GPIO3) for the buzzer and D9 (GPIO46) for the matrix CS. If the board does not boot or upload with everything connected, unplug the wires on these two pins, upload, then plug them back in.

FAQ

Can I power the LED matrix from the ESP32 S3 Uno 5V pin?

Only for a small test at low brightness. A 32x8 display can draw up to 1 A at full brightness, which is too much for the board. Use an external 5V supply and connect its GND to the ESP32 S3 Uno GND.

Does the MAX7219 LED matrix work with 3.3V logic from the ESP32 S3 Uno?

In most cases, yes. The ESP32 S3 Uno sends 3.3V signals, and most MAX7219 modules powered at 5V read them correctly. If you see random dots or a blank screen, add a logic level shifter on DIN, CS and CLK, or lower the wire length.

Which ESP32 S3 Uno pins should I use for DIN and CLK?

Use D11 (GPIO11) for DIN and D13 (GPIO12) for CLK. These are the default SPI MOSI and SCK pins of the ESP32 S3 Uno, the same header spots as on an Arduino Uno. CS can go to any free digital pin; just write its GPIO number in CS_PIN.

How do I use an 8x8 or 64x8 display instead of 32x8?

Change MAX_DEVICES to the number of blocks: 1 for 8x8, 2 for 16x8, 8 for 64x8. The wiring stays the same, because extra blocks are chained from the output pins of the previous block.

Why does my text look mirrored or in the wrong order?

The hardware type in the code does not match your module. Switch between FC16_HW and GENERIC_HW and upload again. FC-16 modules are the most common ones.

What if my project needs more pins than the Uno headers give?

The ESP32 S3 Uno has two extra rows of holes with more GPIOs: GPIO15 and GPIO16 (I/O, PWM, analog), GPIO45 and GPIO35–GPIO42 (I/O, PWM), and GPIO47/GPIO48 (output only, PWM). Solder pin headers to use them and write the GPIO number in code. For this project, GPIO35–GPIO42 are good for a second matrix CS pin or for game buttons; do not use GPIO47/GPIO48 for buttons because they are output only. Two warnings: GPIO47/GPIO48 may run at 1.8V instead of 3.3V on some modules (especially ones with "V" in the name, like R8V or R16V), so 3.3V devices can be damaged or act strangely. Also, GPIO0, GPIO3 (D6), GPIO45 and GPIO46 (D9) are boot strapping pins, and a wrong connection can stop the board from booting or uploading code. Use the other extra pins first.

Troubleshooting

Problem Possible Cause Solution
No COM port in Arduino IDE Missing USB driver or the board is not in upload mode Install the CP210x or CH340 driver, or hold BOOT while pressing RESET, then upload again
Board does not boot or upload with the matrix connected CS is on D9 (GPIO46), a boot strapping pin Unplug the CS wire while uploading, or move CS to another free pin and change CS_PIN
Display stays dark No 5V power, GND not shared, or wrong DIN and CLK pins Check the 5V supply, join all GNDs, and wire DIN to D11 (GPIO11) and CLK to D13 (GPIO12)
Random dots or flicker Weak 3.3V signals or long wires Use shorter wires, or add a logic level shifter on DIN, CS and CLK
Text mirrored or blocks in the wrong order Wrong hardware type Switch between FC16_HW and GENERIC_HW
Only part of the text shows MAX_DEVICES does not match the number of blocks Set MAX_DEVICES to the real block count
Board resets at high brightness Power drawn from the board 5V pin Use an external 5V supply or lower setIntensity()
Compile error MD_Parola.h not found Library not installed Install MD_Parola and click Install All to add MD_MAX72XX

Learn More

※ OUR MESSAGES

  • As freelancers, We are AVAILABLE for HIRE. See how to outsource your project to us
  • Please feel free to share the link of this tutorial. However, Please do not use our content on any other websites. We invested a lot of effort and time to create the content, please respect our work!