IIC协议与OLED

1.认识OLED

1、OLED概述:

  1. OLED(Organic Light-Emitting Diode,有机发光二极管)是一种显示技术,利用有机材料的发光特性来产生光。
  2. OLED显示器由一系列有机材料层组成,当电流通过时,这些材料会发光,每一个像素都是一个独立的发光元素,可以精准控制亮度和颜色。
  3. OLED相较于LCD有很多优势:①OLED显示器由于不需要背光源,所以可以实现自发光,从而可以实现非常高的对比度。②OLED显示器具有更广的可视角度、更快的相应时间和更薄的设计。
  4. OLED技术被应用于各种电子设备,比如智能手机、电视机、电子手表、虚拟现实设备等。它提供了更鲜亮、细腻和动态的图像。
  5. OLED是我们学习的第一个IIC协议外设,它仍然遵循IIC协议的起始、终止、应答、数据传输的标准时序。但是对OLED这个特定的设备,需要我们去编写和设备相关的代码。手册ssd1306.pdf是全英文、除了我们提到的IIC协议以外还可以支持多种协议(SPI、古老的多线协议等),我们之前的IIC时序图来源于叁议电子为我们从该手册中提取出来的有关IIC协议的那部分内容。

IIC协议与OLED_第1张图片

 

2、OLED的特点:

  1. 分辨率:128 segments × 64 commons 的点阵。SSD1306是一款具有控制器的单芯片CMOS OLED/PLED驱动,用于 有机/聚合物 发光二极管点阵图形显示系统。它由128个段和64个公共引脚组成。该集成电路是为共阴极型OLED面板设计的。
  2. SSD1306嵌入了对比度控制、显示RAM和振荡器,减少了外部元件的数量和功耗。它有256级亮度控制。数据/命令通过硬件可选的6800/8000系列兼容并行接口从通用MCU发送;I2C接口或串行外设接口。它适用于许多小型便携式应用,如手机子显示器、MP3播放器、计算器等。

2.开发逻辑1:OLED写模式

1、和单片机的接线方法:

IIC协议与OLED_第2张图片

 

  1. 电源线
    1. GND——GND
    2. VCC——5V
  2. IIC总线
    1. SCL——P0.1
    2. SDA——P0.3

2、写操作时序分析:如图所示是IIC协议数据总线上一帧数据的数据格式。

