这里只是写录学习IIC协议遇到的问题,不会对IIC作过多的解释。有兴趣的话可以看这篇文章-I²C(IIC)总线协议详解—完整版。
完整代码Github
I²C 总线是一种两线串行的通信方式,SCL(时钟线),SDA(数据线)
I²C 总线是一个多主机的总线,其整个的数据格式如下所示:
IIC 总线格式图中相关符号解释:
S: 表示起始条件
P: 表示结束条件
ACK: 表示响应
R/W: = 0 时表示主机写模式, = 1时表示主机读模式
D/C: = 0时表示后面紧跟着的Data byte是一个字节的数据,=1时是命令
主机不管是发送数据还是命令,发送一个字节之后从机(本文章说的从机就是一个使用IIC协议的Oled)都要有一个ACK回应。
IIC总线的数据格式其实就是:
S + 从机地址(7bit) + R/W(1位) + ACK + 控制(1byte) + ACK+数据(1byte) + ACK + P
IIC连接方式大致如下
时序如下所示:
S的条件是:SCL保持高电平期间,SDA由高电平变成低电平,产生一个下降沿。
P的条件是:SCL保持高电平期间,SDA由低电平变成高电平,产生一个上升沿。
另外图中的tHSTART所需的最小时间为0.6us,而单片机一个_nop_()空指令时间差不多比1.08us多点所以用一个_nop_()延时即可,tSSTOP也同理。
根据描述转换成以下时序代码
void iic_start() {
// SCL保持高电平期间,SDA由高电平变成低电平,产生一个下降沿
SCL = 1;
SDA = 1;
// SDA高电平延时一段时间
_nop_();
SDA = 0;
_nop_();
}
void iic_stop() {
// SCL保持高电平期间,SDA由低电平变成高电平,产生一个上升沿
SCL = 1;
// SDA高电平延时一段时间
SDA = 0;
_nop_();
SDA = 1;
_nop_();
}
时序如下所示:
应答的条件: 在SCL高平电期间,检测到SDA是低电平
非应答的条件: 在SCL高平电期间,检测到SDA是高电平
根据条件形式如下代码:
char iic_ack() {
// 在SCL高平电期间,检测到SDA是低电平就是应答了,高电平是非应答
char result;
SCL = 1;
result = SDA;
_nop_();
// 检测完成之后最好将SCL拉低,如果不拉低可能会造成其它问题,我这里碰到了一直闪屏
SCL = 0;
_nop_();
return result;
}
数据的传输时序如下:
从图中可以看出只有在SCL为低电平的时间才允许SDA改变,在SCL为高平期间不允许其改变。另外需要注意的是传输的数据顺序是从高位到低位。所以根据以上条件可以形式如下代码:
void iic_sendByte(char byte) {
char temp = byte;
int i;
for(i = 0; i < 8; i ++) {
// 低电平期间允许更改SDA
SCL = 0;
// SDA每次只取最高位
SDA = temp & 0x80;
_nop_();
SCL = 1;
_nop_();
// 这里要注意拉低电平
SCL = 0;
_nop_();
temp <<= 1;
}
}
由`IIC 总线格式`图片所示,数据格式为
S + 从机地址(7bit) + R/W(1位) + ACK + 控制(1byte) + ACK+数据(1byte) + ACK + P
R/W = 0时为写操作。另外D/C = 1时为写命令,=0时为写数据。
由上可得出如下的代码
void iic_write_cmd(char cmd) {
iic_start();
// 01111100 + R/W(0是写操作)
iic_sendByte(0x78);
iic_ack();
// C0 + D/C + XXXXXX ==> D/C = 1 表示数据 = 0 表示命令
iic_sendByte(0x00);
iic_ack();
iic_sendByte(cmd);
iic_ack();
iic_stop();
}
void iic_write_data(char d) {
iic_start();
// 01111100 + R/W(0是写操作)
iic_sendByte(0x78);
iic_ack();
// C0 + D/C + XXXXXX ==> D/C = 1 表示数据 = 0 表示命令
iic_sendByte(0x40);
iic_ack();
iic_sendByte(d);
iic_ack();
iic_stop();
}
格式和读类似
Oled 是 128 * 64的点阵图,每一个点就是一个bit。每一bit都会对应一个实际的RAM, 而这个RAM又是静态的所以会保存上一次的状态,所以有可能造成花屏的现象,当然我们可以通过将RAM的每个bit写入0就可以避免这个现象。另外对于竖直方向又分成了8个页,每个页管理一个字节。Oled有三种寻址模式分别是页地址模式,水平地址模式和垂直地址模式。这里只描述下页地址模式。
从左到右将列的128位写满,如果未写满,即使切换了页,也会默认从上次写入的列的下一位开始写,而不是新页的第0列开始写入。由于只有八页所以用三个bit即可控制。
选择页的命令如下图:
所以Page0到Page7的地址为0xB0~0xB7
命令如下图:
先发送0x20命令,再发送0x02命令,即可选择页地址模式。
列选择命令如下所示:
解释如下:
128的列可以用一个字节表示,即128=0b1000 0000, 表示的围是0b0000 000到0b0111 1111。所以官方将字节的高四位用一个命令表示,低四位用一个命令表示。如上图所示,一个命令中的前四位表示的是一个地址字节的高/低四位,高四位用0b0001表示,低四位用0b0000表示。命令中的后四位表示具体列址选择。将两个命令中的后四位,组成一个字节的地址。
例1:
选择第0列,那么需要依次写入0x00、0x10命令可以切换列从新Page头开始。
0x0000 0000
0x0001 0000
取出两个命令的后四位得出列地址0x0000 0000
例2:
选择第64列,那么需要依次写入0x00、0x14命令可以切换列从页中间开始写入。
0x0000 0000
0x0001 0100
因为 0x0100 0000 = 64
例3:
给定任意一个列,如果对应命令
iic_write_cmd(0x00 + y0 % 16);
iic_write_cmd(0x10 + y0 / 16);
Oled的初始化按照手册进行操作就行,这里不再描述,非常简单。
void oled_init() {
iic_write_cmd(0xAE);//--display off
iic_write_cmd(0x00);//---set low column address
iic_write_cmd(0x10);//---set high column address
iic_write_cmd(0x40);//--set start line address
iic_write_cmd(0xB0);//--set page address
iic_write_cmd(0x81); // contract control
iic_write_cmd(0xFF);//--128
iic_write_cmd(0xA1);//set segment remap
iic_write_cmd(0xA6);//--normal / reverse
iic_write_cmd(0xA8);//--set multiplex ratio(1 to 64)
iic_write_cmd(0x3F);//--1/32 duty
iic_write_cmd(0xC8);//Com scan direction
iic_write_cmd(0xD3);//-set display offset
iic_write_cmd(0x00);//
iic_write_cmd(0xD5);//set osc division
iic_write_cmd(0x80);//
iic_write_cmd(0xD8);//set area color mode off
iic_write_cmd(0x05);//
iic_write_cmd(0xD9);//Set Pre-Charge Period
iic_write_cmd(0xF1);//
iic_write_cmd(0xDA);//set com pin configuartion
iic_write_cmd(0x12);//
iic_write_cmd(0xDB);//set Vcomh
iic_write_cmd(0x30);//
iic_write_cmd(0x8D);//set charge pump enable
iic_write_cmd(0x14);//
iic_write_cmd(0xAF);//--turn on oled panel
}
1、在Page0显示一个8 * 8 的小方块
void main() {
oled_init();
// 3、addressing setting command table 第三行
iic_write_cmd(0x20);
iic_write_cmd(0x02);
oled_clear();
iic_write_cmd(0xB0);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
iic_write_data(0xff);
while(1);
}
2、显示一个128 * 64的位图
建议用Pctolcd2002取模式,软件分为了好几个模式,在下面代码是用的列行式。
#include "reg52.h"
#include
sbit SDA = P0^3;
sbit SCL = P0^1;
code unsigned char peiqi[]=
{
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x30,0x90,0x50,0x50,0x90,0x20,0x7E,
0xC1,0x1D,0x64,0x44,0x79,0x02,0xBC,0x90,0xD0,0x50,0x90,0x88,0x28,0x68,0xA8,0x28,
0xA8,0x04,0xE4,0x54,0x54,0x64,0x68,0x48,0x90,0x20,0xC0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x04,0xC9,0x32,0x84,0x65,0x13,0x08,
0x05,0x3C,0x46,0x92,0x81,0x05,0xB9,0x83,0x6C,0x10,0x10,0x14,0x17,0x20,0x0C,0x03,
0x07,0x08,0x13,0xA4,0xAC,0x4A,0x12,0x92,0x4F,0x20,0x0F,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xF8,0x07,0xF0,0xFE,0x04,0x72,0x88,0x09,
0x0A,0x8A,0x64,0x08,0xF0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x04,
0xCA,0x71,0x85,0x3C,0xC2,0x02,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1C,0x61,0x8E,0x13,0x44,0x84,0x85,
0x05,0x04,0x02,0x01,0x03,0x04,0x09,0x1A,0x12,0x14,0x14,0x14,0x12,0x8B,0x48,0x82,
0xB1,0x4C,0xE3,0x1C,0x0F,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,
0x06,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0xF0,0x08,0x08,0x48,0x08,0xA8,0x84,0x54,0xC4,0x62,0x02,0xE3,0x1C,0x06,0x02,0x04,
0x05,0x09,0x0A,0x0A,0x0A,0x02,0x0A,0x0A,0x1A,0x19,0x01,0x05,0x04,0x02,0x02,0x01,
0x03,0x0C,0x71,0x82,0x22,0xC4,0x54,0x44,0xA8,0x08,0x68,0x08,0x08,0x40,0x10,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x31,0x4A,0x8A,0x06,0x12,0x83,0xA2,0x84,0x07,0xE0,0x0F,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x07,0xF8,0x03,0x3C,0xE0,0x00,0x00,0x02,0x01,0x02,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x01,0x01,0x00,0x0C,0x03,0x10,0x17,0x14,0x14,0x14,0xD4,0x34,0x84,
0x04,0xF4,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0x14,0xF4,0x04,0xC4,0x04,
0xF4,0x94,0x54,0x54,0x54,0x57,0x10,0x4F,0xA0,0x40,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xC0,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x06,0x01,0x08,0x08,
0x10,0x15,0x15,0x15,0x15,0x11,0x0C,0x00,0x00,0x00,0x00,0x00,0x07,0x08,0x00,0x08,
0x0A,0x0A,0x0A,0x0A,0x08,0x00,0x01,0x90,0x90,0x00,0x80,0x80,0x90,0x90,0x80,0x20,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
};
void iic_start() {
SCL = 1;
SDA = 1;
_nop_();
SDA = 0;
_nop_();
}
void iic_stop() {
SCL = 1;
SDA = 0;
_nop_();
SDA = 1;
_nop_();
}
char iic_ack() {
char result;
SCL = 1;
result = SDA;
_nop_();
SCL = 0;
_nop_();
return result;
}
void iic_sendByte(char byte) {
char temp = byte;
int i;
for(i = 0; i < 8; i ++) {
SCL = 0;
SDA = temp & 0x80;
_nop_();
SCL = 1;
_nop_();
// 拉低电平
SCL = 0;
_nop_();
temp <<= 1;
}
}
void iic_write_cmd(char cmd) {
iic_start();
// 01111100 + R/W(0是写操作)
iic_sendByte(0x78);
iic_ack();
// C0 + D/C + XXXXXX ==> D/C = 1 表示数据 = 0 表示命令
iic_sendByte(0x00);
iic_ack();
iic_sendByte(cmd);
iic_ack();
iic_stop();
}
void iic_write_data(char d) {
iic_start();
// 01111100 + R/W(0是写操作)
iic_sendByte(0x78);
iic_ack();
// C0 + D/C + XXXXXX ==> D/C = 1 表示数据 = 0 表示命令
iic_sendByte(0x40);
iic_ack();
iic_sendByte(d);
iic_ack();
iic_stop();
}
void oled_init() {
iic_write_cmd(0xAE);//--display off
iic_write_cmd(0x00);//---set low column address
iic_write_cmd(0x10);//---set high column address
iic_write_cmd(0x40);//--set start line address
iic_write_cmd(0xB0);//--set page address
iic_write_cmd(0x81); // contract control
iic_write_cmd(0xFF);//--128
iic_write_cmd(0xA1);//set segment remap
iic_write_cmd(0xA6);//--normal / reverse
iic_write_cmd(0xA8);//--set multiplex ratio(1 to 64)
iic_write_cmd(0x3F);//--1/32 duty
iic_write_cmd(0xC8);//Com scan direction
iic_write_cmd(0xD3);//-set display offset
iic_write_cmd(0x00);//
iic_write_cmd(0xD5);//set osc division
iic_write_cmd(0x80);//
iic_write_cmd(0xD8);//set area color mode off
iic_write_cmd(0x05);//
iic_write_cmd(0xD9);//Set Pre-Charge Period
iic_write_cmd(0xF1);//
iic_write_cmd(0xDA);//set com pin configuartion
iic_write_cmd(0x12);//
iic_write_cmd(0xDB);//set Vcomh
iic_write_cmd(0x30);//
iic_write_cmd(0x8D);//set charge pump enable
iic_write_cmd(0x14);//
iic_write_cmd(0xAF);//--turn on oled panel
}
void oled_clear() {
int i, j;
for(i = 0; i < 8; i ++) {
iic_write_cmd(0xB0+i);
iic_write_cmd(0x00);
iic_write_cmd(0x10);
for(j = 0; j < 128; j ++) {
iic_write_data(0);
}
}
}
void oled_image(unsigned char *image)
{
unsigned char i;
unsigned int j;
for(i=0;i<8;i++){
iic_write_cmd(0xB0 + i);
iic_write_cmd(0x00);
iic_write_cmd(0x10);
for(j = 128 * i; j<(128 * (i+1));j++){
iic_write_data(image[j]);
}
}
}
void main() {
oled_init();
// 3、addressing setting command table 第三行
iic_write_cmd(0x20);
iic_write_cmd(0x02);
oled_clear();
oled_image(peiqi);
while(1);
}
闪屏视频:
oled 闪屏