Working with Arduino
This chapter contains the following sections. Please read as needed:
Arduino Getting Started
New to Arduino ESP32 development and looking for a quick start? We have prepared a comprehensive Getting Started Tutorial for you.
- Section 0: Getting to Know ESP32
- Section 1: Installing and Configuring Arduino IDE
- Section 2: Arduino Basics
- Section 3: Digital Output/Input
- Section 4: Analog Input
- Section 5: Pulse Width Modulation (PWM)
- Section 6: Serial Communication (UART)
- Section 7: I2C Communication
- Section 8: SPI Communication
- Section 9: Wi-Fi Basics
- Section 10: Web Server
- Section 11: Bluetooth
- Section 12: LVGL GUI Development
- Section 13: Comprehensive Project
Note: This tutorial uses the ESP32-S3-Zero as a reference example, and all hardware code is based on its pinout. Before you start, we recommend checking the pinout of your development board to ensure the pin configuration is correct.
Setting Up Development Environment
1. Installing and Configuring Arduino IDE
Please refer to the tutorial Installing and Configuring Arduino IDE to download and install the Arduino IDE and add ESP32 support.
| Board Name | Board Installation Requirement | Version Requirement |
|---|---|---|
| esp32 by Espressif Systems | "Install Offline" / "Install Online" | 3.3.7 |
Arduino Settings:

2. Running the Examples
Arduino example programs: ESP32-S3-RGB-Matrix Example - GitHub
Below are the purpose, key points, and operational effects for each example (for quick start).
| Example | Basic Description |
|---|---|
| 01_SimpleTestShapes | Simple shape drawing |
| 02_PatternPlasma | Plasma effect |
| 03_DoubleBuffer | Double buffer test, draw dynamic graphics |
| 04_OtherShiftDriverPanel | Drive the panel using a driver chip |
| 05_AnimatedGIFPanel_SD | Read and display GIF images from the TF card |
| 06_BitmapIcons | Display BMP images |
| 07_Pixel_Mapping_Test | Show basic HUB75 control logic |
Note:
- When driving a P4 series panel, be sure to add
mxconfig.driver = HUB75_I2S_CFG::SHIFTREG;in the code, otherwise display anomalies may occur.
01_SimpleTestShapes
Code Analysis
loop(): Sequentially performs text drawing, solid color filling, and screen clearing operations to quickly verify the basic display functionality of the panel.drawText(wheelval): Draws text and changes color effects based onwheelvalto check whether character rendering and color changes are normal.dma_display->fillScreen(): Fills the screen with solid colors such as black, red, green, blue, and white in sequence to observe the full-screen refresh and color display effect.delay(2000): Pauses for 2 seconds after each display step for easy visual confirmation.dma_display->clearScreen(): Clears the screen after the test to avoid residual content from the previous frame.
void loop() {
// animate by going through the colour wheel for the first two lines
drawText(wheelval);
wheelval +=1;
delay(2000);
dma_display->clearScreen();
dma_display->fillScreen(myBLACK);
delay(2000);
dma_display->fillScreen( myRED);
delay(2000);
dma_display->fillScreen(myGREEN);
delay(2000);
dma_display->fillScreen(myBLUE);
delay(2000);
dma_display->fillScreen(myWHITE);
delay(2000);
dma_display->clearScreen();
}
Operation Result