IIC协议与OLED_第3张图片

 

  1. 发送起始信号:调用IIC协议一节中封装的函数:IIC_Start();
  2. 确认从机地址,准备写入:发送的数据格式为:0111 10 [SA0] [R/W#]
    1. SA0 是从机地址位(slaver address),比如说我们的单片机接着两个显示屏,那么就可以通过SA0这个地址位来区分这两个显示屏
    2. R/W# 是读写选择位,该位为高电平代表读模式(类似于LCD的R/W位,在读取忙信号的时候始终将R/W“置1”),该位为低电平代表写模式(类似于LCD的R/W位,不论是写地址还是写数据,始终将R/W“置0”)。
    • 手册上告知我们SA0应该设置成0,另外我们告诉OLED准备开始写入,所以根据8421码,二进制的0111 1000就是十六进制的0x78。
  3. 应答信号ACK读取:调用IIC协议一节中封装的函数:IIC_ACK();
  4. 发送控制字节:[Co] [D/C#] 00 0000
    1. Co 是继续位(continuation),如果该位“置0”,那么接下来将要传输的信息会包含数据字节
    2. D/C# 是数据指令选择位(data/command selection),该位为高电平代表写入的是数据(类似于LCD的RS位,在写数据的时候将RS“置1”),该位为低电平代表写入的是指令(类似于LCD的RS位,在写位置指令的时候将RS“置0”)。注意当该位“置1”时,写入的数据会被存放在GDDRAM(图形数据存储器)中,GDDRAM的列地址指针在每次数据写入后自动加1。
    • 如果我们打算写入指令,那么根据8421码,二进制的0000 0000就是十六进制的0x00;
    • 如果我们打算写入数据,那么根据8421码,二进制的0100 0000就是十六进制的0x40;
  5. 应答信号ACK读取:调用IIC协议一节中封装的函数:IIC_ACK();
  6. 发送指令或者数据
  7. 应答信号ACK读取:调用IIC协议一节中封装的函数:IIC_ACK();
  8. 发送停止信号:调用IIC协议一节中封装的函数:IIC_Stop();

3、写指令函数封装:

void Oled_Write_Cmd(char dataCmd)
{
    IIC_Start();            //1.发送起始信号
    IIC_Send_Byte(0x78);    //2.确认从机地址,准备写入
    IIC_ACK();              //3.应答信号ACK读取
    IIC_Send_Byte(0x00);    //4.发送控制字节,确认发送指令
    IIC_ACK();              //5.应答信号ACK读取
    IIC_Send_Byte(dataCmd); //6.发送指令
    IIC_ACK();              //7.应答信号ACK读取
    IIC_Stop();             //8.发送停止信号
}

4、写数据函数封装:

void Oled_Write_Data(char dataData)
{
    IIC_Start();            //1.发送起始信号
    IIC_Send_Byte(0x78);    //2.确认从机地址,准备写入
    IIC_ACK();              //3.应答信号ACK读取
    IIC_Send_Byte(0x40);    //4.发送控制字节,确认发送数据
    IIC_ACK();              //5.应答信号ACK读取
    IIC_Send_Byte(dataData);//6.发送数据
    IIC_ACK();              //7.应答信号ACK读取
    IIC_Stop();             //8.发送停止信号
}

3.开发逻辑2:OLED寻址模式

1、OLED点阵的管理:搞清在哪显示

  • OLED的分辨率是128×64,那么用显示器的术语来说就是水平方向上有128段(segments),竖直方向上有64个公共引脚(commons)。这与电脑的分辨率类似,比如电脑的分辨率是1920×1080,那么水平方向上(宽度)就分布着1920个像素点,竖直方向上(高度)就分布着1080个像素点。
  • 在OLED竖直方向上将64个点阵分为8组page,从page0到page7,每页由8位像素点组成。这就像我们控制单片机开发板上面的LED需要配置寄存器一样,控制OLED上像素的亮灭也需要配置PAGE。但是要注意的是page是个常用的用于描述显示器分区的术语,并不是特殊的寄存器。IIC协议与OLED_第4张图片
  • 备注:GDDRAMGraphic Display Data RAM,图形数据显示内存)是一个位映射静态内存,用与保存要显示的位模式,内存的大小就是128×64 bit,同时内存被细分成如上所述的8个page。
  • 如下是GDDRAM的局部放大图片,以PAGE2为例:IIC协议与OLED_第5张图片
  • 当一个字节数据被写入进GDDRAM时,当前列中所有处于同一page的行图像数据都会被填充。OLED的每一页可以管理8行,我们以PAGE2为例,如图,在水平方向上就有128列,每列都有8个像素点,最上方的像素点是bit0,最下方的像素点是bit7。假设我们写入的数据是0x08,那么其二进制就是0000 1000,对应的就会点亮位于SEG0且处于PAGE2上bit4这一个像素点,同时熄灭其他像素点。
  • 这里可以画个大表格,可以帮助我们分析要显示的像素点:
    OLED:128×64 0列 1列 2列 3列 .............. 124列 125列 126列 127列
    PAGE0 bit0 0行
    bit1 1行
    bit2 2行
    bit3 3行
    bit4 4行
    bit5 5行
    bit6 6行
    bit7 7行
    PAGE1 8到15行
    PAGE2 16到23行
    PAGE3 24到31行
    PAGE4 32到39行
    PAGE5 40到47行
    PAGE6 48到55行
    PAGE7 56到63行

 

 

2、三种内存寻址模式:搞清显示地址指针移动方式

  • 目的:不论OLED上显示的是什么,我们首要的工作是做到如何点亮OLED上的一个点,步骤是先选一个page,然后向其发送一个字节数据,点亮page的对应位置。而在此之前,我们需要先了解地址指针在内存GDDRAM中的移动方式。OLED对page有三种寻址办法,如下:
  1. 页寻址模式:图中的箭头是页寻址模式下地址指针的移动方向,以在PAGE0上写入数据0x80为例,如果写入1次,那么仅能点亮COL0上处于PAGE0的bit4这一个像素;在这种寻址模式下,GDDRAM的列地址指针在每次数据写入后会自动加1,如果写入128次,那么就能显示水平的一条线,并且PAGE0中的COL127写完后又会回到PAGE0的COL0。IIC协议与OLED_第6张图片
  2. 水平寻址模式:图中的箭头是水平寻址模式下地址指针的移动方向,在这种寻址模式下,GDDRAM的列地址指针在每次数据写入后会自动加1,但是在当前PAGE的最后一列写完之后,也就是COL127写完之后,地址指针会移动到下一页的首列,比如PAGE0的COL127写完之后,地址指针会移动到PAGE1的COL0。IIC协议与OLED_第7张图片
  3. 垂直寻址模式:图中的箭头是垂直寻址模式下地址指针的移动方向,这种模式用得很少。IIC协议与OLED_第8张图片

 

 

 

3、发送指令确认寻址模式:搞清如何显示,查阅地址设置控制表。

  • 确认寻址模式需要发送长度为两个字节的指令
  1. 指令0x20用于设置存储器。
  2. 指令00b(0x00)用于设置水平寻址模式、指令01b(0x01)用于设置垂直寻址模式、指令10b(0x02)用于设置页寻址模式(默认)。

IIC协议与OLED_第9张图片

 

4、发送指令选择特定页:搞清如何显示,查阅地址设置控制表。

  1. 指令的格式为:1011 0 [X2] [X1] [X0] ,写入的指令是0xB0~0xB7。
  • 因为指令的低三位是可编程,的并且23有8种选择,所以通过发送该指令可以指定GDDRAM中的PAGE的开始地址,比如说1011 0000代表的就是PAGE0
  • 注意:这种指令只适合用于页寻址模式。

IIC协议与OLED_第10张图片

 

5、发送指令选择特定段:搞清如何显示,查阅地址设置控制表。

  • 目的:当我们在某一个PAGE中写入了几个数据之后,重新再往新的PAGE里面写入数据,此时列地址仍然往后面偏移1,所以不是从新的PAGE的开始位置显示。因此如果想从新的PAGE的特定列(比如COL0)开始输入数据,那么就需要发送指令来选择特定列。
  • 查表可见,确认特定列需要发送长度为两个字节的指令
  1. 确认低地址:0000 [X3] [X2] [X1] [X0],写入的指令是0x00~0xF。
  2. 确认高地址:0001 [X3] [X2] [X1] [X0],写入的指令是0x10~0x7。
  • 128列地址需要通过7位二进制数(27=128)来表示。由于只有128列,也就是说只需要7位二进制数就能表示全部的列,所以高地址的[X3]不能被使用,需要设置为0。
  • 这种指令只适用于页寻址模式。

IIC协议与OLED_第11张图片

 

4.开发逻辑3:OLED初始化

1、OLED初始化函数封装:叁议电子为我们整理了如下初始化过程

(01)display off (0xae)

(02)set low column address (0x00)

(03)set high column address (0x10)

(04)set start line address (0x40)

(05)set page address (0xb0)

(06)contract control (0x81)

(07)send 0xff(多字节指令)

(08)set segment remap (0xa1)

(09)set normal/reverse (0xa6)

(10)set multiplex ratio (1 to 64) (0xa8)

(11)set duty 1/32 (0x3f)

(12)com scan direction (0xc8)

(13)set display offset (0xd3)

(14)send 0x00 

(15)set osc division(0xd5)

(16)send 0x80

(17)set area color mode off (0xd8)

(18)send 0x05

(19)set pre-charge period (0xd9)

(20)send 0xf1

(21)set com pin configuration (0xda)

(22)send 0x12

(23)set Vcomh(0xdb)

(24)send0x30

(25)set charge pump enable(0x8d)

(26)send 0x14

(27)turn on oled panel(0xaf)

void Oled_Init()
{
    Oled_Write_Cmd(0xAE);   //(01)display off (0xae)
    Oled_Write_Cmd(0x00);   //(02)set low column address (0x00)
    Oled_Write_Cmd(0x10);   //(03)set high column address (0x10)
    Oled_Write_Cmd(0x40);   //(04)set start line address (0x40)
    Oled_Write_Cmd(0xB0);   //(05)set page address (0xb0)
    Oled_Write_Cmd(0x81);   //(06)contract control (0x81)
    Oled_Write_Cmd(0xFF);   //(07)send 0xff(多字节指令)
    Oled_Write_Cmd(0xA1);   //(08)set segment remap (0xa1)
    Oled_Write_Cmd(0xA6);   //(09)set normal/reverse (0xa6)
    Oled_Write_Cmd(0xA8);   //(10)set multiplex ratio (1 to 64) (0xa8)
    Oled_Write_Cmd(0x3F);   //(11)set duty 1/32 (0x3f)
    Oled_Write_Cmd(0xC8);   //(12)com scan direction (0xc8)
    Oled_Write_Cmd(0xD3);   //(13)set display offset (0xd3)
    Oled_Write_Cmd(0x00);   //(14)send 0x00 
    Oled_Write_Cmd(0xD5);   //(15)set osc division(0xd5)
    Oled_Write_Cmd(0x80);   //(16)send 0x80
    Oled_Write_Cmd(0xD8);   //(17)set area color mode off (0xd8)
    Oled_Write_Cmd(0x05);   //(18)send 0x05
    Oled_Write_Cmd(0xD9);   //(19)set pre-charge period (0xd9)
    Oled_Write_Cmd(0xF1);   //(20)send 0xf1
    Oled_Write_Cmd(0xDA);   //(21)set com pin configuration (0xda)
    Oled_Write_Cmd(0x12);   //(22)send 0x12
    Oled_Write_Cmd(0xDB);   //(23)set Vcomh(0xdb)
    Oled_Write_Cmd(0x30);   //(24)send0x30
    Oled_Write_Cmd(0x8D);   //(25)set charge pump enable(0x8d)
    Oled_Write_Cmd(0x14);   //(26)send 0x14
    Oled_Write_Cmd(0xAF);   //(27)turn on oled panel(0xaf)
}

5.开发逻辑4:OLED清屏

1、OLED添加清屏函数:

  • 原因:为了在OLED屏幕上显示别的图案,我们去修改程序然后重新烧录,这时重新给OLED上电会发现:如果没有对OLED的清屏操作,那么上一次的显示结果会再次显示在屏幕上。这是因为:我们给OLED写数据的时候,实际上是给OLED的GDDRAM这个内存中写数据,OLED的这个内存中的数据实际上断电后是会保留的。
  • 代码:在我们实现了对OLED的每一个PAGE、每一个列的访问之后,接下来就可以通过循环嵌套熄灭128×64中所有的像素点,来实现我们的清屏函数。
    void Oled_Clear()
    {
        int i;
        int j;
        for(i=0; i<8; i++){
            Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
            // 指定SEG0
            Oled_Write_Cmd(0x00);
            Oled_Write_Cmd(0x10);
            // 0到127列,每次写入0,每次写入数据列地址自动偏移
            for(j=0; j<128; j++){
                Oled_Write_Data(0);
            }
        }
    }

6.OLED显示简单的点和线

1、OLED雪花BUG:

 

  1. 现象:给OLED上电后直接显示雪花,或者有时候一行横线能够显示但是其他地方出现雪花。
  2. 原因
    1. 原因1:上一次烧录程序后出现了雪花,这一次烧录程序时忘记调用清屏函数进行屏幕清除。
    2. 原因2:在清屏函数中,如果我们为了省空间去将循环变量 i 和 j 的类型从int改变为char,那么也会导致雪花的出现。这是因为字符型变量的取值范围是﹣128 ~ +127,那么无疑的是在清屏函数的内层循环中,循环变量 j 会发生一次越界,j 最终的值会在+128, 导致出问题。
    3. 原因2:我们一开始在写IIC协议的起始和终止信号函数时,起始信号函数一开始没有将SCL“置0”,终止信号函数的结尾没有将SCL“置1”。——>  回顾我们本节封装的写指令和写数据函数,在写入的一帧数据中都要在数据发送前发送起始信号,在数据发送后发送终止信号。——>  那么我们在第一条指令或数据发送完毕后,IIC上的SCL总线的电平仍然是1。——>  同时我们知道“在SCL为高电平时出现了SDA上数据的翻转,那么IIC接收器会误以为这是起始信号或终止信号”,这就出现了矛盾。——>  导致OLED无法正确识别被写入的内容,最终导致OLED屏幕上的雪花出现。
  3. 解决方法1:清屏函数中,在定义循环变量 i 和 j 的时候,不要用char来定义,用int 或者 unsigned char
  4. 解决方法2:修改IIC协议的起始信号
    void IIC_Start()
    {
        scl = 0;    // 防止OLED雪花
        // o点:
        scl = 1;    // SCL置高电平
        sda = 0;    // SDA置低电平
        // a点:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平转折为高电平,时间未知
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4.7微秒
        // b—>c:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
        // c—>d:
        _nop_();    // SCL延时4微秒
                    // SDA维持低电平
        // d点:
        scl = 0;    // SCL从高电平转折为低电平,时间未知
                    // SDA维持低电平
    }

2、OLED显示一个点及一行水平线:

 

  • 代码:其中API 1~API 8 是通用代码7.OLED显示一个字符
    #include "reg52.h"
    #include "intrins.h"
    
    sbit scl = P0^1;
    sbit sda = P0^3;
    
    /* API1. 启动I2C */
    void IIC_Start();
    /* API2. 终止I2C */
    void IIC_Stop();
    /* API3. 获取IIC的应答位 */
    char IIC_ACK();
    /* API4. I2C发送1字节数据 */
    void IIC_Send_Byte(char cdata);
    /* API5. OLED写指令(1Byte) */
    void Oled_Write_Cmd(char dataCmd);
    /* API6. OLED写数据(1Byte) */
    void Oled_Write_Data(char dataData);
    /* API7. OLED初始化 */ 
    void Oled_Init();
    /* API8. OLED清屏 */
    void Oled_Clear();
    
    void main(void)
    {
        Oled_Init();
        // 设置页寻址模式
        Oled_Write_Cmd(0x20);
        Oled_Write_Cmd(0x02);
        // 清屏
        Oled_Clear();
    
        // 指定PAGE0
        Oled_Write_Cmd(0xB0);
        // 指定SEG0
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        // 写数据,显示一个点
        Oled_Write_Data(0x08);
    
        // 指定PAGE1
        Oled_Write_Cmd(0xB1);
        // 指定SEG0
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        // 写数据,显示一条线
        Oled_Write_Data(0x08);
        Oled_Write_Data(0x08);
        Oled_Write_Data(0x08);
        Oled_Write_Data(0x08);
        Oled_Write_Data(0x08);
    
        while(1);   //防止程序退出
    }
    
    void IIC_Start()
    {
        scl = 0;    // 防止OLED雪花
        // o点:
        scl = 1;    // SCL置高电平
        sda = 0;    // SDA置低电平
        // a点:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平转折为高电平,时间未知
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4.7微秒
        // b—>c:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
        // c—>d:
        _nop_();    // SCL延时4微秒
                    // SDA维持低电平
        // d点:
        scl = 0;    // SCL从高电平转折为低电平,时间未知
                    // SDA维持低电平
    }
    
    void IIC_Stop()
    {
        // o点:
        scl = 0;    // SCL置低电平
        sda = 0;    // SDA置低电平
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,时间未知
                    // SDA维持低电平
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4微秒
        // b—>c:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平折为高电平,时间未知
        // c—>d:
                    // SCL维持低高电平
        _nop_();    // SDA延时4.7微秒
        // d点:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
    }
    
    char IIC_ACK()
    {
        char flag;
        // o点:
                    // SCL置低电平
        sda = 1;    // SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线
        // o—>a:
        _nop_();    // SCL延时
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,代表释放SCL数据线
        // a—>b:
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        flag = sda; // SDA被读
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        // b点:
        scl = 0;    // SCL从高电平转折为低电平
        // b—>c:
        _nop_();    // SCL延时
        return flag;
    }
    
    void IIC_Send_Byte(char cdata)
    {
        int i;
        for(i=0; i<8; i++){
            // o点:
            scl = 0;            // SCL置低电平:允许数据翻转
            sda = cdata & 0x80; // SDA发送1bit数据
            // o—>a:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // a点:
            scl = 1;            // SCL从低电平转折为高电平
            // a—>b:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // b点:
            scl = 0;            // SCL从高电平转折为低电平:为下一次数据翻转做准备
            // b—>c:
            _nop_();            // SCL延时5微秒
            cdata = cdata << 1;
        }
    }
    
    void Oled_Write_Cmd(char dataCmd)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x00);    //发送控制字节,确认发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataCmd); //发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Write_Data(char dataData)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x40);    //发送控制字节,确认发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataData);//发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Init()
    {
        Oled_Write_Cmd(0xAE);   //(01)display off (0xae)
        Oled_Write_Cmd(0x00);   //(02)set low column address (0x00)
        Oled_Write_Cmd(0x10);   //(03)set high column address (0x10)
        Oled_Write_Cmd(0x40);   //(04)set start line address (0x40)
        Oled_Write_Cmd(0xB0);   //(05)set page address (0xb0)
        Oled_Write_Cmd(0x81);   //(06)contract control (0x81)
        Oled_Write_Cmd(0xFF);   //(07)send 0xff(多字节指令)
        Oled_Write_Cmd(0xA1);   //(08)set segment remap (0xa1)
        Oled_Write_Cmd(0xA6);   //(09)set normal/reverse (0xa6)
        Oled_Write_Cmd(0xA8);   //(10)set multiplex ratio (1 to 64) (0xa8)
        Oled_Write_Cmd(0x3F);   //(11)set duty 1/32 (0x3f)
        Oled_Write_Cmd(0xC8);   //(12)com scan direction (0xc8)
        Oled_Write_Cmd(0xD3);   //(13)set display offset (0xd3)
        Oled_Write_Cmd(0x00);   //(14)send 0x00 
        Oled_Write_Cmd(0xD5);   //(15)set osc division(0xd5)
        Oled_Write_Cmd(0x80);   //(16)send 0x80
        Oled_Write_Cmd(0xD8);   //(17)set area color mode off (0xd8)
        Oled_Write_Cmd(0x05);   //(18)send 0x05
        Oled_Write_Cmd(0xD9);   //(19)set pre-charge period (0xd9)
        Oled_Write_Cmd(0xF1);   //(20)send 0xf1
        Oled_Write_Cmd(0xDA);   //(21)set com pin configuration (0xda)
        Oled_Write_Cmd(0x12);   //(22)send 0x12
        Oled_Write_Cmd(0xDB);   //(23)set Vcomh(0xdb)
        Oled_Write_Cmd(0x30);   //(24)send0x30
        Oled_Write_Cmd(0x8D);   //(25)set charge pump enable(0x8d)
        Oled_Write_Cmd(0x14);   //(26)send 0x14
        Oled_Write_Cmd(0xAF);   //(27)turn on oled panel(0xaf)
    }
    
    void Oled_Clear()
    {
        int i;
        int j;
        for(i=0; i<8; i++){
            Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
            // 指定SEG0
            Oled_Write_Cmd(0x00);
            Oled_Write_Cmd(0x10);
            // 0到127列,每次写入0,每次写入数据列地址自动偏移
            for(j=0; j<128; j++){
                Oled_Write_Data(0);
            }
        }
    }

