IIC 协议 和 OLED

IIC 协议概述

IIC全程Inter-Integrated Circuit(集成电路总线),是一种两线式串行总线,用于连接微控制器及其外围设备,IIC属于半双工同步通信方式(同一时间,只能发送或接收)

特点:

简单,有效,多主控(其中任何能够进行发送或接收的设备都可以成为主总线,一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控

构成:

IIC串行总线一般有两条信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线SCL上,对于并联在一条总线上的每个IC都有唯一的地址。

 ​

OLED

之前学习的LCD1602和dht11都属于非标协议的元件,即他们有自己独特的激活方式,需要针对他们不同的器件来写IO口的高低从而进行控制。OLED属于标准协议的元件,即其使用的协议就是IIC,只要使用IIC就可以控制它,因此IIC的学习就从OLED的使用开始。

 

IIC 协议通信流程

IIC传输数据时一共有三种类型的信号:开始信号结束信号应答信号

如果单片机封装了IIC,那么IIC的初始化将会和串口的类似,即操控寄存器的值即可,但是C51没有封装这个协议,那么实现的方法就类似前几节对LCD或dht的初始化编程类似,即通过时序图来实现IO口的变化从而模拟IIC协议

 

起始信号和终止信号

 虚线过程中,SCL始终是高电平,起始信号中关注SDA由高变低的过程;终止信号关注SDA由低变高的过程。

void IIC_start() //SCL为高时,SDA由高变低
{
	SDA = 0;	
	SCL = 1;
	SDA = 1;
	_nop_(); //约5微妙
	SDA = 0;
	_nop_();
	SCL = 0;
	
}

void IIC_stop() //SCL为低时,SDA由低变高
{
	SCL = 0;	
	SCL = 1;
	SDA = 0;
	_nop_();
	SDA = 1;
	_nop_();
	SDA = 0;
	
}

应答信号

 

char IIC_ACK()
{
	char flag;
	
	SDA = 1;//在脉冲9释放数据线
	_nop_();
	SCL = 1;
	_nop_();
	
	flag = SDA;
	_nop_();
	SCL = 0;
	//_nop_();
	
	return flag;
}

 数据发送的时序

 从左到右,在SCL为高时,SDA由高变低为起始信号,之后时钟线SCL就开始发送脉冲,数据线SDA开始相应发送数据,在SCL脉冲为高电平时,SDA的数据不允许改变,要么0要么1,只有在SCL变为低电平时,即在脉冲的间隔中,SDA才可以反转。

同时,在编程的时候,也可以借鉴之前dht11的编程经验,对于一位一位数据的发送或接收,使用“ 移位<< ”和“ 与或 ”的方法实现。

void IIC_sendByte(char data) //data直接定义为char的好处是char就是1个byte(8个bit)
{	
	int i;
	
	for(i = 0; i < 8; i++){
		SCL = 0; //拉低,让SDA准备数据
		SDA = data & 0x80; //与上“1000 0000”,即只有最高位保留,其他全部清0
		_nop_();//给数据建立一个时间
		SCL = 1; //拉高,形成脉冲开始传输,此时SDA不能变
		_nop_();//发送中
		SCL = 0;//重新拉低
		_nop_();
		data = data << 1; //发完1个bit左移一位
	}

}

OLED控制

OLED使用的是IIC协议,所以上面写的4个函数都适用,但是OLED也有自己写命令的一些格式需要学习:

 控制流程大概是:

1. Start:即IIC的start

2. Slave address: 011110+SA0+R/W#

 可见SA0的值可高可低, 它是用于区分多个OLED屏幕的,现在我只有一块不用关心。

 同时根据手册,在写模式下,R/W# 位应该置0。

综上,Slave address应该可以设置为 0111 1000(0x78)0111 1010(0x7A)

3. ACK:即IIC的ack

4. Control byte:Co+D/C+000000

Co位如果是‘ 0 ’,就决定了接下来的传输是不是只包含data byte。

 D/C如果是‘ 0 ’则接下来的data byte是命令,如果是‘ 1 ’则接下来的data byte是数据

综上,如果我想只传输data byte,则应该设置为 0000 0000(0x00)(写命令)0100 0000(0x40)(写数据)

5. ACK:即IIC的ack

6. 写入指令/数据

7. ACK:即IIC的ack

8. STOP即IIC的stop

void OLED_writecmd(char cmd)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x00);
	IIC_ACK();
	IIC_sendByte(cmd);
	IIC_ACK();
	IIC_stop();
		
}

