ESP32 ESP-IDF with MAX7219 Dot Matrix Display Module

In this ESP-IDF tutorial, we will introduce you to MAX7219 dot matrix module and how to program it with ESP32 using ESP-IDF. We will start off by briefly introducing the led matrix, and its interfacing with ESP32 development board and then move ahead to program ESP32 in VS Code with ESP-IDF extension. We will demonstrate how to display different symbols and numbers on the LED dot matrix display which is an LED array commonly used to display different types of texts, symbols, and images in the form of dots (LEDs).

ESP32 ESP-IDF with MAX7219 Dot Matrix Display Module

Introduction to MAX7219 LED Dot Matrix Display

The MAX7219 LED Dot matrix display is commonly used in electronics projects and comes in two variants. One type is known as FC-16 and the other is known as the generic module.

MAX7219 Dot LED Matrix Modules
MAX7219 Module Types

Each MAX7219 dot matric module consists of two units, an 8×8 LED dot matrix and a MAX7219 IC which is one of the most popular drivers IC for displays. An 8×8 LED matrix consists of 8 LEDs in each row and column which turn out to be 64 LEDs in total, where each ‘dot’ represents an LED. Each LED (dot) is associated with its own row and column number.

MAX7219 Dot LED Matrix Module Pinout

As you can view in the diagram above, the MAX7219 module has 5 terminals which consist of two power supply pins (VCC and GND) and three SPI communication pins (DIN, CS, and CLK). Moreover, the module contains of two sets of these pins at both sides forming input and output connections. Both types of modules, FC-16, and the generic module, have the same connections on two sides.

Let’s briefly discuss both of these connections.

Input Connections

The input connection pins are connected with a microcontroller, which in our case is an ESP32 development board.

VCCThis is the pin that supplies power to the MAX7219 LED matrix module. To set the brightness to half, connect it with the Vin pin of ESP32.
GNDThis pin provides the common ground.
DINThis is the data in pin which is used in SPI communication between the microcontroller and the led module. It acts as the SPI input pin.
CSThis is the chip select pin.
CLKThis is the SPI serial clock output pin.

Output Connections

If there is a need to attach more MAX7219 modules together, the output connection pins are used. They offer the same set of five pins which form the input connections except for the data out pin instead of the data in pin.

VCCThis pin is connected with the VCC pin of the next attached module.
GNDThis pin is connected with the GND pin of the next attached module.
DOUTThis is the data out pin which is connected with the data in (DIN) pin of the next attached module.
CSThis is connected with the CS pin of the next attached module.
CLKThis is connected with the CLK pin of the next attached module.

Interface MAX7219 LED Dot Matrix with ESP32

To connect our ESP32 board with MAX7219 LED matrix module, we will need the following components.

  1. ESP32 board
  2. MAX7219 Module
  3. Connecting Wires

To interface MAX7219 LED dot matrix with ESP32, we will use the SPI interface of the development board. The ESP32 development board comes with two general purpose SPI controllers. These SPI controllers can be configured to be used as slave nodes which are driven by an off chip SPI master. These are known as SPI2 (HSPI) and SPI3 (VSPI). Both of them have separate signal buses. Hence, we can use them separately either as a master or slave. Each bus can control up to three SPI devices or slaves in the controller mode.

The table below shows the default ESP32 SPI pins for both the channels.

SPI ChannelMOSIMISOSCK/CLKCS/SS
VSPIGPIO23 GPIO19GPIO18GPIO5
HSPIGPIO13GPIO12GPIO14GPIO15

The connections between the devices are specified in the table below:

MAX7219 Module PinESP32
VCCThe VCC pin will be connected with Vin (5V) pin of the ESP32 board.
GNDCommon ground with ESP32.
DINThis pin is the SPI data in pin. We will connect it with GPIO23 which is the default SPI MOSI pin in ESP32.
CSThis pin is the SPI chip select pin. We will connect it with GPIO5 which is the default SPI CS pin in ESP32.
CLKThis pin is the SPI serial clock pin. We will connect it with GPIO18 which is the default SPI CLK pin in ESP32.

