18. Chapter LED Matrix

In the previous chapter, we have learned how to use some modules and sensors and shows some information on the computer through serial port. Now let us learn some modules which can output images and text.

In this chapter, we will learn how to use the LED matrix to output characters and images.

18.1. Project 74HC595

Firstly, let us learn how to use the 74HC595 chip, which is very helpful for us to control the LED matrix.

18.1.1. Component List

Control board x1

Chapter01_00

Breadboard x1

Chapter02_00

GPIO Extension Board x1

Chapter02_01

USB cable x1

Chapter01_02

Jumper M/M x15

Chapter01_03

74HC595 x1

Chapter18_00

LED bar graph x1

Chapter18_01

Resistor 220Ω x8

Chapter18_02

18.1.2. Code Knowledge

18.1.2.1. Hexadecimal

The conversion between binary and decimal system has been mentioned before. When you write the code, the number is decimal by default. Hexadecimal numbers need to add the 0x prefix in the code, such as 0x01.

One Hexadecimal bit can present one number between 0-15. In order to facilitate writing, the numbers greater than 9 are written into the letter A-F (case-insensitive) such as 0x2A. The corresponding relationship is as follows:

Number

0

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

Represent

0

1

2

3

4

5

6

7

8

9

A

B

C

D

E

F

Conversion between hexadecimal and decimal system is similar to the conversion between hexadecimal and binary such as the sixteen digit 0x12:

Sequence

1

0

Number

1

2

Conversion between hexadecimal and decimal system is similar to the conversion between hexadecimal and binary such as the sixteen digit 0x12:

Sequence

1

0

Number

1

2

When a hexadecimal number need to be converted to decimal number, first, the nth number of it need be multiplied by n power of 16, then sum all multiplicative results. Take 0x12 as an example:

1*16^1+2*16^0=18

When decimal number is converted to hexadecimal number, decimal number is divided by 16. Then we will get quotient and remainder, and quotient obtained will be continuously divided by 16 until quotient is zero. Arrange all remainders from right to left in a line. Then we complete the conversion. For example:

../../../_images/Chapter18_03.png

The result is of the conversion 0x12.

When you write code, sometimes it is convenient to use hexadecimal, especially involving bit operation, because 1 hexadecimal number can be expressed by 4 binary number (2^4=16). The corresponding relationship between 4 bit binary numbers and 1 hexadecimal number is shown as follows:

4 bit binary

0000

0001

0010

0011

0100

0101

0110

0111

1 figure of hexadecimal

0

1

2

3

4

5

6

7

4 bit binary

1000

1001

1010

1011

1100

1101

1110

1111

1 figure of hexadecimal

8

9

A

B

C

D

E

F

For example, binary 00010010 is corresponding to hexadecimal 0x12.

18.1.3. Component Knowledge

18.1.3.1. 74HC595

A 74HC595 chip is used to convert serial data into parallel data. A 74HC595 chip can convert the serial data of one byte into 8 bits, and send its corresponding level to each of the 8 ports correspondingly. With this characteristic, the 74HC595 chip can be used to expand the IO ports the control board. At least 3 ports on the RPI board are required to control the 8 ports of the 74HC595 chip.

../../../_images/Chapter18_04.png

The ports of 74HC595 are described as follows:

Pin name

Pin number

Description

Q0-Q7

15, 1-7

Parallel data output

VCC

16

The positive electrode of power supply, the voltage is 2~6V

GND

8

The negative electrode of power supply

DS

14

Serial data Input

OE

13

Enable output,

When this pin is in high level, Q0-Q7 is in high resistance state

When this pin is in low level, Q0-Q7 is in output mode

ST_CP

12

Parallel update output: when its electrical level is rising, it will update the parallel data output.

SH_CP

11

Serial shift clock: when its electrical level is rising, serial data input register will do a shift.

MR

10

Remove shift register: When this pin is in low level, the content in shift register will be cleared .

Q7’

9

Serial data output: it can be connected to more 74HC595 in series.

For more detail, please refer to the datasheet.

18.1.4. Circuit

Use pin 11, 12, 13 on the control board to control the 74HC595, and connect it to the 8 LEDs of LED bar graph.

Schematic diagram

Chapter18_05

Hardware connection

If you need any support, please feel free to contact us via: support@freenove.com

Chapter18_06

18.1.5. Sketch

18.1.5.1. Sketch 74HC595