7.OLED显示一个字符

1、字模软件的使用:

IIC协议与OLED_第12张图片IIC协议与OLED_第13张图片

 IIC协议与OLED_第14张图片

 

 

  • 目的:在点阵屏幕上显示字符比较复杂,我们需要借助字模软件来帮助我们实现点阵的规划。
  • 字符取模步骤:
  • 拷贝结果:

    /*--  文字:  A  --*/

    /*--  宋体12;  此字体下对应的点阵为:宽x高=8x16   --*/

    0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00,0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20,

  • 对于我们输入的文字A(字体样式为:宋体12),生成的点阵宽为8、高为16。这就意味着需要用到2个PAGE,那么我们就要将这个数据拆分成两个字符数组:

  • 数组1:0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00
  • 数组2:0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20

2、字符'A'的点阵规划:

PAGE0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 1 0 0 0 0
0 0 0 1 1 0 0 0
0 0 1 0 1 0 0 0
0 0 1 0 1 0 0 0
PAGE1 0 0 1 0 0 1 0 0
0 0 1 1 1 1 0 0
0 1 0 0 0 1 0 0
0 1 0 0 0 0 1 0
0 1 0 0 0 0 1 0
1 1 1 0 0 1 1 1
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

3、OLED显示一个字符‘A’: 

 