Connect the MAX7219 module with the ESP32 development board as shown in the schematic diagram below. Here, we are using FC-16 module consisting of four units.

ESP32 with MAX7219 dot led matrix module FC-16 connection diagram

ESP32 MAX7219 LED Dot Matrix Module with ESP-IDF

We will build and create a project in VS Code with ESP-IDF extension to show how to program ESP32 with the MAX7219 module easily. The code will display different images and numbers on the LED matrix in a scrolling manner. We will use the max7219 library provided by UncleRus on GitHub.

Create Example Project

Open your VS Code and head over to View > Command Palette. Type ESP-IDF: New Project in the search bar and press enter.

Specify the project name and directory, along with the ESP-IDF board and target. For the ESP-IDF board, we have chosen the custom board option. For ESP-IDF target, we have chosen ESP32 module. Click ‘Choose Template’ button to proceed forward.

In the Extension, select ESP-IDF option:

ESP-IDF in VS Code New Project 2

We will click the ‘sample_project’ under the get-started tab. Now click ‘Create project using template sample_project.’

ESP-IDF in VS Code New Project 3

You will get a notification that the project has been created. To open the project in a new window, click ‘Yes.’

This opens the project that we created inside the EXPLORER tab. There are several folders inside our project folder. This is the same for every project which you will create through ESP-IDF Explorer.

  • First, let’s add the necessary header files for the libraries required for this project. Create a new folder called ‘components’ and add the files listed below:
  1. max7219.c
  2. max7219.h
  3. max7219_priv.h
  4. CMakeLists.txt

You can obtain these files from this GitHub link.

max7219.c

/*
 * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of itscontributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file max7219.c
 *
 * ESP-IDF driver for MAX7219/MAX7221
 * Serially Interfaced, 8-Digit LED Display Drivers
 *
 * Ported from esp-open-rtos
 *
 * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
 *
 * BSD Licensed as described in the file LICENSE
 */
#include "max7219.h"
#include <string.h>
#include <esp_log.h>

#include "max7219_priv.h"

static const char *TAG = "max7219";

#define ALL_CHIPS 0xff
#define ALL_DIGITS 8

#define REG_DIGIT_0      (1 << 8)
#define REG_DECODE_MODE  (9 << 8)
#define REG_INTENSITY    (10 << 8)
#define REG_SCAN_LIMIT   (11 << 8)
#define REG_SHUTDOWN     (12 << 8)
#define REG_DISPLAY_TEST (15 << 8)

#define VAL_CLEAR_BCD    0x0f
#define VAL_CLEAR_NORMAL 0x00

#define CHECK(x) do { esp_err_t __; if ((__ = x) != ESP_OK) return __; } while (0)
#define CHECK_ARG(VAL) do { if (!(VAL)) return ESP_ERR_INVALID_ARG; } while (0)

static inline uint16_t shuffle(uint16_t val)
{
    return (val >> 8) | (val << 8);
}

static esp_err_t send(max7219_t *dev, uint8_t chip, uint16_t value)
{
    uint16_t buf[MAX7219_MAX_CASCADE_SIZE] = { 0 };
    if (chip == ALL_CHIPS)
    {
        for (uint8_t i = 0; i < dev->cascade_size; i++)
            buf[i] = shuffle(value);
    }
    else buf[chip] = shuffle(value);

    spi_transaction_t t;
    memset(&t, 0, sizeof(t));
    t.length = dev->cascade_size * 16;
    t.tx_buffer = buf;
    return spi_device_transmit(dev->spi_dev, &t);
}

inline static uint8_t get_char(max7219_t *dev, char c)
{
    if (dev->bcd)
    {
        if (c >= '0' && c <= '9')
            return c - '0';
        switch (c)
        {
            case '-':
                return 0x0a;
            case 'E':
            case 'e':
                return 0x0b;
            case 'H':
            case 'h':
                return 0x0c;
            case 'L':
            case 'l':
                return 0x0d;
            case 'P':
            case 'p':
                return 0x0e;
        }
        return VAL_CLEAR_BCD;
    }

    return font_7seg[(c - 0x20) & 0x7f];
}

///////////////////////////////////////////////////////////////////////////////

esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin)
{
    CHECK_ARG(dev);

    memset(&dev->spi_cfg, 0, sizeof(dev->spi_cfg));
    dev->spi_cfg.spics_io_num = cs_pin;
    dev->spi_cfg.clock_speed_hz = clock_speed_hz;
    dev->spi_cfg.mode = 0;
    dev->spi_cfg.queue_size = 1;
    dev->spi_cfg.flags = SPI_DEVICE_NO_DUMMY;

    return spi_bus_add_device(host, &dev->spi_cfg, &dev->spi_dev);
}

esp_err_t max7219_free_desc(max7219_t *dev)
{
    CHECK_ARG(dev);

    return spi_bus_remove_device(dev->spi_dev);
}

esp_err_t max7219_init(max7219_t *dev)
{
    CHECK_ARG(dev);
    if (!dev->cascade_size || dev->cascade_size > MAX7219_MAX_CASCADE_SIZE)
    {
        ESP_LOGE(TAG, "Invalid cascade size %d", dev->cascade_size);
        return ESP_ERR_INVALID_ARG;
    }

    uint8_t max_digits = dev->cascade_size * ALL_DIGITS;
    if (dev->digits > max_digits)
    {
        ESP_LOGE(TAG, "Invalid digits count %d, max %d", dev->digits, max_digits);
        return ESP_ERR_INVALID_ARG;
    }
    if (!dev->digits)
        dev->digits = max_digits;

    // Shutdown all chips
    CHECK(max7219_set_shutdown_mode(dev, true));
    // Disable test
    CHECK(send(dev, ALL_CHIPS, REG_DISPLAY_TEST));
    // Set max scan limit
    CHECK(send(dev, ALL_CHIPS, REG_SCAN_LIMIT | (ALL_DIGITS - 1)));
    // Set normal decode mode & clear display
    CHECK(max7219_set_decode_mode(dev, false));
    // Set minimal brightness
    CHECK(max7219_set_brightness(dev, 0));
    // Wake up
    CHECK(max7219_set_shutdown_mode(dev, false));

    return ESP_OK;
}

esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd)
{
    CHECK_ARG(dev);

    dev->bcd = bcd;
    CHECK(send(dev, ALL_CHIPS, REG_DECODE_MODE | (bcd ? 0xff : 0)));
    CHECK(max7219_clear(dev));

    return ESP_OK;
}

esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value)
{
    CHECK_ARG(dev);
    CHECK_ARG(value <= MAX7219_MAX_BRIGHTNESS);

    CHECK(send(dev, ALL_CHIPS, REG_INTENSITY | value));

    return ESP_OK;
}

esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown)
{
    CHECK_ARG(dev);

    CHECK(send(dev, ALL_CHIPS, REG_SHUTDOWN | !shutdown));

    return ESP_OK;
}

esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val)
{
    CHECK_ARG(dev);
    if (digit >= dev->digits)
    {
        ESP_LOGE(TAG, "Invalid digit: %d", digit);
        return ESP_ERR_INVALID_ARG;
    }

    if (dev->mirrored)
        digit = dev->digits - digit - 1;

    uint8_t c = digit / ALL_DIGITS;
    uint8_t d = digit % ALL_DIGITS;

    ESP_LOGV(TAG, "Chip %d, digit %d val 0x%02x", c, d, val);

    CHECK(send(dev, c, (REG_DIGIT_0 + ((uint16_t)d << 8)) | val));

    return ESP_OK;
}

esp_err_t max7219_clear(max7219_t *dev)
{
    CHECK_ARG(dev);

    uint8_t val = dev->bcd ? VAL_CLEAR_BCD : VAL_CLEAR_NORMAL;
    for (uint8_t i = 0; i < ALL_DIGITS; i++)
        CHECK(send(dev, ALL_CHIPS, (REG_DIGIT_0 + ((uint16_t)i << 8)) | val));

    return ESP_OK;
}

esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s)
{
    CHECK_ARG(dev && s);

    while (s && pos < dev->digits)
    {
        uint8_t c = get_char(dev, *s);
        if (*(s + 1) == '.')
        {
            c |= 0x80;
            s++;
        }
        CHECK(max7219_set_digit(dev, pos, c));
        pos++;
        s++;
    }

    return ESP_OK;
}

esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image)
{
    CHECK_ARG(dev && image);

    for (uint8_t i = pos, offs = 0; i < dev->digits && offs < 8; i++, offs++)
        max7219_set_digit(dev, i, *((uint8_t *)image + offs));

    return ESP_OK;
}

max7219.h

/*
 * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of itscontributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file max7219.h
 * @defgroup max7219 max7219
 * @{
 *
 * ESP-IDF driver for MAX7219/MAX7221
 * Serially Interfaced, 8-Digit LED Display Drivers
 *
 * Ported from esp-open-rtos
 *
 * Copyright (c) 2017 Ruslan V. Uss <unclerus@gmail.com>
 *
 * BSD Licensed as described in the file LICENSE
 */
#ifndef __MAX7219_H__
#define __MAX7219_H__

#include <stdint.h>
#include <stdbool.h>
#include <driver/spi_master.h>
#include <driver/gpio.h> // add by nopnop2002
#include <esp_err.h>

#ifdef __cplusplus
extern "C" {
#endif

#define MAX7219_MAX_CLOCK_SPEED_HZ (10000000) // 10 MHz

#define MAX7219_MAX_CASCADE_SIZE 8
#define MAX7219_MAX_BRIGHTNESS   15

/**
 * Display descriptor
 */
typedef struct
{
    spi_device_interface_config_t spi_cfg;
    spi_device_handle_t spi_dev;
    uint8_t digits;              //!< Accessible digits in 7seg. Up to cascade_size * 8
    uint8_t cascade_size;        //!< Up to `MAX7219_MAX_CASCADE_SIZE` MAX721xx cascaded
    bool mirrored;               //!< true for horizontally mirrored displays
    bool bcd;
} max7219_t;

/**
 * @brief Initialize device descriptor
 *
 * @param dev Device descriptor
 * @param host SPI host
 * @param clock_speed_hz SPI clock speed, Hz
 * @param cs_pin CS GPIO number
 * @return `ESP_OK` on success
 */
esp_err_t max7219_init_desc(max7219_t *dev, spi_host_device_t host, uint32_t clock_speed_hz, gpio_num_t cs_pin);

/**
 * @brief Free device descriptor
 *
 * @param dev Device descriptor
 * @return `ESP_OK` on success
 */
esp_err_t max7219_free_desc(max7219_t *dev);

/**
 * @brief Initialize display
 *
 * Switch it to normal operation from shutdown mode,
 * set scan limit to the max and clear
 *
 * @param dev Display descriptor
 * @return `ESP_OK` on success
 */
esp_err_t max7219_init(max7219_t *dev);

/**
 * @brief Set decode mode and clear display
 *
 * @param dev Display descriptor
 * @param bcd true to set BCD decode mode, false to normal
 * @return `ESP_OK` on success
 */
esp_err_t max7219_set_decode_mode(max7219_t *dev, bool bcd);

/**
 * @brief Set display brightness
 *
 * @param dev Display descriptor
 * @param value Brightness value, 0..MAX7219_MAX_BRIGHTNESS
 * @return `ESP_OK` on success
 */
esp_err_t max7219_set_brightness(max7219_t *dev, uint8_t value);

/**
 * @brief Shutdown display or set it to normal mode
 *
 * @param dev Display descriptor
 * @param shutdown Shutdown display if true
 * @return `ESP_OK` on success
 */
esp_err_t max7219_set_shutdown_mode(max7219_t *dev, bool shutdown);

/**
 * @brief Write data to display digit
 *
 * @param dev Display descriptor
 * @param digit Digit index, 0..dev->digits - 1
 * @param val Data
 * @return `ESP_OK` on success
 */
esp_err_t max7219_set_digit(max7219_t *dev, uint8_t digit, uint8_t val);

/**
 * @brief Clear display
 *
 * @param dev Display descriptor
 * @return `ESP_OK` on success
 */
esp_err_t max7219_clear(max7219_t *dev);

/**
 * @brief Draw text on 7-segment display
 *
 * @param dev Display descriptor
 * @param pos Start digit
 * @param s Text
 * @return `ESP_OK` on success
 */
esp_err_t max7219_draw_text_7seg(max7219_t *dev, uint8_t pos, const char *s);

/**
 * @brief Draw 64-bit image on 8x8 matrix
 *
 * @param dev Display descriptor
 * @param pos Start digit
 * @param image 64-bit buffer with image data
 * @return `ESP_OK` on success
 */
esp_err_t max7219_draw_image_8x8(max7219_t *dev, uint8_t pos, const void *image);

#ifdef __cplusplus
}
#endif

