03 OLED显示屏实现

前言

这一章主要是上一节没有讲完的项目的一个编写,上一章我们说道单片机的输入和输出,包括四种单片机与外设之间的数据交换使用到的方式,这一章就利用前面说到的这几种方式来编写一个OLED显示屏的驱动。

一、软件模拟IIC协议

这使用的OLED屏幕是用的4引脚IIC协议的,在编写相关代码之前我们需要先了解一下什么是IIC协议,在单片机中,IIC通常指的是Inter-Integrated Circuit,也称为I2C(Inter-Integrated Circuit)总线,它是一种用于在集成电路之间进行通信的串行通信协议。

I2C总线由飞利浦公司(现在的恩智浦半导体公司)开发,用于在数字集成电路之间进行通信。它通常用于连接微控制器和外部设备,如传感器、存储器、扩展模块等。

I2C总线使用两根线进行通信:串行数据线(SDA)和串行时钟线(SCL)。通过这两根线,多个设备可以在同一总线上进行通信。每个设备都有一个唯一的地址,因此可以通过地址来选择要通信的特定设备。

I2C总线采用主从结构,主设备负责发起通信并控制总线,而从设备则响应主设备的命令。这种通信协议简单而灵活,适用于连接多种类型的设备。

简单来说IIC协议就是一种串行通讯协议,我们只需要对两根数据线进行操作就可以了,在32中有对应的IIC的函数库,我们可以直接调用来实现,但是那个内容我安排在后面了,我们实现先学习上一节用输入和输出来进行模拟的方式,学习完后我们才可以用一些传感器来观察效果并实现更高级的内容。

1.开启IIC协议

我们在使用IIC协议之前需要发送一个开始信号的,这个开始信号的时序图如下:

3.png

可以看到左边是起始条件,我们要开启IIC协议必须得先发送其实条件,这里的代码很容易就可以编写,这里我用了一下宏定义和位操作:

1
2
3
4
5
6
void IIC_Start(void){
GPIO_SetBits(GPIO, SDA);
GPIO_SetBits(GPIO, SCL);
GPIO_ResetBits(GPIO, SDA);
GPIO_ResetBits(GPIO, SCL);
}

这个函数就可以让IIC协议开始,当开始后我们就可以参数数据和一些命令了。

2.结束IIC协议

当一条IIC协议传输完成后需要发送一条协议结束信号,这个信号的写法也很简单,代码如下:

1
2
3
4
5
void IIC_Stop(void){
GPIO_ResetBits(GPIO, SDA);
GPIO_SetBits(GPIO, SCL);
GPIO_SetBits(GPIO, SDA);
}

这样就可以结束上一次的IIC数据传输。

3.传输数据

这个的时序图也可以参考上面的时序图

3.png

看中间的位置,这个位置就是传输一位数据时的时序图,那么我们就可以根据这个图写出传输一个字节的数据了,这里需要注意一下,我们传输一条字节数据时是需要先传递高位数据,在SCL从高电平到低电平的时候才会将数据传递过去,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void IIC_WriteBit(u8 byte){
u8 i;
for (i = 0; i < 8; i++){
if (byte & 0x80){
GPIO_SetBits(GPIO, SDA);
}
else{
GPIO_ResetBits(GPIO, SDA);
}
byte <<= 1;
GPIO_SetBits(GPIO, SCL);
GPIO_ResetBits(GPIO, SCL);
}
// 跳过一个ACK信号
GPIO_SetBits(GPIO, SCL);
GPIO_ResetBits(GPIO, SCL);
}

这里就是向IIC传递一个字节数据的函数,最后这里写了一个跳过ACK响应,这个ACK响应是什么呢?

我们可以看这个时序图:

3.jpg

可以看到WORD数据后有一个ACK信号,这个ACK信号是响应信号,就是当我们数据传递到IIC设备后,它如果接受到数据后就会发送一个接受信号,这个就是接受信号,我们不需要这个信号,所以直接忽略即可。

好了,模拟IIC的操作就是这些了,下面我们就介绍一下OLED的操作了。

二、OLED的操作

1.传输数据的准备

在用IIC协议向OLED传输数据前需要做的操作有下面这几步,我们只有操作好了才能正确的传输命令或者数据:

1.开启IIC协议

2.向OLED传入从地址

3.传输是写入的是命令还是数据

4.开始传输数据

5.结束IIC协议

在传输数据或者命令之前要先向OLED传入它的地址,相当于一个片选,当选中了才可以进行操作,每个OLED的地址都是固定的,都是为0x78,如果要更改需要联系厂家

4.png

