前言

介绍

什么是 LED点阵屏?

百度百科

1
LED点阵屏通过LED(发光二极管)组成,以灯珠亮灭来显示文字、图片、动画、视频等,是各部分组件都模块化的显示器件,通常由显示模块、控制系统及电源系统组成。LED点阵显示屏制作简单,安装方便,被广泛应用于各种公共场合,如汽车报站器、广告屏以及公告牌等。

8*8的LED点阵屏的内部连接如下

优点

由上图可以知道,每根线都连接了8个LED灯,这样做的好处显而易见,可以只用16根线控制64个LED

缺点

缺点也很明显,当一盏灯点亮时,再点亮另外一个LED可能导致其它无关LED被误点亮

如(1,1)点亮,再点亮(2,2),此时(1,1)、(1,2)、(2,1)、(2,2)都是亮的(电源共地或共阳时)

解决方法

简单的方法

每次只点亮一只LED

流程:先点亮一个LED(为了方便理解标为1),当要点亮下一只LED时,先熄灭当前LED(1),再点亮下一只LED(标为2),再熄灭(2),点亮(1)如此快速循环,由于人眼的视觉暂留,人看这个LED点阵屏就是两个LED一直亮着,这也是为什么有时用手机拍摄LED点阵屏时可能会看到灯光闪烁(LED点阵屏刷新频率较低)

如下图(网图,侵权将删除),由于闪烁导致拍摄的图片显示不全,实际上在人眼看到的是没有缺失

想法

  • 当点亮单独的LED时先看看下一个LED和当前LED点亮会不会影响其它无关LED,无影响就一起点亮

  • 当点亮连着的一片LED时,取消熄灭的流程,直接接通(作为一个单元)

  • 否则按上面流程走

只是一个想法,感觉没有必要,也不知道也没有起到优化作用(不知道会不会减少闪烁,闪烁可以通过提高刷新频率来解决)

成果

本文将从一个STC89C51(51单片机)最小系统开始,使用Keli uv4和STC-ISP

  • 点亮一盏LED(作为Hello World)
  • 点亮全屏的8*8 LED点阵屏
  • 逐个点亮8*8 LED点阵屏上的LED
  • 单独点亮任意两个或两个以上在8*8 LED点阵屏上的LED
  • 根据图片取模的数组点亮LED

本文的结果如下图GIF

0707212233(1)

开始

既然概念已经讲完了,那就开始把

材料准备

5V电源

单片机使用电压为5V

USB转ttl下载器

用于下载程序到单片机

51单片机最小系统-准备

常用的最小系统电路如下

360截图17630325435045

由于我用不到复位开关,故省去

准备材料如下

STC89C51单片机一个

360截图165406048776113

引脚图如下

51

对照图如下

51-2

10μF 电容(10V或更高V)一个

此电容用于单片机复位

360截图17001017172533

10KΩ电阻一个

此电阻用于给上面的10微法电容充电,电容刚开始充电时复位端为高电平,充电完成复位端由高电平变成低电平,完成单片机复位

0708211309

8MHz晶振一个(手头上这个频率的晶振比较多,你也可以用其他的,但是要改本文的程序)

360截图17891225173763

20-25pF电容两个

360截图17001013515844

我使用了贴片的电容

0708211329

普通LED1KΩ电阻一个(这两样东西可以省略,我一开始用于测试单片机)

0707212209

实验准备

8X8 LED点阵屏一个

0707212223

AMS1117-3.3一个(原因后面会讲)

用于LED供电

360截图18290326347439

ss8550三极管若干

360截图1761060888120101

我用的是贴片的ss8550,因为体积比较小

360截图17060218483771

ss8550为PNP型三极管

连接

连接分两个部分,为了确保单片机功能正常

  • 第一个部分先在单片机上连接一盏LED灯,用于测试程序的编译、单片机的复位电路、程序下载和IO口工作是否正常
  • 第二部分开始连接8*8的LED点阵屏,并完成前言部分写的目标