代码:其中API 1~API 8 是通用代码

#include "reg52.h"
#include "intrins.h"

sbit scl = P0^1;
sbit sda = P0^3;

/* API1. 启动I2C */
void IIC_Start();
/* API2. 终止I2C */
void IIC_Stop();
/* API3. 获取IIC的应答位 */
char IIC_ACK();
/* API4. I2C发送1字节数据 */
void IIC_Send_Byte(char cdata);
/* API5. OLED写指令(1Byte) */
void Oled_Write_Cmd(char dataCmd);
/* API6. OLED写数据(1Byte) */
void Oled_Write_Data(char dataData);
/* API7. OLED初始化 */ 
void Oled_Init();
/* API8. OLED清屏 */
void Oled_Clear();
/* API9. OLED显示字符'A' */
void Oled_Show_Char();

/*--  文字:  A  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=8x16,用到两个PAGE,所以拆分成两个字符数组   --*/
char A1[8] = {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00};
char A2[8] = {0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};

void main(void)
{
    int i;
    Oled_Init();
    // 设置页寻址模式
    Oled_Write_Cmd(0x20);
    Oled_Write_Cmd(0x02);
    // 清屏
    Oled_Clear();
    
    Oled_Show_Char();

    while(1);   //防止程序退出
}

void IIC_Start()
{
    scl = 0;    // 防止OLED雪花
    // o点:
    scl = 1;    // SCL置高电平
    sda = 0;    // SDA置低电平
    // a点:
                // SCL维持高电平
    sda = 1;    // SDA从低电平转折为高电平,时间未知
    // a—>b:
                // SCL维持高电平
    _nop_();    // SDA延时4.7微秒
    // b—>c:
                // SCL维持高电平
    sda = 0;    // SDA从高电平转折为低电平,时间未知
    // c—>d:
    _nop_();    // SCL延时4微秒
                // SDA维持低电平
    // d点:
    scl = 0;    // SCL从高电平转折为低电平,时间未知
                // SDA维持低电平
}

void IIC_Stop()
{
    // o点:
    scl = 0;    // SCL置低电平
    sda = 0;    // SDA置低电平
    // a点:
    scl = 1;    // SCL从低电平转折为高电平,时间未知
                // SDA维持低电平
    // a—>b:
                // SCL维持高电平
    _nop_();    // SDA延时4微秒
    // b—>c:
                // SCL维持高电平
    sda = 1;    // SDA从低电平折为高电平,时间未知
    // c—>d:
                // SCL维持低高电平
    _nop_();    // SDA延时4.7微秒
    // d点:
                // SCL维持高电平
    sda = 0;    // SDA从高电平转折为低电平,时间未知
}

char IIC_ACK()
{
    char flag;
    // o点:
                // SCL置低电平
    sda = 1;    // SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线
    // o—>a:
    _nop_();    // SCL延时
    // a点:
    scl = 1;    // SCL从低电平转折为高电平,代表释放SCL数据线
    // a—>b:
    _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
    flag = sda; // SDA被读
    _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
    // b点:
    scl = 0;    // SCL从高电平转折为低电平
    // b—>c:
    _nop_();    // SCL延时
    return flag;
}

void IIC_Send_Byte(char cdata)
{
    int i;
    for(i=0; i<8; i++){
        // o点:
        scl = 0;            // SCL置低电平:允许数据翻转
        sda = cdata & 0x80; // SDA发送1bit数据
        // o—>a:
        _nop_();            // SCL延时5微秒
                            // SDA正在发送数据
        // a点:
        scl = 1;            // SCL从低电平转折为高电平
        // a—>b:
        _nop_();            // SCL延时5微秒
                            // SDA正在发送数据
        // b点:
        scl = 0;            // SCL从高电平转折为低电平:为下一次数据翻转做准备
        // b—>c:
        _nop_();            // SCL延时5微秒
        cdata = cdata << 1;
    }
}

void Oled_Write_Cmd(char dataCmd)
{
    IIC_Start();            //发送起始信号
    IIC_Send_Byte(0x78);    //确认从机地址,准备写入
    IIC_ACK();              //应答信号ACK读取
    IIC_Send_Byte(0x00);    //发送控制字节,确认发送指令
    IIC_ACK();              //应答信号ACK读取
    IIC_Send_Byte(dataCmd); //发送指令
    IIC_ACK();              //应答信号ACK读取
    IIC_Stop();             //发送停止信号
}

void Oled_Write_Data(char dataData)
{
    IIC_Start();            //发送起始信号
    IIC_Send_Byte(0x78);    //确认从机地址,准备写入
    IIC_ACK();              //应答信号ACK读取
    IIC_Send_Byte(0x40);    //发送控制字节,确认发送数据
    IIC_ACK();              //应答信号ACK读取
    IIC_Send_Byte(dataData);//发送数据
    IIC_ACK();              //应答信号ACK读取
    IIC_Stop();             //发送停止信号
}

