• OLED


    1、SSD1306

    SSD1306是一个单芯片的CMOS OLED/PLED驱动器,有着操纵微小的发光二极管组成的矩阵面板而展示图像的控制系统。包含着128segments64commons,是为共阴OLED 面板设计的IC芯片。

    以上就是SSD1306文档的第一段话的大概意思,OLED 12864是一个密度较大的LED点阵,和点阵模块的学习一样,都是在学习一个驱动器,但这个比MAX2719牛逼的多。

    它有五种与MCU交互的方式,但主要用到的只有SPII2C两种,我现在手上也只有I2C的,但这个只是数据传输的方式而已,目的都是给这个昵称叫SSD1306的好友发同样的信息,只要连接得到,用微信和QQ都是OK的。

    2、I2C

    直接跳到I2C部分。

    2.1 I2C写模式

    SSD1306是不用读模式的,所以单独写个I2C写模式的驱动就可以操作这个OLED屏了。

    2.2.1 i2c起始信号

    主机初始化I2C必须要发个起始信号给从机。起始信号的产生必须是在SCL为高电平的情况下,下拉SDA产生下降沿。

    /**
     * 起始信号
     */

    void i2cStart(){
     // 上拉SCL
     SET_SCL();
     // 拉低SDA,产生起始信号
     CLR_SDA();
     // 然后上拉回去,准备发信息
     SET_SDA();
    }

    /**
     * 终止信号
     */

    void i2cStop(){
     // 下拉SDA
     CLR_SDA();
     // SCL保持高电平
     SET_SCL();
     // 上拉SDA产生终止信号
     SET_SDA();
    }

    2.2.2 从机地址

    image-20210301151257717
    image-20210301151257717

    从机地址是由七个位组成的,如上图框起来的高七位,这里说明了SSD1306的从地址高六位是固定的,只有第七位受SA0控制,如果这个位为逻辑0,地址则是0x78,如果为逻辑1则是0x7A。一般情况下看一下背面就可以了,厂家一般会默认选择搞成0x78

    image-20210301152126423

    这样一来,理论上一个MCU是可以控制两个OLED实现"双屏"的。但我没有试过。然后回去看从机地址最后紧跟着的一个R/W#位,表示读写模式,其中写模式对应的是逻辑"0"。

    2.2.3 i2c的应答位

    每次发送了一个字节的数据都会有一个时钟用于接收应答位,地址位+读写位就是一个字节,所以也是会有一个应答位的。但是说明文档里面也说了,不用读模式的情况下,应答位只要根据下图的模式把它忽略掉就可以,反正没什么意义。

    image-20210301164129014
    image-20210301164129014
    /**
     * 处理应答位
     */

    void i2cIgnoreAck(){
     // 上拉SCL,传输数据
     SET_SCL();
     // 下拉SCL,为下一次数据传输或终止信号服务
     CLR_SCL();
    }

    2.2.4 i2c发送数据

    /**
     * 发送一个位
     */

    void i2cSendBit(unsigned char bit){
     // 产生信息
     if(bit==0){
      CLR_SDA();
     }
     else{
      SET_SDA();
     }
     // 发送信息
     SET_SCL();
     // 下拉SCL,为下一次数据传输和终止信号服务
    }

    /**
     * 发送一个字节
     */

    void i2cSendByte(unsigned char dat){
     unsigned char i;
     // 下拉SCL
     CLR_SCL();
     for( i = 0; i < 8; i++ ){
      i2cSendBit((dat<<i)&0x80);
     }
    }

    2.2.5 Command Decoder

    这个是SSD1306负责分析接口传输的信息的解析器,发送完地址和读写位之后,还要跟着发出一位Co位和一个D/C#位,再接着六个逻辑0填满一个字节。

    对Co位,如果它为逻辑0,则会认为接下来要传输的信息只包含字节数据。而对于D/C#,当该位为逻辑0时会被认为本次传输的信息是指令信息,反之则是数据信息。数据信息是会存进GDDRAM的,SDD1306包含着128*64个比特的缓存区,刚好可以控制128*64个LED,也就是一整屏的数据。

    每写完一次数据,GDDRAM的指针就会自动后移一次,比如在屏幕的左边开始写入数据,则不用为每一个数据都设置坐标指令信息,它会像光标一样自动后移的,甚者,它还能自动换行,如果需要的话。

    3、SSD1306工作机制

    3.1 FR同步信号

    传说中的抗锯齿?一开始显示第一帧的图像的速度是取决于单片机的,毕竟单片机数据写的慢了,SSD1306也只能"粉刷"式显示,即一行一行或一列一列刷新下去。如果单片机能够在一个OLED把缓冲的帧数据更新到屏幕的时间内完成一次写入,那就会被归类于快机。如果不行,那就给它刷新两帧的时间,并且归类于慢机,还不行的话它也没辙了。

    对于快机,它应该要在FR同步信号产生上升沿的时候开始把帧数据写入ram中,并且它下次产生上升沿之前完成一帧的读写,然后给停下来,它产生上升沿时再写,这样可以确保帧数据写入时不会影响到屏幕刷新的帧画面。

    而对于慢机,则要在第一次FR上升沿时进行写入,而在第三次上升沿之前写完一帧数据。不然屏幕就得告诉它:自求多福

    3.2 SSD1306复位

    RES#引脚为低电平时,SSD1306就会复位成以下的状态:

    1. 显示(display)状态关闭。
    2. 初始化为 128 x 64 显示模式。
    3. SEG0COM0映射的两个零地址,并且指针也复位到这两个地址。
    4. 串口交互寄存器的数据清空。
    5. COM输出的方向为缺省的扫描方向。
    6. 对比度寄存器的值设为7FH
    7. 切换为缺省的展示模式(A4F指令)。

    2.2.8 Segment/Common驱动器

    Segment翻译下来就是,这个段驱动器将OLED的面板切割成128个段,每个段都有一个电流源去驱动OLED面板。电流源的强度可以用一个八位的寄存器在0~100uA的范围内进行调整,也就是说,电流的大小被化分成256个等级,也可以对应着256个亮度等级。

    然而一个发光二极管要工作,光有电流是没用的,负载直接与电流源连接是不会工作的。电流、负载有了,差个电压,就是Common驱动器了。

    在抽象的地方想,SegmentCommon可以想象成一个直角坐标系,每一个坐标点都会对应一个像素点(或发光二极管),只要是Segment和Common对应的坐标上的负载,就是点亮的二极管(或有颜色的像素点)。

    3.4 图像数据缓存区(GDDRAM)

    这个中文名是我起的,毕竟好理解一点。正名叫图形显示数据随机存储器(机翻Graphic Display Data RAM)。我们给SSD1306写入的帧数据都应该写入这个随机存储器中。

    segment(SEG)和common(COM)从这里映射出来的,但这两个东西映射的都是一个个比特,而存储器是按字存储的东西,最小的存储单元也应该是字节才对。于是就引出了一个新的概念:页(Page)。每一个页由8个COM组成,即128 x 64 比特的矩阵映射成了 128 x 8 字节(或页)的矩阵。

    再来看OLED面板:

    一个128 x 64的矩阵面板被划分成了127个,每一个段都有公共的8个,每一页都包含着8个公共端(COM)。每当有一个字节的数据被写入缓冲区的某一页时,页内八个公共端都会上电,然后段指针寄存器所指向的对应列就会从地位到高位写入该字节的数据。

    如上图,假设有一个字节的数据要写入PAGE2页,并且当前的"光标"就在SEG1上,那么该数据就会在这个PAGE2内的SEG1列自上向下写入。

    SSD1306也支持自下向上写入,有两种方法:第一种方法就是坐标重新反方向重映射(re-mapping,后面会说),这样SEG0就会在最右边且COM0在最下面;第二种方法就是直接把屏幕倒过来。

    4、点亮OLED

    SSD1306的文档看到这里就差不多已经是可以停下来了,因为文档后面的内容都是一些指令的说明。指令的话,还是用到什么再查什么比较好。

    4.1 指令集

    4.1.1 基础指令

    Hex 指令名 指令N 备注
    81H 设置对比度控制 A[] 实际取值是A[] + 1
    A4H/A5H 设置全局显示 A5H强制全局显示
    A6H/A7H 设置反白显示 A7H即比特值为0时显示
    AEH/AFH 设置显示开关 AEH为关闭显示

    4.1.2 滚动指令

    Hex 指令名 指令 备注
    26H/27H 连续水平滚动 AEF[]、BCD[2:0] Hex左/右滚,AEF空字节,BD始末页地址,C帧率
    29H/2AH 水平和竖直连续滚动 ABCD[2:0]、E[5:0] Hex左/右滚+竖直,指令同上,E是竖直滚动偏移
    NA 没有单独连续竖直滚动的指令
    2EG/2FH 滚动开关 启动/关闭滚动
    A3H 垂直滚动区域 A[5:0]、B[6:0] A是固定区域的顶行号,B是行数

    4.1.3 地址设置指令

    Hex 指令名 指令 备注
    00H~0F 设置起始列地址的低位 只在页地址模式有效
    10H~1F 设置起始列地址的高位 只在页地址模式有效
    20H 设置存储地址的模式 A[1:0] 水平、竖直、页、[无效],四种,复位页模式
    21H 设置列地址 AB[6:0] 设置列地址的始末区域,在水平/竖直模式有效
    22H 设置页地址区域 AB[2:0] 设置页地址的始末区域,在页地址模式有效
    B0H~B7H 设置页地址 只在页地址模式有效,刚好对应八页

    4.1.4 硬件配置指令

    Hex 指令名 指令 备注
    40H~7FH 设置显示的起始行 总共64行
    A0H/A1H 设置段(SEG)重映射 A1H是列翻转
    C0H/C8H 设置COM输出扫描方向 C8H是行反转
    注意两个同时反转才是把屏幕倒过来
    A8H 设置多路复用率 A[5:0] 起始也就是显示几行,最低设置15,对应16行
    D3H 设置显示偏移 A[5:0] 图像竖直向上偏移,复位是00H
    DAH 设置COM引脚硬件配置 A[5:4] 复位是12H,加上重映射总共八种玩法,太花了

    4.1.5 其它的配置

    Hex 指令名 指令 备注
    D5H 设置时钟倍频和振荡频率 A[] 高位振荡频率低位分频,复位80H
    D9H 设置预充电周期 A[] 同步亮度的,复位22H
    DBH 设置COM的反压 A[6:4] 复位是20H
    8DH 电荷泵设置 A[2] 复位是0B,启用电荷泵需要配置成1B
    OLED面板的驱动电压就是靠这个泵上去的
    E3H NOP 空指令

    整理好了,需要用的时候参考这个表格就可以做大部分配置了。

    4.2 写指令

    前面的SSD1306复位的内容提到,OLED默认是关掉显示的,所以要点亮这个OLED屏,得给它写入开显示屏的指令。所以首先得封装一个发送指令的函数:

    /**
     * 发送一个指令
     */

    void oledSendCmd(unsigned char cmd){
     // 启动I2C
     i2cStart();
     // 把地址和R/W#位一起发过去
     i2cSendByte(I2C_ADDR_W);
     i2cIgnoreAck();
     // 告诉指令解析器,接下来的是一个指令
     i2cSendByte(0x00);
     i2cIgnoreAck();
     i2cSendByte(cmd);
     i2cIgnoreAck();
     i2cStop();
    }

    4.2 点亮OLED

    最简单的点亮方法就是直接启动和点亮OLED面板两个指令。

    // 启动OLED面板
    oledSendCmd(0x8D);
    oledSendCmd(0x14);
    // 点亮OLED
    oledSendCmd(0xAF);
    image-20210302221910120

    这样的话亮的其实也不平均,反正什么数据都没写入,干脆就把整个屏点亮!

    // 全局显示
    oledSendCmd(0xA5);
    image-20210302222158778

    5、 显示一张图片

    默认是页模式,所以全屏图片的数组数据应该是128x8 = 1024长度。

    /**
     * 显示一张128x8的图片
     */

    void oledShowFullImg(){
     unsigned int i = 0;
     unsigned char j, k;
     for( j = 0; j < 8; j++ ){
      oledSetPos(0, j);
      for( k = 0; k < 128; k++ ){
       oledSendDat(~yize_image[i++]);
      }
     }
    }

    main.c

    #include "delay.h"
    #include "oled.h"

    // 这里是图片数组,但因为太多了,放到末尾了

    /**
     * 显示一张128x8的图片
     */
    void oledShowFullImg(){
     unsigned int i = 0;
     unsigned char j, k;
     for( j = 0; j < 8; j++ ){
      oledSetPos(0, j);
      for( k = 0; k < 128; k++ ){
       oledSendDat(~image[i++]);
      }
     }
    }

    int main(void){
     delay_init();
     oledInit();
     
     delay_ms(800);
     
     // 屏幕倒转
     oledSendCmd(0xA1);
     oledSendCmd(0xC8);
     
     oledSendCmd(0x8D);
     oledSendCmd(0x14);
     oledSendCmd(0xAF);
     
     oledShowFullImg();

     while(1);
    }
    image-20210302232448238
    image-20210302232448238

    5.1 图片数组数据

    unsigned char image[1024]={
    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
    255,255,255,255,255,255,255,255,255,255,127,31,15,7,7,39,63,63,63,63,63,63,63,63,63,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,127,31,15,7,7,39,63,63,63,63,63,63,63,63,63,255,255,255,255,255,255,255,255,255,255,255,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,31,255,255,255,255,255,255,255,15,15,15,143,143,143,143,143,143,15,15,15,143,143,143,143,143,143,15,15,15,255,255,255,255,255,255,255,255,255,
    255,255,255,255,255,231,199,195,1,0,0,228,230,231,231,231,231,7,7,3,225,224,224,228,230,231,7,7,7,255,255,255,255,255,255,231,199,195,1,0,0,228,230,231,231,231,231,7,7,3,225,224,224,228,230,231,7,7,7,255,255,255,255,255,255,255,255,255,255,255,255,255,127,63,31,15,3,1,0,16,252,126,63,127,127,255,255,255,255,255,255,255,255,255,255,255,255,127,64,64,64,68,68,68,68,68,68,0,0,0,68,68,68,68,68,68,64,64,64,127,255,255,255,255,255,255,255,255,
    255,255,255,255,255,255,255,255,0,0,0,24,24,24,24,24,24,0,0,0,24,24,24,24,24,24,0,0,0,255,255,255,255,255,255,255,255,255,0,0,0,24,24,24,24,24,24,0,0,0,24,24,24,24,24,24,0,0,0,255,255,255,255,255,223,159,15,135,131,195,225,240,240,248,252,254,255,0,0,0,255,254,252,248,248,240,225,195,131,135,143,207,255,255,255,255,255,0,0,128,198,70,70,102,230,230,38,32,32,96,230,230,224,192,2,14,30,0,0,192,255,255,255,255,255,255,255,255,
    255,255,255,255,255,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,255,255,255,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,231,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,128,128,128,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,231,193,224,248,254,255,240,192,192,199,207,207,206,204,204,206,207,199,192,224,241,254,248,240,224,227,231,255,255,255,255,255,255,
    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
    255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,255,
    };
  • 相关阅读:
    省队集训 Day1 残缺的字符串
    省队集训 Day3 吴清华
    省队集训 Day3 陈姚班
    Java多线程中的join方法
    Java多线程同步机制之同步块(方法)——synchronized
    java-实用的sql语句
    java-分页之页面分页
    java下实现调用oracle的存储过程和函数
    java-MySQL存储过程
    MySQL存储过程
  • 原文地址:https://www.cnblogs.com/rcklos/p/14472140.html
Copyright © 2020-2023  润新知