Introduction: BI-COLORS MATRIX 32x32 SHOWING 256 COLORS WITH GIF PICTURES
Today, I'd share with you how “32x32 Bicolor Led Matrix” can display 256 separated colors by using 74HC595 for column scan and 74HC238 for row scan. Of course, if the human eye can recognize this color difference. The flickers you see on videos is due to CAMERA, human eyes can't see these flickers.
Bicolor matrix 32x32 has totally 1024 RED leds, 1024 GREEN leds. And with B.A.M - 4 bit (Bit Angle Modulation), every combination (RED + GREEN) led can generated 256 separated colors. How can Arduino handle these jobs with cheap and easy-to-find ICs on the market ?
In addition, this project also share how to display animation pictures (GIF pictures) by simple manual way.
Step 1: BILL OF MATERIALS
Besides, I have to buy 3 copper clad boards, single sided and do PCB etching by myself.
Step 2: Bi-Colors Led Matrix 8x8 - Overview
With order code 2088ABEG-5, led matrix 8x8 is common anode, dimension 60x60mm.
Step 3: Make a Simple LED Tester
Check the topic: https://www.instructables.com/id/Make-a-simple-LED...
Check whether Led Matrix 8x8 is common anode or common cathode, and identify all red and green pins.
Step 4: Principle Diagram
In simulation schematic, we don’t see (2N3904/2N2222 + TIP42C) connected after 74HC238, as well as, (ULN2803 + R100) connected after 74HC595. See next step for more detail.
Step 5: Actual Schematic - Row Scanning
Totally, for 32 rows scanning, we just need:
- 02 x 74HC238.
- 16 x 2N3904/2N2222.
- 16 x TIP42C.
Why do we use 74HC238 instead of 74HC138?
Because in my design, I need to connect a decoder with active-high outputs to transistor 2N3904 (NPN).
Note: 74HC138 (active-low outputs) & 74HC238 (active-high outputs) have totally the same pinouts.
Row scanning program:
void rowScan(byte row) { if (row & 0x01) PORTD |= 1<<RowA_Pin else PORTD &= ~(1<<RowA_Pin); if (row & 0x02) PORTD |= 1<<RowB_Pin; else PORTD &= ~(1<<RowB_Pin); if (row & 0x04) PORTD |= 1<<RowC_Pin; else PORTD &= ~(1<<RowC_Pin); if (row & 0x08) PORTD |= 1<<RowD_Pin; else PORTD &= ~(1<<RowD_Pin); }
With multiplexer pins setup on PORTD of Arduino Uno:
#define RowA_Pin 4 #define RowB_Pin 5 #define RowC_Pin 6 #define RowD_Pin 7
Step 6: Actual Schematic - Column Scanning
For column scanning, totally components using:
- 16 x 74HC595.
- 16 x ULN2803.
- 128 x R100.
So, it means we need 2 boards as above, one for RED led & one for GREEN led.
Program with row and column scanning:
ISR(TIMER1_COMPA_vect) { PORTD |= ((1<<blank_pin)); if (BAM_Counter == 8) BAM_Bit++; else if (BAM_Counter == 24) BAM_Bit++; else if (BAM_Counter == 56) BAM_Bit++; BAM_Counter++; switch (BAM_Bit) { case 0: //Red myTransfer(red[0][level + 0]); myTransfer(red[0][level + 1]); myTransfer(red[0][level + 2]); myTransfer(red[0][level + 3]); myTransfer(red[0][level + 64]); myTransfer(red[0][level + 65]); myTransfer(red[0][level + 66]); myTransfer(red[0][level + 67]); //Green myTransfer(green[0][level + 0]); myTransfer(green[0][level + 1]); myTransfer(green[0][level + 2]); myTransfer(green[0][level + 3]); myTransfer(green[0][level + 64]); myTransfer(green[0][level + 65]); myTransfer(green[0][level + 66]); myTransfer(green[0][level + 67]); break; case 1: //Red myTransfer(red[1][level + 0]); myTransfer(red[1][level + 1]); myTransfer(red[1][level + 2]); myTransfer(red[1][level + 3]); myTransfer(red[1][level + 64]); myTransfer(red[1][level + 65]); myTransfer(red[1][level + 66]); myTransfer(red[1][level + 67]); //Green myTransfer(green[1][level + 0]); myTransfer(green[1][level + 1]); myTransfer(green[1][level + 2]); myTransfer(green[1][level + 3]); myTransfer(green[1][level + 64]); myTransfer(green[1][level + 65]); myTransfer(green[1][level + 66]); myTransfer(green[1][level + 67]); break; case 2: //Red myTransfer(red[2][level + 0]); myTransfer(red[2][level + 1]); myTransfer(red[2][level + 2]); myTransfer(red[2][level + 3]); myTransfer(red[2][level + 64]); myTransfer(red[2][level + 65]); myTransfer(red[2][level + 66]); myTransfer(red[2][level + 67]); //Green myTransfer(green[2][level + 0]); myTransfer(green[2][level + 1]); myTransfer(green[2][level + 2]); myTransfer(green[2][level + 3]); myTransfer(green[2][level + 64]); myTransfer(green[2][level + 65]); myTransfer(green[2][level + 66]); myTransfer(green[2][level + 67]); break; case 3: //Red myTransfer(red[3][level + 0]); myTransfer(red[3][level + 1]); myTransfer(red[3][level + 2]); myTransfer(red[3][level + 3]); myTransfer(red[3][level + 64]); myTransfer(red[3][level + 65]); myTransfer(red[3][level + 66]); myTransfer(red[3][level + 67]); //Green myTransfer(green[3][level + 0]); myTransfer(green[3][level + 1]); myTransfer(green[3][level + 2]); myTransfer(green[3][level + 3]); myTransfer(green[3][level + 64]); myTransfer(green[3][level + 65]); myTransfer(green[3][level + 66]); myTransfer(green[3][level + 67]); if(BAM_Counter == 120) { BAM_Counter=0; BAM_Bit=0; } break; } rowScan(row); PORTD |= 1<<latch_pin; PORTD &= ~(1<<<latch_pin); delaymicroseconds(5); PORTD &= ~(1<<blank_pin); delayMicroseconds(5); level = row*4; if(row==16) row=0; if(level==64) level=0; pinMode(blank_pin, OUTPUT); }
Step 7: Actual Schematic – Led Matrix Display 32x32
With matrix 32x32, it includes 16 x Bi-colors Led Matrix 8x8. Picture above is PCB design for Led display.
Step 8: Connection Diagram
1. For ROWS
- Rows 0 ~ 7 and rows 16 ~ 23 are connected to TIP42C_0 – TIP42C_7.
- Rows 8 ~ 15 and rows 24 ~ 31 are connected to TIP42C_8 – TIP42C_15.
2. For COLUMNS:
- RED Led:
- Led Matrix #0 & #4 are connected to 74HC595_RED_0.
- …….
- Led Matrix #11 & #15 are connected to 74HC595_RED _7.
- GREEN Led:
- Led Matrix #0 & #4 are connected to 74HC595_GREEN_0.
- …….
- Led Matrix #11 & #15 are connected to 74HC595_GREEN _7.
Step 9: Main Program
Step 10: How Does B.A.M Work?
To control a bicolor LED, we pay attention to two parameters: COORDINATES and COLORS, for example: LED(x, y, red, green).
To set one LED, see program below:
void LED(int X, int Y, int R, int G) { X = constrain(X, 0, 31);//Matrix X axis Y = constrain(Y, 0, 31);//Matrix Y axis R = constrain(R, 0, 15); G = constrain(G, 0, 15); int WhichByte = int(Y*4+ X/8); int WhichBit = 7-(X%8); for (byte BAM = 0; BAM < 4; BAM++) { //*** RED *** bitWrite(red[BAM][WhichByte], WhichBit, bitRead(R, BAM)); //*** GREEN *** bitWrite(green[BAM][WhichByte], WhichBit, bitRead(G, BAM)); } }
Now for more specific, we explain a command LED (19, 22, 10, 5) with:
- Coordinates: x = 19, y = 22.
- Colors: red = 10, green = 5.
With 4 bit B.A.M, red & green colors are limited in range: 0 ~ 15.
Step 11: Coordinates
Following the main program, coordinates (x=19, y=22) is calculated and transferred to byte & bit:
- WhichByte = 90 <--> int(y*4+ x/8)
- WhichBit = 4 <--> 7 - (x%8)
Step 12: Colors
At address [Byte = 90][Bit = 4], color is calculated:
- RED = 10 (00001010)
RED[0][90][4] = 0
RED[1][90][4] = 1
RED[2][90][4] = 0
RED[3][90][4] = 1
- GREEN = 5 (00000101)
GREEN[0][90][4] = 1
GREEN[1][90][4] = 0
GREEN[2][90][4] = 1
GREEN[3][90][4] = 0
In picture above, you can see all raw data for describing B.A.M method and prepare for timing chart on next step.
Step 13: Coordinates With Colors
Let start from Timer 1 setup:
TCCR1A = B00000000;
TCCR1B = B00001011;
TIMSK1 = B00000010;
OCR1A=20;
With setup above, our timer clock runs at 250kHz (16MHz/64=250kHz) or 1/250kHz = 4us. When we set OCR1A to 20, this means the interrupt will be called every (20+1)x4us=84us (so called Time_Base), which gives a multiplex frequency of about 11,9 kHz.
In program, we take note the BAM_Counter with these numbers = 8, 24, 56 and 120, and BAM_Bit ++. For every TIMER1 interruption (after 84us), BAM_Counter will increment the value to 1, and if it reaches 120 then reset to 0. The relationship between BAM_Counter and BAM_Bit is as follows:
- BAM_Bit = 0, when BAM_Counter is in range: 0 ~ 7.
- BAM_Bit = 1, when BAM_Counter is in range: 8 ~ 23.
- BAM_Bit = 2, when BAM_Counter is in range: 24 ~ 55.
- BAM_Bit = 3, when BAM_Counter is in range: 56 ~ 119.
Or we can say, the X axis on timing chart above indicates the time of LEDs are ON / OFF with Time_Base = 84us (one unit on X axis is 84us).
- Bam_Bit[0] = 8 x Time_Base = 672us --> RED[0][90][4] = 0 & GREEN[0][90][4] = 1, means at address (x=19, y=22), RED led OFF and GREEN led ON during 672 us.
- Bam_Bit[1]=16 x Time_Base = 1,344us --> RED[1][90][4] = 1 & GREEN[1][90][4] = 0, means at address (x=19, y=22), RED led ON and GREEN led OFF during 1,344us.
- Bam_Bit[2]=32 x Time_Base = 2,688us --> RED[2][90][4] = 0 & GREEN[2][90][4] = 1, means at address (x=19, y=22), RED led OFF and GREEN led ON during 2,688us.
- Bam_Bit[3]=64 x Time_Base = 5,376us --> RED[3][90][4] = 1 & GREEN[3][90][4] = 0, means at address (x=19, y=22), RED led ON and GREEN led OFF during 5,376us.
Step 14: Actual Pictures
Actual images for:
- LED(19, 22, 10, 5).
- fillTable(4,8).
- fillTable(8,8).
- And command:
for (int x = 0; x < 32; x++)
{
for (int y = 0; y < 32; y++)
{
LED(x, y, int(x / 2), int(y / 2));
delay(50);
}
}
Step 15: GIF Pictures
All GIF pictures above is including the introduction video. We have to choose size 32x32 or resize GIF picture to fit with matrix display 32x32.
The way I display my animations is very simple. Firstly, I converted *.GIF file to some *.JPG files. And then converting JPG picture to HEX code and show them with suitable delay. That's all!
There're many conversion programs on internet, you can search by keyword: "convert gif to jpg online" and "picture to hex converter online"
Programs below to show pictures:
- Draw image from HEX code
drawImage(32, 32, FACE00, clearcolor, greencolor);
RG Color template
struct Color { unsigned char red, green; Color(int r, int g) : red(r), green(g) {} Color() : red(0), green(0) {} }; const Color redcolor = Color(0x0F,0x00); const Color orangecolor = Color(0x0F,0x0F); const Color yellowcolor = Color(0x0F,0x09); const Color greencolor = Color(0x00,0x0F); const Color clearcolor = Color(0x00,0x00);
- Image HEX code
static const uint8_t FACE00[]PROGMEM = {0xFF, 0xBF, 0xFF, 0xFF, 0xC0, 0x20, 0x07, 0xFF, 0xFF, 0xC0, 0x00, 0x03, 0xFF, 0xE0, 0x00, 0xFF, 0x00, 0x30, 0x01, 0xFF, 0x00, 0x38, 0x03, 0x81, 0x00, 0x3C, 0x07, 0x01, 0xC0, 0xF0, 0x0F, 0x83, 0xFF, 0xE0, 0x1F, 0xFF, 0xEF, 0x70, 0x1F, 0xFF, 0xBF, 0xD0, 0x1F, 0xFF, 0x00, 0x20, 0x1B, 0xFF, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x18, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00, 0x0C, 0x01, 0x00, 0x01, 0x1C, 0x01, 0x00, 0x0B, 0xFC, 0x03, 0x00, 0x0F, 0xFC, 0x03, 0x00, 0x03, 0xFC, 0x01, 0x00, 0x01, 0xF8, 0x03, 0x00, 0x00, 0x10, 0x03, 0x00, 0x00, 0x10, 0x03, 0x00, 0x07, 0x7C, 0x02, 0x00, 0x3F, 0xFF, 0x86, 0x01, 0xFF, 0xFF, 0x84, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x03, 0x08, 0x00, 0x1E, 0x07, 0x00, };
- Image drawing program
void drawImage(uint8_t width, uint8_t height, const uint8_t *image, Color For_color, Color Bk_color) { for (uint8_t y = 0; y < height; y++) { for (uint8_t x = 0; x < width; x++) { uint8_t WhichByte = x/8 + y*4; uint8_t WhichBit = 7-(x % 8); uint8_t colorImage = bitRead(pgm_read_byte(&image[WhichByte]), WhichBit) & 1; if (colorImage) { LED(x, y, For_color.red, For_color.green); } else { LED(x, y, Bk_color.red, Bk_color.green); } } } }
Step 16: VU Meter
To make VU meter, I bought 2 MSGEQ7 ICs (Seven Band Graphic Equalizer Display Filter), one for left channel the other for right channel. MSGEQ7 can divides the audio spectrum into seven bands: 63Hz, 160Hz, 400Hz, 1kHz, 2.5kHz, 6.25kHz, 16kHz and output a amplitude of each frequency band.
MSGEQ7 datasheet: https://www.sparkfun.com/datasheets/Components/Gen...
Step 17: PCB Design
All pictures above are PCB designs for all project:
- LED matrix board.
- Row scanning board.
- Column scanning board.
Hope this helps!!!
Step 18: More Videos
For more videos, you can check at:
And
Step 19: And More Pictures
In videos, you can see some more effects like plasma, color-wheel, test scrolling, picture scrolling...As I said before, flicker is due to CAMERA, human eye cannot detect this flicker in reality....