void Oled_Init()
{
    Oled_Write_Cmd(0xAE);   //(01)display off (0xae)
    Oled_Write_Cmd(0x00);   //(02)set low column address (0x00)
    Oled_Write_Cmd(0x10);   //(03)set high column address (0x10)
    Oled_Write_Cmd(0x40);   //(04)set start line address (0x40)
    Oled_Write_Cmd(0xB0);   //(05)set page address (0xb0)
    Oled_Write_Cmd(0x81);   //(06)contract control (0x81)
    Oled_Write_Cmd(0xFF);   //(07)send 0xff(多字节指令)
    Oled_Write_Cmd(0xA1);   //(08)set segment remap (0xa1)
    Oled_Write_Cmd(0xA6);   //(09)set normal/reverse (0xa6)
    Oled_Write_Cmd(0xA8);   //(10)set multiplex ratio (1 to 64) (0xa8)
    Oled_Write_Cmd(0x3F);   //(11)set duty 1/32 (0x3f)
    Oled_Write_Cmd(0xC8);   //(12)com scan direction (0xc8)
    Oled_Write_Cmd(0xD3);   //(13)set display offset (0xd3)
    Oled_Write_Cmd(0x00);   //(14)send 0x00 
    Oled_Write_Cmd(0xD5);   //(15)set osc division(0xd5)
    Oled_Write_Cmd(0x80);   //(16)send 0x80
    Oled_Write_Cmd(0xD8);   //(17)set area color mode off (0xd8)
    Oled_Write_Cmd(0x05);   //(18)send 0x05
    Oled_Write_Cmd(0xD9);   //(19)set pre-charge period (0xd9)
    Oled_Write_Cmd(0xF1);   //(20)send 0xf1
    Oled_Write_Cmd(0xDA);   //(21)set com pin configuration (0xda)
    Oled_Write_Cmd(0x12);   //(22)send 0x12
    Oled_Write_Cmd(0xDB);   //(23)set Vcomh(0xdb)
    Oled_Write_Cmd(0x30);   //(24)send0x30
    Oled_Write_Cmd(0x8D);   //(25)set charge pump enable(0x8d)
    Oled_Write_Cmd(0x14);   //(26)send 0x14
    Oled_Write_Cmd(0xAF);   //(27)turn on oled panel(0xaf)
}

void Oled_Clear()
{
    int i;
    int j;
    for(i=0; i<8; i++){
        Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
        // 指定SEG0
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        // 0到127列,每次写入0,每次写入数据列地址自动偏移
        for(j=0; j<128; j++){
            Oled_Write_Data(0);
        }
    }
}

void Oled_Show_Char()
{
    /* 显示字符'A' */
    // 指定PAGE0
    Oled_Write_Cmd(0xB0);
    // 指定SEG0
    Oled_Write_Cmd(0x00);
    Oled_Write_Cmd(0x10);
    // 写数据,显示字符'A'的上半部分
    for(i=0; i<8; i++){
        Oled_Write_Data(A1[i]);
    }
    // 指定PAGE1
    Oled_Write_Cmd(0xB1);
    // 指定SEG0
    Oled_Write_Cmd(0x00);
    Oled_Write_Cmd(0x10);
    // 写数据,显示字符'A'的下半部分
    for(i=0; i<8; i++){
        Oled_Write_Data(A2[i]);
    }
}

8.OLED显示一串汉字

1、汉字字模提取:

  • 一个汉字的宽度是16,高度是16,所以也需要用到2个PAGE,那么我们就要将这个数据拆分成两个字符数组。