02_PatternPlasma
Code Analysis
loop(): Generates a dynamic plasma effect through per-pixel calculation and palette mapping.for (int x ...)/for (int y ...): Iterates over each pixel of the panel, calculating and drawing colors point by point.sin8(),sin16(),cos16(): Combine coordinates andtime_counterto generate a dynamically changing intermediate valuev, creating a flowing ripple effect.ColorFromPalette(currentPalette, (v >> 8)): Fetches a color from the current palette based on the calculation result.dma_display->drawPixelRGB888(): Writes the calculated RGB color to the current pixel.time_counter,cycles,fps: Used to drive animation changes, count the number of cycles, and calculate the drawing frame rate.if (cycles >= 1024): Resets the counter and randomly switches the palette to achieve automatic cycling through different color schemes.Serial.printf_P(): Outputs theEffect fpsevery 5 seconds to observe the effect drawing speed.
void loop() {
for (int x = 0; x < PANE_WIDTH; x++) {
for (int y = 0; y < PANE_HEIGHT; y++) {
int16_t v = 128;
uint8_t wibble = sin8(time_counter);
v += sin16(x * wibble * 3 + time_counter);
v += cos16(y * (128 - wibble) + time_counter);
v += sin16(y * x * cos8(-time_counter) / 8);
currentColor = ColorFromPalette(currentPalette, (v >> 8)); //, brightness, currentBlendType);
dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b);
}
}
++time_counter;
++cycles;
++fps;
if (cycles >= 1024) {
time_counter = 0;
cycles = 0;
currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))];
}
// print FPS rate every 5 seconds
// Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second
if (fps_timer + 5000 < millis()){
Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5);
fps_timer = millis();
fps = 0;
}
} // end loop
Operation Result

03_DoubleBuffer
Code Analysis
loop(): Demonstrates the full flow of double-buffered animation, including buffer swapping, back‑buffer drawing, and motion state updates.display->flipDMABuffer(): Switches subsequent drawing to the back buffer that is not currently displayed, used to reduce flicker in dynamic graphics.delay(1000/display->calculated_refresh_rate): Waits long enough for the current frame to finish displaying, avoiding tearing or flicker caused by flipping the buffer too early.display->clearScreen(): Clears the back buffer to prepare for drawing the next frame.delay(25): Simulates a time-consuming drawing process, making it easier to visually observe the smoothness effect after enabling double buffering.display->fillRect(): Draws multiple moving squares to form a dynamic test screen.velocityx/velocityylogic: Reverses the movement direction when a square hits the screen edge, creating a bounce effect.Squares[i].xpos/Squares[i].yposupdate: Updates the square coordinates based on the velocity, driving the next frame of animation.
void loop()
{
// Flip all future drawPixel calls to write to the back buffer which is NOT being displayed.
display->flipDMABuffer();
// SUPER IMPORTANT: Wait at least long enough to ensure that a "frame" has been displayed on the LED Matrix Panel before the next flip!
delay(1000/display->calculated_refresh_rate);
// Now clear the back-buffer we are drawing to.
display->clearScreen();
// This is here to demonstrate flicker if double buffering is disabled. Emulates a long draw routine that would typically occur after a 'clearscreen'.
delay(25);
for (int i = 0; i < numSquares; i++)
{
// Draw rect and then calculate
display->fillRect(Squares[i].xpos, Squares[i].ypos, Squares[i].square_size, Squares[i].square_size, Squares[i].colour);
if (Squares[i].square_size + Squares[i].xpos >= display->width()) {
Squares[i].velocityx *= -1;
} else if (Squares[i].xpos <= 0) {
Squares[i].velocityx = abs (Squares[i].velocityx);
}
if (Squares[i].square_size + Squares[i].ypos >= display->height()) {
Squares[i].velocityy *= -1;
} else if (Squares[i].ypos <= 0) {
Squares[i].velocityy = abs (Squares[i].velocityy);
}
Squares[i].xpos += Squares[i].velocityx;
Squares[i].ypos += Squares[i].velocityy;
}
}
Operation Result