/**@}*/

#endif /* __MAX7219_H__ */

max7219_priv.h

/*
 * Copyright (c) 2019 Ruslan V. Uss <unclerus@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice,
 *    this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of the copyright holder nor the names of itscontributors
 *    may be used to endorse or promote products derived from this software without
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
 * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file max7219_priv.h
 *
 * ESP-IDF driver for MAX7219/MAX7221
 * Serially Interfaced, 8-Digit LED Display Drivers
 *
 * Ported from esp-open-rtos
 *
 * Copyright (c) 2017, 2018 Ruslan V. Uss <unclerus@gmail.com>
 *
 * BSD Licensed as described in the file LICENSE
 */
#ifndef __MAX7219_PRIV_H__
#define __MAX7219_PRIV_H__

static const uint8_t font_7seg[] = {
    /*  ' '   !     "     #     $     %     &     '     (     )     */
        0x00, 0x00, 0x22, 0x00, 0x00, 0x00, 0x00, 0x02, 0x4e, 0x78,
    /*  *     +     ,     -     .     /     0     1     2     3     */
        0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x7e, 0x30, 0x6d, 0x79,
    /*  4     5     6     7     8     9     :     ;     <     =     */
        0x33, 0x5b, 0x5f, 0x70, 0x7f, 0x7b, 0x00, 0x00, 0x0d, 0x09,
    /*  >     ?     @     A     B     C     D     E     F     G     */
        0x19, 0x65, 0x00, 0x77, 0x1f, 0x4e, 0x3d, 0x4f, 0x47, 0x5e,
    /*  H     I     J     K     L     M     N     O     P     Q     */
        0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d, 0x67, 0x73,
    /*  R     S     T     U     V     W     X     Y     Z     [     */
        0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b, 0x6d, 0x4e,
    /*  \     ]     ^     _     `     a     b     c     d     e     */
        0x00, 0x78, 0x00, 0x08, 0x02, 0x77, 0x1f, 0x4e, 0x3d, 0x4f,
    /*  f     g     h     i     j     k     l     m     n     o     */
        0x47, 0x5e, 0x37, 0x06, 0x38, 0x57, 0x0e, 0x76, 0x15, 0x1d,
    /*  p     q     r     s     t     u     v     w     x     y     */
        0x67, 0x73, 0x05, 0x5b, 0x0f, 0x1c, 0x3e, 0x2a, 0x49, 0x3b,
    /*  z     {     |     }     ~     */
        0x6d, 0x4e, 0x06, 0x78, 0x00
};


#endif /* __MAX7219_PRIV_H__ */

CMakeLists.txt

idf_component_register(
    SRCS max7219.c
    INCLUDE_DIRS .
    REQUIRES driver log
)

Now head over to the main.c file. The main folder contains the source code meaning the main.c file will be found here. Go to main > main.c and open it. Copy the code given below in that file and save it.

ESP32 ESP-IDF MAX7219 Module Code

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_idf_version.h>
#include <max7219.h>

#ifndef APP_CPU_NUM
#define APP_CPU_NUM PRO_CPU_NUM
#endif

#if ESP_IDF_VERSION < ESP_IDF_VERSION_VAL(4, 0, 0)
#define HOST    HSPI_HOST
#else
#define HOST    SPI2_HOST
#endif

#define CASCADE_SIZE 1
#define MOSI_PIN 23
#define CS_PIN 5
#define CLK_PIN 18