Now write code to control the 8 LEDs of LED bar graph through 74HC595.

 1/**********************************************************************
 2  Filename    : Sketch_18.1.1_74HC595
 3  Description : 74HC595
 4  Auther      : www.freenove.com
 5  Modification: 2024/08/05
 6**********************************************************************/
 7
 8int latchPin = 12;          // Pin connected to ST_CP of 74HC595(Pin12)
 9int clockPin = 13;          // Pin connected to SH_CP of 74HC595(Pin11)
10int dataPin = 11;           // Pin connected to DS of 74HC595(Pin14)
11
12void setup() {
13  // set pins to output
14  pinMode(latchPin, OUTPUT);
15  pinMode(clockPin, OUTPUT);
16  pinMode(dataPin, OUTPUT);
17}
18
19void loop() {
20  // Define a one-byte variable to use the 8 bits to represent the state of 8 LEDs of LED bar graph.
21  // This variable is assigned to 0x01, that is binary 00000001, which indicates only one LED light on.
22  byte x = 0x01;
23  for (int j = 0; j < 8; j++) {
24    // Output low level to latchPin
25    digitalWrite(latchPin, LOW);
26    // Send serial data to 74HC595
27    shiftOut(dataPin, clockPin, LSBFIRST, x);
28    // Output high level to latchPin, and 74HC595 will update the data to the parallel output port.
29    digitalWrite(latchPin, HIGH);
30    // make the variable move one bit to left once, then the bright LED move one step to the left once.
31    x <<= 1;
32    delay(100);
33  }
34}

In the code, we configure three pins to control the 74HC595. And define a one-byte variable to control the state of 8 LEDs through the 8 bits of the variable. The LED lights on when the corresponding bit is 1. If the variable is assigned to 0x01, that is 00000001 in binary, there will be only one LED on.

1byte x = 0x01;

In each loop, the x is sent to 74HC595. The sending process is as follows:

1// Output low level to latchPin
2digitalWrite(latchPin, LOW);
3// Send serial data to 74HC595
4shiftOut(dataPin, clockPin, LSBFIRST, x);
5// Output high level to latchPin, and 74HC595 will update the data to the parallel output port.
6digitalWrite(latchPin, HIGH);

The x will be shift 1 bit to left in each cycle, which makes the bright LED of the 8 LEDs move one bit.

1x <<= 1;
<< operator

“<<” is the left shift operator, which can make all bits of 1 byte shift by several bits to the left (high) direction and add 0 on the right (low). For example, shift binary 00000001 by 1 bit to left:

byte x = 1 << 1;

../../../_images/Chapter18_07.png

The result of x is 2(binary 00000010).

../../../_images/Chapter18_08.png

There is another similar operator” >>”. For example, shift binary 00000001 by 1 bit to right:

byte x = 1 >> 1;

../../../_images/Chapter18_09.png

The result of x is 0(00000000).

../../../_images/Chapter18_10.png

X <<= 1 is equivalent to x = x << 1 and x >>= 1 is equivalent to x = x >> 1

Verify and upload the code, and then you will see the LED bar graph with the effect of flowing water.

../../../_images/Chapter18_11.png

18.2. Project LED Matrix

In the previous section, we have used 74HC595 to control 8 LEDs of the LED bar graph. Now let’s use 74HC595 to control LED matrix.

18.2.1. Component List

Control board x1

Chapter01_00

Breadboard x1

Chapter02_00

GPIO Extension Board x1

Chapter02_01

USB cable x1

Chapter01_02

Jumper M/M x15

Chapter01_03

74HC595 x1

Chapter18_00

LED matrix x1

Chapter18_12

Resistor 220Ω x8

Chapter18_02

18.2.2. Component Knowledge

18.2.2.1. LED matrix

An LED Matrix is a rectangular display module that consists of a uniform grid of LEDs. The following is an 8X8 monochrome (one color) LED Matrix containing 64 LEDs (8 rows by 8 columns).

../../../_images/Chapter18_13.png

In order to facilitate the operation and reduce the number of ports required to drive this component, the Positive Poles of the LEDs in each row and Negative Poles of the LEDs in each column are respectively connected together inside the LED Matrix module, which is called a Common Anode. There is another arrangement type. Negative Poles of the LEDs in each row and the Positive Poles of the LEDs in each column are respectively connected together, which is called a Common Cathode.

The LED Matrix that we use in this project is a Common Anode LED Matrix.

../../../_images/Chapter18_14.png

