IIC(Inter-Integrated Circuit) 集成电路总线
IIC串行总线一般有两跟信号线,一根是双向的数据线SDA,另一根是时钟线SCL,其时钟信号由主控器件产生。所有接到IIC总线设备上的串行数据SDA都接到总线SDA上,各设备的时钟线SCL接到SCL上。对于并联在一条总线上的每一个IC都有唯一的地址。
IIC在传输数据的过程中有三种类型的信号 ,分别为:开始信号、结束信号和应答信号。
(1)起始信号和终止信号:
注意这里只有当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),表示没有成功收到
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为高电平时传输数据
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; } }
(1)自己对于oled的简单理解:
我的oled是128*64(像素)大小的,也就是说有这么多个“点”,给每个点写0,就是灭的,写1就是亮的。它是64行128列,为了方便管理,将每8行分为一页,共八页,也就是说每页是8行,128列。
关于oled这篇文章讲的不错:https://www.yii666.com/blog/389047.html
(2)oled刷新的三种模式
页地址模式、水平地址模式还有垂直地址模式 其实差别在于显示完本位置后,下一位要在哪显示的问题。
页地址模式
每一页一列一列刷新(每列8bit),刷新完一行继续从下一行开始刷新,默认是这种方式,刷新每一页时列自动加一,但是刷新下一页时需要自己重新设定列,否则会从上次刷新的列处继续向后刷新。
在页模式中,显示RAM读写完后,列地址指针自动加一。如果列地址指针到达了列地址尾部,列地址指针重新回到列地址开始的地方,但是页地址指针不变。用户要设置新的页指针和列指针来获取下一页RAM的内容。
水平地址模式
在水平地址中,显示RAM读写完后,列地址指针自动加一。如果列地址指针到达了列地址尾部,列地址指针重新回到列开始地址,同时页地址指针也加一。而PAGE和列地址指针扫描每页地址的模型在下面。当列地址指针和页指针都到达末尾时,两个指针会调回到列地址和页地址指针开始的位置(图中虚线所示)。
页地址模式
在垂直地址中,显示RAM读写完后,页地址指针自动加一。如果页地址指针到达了页地址尾部,页地址指针重新回到页开始地址,同时列地址指针也加一。而PAGE和列地址指针扫描地址的模型在下面。当列地址指针和页指针都到达末尾时,两个指针会调回到列地址和页地址指针开始的位置(图中虚线所示)。
oled使用的是IIC协议,其通信过程如图:若要写一个命令给主机的话,步骤如下:(可以查看oled的手册,信息更详细)
1.strat
2.写从机地址Slave Address 011110000:红色标注的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.ACK8.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) }
初始化完了,要显示一个点,首先我们先选择一页,然后确定是哪一列
相关的寄存器如下
比如要在第一页发送一个点,则写入 Oled_Write_Cmd(0xB0);即可
配置列的寄存器
配置列需要设置两次,高位和低位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’,我们需要借助液晶显示字模工具,如下
打开软件, 选择 参数设置—>其他选项
按照如下方式设置
设置完后继续 参数设置—>文字输入区字体选择 ,选择想要的字体大小,这里我们选12号(宽*高=8*16),后期方便显示
选择完成后在右下方输入字符,并按"回车+Ctrl",就会出现字符A的点阵图,如下
之后选择 取模方式—>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}; 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格式的
在字模软件中选择打开图像图标选中即可
选择 取模方式—>C5格式,生成点阵
这里一共生成了128*8个8bit的16进制数,将它存入数组中,依次显示,这里显示和电脑清屏类似,不过写的不是零,而是数组中的数。
这里数组过于庞大,就不显示代码了,截图看一下:
注意这里定义数组时前面要加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);//死循环防止程序退出看不到结果 }
成果展示一下吧: