单片机
返回首页

51单片机OLED12864 I2C接口使用教程

2020-08-06 来源:51hei

    现在能买到的OLED12864显示屏大多为SPI和I2C接口的,I2C通信协议只需要两条总线就可以进行通信,下面介绍一下如何用51单片机使用I2C接口的OLED12864。


       首先介绍一下I2C通信协议,I2C(Inter-Integrated Circuit)字面上的意思是集成电路之间,它其实是I2CBus简称,所以中文应该叫集成电路总线,它是一种串行通信总线,使用多主从架构,由飞利浦公司在1980年代为了让主板、嵌入式系统或手机用以连接低速周边设备而发展。I2C的正确读法为“I平方C”('I-squared-C')。

       I2C只使用两条双向漏极开路(Open Drain)(串行数据(SDA)及串行时钟频率(SCL))总线,且利用上拉电阻将两条总线的电位上拉。I2C允许相当大的工作电压范围,但典型的电压准位为+3.3V或+5V。

       I2C的参考设计使用一个7比特长度的地址空间但保留了16个地址,所以在一组总线最多可和112个节点通信[a]。常见的I2C总线依传输速率的不同而有不同的模式:标准模式(100 Kbit/s)、低速模式(10 Kbit/s),但时钟频率可被允许下降至零,这代表可以暂停通信。而新一代的I2C总线可以和更多的节点(支持10比特长度的地址空间)以更快的速率通信:快速模式(400 Kbit/s)、高速模式(3.4 Mbit/s)。

       我们在51单片机中使用I2C通信协议的时候,需要编写程序去模拟I2C总线的通信,详细的I2C通信协议的介绍可以参考:http://www.51hei.com/bbs/dpj-110328-1.html

       对于I2C通信协议,需要补充的一点是:在实际通信传输数据时,SCL总线拉高的时间只要大于1.5μs都能够正常传输数据。

       OLED12864的裸屏是由SSD1306驱动的,I2C接口的OLED12864模块对外一共有4个接口,从左到右分别是GND(接地)、VCC(电源正极,可加3.3V,也可加5V)、SCL(时钟总线)、SDA(数据总线):



       模块背面的IIC ADRESSSELECT表示该模块在I2C通信作为从机时的地址,当中间的脚用电阻和左边接起来时,地址为0x78,当和右边接起来时,地址为0x7A。



       SSD1306的I2C总线数据格式,可以看出,往OLED12864写数据时,先发送一个起始信号,接着发送从机地址,从机地址带有读写位(低电平为写),之后就可以发送指令或数据。在发送指令或数据之前,一般都需要发送一个控制字节,如图,控制字节的最高位为连续位(如果连续位为0,接下来发送的信息只能包含数据字节),次高位为数据/指令选择位(该位声明接下来发送的是数据还是指令,0为指令,1为数据),控制字节的低六位为0。可以在一个声明连续发送数据的控制字节后面跟上多个数据字节。


注释详细的51单片机源程序如下(IIC.c):

#include 'IIC.h'


void delay5us()               

{


}


void I2C_init()                                   //初始化

{

        SDA = 1;

        _nop_();

        SCL = 1;

        _nop_();                //空闲时,两条线均为高电平         

}


/*I2C通信起始信号*/

void I2C_start()

{

        SCL = 1;   //此时主机有总线控制权,先把SCL线拉高

        _nop_();   //稳定一下

        SDA = 1;   //把SDA线拉高,以便发出起始信号(不确定是否为高)

        delay5us();//通信协议规定延时大于4.7us

        SDA = 0;   //拉低SDA线,制造下降沿的起始信号

        delay5us();//通信协议规定延时大于4us

}

                                 

/*I2C通信终止信号*/

void I2C_stop()

{

        SDA = 0;   //拉低SDA线,以便发出终止信号

        _nop_();   //稳定一下

        SCL = 1;   //拉高SCL线

        delay5us();//通信协议规定延时大于4us

        SDA = 1;   //拉高SDA线

        delay5us();//通信协议规定延时大于4.7us

}


/*从机应答检测*/

bit Test_ack()

