Hi there. In this article, I'll take a look at the MAX7219 integrated circuit and explain, how to drive an 8x8 LED Matrix with this IC. Since there is an Arduino library for this chip, I'll use Arduino for my examples. However, SPI (Serial Peripheral Interface) protocol is independent of microcontroller and my examples can be easily ported to other microcontrollers as well. MAX7219 is a fairly simple IC, it is also really easy to use it without any library. I'll give two versions of an example code, one with library and another without.
MAX7219 and MAX7221 ICs
MAX7219 and MAX7221 ICs provide an interface to 7 segment displays or 8x8 LED matrices via SPI. While they are pin and instruction compatible, they share same datasheet and same functions, MAX7221 supports other serial protocols besides SPI and operates in a more robust way. Therefore, it is also more expensive.
These ICs support up to eight 7 segment displays with decimal points, bar graph and 8x8 LED matrix displays, and have builtin decoding options for them. Up to 64 LEDs can be driven through 8 common cathodes. As I bought an assembled kit, I won't dive deeper into IC pin connections.
These ICs can also be cascaded up to eight times, which means, by purchasing eight of these modules and connecting their DOUT pin to the DIN pin of next module in a chain, I can drive a larger number of LED displays. The second 5-pin connector on the distal side of the kit is for cascading. Different displays can also be cascaded.
Quad 8x8 LED matrix displays are also available as pre-assembled kits. I also bought a quad kit, to understand and experiment cascading. I'll explain it in detail later in this article.
By looking at the PCB from back, you can see how simply and easily the cascading is done:
The module has five pins. Vcc and GND pins require no explanation. DIN (Data In) pin is where the data is written to. CS (Chip Select) pin is active low. When this pis is set to active, the latches are enabled and the data coming thru DIN is received by the internal shift registers. In the meantime, CLK carries the clock signal synchronously with the data coming from DIN. The data is processed with the rising edge of CS.
Registers of MAX7219
At the beginning of this article, I mentioned that the MAX7219 is a fairly simple IC. All the functionality is handled by a total of 14 registers and eight of them are simple data registers that control LEDs. For convenience, I've copied the table of registers from the datasheet and pasted it to the right side.
All of these addresses (or commands from another perspective) are 8-bit, and each of them is followed by another 8-bit data (or operand). In other words, each data packet arriving at DIN must be 16-bit. To send data, CS' (nChipSelect) signal must be set to logic zero first, and the data must be sent to DIN synchronously with CLK. Setting CS' back to logic 1 terminates data transmission. Communication is described in detail in the "Serial Addressing Modes" section of the datasheet.
I'll come to the first register No-Op later, because No-Op doesn't actually perform a No-Op.
The registers Digit 0 to Digit 7 are holding the LED states, i.e. a row of a 8x8 LED Matrix display or a segment of a 7-segment display. For example, by sending the value 0x0F to the Digit 0 register, i.e. by pushing the data 0x01 0x0F onto data bus, I turn the rightmost four LEDs of the first row on, and turn the leftmost four off. For Digit 1, this controls the second LED row, for Digit 2, the third row, and so on.
Decode Mode (0x09) controls the internal 7-segment decoder unit of the IC. If it has 0xFF, the ICs only looks at the lower four bits of Digit registers and decodes them for a 7-segment display. If it has 0x00, no decoding is performed. This is the appropriate mode for 8x8 LED Matrix displays.
Intensity (0x0A) register is used to adjust the LED intensity with PWM.
Scan Limit (0x0B) register is used to optimize the scan rate of LEDs, if not all LEDs will be used (e.g. a 7-segment display without decimal point), by deactivating unused LED pins. As all LEDs are used in a LED Matrix display this should be usually zero.
If the shutdown (0x0C) register is zero, the IC shuts itself down. The scan oscillator of IC is shut down and all LEDs turn off. The supply current drops to 150 µA. It is in mA range during normal operation, and is approx. 300 mA when all LEDs are on. The IC boots up in shutdown mode. Therefore, first step of initialization is to write 0x1 to this register.
When 0x1 is written to the display test (0x0F) register, all LEDs turns on. This allows you to check if any of them are faulty. During the test, Digit registers are not touched, the output is overridden. When 0x0 is written here, IC returns to its normal operating mode.
No-Op operation (0x0) is used for cascading ICs. This has no effect on the display receiving the command. The IC receiving a No-Op simply sends the subsequent command via DOUT pin. For example, in a quad 8x8 LED matrix display, if you need to do something just with the fourth display, the microcontroller issues three No-Op commands followed by the actual operation. In this case, first display receives these data, stripes first No-Op and sends two No-Ops followed by the actual operation to the second module via its DOUT pin. Second display likewise sends just one No-Op and the actual operation to the third display. The third display receives the remaining No-Op and the operation destined to fourth display and forwards only the actual operation one last time to the fourth. Below is a diagram illustrating this process.
The block diagram in the datasheet doesn't show a register named "No-Op", but since it is covered under the "No-Op Register" section on page ten, I can't tell if this is an operation or a register. By the way, as it can be seen above, even though it's completely meaningless, even No-Op command is 16-bit, so it has be to packed with a 8-bit data.
Arduino LedControl Library
After explaining the registers in such detail, using a library may actually seem pointless, but the LedControl library simplifies some tasks quite a lot. For example, while Digit registers provide row-by-row access to LEDs, the library has some other functions like setColumn(), setLed() and setChar() in addition to setRow().
To install the library, go to Tools -> Manage Libraries in Arduino IDE and search for "LedControl" and click Add. Then, include LedControl.h header file in your code and create a LedControl object. While creating this, you need to pass which pin is connected to which Arduino pin and how many devices are cascaded, to constructor function. E.g.
LedControl lc = LedControl(data = 12, clk = 11, chipSel = 10, 4);
In all my examples, DIN is connected to Arduino's 12th pin, CLK to the 11th and CS to the 10th pin, like this:
The Matrix display image in Fritzing has six pins. The top pin is not connected, which is the second Vcc, so it doesn't really matter.
Code Examples
My first example is a simple character scrolling. Here, the characters are scrolled module by module. There won't be any bit operations. I uploaded the code to my github account. As I mentioned before, MAX7219 starts up in shutdown mode. In the for loop, on line 61 inside the setup() routine, each display is first taken out of shutdown mode one by one, and LED intensity is set to lowest. The "dizi" array holds the character sequence to be displayed. It is actually a string variable in broader sense. At the end of the array, the first three characters repeat for endless scrolling effect on display.
The "table" array contains the bitmaps of characters. I downloaded a font package from here, and used BIOS.F08 font. This file contains the classic 8x8 BIOS font. I opened it in GIMP and exported it as C source code or C header. Since the entire character table 2 KB (256 * 8), it is not possible to to load whole table to Arduino UNO's 2 KB RAM, yet it's not necessary anyway. I only imported the characters, that is going to be displayed. In the main for loop, the values of bitmaps are sent to MAX7219 row by row using the setRow() function. Although the character sequence is 20 characters long, when the pointer value is 16, 16th, 17th, 18th and 19th characters will appear on the display, so the pointer must not exceed 20 - 4.
Second example (counter) is a counter as its name suggests, which is even simpler than the first example. Arduino increments the values in the "a" array in full speed, starting from the zero-indexed element. In the for loop on line 25, the array elements are checked for overflow at byte boundary. If an overflow occurs, this array element is reset, and the carry is transferred to the next element (line 29), and this binary counter is visualized with LEDs on line 32.
Counting from zero to 256 takes less than a second. The LED corresponding to the tenth bit flashes approximately at one second intervals. If we ignore less significant nine bits, it would take roughly 2(64-10)=254 seconds for the remaining 54 LEDs to light up completely, which is about 5.709 * 108 (571 million) years.
My third example is the same of the second one, but I wrote it without library. In this code, the pins are first set to OUTPUT. Then, the IC is taken out of shutdown mode (line 32), the scan limit is set to 7 (line 37), decoding is disabled on line 43, as we have an 8x8 LED display. That's the initialization sequence. On line 51, the LED intensity is set to the lowest level and all LEDs are cleared (line 60). The logic in the loop() procedure is same as the above example, with one difference. Each array element is written to the corresponding digit register directly row by row (line 77).
As you can see, the IC can also be easily programmed without library. My goal here was not perform a speed test with or without library. I'd probably get similar results anyway. But if my focus were speed, I would be using Assembly.
In the fourth and final example, I created a smooth scrolling text using bit operations. To do this, I took two long German words. German is really perfect for this task. I converted these words into char arrays (lines 16 or 19). I then created a bitmap table like I did in the first example, but with more characters this time. As usual, the IC is initialized in the setup() routine, and the bitmap images of characters are copied to the "kayanyazi" array.
In the loop() function, the first four characters of "kayanyazi" array are sent to display. Since the text will be scrolled to the left, I assign the most significant bits (MSB) of each LED line to the "carry_old" variable, which means, carry_old contains the first column of character array (or first LED column). Then all characters are shifted by one bit (line 108), but the first column of each character is copied to carry_new (line 106) before any shift operation, so that any carry bits of byte order is kept before it gets lost and this is inserted to the least significant bit (LSB) of the trailing character.