第一部分的连接

先完成最小系统的连接

6d81800a19d8bc3eee1b7dba828ba61ea8d345aa

单片机的引脚对照图在上面已经给过了

51-2

连接测试LED

取LED和1K电阻,我这里电阻连接了LED的负极

0707212209

LED正极连接到VCC,电阻连接到单片机P1.0脚,电阻用于限流,因为VCC为5V

这样连接是因为stc89c51单片机灌电流为20mA,拉电流为230uA,

如果LED正极连到P1.0脚,电阻连GND,P1.0为高电平时驱动时仅能看到LED微亮

如果LED正极连接到VCC,电阻连接到单片机P1.0脚,P1.0为低电平时LED能正常发亮,而此时灌电流为2mA,小于20mA,可行

上面所提到的计算如下:

1
2
3
4
输入5V,电阻1K,LED要求为3V,则电阻要分掉2V,则
电流为U/R=(5-3)V/1000Ω=2V/1000Ω=2mA
即使LED要求2.5V,此时
电流为U/R=(5-2.5)V/1000Ω=2.5V/1000Ω=2.5mA

0708212105

将单片机上的TX连接到USB转ttl下载器的RX,单片机上的RX连接到USB转ttl下载器的TX,GND连GND

程序编译

打开keli uvision4,新建C51项目,打开输出hex选项

2021-08-18-151724

360截图17860608111116116

360截图16290614327870

编译以下程序

360截图179110148512590

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
#include<reg52.h>
#include<intrins.h>

sbit D0=P1^0; //定义D0为引脚P1^0

//暂停500毫秒
void Delay500ms() //@8.000MHz
{
unsigned char i, j, k;

i = 2;
j = 134;
k = 152;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}

//闪烁D0
void bliker(unsigned int times) {
unsigned int tc = 0;
while(1) {
D0 = 0;
Delay500ms();
D0 = 1;
Delay500ms();
tc++;
if (tc == times) {
break;
}
}
}

void main(void) {
bliker(3);
while (1) {
//主程序
}
}

程序下载

打开STC-ISP,选择STC89C51RC,串口号选择你的USB转ttl下载器

360截图1740121078115114

点击”打开程序文件”选择位于项目目录下的Objects文件夹下的编译输出的hex文件

360截图18750816226278

360截图17891228549984

点击检测MCU选项,单片机VCC再接上5V

看到输出后说明单片机最小系统搭建完成

360截图18141219518955

断开单片机电源后即可点击”下载/编程“,再接上电源进行程序下载

因为此单片机只有上电时才能下载

360截图17860602434381

下载完成后LED将会闪烁3次

0818211512(1)(1)

第二部分的连接

AMS1117-3.3端口实物对照如下

ss8550端口实物对照如下

按照此图连接8X8 LED点阵屏

定义如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
sbit D0=P1^0; //定义D0为引脚P1^0,测试LED

//x轴,输出0有效,因为接在LED点阵的阴极
sbit D1=P1^1;
sbit D2=P1^2;
sbit D3=P1^3;
sbit D4=P1^4;
sbit D5=P1^5;
sbit D6=P1^6;
sbit D7=P1^7;
sbit D8=P3^2;
//y轴,输出0有效,因为连接在pnp三极管上
sbit E1=P2^0;
sbit E2=P2^1;
sbit E3=P2^2;
sbit E4=P2^3;
sbit E5=P2^4;
sbit E6=P2^5;
sbit E7=P2^6;
sbit E8=P2^7;

解释

由于手上电阻没有多少个了,所以我使用ams1117-3.3将5V降为3.3V,与ss8550三极管直接相连,三极管再与点阵屏阳极相连,省去LED的限流电阻

点阵屏的阴极统一接到单片机IO上,而三极管的基极(B极)接到单片机IO上

这样保证LED驱动电流足够,又由于3.3V低于5V,连IO到三极管基极的限流电阻都不需要,同时确保每个LED能点亮