void OLED_writedata(char wdata)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x40);
	IIC_ACK();
	IIC_sendByte(wdata);
	IIC_ACK();
	IIC_stop();
	
}

OLED显示

OLED的控制手法的学习就像掌握了工具,OLED归根结底还是一块显示屏,所有的控制和协议都是为了最后在屏幕上显示让人读懂的手段:

其实,学习过LCD1602之后,显示的方式已经大概熟悉,主要的流程就是初始化,然后决定“在哪里显示什么

初始化:

查阅手册,OLED同样给出了初始化的方式:

因此实现的方式也相对容易,直接用刚刚封装的写命令的函数就好了:

void Oled_Init()
{
	OLED_writecmd(0xAE);//--display off
	OLED_writecmd(0x00);//---set low column address
	OLED_writecmd(0x10);//---set high column address
	OLED_writecmd(0x40);//--set start line address  
	OLED_writecmd(0xB0);//--set page address
	OLED_writecmd(0x81); // contract control
	OLED_writecmd(0xFF);//--128   
	OLED_writecmd(0xA1);//set segment remap 
	OLED_writecmd(0xA6);//--normal / reverse
	OLED_writecmd(0xA8);//--set multiplex ratio(1 to 64)
	OLED_writecmd(0x3F);//--1/32 duty
	OLED_writecmd(0xC8);//Com scan direction
	OLED_writecmd(0xD3);//-set display offset
	OLED_writecmd(0x00);//
	
	OLED_writecmd(0xD5);//set osc division
	OLED_writecmd(0x80);//
	
	OLED_writecmd(0xD8);//set area color mode off
	OLED_writecmd(0x05);//
	
	OLED_writecmd(0xD9);//Set Pre-Charge Period
	OLED_writecmd(0xF1);//
	
	OLED_writecmd(0xDA);//set com pin configuartion
	OLED_writecmd(0x12);//
	
	OLED_writecmd(0xDB);//set Vcomh
	OLED_writecmd(0x30);//
	
	OLED_writecmd(0x8D);//set charge pump enable
	OLED_writecmd(0x14);//
	
	OLED_writecmd(0xAF);//--turn on oled panel		
}

在哪里?

即确定“显示的位置”,OLED其实就是一块128x64的点阵。OLED的屏幕在竖直方向上被分为8个Page,而竖直方向上一共有64个显示点,所以1个Page就是128x8的排列。

对于Page的管理方式有3种:

1. 页地址模式 

在这种模式下,如果对于PAGE0不断写数据,会从第0列一直写到第127列,然后重新写PAGE0的第0列。

 

2. 水平地址模式

在这种模式下,如果对于PAGE0不断写数据,会从第0列一直写到第127列,然后写PAGE1的第0列,再写128列之后切换到PAGE3...一直写到PAGE7的127列之后,会回到PAGE0的第0列。

3. 垂直地址模式

在这种模式下,如果对于PAGE0不断写数据,PAGE0的第0列写完会写PAGE1的第0列,然后是PAGE2的第0列....直到PAGE7的第0列也写完,才会开始写PAGE0的第1列,再把所有PAGE的第1列写完....一直写到PAGE7的127列之后,会回到PAGE0的第0列。

 那么问题又来了,如何配置这三种地址模式呢?

1. 在说明列表的红框处这一行,写出了配置的方法: 需要两个命令来设置,其中第二个命令的A1和A0的值的变化可以决定使用哪种PAGE的管理方式。

在这里我选择页寻址的模式:即要发送1. 0x20的cmd  2. 0x02的cmd (0000 0010)

2. 另外,前两行里面,可以选择从哪一列开始写,有两组X3,X2,X1,X0(低4位和高4位)来决定,如果我想要在第0列开始写,则应该发送决定低四位的 0x00的cmd(0000 0000)和 决定高四位的 0x10的cmd(0001 0000)

那么最后一个问题来了,配置完PAGE的模式,并可以决定从哪一列开始写了之后,如何选择从哪个PAGE开始写?

