LED点阵介绍

  LED点阵是由发光二极管排列组合的显示器件,在我们日常生活的电器中随处可见,被广泛应用于汽车报站器,广告屏等。如下所示:

  通常应用较多的是88点阵,然后使用多个88点阵可组成不同分辨率的LED点阵显示屏,比如1616点阵可以使用4个88点阵构成。因此理解了88LED点阵的工作原理,其他分辨率的LED点阵显示屏都是一样的。这里以88LED点阵来做介绍。其内部结构图如下所示:

  8*8 点阵共由64个发光二极管组成,且每个发光二极管是放置在行线和列线的交叉点上,当对应的某一行置1电平,某一列置0电平,则相应的二极管就亮;如要将第一个点点亮,则1脚接高电平a脚接低电平,则第一个点就亮了;如果要将第一行点亮,则第1脚要接高电平,而(a、b、c、d、e、f、g、h )这些引脚接低电平,那么第一行就会点亮;如要将第一列点亮,则第a脚接低电平,而(1、2、3、4、5、6、7、8)接高电平,那么第一列就会点亮。由此可见,LED点阵的使用也是非常简单的。

硬件设计

  本实验使用到硬件资源如下:

  1. 16*16LED点阵模块
  2. 74HC595模块

  从上图中可以看出,该电路是独立的,74HC595模块内使用了4块74HC595芯片,它们采用了级联方式,即RCLK和SRCLK管脚并联在一起,并且74HC595(A)的输出QH非连接到74HC595(B)的串行输入口SER,而74HC595(B)的输出QH非又连接到74HC595(C)的串行输入口SER,依次类推。并且每块芯片的输出端都连接到对应的端子上,74HC595(A)的输出连接到J27端子,74HC595(B)的输出连接到J32端子,74HC595(C)的输出连接到LED点阵前8列,74HC595(D)的输出连接到LED点阵后8列。图上的NEGx是网络标号,与LED点阵列相连。74HC595 需要用到的控制管脚RCLK、SRCLK、SER并未直接连接到51单片机的IO上,而是连接到J24端子上

  要想控制LED点阵,可以将单片机管脚按照74HC595芯片的通信时序要求来传输数据,因为使用了4片74HC595芯片,A/B两块控制点阵行POS1-POS16,C/D两块控制点阵列NEG1-NEG16。这样即可控制LED点阵。根据LED发光二极管导通原理,当阳极为高电平,阴极为低电平则点亮,否则熄灭。因此通过单片机发送4组数据,通过595将这四组数据分配到对应输出从而控制LED点阵。

软件设计

实物接线图

点亮一个点

  这里主要是理解如何让LED点阵的左上角第一个点点亮,实际上就是将第一个点对应的行为高电平,列为低电平即可。也就是让第一个74HC595输出0X01(00000001),这样点阵第一行就是高电平,而第3个74HC595输出0XFE(11111110),第4个74HC595输出0XFF(11111111),这样点阵第一列就是低电平,从而让LED点阵第一个点点亮。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
#include "reg52.h"

// 对系统默认数据类型进行重命名
typedef unsigned int u16;
typedef unsigned char u8;

// 定义74HC595控制管脚
sbit SRCLK = P3^6; // 移位寄存器时钟输入
sbit RC = P3^5; // 存储寄存器时钟输入
sbit SER = P3^4; // 串行数据输入

/********************************************************************
*函数名: delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入: ten_us
*输出:无
*********************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/********************************************************************
*函数名: delay_ms
*函数功能: ms延时函数,ms=1时,大约延时1ms
*输入: ten_us
*输出:无
*********************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i = ms;i > 0;i--)
for(j = 110;j > 0;j--);
}

/********************************************************************
*函数名: hc595_write_data(u8 dat)
*函数功能:向74HC595写入4个字节的数据
*输入: dat1:对应74HC595(A)输出第1行-第8行
dat2:对应74HC595(B)输出第9行-第16行
dat3:对应74HC595(C)输出第1列-第8列
dat4:对应74HC595(D)输出第9列-第16列
*输出:无
*********************************************************************/
void hc595_write_data(u8 dat1,u8 dat2,u8 dat3,u8 dat4)
{
u8 i = 0;

for(i = 0;i < 8;i++) // 循环8次即可将一个字节写入寄存器中
{
SER = dat4 >> 7; // 优先传输一个字节中的高位
dat4 <<= 1; // 将低位移动到高位

// 移位寄存器时钟上升沿将端口数据送入寄存器中
SRCLK = 0;
delay_10us(1);
SRCLK = 1;
delay_10us(1);
}

for(i = 0;i < 8;i++)
{
SER = dat3 >> 7;
dat3 <<= 1;

SRCLK = 0;
delay_10us(1);
SRCLK = 1;
delay_10us(1);
}

for(i = 0;i < 8;i++)
{
SER = dat2 >> 7;
dat2 <<= 1;

SRCLK = 0;
delay_10us(1);
SRCLK = 1;
delay_10us(1);
}

for(i = 0;i < 8;i++)
{
SER = dat1 >> 7;
dat1 <<= 1;

SRCLK = 0;
delay_10us(1);
SRCLK = 1;
delay_10us(1);
}

// 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
RC = 0;
delay_10us(1);
RC = 1;
}