04_OtherShiftDriverPanel
Code Analysis
loop(): Continuously generates a full‑screen dynamic effect to verify display compatibility with panels using shift‑register driver chips.for (int x ...)/for (int y ...): Iterates over each pixel of the screen, calculating the color value for each pixel individually.sin8(),sin16(),cos16(): Combine coordinates andtime_counterto generate a continuously changing color index.ColorFromPalette(currentPalette, (v >> 8) + 127): Maps the intermediate value to a final color from the palette.dma_display->drawPixelRGB888(): Writes the RGB color point‑by‑point to the DMA display buffer.time_counter,cycles,fps: Used to drive animation changes, control the color‑cycling period, and count the drawing frame rate.if (cycles >= 1024): Periodically resets animation parameters and randomly switches the palette for easy observation of display effects under different color schemes.Serial.printf_P(): Periodically outputs FPS information to evaluate the current pattern drawing speed.
void loop() {
for (int x = 0; x < dma_display->width(); x++) {
for (int y = 0; y < dma_display->height(); y++) {
int16_t v = 0;
uint8_t wibble = sin8(time_counter);
v += sin16(x * wibble * 3 + time_counter);
v += cos16(y * (128 - wibble) + time_counter);
v += sin16(y * x * cos8(-time_counter) / 8);
currentColor = ColorFromPalette(currentPalette, (v >> 8) + 127); //, brightness, currentBlendType);
dma_display->drawPixelRGB888(x, y, currentColor.r, currentColor.g, currentColor.b);
}
}
++time_counter;
++cycles;
++fps;
if (cycles >= 1024) {
time_counter = 0;
cycles = 0;
currentPalette = palettes[random(0,sizeof(palettes)/sizeof(palettes[0]))];
}
// print FPS rate every 5 seconds
// Note: this is NOT a matrix refresh rate, it's the number of data frames being drawn to the DMA buffer per second
if (fps_timer + 5000 < millis()){
Serial.printf_P(PSTR("Effect fps: %d\n"), fps/5);
fps_timer = millis();
fps = 0;
}
}
Operation Result

05_AnimatedGIFPanel_SD
Code Analysis
setup(): Completes initializations of the TF card, HUB75 panel, and GIF decoder, preparing for GIF animation playback.SD_MMC.setPins(): Configures the clock, command, and data pins used by the TF card.SD_MMC.begin("/sdcard", true): Mounts the TF card file system in 1‑bit mode.SD_MMC.cardType(),cardSize(),totalBytes(),usedBytes(): Reads the card type, capacity, and space usage information to confirm storage medium status.HUB75_I2S_CFG mxconfig(...): Configures the width, height, and cascade count of the HUB75 panel.dma_display = new MatrixPanel_I2S_DMA(mxconfig): Creates the DMA display object.dma_display->begin(): Starts DMA display and allocates the display buffer.SD_MMC.open("/gifs"): Opens the GIF file directory.root.openNextFile(): Iterates through files in the/gifsdirectory.GifFiles.push_back(filename): Saves the found GIF file paths to a list for later loop playback.gif.begin(LITTLE_ENDIAN_PIXELS): Initializes the GIF decoder and sets the pixel byte order.
void setup()
{
Serial.begin(115200);
// **************************** Setup TF Card access via SD_MMC 1-bit ****************************
if (!SD_MMC.setPins(BSP_SD_CLK, BSP_SD_CMD, BSP_SD_D0)) {
Serial.println("SD_MMC setPins Failed");
return;
}
if (!SD_MMC.begin( "/sdcard", true)) {
Serial.println("Card Mount Failed");
return;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No TF card attached");
return;
}
Serial.print("TF Card Type: ");
if (cardType == CARD_MMC) {
Serial.println("MMC");
} else if (cardType == CARD_SD) {
Serial.println("SDSC");
} else if (cardType == CARD_SDHC) {
Serial.println("SDHC");
}else{
Serial.println("UNKNOWN");
}
uint64_t cardSize = SD_MMC.cardSize() / (1024 * 1024);
Serial.printf("TF Card Size: %lluMB\n", cardSize);
//listDir(SD_MMC, "/", 1, false);
Serial.printf("Total space: %lluMB\n", SD_MMC.totalBytes() / (1024 * 1024));
Serial.printf("Used space: %lluMB\n", SD_MMC.usedBytes() / (1024 * 1024));
// **************************** Setup DMA Matrix ****************************
HUB75_I2S_CFG mxconfig(
PANEL_RES_X, // module width
PANEL_RES_Y, // module height
PANEL_CHAIN // Chain length
);
// Keep ESP32-S3 default HUB75 mapping to avoid Flash/PSRAM reserved pins.
//mxconfig.clkphase = false;
//mxconfig.driver = HUB75_I2S_CFG::FM6126A;
// Display Setup
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
// Allocate memory and start DMA display
if( not dma_display->begin() )
Serial.println("****** !KABOOM! HUB75 memory allocation failed ***********");
dma_display->setBrightness8(128); //0-255
dma_display->clearScreen();
// **************************** Setup Sketch ****************************
Serial.println("Starting AnimatedGIFs Sketch");
// TF CARD STOPS WORKING WITH DMA DISPLAY ENABLED>...
File root = SD_MMC.open("/gifs");
if (!root) {
Serial.println("Failed to open directory");
return;
}
File file = root.openNextFile();
while (file) {
if(!file.isDirectory())
{
Serial.print(" FILE: ");
Serial.print(file.name());
Serial.print(" SIZE: ");
Serial.println(file.size());
std::string filename = "/gifs/" + std::string(file.name());
Serial.println(filename.c_str());
GifFiles.push_back( filename );
// Serial.println("Adding to gif list:" + String(filename));
totalFiles++;
}
file = root.openNextFile();
}
file.close();
Serial.printf("Found %d GIFs to play.", totalFiles);
//totalFiles = getGifInventory("/gifs");
// This is important - Set the right endianness.
gif.begin(LITTLE_ENDIAN_PIXELS);
}
Operation Result

