简明易懂:深入理解I2C总线协议

基本了解:

和串口通讯相比,I2C是半双工的,意思就是要么只能发,要么只能收

速度最高10kbps,串口最高达到20kbps

优点是一条总线上可以挂载多个支持I2C协议的设备,每个IC有唯一的地址

所有设备都可以是主机,但是同一时间只能有一个主机

构成:

一般有两根信号线,双向数据线SDA和单向时钟线SCL

图一

I2C协议:

传输过程一般有三种信号:起始信号、结束信号、应答信号

如图一所示:两条信号线在初始状态下都是通过上拉电阻拉至高电平的,所有需要给总线一些信号才能精确的工作

完成三个信号的配置就可以使用I2C协议来收发数据了

起始信号:

分析时序图:

起始信号是S,是一个下降沿触发

两个时间段的配置:

  1. SCL高电平且SDA高电平持续时间超过4.7us

  1. SCL高电平且SDA低电平持续时间超过4us

在1和2之间转变SDA的电平状态即可给I2C总线发送起始信号,I2C总线进入工作状态

sbit scl = P0^1;
sbit sda = P0^3;
void IIC_Start()
{
scl = 0;//防止屏幕显示雪花
sda = 1;
scl = 1;
_nop_();//一个_nop_()在11.059MHZ的51单片机中差不多为5us
sda = 0;
_nop_();
}

终止信号:

终止信号是P,上升沿触发

两个时间段配置:

  1. SCL高电平且SDA低电平持续时间超过4us

  1. SCL高电平且SDA高电平持续时间超过4.7us

在1和2之间转变SDA的电平状态即可向总线发送终止信号,I2C总线停止工作

void IIC_Stop()
{
scl = 0;//防止屏幕显示雪花
sda = 0;
scl = 1;
_nop_();
sda = 1;
_nop_();
}

应答信号:

发送器每发送一个8bit字节,就会释放数据线,并且由接收器接收应答信号,应答信号为0表示有效应答(ACK);1表示无效应答,既接收数据不成功(NACK)

ACK:

一个时间区间:SCL高电平和SDA低电平持续时间超过4us

NACK:

一个时间区间:SCL高电平和SDA高电平持续时间超过4us

代码流程:

先释放数据线等待,再让SCL处于高电平时检测SDA的电平状态,赋予flag进入应答时间区间,结束以后拉低SCL等待

char IIC_ACK()
{
char flag;
sda = 1;//在时钟脉冲期间释放数据线
_nop_();
scl = 1;
_nop_();//等待检测应答信号
flag = sda;
_nop_();//应答时间区间
scl = 0;
_nop_();
return flag;

}

发送规格配置:

一次发送8位数据,高位开始发送,所以在读高位数据的时候需要(data & 0x80)按位计算知识点

发送时序分析:

上图只需要注意一点:

SDA转变电平状态的时候,SCL必须处于低电平

为什么?以为起始信号,终止信号和应答信号都是SCL位于高电平时做出的动作,发送数据低电平才能转变的目的时为了防止I2C总线提前退出工作

void IIC_Send_Byte(char dataSend)
{
int i;
for(i = 0;i<8;i++){//一次发送1位,循环8次发送8位
scl = 0;//scl拉低,让sda做好数据准备
sda = dataSend & 0x80;//1000 0000获得dataSend的最高位,给sda
_nop_();//发送数据建立时间
scl = 1;//scl拉高开始发送
_nop_();//数据发送时间
scl = 0;//发送完毕拉低
_nop_();//
dataSend = dataSend << 1;//数据左移一位,让下一个数据变为下一轮的高位
}
}

前面四步完成以后,即可向设备写入指令/数据

写入指令:

如上图:

Slave Address ->从机地址

  1. start

  1. 0111 10()()(后面两位为用户自己配置)**

看这一小块,后面两个位代表SA0和R/W#

SA0:1、0均可配置,一般用于区分两个不同设备 第一个括号姑且让它为0

R/W#:0代表配置为写入模式 第二个括号为0

2.设置为写入 0111 1000->0x78

  1. ACK

  1. control byte xx000000

co:“0”表示此后只接数据字节