Here is how a Common Anode LED Matrix works. First, choose 16 ports on RPI board to connect to the 16 ports of LED Matrix. Configure one port in columns for low level, which makes that column the selected port. Then configure the eight port in the row to display content in the selected column. Add a delay value and then select the next column that outputs the corresponding content. This kind of operation by column is called Scan. If you want to display the following image of a smiling face, you can display it in 8 columns, and each column is represented by one byte.

../../../_images/Chapter18_15.png

Column

Binary

Hexadecimal

1

0001 1100

0x1c

2

0010 0010

0x22

3

0101 0001

0x51

4

0100 0101

0x45

5

0100 0101

0x45

6

0101 0001

0x51

7

0010 0010

0x22

8

0001 1100

0x1c

Scanning rows is another option to display on an LED Matrix (dot matrix grid). Whether scanning by row or column, 16 GPIO is required. In order to save GPIO ports of control board, two 74HC595 IC Chips are used in the circuit.

18.2.3. Circuit

Use pin 11, 12, 13 on control board to control the 74HC595. And connect 74HC595 to the 8 anode pins of LED Matrix, in the meanwhile, connect 8 digitals port on control board to the 8 cathode pins of LED Matrix.

Schematic diagram

Chapter18_16

Hardware connection

If you need any support, please feel free to contact us via: support@freenove.com

Chapter18_17

18.2.4. Sketch

18.2.4.1. Sketch LED_Matrix

Now write the code to drive LED dot matrix to display static and dynamic images, in fact, the dynamic image is formed by continuous static image.

 1/**********************************************************************
 2  Filename    : Sketch_18.2.1_LED_Matrix
 3  Description : LED Matrix
 4  Auther      : www.freenove.com
 5  Modification: 2024/08/05
 6**********************************************************************/
 7
 8int latchPin = 12;          // Pin connected to ST_CP of 74HC595(Pin12)
 9int clockPin = 13;          // Pin connected to SH_CP of 74HC595(Pin11)
10int dataPin = 11;           // Pin connected to DS of 74HC595(Pin14)
11int LEDPin[] = {2, 3, 4, 5, 6, 7, 8, 9};    // column pin (cathode) of LED Matrix
12
13// Define the pattern data for a smiling face
14const int smilingFace[] = {
15  0x1C, 0x22, 0x51, 0x45, 0x45, 0x51, 0x22, 0x1C
16};
17// Define the data of numbers and letters, and save them in flash area
18const int data[] PROGMEM = {
19  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // " "
20  0x00, 0x00, 0x21, 0x7F, 0x01, 0x00, 0x00, 0x00, // "1"
21  0x00, 0x00, 0x23, 0x45, 0x49, 0x31, 0x00, 0x00, // "2"
22  0x00, 0x00, 0x22, 0x49, 0x49, 0x36, 0x00, 0x00, // "3"
23  0x00, 0x00, 0x0E, 0x32, 0x7F, 0x02, 0x00, 0x00, // "4"
24  0x00, 0x00, 0x79, 0x49, 0x49, 0x46, 0x00, 0x00, // "5"
25  0x00, 0x00, 0x3E, 0x49, 0x49, 0x26, 0x00, 0x00, // "6"
26  0x00, 0x00, 0x60, 0x47, 0x48, 0x70, 0x00, 0x00, // "7"
27  0x00, 0x00, 0x36, 0x49, 0x49, 0x36, 0x00, 0x00, // "8"
28  0x00, 0x00, 0x32, 0x49, 0x49, 0x3E, 0x00, 0x00, // "9"
29  0x00, 0x00, 0x3E, 0x41, 0x41, 0x3E, 0x00, 0x00, // "0"
30  0x00, 0x00, 0x3F, 0x44, 0x44, 0x3F, 0x00, 0x00, // "A"
31  0x00, 0x00, 0x7F, 0x49, 0x49, 0x36, 0x00, 0x00, // "B"
32  0x00, 0x00, 0x3E, 0x41, 0x41, 0x22, 0x00, 0x00, // "C"
33  0x00, 0x00, 0x7F, 0x41, 0x41, 0x3E, 0x00, 0x00, // "D"
34  0x00, 0x00, 0x7F, 0x49, 0x49, 0x41, 0x00, 0x00, // "E"
35  0x00, 0x00, 0x7F, 0x48, 0x48, 0x40, 0x00, 0x00  // "F"
36};
37
38void setup() {
39  // set pins to output
40  pinMode(latchPin, OUTPUT);
41  pinMode(clockPin, OUTPUT);
42  pinMode(dataPin, OUTPUT);
43  for (int i = 0; i < 8; i++) {
44    pinMode(LEDPin[i], OUTPUT);
45  }
46}
47
48void loop() {
49  // Define a one-byte variable (8 bits) which is used to represent the selected state of 8 column.
50  int cols;
51  // Display the static smiling pattern
52  for (int j = 0; j < 500; j++ ) {  // repeat 500 times
53    cols = 0x01; // Assign 0x01(binary 00000001) to the variable, which represents the first column is selected.
54    for (int i = 0; i < 8; i++) {   // display 8 column data by scaning
55      matrixColsVal(cols);          // select this column
56      matrixRowsVal(smilingFace[i]);// display the data in this column
57      delay(1);                     // display them for a period of time
58      matrixRowsVal(0x00);          // clear the data of this column
59      cols <<= 1;                   // shift"cols" 1 bit left to select the next column
60    }
61  }
62  // Display the dynamic patterns of numbers and letters
63  for (int i = 0; i < 128; i++) { // "space,0-9,A-F"16 letters ,each letter hold 8 columns, total 136 columns. Firstly, display space ,then we need shift 128 times(136-8)
64    for (int k = 0; k < 10; k++) {      // repeat image of each frame 10 times.
65      cols = 0x01;      // Assign binary 00000001. Means the first column is selected.
66      for (int j = i; j < 8 + i; j++) { // display image of each frame
67        matrixColsVal(cols);            // select this column
68        matrixRowsVal(pgm_read_word_near(data + j));// display the data in this column
69        delay(1);                       // display them for a period of time
70        matrixRowsVal(0x00);            // close the data of this column
71        cols <<= 1;                 // shift"cols" 1 bit left to select the next column
72      }
73    }
74  }
75}
76
77void matrixRowsVal(int value) {
78  // make latchPin output low level
79  digitalWrite(latchPin, LOW);
80  // Send serial data to 74HC595
81  shiftOut(dataPin, clockPin, LSBFIRST, value);
82  // make latchPin output high level, then 74HC595 will update the data to parallel output
83  digitalWrite(latchPin, HIGH);
84}
85
86void matrixColsVal(byte value) {
87  byte cols = 0x01;
88  // Output the column data to the corresponding port.
89  for (int i = 0; i < 8; i++) {
90    digitalWrite(LEDPin[i], ((value & cols) == cols) ? LOW : HIGH);
91    cols <<= 1;
92  }
93}

