Step‑by‑step guide to scroll text on a MAX7219 dot‑matrix display with STM32 via SPI. Includes font handling, bit‑endianness and shift functions. The project is available to download at the end of the post.

Recommended Resources:
This is the 3rd tutorial in a mini series covering the Dot Matrix Module with STM32 via SPI. In the previous tutorials we covered initialise the display and printing some characters on a single 8×8 dot matrix. We also saw how to cascade the displays together to act like a single big display.
Today we will see how to print and scroll string on these displays. You should take a look at the previous tutorial before continuing with this one.
Introducing Dot Matrix Module
The dot matrix module with MAX7219 is a compact display unit that uses an 8×8 LED matrix controlled by the MAX7219 driver chip. It simplifies the process of controlling multiple LEDs by handling all the multiplexing and current regulation internally. Commonly used in electronics projects for scrolling text, symbols, or simple animations, it communicates with microcontrollers using the SPI protocol, making it efficient and easy to integrate with platforms like STM32, Arduino, and ESP32.

Some of the important features of Dot Matrix Module are:
- Built-in MAX7219 Driver:
Handles LED scanning, multiplexing, and current control, reducing microcontroller workload. - SPI Interface:
Communicates efficiently over a 3-wire SPI protocol (DIN, CLK, CS), supporting daisy chaining of multiple modules. - 8×8 LED Display:
Offers 64 individually addressable LEDs in a matrix format, ideal for characters and patterns. - Cascadable Design:
Multiple modules can be connected in series to create larger displays for scrolling text or graphics.
CUBEMX CONFIGURATION
Clock Configuration
Below is the image showing the clock configuration.
The STM32F103 is clocked by the external 8MHz crystal. The system is running at maximum 72MHz clock.
SPI Configuration
Below is the image showing the SPI configuration.
I am using the SPI1 for this tutorial. The SPI is configured in the Half Duplex Mode. This is because we only need the SPI to send the data to the display, but not receive anything from it.
- The Data Size is set to 16 bits with MSV first. This is because the MAX7219 expects the data in 16 bit format.
- There is no requirement for the Baud Rate, so I have configured it around 2 Mbps.
- The Clock Polarity is set to HIGH and Clock Phase is set to 1 Edge. This is important and as per the MAX7129 datasheet, the CPOL must be 1.
The pins PA5 and PA7 are configured as the SCK and MOSI pins. We will also set the pin PA4 as output and rename it to CS.
WIRING DIAGRAM
Below is the image showing the connection between STM32F103 and Dot Matrix Display.
As you can see in the image above,
STM32 Pin | SPI Function | Connected to Device Pin |
---|---|---|
PA5 | SPI1_SCK | CLK |
PA7 | SPI1_MOSI | DIN |
PA4 | CS | CS |
The device is powered with 5V supply from a battery. You must use some external power source for these type of displays. There are a lot of LEDs connected, and when they all are turned ON, they consume a lot of current. This might damage the MCU. Therefor use some external 5V power supply for these type of displays.
The connection and cubeMX configuration will remain the same as we covered in previous tutorial.
You must Read the PART1 and PART2 before continuing with this one. These parts are written in series, and I am not going to cover the functions which have already been explained previously.
SHIFTING THE DISPLAY
We will start by shifting the display towards the left. This is needed to create a scroll like effect. Below is the function for the same.
void shiftLeft (void)
{
for (int cnt=NUM_DEV*8-2; cnt>=0; cnt--)
{
bufferCol[cnt+1] = bufferCol[cnt];
}
bufferCol[0] = 0;
flushBuffer();
}
The shiftLeft
function will shift the entire display to the left by 1 position. We know that the columns are arranged such that the rightmost column is COL1 and the leftmost is COL31. To shift the display to the left, we need to copy the data from the current column to the left column, which is 1 higher than the current column. The copying must start from the higher end, so that we can avoid the same data being passed to all the columns.
The cnt variable keep track of the current column we are working with.
Imagine when the cnt value is 31. Inside the for loop, we will copy the content of the COL31 into COL32. But COL32 can not exist for a cascade of 4 devices, hence we must start the cnt value from NUM_DEV*8-2, i.e 30.
Again when the cnt value is 0, the content of COL0 will be copied into COL1. But the for loop will end here and the content of the COL0 will remain unchanged. Here we need to manually update the content of COL0. We can either set it to 0, or we can assign the COL31 value to it so that there will be a circular effect.
The flushBuffer
function will then flush the updated column data to the display.
Let’s test this function. Below is the code from the main()
const uint8_t data[] ={ 0x00, 0x22, 0x41, 0x49, 0x49, 0x49, 0x36, 0x00}; // NUM 3
int main()
{
....
matrixInit();
clearDisplay();
for (int i=0; i<8; i++)
{
bufferCol[i] = data[7-i];
}
flushBuffer();
while (1)
{
shiftLeft();
HAL_Delay(100);
}
}
Here we will first copy the data for the number 3 into the start of the column buffer. Then call the function flsuhBuffer
to display this number on the device.
Then call the shiftLeft
function is the while loop every 100ms. This will continuously shift the entire display to the left.
Below is the video showing the output of the above code.
You can see the number 3 has been shifted all the way to the left. The shiftLeft function will shift everything on the display, hence we will utilise this function for scrolling string.
HOW TO USE THE FONTS
I have found a font library which can almost work with our setup. The font file contain the data for all the possible characters.
Below is the data for the number 1 from the font file.
{ 0x40, 0x42, 0x7F, 0x7F, 0x40, 0x40, 0x00, 0x00 }, // '1''
If we plot this data on the character generator tool with our setup, we will get the character as shown in the image below.
We got a inverted 1. That is why I said the library can “almost” work with our setup. We will get all the characters inverted if we use the library as it is.
Below is the image showing the data output, if we generate the number 1 with our setup.
The data output above does not look similar to what we have in the font file. But if we change the endianness in the character generator tool, we will get the exact data that we have in the font file. This is shown below.
This means that the font data is fine, we just need to change the endianness of each byte. The data in the font file is in Little Endian format and we need to change it to the Big Endian.
Changing the endianness is not that hard. Below are the images showing what it means.
As shown in the images above, Little Endian means that the data in the columns is arranged such that the Least Significant Bit is at the TOP. Whereas in Big Endian format, the Least Significant Bit is at the Bottom. So we just need to change the order of the bits before storing them to the main column buffer.
SHIFTING A CHARACTER
Below id the function for shifting a character on the display.
void shiftchar (uint8_t ch, int delay)
{
int indx=0;
for (int i=0; i<FONT_WIDTH-1; i++) // loop for all the bytes of the font
{
uint8_t data = 0;
/* Chnage the order of the bits */
for (int j=7; j>=0; j--) // extract bits from a single byte
{
if ((MAX7219_Dot_Matrix_font[ch][indx])&(1<<j)) // if the bit is 1 // start extracting from the 7th bit of byte
{
data |= (1<<(7-j)); // start writing it from the 0th bit of data
}
}
bufferCol[0] = data; // store the modified byte to the first element only. It will shift later
flushBuffer();
shiftLeft();
indx++;
HAL_Delay(delay);
}
}
Inside this function we will change the order of the bits for a single byte and then store the updated data byte in the bufferCol[0]. This column will print on the display and then its content will shift to bufferCol[1]. We will repeat this process for the entire font width, so that we can get the complete character on the display.
To change the order of the bits, we will take a single byte data for the specified character from the font file. Then check the bit at the 7th position of this byte. If the bit is a 1, we will write a 1 to the 0th position of the data byte. Next we will check the bit at the 6th position and update the 1st position of the data byte according to it.
The delay function controls how quickly we want to shift each column of the specific character.
Let’s test the above function. Below is the code for the main file.
int main()
{
....
matrixInit();
clearDisplay();
shiftchar('C', 250);
while (1)
{}
}
Here we will simply call the function shiftchar
to display the character ‘C’ on the device. Below is the video showing the output of the above code.
The character ‘C’ is successfully shifted on the display. We printed this character using the font library, so this means that we can use the font library for rest of the project as well.
SCROLLING THE STRING
Scrolling the string is same as shifting a character. Here we will pass all the characters from the string to the shiftChar function.
void scrollString (char *str, int delay)
{
while (*str)
{
shiftchar(*str, delay);
*str++;
}
}
int main ()
{
....
matrixInit();
clearDisplay();
while (1)
{
scrollString((uint8_t *)"Hello World ", 100);
}
}
Here we will call the scrollString function inside the while loop, so that we can have a continuous scrolling effect. Below is the gif showing the output of the above code.
PRINT THE STRING
We were able to scroll the string on the display. Now we will see how to print a string on it. Below is the function to print a string on the display.
void printString (uint8_t *str)
{
int strindx = 0;
for (int k = NUM_DEV*8-1; k>=0; )
{
int indx=0;
for (int i=0; i<FONT_WIDTH-1; i++) // loop for all the bytes of the font
{
uint8_t data = 0;
/* Chnage the order of the bits */
for (int j=7; j>=0; j--) // extract bits from a single byte
{
if ((MAX7219_Dot_Matrix_font[str[strindx]][indx])&(1<<j)) // if the bit is 1 // start extracting from the 7th bit of byte
{
data |= (1<<(7-j)); // start writing it from the 0th bit of data
}
}
bufferCol[k--] = data; // store the modified byte to the first element only. It will shift later
indx++;
}
strindx++;
}
flushBuffer();
}
Inside this function, we will do the same as we did for the shiftChar function. We will get the data bytes for the respective character from the font library, and then change the order of the bits.
The updated data byte will be stored, starting from the COL31. This is necessary for the string to display from left to right.
Let’s test the above function. Below is the code from the main function.
int main ()
{
....
matrixInit();
clearDisplay();
printString("1234");
while (1)
{}
}
Here we will print the number 1234 on the display. Below is the image showing the output of the above code.
You can see the number has been printed on the display. The display acts as one single unit, and therefore the characters are overlapping between the devices.
VIDEO TUTORIAL
You can check the video to see the complete explanation and working of this project.
Check out the Video Below
PROJECT DOWNLOAD
Info
You can help with the development by DONATING Below.
To download the project, click the DOWNLOAD button.