void main()
{
u8 i = 0;

while(1)
{
// 将LED点阵上边第一行设置为1,即LED阳极为高电平,其余为0,即低电平
hc595_write_data(0x01,0x00,0xFE,0xFF);
}
}

显示数字

  要实现行列不同位置亮灯,需要使用动态显示的方法,也要结合扫描的方法。在第一行亮灯一段时间以后灭掉,点亮第二行一段时间以后灭掉,点亮第三行一段时间以后灭掉,如此点亮,直到八行全部点亮一次,在第一行点亮到最后一行灭掉的总时间不能超过人肉眼可识别的时间,即 24 毫秒。在每一行点亮的时候,给列一个新的数据,此时对应列的数据就可以体现在这行上要点亮的灯上。这样就和动态数码管的显示一样,只不过数码管的 LED 灯是段值。这里使用 LED 点阵显示数字,也是多个LED同时点亮。

  要想在点阵上显示数字等字符,首先要获取在LED点阵上显示数字字符所需的数据,即一个数字字符在LED点阵上显示,对应的每行每列都会有一些灯点亮或者熄灭,这样就会构成一组数据,也就是数字字符的显示数据,我们只要将这些数据通过74HC595发送到点阵对应的行或列就能显示数字字符。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "reg52.h"
#include "intrins.h"

// 对系统默认数据类型进行重命名
typedef unsigned int u16;
typedef unsigned char u8;

// 定义74HC595控制管脚
sbit SRCLK = P3^6; // 移位寄存器时钟输入
sbit RC = P3^5; // 存储寄存器时钟输入
sbit SER = P3^4; // 串行数据输入

//LED点阵显示数字0的列数据
u8 gled_col[32]=
{0x00,0x00,0xE0,0x03,0x10,0x04,0x08,0x08,
0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,
0x04,0x10,0x04,0x10,0x04,0x10,0x04,0x10,
0x08,0x08,0x10,0x04,0xE0,0x03,0x00,0x00};
//LED点阵显示数字0的行数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