{

        SCL = 1;        

        //拉高SCL线,以便主机读取从机发出的应答或非应答信号

        delay5us();//通信协议规定延时大于4us

        if(SDA)           

        //主机读取的SDA线为高,说明从机发送了一个非应答信号

        {

                SCL = 0;/*接下来准备发送停止信号,所以让时钟总线SCL拉低,

                                让I2C_stop();函数中的SDA可变为0*/

                _nop_();//稳定总线

                I2C_stop();

                return 0;//结束检测从机应答函数

        }        

        else

        //主机读取的SDA线为低,说明从机发送了一个应答信号

        {

                SCL = 0;/*将时钟总线SCL拉低,此时SDA上数据的变化才有效,

                                因为接下来会继续发数据*/

                _nop_();//稳定总线

                return 1;

        }

}


/*I2C通信:发送一个字节*/

void I2C_send_byte(uint8_t byte)

{

        uint8_t i;//声明一个计数变量i

        for(i = 0; i < 8; i++)//一个字节有8位,发8次

        {

                SCL = 0;//拉低SCL,让数据总线SDA变化有效

                _nop_();//稳定总线

                if(byte & 0x80)//1000 0000 & byte

/*如果(要发送数据的当前传输位)byte的最高位为1,执行该if语句,

  如果(要发送数据的当前传输位)byte的最高位为0,不执行该if语句*/

                {

                        SDA = 1;   

                        //(当前传输位)byte的最高位为1,即把1放到SDA线上

                        _nop_();//稳定总线

                }        

                else

                //如果(当前发送位)byte的最高位为0(不为1),给SDA送0

                {

                        SDA = 0;

                        _nop_();//稳定总线

                }

                SCL = 1;//拉高SCL线,使从机能够从SDA线上读取到当前的数据

                _nop_();//稳定总线

                byte <<= 1;

/*使byte左移一位,即原来已被发送到SDA线上的最高位被移除,

第七位(还未发送的数据位)变成最高位变为下一次循环的当前发送位*/

        }

        SCL = 0;

        //发送完数据之后,将SCL拉低,以便从机改变SDA线,发出应答信号

        _nop_();//稳定总线

        SDA = 1;//释放总线控制权

        _nop_();//稳定总线

}



OLED_12864.c

#include 'OLED_12864.h'



void Delay300ms()

{


}



/*写指令函数,第一个参数为指令,第二、三个参数选择是否需要通信开始和结束函数,=1有,=0没有*/

bit OLED12864_Write_Commmand(uint8_t cmd, bit start, bit stop)

{

        if(start)

        {

                I2C_start();

                I2C_send_byte(OLED_12864_Address+0);//写从机地址,并且加上读写标志位(最后一位)

                if(!Test_ack())

                {

                        return 0;

                }

                /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        }

        I2C_send_byte(0x80 | 0x00 | 0x00);  //Co位为1(接下来要传指令),DC为0(接下来是指令)

        if(!Test_ack())

        {

                return 0;

        }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/


        I2C_send_byte(cmd);

        if(!Test_ack())

        {

                return 0;

        }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/


        if(stop)

                I2C_stop();


        return 1;

}


/*写连续数据函数,第一个参数为数据,第二个参数为发送连多少位连续的数据,第三、四个参数选择是否需要通信开始和结束函数,=1有,=0没有*/

bit OLED12864_Write_Continuous_Data(uint8_t* dat, uint8_t count, bit start, bit stop)

{

        uint8_t i = 0;//定义计数变量

        if(start)

        {

                I2C_start();

                I2C_send_byte(OLED_12864_Address+0);//写从机地址,并且加上读写标志位(最后一位)

                if(!Test_ack())

                {

                        return 0;

                }

                /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        }

        I2C_send_byte(0x00 | 0x40 | 0x00);  //Co位为0(接下来只传数据),DC为1(接下来是数据)

        if(!Test_ack())

        {

                return 0;

        }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        for(i = 0 ;i < count ;i++)

        {

                I2C_send_byte(*dat++);

                if(!Test_ack())

                {

                        return 0;

                }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        }


        if(stop)

                I2C_stop();


        return 1;

}



/*写相同的连续数据函数,第一个参数为数据,第二参数为发送的次数*/

bit OLED12864_Write_Same_Continuous_Data(uint8_t dat, uint8_t count)

{

        uint8_t i = 0;//定义计数变量

        I2C_start();

        I2C_send_byte(OLED_12864_Address+0);//写从机地址,并且加上读写标志位(最后一位)

        if(!Test_ack())

        {

                return 0;

        }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/


        I2C_send_byte(0x00 | 0x40 | 0x00);  //Co位为0(接下来只传数据),DC为1(接下来是数据)


        if(!Test_ack())

        {

                return 0;

        }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        for(i = 0 ;i < count ;i++ )

        {

                I2C_send_byte(dat);

                if(!Test_ack())

                {

                        return 0;

                }

        /*执行从机应答检测函数,如果从机发送了非应答信号,那么就退出数据发送函数*/

        }

        

        I2C_stop();


        return 1;

}


   

//Set the cursor position of start  

void OLED12864_SetPos(uint8_t x, uint8_t y)  

{   

    OLED12864_Write_Commmand(0xb0+y,1,0);  

    OLED12864_Write_Commmand(((x&0xf0)>>4)|0x10,0,0);  

    OLED12864_Write_Commmand((x&0x0f)|0x01,0,1);  

}  

  

//Fill screen wit data  

//0x00 is black  

//0xff is blue  

void OLED12864_Fill(uint8_t fill_Data)  

{  

    uint8_t m;

        //uint8_t n;  

    for(m=0;m<8;m++)  

    {  

        OLED12864_Write_Commmand(0xb0+m,1,0);       //page0-page1  

        OLED12864_Write_Commmand(0x00,0,0);     //low column start address  

        OLED12864_Write_Commmand(0x10,0,1);     //high column start address  

                OLED12864_Write_Same_Continuous_Data(fill_Data, 128);

    }  

}  

  

void OLED12864_CLS_LINE(uint8_t rowIndex)  

{  

    uint8_t n=0;  

    OLED12864_Write_Commmand(0xb0+rowIndex,1,0);        //page0-page1  

    OLED12864_Write_Commmand(0x00,0,0);     //low column start address  

    OLED12864_Write_Commmand(0x10,0,1);     //high column start address  

        OLED12864_Write_Same_Continuous_Data(0x00, 128);  

}  

  

//clear screen( fill screen with black)  

void OLED12864_CLS(void)  

{  

    OLED12864_Fill(0x00);  

}  

  

//--------------------------------------------------------------  

// wake up screen from hibernation  

//--------------------------------------------------------------  

void OLED12864_ON(void)  

{  

    OLED12864_Write_Commmand(0X8D,1,0);  //set charge  

    OLED12864_Write_Commmand(0X14,0,0);  //open charge  

    OLED12864_Write_Commmand(0XAF,0,1);  //OLED wake up  

}  

  

//--------------------------------------------------------------  

// Prototype      : void OLED12864_OFF(void)  

// Calls          :   

// Parameters     : none  

// Description    : ?OLED?? -- ?????,OLED????10uA  

//--------------------------------------------------------------  

void OLED12864_OFF(void)  

{  

    OLED12864_Write_Commmand(0X8D,1,0);  //set charge   

    OLED12864_Write_Commmand(0X10,0,0);  //close charge  

    OLED12864_Write_Commmand(0XAE,0,1);  //OLED hibernate  

}  


bit OLED12864_Initial()  

{  

    Delay300ms(); // very important delay  

      

    OLED12864_Write_Commmand(0xAE,1,0); //display off  

    OLED12864_Write_Commmand(0x20,0,0); //Set Memory Addressing Mode      

    OLED12864_Write_Commmand(0x10,0,0); //00,Horizontal Addressing Mode;01,Vertical Addressing Mode;10,Page Addressing Mode (RESET);11,Invalid  

    OLED12864_Write_Commmand(0xb0,0,0); //Set Page Start Address for Page Addressing Mode,0-7  

    OLED12864_Write_Commmand(0xc8,0,0); //Set COM Output Scan Direction  

    OLED12864_Write_Commmand(0x00,0,0); //---set low column address  

    OLED12864_Write_Commmand(0x10,0,0); //---set high column address  

    OLED12864_Write_Commmand(0x40,0,0); //--set start line address  

    OLED12864_Write_Commmand(0x81,0,0); //--set contrast control register  

    OLED12864_Write_Commmand(0xff,0,0); //???? 0x00~0xff  

    OLED12864_Write_Commmand(0xa1,0,0); //--set segment re-map 0 to 127  

    OLED12864_Write_Commmand(0xa6,0,0); //--set normal display  

    OLED12864_Write_Commmand(0xa8,0,0); //--set multiplex ratio(1 to 64)  

    OLED12864_Write_Commmand(0x3F,0,0); //  

    OLED12864_Write_Commmand(0xa4,0,0); //0xa4,Output follows RAM content;0xa5,Output ignores RAM content  

    OLED12864_Write_Commmand(0xd3,0,0); //-set display offset  

    OLED12864_Write_Commmand(0x00,0,0); //-not offset  

    OLED12864_Write_Commmand(0xd5,0,0); //--set display clock divide ratio/oscillator frequency  

    OLED12864_Write_Commmand(0xf0,0,0); //--set divide ratio  

    OLED12864_Write_Commmand(0xd9,0,0); //--set pre-charge period  

    OLED12864_Write_Commmand(0x22,0,0); //  

    OLED12864_Write_Commmand(0xda,0,0); //--set com pins hardware configuration  

    OLED12864_Write_Commmand(0x12,0,0);  

    OLED12864_Write_Commmand(0xdb,0,0); //--set vcomh  

    OLED12864_Write_Commmand(0x20,0,0); //0x20,0.77xVcc  

    OLED12864_Write_Commmand(0x8d,0,0); //--set DC-DC enable  

    OLED12864_Write_Commmand(0x14,0,0); //  

    OLED12864_Write_Commmand(0xaf,0,1); //--turn on oled panel  

      

    OLED12864_CLS();  

    return 1;  

}

  

//--------------------------------------------------------------  

// show string  

// x,y -- start position(x:0~127,column; y:0~7,row);   

// ch[] -- the string to show;   

// TextSize -- (1:6*8 ; 2:8*16)  

//OLED12864_ShowStr(0,3,'I2C Test',1);// 6*8  

//OLED12864_ShowStr(0,4,'Hello Delta',2) //8*16  

//--------------------------------------------------------------  

void OLED12864_ShowStr(uint8_t x, uint8_t y, uint8_t ch[], uint8_t TextSize)  

{  

    uint8_t c = 0,i = 0,j = 0;  

    switch(TextSize)  

    {  

        case 1:  

        {  

            while(ch[j] != '' && ch[j] !='n')  

            {  

                c = ch[j] - 32;  

                if(x > 126)  

                {  

                    x = 0;  

                    y++;  

                }  

                OLED12864_SetPos(x,y);  

                /*for(i=0;i<6;i++)  

                    OLED12864_Write_Data(F6x8[c][i],1,1);*/

                                OLED12864_Write_Continuous_Data(&F6x8[c][0],6,1,1);//将该位的指针传给函数,并且写连续的6个数据即可  

                x += 6;  

                j++;  

            }  

        }break;  

        case 2:  

        {  

            while(ch[j] != ''  && ch[j] !='n')  

            {  

                c = ch[j] - 32;  

                if(x > 120)  

                {  

                    x = 0;  

                    y++;  

                }  

                OLED12864_SetPos(x,y);  

                /*for(i=0;i<8;i++)  

                   OLED12864_Write_Data(F8X16[c*16+i],1,1);  

                OLED12864_SetPos(x,y+1);  

                for(i=0;i<8;i++)  

                    OLED12864_Write_Data(F8X16[c*16+i+8],1,1);*/

                                OLED12864_Write_Continuous_Data(&F8X16[c*16],8,1,1);//将该位的指针传给函数,并且写连续的8个数据即可

                                OLED12864_SetPos(x,y+1);

                                OLED12864_Write_Continuous_Data(&F8X16[c*16+8],8,1,1);//将该位的指针传给函数,并且写连续的8个数据即可  

                x += 8;  

                j++;  

            }  

        }break;  

    }  

}


uint8_t rowNum = 0;

  

//--------------------------------------------------------------  

// x,y -- start position(x:0~127,column; y:0~7,row);   

// the N is the chinese index of F16x16 in codetab  

/*

for(i=0;i<5;i++)

{

    OLED12864_ShowCN(22+i*16,0,i);

}

……………………


进入单片机查看更多内容>>
相关视频
  • RISC-V嵌入式系统开发

  • SOC系统级芯片设计实验

  • 云龙51单片机实训视频教程(王云,字幕版)

  • 2022 Digi-Key KOL 系列: 你见过1GHz主频的单片机吗?Teensy 4.1开发板介绍

  • TI 新一代 C2000™ 微控制器:全方位助力伺服及马达驱动应用

  • MSP430电容触摸技术 - 防水Demo演示

精选电路图
  • PIC单片机控制的遥控防盗报警器电路

  • 红外线探测报警器

  • 使用ESP8266从NTP服务器获取时间并在OLED显示器上显示

  • 用NE555制作定时器

  • RS-485基础知识:处理空闲总线条件的两种常见方法

  • 基于ICL296的大电流开关稳压器电源电路

    相关电子头条文章