IIC介绍

什么是IIC

I2C(IIC,Inter-Integrated Circuit),两线式串行通信总线,其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构。由PHILIPS公司开发用于连接微控制器及其外围设备。它是由数据线SDA和时钟SCL构成的串行总线,可发送和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传送,高速IIC总线一般可达400kbps

IIC是半双工通信方式,SDA 和 SCL 都是双向的;数据可以在主设备和从设备之间来回传送。主设备启动总线上的所有数据传输,并生成所有时钟信号。在开始传输数据之前,主设备必须先寻址一个特定的从设备并接收一条确认信息。

协议层

每个I2C数据操作都由以下元素组成:启动(或重复启动)、寻址、数据和停止。

空闲

I2C总线总线的SDA和SCL两条信号线同时处于高电平时,规定为总线的空闲状态。此时各个器件的输出级场效应管均处在截止状态,即释放总线,由两条信号线各自的上拉电阻把电平拉高。

image-20200822161555286

启动或重复启动

主设备控制时钟线路,因此它启动总线上的所有通信。要控制总线并启动数据传输,主设备会首先发送一个启动条件。

启动条件向总线上的所有器件发送信号,通知主设备已经控制了总线,并将发送一个地址。此时,总线即被视为繁忙,其他主设备必须等待停止条件使总线恢复空闲状态后,才能启动数据传输。

重复启动条件在物理层面上与启动条件相同。它表示主设备已经维持对总线的控制而且该总线没有处于闲置状态。

启动信号

当SCL为高期间,SDA由高到低的跳变;启动信号是一种电平跳变时序信号,而不是一个电平信号。

image-20200822161710522

地址

主设备发出启动信号后,发送的第一个数据字节就是地址。每个从设备都有唯一的地址。

大多数I2C地址的长度都为七个数据位,加上一个读/写(R/W)位凑满8位,并指出接下来数据传输的通信方向。

“读”代表主设备希望从设备中读取数据;“写”表明主设备希望向从设备中写入数据。

所有地址和数据字节都优先发送最高有效位 (MSB)。

ACK/NACK

主设备发送地址后,等待从设备发出确认(ACK)信息。总线上所有从设备都要读取接收到的地址,并将其与自己的内部地址进行比较。如果地址相匹配,从设备必须在第九个时钟循环发出ACK信息。如果地址不相匹配,从设备将不响应。

ACK是一个附加的状态位,位于每个数据字节的末尾,因此,所有I2C数据传输的长度都是九位。发送器每发送一个字节,就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号。应答信号为低电平时,规定为有效应答位(ACK简称应答位),表示接收器已经成功地接收了该字节;应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。

对于反馈有效应答位ACK的要求是,接收器在第9个时钟脉冲之前的低电平期间将SDA线拉低,并且确保在该时钟的高电平期间为稳定的低电平。 如果接收器是主控器,则在它收到最后一个字节后,发送一个NACK信号,以通知被控发送器结束数据发送,并释放SDA线,以便主控接收器发送一个停止信号。

释放数据线:IIC如何释放数据总线? 为什么=1就是释放? - wdliming - 博客园

ACK总结:

  • 规定如果是发送一个字节,必须接受一个应答信号;同理,接受一个字节,必定发送一个应答信号。
  • 规定应答信号:低电平是有效应答(也就是正常接受了一个字节数据),高电平为无效应答,也称为非应答(也就是接受字节异常,或者是用于结束接受)

数据

主设备发送地址而且从设备确认后,后续每次可以传送八位数据。主设备和从设备都可以传送和接收,因此它们一起拥有SDA线的控制权。

数据的有效性

I2C总线进行数据传送时,时钟信号为高电平期间,数据线上的数据必须保持稳定,只有在时钟线上的信号为低电平期间,数据线上的高电平或低电平状态才允许变化。即:数据在SCL的上升沿到来之前就需准备好。并在在下降沿到来之前必须稳定。且仅当SCL为低电平时,才能更改SDA上的数据。