/********************************************************************
*函数名: delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入: ten_us
*输出:无
*********************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/********************************************************************
*函数名: delay_ms
*函数功能: ms延时函数,ms=1时,大约延时1ms
*输入: ten_us
*输出:无
*********************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i = ms;i > 0;i--)
for(j = 110;j > 0;j--);
}

/********************************************************************
*函数名: hc595_write_data(u8 dat)
*函数功能:向74HC595写入4个字节的数据
*输入: dat1:对应74HC595(A)输出第1行-第8行
dat2:对应74HC595(B)输出第9行-第16行
dat3:对应74HC595(C)输出第1列-第8列
dat4:对应74HC595(D)输出第9列-第16列
*输出:无
*********************************************************************/
void hc595_write_data(u8 dat1,u8 dat2,u8 dat3,u8 dat4)
{
u8 i = 0;

for(i = 0;i < 8;i++) // 循环8次即可将一个字节写入寄存器中
{
SER = dat4 >> 7; // 优先传输一个字节中的高位
dat4 <<= 1; // 将低位移动到高位

// 移位寄存器时钟上升沿将端口数据送入寄存器中
SRCLK = 0;
// delay_10us(1);
_nop_();
SRCLK = 1;
// delay_10us(1);
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat3 >> 7;
dat3 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat2 >> 7;
dat2 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat1 >> 7;
dat1 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

// 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
RC = 0;
delay_10us(1);
RC = 1;
}

void main()
{
u8 i = 0;

while(1)
{
for(i = 0;i < 16;i++) // 循环16次扫描16行、列
{
// 传送行列选数据
hc595_write_data(gled_row[i],gled_row[i + 16],~gled_col[i * 2],~gled_col[i * 2 + 1]);
delay_10us(10); // 延时一段时间,等待显示稳定
hc595_write_data(0x00,0x00,0x00,0x00); // 消影
}
}
}

显示汉字

  显示汉字与显示数字一致,只需替换列显示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "reg52.h"
#include "intrins.h"

// 对系统默认数据类型进行重命名
typedef unsigned int u16;
typedef unsigned char u8;

// 定义74HC595控制管脚
sbit SRCLK = P3^6; // 移位寄存器时钟输入
sbit RC = P3^5; // 存储寄存器时钟输入
sbit SER = P3^4; // 串行数据输入

//LED 点阵显示汉字“普”数据
u8 gled_col[32]=
{0x10,0x04,0x20,0x02,0xFE,0x3F,0x20,0x02,
0x24,0x12,0x28,0x0A,0xFF,0x7F,0x00,0x00,
0xF8,0x0F,0x08,0x08,0x08,0x08,0xF8,0x0F,
0x08,0x08,0x08,0x08,0xF8,0x0F,0x08,0x08};
//LED点阵显示数字0的行数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};

/********************************************************************
*函数名: delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入: ten_us
*输出:无
*********************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/********************************************************************
*函数名: delay_ms
*函数功能: ms延时函数,ms=1时,大约延时1ms
*输入: ten_us
*输出:无
*********************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i = ms;i > 0;i--)
for(j = 110;j > 0;j--);
}

/********************************************************************
*函数名: hc595_write_data(u8 dat)
*函数功能:向74HC595写入4个字节的数据
*输入: dat1:对应74HC595(A)输出第1行-第8行
dat2:对应74HC595(B)输出第9行-第16行
dat3:对应74HC595(C)输出第1列-第8列
dat4:对应74HC595(D)输出第9列-第16列
*输出:无
*********************************************************************/
void hc595_write_data(u8 dat1,u8 dat2,u8 dat3,u8 dat4)
{
u8 i = 0;

for(i = 0;i < 8;i++) // 循环8次即可将一个字节写入寄存器中
{
SER = dat4 >> 7; // 优先传输一个字节中的高位
dat4 <<= 1; // 将低位移动到高位

// 移位寄存器时钟上升沿将端口数据送入寄存器中
SRCLK = 0;
// delay_10us(1);
_nop_();
SRCLK = 1;
// delay_10us(1);
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat3 >> 7;
dat3 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat2 >> 7;
dat2 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat1 >> 7;
dat1 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

// 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
RC = 0;
delay_10us(1);
RC = 1;
}

void main()
{
u8 i = 0;

while(1)
{
for(i = 0;i < 16;i++) // 循环16次扫描16行、列
{
// 传送行列选数据
hc595_write_data(gled_row[i],gled_row[i + 16],~gled_col[i * 2],~gled_col[i * 2 + 1]);
delay_10us(10); // 延时一段时间,等待显示稳定
hc595_write_data(0x00,0x00,0x00,0x00); // 消影
}
}
}

显示图像

  显示汉字与显示数字一致,只需替换列显示。在每个数据后面添加0x00,因为这里仅需1616点阵中的第一个88点阵显示,然后将对应的行扫描数据中后面8行修改为0X00

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
#include "reg52.h"
#include "intrins.h"

// 对系统默认数据类型进行重命名
typedef unsigned int u16;
typedef unsigned char u8;

// 定义74HC595控制管脚
sbit SRCLK = P3^6; // 移位寄存器时钟输入
sbit RC = P3^5; // 存储寄存器时钟输入
sbit SER = P3^4; // 串行数据输入

//LED 点阵显示爱心数据
u8 gled_col[32]=
{0x00,0x00,0x66,0x00,0xFF,0x00,0xFF,0x00,
0xFF,0x00,0x7E,0x00,0x3C,0x00,0x18,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
//LED 点阵行扫描数据
u8 gled_row[32]=
{0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

/********************************************************************
*函数名: delay_10us
*函数功能:延时函数,ten_us=1时,大约延时10us
*输入: ten_us
*输出:无
*********************************************************************/
void delay_10us(u16 ten_us)
{
while(ten_us--);
}

/********************************************************************
*函数名: delay_ms
*函数功能: ms延时函数,ms=1时,大约延时1ms
*输入: ten_us
*输出:无
*********************************************************************/
void delay_ms(u16 ms)
{
u16 i,j;
for(i = ms;i > 0;i--)
for(j = 110;j > 0;j--);
}

/********************************************************************
*函数名: hc595_write_data(u8 dat)
*函数功能:向74HC595写入4个字节的数据
*输入: dat1:对应74HC595(A)输出第1行-第8行
dat2:对应74HC595(B)输出第9行-第16行
dat3:对应74HC595(C)输出第1列-第8列
dat4:对应74HC595(D)输出第9列-第16列
*输出:无
*********************************************************************/
void hc595_write_data(u8 dat1,u8 dat2,u8 dat3,u8 dat4)
{
u8 i = 0;

for(i = 0;i < 8;i++) // 循环8次即可将一个字节写入寄存器中
{
SER = dat4 >> 7; // 优先传输一个字节中的高位
dat4 <<= 1; // 将低位移动到高位

// 移位寄存器时钟上升沿将端口数据送入寄存器中
SRCLK = 0;
// delay_10us(1);
_nop_();
SRCLK = 1;
// delay_10us(1);
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat3 >> 7;
dat3 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat2 >> 7;
dat2 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

for(i = 0;i < 8;i++)
{
SER = dat1 >> 7;
dat1 <<= 1;

SRCLK = 0;
_nop_();
SRCLK = 1;
_nop_();
}

// 存储寄存器时钟上升沿将前面写入到寄存器的数据输出
RC = 0;
delay_10us(1);
RC = 1;
}

void main()
{
u8 i = 0;

while(1)
{
for(i = 0;i < 16;i++) // 循环16次扫描16行、列
{
// 传送行列选数据
hc595_write_data(gled_row[i],gled_row[i + 16],~gled_col[i * 2],~gled_col[i * 2 + 1]);
delay_10us(10); // 延时一段时间,等待显示稳定
hc595_write_data(0x00,0x00,0x00,0x00); // 消影
}
}
}