这个就是时序图,在右边的SSD1306 Slave Address就是它的地址,我们就将这个先传入即可,在下面的写入命令会有这个代码。

然后就是传入是数据还是命令,在words这个数据块中可以告诉你该如何传入,第一个Co不用管,默认0皆可,然后第二个是选择命令还是数据,1是数据,0是命令,所以就可以得到下面的内容,当我们要写命令的时候,传入完地址后再传入0x00。如果要写数据得到的时候,要传入0x40

2.写入命令

知道了上面的过程后我们就可以写写入命令的函数了:

1
2
3
4
5
6
7
void write_command(u8 cmd){
IIC_Start(); // 开启IIC协议
IIC_Write(0x78); // 传入地址
IIC_Write(0x00); // 传入0x00代表要传入命令
IIC_Write(cmd); // 传入命令
IIC_Stop(); // 结束IIC协议
}

这样就完成了一次写入命令。

3.写入数据

写入数据的函数和上面的函数一样,只不过就是传入地址后传入的内容不同:

1
2
3
4
5
6
7
void write_data(u8 data){
IIC_Start();
IIC_Write(0x78);
IIC_Write(0x40);
IIC_Write(data);
IIC_Stop();
}

这样就可以完成一次数据的传送。

这些基础功能讲完了,后面的操作是需要结合命令列表来进行操作了。

4.初始化函数

首先我们要操作需要先进行初始化,这里初始化的功能不会全部说明,大家如果有问题可以查一下OLED的数据手册即可。

初始化传入的命令步骤如下:

  1. 关闭显示屏

  2. 设置显示时钟分频比/振荡器频率

  3. 设置多路复用率

  4. 显示开始行

  5. 设置左右方向

  6. 设置上下方向

  7. 设置COM引脚硬件配置

  8. 设置对比度

  9. 设置预充电周期

  10. 设置VCOMH取消选择级别

  11. 设置整个显示打开/关闭

  12. 设置正常/倒转显示

  13. 设置充电泵

  14. 开启显示

这个内容需要查看手册来慢慢写,我这里就不一个一个介绍了,我这写好了,你们可以直接复制和粘贴

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
#define CMD 0x00
#define DATA 0x40
#define CLOSESHOW 0xAE
#define SETCLS 0xD5
#define RATE 0xA8
#define OFFSET 0xD3
#define START 0x40
#define LEFTRIG 0xA1
#define UPDOWN 0xC8
#define HARD 0xDA
#define RATION 0x81
#define CYCLE 0xD9
#define VCOM 0xDB
#define SHOWUP 0xA4
#define BEN 0x8D
#define SHOWOK 0xAF
void OLED_Init(){
write_command(CLOSESHOW);
write_command(SETCLS);
write_command(0x80);
write_command(RATE);
write_command(0x3F);
write_command(OFFSET);
write_command(0x00);
write_command(START);
write_command(LEFTRIG);
write_command(UPDOWN);
write_command(HARD);
write_command(0x12);
write_command(RATION);
write_command(0xCF);
write_command(CYCLE);
write_command(0xF1);
write_command(VCOM);
write_command(0x30);
write_command(SHOWUP);
write_command(0xA6);
write_command(BEN);
write_command(0x14);
write_command(SHOWOK);
}

5.设置光标

在屏幕中有一个光标,只不过我们在初始化的时候就把这个光标给隐藏了,但是你要有这个概念,设置光标也是需要使用命令来进行操作,这里也需要参考一下命令手册,但是,我这已经写好了,直接用就可以了:

1
2
3
4
5
void set_currsor(u8 row, u8 col){
write_command(0xB0 |row);
write_command(0x10 | ((col & 0xF0) >> 4));
write_command(0x00 | (col & 0x0F));
}

row就是光标所在的行,col是光标所在的列。

6.显示字符

要让OLED显示字符需要使用到取摸3工具取出来,因为我们显示其实就是让那些液晶块亮,哪些不亮,所以需要取摸,这里我没找到Linux下的取摸工具,等后面我有时间后我写一个,然后下面是我之前取好的ASCII后32个字符对应的字摸,大家可以直接拿来用:

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
static unsigned char ASCII1[][16] = {
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},

{0x00,0x00,0x00,0xF8,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x33,0x00,0x00,0x00,0x00},/*"!",0*/