image-20200823132727777image-20200822162533199

数据传输

在I2C总线上传送的每一位数据都有一个时钟脉冲相对应(或同步控制),即在SCL串行时钟的配合下,在SDA上逐位地串行传送每一位数据。数据位的传输是边沿触发。

主设备执行写入操作时,在SDA上写出八位的数据,并在SCL上提供八个时钟周期。主设备发送八位数据后,从设备负责在第九个时钟周期发送ACK或者NAK信号;请参考图7。

image-20200823133148069
  • ACK = 从设备有足够空间容纳更多数据。

  • NAK = 从设备无法容纳更多数据。

交替条件表明主设备希望在从设备中读取数据。在这种情况下,主设备会提供八个时钟周期,但是从设备在SDA上传输八个数据位。传输八个数据位后,主设备必须发送ACK或NAK信号。

  • ACK = 主设备希望读取更多数据。
  • NAK = 主设备读取完毕。

注意:从设备无法通知主设备没有要发送的数据。

停止

发送所有字节后,主设备要发送一个停止条件,表明当前数据传输已完成,总线处于闲置状态。

除了发送停止条件之外,主设备还可以发送重复启动条件。重复启动条件在物理层面上与启动条件相同。

当主设备想要通知从设备开始新的数据传输或者更改数据流的方向时,主设备会发送重复启动条件,这样可以保持对总线的控制。当主设备想要向特定的从设备写入数据,然后反过来从该从设备中读取数据时,需要使用重复启动条件。

通过使用重复启动条件,主设备可以维持对总线的控制。如果使用停止条件,则总线可能由其他主设备控制。

停止信号

当SCL为高期间,SDA由低到高的跳变;停止信号也是一种电平跳变时序信号,而不是一个电平信号。

image-20200822161737129

仲裁

I2C协议允许多个主设备在同一总线上进行通信。两个主设备可以同时进行通信。为了防止数据丢失,每个I2C主设备都必须查看I2C总线,确保总线上的数据为自己所提供。如果数据不匹配,仲裁失败的I2C主设备必须停止驱动SDA线,并等待总线闲置后再重新尝试发送数据。

如图10所示,当一个主设备将SDA保持为高电平,而另一个主设备尝试将SDA驱动至低电平时,前者仲裁失败,必须等待。

image-20200823134806106 image-20200822160653691

模拟IIC

MCU有部分引脚的复用功能,本身实现并支持IIC通信,但不是所有引脚都是这样。在IO资源有限的情况下,可以使用任意两个引脚模拟实现IIC时序。

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
/*
引脚说明:(可以是任意两个引脚)
PB8 -- SCL
PB9 -- SDA
*/

void Iic_Init(void)
{
GPIO_InitTypeDef GPIO_InitStruct;
RCC_AHB1PeriphClockCmd(RCC_AHB1Periph_GPIOB, ENABLE);

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_8|GPIO_Pin_9; //引脚8 9
GPIO_InitStruct.GPIO_Mode = GPIO_Mode_OUT; //输出模式
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //快速
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB, &GPIO_InitStruct);

//总线空闲
SCL = 1;
SDA_OUT = 1;
}

//切换SDA模式
void Iic_Sda_Mode(GPIOMode_TypeDef Mode)
{
GPIO_InitTypeDef GPIO_InitStruct;

GPIO_InitStruct.GPIO_Pin = GPIO_Pin_9; //引脚9
GPIO_InitStruct.GPIO_Mode = Mode;
GPIO_InitStruct.GPIO_OType = GPIO_OType_PP; //推挽
GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz; //快速
GPIO_InitStruct.GPIO_PuPd = GPIO_PuPd_UP; //上拉
GPIO_Init(GPIOB, &GPIO_InitStruct);
}