SS8550-PNP三极管当作开关使用,单片机引脚输出低电平,三极管导通,连接在y轴

我的连接

最小系统与点阵连接

0707212223a

三极管连接

0707212224a

正面

0707212224

只点亮任意一个LED

我写了一个函数用于控制多个IO

P1 = 254^0为11111110即P1.0输出为0,这里接的是测试LED的阴极,如果P1.0输出为0,LED会亮

P1 = 254^1时为255,即11111111,即0xff,即P1.0到P1.7输出为1

P1 = 254^2时为252,即11111100,即P1.0、P1.1输出为0,其它为1

P1 = 254^4为250,即11111010,即P1.0、P1.2输出为0,其它为1,以此类推

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
//对2求n次方
unsigned int pow2(unsigned int n_in) {
if (n_in == 0) {
return 1;
} else {
unsigned char nnn, result_ = 1;
for (nnn=0;nnn<n_in;nnn++) {
result_ *= 2;
}
return result_;
}
}
sbit D8=P3^2;
void set_io(unsigned char x, unsigned char y, unsigned char set_state) {
if (set_state < 2) {
if (x == 7) {
D8 = 1^set_state;//0为有效信号,所以对set_state进行异或操作
P1 = 0xff;
} else {
D8 = 1;
if (set_state)
P1 = 254^pow2(x+1);
else
P1 = 0xff;
}

if (set_state)
P2 = 255^pow2(7-y);
else
P2 = 0xff;
}
}

使用时只需要指定坐标和状态,从0开始

1
2
//例如
set_io(0,0,1);

程序编译

编译以下程序

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
#include<reg52.h>
#include<intrins.h>

sbit D0=P1^0; //定义D0为引脚P1^0

//对2求n次方
unsigned int pow2(unsigned int n_in) {
if (n_in == 0) {
return 1;
} else {
unsigned char nnn, result_ = 1;
for (nnn=0;nnn<n_in;nnn++) {
result_ *= 2;
}
return result_;
}
}
sbit D8=P3^2;
void set_io(unsigned char x, unsigned char y, unsigned char set_state) {
if (set_state < 2) {
if (x == 7) {
D8 = 1^set_state;//0为有效信号,所以对set_state进行异或操作
P1 = 0xff;
} else {
D8 = 1;
if (set_state)
P1 = 254^pow2(x+1);
else
P1 = 0xff;
}

if (set_state)
P2 = 255^pow2(7-y);
else
P2 = 0xff;
}
}

//暂停500毫秒
void Delay500ms() //@8.000MHz
{
unsigned char i, j, k;

i = 2;
j = 134;
k = 152;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}

//闪烁D0
void bliker(unsigned int times) {
unsigned int tc = 0;
while(1) {
D0 = 0;
Delay500ms();
D0 = 1;
Delay500ms();
tc++;
if (tc == times) {
break;
}
}
}

void main(void) {
int a,b;
bliker(3);
for (a=0;a<8;a++) {
for (b=0;b<8;b++) {
set_io(b,a,1);
Delay500ms();
}
}
set_io(0,0,0);
while (1) {
//主程序
}
}

程序下载

下载方法与上面一致,这里不再重复

结果

正确下载后将出现下面GIF显示画面

将测试每个LED是否能够点亮

0707212233(1)(1)

显示位图

绘制图案

打开Windows自带画图

360截图16550424568751

使用”铅笔”工具绘制

360截图18250828183651

我的图案

huatu

如下(很小)

无标题

保存为BMP

360截图18770529244626

360截图17290508779282

LED点阵展示

先对图像进行取模(使用取模软件),得到数组

需要注意,只能使用8*8分辨率的图像

在线取模

360截图18250901859467

得到

1
2
// width: 8, height: 8
const unsigned char col[] U8X8_PROGMEM = { 0xef,0xef,0xef,0xef,0xef,0xd7,0xbb,0x7d };

需要改成