{0x00,0x10,0x0C,0x02,0x10,0x0C,0x02,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*""",1*/

{0x00,0x40,0xC0,0x78,0x40,0xC0,0x78,0x00,0x00,0x04,0x3F,0x04,0x04,0x3F,0x04,0x00},/*"#",2*/

{0x00,0x70,0x88,0x88,0xFC,0x08,0x30,0x00,0x00,0x18,0x20,0x20,0xFF,0x21,0x1E,0x00},/*"$",3*/

{0xF0,0x08,0xF0,0x80,0x60,0x18,0x00,0x00,0x00,0x31,0x0C,0x03,0x1E,0x21,0x1E,0x00},/*"%",4*/

{0x00,0xF0,0x08,0x88,0x70,0x00,0x00,0x00,0x1E,0x21,0x23,0x2C,0x19,0x27,0x21,0x10},/*"&",5*/

{0x00,0x12,0x0E,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"'",6*/

{0x00,0x00,0x00,0xE0,0x18,0x04,0x02,0x00,0x00,0x00,0x00,0x07,0x18,0x20,0x40,0x00},/*"(",7*/

{0x00,0x02,0x04,0x18,0xE0,0x00,0x00,0x00,0x00,0x40,0x20,0x18,0x07,0x00,0x00,0x00},/*")",8*/

{0x40,0x40,0x80,0xF0,0x80,0x40,0x40,0x00,0x02,0x02,0x01,0x0F,0x01,0x02,0x02,0x00},/*"*",9*/

{0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x0F,0x01,0x01,0x01},/*"+",10*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x90,0x70,0x00,0x00,0x00,0x00,0x00},/*",",11*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x01,0x01,0x01,0x01,0x01,0x00},/*"-",12*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00,0x00,0x00},/*".",13*/

{0x00,0x00,0x00,0x00,0xC0,0x38,0x04,0x00,0x00,0x60,0x18,0x07,0x00,0x00,0x00,0x00},/*"/",14*/

{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},/*"0",0*/

{0x00,0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00},/*"1",1*/

{0x00,0x70,0x08,0x08,0x08,0x08,0xF0,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},/*"2",2*/

{0x00,0x30,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x18,0x20,0x21,0x21,0x22,0x1C,0x00},/*"3",3*/

{0x00,0x00,0x80,0x40,0x30,0xF8,0x00,0x00,0x00,0x06,0x05,0x24,0x24,0x3F,0x24,0x24},/*"4",4*/

{0x00,0xF8,0x88,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x20,0x20,0x20,0x11,0x0E,0x00},/*"5",5*/

{0x00,0xE0,0x10,0x88,0x88,0x90,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x20,0x1F,0x00},/*"6",6*/

{0x00,0x18,0x08,0x08,0x88,0x68,0x18,0x00,0x00,0x00,0x00,0x3E,0x01,0x00,0x00,0x00},/*"7",7*/

{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},/*"8",8*/

{0x00,0xF0,0x08,0x08,0x08,0x10,0xE0,0x00,0x00,0x01,0x12,0x22,0x22,0x11,0x0F,0x00},/*"9",9*/

{0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x30,0x30,0x00,0x00,0x00},/*":",0*/

{0x00,0x00,0x00,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00},/*";",1*/

{0x00,0x00,0x80,0x40,0x20,0x10,0x08,0x00,0x00,0x01,0x02,0x04,0x08,0x10,0x20,0x00},/*"<",2*/

{0x00,0x40,0x40,0x40,0x40,0x40,0x40,0x00,0x00,0x02,0x02,0x02,0x02,0x02,0x02,0x00},/*"=",3*/

{0x00,0x08,0x10,0x20,0x40,0x80,0x00,0x00,0x00,0x20,0x10,0x08,0x04,0x02,0x01,0x00},/*">",4*/

{0x00,0x70,0x48,0x08,0x08,0x88,0x70,0x00,0x00,0x00,0x00,0x30,0x37,0x00,0x00,0x00},/*"?",5*/

{0xC0,0x30,0xC8,0x28,0xE8,0x10,0xE0,0x00,0x07,0x18,0x27,0x28,0x2F,0x28,0x17,0x00},/*"@",6*/

{0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20},/*"A",0*/

{0x08,0xF8,0x88,0x88,0x88,0x70,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x11,0x0E,0x00},/*"B",1*/

{0xC0,0x30,0x08,0x08,0x08,0x08,0x38,0x00,0x07,0x18,0x20,0x20,0x20,0x10,0x08,0x00},/*"C",2*/

{0x08,0xF8,0x08,0x08,0x08,0x10,0xE0,0x00,0x20,0x3F,0x20,0x20,0x20,0x10,0x0F,0x00},/*"D",3*/

{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x20,0x23,0x20,0x18,0x00},/*"E",4*/

{0x08,0xF8,0x88,0x88,0xE8,0x08,0x10,0x00,0x20,0x3F,0x20,0x00,0x03,0x00,0x00,0x00},/*"F",5*/

{0xC0,0x30,0x08,0x08,0x08,0x38,0x00,0x00,0x07,0x18,0x20,0x20,0x22,0x1E,0x02,0x00},/*"G",6*/

{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x20,0x3F,0x21,0x01,0x01,0x21,0x3F,0x20},/*"H",7*/

{0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"I",8*/

{0x00,0x00,0x08,0x08,0xF8,0x08,0x08,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00,0x00},/*"J",9*/

{0x08,0xF8,0x88,0xC0,0x28,0x18,0x08,0x00,0x20,0x3F,0x20,0x01,0x26,0x38,0x20,0x00},/*"K",10*/

{0x08,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x20,0x3F,0x20,0x20,0x20,0x20,0x30,0x00},/*"L",11*/

{0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x20,0x3F,0x01,0x3E,0x01,0x3F,0x20,0x00},/*"M",12*/

{0x08,0xF8,0x30,0xC0,0x00,0x08,0xF8,0x08,0x20,0x3F,0x20,0x00,0x07,0x18,0x3F,0x00},/*"N",13*/

{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x20,0x20,0x20,0x10,0x0F,0x00},/*"O",14*/

{0x08,0xF8,0x08,0x08,0x08,0x08,0xF0,0x00,0x20,0x3F,0x21,0x01,0x01,0x01,0x00,0x00},/*"P",15*/

{0xE0,0x10,0x08,0x08,0x08,0x10,0xE0,0x00,0x0F,0x10,0x28,0x28,0x30,0x50,0x4F,0x00},/*"Q",16*/

{0x08,0xF8,0x88,0x88,0x88,0x88,0x70,0x00,0x20,0x3F,0x20,0x00,0x03,0x0C,0x30,0x20},/*"R",17*/

{0x00,0x70,0x88,0x08,0x08,0x08,0x38,0x00,0x00,0x38,0x20,0x21,0x21,0x22,0x1C,0x00},/*"S",18*/

{0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"T",19*/

{0x08,0xF8,0x08,0x00,0x00,0x08,0xF8,0x08,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"U",20*/

{0x08,0x78,0x88,0x00,0x00,0xC8,0x38,0x08,0x00,0x00,0x07,0x38,0x0E,0x01,0x00,0x00},/*"V",21*/

{0x08,0xF8,0x00,0xF8,0x00,0xF8,0x08,0x00,0x00,0x03,0x3E,0x01,0x3E,0x03,0x00,0x00},/*"W",22*/

{0x08,0x18,0x68,0x80,0x80,0x68,0x18,0x08,0x20,0x30,0x2C,0x03,0x03,0x2C,0x30,0x20},/*"X",23*/

{0x08,0x38,0xC8,0x00,0xC8,0x38,0x08,0x00,0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00},/*"Y",24*/

{0x10,0x08,0x08,0x08,0xC8,0x38,0x08,0x00,0x20,0x38,0x26,0x21,0x20,0x20,0x18,0x00},/*"Z",25*/

{0x00,0x00,0x00,0xFE,0x02,0x02,0x02,0x00,0x00,0x00,0x00,0x7F,0x40,0x40,0x40,0x00},/*"[",0*/

{0x00,0x04,0x38,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x06,0x38,0xC0,0x00},/*"\",1*/

{0x00,0x02,0x02,0x02,0xFE,0x00,0x00,0x00,0x00,0x40,0x40,0x40,0x7F,0x00,0x00,0x00},/*"]",2*/

{0x00,0x00,0x04,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"^",3*/

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80},/*"_",4*/

{0x00,0x02,0x02,0x04,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"`",5*/

{0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x00,0x19,0x24,0x24,0x12,0x3F,0x20,0x00},/*"a",0*/

{0x10,0xF0,0x00,0x80,0x80,0x00,0x00,0x00,0x00,0x3F,0x11,0x20,0x20,0x11,0x0E,0x00},/*"b",1*/

{0x00,0x00,0x00,0x80,0x80,0x80,0x00,0x00,0x00,0x0E,0x11,0x20,0x20,0x20,0x11,0x00},/*"c",2*/

{0x00,0x00,0x80,0x80,0x80,0x90,0xF0,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},/*"d",3*/

{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x24,0x24,0x24,0x24,0x17,0x00},/*"e",4*/

{0x00,0x80,0x80,0xE0,0x90,0x90,0x20,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"f",5*/

{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x6B,0x94,0x94,0x94,0x93,0x60,0x00},/*"g",6*/

{0x10,0xF0,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"h",7*/

{0x00,0x80,0x98,0x98,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"i",8*/

{0x00,0x00,0x00,0x80,0x98,0x98,0x00,0x00,0x00,0xC0,0x80,0x80,0x80,0x7F,0x00,0x00},/*"j",9*/

{0x10,0xF0,0x00,0x00,0x80,0x80,0x80,0x00,0x20,0x3F,0x24,0x06,0x29,0x30,0x20,0x00},/*"k",10*/

{0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00,0x00},/*"l",11*/

{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x20,0x3F,0x20,0x00,0x3F,0x20,0x00,0x3F},/*"m",12*/

{0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x20,0x3F,0x21,0x00,0x00,0x20,0x3F,0x20},/*"n",13*/

{0x00,0x00,0x80,0x80,0x80,0x80,0x00,0x00,0x00,0x1F,0x20,0x20,0x20,0x20,0x1F,0x00},/*"o",14*/

{0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x00,0x80,0xFF,0x91,0x20,0x20,0x11,0x0E,0x00},/*"p",15*/

{0x00,0x00,0x00,0x80,0x80,0x00,0x80,0x00,0x00,0x0E,0x11,0x20,0x20,0x91,0xFF,0x80},/*"q",16*/

{0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x20,0x20,0x3F,0x21,0x20,0x00,0x01,0x00},/*"r",17*/

{0x00,0x00,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x33,0x24,0x24,0x24,0x24,0x19,0x00},/*"s",18*/

{0x00,0x80,0x80,0xE0,0x80,0x80,0x00,0x00,0x00,0x00,0x00,0x1F,0x20,0x20,0x10,0x00},/*"t",19*/

{0x80,0x80,0x00,0x00,0x00,0x80,0x80,0x00,0x00,0x1F,0x20,0x20,0x20,0x10,0x3F,0x20},/*"u",20*/

{0x80,0x80,0x80,0x00,0x80,0x80,0x80,0x00,0x00,0x03,0x0C,0x30,0x0C,0x03,0x00,0x00},/*"v",21*/

{0x80,0x80,0x00,0x80,0x80,0x00,0x80,0x80,0x01,0x0E,0x30,0x0C,0x07,0x38,0x06,0x01},/*"w",22*/

{0x00,0x80,0x80,0x80,0x00,0x80,0x80,0x00,0x00,0x20,0x31,0x0E,0x2E,0x31,0x20,0x00},/*"x",23*/

{0x80,0x80,0x80,0x00,0x00,0x80,0x80,0x80,0x00,0x81,0x86,0x78,0x18,0x06,0x01,0x00},/*"y",24*/

{0x00,0x80,0x80,0x80,0x80,0x80,0x80,0x00,0x00,0x21,0x30,0x2C,0x22,0x21,0x30,0x00},/*"z",25*/
};

static unsigned char number1[][16] = {
{0x00,0xE0,0x10,0x08,0x08,0x10,0xE0,0x00,0x00,0x0F,0x10,0x20,0x20,0x10,0x0F,0x00},/*"0",0*/

{0x00,0x00,0x10,0x10,0xF8,0x00,0x00,0x00,0x00,0x00,0x20,0x20,0x3F,0x20,0x20,0x00},/*"1",1*/

{0x00,0x70,0x08,0x08,0x08,0x08,0xF0,0x00,0x00,0x30,0x28,0x24,0x22,0x21,0x30,0x00},/*"2",2*/

{0x00,0x30,0x08,0x08,0x08,0x88,0x70,0x00,0x00,0x18,0x20,0x21,0x21,0x22,0x1C,0x00},/*"3",3*/

{0x00,0x00,0x80,0x40,0x30,0xF8,0x00,0x00,0x00,0x06,0x05,0x24,0x24,0x3F,0x24,0x24},/*"4",4*/

{0x00,0xF8,0x88,0x88,0x88,0x08,0x08,0x00,0x00,0x19,0x20,0x20,0x20,0x11,0x0E,0x00},/*"5",5*/

{0x00,0xE0,0x10,0x88,0x88,0x90,0x00,0x00,0x00,0x0F,0x11,0x20,0x20,0x20,0x1F,0x00},/*"6",6*/

{0x00,0x18,0x08,0x08,0x88,0x68,0x18,0x00,0x00,0x00,0x00,0x3E,0x01,0x00,0x00,0x00},/*"7",7*/

{0x00,0x70,0x88,0x08,0x08,0x88,0x70,0x00,0x00,0x1C,0x22,0x21,0x21,0x22,0x1C,0x00},/*"8",8*/

{0x00,0xF0,0x08,0x08,0x08,0x10,0xE0,0x00,0x00,0x01,0x12,0x22,0x22,0x11,0x0F,0x00},/*"9",9*/
};

然后根据这个字摸,我们可以显示字符了,这里写一个显示字符的函数:

1
2
3
4
5
6
7
8
9
10
11
void show_char(u8 row, u8 col, u8 charr){
u8 i; // 遍历变量
set_currsor(row, col + 8); // 设置显示位置
for (i = 0; i < 8; i++){
IIC_Write(ASCII1[charr - 32][i]);
}
set_currsor(row + 1, col + 8); // 设置下一行显示的位置
for (i = 0; i < 8; i++){
IIC_Write(ASCII1[charr - 32][i + 8]);
}
}

字摸的长度是16位,而我们显示的这个ASCII是8*8大小的,我们在第一次设置光标的时候要加上8是为了错过前面的一个字符,然后在显示字符的时候选择的是逐列式。

也就是先将第一个字节的字摸先显示,然后转到下一列再显示第二个字摸,依次下去,这样如果只有一半,也就是只有4*8,还需要转到下一行补上那4行才可以变成8*8。

在写这个函数的时候,我发现了一个好玩的,我这如果让列不跳过一个字符,而是跳过一位,那这个字符就会扭动,虽然没有什么用,但是感觉很有趣,代码如下,大家可以来试试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 扭动字符显示
// 只能显示一个,显示多个就不流畅了
void show_Twistchar(u8 row, u8 col, u8 word){
u8 i;
set_currsor(row, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i]);
}
set_currsor(row + 1, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i + 8]);
}
// 下面这些可以删掉,然后在主函数调用的时候调用两遍,要一样的字符
delay_us(5);
set_currsor(row, col * 8 + 1);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i]);
}
set_currsor(row + 1, col * 8 + 1);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i + 8]);
}
}

7.显示字符串

这个就得看大家对于串操作的熟练程度了,就是用到对字符串的操作,但是呢,这个需要考虑到换行,代码如下:

1
2
3
4
5
6
7
8
9
10
11
void show_string(u8 row, u8 col, u8* str){
u8 i, j = 0;
for (i = 0; str[i] != '\0'; i++, j++){
if (j >= 16){
row += 1;
col = 0;
j = 0;
}
show_char(row, col + j, str[i]);
}
}

这里我用一个变量作为判断条件,因为一行有128个像素点,我们显示8*8的字符,那一行只能显示16个8*8的字符,所以当j变量到了16个字符,我们就让行换一行,列清零,这样就可以实现换行操作了。

8.清屏

这个操作很简单,就是将每一行和每一列的所有像素点全部变为0x00就可以了,代码如下:

1
2
3
4
5
6
7
8
9
void clear(){
u8 i, j;
for (i = 0; i < 8; i++){
set_currsor(i, 0);
for (j = 0; j < 128; j++){
write_data(0x00);
}
}
}

9.显示汉字

这个也是需要使用取摸得到,然后每个汉字是16*16的点阵,我们只需要显示即可:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 现实汉字
void show_hz(u8 row, u8 col, u8 len){
u8 i, j, x, y;
y = row;
x = col;
for (j = 0; j < len * 2; j += 2, y+= 2){
if ((x + y) * 8 >= 128){
y = 0;
row = 2;
}
set_currsor(row, (x + y) * 8);
for (i = 0; i < 16; i++){
write_data(hz[j][i]);
}
set_currsor(row + 1, (x + y) * 8);
for (i = 0; i < 16; i++){
write_data(hz[j + 1][i]);
}
}
}

这个也是可以自动换行的,但在使用前需要将字摸放入到hz这个数组中,这个数组的格式如下:

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
static unsigned char hz[][16] = {
{0x20,0x24,0x24,0x24,0xFE,0x23,0x22,0x20,0x20,0xFF,0x20,0x22,0x2C,0xA0,0x20,0x00},
{0x00,0x08,0x48,0x84,0x7F,0x02,0x41,0x40,0x20,0x13,0x0C,0x14,0x22,0x41,0xF8,0x00},/*"我",0*/

{0x00,0x80,0x60,0xF8,0x07,0x00,0xF8,0x01,0x06,0x00,0x04,0x04,0x04,0xFC,0x00,0x00},
{0x01,0x00,0x00,0xFF,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x40,0x80,0x7F,0x00,0x00},/*"们",1*/

{0x20,0x24,0x24,0xA4,0x7F,0x24,0x34,0x28,0x26,0x20,0xFE,0x02,0x22,0xDA,0x06,0x00},
{0x04,0x02,0xFF,0x49,0x49,0x49,0x49,0xFF,0x00,0x00,0xFF,0x08,0x10,0x08,0x07,0x00},/*"都",2*/

{0x04,0x04,0x04,0x84,0xE4,0x3C,0x27,0x24,0x24,0x24,0x24,0xE4,0x04,0x04,0x04,0x00},
{0x04,0x02,0x01,0x00,0xFF,0x09,0x09,0x09,0x09,0x49,0x89,0x7F,0x00,0x00,0x00,0x00},/*"有",3*/

{0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x80,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"一",4*/

{0x80,0x80,0x40,0x20,0x10,0x08,0x04,0xC3,0x04,0x08,0x10,0x20,0x40,0x80,0x80,0x00},
{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*"个",5*/

{0x40,0x44,0x24,0x14,0x7F,0x94,0x24,0x00,0x24,0x14,0x7F,0x14,0x24,0x44,0x40,0x00},
{0x00,0x80,0x88,0x84,0x42,0x45,0x29,0x11,0x11,0x09,0x05,0x03,0x00,0x00,0x00,0x00},/*"梦",6*/

{0x00,0x08,0x88,0x68,0xFF,0x48,0x88,0x00,0xFE,0x52,0x52,0x52,0xFE,0x00,0x00,0x00},
{0x40,0x31,0x00,0x00,0x3B,0x40,0x40,0x44,0x5B,0x42,0x42,0x72,0x03,0x08,0x30,0x00},/*"想",7*/

{0x00,0x08,0x88,0x68,0xFF,0x48,0x88,0x00,0xFE,0x52,0x52,0x52,0xFE,0x00,0x00,0x00},
{0x40,0x31,0x00,0x00,0x3B,0x40,0x40,0x44,0x5B,0x42,0x42,0x72,0x03,0x08,0x30,0x00},/*"想",7*/

};

这里面有一些测试的汉字,大家可以测试一下。

10.显示图片

显示图片的代码我没有写,其实和显示字符和汉字一样,然后用取摸工具取出,然后看一下行有多少,列有多少,然后显示即可,这里我后面写完后会放入gitee上的,github的话因为我最近资金不足,所以没工具,懂得都懂。

11.显示动图

这个其实和GIF的原理一样,图片上覆盖图片,这样就会让图片动起来。

三、完整代码

我这个项目是分文件编写的,分为oled.c函数原型,oled.h函数声明和头文件引入,oledfont.h这个是字符库,sys.c是延时函数和系统操作,gpio.h是gpio口的初始化函数的声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// gpio.c
#include "gpio.h"

void MX_GPIOInit(){
GPIO_InitTypeDef GPIO_InitStruct = {0};

RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB, ENABLE);

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin = CLK;
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(GPIO, &GPIO_InitStruct);

GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_OD;
GPIO_InitStruct.GPIO_Pin = SDA;
GPIO_Init(GPIO, &GPIO_InitStruct);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// gpio.h
#ifndef __GPIO_H
#define __GPIO_H

#include "stm32f10x.h"

#define CLK GPIO_Pin_8
#define SDA GPIO_Pin_9
#define GPIO GPIOB

void MX_GPIOInit();

#endif

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
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
// oled.c
#include "oled.h"

// I2C协议开始
void IIC_Start(){
GPIO_SetBits(GPIO, SDA);
GPIO_SetBits(GPIO, CLK);
GPIO_ResetBits(GPIO, SDA);
GPIO_ResetBits(GPIO, CLK);
}

// IIC协议结束
void IIC_Stop(){
GPIO_ResetBits(GPIO, SDA);
GPIO_SetBits(GPIO, CLK);
GPIO_SetBits(GPIO, SDA);
}

// 通过IIC写入数据
void IIC_Write(u8 data){
u8 i;
for (i = 0; i < 8; i++){

if (data & 0x80){
GPIO_SetBits(GPIO, SDA);
}
else{
GPIO_ResetBits(GPIO, SDA);
}
GPIO_SetBits(GPIO, CLK);
GPIO_ResetBits(GPIOB, CLK);
data <<= 1;
// GPIO_ResetBits(GPIO, CLK);
// GPIO_SetBits(GPIO, CLK);
}
GPIO_SetBits(GPIO, CLK);
GPIO_ResetBits(GPIO, CLK);
}

// 写入命令
void write_command(u8 cmd){
IIC_Start();
IIC_Write(ADDR);
IIC_Write(0x00);
IIC_Write(cmd);
IIC_Stop();
}

// 写入数据
void write_data(u8 data){
IIC_Start();
IIC_Write(ADDR);
IIC_Write(0x40);
IIC_Write(data);
IIC_Stop();
}

// 设置光标
void set_currsor(u8 row, u8 col){
write_command(0xB0 |row);
write_command(0x10 | ((col & 0xF0) >> 4));
write_command(0x00 | (col & 0x0F));
}

// 扭动字符显示
// 只能显示一个,显示多个就不流畅了
void show_Twistchar(u8 row, u8 col, u8 word){
u8 i;
set_currsor(row, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i]);
}
set_currsor(row + 1, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i + 8]);
}
delay_us(5);
set_currsor(row, col * 8 + 1);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i]);
}
set_currsor(row + 1, col * 8 + 1);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i + 8]);
}
}

// 显示一个字符
void show_char(u8 row, u8 col, u8 word){
u8 i;
set_currsor(row * 2, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i]);
}
set_currsor(row * 2 + 1, col * 8);
for (i = 0; i < 8; i++){
write_data(ASCII1[word - 32][i + 8]);
}
}

// 显示字符串
void show_string(u8 row, u8 col, u8* str){
u8 i, j = 0;
for (i = 0; str[i] != '\0'; i++, j++){
if (j >= 16){
row += 1;
col = 0;
j = 0;
}
show_char(row, col + j, str[i]);
}
}

// 现实汉字
void show_hz(u8 row, u8 col, u8 len){
u8 i, j, x, y;
y = row;
x = col;
for (j = 0; j < len * 2; j += 2, y+= 2){
// x = (x + j) * 8;
if ((x + y) * 8 >= 128){
y = 0;
row = 2;
}
set_currsor(row, (x + y) * 8);
for (i = 0; i < 16; i++){
write_data(hz[j][i]);
}
set_currsor(row + 1, (x + y) * 8);
for (i = 0; i < 16; i++){
write_data(hz[j + 1][i]);
}
}
}

// 清屏
void clear(){
u8 i, j;
for (i = 0; i < 8; i++){
set_currsor(i, 0);
for (j = 0; j < 128; j++){
write_data(0x00);
}
}
}

// 初始化函数
void OLED_Init(){
u32 i, j;
for (i = 0; i < 1000; i++){
for (j = 0; j < 1000; j++);
}
write_command(CLOSESHOW);
write_command(SETCLS);
write_command(0x80);
write_command(RATE);
write_command(0x3F);
write_command(OFFSET);
write_command(0x00);
write_command(START);
write_command(LEFTRIG);
write_command(UPDOWN);
write_command(HARD);
write_command(0x12);
write_command(RATION);
write_command(0xCF);
write_command(CYCLE);
write_command(0xF1);
write_command(VCOM);
write_command(0x30);
write_command(SHOWUP);
write_command(0xA6);
write_command(BEN);
write_command(0x14);
write_command(SHOWOK);
clear();
}
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
// oled.h
#ifndef __OLED_H
#define __OLED_H

#include "stm32f10x.h"
#include "gpio.h"
#include "oledfont.h"
#include "sys.h"

#define ADDR 0x78

#define CMD 0x00
#define DATA 0x40
#define CLOSESHOW 0xAE
#define SETCLS 0xD5
#define RATE 0xA8
#define OFFSET 0xD3
#define START 0x40
#define LEFTRIG 0xA1
#define UPDOWN 0xC8
#define HARD 0xDA
#define RATION 0x81
#define CYCLE 0xD9
#define VCOM 0xDB
#define SHOWUP 0xA4
#define BEN 0x8D
#define SHOWOK 0xAF

void OLED_Init();
void clear();
void show_char(u8 row, u8 col, u8 word);
void set_currsor(u8 row, u8 col);
void show_Twistchar(u8 row, u8 col, u8 word);
void show_string(u8 row, u8 col, u8* str);
void show_hz(u8 row, u8 col, u8 len);

#endif

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// sys.c
#include "sys.h"

void delay_us(u32 time){
u32 temp;
SysTick -> LOAD = 9 * time;
SysTick -> CTRL = 0x01;
SysTick -> VAL = 0;
do{
temp = SysTick -> CTRL;
}while((temp & 0x01) && (!(temp & (1 << 16))));
SysTick -> CTRL = 0;
SysTick -> VAL = 0;
}
1
2
3
4
5
6
7
8
9
10
// sys.h
#ifndef __SYS_H
#define __SYS_H

#include "stm32f10x.h"

void delay_us(u32 time);

#endif

总结

学会了这个显示屏如何显示后后面我们可以对传感器进行操作了,到时候传感器接收到的数据就可以直接显示在OLED上,这样就能很好的观察其中的变化了。

下一章准备介绍一下显示屏的一些操作,比如显示进度条,显示动图或者其他内容。