static const uint64_t symbols[] = {

    0x3c66666e76663c00, // digits 0-9
    0x7e1818181c181800, 
    0x7e060c3060663c00,
    0x3c66603860663c00,
    0x30307e3234383000,
    0x3c6660603e067e00,
    0x3c66663e06663c00,
    0x1818183030667e00,
    0x3c66663c66663c00,
    0x3c66607c66663c00 ,

    0x383838fe7c381000, // arrow up
    0x10387cfe38383800, //arrow down
    0x10307efe7e301000, //arrow right
    0x1018fcfefc181000 //arrow left
    
};
static const size_t symbols_size = sizeof(symbols) - sizeof(uint64_t) * CASCADE_SIZE;

void task(void *pvParameter)
{

    spi_bus_config_t cfg = {
       .mosi_io_num = MOSI_PIN,
       .miso_io_num = -1,
       .sclk_io_num = CLK_PIN,
       .quadwp_io_num = -1,
       .quadhd_io_num = -1,
       .max_transfer_sz = 0,
       .flags = 0
    };
    ESP_ERROR_CHECK(spi_bus_initialize(HOST, &cfg, 1));


    max7219_t dev = {
       .cascade_size = CASCADE_SIZE,
       .digits = 0,
       .mirrored = true
    };
    ESP_ERROR_CHECK(max7219_init_desc(&dev, HOST, MAX7219_MAX_CLOCK_SPEED_HZ, CS_PIN));
    ESP_ERROR_CHECK(max7219_init(&dev));

    size_t offs = 0;

   while (1)
    {
        for (uint8_t i=0; i<CASCADE_SIZE; i++)
        max7219_draw_image_8x8(&dev,i*8,(uint8_t *)symbols + i*8 + offs);
        vTaskDelay(pdMS_TO_TICKS(500));

        if (++offs == symbols_size)
            offs = 0;
    }
}

void app_main()
{
    xTaskCreatePinnedToCore(task, "task", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL, APP_CPU_NUM);
}

How does the Code Works?

We start off by including the necessary libraries for this project which includes the FreeRTOS libraries to create tasks and generate delays and max7219 library for the led matrix functionality.

#include <stdio.h>
#include <freertos/FreeRTOS.h>
#include <freertos/task.h>
#include <esp_idf_version.h>
#include <max7219.h>

Next we add some definitions that include the cascade size which we have set as ‘1’ as we will be using a single MAX7214 module.

#define CASCADE_SIZE 1

The SPI pins for MOSI, CS and CLK have been set as GPIO23, GPIO5 and GPIO18 respectively. These are also the default ESP32 VSPI SPI pins.

#define MOSI_PIN 23
#define CS_PIN 5
#define CLK_PIN 18

Next, we create the input matrix as 64 bit numbers in HEX formats. In this case, the state of the 8×8 LED matrix is represented as an unsigned long integer (uint64_t). The symbols depict digits from 0 to 9 and then arrows facing up, down, right and left. These images will be displayed on the LED matrix.

We used an online LED Matrix editor to form the symbols[] array. You can also use it to display your preferred animations.

static const uint64_t symbols[] = {

    0x3c66666e76663c00, // digits 0-9
    0x7e1818181c181800, 
    0x7e060c3060663c00,
    0x3c66603860663c00,
    0x30307e3234383000,
    0x3c6660603e067e00,
    0x3c66663e06663c00,
    0x1818183030667e00,
    0x3c66663c66663c00,
    0x3c66607c66663c00 ,

    0x383838fe7c381000, // arrow up
    0x10387cfe38383800, //arrow down
    0x10307efe7e301000, //arrow right
    0x1018fcfefc181000 //arrow left
    
};

The size of the symbol/image is calculated using the total size of the LED matrix subtracted by the size of number of modules (CASCADE_SIZE) being used.

static const size_t symbols_size = sizeof(symbols) - sizeof(uint64_t) * CASCADE_SIZE;

Inside the task() function, we first configure and initialize SPI bus and then configure and initialize the MAX7219 device to display the animations on the LED matrix.