右下角的方框里提到了,for page addressing mode,所以在页寻址的模式下,X2,X1,X0三位可以确定开始的PAGE,如果配置成0 0 0即从PAGE0开始,即写入0xB0的cmd (1011 0000)

显示什么?

即决定“显示的内容”,正如上面的说明,1个Page就是128x8的排列,所以每一列正好可以使用1个Byte来控制:例如这个Byte的值为0x08,即这一列从上往下8个显示点依次是0001 0000, 即从上往下数第4个显示点被点亮。 

在了解如何点亮一个点了之后,结合刚刚选择PAGE和列的方法,就可以先实现“清屏函数”,因为如果不清屏,OLED的屏幕会显示所有显示过的历史信息,这显然是不对的:

void Oled_Clear()
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 0; j<128; j++){
			OLED_writedata(0x00);//由于地址会自动偏移,所以只要重复写128次全0,就可以清一个PAGE
		}
	}
}

以上就是显示的基础逻辑,但是这只可以显示一个点,一条线,如何显示一个字符?

理论上,只要花时间研究哪一行显示哪几个点,就可以推出一个字符如何显示,但是这样未免太过复杂,因此,可以使用字模软件

1. 选择左下角的“参数设置”,然后选择“文字输入区字体选择”,进行设置

2. 同样选择左下角的“参数设置”,然后选择“其他选项”,进行设置

3. 选择右下角的“文字输入区”,输入大写字符A,然后按下“Ctrl”+"回车"

3. 选择左下角的“取模方式” ,然后选择“C51格式”,再点击右下角的点阵生成区:

 这时,就可以生成显示A需要的位置信息!