1
2
// width: 8, height: 8
const unsigned char col[] = { 0xef,0xef,0xef,0xef,0xef,0xd7,0xbb,0x7d };

原理

上面的col数组中存储的其实是每个LED的亮灭状态,例如0xef其实是11101111,即第四个LED亮,

颜色反转后就是00010000,即第四个LED不亮,其它都亮

程序

位图数组读取程序在此

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void bmp_light(unsigned char data_input[], unsigned char convert) {
unsigned char i,j;
unsigned char data0 = 0;
unsigned char data1 = 0;
for (j=0;j<8;j++) {
data0 = data_input[j];
for (i=0;i<8;i++) {
if (convert)
data1=(data0&0x80)?1:0;
else
data1=(data0&0x80)?0:1;
data0 <<= 1;
if (data1)
set_io(i,j,1);
}
}
}

完整程序如下

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
#include<reg52.h>
#include<intrins.h>

sbit D0=P1^0; //定义D0为引脚P1^0

//对2求n次方
unsigned int pow2(unsigned int n_in) {
if (n_in == 0) {
return 1;
} else {
unsigned char nnn, result_ = 1;
for (nnn=0;nnn<n_in;nnn++) {
result_ *= 2;
}
return result_;
}
}
sbit D8=P3^2;
void set_io(unsigned char x, unsigned char y, unsigned char set_state) {
if (set_state < 2) {
if (x == 7) {
D8 = 1^set_state;//0为有效信号,所以对set_state进行异或操作
P1 = 0xff;
} else {
D8 = 1;
if (set_state)
P1 = 254^pow2(x+1);
else
P1 = 0xff;
}

if (set_state)
P2 = 255^pow2(7-y);
else
P2 = 0xff;
}
}

//暂停500毫秒
void Delay500ms() //@8.000MHz
{
unsigned char i, j, k;

i = 2;
j = 134;
k = 152;
do
{
do
{
while (--k);
} while (--j);
} while (--i);
}

void bmp_light(unsigned char data_input[], unsigned char convert) {
unsigned char i,j;
unsigned char data0 = 0;
unsigned char data1 = 0;
for (j=0;j<8;j++) {
data0 = data_input[j];
for (i=0;i<8;i++) {
if (convert)
data1=(data0&0x80)?1:0;
else
data1=(data0&0x80)?0:1;
data0 <<= 1;
if (data1)
set_io(i,j,1);
}
}
}

// width: 8, height: 8
const unsigned char col[] = { 0xef,0xef,0xef,0xef,0xef,0xd7,0xbb,0x7d };

void main(void) {
bliker(3);

while (1) {
//主程序
bmp_light(col, 0);
}
}

效果

0818211809_HDR

最后

还可以弄个数字显示

1
2
3
4
5
6
7
8
9
10
11
12
unsigned char num_find[10][8] = {
{0x00,0x00,0x00,0xe0,0xa0,0xa0,0xa0,0xe0},
{0x00,0x00,0x00,0x40,0xc0,0x40,0x40,0xe0},
{0x00,0x00,0x00,0xe0,0x20,0xe0,0x80,0xe0},
{0x00,0x00,0x00,0xe0,0x20,0xe0,0x20,0xe0},
{0x00,0x00,0x00,0xa0,0xa0,0xe0,0x20,0x20},
{0x00,0x00,0x00,0xe0,0x80,0xe0,0x20,0xe0},
{0x00,0x00,0x00,0xe0,0x80,0xe0,0xa0,0xe0},
{0x00,0x00,0x00,0xe0,0x20,0x20,0x20,0x20},
{0x00,0x00,0x00,0xe0,0xa0,0xe0,0xa0,0xe0},
{0x00,0x00,0x00,0xe0,0xa0,0xe0,0x20,0xe0}
};

在主程序加上下面代码,就能显示数字5,5可以改成0到9的任意一个

1
bmp_light(num_find[5], 1);

效果如下

0818211825_HDR

谢谢观看

EOF