In the code, use an array to define the column pins of LED Matrix.

1const int smilingFace[] = {
2  0x1C, 0x22, 0x51, 0x45, 0x45, 0x51, 0x22, 0x1C
3};

Use another array to define some numbers and letters, and every eight elements of the array represent a dot matrix pattern data of a number or a letter.

 1const int data[] PROGMEM = {
 2  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, // " "
 3  0x00, 0x00, 0x21, 0x7F, 0x01, 0x00, 0x00, 0x00, // "1"
 4  0x00, 0x00, 0x23, 0x45, 0x49, 0x31, 0x00, 0x00, // "2"
 5  0x00, 0x00, 0x22, 0x49, 0x49, 0x36, 0x00, 0x00, // "3"
 6  0x00, 0x00, 0x0E, 0x32, 0x7F, 0x02, 0x00, 0x00, // "4"
 7  0x00, 0x00, 0x79, 0x49, 0x49, 0x46, 0x00, 0x00, // "5"
 8  0x00, 0x00, 0x3E, 0x49, 0x49, 0x26, 0x00, 0x00, // "6"
 9  0x00, 0x00, 0x60, 0x47, 0x48, 0x70, 0x00, 0x00, // "7"
10  0x00, 0x00, 0x36, 0x49, 0x49, 0x36, 0x00, 0x00, // "8"
11  0x00, 0x00, 0x32, 0x49, 0x49, 0x3E, 0x00, 0x00, // "9"
12  0x00, 0x00, 0x3E, 0x41, 0x41, 0x3E, 0x00, 0x00, // "0"
13  0x00, 0x00, 0x3F, 0x44, 0x44, 0x3F, 0x00, 0x00, // "A"
14  0x00, 0x00, 0x7F, 0x49, 0x49, 0x36, 0x00, 0x00, // "B"
15  0x00, 0x00, 0x3E, 0x41, 0x41, 0x22, 0x00, 0x00, // "C"
16  0x00, 0x00, 0x7F, 0x41, 0x41, 0x3E, 0x00, 0x00, // "D"
17  0x00, 0x00, 0x7F, 0x49, 0x49, 0x41, 0x00, 0x00, // "E"
18  0x00, 0x00, 0x7F, 0x48, 0x48, 0x40, 0x00, 0x00  // "F"
19};
PROGMEM keyword