//开始信号
void Iic_Start(void)
{
Iic_Sda_Mode(GPIO_Mode_OUT);

//总线空闲
SCL = 1;
SDA_OUT = 1;
delay_us(5);

SDA_OUT = 0;
delay_us(5);
SCL = 0; //钳住总线
}

//停止信号
void Iic_Stop(void)
{
Iic_Sda_Mode(GPIO_Mode_OUT);

SCL = 0;
SDA_OUT = 0;
delay_us(5);

SCL = 1;
delay_us(5);
SDA_OUT = 1;
}

//发送一位数据
void Iic_Send_Ack(u8 ack)
{
Iic_Sda_Mode(GPIO_Mode_OUT);

SCL = 0;
//准备数据
//要发数据1
if(ack == 1)
{
SDA_OUT = 1; //引脚输1
}
//要发数据0
if(ack == 0)
{
SDA_OUT = 0; //引脚输0
}

delay_us(5);
SCL = 1;
delay_us(5);//两个延时5us,则为10us一个脉冲周期
SCL = 0;
}

//发一个字节
void Iic_Send_Byte(u8 data)
{
u8 i;

Iic_Sda_Mode(GPIO_Mode_OUT);

SCL = 0;

for(i=0; i<8; i++)
{
//准备数据 如数据 0x87 1 0 0 0 0 1 1 1
if(data & (1<<(7-i)))
{
SDA_OUT = 1; //引脚输1
}
//要发数据0
else
{
SDA_OUT = 0; //引脚输0
}
delay_us(5);
SCL = 1;
delay_us(5);
SCL = 0;
}
}

//接受一位数据
u8 Iic_Recv_Ack(void)
{
u8 ack = 0;

Iic_Sda_Mode(GPIO_Mode_IN);//模式设置为输入。这里就相当于释放了数据线

SCL = 0;
delay_us(5);
SCL = 1;
delay_us(5);
if(SDA_IN == 1) //判断引脚电平是否为高电平
{
ack = 1;
}
if(SDA_IN == 0) //判断引脚电平是否为低电平
{
ack = 0;
}

SCL = 0;
return ack;
}

//接受一个字节
u8 Iic_Recv_Byte(void)
{
u8 i, data = 0; //0 0 0 0 0 0 0 0

Iic_Sda_Mode(GPIO_Mode_IN);

SCL = 0;
//循环8次,接受一个字节
for(i=0; i<8; i++)
{
delay_us(5);
SCL = 1;
delay_us(5);
if(SDA_IN == 1) //判断引脚电平是否为高电平
{
data |= (1<<(7-i));
}

SCL = 0;
}
return data;
}

AT24C02

AT24C02是一个串行CMOS EEPROM,该器件通过IIC总线接口进行操作,有一个专门的写保护功能。AT24C02的存储容量为2K bit,内容分成32页,每页8Byte,共256Byt。操作时有两种寻址方式:芯片寻址和片内子地址寻址。

  1. 芯片寻址:AT24C02的芯片地址为1010,其地址控制字格式为1010+A2+A1+A0+R/W。其中A2,A1,A0可编程地址选择位。A2,A1,A0引脚接高、低电平后得到确定的三位编码,与1010形成7位编码,即为该器件的地址码。R/W为芯片读写控制位,该位为0,表示芯片进行写操作。
  2. 片内子地址寻址:芯片寻址可对内部256B中的任一个进行读/写操作,其寻址范围为00~FF,共256个寻址单位。

电路原理图

image-20200822161519605

24C02简介

  • 24C02是一个2K位串行CMOS 的EEPROM,内部含有256个8位字节。

  • 与 400KHz I2C 总线兼容

  • 1.8 到 6.0 伏工作电压范围

  • 低功耗 CMOS 技术

  • 写保护功能 当 WP 为高电平时进入写保护状态

  • 页写缓冲器

  • 自定时擦写周期

  • 1,000,000 编程/擦除周期

  • 可保存数据 100 年

