嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议

一、概述

 IIC(Inter-Integrated Circuit) 集成电路总线

IIC串行总线一般有两跟信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线SDA上,各设备的时钟线SCL接到SCL上。对于并联在一条总线上的每一个IC都有唯一的地址。

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第1张图片

二、IIC协议

IIC在传输数据的过程中有三种类型的信号 ,分别为:开始信号结束信号和应答信号

(1)起始信号终止信号

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第2张图片

 注意这里只有当SCL为高电平时,当遇到下降沿,则为起始信号,当为上升沿为终止信号

 SDA只在SCL为低电平时修改数据

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_();
}

 (2)应答信号

发送方每发送一个字节(8个bit),就在时钟脉冲9期间释放数据线,由接收器反馈一个应答信号,应答信号为低电平时,规定为有效应答(ACK,简称应答位),表示接收器已经成功接收了该字节;应答信号为高电平时,规定为非NACK),表示没有成功收到

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第3张图片

char  IIC_ACK()
{
	char flag;
	sda =1;//在时钟脉冲9期间,将sda拉高,释放数据线,接收器反馈一个应答信号
	_nop_();
	scl = 1;
	_nop_();
    flag = sda;//读反馈回来的信息
	_nop_();
	scl = 0;
	_nop_();
	return flag;
}

(3)发送数据 

IIC在SCL的每一个低电平上改变SDA的值,并当DCL为高电平时传输数据

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第4张图片

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

三、IIC的应用——oled液晶显示

(1)自己对于oled的简单理解

   我的oled是128*64(像素)大小的,也就是说有这么多个“点”,给每个点写0,就是灭的,写1就是亮的。它是64行128列,为了方便管理,将每8行分为一页,共八页,也就是说每页是8行,128列。

关于oled这篇文章讲的不错:https://www.yii666.com/blog/389047.html

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第5张图片

 (2)oled刷新的三种模式

     页地址模式、水平地址模式还有垂直地址模式 其实差别在于显示完本位置后,下一位要在哪显示的问题。

    页地址模式

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第6张图片

         每一页一列一列刷新(每列8bit),刷新完一行继续从下一行开始刷新,默认是这种方式,刷新每一页时列自动加一,但是刷新下一页时需要自己重新设定列,否则会从上次刷新的列处继续向后刷新。

        在页模式中,显示RAM读写完后,列地址指针自动加一。如果列地址指针到达了列地址尾部,列地址指针重新回到列地址开始的地方,但是页地址指针不变。用户要设置新的页指针和列指针来获取下一页RAM的内容。

   水平地址模式

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第7张图片

        在水平地址中,显示RAM读写完后,列地址指针自动加一。如果列地址指针到达了列地址尾部,列地址指针重新回到列开始地址,同时页地址指针也加一。而PAGE和列地址指针扫描每页地址的模型在下面。当列地址指针和页指针都到达末尾时,两个指针会调回到列地址和页地址指针开始的位置(图中虚线所示)。

页地址模式

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第8张图片

  在垂直地址中,显示RAM读写完后,页地址指针自动加一。如果页地址指针到达了页地址尾部,页地址指针重新回到页开始地址,同时列地址指针也加一。而PAGE和列地址指针扫描地址的模型在下面。当列地址指针和页指针都到达末尾时,两个指针会调回到列地址和页地址指针开始的位置(图中虚线所示)。

   嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第9张图片

 oled使用的是IIC协议,其通信过程如图:若要写一个命令给主机的话,步骤如下:(可以查看oled的手册,信息更详细)

    1.strat
    2.写从机地址Slave Address 01111000

      0:红色标注的0,用于区分不同的从机,I2C一个主机可以挂载多个从机

      0:绿色标注的0,用于判断接下来是读还是写
    3.ACK
    4.Control byte  共8bit一字节, 7bit:Co,6 bit :D/C,  其余位为0
     (0)(0)0000 写入命令,(0)(1)0000写入数据 */
    5.ACK
    6.写入命令
    7.ACK

    8.Stop

  显示数据,则需要根据实际情况和需要来显示了。每一次传输的数据和显示的数据都只能是8位,因此,如果要显示16x8(16行,8列)的数据,则必须要跨页显示,所以要定位到对应的位置来显示对应的数字。同样,如果需要显示图片,那么只需要使用取模软件来得到其16进制显示即可。但要注意取模的方式:从上到下8位(上面为低位,下面为高位)为一个字节,从左往右递增。  

void Oled_Write_Cmd(char cmd)
{
    //1.strat
	IIC_Start();
	//2.写从机地址Slave Address 01111000
	IIC_Send_Byte(0x78);
	//3.ACK
	IIC_ACK();
	/*4.Control byte  共8bit一字节,7bit:Co,6bit :D/C,其余位为0
	 (0)(0)0000 写入命令,(0)(1)0000写入数据 */
	IIC_Send_Byte(0x00);//(0)(0)000000
	//5.ACK
	IIC_ACK();
	//6.写入命令
	IIC_Send_Byte(cmd);
	//7.ACK
	IIC_ACK();
	//8.Stop
	IIC_Stop();
}

写数据同理,只需要修改一下 IIC_Send_Byte(0x40);

 (1)oled显示一个点