void task(void *pvParameter)
{

    spi_bus_config_t cfg = {
       .mosi_io_num = MOSI_PIN,
       .miso_io_num = -1,
       .sclk_io_num = CLK_PIN,
       .quadwp_io_num = -1,
       .quadhd_io_num = -1,
       .max_transfer_sz = 0,
       .flags = 0
    };
    ESP_ERROR_CHECK(spi_bus_initialize(HOST, &cfg, 1));


    max7219_t dev = {
       .cascade_size = CASCADE_SIZE,
       .digits = 0,
       .mirrored = true
    };
    ESP_ERROR_CHECK(max7219_init_desc(&dev, HOST, MAX7219_MAX_CLOCK_SPEED_HZ, CS_PIN));
    ESP_ERROR_CHECK(max7219_init(&dev));

    size_t offs = 0;

   while (1)
    {
        for (uint8_t i=0; i<CASCADE_SIZE; i++)
        max7219_draw_image_8x8(&dev,i*8,(uint8_t *)symbols + i*8 + offs);
        vTaskDelay(pdMS_TO_TICKS(500));

        if (++offs == symbols_size)
            offs = 0;
    }
}

To configure the SPI bus, we set up the SPI configuration structure spi_bus_config_t. This will hold the SPI bus parameters including the pins being used. After that we will initialize the SPI bus with the SPI configuration structure by calling spi_bus_initialize() function. This master bus initialization function takes in three parameters which are the host id, pointer to the bus configuration structure and the dma channel.

    spi_bus_config_t cfg = {
       .mosi_io_num = MOSI_PIN,
       .miso_io_num = -1,
       .sclk_io_num = CLK_PIN,
       .quadwp_io_num = -1,
       .quadhd_io_num = -1,
       .max_transfer_sz = 0,
       .flags = 0
    };
    ESP_ERROR_CHECK(spi_bus_initialize(HOST, &cfg, 1));

Next, we will configure the MAX7219 device by setting the parameters of the display descriptor structure max7219_t. It holds the cascade size, the accessible digits and the state of the horizontal mirror display. After that, we initialize the MAX7219 module by calling max7219_init_desc() and max7219_init() functions side by side. The first function initializes the device on the SPI bus by taking in the pointer to the max7219_t structure, host id, clock speed of the device and the SPI CS pin set as parameters inside it. The second function takes the pointer to the max7219_t structure as a parameter inside it.

 max7219_t dev = {
       .cascade_size = CASCADE_SIZE,
       .digits = 0,
       .mirrored = true
    };
    ESP_ERROR_CHECK(max7219_init_desc(&dev, HOST, MAX7219_MAX_CLOCK_SPEED_HZ, CS_PIN));
    ESP_ERROR_CHECK(max7219_init(&dev));

Inside the infinite while loop, we will use a for loop to display the images on the LED matrix after a delay of 0.5 seconds. The max7219_draw_image_8x8() function is called inside the for loop which runs once in our case because the CASCADE_SIZE is set as 1. This function takes in three parameters which are the pointer to the max7219_t structure, the position for the starting digit and the 64 bit buffer with the image data respectively.

  size_t offs = 0;

   while (1)
    {
        for (uint8_t i=0; i<CASCADE_SIZE; i++)
        max7219_draw_image_8x8(&dev,i*8,(uint8_t *)symbols + i*8 + offs);
        vTaskDelay(pdMS_TO_TICKS(500));

        if (++offs == symbols_size)
            offs = 0;
    }

Inside the app_main() function, the task is created by calling xTaskCreatePinnedToCore() function.

void app_main()
{
    xTaskCreatePinnedToCore(task, "task", configMINIMAL_STACK_SIZE * 3, NULL, 5, NULL, APP_CPU_NUM);
}

Compiling the Sketch

To flash your chip, type the following command in the serial terminal. Remember to replace the COM port with the one through which your board is connected.

idf.py -p COMX flash monitor

After the code is successfully flashed, the MAX7219 Dot LED matrix starts displaying the different digits and the arrows repeatedly.

Watch the video below:

You may also like to read:

If you are using Arduino IDE instead of ESP-IDF, you can read this article:

1 thought on “ESP32 ESP-IDF with MAX7219 Dot Matrix Display Module”

Leave a Comment