Microprocessors generally have two storage areas, namely ROM and RAM. ROM is used to store code. And these stored data will not change with the execution of code until the code are uploaded. RAM is used to store data, for example, the variables we defined are stored here. The stored data will change in real time with the execution of the code. Generally, capacity of RAM is small. So we can use PROGMEM keyword to save the data that don’t change in ROM.

Define two functions, one of them uses control board port to select the column.

1void matrixRowsVal(int value) {
2  // make latchPin output low level
3  digitalWrite(latchPin, LOW);
4  // Send serial data to 74HC595
5  shiftOut(dataPin, clockPin, LSBFIRST, value);
6  // make latchPin output high level, then 74HC595 will update the data to parallel output
7  digitalWrite(latchPin, HIGH);
8}

Another one uses 74HC595 to write data of the row.

1void matrixColsVal(byte value) {
2  byte cols = 0x01;
3  // Output the column data to the corresponding port.
4  for (int i = 0; i < 8; i++) {
5    digitalWrite(LEDPin[i], ((value & cols) == cols) ? LOW : HIGH);
6    cols <<= 1;
7  }
8}
? :  operator

“? :” operator is similar to conditional statements. When the expression in front of “?” is tenable, the statement in front of “:” will be executed. When the expression is not tenable, the statement behind “:” will be executed. For example:

int a = (1 > 0) ? 2: 3;

Because 1>0 is tenable, so “a” will be assigned to 2.

Bitwise logical operation

There are many bitwise logical operators such as and (&), or (|), xor (^), negate (~). The result of exclusive or (^) is true only when two corresponding bit is not equal.

&, | and ^ is used to operate the corresponding bit of two numbers. Such as:

byte a = 1 & 2;

“a” will be assigned to 0. The calculation procedure is as follows:

1(00000001)

& 2(00000010)

0(00000000)

Negate (~) is used to negate a number, for example:

byte a = ~15;

“a” will be assigned to 240. The calculation procedure is as follows:

~ 15(00001111)

240(11110000)

In the loop () function, firstly, show the static smile pattern. Select one of the 8 columns circularly in turn to display each the result. Repeat 500 times the process, then we can see a static smile pattern.

 1for (int j = 0; j < 500; j++ ) {  // repeat 500 times
 2  cols = 0x01; // Assign 0x01(binary 00000001) to the variable, which represents the first column is selected.
 3  for (int i = 0; i < 8; i++) {   // display 8 column data by scaning
 4    matrixColsVal(cols);          // select this column
 5    matrixRowsVal(smilingFace[i]);// display the data in this column
 6    delay(1);                     // display them for a period of time
 7    matrixRowsVal(0x00);          // clear the data of this column
 8    cols <<= 1;                   // shift"cols" 1 bit left to select the next column
 9  }
10}

Then display the dynamic pattern of the numbers and letters. We have defined space, 0-9, A-F, total of 16 characters (136 columns) in an array, among which 8 adjacent rows of data form one frame. Shift one column once. There are for 128 frames of image from the first frame (1-8) to the last frame (128-136 column). Each frame image is displayed 10 times, then display the next frame. Repeat the process above, then we can see the pattern of scrolling numbers and letters.

 1for (int i = 0; i < 128; i++) { // "space,0-9,A-F"16 letters ,each letter hold 8 columns, total 136 columns. Firstly, display space ,then we need shift 128 times(136-8)
 2  for (int k = 0; k < 10; k++) {      // repeat image of each frame 10 times.
 3    cols = 0x01;      // Assign binary 00000001. Means the first column is selected.
 4    for (int j = i; j < 8 + i; j++) { // display image of each frame
 5      matrixColsVal(cols);            // select this column
 6      matrixRowsVal(pgm_read_word_near(data + j));// display the data in this column
 7      delay(1);                       // display them for a period of time
 8      matrixRowsVal(0x00);            // close the data of this column
 9      cols <<= 1;                 // shift"cols" 1 bit left to select the next column
10    }
11  }
12}

Verify and upload the code, then LED Matrix begins to display the static smile pattern. A few seconds later, LED Matrix will display the scrolling number 0-9 and the letter A-F.