要在oled上显示一个点,我们需要初始化它,这里就需要用到我们前面写的IIC代码了,查看手册,我们需要一下的初始化,有兴趣的可以仔细看看

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 (多字节指令) --128 
	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)send 0x30
	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)
}

   初始化完了,要显示一个点,首先我们先选择一页,然后确定是哪一列

  相关的寄存器如下

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第10张图片

 比如要在第一页发送一个点,则写入 Oled_Write_Cmd(0xB0);即可

   配置列的寄存器

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第11张图片

 配置列需要设置两次,高位和低位Oled_Write_Cmd(0x00);

                                                      Oled_Write_Cmd(0x10);

                         //设置从第0列开始,注意这里是16进制,设置时要注意转换

接下来就显示一个点吧:注意这里没有学到清屏,刚开始打开很有可能有很多雪花,下面学到清屏就可以解决了,代码中还设置了寻址模式,默认为页寻址,可以不设置,需要时可以查看数据手册中相关寄存器的配置。

void main()
{
	//1.OLED初始化
	Oled_Init();
	//2.选择一个位置
	//2.1确认页寻址模式
	Oled_Write_Cmd(0x20);
	Oled_Write_Cmd(0x02);
	//2.2选择PAGE0  1011 0000
	//选择从那一页开始
	Oled_Write_Cmd(0xB0);
	Oled_Write_Cmd(0x00);//显示在某一列
	Oled_Write_Cmd(0x10);
	
	Oled_Write_Data(0x80);//显示一个点

	while(1);//死循环防止程序退出看不到结果
}

(2)oled清屏 

上面学习过了如何显示一个点,清屏就很简单了,其实就是扫描屏幕上的每一个点然后将其置为0

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

(3)oled显示字符‘A’ 

显示一个字符‘A’,我们需要借助液晶显示字模工具,如下

打开软件, 选择 参数设置—>其他选项

按照如下方式设置

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第12张图片

 嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第13张图片

设置完后继续 参数设置—>文字输入区字体选择 ,选择想要的字体大小,这里我们选12号(宽*高=8*16),后期方便显示 

选择完成后在右下方输入字符,并按"回车+Ctrl",就会出现字符A的点阵图,如下

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第14张图片

 之后选择 取模方式—>C51格式 下方出现字符对应点阵,复制即可使用

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第15张图片 该字符 宽*高=8*16,正好占两页,;有8列

/*--  文字:  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};


void main()
{
    int i;
	
	Oled_Init();         //1.OLED初始化
	Oled_Clear();        //2.清屏
	Oled_Write_Cmd(0x20);//3.选择一个位置,确认页寻址模式
	Oled_Write_Cmd(0x02);

	Oled_Write_Cmd(0xB0);//4.选择从哪一页开始
	Oled_Write_Cmd(0x00);//5.选择从哪一行开始
	Oled_Write_Cmd(0x10);
	
	for(i=0;i<8;i++){ 
		Oled_Write_Data(A1[i]);
    }
	Oled_Write_Cmd(0xB1);//4.选择从哪一页开始
	Oled_Write_Cmd(0x00);//5.选择从哪一行开始
	Oled_Write_Cmd(0x10);	
	for(i=0;i<8;i++){ 
		Oled_Write_Data(A2[i]);
    }

	while(1);//死循环防止程序退出看不到结果
}

  (4) oled显示一行字符  

上面显示了一个字符,那么显示一行字符其实也不难了,在字模软件中找到对应字的点阵,封装成数组,写到oled中即可,这里就不展示了

(5)oled显示图片 

oled显示128*64像素的图片,这里可以打开电脑画板选择大小后自己画,也可以自己找一张图,但是图片一定要时bmp格式的

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第16张图片   嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第17张图片

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第18张图片

        在字模软件中选择打开图像图标选中即可

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第19张图片

      选择 取模方式—>C5格式,生成点阵

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第20张图片

 这里一共生成了128*8个8bit的16进制数,将它存入数组中,依次显示,这里显示和电脑清屏类似,不过写的不是零,而是数组中的数。

 这里数组过于庞大,就不显示代码了,截图看一下:

嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第21张图片

注意这里定义数组时前面要加code:以前也用到过,但是没有细查,后来找了几篇文章看了看,其实就是数组太大了,给它重新找个地方放着。

https://mp.csdn.net/mp_blog/creation/editor/132368516?spm=1000.2115.3001.5352

void Show_Bmp()
{
	char i;
	int j;
	for( i= 0;i<8;i++){
		Oled_Write_Cmd( 0xB0 + i );//选择从哪一页开始
		Oled_Write_Cmd(0x00);//选择从哪一列开始
		Oled_Write_Cmd(0x10);
		for(j=128*i;j<128*(i+1);j++){
			Oled_Write_Data(bmp[j]);
		}
	}
}

主函数也很简单,只是调用一下: 

void main()
{

	Oled_Init();         //1.OLED初始化
	Oled_Clear();        //2.清屏
	Oled_Write_Cmd(0x20);//3.选择一个位置,确认页寻址模式
	Oled_Write_Cmd(0x02);
	
    Show_Bmp();

	while(1);//死循环防止程序退出看不到结果
}

 成果展示一下吧:

                                      嵌入式】基于STC89C52RC的51单片机学习(十二)——IIC协议_第22张图片

你可能感兴趣的:(51单片机,学习,嵌入式硬件)