/*--  文字:  A  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/
0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20

可见,生成的8x16的A字符,要在纵向占用2个PAGE,因为1个PAGE是128x8,因此需要将以上16个数据拆分成两个,8个一组:0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00  0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20

之后,根据PAGE和列的选择,分别打印A的上半部分和下半部分,就可以完成A的完整显示:

	OLED_writecmd(0xB0);//选择从PAGE0开始	
	OLED_writecmd(0x00);//选择列
	OLED_writecmd(0x10);//选择列
	for(i = 0; i<8; i++){
		OLED_writedata(A1[i]); //绘制字符A的上半部分
	}
	
	OLED_writecmd(0xB1);//选择从PAGE1开始	
	OLED_writecmd(0x00);//选择列
	OLED_writecmd(0x10);//选择列
	for(i = 0; i<8; i++){
		OLED_writedata(A2[i]); //绘制字符A的下半部分
	}

以上就是打印一个字符的逻辑,由此,可以打印各种字符,那么如何打印图像?

首先:由于屏幕是128x64的大小,所以图片也必须是128x64的像素点大小,并且由于屏幕不是彩色的,所以必须保存为单色位图的bmp格式

准备好图片之后,再次打开字模软件

1.点击左上角的“基本操作”,然后点击“打开图像图标”,并选择刚刚准备好的图片: 

2. 然后同样选择左下角的“取模方式” ,然后选择“C51格式”,再点击右下角的点阵生成区:


3. 最后,图片的显示过程其实和清屏的过程类似:

void print_pic(unsigned char *pic)
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 128*i; j<(128*(i+1)); j++){ 
			OLED_writedata(pic[j]);
		}
	}
}
	

最终代码实现

1. 如果我现在想要选择页寻址模式从PAGE0开始显示一个点的话,代码应该如下:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;
sbit SCL = P0^1;
sbit SDA = P0^3;


void IIC_start()
{
	SDA = 0;	
	SCL = 1;
	SDA = 1;
	_nop_(); //约5微妙
	SDA = 0;
	_nop_();
	SCL = 0;
	
}

void IIC_stop()
{
	SCL = 0;	
	SCL = 1;
	SDA = 0;
	_nop_();
	SDA = 1;
	_nop_();
	SDA = 0;
	
}

char IIC_ACK()
{
	char flag;
	
	SDA = 1;//在脉冲9释放数据线
	_nop_();
	SCL = 1;
	_nop_();
	
	flag = SDA;
	_nop_();
	SCL = 0;
	//_nop_();
	
	return flag;
}

void IIC_sendByte(char sdata) //data直接定义为char的好处是char就是1个byte(8个bit)
{	
	int i;
	
	for(i = 0; i < 8; i++){
		SCL = 0; //拉低,让SDA准备数据
		SDA = sdata & 0x80; //与上“1000 0000”,即只有最高位保留,其他全部清0
		_nop_();//给数据建立一个时间
		SCL = 1; //拉高,形成脉冲开始传输,此时SDA不能变
		_nop_();//发送中
		SCL = 0;//重新拉低
		_nop_();
		sdata = sdata << 1; //发完1个bit左移一位
	}

}

void OLED_writecmd(char cmd)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x00);
	IIC_ACK();
	IIC_sendByte(cmd);
	IIC_ACK();
	IIC_stop();
		
}

void OLED_writedata(char wdata)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x40);
	IIC_ACK();
	IIC_sendByte(wdata);
	IIC_ACK();
	IIC_stop();
	
}

void Oled_Init()
{
	OLED_writecmd(0xAE);//--display off
	OLED_writecmd(0x00);//---set low column address
	OLED_writecmd(0x10);//---set high column address
	OLED_writecmd(0x40);//--set start line address  
	OLED_writecmd(0xB0);//--set page address
	OLED_writecmd(0x81); // contract control
	OLED_writecmd(0xFF);//--128   
	OLED_writecmd(0xA1);//set segment remap 
	OLED_writecmd(0xA6);//--normal / reverse
	OLED_writecmd(0xA8);//--set multiplex ratio(1 to 64)
	OLED_writecmd(0x3F);//--1/32 duty
	OLED_writecmd(0xC8);//Com scan direction
	OLED_writecmd(0xD3);//-set display offset
	OLED_writecmd(0x00);//
	
	OLED_writecmd(0xD5);//set osc division
	OLED_writecmd(0x80);//
	
	OLED_writecmd(0xD8);//set area color mode off
	OLED_writecmd(0x05);//
	
	OLED_writecmd(0xD9);//Set Pre-Charge Period
	OLED_writecmd(0xF1);//
	
	OLED_writecmd(0xDA);//set com pin configuartion
	OLED_writecmd(0x12);//
	
	OLED_writecmd(0xDB);//set Vcomh
	OLED_writecmd(0x30);//
	
	OLED_writecmd(0x8D);//set charge pump enable
	OLED_writecmd(0x14);//
	
	OLED_writecmd(0xAF);//--turn on oled panel		
}

void Oled_Clear()
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 0; j<128; j++){
			OLED_writedata(0x00);//由于地址会自动偏移,所以只要重复写128次全0,就可以清一个PAGE
		}
	}
}

void main()
{
	Oled_Init();
	Oled_Clear();
	
	OLED_writecmd(0x20);//页寻址模式
	OLED_writecmd(0x02);//页寻址模式
	
	OLED_writecmd(0xB0);//选择从PAGE0开始
	
	OLED_writecmd(0x00);//选择列
	OLED_writecmd(0x10);//选择列
	
	OLED_writedata(0x08);
	while(1);//不让程序退出


}

显示效果

 

2. 如果我现在想要选择页寻址模式在左上角显示字符’马佳茗'的话,代码应该如下:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;
sbit SCL = P0^1;
sbit SDA = P0^3;


char ma_1[16] = {0x00,0x02,0x02,0xF2,0x82,0x82,0x82,0x82,0x82,0x82,0xFE,0x80,0x80,0x80,0x00,0x00};
char ma_2[16] = {0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x08,0x48,0x80,0x40,0x3F,0x00,0x00};
char jia_1[16] = {0x00,0x80,0x60,0xF8,0x47,0x40,0x44,0x44,0x44,0x7F,0x44,0x44,0x44,0x44,0x40,0x00};
char jia_2[16] = {0x01,0x00,0x00,0xFF,0x40,0x40,0x44,0x44,0x44,0x7F,0x44,0x44,0x44,0x44,0x40,0x00};
char ming_1[16] = {0x04,0x04,0x84,0x84,0x4F,0xA4,0x34,0x24,0x24,0x24,0xAF,0x64,0x24,0x04,0x04,0x00};
char ming_2[16] = {0x10,0x10,0x10,0x08,0xFC,0x44,0x45,0x46,0x46,0x45,0x44,0x44,0xFC,0x00,0x00,0x00};


void IIC_start()
{
	SDA = 0;	
	SCL = 1;
	SDA = 1;
	_nop_(); //约5微妙
	SDA = 0;
	_nop_();
	SCL = 0;
	
}

void IIC_stop()
{
	SCL = 0;	
	SCL = 1;
	SDA = 0;
	_nop_();
	SDA = 1;
	_nop_();
	SDA = 0;
	
}

char IIC_ACK()
{
	char flag;
	
	SDA = 1;//在脉冲9释放数据线
	_nop_();
	SCL = 1;
	_nop_();
	
	flag = SDA;
	_nop_();
	SCL = 0;
	//_nop_();
	
	return flag;
}

void IIC_sendByte(char sdata) //data直接定义为char的好处是char就是1个byte(8个bit)
{	
	int i;
	
	for(i = 0; i < 8; i++){
		SCL = 0; //拉低,让SDA准备数据
		SDA = sdata & 0x80; //与上“1000 0000”,即只有最高位保留,其他全部清0
		_nop_();//给数据建立一个时间
		SCL = 1; //拉高,形成脉冲开始传输,此时SDA不能变
		_nop_();//发送中
		SCL = 0;//重新拉低
		_nop_();
		sdata = sdata << 1; //发完1个bit左移一位
	}

}

void OLED_writecmd(char cmd)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x00);
	IIC_ACK();
	IIC_sendByte(cmd);
	IIC_ACK();
	IIC_stop();
		
}

void OLED_writedata(char wdata)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x40);
	IIC_ACK();
	IIC_sendByte(wdata);
	IIC_ACK();
	IIC_stop();
	
}

void Oled_Init()
{
	OLED_writecmd(0xAE);//--display off
	OLED_writecmd(0x00);//---set low column address
	OLED_writecmd(0x10);//---set high column address
	OLED_writecmd(0x40);//--set start line address  
	OLED_writecmd(0xB0);//--set page address
	OLED_writecmd(0x81); // contract control
	OLED_writecmd(0xFF);//--128   
	OLED_writecmd(0xA1);//set segment remap 
	OLED_writecmd(0xA6);//--normal / reverse
	OLED_writecmd(0xA8);//--set multiplex ratio(1 to 64)
	OLED_writecmd(0x3F);//--1/32 duty
	OLED_writecmd(0xC8);//Com scan direction
	OLED_writecmd(0xD3);//-set display offset
	OLED_writecmd(0x00);//
	
	OLED_writecmd(0xD5);//set osc division
	OLED_writecmd(0x80);//
	
	OLED_writecmd(0xD8);//set area color mode off
	OLED_writecmd(0x05);//
	
	OLED_writecmd(0xD9);//Set Pre-Charge Period
	OLED_writecmd(0xF1);//
	
	OLED_writecmd(0xDA);//set com pin configuartion
	OLED_writecmd(0x12);//
	
	OLED_writecmd(0xDB);//set Vcomh
	OLED_writecmd(0x30);//
	
	OLED_writecmd(0x8D);//set charge pump enable
	OLED_writecmd(0x14);//
	
	OLED_writecmd(0xAF);//--turn on oled panel		
}

void Oled_Clear()
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 0; j<128; j++){
			OLED_writedata(0x00);//由于地址会自动偏移,所以只要重复写128次全0,就可以清一个PAGE
		}
	}
}

void print_mjm()
{
	int i;
	
	OLED_writecmd(0xB0);//选择从PAGE0开始	
	OLED_writecmd(0x00);//选择列0
	OLED_writecmd(0x10);//选择列0
	for(i = 0; i<16; i++){
		OLED_writedata(ma_1[i]); //绘制字符‘马’的上半部分
	}
	
	OLED_writecmd(0xB1);//选择从PAGE1开始	
	OLED_writecmd(0x00);//选择列0
	OLED_writecmd(0x10);//选择列0
	for(i = 0; i<16; i++){
		OLED_writedata(ma_2[i]); //绘制字符‘马’的下半部分
	}
	
	OLED_writecmd(0xB0);//选择从PAGE0开始	
	OLED_writecmd(0x00);//选择列16
	OLED_writecmd(0x11);//选择列16
	for(i = 0; i<16; i++){
		OLED_writedata(jia_1[i]); //绘制字符‘佳’的上半部分
	}
	
	OLED_writecmd(0xB1);//选择从PAGE1开始	
	OLED_writecmd(0x00);//选择列16
	OLED_writecmd(0x11);//选择列16
	for(i = 0; i<16; i++){
		OLED_writedata(jia_2[i]); //绘制字符‘佳’的下半部分
	}
	
	OLED_writecmd(0xB0);//选择从PAGE0开始	
	OLED_writecmd(0x00);//选择列32
	OLED_writecmd(0x12);//选择列32
	for(i = 0; i<16; i++){
		OLED_writedata(ming_1[i]); //绘制字符‘茗’的上半部分
	}
	
	OLED_writecmd(0xB1);//选择从PAGE1开始	
	OLED_writecmd(0x00);//选择列32
	OLED_writecmd(0x12);//选择列32
	for(i = 0; i<16; i++){
		OLED_writedata(ming_2[i]); //绘制字符‘茗’的下半部分
	}
	
	
}

void main()
{
	
	Oled_Init();
	Oled_Clear();
	
	OLED_writecmd(0x20);//页寻址模式
	OLED_writecmd(0x02);//页寻址模式
	
	print_mjm();

		
	while(1);//不让程序退出


}

显示效果

 PS:其实这部分的代码我实现的有点麻烦,因为OLED的列也是会自动往后的,所以其实PAGE0的马佳茗的三个字符的上半部分既然是连续的,就可以写在一起,只定义一个列的初值不断打印就可以;同理再一起打印位于PAGE1的马佳茗三个字符的下半部分即可,不需要每次针对不同的字符写不同的开始列。

 3. 如果我现在想要选择页寻址模式显示以下图像的话,代码应该如下:

#include "reg52.h"
#include "intrins.h" //这个库加了,delay函数里面的nop()才不会报错
#include <string.h>


sfr AUXR = 0x8E; //配置了这句话,才可以在UART的初始化里写AUXR寄存器,原因见STC89系列的手册
sbit D5 = P3^7;
sbit SCL = P0^1;
sbit SDA = P0^3;

code unsigned char bmpPic[] = { //使用unsigned可以使得原本从负数到正数的范围扩展到两倍的正数,适用于大量无符号数存在时
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xE0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x10,
0x10,0x10,0x10,0x10,0x18,0x08,0x08,0x08,0x08,0x08,0xF8,0x18,0x08,0x08,0x08,0x08,
0x08,0x08,0x08,0x08,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xE0,0xE0,0x00,0x00,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,0x38,0x0F,0x01,0x07,0x1C,0x70,0xC0,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xF8,0x0F,0x01,0x01,0x0F,0x70,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,0x03,0xFE,0x00,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,0x7E,0x03,0x07,0x3C,0xE0,0x80,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xC0,0x70,0x0E,0x03,0x00,0x00,0x07,0x1C,0x70,
0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0xE0,0x1E,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1E,
0x70,0x80,0x00,0x00,0x00,0xF0,0x1F,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x1E,0xF0,
0x80,0x00,0x00,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,0xFC,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x80,0xE0,0x3C,0x07,0x00,0x00,0x00,0x00,0x00,0x07,0x1C,0x70,0x80,0x00,
0x00,0x00,0x00,0x00,0x00,0xE0,0x3E,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x01,0x07,0x0C,0x30,0xC0,0x80,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x30,0x3E,0x03,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x03,0x0C,0x0C,0x03,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x0F,0x78,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,
0x3F,0x60,0xC0,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0xF0,0x1F,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x60,0x3C,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x03,0x0E,
0x38,0xE0,0xE0,0x3C,0x07,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x03,0x06,0x1C,0x70,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,0x01,0x03,0x0E,0x08,0x08,0x08,0x0C,0x06,0x03,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,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,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,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()
{
	SDA = 0;	
	SCL = 1;
	SDA = 1;
	_nop_(); //约5微妙
	SDA = 0;
	_nop_();
	SCL = 0;
	
}

void IIC_stop()
{
	SCL = 0;	
	SCL = 1;
	SDA = 0;
	_nop_();
	SDA = 1;
	_nop_();
	SDA = 0;
	
}

char IIC_ACK()
{
	char flag;
	
	SDA = 1;//在脉冲9释放数据线
	_nop_();
	SCL = 1;
	_nop_();
	
	flag = SDA;
	_nop_();
	SCL = 0;
	//_nop_();
	
	return flag;
}

void IIC_sendByte(char sdata) //data直接定义为char的好处是char就是1个byte(8个bit)
{	
	int i;
	
	for(i = 0; i < 8; i++){
		SCL = 0; //拉低,让SDA准备数据
		SDA = sdata & 0x80; //与上“1000 0000”,即只有最高位保留,其他全部清0
		_nop_();//给数据建立一个时间
		SCL = 1; //拉高,形成脉冲开始传输,此时SDA不能变
		_nop_();//发送中
		SCL = 0;//重新拉低
		_nop_();
		sdata = sdata << 1; //发完1个bit左移一位
	}

}

void OLED_writecmd(char cmd)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x00);
	IIC_ACK();
	IIC_sendByte(cmd);
	IIC_ACK();
	IIC_stop();
		
}

void OLED_writedata(char wdata)
{
	IIC_start();
	IIC_sendByte(0x78);
	IIC_ACK();
	IIC_sendByte(0x40);
	IIC_ACK();
	IIC_sendByte(wdata);
	IIC_ACK();
	IIC_stop();
	
}

void Oled_Init()
{
	OLED_writecmd(0xAE);//--display off
	OLED_writecmd(0x00);//---set low column address
	OLED_writecmd(0x10);//---set high column address
	OLED_writecmd(0x40);//--set start line address  
	OLED_writecmd(0xB0);//--set page address
	OLED_writecmd(0x81); // contract control
	OLED_writecmd(0xFF);//--128   
	OLED_writecmd(0xA1);//set segment remap 
	OLED_writecmd(0xA6);//--normal / reverse
	OLED_writecmd(0xA8);//--set multiplex ratio(1 to 64)
	OLED_writecmd(0x3F);//--1/32 duty
	OLED_writecmd(0xC8);//Com scan direction
	OLED_writecmd(0xD3);//-set display offset
	OLED_writecmd(0x00);//
	
	OLED_writecmd(0xD5);//set osc division
	OLED_writecmd(0x80);//
	
	OLED_writecmd(0xD8);//set area color mode off
	OLED_writecmd(0x05);//
	
	OLED_writecmd(0xD9);//Set Pre-Charge Period
	OLED_writecmd(0xF1);//
	
	OLED_writecmd(0xDA);//set com pin configuartion
	OLED_writecmd(0x12);//
	
	OLED_writecmd(0xDB);//set Vcomh
	OLED_writecmd(0x30);//
	
	OLED_writecmd(0x8D);//set charge pump enable
	OLED_writecmd(0x14);//
	
	OLED_writecmd(0xAF);//--turn on oled panel		
}

void Oled_Clear()
{
	int i;
	int j;
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一页
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 0; j<128; j++){
			OLED_writedata(0x00);//由于地址会自动偏移,所以只要重复写128次全0,就可以清一个PAGE
		}
	}
}

void print_pic(unsigned char *pic)
{
	unsigned int i;//使用unsigned可以使得原本从负数到正数的范围扩展到两倍的正数,适用于大量无符号数存在时
	unsigned int j;//使用unsigned可以使得原本从负数到正数的范围扩展到两倍的正数,适用于大量无符号数存在时
	
	for(i = 0; i<8; i++){
		OLED_writecmd(0xB0 + i);//依次选择每一page
		OLED_writecmd(0x00);//选择0列
	  OLED_writecmd(0x10);//选择0列
		for(j = 128*i; j<(128*(i+1)); j++){ 
			OLED_writedata(pic[j]);
		}
	}
}
	
	

void main()
{
	
	Oled_Init();
	Oled_Clear();
	
	OLED_writecmd(0x20);//页寻址模式
	OLED_writecmd(0x02);//页寻址模式
	
	print_pic(bmpPic);

		
	while(1);//不让程序退出


}

 显示效果

 

  • 5
    点赞
  • 31
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值