嵌入式Linux——IIC总线驱动(1):2440裸板驱动OLED

2019-07-12 21:10发布

简介:本文主要介绍在裸板上使用IIC总线驱动OLED 屏幕来显示你想显示的东西。而本文大致分为两部分,第一部分主要做OLED 屏幕硬件方面的介绍以使大家对OLED 屏幕有一个更好的了解。而第二部分,我将结合裸机的程序来介绍如何将你想要显示的东西显示到OLED 屏幕上。 所用开发板:JZ2440 V3 所用OLED 屏幕:中景园电子0.96 寸OLED 显示屏12864液晶屏模块 所用OLED 驱动芯片:SSD1306     在写这篇文章之前,我先看了韦东山老师IIC裸机的视频,然而我用的开发板是JZ2440,而老师讲课用的是MINI2440。而在JZ2440板子上没有接相应的IIC驱动的外设。并且我这里也没有老师上课时用的AT24c08存储芯片。所以我就想修改老师的IIC的程序来使OLED 屏幕显示。并且老师在写AT24c08时已经将IIC协议的程序写好了,所以我只是做了与OLED 屏幕硬件相关的修改就可以将厂家提供的程序运用到本开发板了。这中间我也犯过一些错误,所以现在将这个文章写出,希望可以帮到其他想用IIC驱动的OLED 屏幕的人。 下面我们开始第一部分:硬件介绍         讲解硬件的目的是介绍OLED模块的接线方法以及通过OLED驱动模块了解IIC程序编写的方法。         我们所使用的OLED模块为: 从图中可知我们的模块有四个引脚分别为:     1. GND 电源地     2. VCC 电源正(3~5.5V)     3. SCL OLED 的D0 脚,在IIC 通信中为时钟管脚     4. SDA OLED 的D1 脚,在IIC 通信中为数据管脚 而他对应的电路图为: 从图中见BS0,BS2接地,而BS1接高电平,所以如下图我们直接选择了IIC接口模式 上面的介绍我们对OLED模块有了大致的认识,或者知道该OLED的引脚对应的是什么。而如何将他接到开发板上哪?那我们就要看开发板的电路图了。 而图中用红 {MOD}边框圈起的部分对应的就是OLED模块要连接的引脚了。而对应到真实的开发板上,他的引脚为: 该图片引自:oled ssd1306 for jz2440(linux i2c driver) 下面我们进入OLED驱动芯片中了解通过该芯片如何驱动OLED模块。而本OLED使用的驱动芯片为:SSD1306。 由于厂家已将芯片封装连接好,并帮我们将引脚线引出,所以我们要做的就是了解如何操作这个芯片进而来操作OLED显示屏。下面我将比较重要的信息翻译下来: 1.  IIC总线接口     IIC通信接口包括:从机地址位SA0,IIC总系数据信号SDA(D1)和IIC时钟信号SCL(D0)。而数据信号线和时钟信号线要接上拉电阻。而RES#引脚用于初始化设备。 a)      从机地址位(SA0)         SSD1306在通过IIC总线收发任何信息前要识别出从机地址。而设备将回复从机地址通过以下方式设置从机地址位(SA0)和读写选择位(R/W#)。        b7 b6 b5 b4 b3 b2 b1 b0 0 1 1 1 1 0 SA0 R/W#
       SA0位作为从机地址的扩展位,即通过设置SA0来选择“0111100” 或者 “0111101”作为SSD1306的从机地址而D/C#引脚作为SA0的从机地址选择引脚。        而R/W#位可以用作IIC总线接口的操作模式的选择。当R/W#为高电平时,为读模式。而当R/W#为低电平时,为写模式 b)      IIC总线数据引脚(SDA)        SDA在通信中作为收发双方的传输通道。数据和应答信号通过SDA线进行传输。        需要注意的是ITO轨道电阻和在SDA引脚的上拉电阻变成电压保护分压器。因此在SDA线上应答可能将不会拉低到逻辑0。        而SDAIN和SDAout会捆绑在一起作为SDA。SDAIN一定要作为SDA而SDAout却可以不用接到SDAIN作SDA。而当SDAout没有做SDA时,IIC总线的应答信号将被忽略。 c)     IIC总线时钟信号(SCL)         在IIC总线上信息按着SCL信号传递。每一个传递的数据位对应SCL中一个时钟周期   2.  IIC总线写数据           IIC总线接口提供写数据和命令的权限。请参照下图:在时间顺序下的写模式图 注意:     Co  表示连续的位     D/C#表示数据/命令选择位     ACK 表示应答位     SA0 表示从机地址位     R/W#表示读写选择位     S    表示开始信号     P    表示结束信号 3.  IIC总线写模式 传输完从机地址后,一个控制字节或者一个数据字节应该通过SDA送达。一个控制字节主要包括Co位,D/C#位以及他后面的六个0.     a.     如果Co位设置为逻辑0,接下来传输的字节只能包含数据位。     b.     而D/C#位决定了传输的下一位是命令还是数据。如果D/C#位设为逻辑0,他表示接下来的数据为命令。而如果D/C#位设为逻辑1,他表示接下来的数据为数据同时会将其存储到GDDRAM寄存器中。而GDDRAM寄存器的列地址指针将会自动加一在写完一位后。 4.  命令解码器 这个模块决定输入的数据是解码为数据还是命令。而输入数据的解码       取决于D/C#引脚的值。 如果D/C#引脚为高电平,D[7:0]将解码为数据而后被写入图显数据存储(GDDRAM)寄存器。如果D/C#引脚为低电平,D[7:0]将解码为命令而后被写入相关的命令寄存器。 上面就是我翻译的SSD1306中比较重要的部分,我们会按着2中图上提供的方法写程序。但是程序中我们要用到的关于OLED设置的命令就要读者自己去阅读和翻译了。而我翻译的SSD1306部分命令 中有部分命令希望对你有帮助(由于个人能力有限不能翻译全部)。 上面就是我要讲解的关于OLED模块的硬件部分,我想我讲完后大家应该知道怎么去将OLED模块接到JZ2440开发板上,并知道按着什么方式去编写IIC程序。   下面我们讲解本文的第二部分,通过分析代码了解裸机IIC总线,并让OLED显示我们想要的东西。         而在分析代码前我们需要了解JZ2440开发板中集成的IIC控制器。而详细的关于开发板中IIC控制器的介绍我在文章:S3C2440A 第二十章IIC总线接口 中已经做了介绍,你可以进去了解一下。而下面我将直接用里面的东西。           下面我们从main函数讲起,而在其前面的一些关于开发板的其他初始化函数,如看门狗,nand,串口等。我就不做介绍了,因为我想看过韦东山老师视频的人都有,如果你想要看可以从连接:2440裸机驱动OLED 中查看相应的代码,或者可以联系我。         现在我们开始讲解代码,在main函数中我们的第一条语句就是开发板IIC总线初始化,他的详细代码为: /* * I2C初始化 */ void i2c_init(void) { GPEUP |= 0xc000; // 禁止内部上拉 GPECON |= 0xa0000000; // 选择引脚功能:GPE15:IICSDA, GPE14:IICSCL INTMSK &= ~(BIT_IIC); /* bit[7] = 1, 使能ACK * bit[6] = 0, IICCLK = PCLK/16 * bit[5] = 1, 使能中断 * bit[3:0] = 0xf, Tx clock = IICCLK/16 * PCLK = 50MHz, IICCLK = 3.125MHz, Tx Clock = 0.195MHz */ IICCON = (1<<7) | (0<<6) | (1<<5) | (0xf); // 0xaf IICADD = 0x3c; // S3C24xx slave address = [7:1] IICSTAT = 0x10; // I2C串行输出使能(Rx/Tx) }         从2440开发板的电路图上:         我们看到IIC的SCL和SDA引脚是与GPE14和GPE15复用的,所以在初始化开发板之前我们要先设置其引脚为SCL和SDA。而后我们将开启IIC所对应的中断并设置IIC总线的相关参数。最后使能IIC总线,等待数据传输。         初始化完开发板的IIC使其可以收发数据,那么我们接下来要做的就是初始化OLED模块,而更确切的就是初始化SSD1306芯片。 /******************************************** * init_oled * 初始化OLED 屏幕 ********************************************/ void init_oled(unsigned int addr) { unsigned char data[] ={0xAE,0x00,0x10,0x40,0xB0,0x81,0xFF,0xA1,0xA6,0xA8, 0x3F,0xC8,0xD3,0x00,0xD5,0x80,0xD8,0x05,0xD9,0xF1,0xDA,0x12,0xDB,0x30,0x8D,0x14,0xAF}; int len,i; len = sizeof(data)/sizeof(data[0]); printf(" len=%d ",len); for(i=0;i         而在上面程序中我们所做的就是按照SSD1306芯片所提供的的方法将相应的命令写入到芯片中,以实现对OLED的初始化。而具体的命令在数组data中。而我们是通过函数oled_write_1bit_cmd将每个命令依次发出的。而oled_write_1bit_cmd函数的实现为: /******************************************** * oled_write_1bit_cmd * 写一字节命令 ********************************************/ void oled_write_1bit_cmd(unsigned int addr,unsigned char data) { unsigned char cmd[2]; cmd[0] = 0x00; cmd[1] = data; i2c_write(addr, cmd, 2); }         很多人看完这个代码可能会问为什么不直接将数据发出而要在其前面加一个0x00参数那?这是由于SSD1306芯片规定的,我们看下图:         在上图中我们可以看到两个用红 {MOD}框框起来的字节。而上面一个介绍的是从机地址,而下面一个就是我们现在要讲的控制字节。我们的SSD1306就是通过这个字节的数据来区分我们下一个数据发送的是命令还是数据。当D/C#为0时,此时控制字节位0x00表示发送的是命令。而当D/C#为1时,此时控制字节位0x40表示发送的是数据。所以我们可以通过上面了解到为什么要在命令前加一个0x00了吧。而相应的我们的发数据函数就是在其前面加一个0x40的控制字节。所以写数据的函数为: /******************************************** * oled_write_1bit_dat * 写一字节数据 ********************************************/ void oled_write_1bit_dat(unsigned int addr,unsigned char data) { unsigned char dat[2]; dat[0] = 0x40; dat[1] = data; i2c_write(addr, dat, 2); }         而上面两个函数中都调用的i2c_write函数就是韦东山老师所写的函数,我是在这个基础上实现的对OLED的控制。而在分析这个函数之前我们要先引入一张图,我们根据图来分析这个函数。         上面这个图为2440主机发送模式流程图,也就是写模式的流程图。而我们就是要按照这个流程图来分析i2c_write函数,而下面我们就分析这个函数: /* * 主机发送 * slvAddr : 从机地址,buf : 数据存放的缓冲区,len : 数据长度 */ void i2c_write(unsigned int slvAddr, unsigned char *buf, int len) { g_tS3C24xx_I2C.Mode = WRDATA; // 写操作 g_tS3C24xx_I2C.Pt = 0; // 索引值初始为0 g_tS3C24xx_I2C.pData = buf; // 保存缓冲区地址 g_tS3C24xx_I2C.DataCount = len; // 传输长度 IICDS = slvAddr; IICSTAT = 0xf0; // 主机发送,启动 /* 等待直至数据传输完毕 */ while (g_tS3C24xx_I2C.DataCount != -1); }         上面程序的第一个参数就是从机地址,而由前我们可以知道从机地址取决于SA0引脚,而在本芯片中SA0接地,所以从机地址为0x78.         而上面代码主要完成了流程图的前三步,即 1. 设置主机传送模式 2. 将从机地址写入到IICDS寄存器中 3. 写0xf0到IICSTAT寄存器中         而后2440会进入到中断中对数据进行传输,代码为:             if((g_tS3C24xx_I2C.DataCount--) == 0) /* 看是否还有要传输的数据 ,如果没有进行如下操作*/ { // 下面两行用来恢复I2C操作,发出P信号 IICSTAT = 0xd0; IICCON = 0xaf; Delay(10000); // 等待一段时间以便P信号已经发出 break; } /* 如果有要发送的数据,继续发送 */     printf(" data=0x%2x ",g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt]); IICDS = g_tS3C24xx_I2C.pData[g_tS3C24xx_I2C.Pt++]; // 将数据写入IICDS后,需要一段时间才能出现在SDA线上 for (i = 0; i < 10; i++); IICCON = 0xaf; // 恢复I2C传输         而上面的两段代码就可以将流程图的步骤走遍了。同时通过流程图和上面的代码我们知道了如何将一个命令或者数据写入到IIC总线中,然后通过主机发送到从机。         而完成了上面的操作我们就可以实现对OLED模块的操作了。而我们主要是显示一张图片/****************************************** * picture * 用来显示一个图片 ******************************************/ void Picture(unsigned int addr) { unsigned char x,y; unsigned int i=0; for(y=0;y<8;y++) { oled_write_1bit_cmd(addr,(0xb0+y)); //page0-page7 oled_write_1bit_cmd(addr,(0x00)); //low column start address oled_write_1bit_cmd(addr,(0x10)); //high column start address for(x=0;x<128;x++) { int len; len = sizeof(show)/sizeof(show[0]); oled_write_1bit_dat(addr,show[x+128*y]); } } }         而上图就是显示一张图片的代码,而在显示之前我们要先定义好开始显示图片的指针位置,而前面的三个命令: oled_write_1bit_cmd(addr,(0xb0+y)); //page0-page7 oled_write_1bit_cmd(addr,(0x00)); //low column start address oled_write_1bit_cmd(addr,(0x10)); //high column start address         就是定义每个页的开始地址指针(一共8页)而第二个命令定义的是开始地址的低字节而第三个命令定义的是开始地址的高字节。而如果想更好的了解这方面的知识就要去看SSD1306的数据手册了解更多的命令。而定义好开始指针之后就要将我们要写入OLED的数据一行一行的写入数据存储的寄存器了。而后这些数据将会显示到屏幕上。而程序中的show就是用于存放照片的数组。 其效果图为:
好了,关于IIC裸机驱动OLED就讲到这里。而后我们会更深入的讲解内核中IIC驱动程序的编写。