打印
[资料下载]

iic驱动0.96oled

[复制链接]
491|0
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
elsaflower|  楼主 | 2023-11-26 20:24 | 只看该作者 回帖奖励 |倒序浏览 |阅读模式
1.IIC概述
IIC(Inter-Integrated Circuit)其实是IICBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。
2.IIC物理层
只要求两条总线线路,一条是串行数据线SDA,一条是串行时钟线SCL,IIC是半双工,而不是全双工。每个连接到总线的器件都可以通过唯一的地址和其它器件通信,主机/从机角色和地址可配置,主机可以作为主机发送器和主机接收器。IIC是真正的多主机总线,可以在通讯过程中改变主机,如果两个或更多的主机同时请求总线,可以通过冲突检测和仲裁防止总线数据被破坏。
连接到总线的IIC数量只是受到总线的最大负载电容400pf限制。
IIC总线通过上拉电阻接正电源。当总线空闲时,两根线均为高电平。连到总线上的任何一个器件输出的低电平,都将使总线的信号变低,即各器件的SDA和SCL都是线“与”的关系。
3.IIC协议层3-1.数据有效性
在时钟的高电平周期内,SDA线上的数据必须保持稳定,数据线仅可以在时钟SCL为低电平时改变。
3-2.起始信号和终止信号
SCL线为高电平期间,SDA线由高到低变化表示起始。 SCL线为高电平期间,SDA线由低到高变化表示终止。
要注意起始和终止信号都是由主机发出的,连接到IIC总线上的器件,若具有IIC总线的硬件接口,则很容易检测到起始和终止信号。总线在起始条件之后,视为忙状态,在停止条件之后被视为空闲状态。
3-3.应答
每当主机向从机发送完一个字节的数据,主机总是需要等待从机给出一个应答信号,以确认从机是否成功接收到了数据,从机应答主机所需要的时钟仍是主机提供的,应答出现在每一次主机完成8个数据位传输后紧跟着的时钟周期,0表示应答,1表示非应答。
3-4.数据帧格式
IIC总线上传输的数据信息是广义的,既包括地址信号,又包括真正的数据信号。起始信号后必须传送一个从机的地址(7位),第八位是数据的传送方向位(R/T),用“0”表示主机发送数据“T”,“1”表示主句接收数据(R)。每次数据传送总是由主机产生的终止信号结束。但是,若主机希望继续占用总线进行新的数据传送,则可以不产生终止信号,马上再次发出起始信号对另一从机进行寻址。
IIC传输时时从MSB开始传输到LSB结束。MSB是Most Significant Bit的缩写,最高有效位。在二进制数中,MSB是最高加权位。与十进制数字中最左边的一位类似。通常,MSB位于二进制数的最左侧,LSB位于二进制数的最右侧。LSB,英文 least significant bit,中文义最低有效位。
IIC写时序
  • 产生start位
  • 传送器件地址ID_Address,器件地址的最后一位为数据的传输方向位,R/W,低电平0表示主机往从机写数据(W),1表示主机从从机读数据(R)。ACK应答,应答是从机发送给主机的应答,这里不用管。
  • 传送写入器件寄存器地址,即数据要写入的位置。同样ACK应答不用管。
  • 传送要写入的数据。ACK应答不用管。
  • 产生stop信号。
IIC读时序
  • 产生start信号
  • 传送器件地址(写ID_Address),ACK。
  • 传送字地址(写REG_Address),ACK。
  • 再次产生start信号
  • 再传送一次器件地址,ACK。
  • 读取一个字节的数据,读数据最后结束前无应答ACK信号。
  • 产生stop信号。