AT24C02地址意义

image-20200822163957453 image-20200822164038218

高四位1010是24Cxx系列的固定器件地址,接下来是A2、A1、A0是根据器件连接来决定,我们的原理图都接地所以是000。R/W为是选择读还是写,1的时候是读,0的时候是写。

所以写操作的地址为0xA0,读操作的地址是A1。

写时序

image-20200822164301888 image-20200822164829219

读时序

image-20200822165654054

示例代码

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
//使用前面所述的模拟IIC
void AT24C02_Write(u8 addr, u8 *write_buf, u8 len)
{
u8 ack;
//开始信号
Iic_Start();

//发送设备地址,并执行写操作
Iic_Send_Byte(0xA0);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}

//发送写数据的起始地址
Iic_Send_Byte(addr);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}

while(len--)
{
//发送数据
Iic_Send_Byte(*write_buf);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}
//地址加1
write_buf++;
}

Iic_Stop();
printf("write finish\n");
}

void AT24C02_Read(u8 addr, u8 *read_buf, u8 len)
{
u8 ack;
//开始信号
Iic_Start();
//发送设置地址,并执行写操作
Iic_Send_Byte(0xA0);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}

//发送读数据的起始地址
Iic_Send_Byte(addr);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}
//开始信号
Iic_Start();//重复启动条件。可能是启动条件后面发送的第一个字节一定是地址吧,不然就当普通数据处理了。

//发送设备地址,并执行读操作
Iic_Send_Byte(0xA1);
ack = Iic_Recv_Ack();
if(ack == 1)
{
printf("ack failure\n");
Iic_Stop();
return;
}

while(len--) //len = 5
{
// len 4 3 2 1 0
//接受数据
*read_buf = Iic_Recv_Byte();
//地址加1
read_buf++;

if(len > 0)
Iic_Send_Ack(0);
}

//发送非应答
Iic_Send_Ack(1);
Iic_Stop();
printf("read finish\n");
}

OLED

硬件连接图

image-20200822172430274 image-20200822172541989

OLED本身是没有显存的,它的显存是依赖于SSD1306(驱动芯片)提供的,SSD1306本身是不带字库的,只能通过绘图扫描的方式来进行显示。

SSD1306的显存GDDRAM在串行模式下不提供数据读,总共为128 * 64bit大小,SSD1306将这些显存分为了8页。每页包含了128个字节,总共8页,这样刚好是128*64的点阵大小。

虽然不提供数据读,但是我们可以构建一个虚拟缓存unsigned char GDDRAM[8][128],每次向SSD1306的显存中更新这个数组的值,而在程序中就只需要对数组做操作。

0.96寸OLED驱动(基于STM32f103)_0.96 tft ui-CSDN博客

内存地址模式

内存地址模式一共有三种:页地址模式,水平地址模式和垂直地址模式。需要使用命令将地址模式设置为以上三种之一。

image-20200914232207021 image-20200914233057167
  • 页地址模式(A[1:0]=10b)
    当处于此模式时,在GDDRAM访问后(读/写),列地址指针将自动增加1。如果列地址指针到达列终止地址,列地址指针将复位到列起始地址,但页地址指针不会改变。为了访问GDDRAM中下一页的内容,用户必须设置新的页地址和列地址。通常在页地址模式下访问GDDRAM, 需要如下步骤来定义起始RAM访问指针指向:

    • 通过命令(B0h-B7h)设置目标显示位置页起始地址
    • 通过命令(00h-0Fh)设置列起始地址低位
    • 通过命令(10h-1Fh)设置列起始地址高位(移植的源码经过测试只用了三位)

    如果页地址是B2h,列地址低位是03h,列地址高位是10h,起始列将为PAGE2的SEG3。

    image-20201129213806741
  • 水平地址模式(A[1:0]=00b)
    当处于此模式时, 在GDDRAM访问后(读/写), 列地址指针将自动增加1。如果列地址指针到达列终止地址, 列地址指针将复位到列起始地址, 且页地址指针将自动增加1。水平地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)

    image-20200914180435644
  • 垂直地址模式(A[1:0]=01b)
    当处于此模式时, 在GDDRAM访问后(读/写), 页地址指针将自动增加1。如果页地址指针到达页终止地址, 页地址指针将复位到页起始地址, 且列地址指针将自动增加1。垂直地址模式下页以及列地址指针的行为如下图所示, 如果列地址指针和页地址指针都到达各自的终止地址时, 他们都将复位到各自的起始地址。(图中虚线)

    image-20200914180516635