D/C:“0”代表发送命令;“1”代表发送数据

发送命令:0000 0000 (0x00)

发送数据:0100 0000 (0x40)

  1. ACK

  1. 写入数据/命令

  1. ACK

  1. stop

写入指令代码:

void Oled_Write_Cmd(char dataCmd)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x00);
// 5. ACK
IIC_ACK();
//6. 写入指令/数据
IIC_Send_Byte(dataCmd);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}

写入数据代码:

void Oled_Write_Data(char dataData)
{
// 1. start()
IIC_Start();
//
// 2. 写入从机地址 b0111 1000 0x78
IIC_Send_Byte(0x78);
// 3. ACK
IIC_ACK();
// 4. cotrol byte: (0)(0)000000 写入命令 (0)(1)000000写入数据
IIC_Send_Byte(0x40);
// 5. ACK
IIC_ACK();
///6. 写入指令/数据
IIC_Send_Byte(dataData);
//7. ACK
IIC_ACK();
//8. STOP
IIC_Stop();
}

先在OLED上显示一个点:

初始化OLED、找位置、写入字符

初始化:

void Oled_Init(void){
Oled_Write_Cmd(0xAE);//--display off
Oled_Write_Cmd(0x00);//---set low column address
Oled_Write_Cmd(0x10);//---set high column address
Oled_Write_Cmd(0x40);//--set start line address
Oled_Write_Cmd(0xB0);//--set page address
Oled_Write_Cmd(0x81); // contract control
Oled_Write_Cmd(0xFF);//--128
Oled_Write_Cmd(0xA1);//set segment remap
Oled_Write_Cmd(0xA6);//--normal / reverse
Oled_Write_Cmd(0xA8);//--set multiplex ratio(1 to 64)
Oled_Write_Cmd(0x3F);//--1/32 duty
Oled_Write_Cmd(0xC8);//Com scan direction
Oled_Write_Cmd(0xD3);//-set display offset
Oled_Write_Cmd(0x00);//
Oled_Write_Cmd(0xD5);//set osc division
Oled_Write_Cmd(0x80);//
Oled_Write_Cmd(0xD8);//set area color mode off
Oled_Write_Cmd(0x05);//
Oled_Write_Cmd(0xD9);//Set Pre-Charge Period
Oled_Write_Cmd(0xF1);//
Oled_Write_Cmd(0xDA);//set com pin configuartion
Oled_Write_Cmd(0x12);//
Oled_Write_Cmd(0xDB);//set Vcomh
Oled_Write_Cmd(0x30);//
Oled_Write_Cmd(0x8D);//set charge pump enable
Oled_Write_Cmd(0x14);//
Oled_Write_Cmd(0xAF);//--turn on oled panel
}
//定义了这个函数之后直接在main函数中调用即可实现初始化

找位置:

怎么找?页寻址、水平寻址、垂直寻址

一般采用页寻址

  1. 发送cmd:0x20 Oled_Write_Cmd(0x20)

  1. 发送cmd:0x02 Oled_Write_Cmd(0x02)

选择到第0页:(一共8列)低八位控制

1011 0000(0xB0)

  1. Oled_Write_Cmd(0xB0)

  1. 选择到第几列,输入两次,都是配置低位,一共128列,最低为0x00,0x10;最高位0x0f,0x17

Oled_Write_Cmd(0x00)

Oled_Write_Cmd(0x10)//第0列

配置为页寻址

配置从第几页(下)的第几列(上)开始

写入字符:

例如:

Oled_Write_Data(0x08);

以上在屏幕上显示的东西因为时保存在内存里的,所以都会保留在屏幕上,所以需要一个清屏函数

清屏:

  1. 从page0-page7

  1. 每个page从第0列开始

  1. 0到127列,依次写0,每写入一次,列地址会自动偏移

代码如下:

void Oled_Clear()
{
unsigned char i,j; //256个数
for(i=0;i<8;i++){
Oled_Write_Cmd(0xB0 + i);//page0--page7
//每个page从0列
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
//0到127列,依次写入0,每写入数据,列地址自动偏移
for(j = 0;j<128;j++){
Oled_Write_Data(0);
}
}
}

显示一个字母:

运用自字模软件:

  1. 参数设置:

2.1字体设置:一般采用12

2.2其他选项:纵向取模,字节倒序,保留,任何时候都加0

  1. 输入要取模的字母,例如:A

  1. ctr+enter

  1. 取模方式:C51

/*-- 文字: A --*/
/*-- 宋体12; 此字体下对应的点阵为:宽x高=8x16 --*/
char A1[8] = {0x00,0x00,0xC0,0x38,0xE0,0x00,0x00,0x00};
char A2[8] = {0x20,0x3C,0x23,0x02,0x02,0x27,0x38,0x20};
//main函数中
int i;
//第0页
Oled_Write_Cmd(0xB0);
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
for(i = 0;i < 8;i++){
     Oled_Write_Data(A1[i]);

}
//第1页
Oled_Write_Cmd(0xB1);
Oled_Write_Cmd(0x00);
Oled_Write_Cmd(0x10);
for(i = 0;i < 8;i++){
     Oled_Write_Data(A2[i]);

}

宽*高=8*16,宽代表列,高代表页,意思是需要8列2页(一页是8)所以需要用两页来分开显示一个字母A

显示一段话:

取模步骤同上,注意规格

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

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

void IIC_Start()
{
    scl = 0;
    sda = 1;
    scl = 1;
    _nop_();
    sda = 0;
    _nop_();
}

void IIC_Stop()
{
    scl = 0;
    sda = 0;
    scl = 1;
    _nop_();
    sda = 1;
    _nop_();
}

char IIC_ACK()
{
    char flag;
    sda = 1;//就在时钟脉冲9期间释放数据线
    _nop_();
    scl = 1;
    _nop_();
    flag = sda;
    _nop_();
    scl = 0;
    _nop_();
    
    return flag;
}

void IIC_Send_Byte(char dataSend)
{
    int i;
    
    for(i = 0;i<8;i++){
        scl = 0;//scl拉低,让sda做好数据准备
        sda = dataSend & 0x80;//1000 0000获得dataSend的最高位,给sda
        _nop_();//发送数据建立时间
        scl = 1;//scl拉高开始发送
        _nop_();//数据发送时间
        scl = 0;//发送完毕拉低
        _nop_();//
        dataSend = dataSend << 1;
    }
}

void Oled_Write_Cmd(char dataCmd)
{
    //    1. start()
    IIC_Start();
    //        
    //    2. 写入从机地址  b0111 1000 0x78
    IIC_Send_Byte(0x78);
    //    3. ACK
    IIC_ACK();
    //    4. cotrol byte: (0)(0)000000 写入命令   (0)(1)000000写入数据
    IIC_Send_Byte(0x00);
    //    5. ACK
    IIC_ACK();
    //6. 写入指令/数据
    IIC_Send_Byte(dataCmd);
    //7. ACK
    IIC_ACK();
    //8. STOP
    IIC_Stop();
}

void Oled_Write_Data(char dataData)
{
    //    1. start()
    IIC_Start();
    //        
    //    2. 写入从机地址  b0111 1000 0x78
    IIC_Send_Byte(0x78);
    //    3. ACK
    IIC_ACK();
    //    4. cotrol byte: (0)(0)000000 写入命令   (0)(1)000000写入数据
    IIC_Send_Byte(0x40);
    //    5. ACK
    IIC_ACK();
    ///6. 写入指令/数据
    IIC_Send_Byte(dataData);
    //7. ACK
    IIC_ACK();
    //8. STOP
    IIC_Stop();
}


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

void Oled_Clear()
{
    unsigned char i,j; //-128 --- 127
    
    for(i=0;i<8;i++){
        Oled_Write_Cmd(0xB0 + i);//page0--page7
        //每个page从0列
        Oled_Write_Cmd(0x00);
        Oled_Write_Cmd(0x10);
        //0到127列,依次写入0,每写入数据,列地址自动偏移
        for(j = 0;j<128;j++){
            Oled_Write_Data(0);
        }
    }
}
/*--  文字:  麦  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
char m1[16] = {0x00,0x04,0x24,0x24,0x24,0x24,0x24,0xFF,0x24,0x24,0x24,0x24,0x24,0x04,0x00,0x00};
char m2[16] = {0x81,0x91,0x89,0x45,0x4F,0x55,0x25,0x25,0x25,0x55,0x4D,0x45,0x81,0x81,0x81,0x00};

/*--  文字:  仕  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char s1[16] = {0x00,0x80,0x60,0xF8,0x27,0x20,0x20,0x20,0x20,0xFF,0x20,0x20,0x20,0x20,0x20,0x00};
code char s2[16] = {0x01,0x00,0x00,0xFF,0x00,0x20,0x20,0x20,0x20,0x3F,0x20,0x20,0x20,0x20,0x00,0x00};

/*--  文字:  腾  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char t1[16] = {0x00,0xFE,0x22,0x22,0xFE,0x28,0xA9,0x6E,0x28,0x3F,0x28,0x6C,0xAB,0x28,0x20,0x00};
code char t2[16] = {0x80,0x7F,0x02,0x82,0xFF,0x01,0x20,0x2D,0x29,0x29,0x29,0x4F,0x88,0x79,0x01,0x00};

/*--  文字:  真  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char z1[16] = {0x00,0x04,0x04,0xF4,0x54,0x54,0x54,0x5F,0x54,0x54,0x54,0xF4,0x04,0x04,0x00,0x00};
code char z2[16] = {0x10,0x10,0x90,0x5F,0x35,0x15,0x15,0x15,0x15,0x15,0x35,0x5F,0x90,0x10,0x10,0x00};

/*--  文字:  T  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char T_1[16] = {0x18,0x08,0x08,0xF8,0x08,0x08,0x18,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
code char T_2[16] = {0x00,0x00,0x20,0x3F,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

/*--  文字:  M  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char M_1[16] = {0x08,0xF8,0xF8,0x00,0xF8,0xF8,0x08,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};
code char M_2[16] = {0x20,0x3F,0x01,0x3E,0x01,0x3F,0x20,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00};

/*--  文字:  帅  --*/
/*--  宋体12;  此字体下对应的点阵为:宽x高=16x16   --*/
code char S_1[16] = {0x00,0xFC,0x00,0x00,0xFF,0x00,0x00,0xF0,0x10,0x10,0xFF,0x10,0x10,0xF0,0x00,0x00};
code char S_2[16] = {0x00,0x87,0x40,0x30,0x0F,0x00,0x00,0x1F,0x00,0x00,0xFF,0x08,0x10,0x0F,0x00,0x00};



void main()
{
        unsigned char  i;
        //1. OLED初始化
        Oled_Init();
        
        //2. 选择一个位置
        //2.1 确认页寻址模式
        Oled_Write_Cmd(0x20);
        Oled_Write_Cmd(0x02);
        Oled_Clear();
        //2.2 选择PAGE0   1011 0000
        //                                0xB0
        Oled_Write_Cmd(0xB2);
        Oled_Write_Cmd(0x08);
        Oled_Write_Cmd(0x10);
      //3. 显示一个点
//    Oled_Write_Data(0x08);
//        Oled_Write_Data(0x08);
//        Oled_Write_Data(0x08);
//        Oled_Write_Data(0x08);
//        Oled_Write_Data(0x08);
        for(i=0;i<16;i++){
            
                Oled_Write_Data(m1[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(s1[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(t1[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(z1[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(T_1[i]);
        }
            for(i=0;i<16;i++){
            
                Oled_Write_Data(M_1[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(S_1[i]);
        }
        
        Oled_Write_Cmd(0xB3);
        Oled_Write_Cmd(0x08);
        Oled_Write_Cmd(0x10);

        for(i=0;i<16;i++){
            
                Oled_Write_Data(m2[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(s2[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(t2[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(z2[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(T_2[i]);
        }
            for(i=0;i<16;i++){
            
                Oled_Write_Data(M_2[i]);
        }
        for(i=0;i<16;i++){
            
                Oled_Write_Data(S_2[i]);
        }
        while(1);
}

显示一张图片:

待更新…

物联沃分享整理
物联沃-IOTWORD物联网 » 简明易懂:深入理解I2C总线协议

发表评论