IIC 协议概述
IIC全程Inter-Integrated Circuit(集成电路总线),是一种两线式串行总线,用于连接微控制器及其外围设备,IIC属于半双工同步通信方式(同一时间,只能发送或接收)。
特点:
简单,有效,多主控(其中任何能够进行发送或接收的设备都可以成为主总线,一个主控能够控制信号的传输和时钟频率。当然,在任何时间点上只能有一个主控)
构成:
IIC串行总线一般有两条信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号是由主控器件产生。所有接到IIC总线上的串行数据SDA都接到总线的SDA上,各设备的时钟线SCL接到总线SCL上,对于并联在一条总线上的每个IC都有唯一的地址。
![](https://img-blog.csdnimg.cn/577ef893c76743be9526eb8be4cb0bef.png)
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);//不让程序退出
}
显示效果