示例代码

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
1.使用移植核心文件中的代码(路径:D:\STM32\09\移植核心文件),移植程序
DelayInit();修改为STM32平台Delay_Init();
I2C_Configuration();里面的初始化修改为模拟IIC初始化(PE8 PE10)
OLED_Init();函数里面的延时函数修改为STM32F407的延时函数

2.修改函数
void I2C_WriteByte(uint8_t addr,uint8_t data)
{
//启动信号
I2C_GenerateSTART(I2C1, ENABLE);//开启I2C1
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_MODE_SELECT));/*EV5,主模式*/

//发送设备地址(一个字节)
I2C_Send7bitAddress(I2C1, OLED_ADDRESS, I2C_Direction_Transmitter);//器件地址 -- 默认0x78
//等待应答
while(!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_TRANSMITTER_MODE_SELECTED));

//发送寄存器地址(一个字节)
I2C_SendData(I2C1, addr);//寄存器地址
//等待应答
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));

//发送数据(一个字节)
I2C_SendData(I2C1, data);//发送数据
//等待应答
while (!I2C_CheckEvent(I2C1, I2C_EVENT_MASTER_BYTE_TRANSMITTED));
//停止信号
I2C_GenerateSTOP(I2C1, ENABLE);//关闭I2C1总线
}

3.修改主函数当中的延时函数,修改相关头文件
#include "stm32f10x.h"头文件修改为 #include "stm32f4xx.h"

WriteCmd(0xAE); //关闭显示 休眠
WriteCmd(0x20); //设置内存寻址模式
WriteCmd(0x10); //00,水平寻址模式;01,垂直寻址模式;10,页面寻址模式(复位);11,无效

WriteCmd(0xb0); //为页面寻址模式设置页面起始地址,0-7
WriteCmd(0xc8); //设置COM输出扫描方向。此指令用于设置列输出的扫描方向, 增强了OLED模块设计的布局的伸缩性。
//注意, 此指令会立即生效。例如当屏幕正常显示时调用此指令, 屏幕将会立刻垂直翻转。
WriteCmd(0x00); //设置低位列地址
WriteCmd(0x10); //设置高位列地址
WriteCmd(0x40); //设置起始行地址

WriteCmd(0x81); //设置对比度控制寄存器
WriteCmd(0xff); //亮度调节 0x00~0xff

WriteCmd(0xa1); //--set segment re-map 0 to 127 设置段重映射
//此指令用于改变屏幕数据列地址和段驱动器间的映射关系, 这增强和OLED模块设计的可伸缩性。此命令只影响其后的数据输入, 已存储在 GDDRAM中的数据将保持不变。
WriteCmd(0xa6); //设置正常显示
WriteCmd(0xa8); //设置多路复用比率(1到64)
WriteCmd(0x3F); //

WriteCmd(0xa4); //0xa4,输出跟随RAM内容;0xa5,输出忽略RAM内容。命令A4h启用输出GDDRAM中的数据。如果命令A5h已被调用, 通过A4h指令, 可以将屏 幕显示从全屏点亮状态中恢复。命令A5h通过忽略GDDRAM中的数据以点亮全屏

WriteCmd(0xd3); //设置显示偏移
WriteCmd(0x00); //不偏移