06_BitmapIcons
Code Analysis
setup(): Completes display initialization and draws a Wi-Fi icon with a fade‑in effect to verify that bitmap resources and drawing interfaces are working properly.dma_display->begin(): Starts the HUB75 panel display.dma_display->setBrightness8(90): Sets the panel brightness.dma_display->fillScreen()/dma_display->clearScreen(): Clears the screen during initialization and between icon switches to avoid image retention.for (int r = 0; r < 255; r++): Gradually increases the red component to create a fade‑in animation.drawXbm565(0,0,64,32, wifi_image1bit, ...): Draws the Wi-Fi XBM bitmap onto the screen.loop(): Cycles through different icons, displaying them one after another.drawXbm565(5,0, 32, 32, icon_bits[current_icon]): Draws the icon data corresponding to the current index.icon_name[current_icon]: Outputs the name of the currently displayed icon via the serial port for debugging purposes.current_icon = (current_icon + 1) % num_icons: Updates the icon index cyclically to ensure the rotation does not go out of bounds.
void setup() {
// put your setup code here, to run once:
delay(1000); Serial.begin(115200); delay(200);
/************** DISPLAY **************/
Sprintln("...Starting Display");
dma_display = new MatrixPanel_I2S_DMA(mxconfig);
dma_display->begin();
dma_display->setBrightness8(90); //0-255
dma_display->clearScreen();
dma_display->fillScreen(dma_display->color444(0, 0, 0));
// Fade a Red Wi-Fi Logo In
for (int r=0; r < 255; r++ )
{
drawXbm565(0,0,64,32, wifi_image1bit, dma_display->color565(r,0,0));
delay(10);
}
delay(2000);
dma_display->clearScreen();
}
void loop() {
// Loop through Weather Icons
Serial.print("Showing icon ");
Serial.println(icon_name[current_icon]);
drawXbm565(5,0, 32, 32, icon_bits[current_icon]);
current_icon = (current_icon +1 ) % num_icons;
delay(2000);
dma_display->clearScreen();
}
Operation Result

07_Pixel_Mapping_Test
Code Analysis
loop(): Lights pixels one by one in row‑column order to verify that the panel pixel mapping matches the software configuration.for (int i ...)/for (int j ...): Iterates over all pixel coordinates ofFourScanPanel.FourScanPanel->drawPixel(j, i, FourScanPanel->color565(255, 0, 0)): Lights the current pixel in red, creating an observable scanning trace.delay(30): Moves the scanning point slowly for easy observation of the actual lighting order.dma_display->clearScreen(): Clears the screen after a full‑panel scan to start the next test round.delay(2000): Pauses for 2 seconds after each scanning round to allow confirmation of mapping correctness.
void loop() {
for (int i = 0; i < FourScanPanel->height(); i++)
{
for (int j = 0; j < FourScanPanel->width(); j++)
{
FourScanPanel->drawPixel(j, i, FourScanPanel->color565(255, 0, 0));
delay(30);
}
}
delay(2000);
dma_display->clearScreen();
} // end loop
Operation Result
