51单片机之手撸IIC协议,并点亮oled

这里只是写录学习IIC协议遇到的问题,不会对IIC作过多的解释。有兴趣的话可以看这篇文章-I²C(IIC)总线协议详解—完整版。

完整代码Github​​​​​​​

一、IIC 总线数据格式

  I²C 总线是一种两线串行的通信方式,SCL(时钟线),SDA(数据线)

  I²C 总线是一个多主机的总线,其整个的数据格式如下所示:

51单片机之手撸IIC协议,并点亮oled_第1张图片 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连接方式大致如下

51单片机之手撸IIC协议,并点亮oled_第2张图片

1.1、S (起始条件), P(结束条件)

 时序如下所示:

51单片机之手撸IIC协议,并点亮oled_第3张图片

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_();
}

1.2、ACK(响应信号)

 时序如下所示:

51单片机之手撸IIC协议,并点亮oled_第4张图片

应答的条件: 在SCL高平电期间,检测到SDA是低电平

非应答的条件: 在SCL高平电期间,检测到SDA是高电平

根据条件形式如下代码:

char iic_ack() {
    // 在SCL高平电期间,检测到SDA是低电平就是应答了,高电平是非应答
	char result;
	SCL = 1;
	result = SDA;
	_nop_();
    // 检测完成之后最好将SCL拉低,如果不拉低可能会造成其它问题,我这里碰到了一直闪屏
	SCL = 0;
	_nop_();
	return result;
}

1.3、传输一个Byte

数据的传输时序如下:

51单片机之手撸IIC协议,并点亮oled_第5张图片

从图中可以看出只有在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;
	}
}

1.4、写操作

由`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();
}

1.5、读操作

        格式和读类似

二、利用 IIC 操作Oled(SSD1306)

Oled 是 128 * 64的点阵图,每一个点就是一个bit。每一bit都会对应一个实际的RAM, 而这个RAM又是静态的所以会保存上一次的状态,所以有可能造成花屏的现象,当然我们可以通过将RAM的每个bit写入0就可以避免这个现象。另外对于竖直方向又分成了8个页,每个页管理一个字节。Oled有三种寻址模式分别是页地址模式,水平地址模式和垂直地址模式。这里只描述下页地址模式。

2.1、SSD1396的页结构如下所示

51单片机之手撸IIC协议,并点亮oled_第6张图片

2.2、页地址模式的移动规律如下图所示:

51单片机之手撸IIC协议,并点亮oled_第7张图片

从左到右将列的128位写满,如果未写满,即使切换了页,也会默认从上次写入的列的下一位开始写,而不是新页的第0列开始写入。由于只有八页所以用三个bit即可控制。

选择页的命令如下图

51单片机之手撸IIC协议,并点亮oled_第8张图片

所以Page0到Page7的地址为0xB0~0xB7

2.3、选择页地址模式

命令如下图: 

先发送0x20命令,再发送0x02命令,即可选择页地址模式。

2.4、选择列

列选择命令如下所示:

51单片机之手撸IIC协议,并点亮oled_第9张图片

解释如下:

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);

2.5、oled初始化

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);
}

51单片机之手撸IIC协议,并点亮oled_第10张图片

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);
}

51单片机之手撸IIC协议,并点亮oled_第11张图片

闪屏视频:

oled 闪屏

你可能感兴趣的:(51单片机,嵌入式硬件,单片机)