WriteCmd(0xd5); //设置显示时钟分频比/振荡器频率
WriteCmd(0xf0); //设置分割比

WriteCmd(0xd9); //设置预充电周期
WriteCmd(0x22); //

WriteCmd(0xda); //设置com引脚硬件配置
WriteCmd(0x12);

WriteCmd(0xdb); //set vcomh。设置VCOMH反压值
WriteCmd(0x20); //0x20,0.77xVcc

WriteCmd(0x8d); //设置DC-DC启用,设置电荷泵
WriteCmd(0x14); //0x14开启电荷泵 0x10 关闭电荷泵

WriteCmd(0xaf); //打开oled面板 OLED唤醒

//示例代码
void OLED_SetPos(unsigned char x, unsigned char y) //设置起始点坐标
{
WriteCmd(0xb0+y);//相当于设置从哪一行开始
//设置从哪一列开始
WriteCmd(((x&0xf0)>>4)|0x10);//设置列起始地址高位
WriteCmd((x&0x0f)|0x01);//设置列起始地址低位
}

void OLED_Fill(unsigned char fill_Data)//全屏填充
{
unsigned char m,n;
for(m=0;m<8;m++)
{
WriteCmd(0xb0+m); //page0-page1
WriteCmd(0x00); //low column start address
WriteCmd(0x10); //high column start address
for(n=0;n<128;n++)
{
WriteDat(fill_Data);
}
}
}

// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); ch[] -- 要显示的字符串; TextSize -- 字符大小(1:6*8 ; 2:8*16)
// Description : 显示codetab.h中的ASCII字符,有6*8和8*16可选择
void OLED_ShowStr(unsigned char x, unsigned char y, unsigned char ch[], unsigned char TextSize)
{
unsigned char c = 0,i = 0,j = 0;
switch(TextSize)
{
case 1:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;//ascii码值-32 算出对应的索引
if(x > 126)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<6;i++)
WriteDat(F6x8[c][i]);
x += 6;
j++;
}
}break;
case 2:
{
while(ch[j] != '\0')
{
c = ch[j] - 32;
if(x > 120)
{
x = 0;
y++;
}
OLED_SetPos(x,y);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i]);
OLED_SetPos(x,y+1);
for(i=0;i<8;i++)
WriteDat(F8X16[c*16+i+8]);
x += 8;
j++;
}
}break;
}
}

// Parameters : x,y -- 起始点坐标(x:0~127, y:0~7); N:汉字在codetab.h中的索引
// Description : 显示codetab.h中的汉字,16*16点阵
void OLED_ShowCN(unsigned char x, unsigned char y, unsigned char N)
{
unsigned char wm=0;
unsigned int adder=32*N;
OLED_SetPos(x , y);
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
OLED_SetPos(x,y + 1);
for(wm = 0;wm < 16;wm++)
{
WriteDat(F16x16[adder]);
adder += 1;
}
}

// Parameters : x0,y0 -- 起始点坐标(x0:0~127, y0:0~7); x1,y1 -- 起点对角线(结束点)的坐标(x1:1~128,y1:1~8)
// Description : 显示BMP位图
void OLED_DrawBMP(unsigned char x0,unsigned char y0,unsigned char x1,unsigned char y1,unsigned char BMP[])
{
unsigned int j=0;
unsigned char x,y;

if(y1%8==0)
y = y1/8;
else
y = y1/8 + 1;

for(y=y0;y<y1;y++)
{
OLED_SetPos(x0,y);
for(x=x0;x<x1;x++)
{
WriteDat(BMP[j++]);
}
}
}

其他

取模软件的设置参考

image-20201122202115798

打赏
  • 版权声明: 本博客所有文章除特别声明外,著作权归作者所有。转载请注明出处!
  • Copyrights © 2021-2025 wrd
  • 访问人数: | 浏览次数:

      请我喝杯咖啡吧~

      支付宝
      微信