2、OLED显示“嵌入式软件”:

  1. 思路:先在PAGE 0中统一显示一串汉字的上半部分,再在PAGE 1中统一显示一串汉字的下半部分。
  2. 代码:其中API 1~API 8 是通用代码
    #include "reg52.h"
    #include "intrins.h"
    
    sbit scl = P0^1;
    sbit sda = P0^3;
    
    /* API1. 启动I2C */
    void IIC_Start();
    /* API2. 终止I2C */
    void IIC_Stop();
    /* API3. 获取IIC的应答位 */
    char IIC_ACK();
    /* API4. I2C发送1字节数据 */
    void IIC_Send_Byte(char cdata);
    /* API5. OLED写指令(1Byte) */
    void Oled_Write_Cmd(char dataCmd);
    /* API6. OLED写数据(1Byte) */
    void Oled_Write_Data(char dataData);
    /* API7. OLED初始化 */ 
    void Oled_Init();
    /* API8. OLED清屏 */
    void Oled_Clear();
    /* API9. OLED显示汉字 */
    void Oled_Show_Hanzi();
    
    /*--  文字:  嵌  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    code char qian1[16] = {0x80,0x80,0xEE,0x88,0x88,0x88,0xE8,0x8F,0x08,0x88,0x78,0x48,0x4E,0x40,0xC0,0x00};
    code char qian2[16] = {0x00,0x00,0x7F,0x24,0x24,0x24,0x7F,0x00,0x81,0x40,0x30,0x0F,0x30,0x41,0x80,0x00};
    
    /*--  文字:  入  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    code char ru1[16] = {0x00,0x00,0x00,0x00,0x00,0x01,0xE2,0x1C,0xE0,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
    code char ru2[16] = {0x80,0x40,0x20,0x10,0x0C,0x03,0x00,0x00,0x00,0x03,0x0C,0x30,0x40,0x80,0x80,0x00};
    
    /*--  文字:  式  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    code char shi1[16] = {0x10,0x10,0x90,0x90,0x90,0x90,0x90,0x10,0x10,0xFF,0x10,0x10,0x11,0x16,0x10,0x00};
    code char shi2[16] = {0x00,0x20,0x60,0x20,0x3F,0x10,0x10,0x10,0x00,0x03,0x0C,0x10,0x20,0x40,0xF8,0x00};
    
    /*--  文字:  软  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    code char ruan1[16] = {0x08,0xC8,0xB8,0x8F,0xE8,0x88,0x88,0x40,0x30,0x0F,0xC8,0x08,0x28,0x18,0x00,0x00};
    code char ruan2[16] = {0x08,0x18,0x08,0x08,0xFF,0x04,0x84,0x40,0x30,0x0E,0x01,0x0E,0x30,0x40,0x80,0x00};
    
    /*--  文字:  件  --*/
    /*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
    code char jian1[16] = {0x00,0x80,0x60,0xF8,0x07,0x80,0x60,0x1C,0x10,0x10,0xFF,0x10,0x10,0x10,0x00,0x00};
    code char jian2[16] = {0x01,0x00,0x00,0xFF,0x00,0x02,0x02,0x02,0x02,0x02,0xFF,0x02,0x02,0x02,0x02,0x00};
    
    void main(void)
    {
        unsigned char i;
        Oled_Init();
        // 设置页寻址模式
        Oled_Write_Cmd(0x20);
        Oled_Write_Cmd(0x02);
        // 清屏
        Oled_Clear();
    
        Oled_Show_Hanzi();
    
        while(1);   //防止程序退出
    }
    
    void IIC_Start()
    {
        scl = 0;    // 防止OLED雪花
        // o点:
        scl = 1;    // SCL置高电平
        sda = 0;    // SDA置低电平
        // a点:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平转折为高电平,时间未知
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4.7微秒
        // b—>c:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
        // c—>d:
        _nop_();    // SCL延时4微秒
                    // SDA维持低电平
        // d点:
        scl = 0;    // SCL从高电平转折为低电平,时间未知
                    // SDA维持低电平
    }
    
    void IIC_Stop()
    {
        // o点:
        scl = 0;    // SCL置低电平
        sda = 0;    // SDA置低电平
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,时间未知
                    // SDA维持低电平
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4微秒
        // b—>c:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平折为高电平,时间未知
        // c—>d:
                    // SCL维持低高电平
        _nop_();    // SDA延时4.7微秒
        // d点:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
    }
    
    char IIC_ACK()
    {
        char flag;
        // o点:
                    // SCL置低电平
        sda = 1;    // SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线
        // o—>a:
        _nop_();    // SCL延时
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,代表释放SCL数据线
        // a—>b:
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        flag = sda; // SDA被读
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        // b点:
        scl = 0;    // SCL从高电平转折为低电平
        // b—>c:
        _nop_();    // SCL延时
        return flag;
    }
    
    void IIC_Send_Byte(char cdata)
    {
        int i;
        for(i=0; i<8; i++){
            // o点:
            scl = 0;            // SCL置低电平:允许数据翻转
            sda = cdata & 0x80; // SDA发送1bit数据
            // o—>a:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // a点:
            scl = 1;            // SCL从低电平转折为高电平
            // a—>b:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // b点:
            scl = 0;            // SCL从高电平转折为低电平:为下一次数据翻转做准备
            // b—>c:
            _nop_();            // SCL延时5微秒
            cdata = cdata << 1;
        }
    }
    
    void Oled_Write_Cmd(char dataCmd)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x00);    //发送控制字节,确认发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataCmd); //发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Write_Data(char dataData)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x40);    //发送控制字节,确认发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataData);//发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Init()
    {
        Oled_Write_Cmd(0xAE);   //(01)display off (0xae)
        Oled_Write_Cmd(0x00);   //(02)set low column address (0x00)
        Oled_Write_Cmd(0x10);   //(03)set high column address (0x10)
        Oled_Write_Cmd(0x40);   //(04)set start line address (0x40)
        Oled_Write_Cmd(0xB0);   //(05)set page address (0xb0)
        Oled_Write_Cmd(0x81);   //(06)contract control (0x81)
        Oled_Write_Cmd(0xFF);   //(07)send 0xff(多字节指令)
        Oled_Write_Cmd(0xA1);   //(08)set segment remap (0xa1)
        Oled_Write_Cmd(0xA6);   //(09)set normal/reverse (0xa6)
        Oled_Write_Cmd(0xA8);   //(10)set multiplex ratio (1 to 64) (0xa8)
        Oled_Write_Cmd(0x3F);   //(11)set duty 1/32 (0x3f)
        Oled_Write_Cmd(0xC8);   //(12)com scan direction (0xc8)
        Oled_Write_Cmd(0xD3);   //(13)set display offset (0xd3)
        Oled_Write_Cmd(0x00);   //(14)send 0x00 
        Oled_Write_Cmd(0xD5);   //(15)set osc division(0xd5)
        Oled_Write_Cmd(0x80);   //(16)send 0x80
        Oled_Write_Cmd(0xD8);   //(17)set area color mode off (0xd8)
        Oled_Write_Cmd(0x05);   //(18)send 0x05
        Oled_Write_Cmd(0xD9);   //(19)set pre-charge period (0xd9)
        Oled_Write_Cmd(0xF1);   //(20)send 0xf1
        Oled_Write_Cmd(0xDA);   //(21)set com pin configuration (0xda)
        Oled_Write_Cmd(0x12);   //(22)send 0x12
        Oled_Write_Cmd(0xDB);   //(23)set Vcomh(0xdb)
        Oled_Write_Cmd(0x30);   //(24)send0x30
        Oled_Write_Cmd(0x8D);   //(25)set charge pump enable(0x8d)
        Oled_Write_Cmd(0x14);   //(26)send 0x14
        Oled_Write_Cmd(0xAF);   //(27)turn on oled panel(0xaf)
    }
    
    void Oled_Clear()
    {
        int i;
        int j;
        for(i=0; i<8; i++){
            Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
            // 指定SEG0
            Oled_Write_Cmd(0x00);
            Oled_Write_Cmd(0x10);
            // 0到127列,每次写入0,每次写入数据列地址自动偏移
            for(j=0; j<128; j++){
                Oled_Write_Data(0);
            }
        }
    }
    
    void Oled_Show_Hanzi()
    {
        /* 显示汉字"嵌入式软件"的上半部分 */
        // 指定PAGE0
        Oled_Write_Cmd(0xB0);
        // 指定SEG0
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        // 写数据,显示汉字'嵌'的上半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(qian1[i]);
        }
        // 写数据,显示汉字'入'的上半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(ru1[i]);
        }
        // 写数据,显示汉字'式'的上半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(shi1[i]);
        }
        // 写数据,显示汉字'软'的上半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(ruan1[i]);
        }
        // 写数据,显示汉字'件'的上半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(jian1[i]);
        }
        /* 显示汉字"嵌入式软件"的下半部分 */
        // 指定PAGE1
        Oled_Write_Cmd(0xB1);
        // 指定SEG0
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        // 写数据,显示汉字'嵌'的下半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(qian2[i]);
        }
        // 写数据,显示汉字'入'的下半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(ru2[i]);
        }
        // 写数据,显示汉字'式'的下半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(shi2[i]);
        }
        // 写数据,显示汉字'软'的下半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(ruan2[i]);
        }
        // 写数据,显示汉字'件'的下半部分
        for(i=0; i<16; i++){
            Oled_Write_Data(jian2[i]);
        }
    }

9.OLED显示图片

1、图片字模提取:

  • 首先是使用电脑上的画图软件将图片的像素确定在128×64以内。最后,保存成bmp(单色位图)数据格式,bmp格式相对于jpeg格式的特点是没有压缩。这种数据格式方便取模软件识别像素,而不需要解压。
  • 基本操作:打开图像图标IIC协议与OLED_第15张图片
  • 取模方式:C51 格式IIC协议与OLED_第16张图片
  • 图片的取模不像字符那样逐个字符取模,所以不需要用多个字符数组来保存要写入的数据,可以将取模来的数据直接放在一个字符数组中。如果图片的像素沾满整个OLED屏幕的话,那么字符数组的长度是128×8个字节。现在我这里的图片大小是127×64像素的,所以设置的字符数组是127×8字节长度的。