从时序图上可以看出,IIC读时序要写两次器件地址,刚开始接触的时候我也很疑惑 dummy write。我个人这样理解这里,首先传送器件地址到总线上找到器件,然后写入寄存器地址,也就是word address找到需要读取数据的地址,但并不是真正的写入数据所以叫做dummy wirte(假写)。然后再传输一次器件地址后开始读数据。
IIC协议在读写数据时,总是要发送器件地址,这里需要注意的是,不是主机给从机发送地址,而是主机给地址总线上发送地址,挂IIC总线上的所有从机都能收到地址,如果发过来的地址和自己的地址匹配上了,从机就会给主机一个应答,这样就建立起来了一个通讯。所以我在想,如果从机的器件是完全一样的,那么IIC协议就可以同时给多个从机,即对多个器件进行配置。这种理论上是可行的,但其实是不行的,IIC协议就是通过地址不同来判断给哪个器件传送数据的,如果两个器件的地址完全一样,器件会产生应答,那么两个器件就通过竞争判断给谁通信了,有随机性。即IIC协议一次只能和一个设备/器件进行通讯。
二、模拟IIC信号控制0.96寸OLED屏(SSD1306)1.OLED屏基本规格1-1.显示规格
  • 显示方式:无源矩阵
  • 显示颜色:单色(蓝色)
  • 驱动占空比:1/64 Duty
1-2.机械规格
  • 像素数:128 × 64
  • 面板尺寸:6.70 × 19.26 × 1.4 (mm)
  • 有效面积:21.744 × 10.864 (mm)
  • 像素间距:0.17 × 0.17 (mm)
  • 像素尺寸:0.154 × 0.154 (mm)
  • 重量:1.54 (g)