2、OLED显示图片:

  1. 思路:显示一张图片的逻辑和清屏代码的逻辑很像,但要注意循环变量 i 和 j 的数据类型选择,代表PAGE编号的外层循环变量 i 的数据类型可以选择为char,代表字符数组下标的循环变量 j 的数据类型要选择为int 或 unsigned int。如果图片像素大小不同,不可以直接套用以下代码,需要修改一下循环变量的初值和终值。
  2. 代码:其中API 1~API 8 是通用代码
    #include "reg52.h"
    #include "intrins.h"
    
    sbit scl = P0^1;
    sbit sda = P0^3;
    
    /* API1. 启动I2C */
    void IIC_Start();
    /* API2. 终止I2C */
    void IIC_Stop();
    /* API3. 获取IIC的应答位 */
    char IIC_ACK();
    /* API4. I2C发送1字节数据 */
    void IIC_Send_Byte(char cdata);
    /* API5. OLED写指令(1Byte) */
    void Oled_Write_Cmd(char dataCmd);
    /* API6. OLED写数据(1Byte) */
    void Oled_Write_Data(char dataData);
    /* API7. OLED初始化 */ 
    void Oled_Init();
    /* API8. OLED清屏 */
    void Oled_Clear();
    /* API9. OLED显示一张图片 */
    void Oled_Show_Pic(char *image);
    
    code unsigned char bmp[] = {
        /*--  调入了一幅图像:F:\51project\6.IIC协议\IIC协议与OLED\OLED显示图片\ikun.bmp  --*/
        /*--  宽度x高度=127x64  --*/
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x80,0x80,0x80,
        0xC0,0xC0,0xC0,0xC0,0xC0,0xE0,0xE0,0xE0,0x60,0x70,0x70,0x70,0x30,0x30,0x30,0x38,
        0x38,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x38,0x30,0x30,0x30,0x30,0x30,
        0x30,0x30,0x38,0x3C,0x3C,0x1C,0x0E,0x0E,0x0E,0x06,0x07,0x07,0x07,0x07,0x07,0x07,
        0x07,0x07,0x06,0x06,0x06,0x0E,0x0E,0x0C,0x0C,0x0C,0x1C,0x1C,0x18,0x38,0x38,0x30,
        0x30,0x70,0x70,0x70,0xE0,0xE0,0xC0,0xC0,0xC0,0x80,0x80,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,0x00,0x00,0x00,0x80,0x80,0xC0,0xC0,0xE0,0xE0,0x70,
        0x70,0x78,0x38,0x38,0x1C,0x1C,0x1E,0x0E,0x0E,0x06,0x07,0x07,0x07,0x03,0x03,0x01,
        0x01,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,0xC0,0xE0,0xF0,0xFC,0xFE,0xFE,
        0xFE,0xFE,0x76,0x7A,0x78,0x7C,0x7C,0x7C,0x7E,0x7E,0x7E,0x77,0x76,0x7E,0x7E,0x7E,
        0x7C,0x7C,0x78,0x78,0x78,0x70,0xF0,0xE0,0xE0,0xE0,0xC0,0x80,0x00,0x00,0x00,0x00,
        0x40,0xC0,0xC0,0xC0,0xC1,0x81,0x81,0x81,0x03,0x07,0x07,0x07,0x0E,0x0E,0x1E,0x1C,
        0x3C,0x38,0x78,0x78,0xF0,0xE0,0xE0,0xC0,0xC0,0x00,0x00,0x00,0x00,0x00,0xC0,0xE0,
        0xF0,0xF8,0x78,0x7C,0x1E,0x9E,0x8F,0x8F,0x87,0x83,0x01,0x01,0x01,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0xC0,0xF8,0xF8,0xFC,0xFC,0x3C,0x04,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0xFF,0xFF,0xFF,0xFF,0x1F,0x1D,0x1C,0x1C,
        0x18,0x18,0x18,0x18,0x18,0x18,0x38,0x30,0x30,0x70,0x60,0xE0,0xE0,0xC0,0x80,0x80,
        0x00,0x80,0x80,0xC0,0xC0,0xE1,0x69,0x73,0x77,0x3F,0x3F,0xFF,0xFF,0xFE,0xF0,0xE0,
        0x80,0x80,0x81,0x83,0x07,0x07,0x0F,0x3E,0x7E,0x7C,0xF8,0xF0,0xE0,0xC0,0x80,0x00,
        0x00,0x00,0x00,0x00,0x01,0x03,0x07,0x07,0x3F,0x7F,0xFE,0xFC,0xE0,0x0F,0x0F,0x0F,
        0x1D,0x1C,0x18,0xFF,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0xFF,0xFF,0xFF,0xFF,0xFF,0x01,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
        0x00,0x00,0xC0,0xE0,0xF0,0xF8,0xF8,0xF8,0xD0,0xC0,0xE0,0xE0,0xE0,0xEC,0xFC,0xFC,
        0xFC,0xFC,0xF8,0xB8,0x38,0x30,0x30,0x3F,0x3F,0x3F,0x3F,0x20,0x00,0x00,0x00,0x00,
        0x00,0x18,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x3C,0x18,0x81,0xEF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xC3,0x81,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x03,0x03,0x03,0x03,0x1B,
        0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x3F,0x9F,0xDF,0xE7,0xFF,0xFF,0x7F,0x7F,0x7C,
        0xFC,0xF0,0xE0,0xC0,0xC0,0xC0,0xC0,0xE0,0xE0,0xF1,0xFF,0x3F,0xC0,0xC0,0xC0,0xC0,
        0xC0,0xE0,0x67,0x6F,0x6F,0x6F,0xEF,0xEC,0xEE,0xEE,0xFE,0xFE,0xFE,0xFF,0xFF,0x07,
        0x07,0x0F,0x1F,0xFF,0xFF,0xFF,0xFF,0xFC,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xF8,0xFC,
        0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFC,0xF8,0xF8,0x00,0x00,0x01,
        0x01,0x03,0x03,0x03,0x07,0x07,0x06,0x0E,0x0C,0x0C,0x0C,0x0C,0x0C,0x0C,0xEC,0xFC,
        0xFC,0xFC,0x9C,0x1C,0x0C,0x0C,0x0E,0x07,0x07,0x07,0x07,0x07,0x07,0x06,0x06,0x06,
        0x07,0x07,0x07,0x07,0x07,0x06,0x0E,0x8E,0x8C,0xCC,0xDC,0xFC,0xFC,0xFC,0xFC,0xEC,
        0x0C,0x0C,0x06,0x06,0x06,0xE3,0xF3,0xFB,0xFB,0xFC,0xFC,0xFC,0xFE,0xFE,0xFE,0xFE,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0x30,0x00,0x00,0x00,0x00,0x5D,0x1C,0x7D,0x1D,0x3C,
        0x1C,0x1C,0x9C,0x9C,0x1C,0x3D,0xFD,0xFF,0xFF,0x7F,0xDF,0xFF,0xFF,0xFF,0xFE,0xFE,
        0xFC,0xF8,0xF1,0xE1,0xE3,0xE7,0xE7,0xCF,0xCF,0x8F,0x8F,0x9F,0xDF,0xDF,0xDF,0xDF,
        0xDF,0xDF,0xDF,0xDF,0xCF,0xCF,0xCF,0xE7,0xE7,0xE3,0xE1,0xE1,0xE0,0xE0,0xE0,0xE0,
        0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE0,0xE3,0xC7,0xC7,
        0xC7,0xCF,0xCF,0xCF,0xDF,0xDB,0xDB,0xDF,0xDF,0xDE,0x96,0x96,0xB6,0xB6,0xB7,0xB7,
        0x93,0x93,0x93,0x9B,0x9B,0x9B,0x99,0x1D,0x0D,0x0F,0x0F,0x07,0x03,0x03,0x01,0x00,
        0x00,0x00,0x00,0x80,0xC1,0xC7,0xC7,0xCF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xBF,0x9F,
        0x9F,0x8F,0x87,0x87,0x03,0x00,0x00,0x00,0x00,0x00,0x07,0x87,0x67,0x47,0x67,0x67,
        0xE6,0xE6,0x2E,0x0E,0x0E,0xCF,0x4E,0x1E,0x1C,0xDF,0xFF,0xFF,0xFF,0xBF,0x9F,0x9F,
        0x9F,0x9F,0x9F,0x9F,0x9F,0x9F,0x1F,0x1F,0x1F,0x1F,0x1F,0x1F,0x3F,0x3F,0x3F,0x3F,
        0x3F,0x7F,0x7F,0x7F,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFE,0x7F,0x7F,0x7F,0x7F,0x3F,0x3F,0x3F,0x3F,0x3F,0x1F,0x1F,0x1F,0x1F,0x9F,0xFF,
        0xFF,0xFF,0x07,0x07,0x00,0x00,0x00,0x00,0x00,0x87,0x8F,0x4E,0x5E,0x2F,0x4F,0x1C,
        0x3C,0x7D,0x79,0x7A,0x74,0x77,0xF8,0xFC,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFE,0xFE,0xFE,0xFC,0xFC,0xFC,
        0xFC,0xF8,0x60,0x00,0x00,0x01,0x03,0x0F,0xFF,0xFD,0xFD,0xFD,0xFF,0xFF,0xFB,0xFB,
        0xFB,0xFB,0xFB,0xFB,0xFB,0xF3,0xF3,0xF7,0xF7,0xF7,0xF7,0xF7,0x37,0x37,0x37,0x0F,
        0x0F,0x07,0x17,0x37,0x37,0x37,0x77,0xF7,0xF7,0xF3,0xF3,0xFB,0xFB,0xFB,0xFB,0xFB,
        0xFB,0xFF,0xFF,0xFF,0xFF,0xFD,0xFD,0xFD,0xFD,0x1F,0x07,0x03,0x03,0x01,0xC0,0xF0,
        0xF8,0xF8,0xFC,0xFC,0xFC,0xFE,0xFE,0xFE,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,0xFF,
        0xFC,0xFC,0xF8,0xF0,0xE0,0xC0,0x80,0x00
    };
    
    void main(void)
    {
        Oled_Init();
        // 设置页寻址模式
        Oled_Write_Cmd(0x20);
        Oled_Write_Cmd(0x02);
        // 清屏
        Oled_Clear();
        Oled_Show_Pic(bmp);
        while(1);   //防止程序退出
    }
    
    void IIC_Start()
    {
        scl = 0;    // 防止OLED雪花
        // o点:
        scl = 1;    // SCL置高电平
        sda = 0;    // SDA置低电平
        // a点:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平转折为高电平,时间未知
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4.7微秒
        // b—>c:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
        // c—>d:
        _nop_();    // SCL延时4微秒
                    // SDA维持低电平
        // d点:
        scl = 0;    // SCL从高电平转折为低电平,时间未知
                    // SDA维持低电平
    }
    
    void IIC_Stop()
    {
        // o点:
        scl = 0;    // SCL置低电平
        sda = 0;    // SDA置低电平
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,时间未知
                    // SDA维持低电平
        // a—>b:
                    // SCL维持高电平
        _nop_();    // SDA延时4微秒
        // b—>c:
                    // SCL维持高电平
        sda = 1;    // SDA从低电平折为高电平,时间未知
        // c—>d:
                    // SCL维持低高电平
        _nop_();    // SDA延时4.7微秒
        // d点:
                    // SCL维持高电平
        sda = 0;    // SDA从高电平转折为低电平,时间未知
    }
    
    char IIC_ACK()
    {
        char flag;
        // o点:
                    // SCL置低电平
        sda = 1;    // SDA:在主控设备发送完成一个字节数据后,就立马发送一个高电平,代表释放SDA数据线
        // o—>a:
        _nop_();    // SCL延时
        // a点:
        scl = 1;    // SCL从低电平转折为高电平,代表释放SCL数据线
        // a—>b:
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        flag = sda; // SDA被读
        _nop_();    // SCL延时4微秒,因为读取SDA的时候一定要保证SCL处于高电平的状态
        // b点:
        scl = 0;    // SCL从高电平转折为低电平
        // b—>c:
        _nop_();    // SCL延时
        return flag;
    }
    
    void IIC_Send_Byte(char cdata)
    {
        int i;
        for(i=0; i<8; i++){
            // o点:
            scl = 0;            // SCL置低电平:允许数据翻转
            sda = cdata & 0x80; // SDA发送1bit数据
            // o—>a:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // a点:
            scl = 1;            // SCL从低电平转折为高电平
            // a—>b:
            _nop_();            // SCL延时5微秒
                                // SDA正在发送数据
            // b点:
            scl = 0;            // SCL从高电平转折为低电平:为下一次数据翻转做准备
            // b—>c:
            _nop_();            // SCL延时5微秒
            cdata = cdata << 1;
        }
    }
    
    void Oled_Write_Cmd(char dataCmd)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x00);    //发送控制字节,确认发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataCmd); //发送指令
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Write_Data(char dataData)
    {
        IIC_Start();            //发送起始信号
        IIC_Send_Byte(0x78);    //确认从机地址,准备写入
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(0x40);    //发送控制字节,确认发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Send_Byte(dataData);//发送数据
        IIC_ACK();              //应答信号ACK读取
        IIC_Stop();             //发送停止信号
    }
    
    void Oled_Init()
    {
        Oled_Write_Cmd(0xAE);   //(01)display off (0xae)
        Oled_Write_Cmd(0x00);   //(02)set low column address (0x00)
        Oled_Write_Cmd(0x10);   //(03)set high column address (0x10)
        Oled_Write_Cmd(0x40);   //(04)set start line address (0x40)
        Oled_Write_Cmd(0xB0);   //(05)set page address (0xb0)
        Oled_Write_Cmd(0x81);   //(06)contract control (0x81)
        Oled_Write_Cmd(0xFF);   //(07)send 0xff(多字节指令)
        Oled_Write_Cmd(0xA1);   //(08)set segment remap (0xa1)
        Oled_Write_Cmd(0xA6);   //(09)set normal/reverse (0xa6)
        Oled_Write_Cmd(0xA8);   //(10)set multiplex ratio (1 to 64) (0xa8)
        Oled_Write_Cmd(0x3F);   //(11)set duty 1/32 (0x3f)
        Oled_Write_Cmd(0xC8);   //(12)com scan direction (0xc8)
        Oled_Write_Cmd(0xD3);   //(13)set display offset (0xd3)
        Oled_Write_Cmd(0x00);   //(14)send 0x00 
        Oled_Write_Cmd(0xD5);   //(15)set osc division(0xd5)
        Oled_Write_Cmd(0x80);   //(16)send 0x80
        Oled_Write_Cmd(0xD8);   //(17)set area color mode off (0xd8)
        Oled_Write_Cmd(0x05);   //(18)send 0x05
        Oled_Write_Cmd(0xD9);   //(19)set pre-charge period (0xd9)
        Oled_Write_Cmd(0xF1);   //(20)send 0xf1
        Oled_Write_Cmd(0xDA);   //(21)set com pin configuration (0xda)
        Oled_Write_Cmd(0x12);   //(22)send 0x12
        Oled_Write_Cmd(0xDB);   //(23)set Vcomh(0xdb)
        Oled_Write_Cmd(0x30);   //(24)send0x30
        Oled_Write_Cmd(0x8D);   //(25)set charge pump enable(0x8d)
        Oled_Write_Cmd(0x14);   //(26)send 0x14
        Oled_Write_Cmd(0xAF);   //(27)turn on oled panel(0xaf)
    }
    
    void Oled_Clear()
    {
        int i;
        int j;
        for(i=0; i<8; i++){
            Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
            // 指定SEG0
            Oled_Write_Cmd(0x00);
            Oled_Write_Cmd(0x10);
            // 0到127列,每次写入0,每次写入数据列地址自动偏移
            for(j=0; j<128; j++){
                Oled_Write_Data(0);
            }
        }
    }
    
    void Oled_Show_Pic(char *image)
    {
        char i;
        int j;
        for(i=0; i<8; i++){
            Oled_Write_Cmd(0xB0 + i); // 依次指定PAGE0~PAGE7
            // 指定SEG0
            Oled_Write_Cmd(0x00);
            Oled_Write_Cmd(0x10);
            // 0到127列,每次写入0,每次写入数据列地址自动偏移
            for(j=127*i; j<127*(i+1); j++){
                Oled_Write_Data(image[j]);
            }
        }
    }

你可能感兴趣的:(单片机)