1-3.内存映射和像素构造1-4.引脚定义
GND——逻辑电路接地,接电源负极
VCC——
电压供应引脚,接电源正极
SCL——串行时钟输入
SDA——
串行数据输入
2.IIC信号的模拟2-1.起始信号、停止信号和获取应答信号
89C51系列单片机不带IIC总线接口,但是可以利用软件实现IIC总线的数据传送,即软件与硬件结合的信号模拟。(即使是含有IIC硬件的STM32一般也会模拟IIC的时序)。为了保证数据传送的可靠性,标准的I2C总线的数据传送有严格的时序要求。I2C总线的起始信号、终止信号、发送“0”及发送“1”的模拟时序如下:
具体代码如下:
//起始信号void IIC_start(){    SDA = 1;        //把数据线拉高    SCL = 1;        //把时钟线拉高    Delay6us();     //延时6微秒,要求大于4.7微秒    SDA = 0;        //把数据线拉低,产生下降沿    Delay6us();     //延时6微秒    SCL = 0;        //把时钟线拉低,因为只有时钟线拉低的时候才允许数据变化。}//停止信号  void IIC_stop(){    SDA = 0;        //把数据线拉低    SCL = 1;        //把时钟线拉高    Delay6us();     //延时6微秒,要求大于4微秒    SDA = 1;        //把数据线拉高,产生上升沿    Delay6us();     //延时6微秒}//主机获取应答信号ACKchar IIC_ACK(){    char flag;      //定义一个读取ACK的字符    SDA = 1;        //先拉高数据线    Delay6us();     //延时    SCL = 1;        //拉高时钟线    Delay6us();     //延时,要求大于4微妙    flag = SDA;     //读取从机的ACK信号    Delay6us();     //延时    SCL = 0;        //拉低时钟线    Delay6us();    return flag;}
2-2.数据发送的时序
查询手册,C51单片机应使用如下时序图:
代码实现:
void IIC_send_byte(char send_data){    int i;    for(i=0;i<8;i++)    {        SCL = 0;                    //把时钟线拉低,做好发送数据的准备,在时钟线为低电平时,才能修改数据        SDA = send_data & 0x80;      //数据发送由高位先发送,与上0X80获取高位数据        Delay6us();                 //延时        SCL = 1;                    //时钟线拉高电平,发送数据        Delay6us();                         SCL = 0;                    //发送完拉低时钟线        Delay6us();        send_data <<= 1;            //左移数据位    }}
2-3.IIC写数据
以本次OLED屏(SD1306)为例,写入数据的数据帧如下:
  • 发送一个起始信号
  • 发送7位从站地址+1位的读写信号,这里由于只对OLED写入数据,所以用b01111000,也就是0x78。
  • 等待ACK
  • 发送一个control byte,control byte有两个类型,如果是0x00就是写入命令,如果是0x40就是写入数据(显示数据)。
  • 等待ACK
  • 写入指令或数据,由第4步决定
  • 等待ACK
  • 发送一个停止信号
//写入指令void oled_cmd(char cmd){    IIC_start();    IIC_send_byte(0x78);    IIC_ACK();    IIC_send_byte(0x00);    IIC_ACK();    IIC_send_byte(cmd);    IIC_ACK();    IIC_stop();}//写入数据void oled_data(char Data){    IIC_start();    IIC_send_byte(0x78);    IIC_ACK();    IIC_send_byte(0x40);    IIC_ACK();    IIC_send_byte(Data);    IIC_ACK();    IIC_stop();}
2-4.OLED初始化
手册中关于OLED的流程图
根据流程图,编写以下初始化代码
void oled_init(){    oled_cmd(0xAE);    oled_cmd(0x00);    oled_cmd(0x10);    oled_cmd(0x40);    oled_cmd(0xB0);    oled_cmd(0x81);    oled_cmd(0xFF);    oled_cmd(0xA1);    oled_cmd(0xA6);    oled_cmd(0xA8);    oled_cmd(0x3F);    oled_cmd(0xC8);    oled_cmd(0xD3);    oled_cmd(0x00);    oled_cmd(0xD5);    oled_cmd(0x80);    oled_cmd(0xD9);    oled_cmd(0xF1);    oled_cmd(0xDA);    oled_cmd(0x12);    oled_cmd(0xDB);    oled_cmd(0x30);    oled_cmd(0x8D);    oled_cmd(0x14);    oled_cmd(0xAF);}
3.OLED的显示管理
手册原文如下:
整个屏幕由上至下被分为8个区域,也就是有8个页,每个页有128×8个点阵,把每个点看作一个位,可以把每个页看作由128个字节控制的区域。
由此可以看出,每个页共有128列,1列对应1个字节,每列由上至下8个位对应,写入数据的低位到高位。例如往第0页的第0列写入0x01,那么点亮的就是整个屏幕最左上角的点。
4.寻址模式4-1.页寻址模式(默认值,也是最常用的模式)
在页寻址模式下,读取/写入显示RAM后,列地址指针增加自动加1。如果列地址指针到达列结束地址,则列地址指针为重置为列起始地址而页地址指针不会改变。用户必须设置新页面和列地址,以便访问下一页RAM的内容。
4-2.水平寻址模式
在水平寻址模式下,读取/写入显示RAM后,列地址指针增加自动加1。如果列地址指针到达列结束地址,则列地址指针为重置为列起始地址和页地址指针增加1。列和页地址指针都到达结束地址,指针被重置为列起始地址和页起始地址。
4-3.垂直寻址模式
在垂直寻址模式下,读取/写入显示RAM后,页面地址指针增加自动加1。如果页面地址指针到达页面结束地址,页面地址指针将被重置页起始地址和列地址指针加1。当列和页地址指针到达结束地址,指针被重置为列起始地址和页起始地址
4-4.设置内存寻址模式
三种寻址模式分别对应:
  • 页寻址模式:0x02(默认值,这里的RESET指的就是默认值)
  • 水平寻址模式:0x00
  • 垂直寻址模式:0x01
先发送命令0x20,再发送需要设置的寻址模式。(如果是页寻址模式,两条指令都可以省略)
4-5.在页寻址模式下设置GDDRAM访问指针的示例
在正常的显示数据RAM读写和页寻址模式下,需要执行以下步骤
定义RAM访问指针的起始位置:
  • 设置页面的起始地址命令B0h~B7h目标显示位置。
  • 通过命令00h~0Fh设置指针的低起始列地址。
  • 通过命令10h~1Fh设置指针的高起始列地址。
例如设置页面地址为B2h,下列地址为03h,上列地址为10h,那么开始列是PAGE2的SEG3,输入数据字节将被写入第3列的RAM位置(手册原文有误,翻译时更正了)。
页地址的设置比较简单,发送命令0xB0~0xB7分别表示页指针指向0~7页。
列的设置稍微麻烦了一点,需要分别设置列的低地址和高地址,当列指针指向第0列时,列的低地址和高地址分别为0x00和0x10。
例如,当前列指针指向第15列,那么列的低地址为0x0F,高地址为0x10。当列指针指向第16列时,列的低地址加1,即0x0F+1=0x10,但是低地址的高4位不能操作,因此当列的低地址等于0x10时就会溢出,重置为0x00,然后把高4位里的1传递给列的高地址,列的高地址就变成了0x11了。所以当列指针指向第16列时,列的低地址为0x00,高地址为0x11。可以这么理解,只要低地址的低4位溢出,溢出值就会给到高地址,当高地址的低4位也溢出时,就会重置为0x10,也就是列指针指向第0列。
5.实现OLED显示
在OLED显示一个点,整合前面的代码,可得下面的代码:
#include "reg52.h"#include <intrins.h>sbit SCL = P1^4;sbit SDA = P1^3;void Delay6us()     //@11.0592MHz{    _nop_();}void IIC_start(){    SDA = 1;    SCL = 1;    Delay6us();    SDA = 0;    Delay6us();    SCL = 0;}void IIC_stop(){    SDA = 0;    SCL = 1;    Delay6us();    SDA = 1;    Delay6us();}char IIC_ACK(){    char flag;    SDA = 1;    Delay6us();    SCL = 1;    Delay6us();    flag = SDA;    Delay6us();    SCL = 0;    Delay6us();    return flag;}void IIC_send_byte(char send_data){    int i;    for(i=0;i<8;i++)    {        SCL = 0;        SDA = send_data & 0x80;        Delay6us();        SCL = 1;        Delay6us();        SCL = 0;        Delay6us();        send_data <<= 1;    }}void oled_cmd(char cmd){    IIC_start();    IIC_send_byte(0x78);    IIC_ACK();    IIC_send_byte(0x00);    IIC_ACK();    IIC_send_byte(cmd);    IIC_ACK();    IIC_stop();}void oled_data(char Data){    IIC_start();    IIC_send_byte(0x78);    IIC_ACK();    IIC_send_byte(0x40);    IIC_ACK();    IIC_send_byte(Data);    IIC_ACK();    IIC_stop();}void oled_clear()       //清屏程序,给每页每列都写入0{    unsigned char i,j;    for(i=0;i<8;i++)        //遍历每个页    {        oled_cmd(0xB0+i);        oled_cmd(0x00);                 oled_cmd(0x10);        for(j=0;j<128;j++)  //遍历每个列        {            oled_data(0);   //写入0x00        }    }}void oled_init(){    oled_cmd(0xAE);    oled_cmd(0x00);    oled_cmd(0x10);    oled_cmd(0x40);    oled_cmd(0xB0);    oled_cmd(0x81);    oled_cmd(0xFF);    oled_cmd(0xA1);    oled_cmd(0xA6);    oled_cmd(0xA8);    oled_cmd(0x3F);    oled_cmd(0xC8);    oled_cmd(0xD3);    oled_cmd(0x00);    oled_cmd(0xD5);    oled_cmd(0x80);    oled_cmd(0xD9);    oled_cmd(0xF1);    oled_cmd(0xDA);    oled_cmd(0x12);    oled_cmd(0xDB);    oled_cmd(0x30);    oled_cmd(0x8D);    oled_cmd(0x14);    oled_cmd(0xAF);}void main(){    oled_init();    oled_clear();       //清屏    oled_cmd(0x20);         oled_cmd(0x02);     //页寻址模式    oled_cmd(0xB0);     //选择第0页    oled_data(0x08);    //在第0页的第0列写入b00001000    while(1);}
如果要显示一条直线,可以参考上面的oled_clear()这个函数,把main函数里面的代码改成如下代码:
void main(){    unsigned char j;    oled_init();    oled_clear();    oled_cmd(0x20);    oled_cmd(0x02);    oled_cmd(0xB0);     //选择第0页    for(j=0;j<128;j++)    {        oled_data(0x80);    }    while(1);}

使用特权

评论回复
发新帖 我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

21

主题

1262

帖子

0

粉丝