I2C驱动0.96寸OLED屏幕, STC32位8051
I2C驱动0.96寸OLED屏幕, STC32位8051I2C驱动0.96寸OLED屏幕, STC32位8051
首先我们来看下IIC协议:
1. IIC简介IIC(Inter-Integrated Circuit)总线是一种由NXP(原PHILIPS)公司开发的两线式串行总线,用于连接微控制器及其外围设备。多用于主控制器和从器件间的主从通信,在小数据量场合使用,传输距离短,任意时刻只能有一个主机等特性。在 CPU 与被控 IC 之间、IC 与 IC 之间进行双向传送,高速 IIC 总线一般可达 3.4Mbit/s 以上。
1.IIC总线支持任何IC生产过程NMOS,CMOS;2.双极性两线:串行数据SDA 和串行时钟SCL 线在连接到总线的器件间传递信息;3.每个器件都有一个唯一的地址识别;4.而且都可以作为一个发送器或接收器由器件的功能决定;5.连接到总线的接口数量只由总线电容是400pF的限制决定;2. IIC的硬件电路
1.SCL和SDA都需要接上拉电阻(大小由速度和容性负载决定一般在3.3K-10K之间) 保证数据的稳定性,减少干扰。2.IIC是半双工,而不是全双工,同一时间只可以单向通信3.为了避免总线信号的混乱,要求各设备连接到总线的输出端时必须是漏极开路(OD)输出或集电极开路(OC)输出。
IIC的高阻态(加上拉电阻的时候):漏极开路(Open Drain)即高阻状态,适用于输入/输出,其可独立输入/输出低电平和高阻状态,若需要产生高电平,则需使用外部上拉电阻高阻状态:高阻状态是三态门电路的一种状态。逻辑门的输出除有高、低电平两种状态外,还有第三种状态——高阻状态的门电路。电路分析时高阻态可做开路理解。
我们知道IIC的所有设备是接在一根总线上的,那么我们进行通信的时候往往只是几个设备进行通信,那么这时候其余的空闲设备可能会受到总线干扰,或者干扰到总线,怎么办呢?为了避免总线信号的混乱,IIC的空闲状态只能有外部上拉,而此时空闲设备被拉到了高阻态,也就是相当于断路, 整个IIC总线只有开启了的设备才会正常进行通信,而不会干扰到其他设备。
3. IIC的各个信号IIC通信过程由开始、结束、发送、响应、接收五个部分构成。
在这些信号处理过程中(注意事项):在发送、接受数据时:1.当SCL为高电平时,SDA线不允许变化;2.当SCL线为低电平时,SDA可以任意0,1变化;在任意时候:1.当SCL为高电平时,IIC电路才对SDA线上的电平(0或者1)进行记录;2.当SCL线为低电平时,无论SDA是高或者低,IIC都不对SDA进行采样。
(1) 起始信号SCL保持高电平,SDA由高电平变为低电平后,延时(>4.7us),SCL变为低电平。
(2) 停止信号SCL保持高电平,延时(>4us),SDA由低电平变为高电平。
(3) 数据信号IIC总线在数据传输的时候要保证在SCL高电平期间,SDA上的数据稳定,因此SDA上的数据变化只能在SCL低电平期间发生。
(4) 应答信号与非应答信号当IIC主机发送完8位数据以后会将SDA设置为输入状态,等待IIC从机应答,也就是等到IIC从机告诉主机它接收到数据了。应答信号是由从机发出的,主机需要提供应答信号所需的时钟,主机发送完8位数据以后紧跟着的一个时钟信号就是应答信号使用的。从机通过将SDA拉低来表示发出应答信号,表示通信成功,否则表示通信失败。
应答信号:应答信号为低电平时,规定为有效应答位(ACK,简称应答位),表示接收器已经成功地接收了该字节;
非应答信号:应答信号为高电平时,规定为非应答位(NACK),一般表示接收器接收该字节没有成功。
4. IIC的通信模板
(1)从机地址和读写位:
开始信号—>地址—>读写位—>ACK—>数据—>ACK—>数据—>ACK...—>停止位
这里只说7位地址(ADDRESS),前7位为地址,第八位为读写位:1表示读操作,0表示写操作 (2)写时序:
主机发给从机数据,也就是写,没有数据转向时 实际常用:
具体步骤:1)、开始信号。2)、发送IIC设备地址,每个IIC器件都有一个设备地址,通过发送具体的设备地址来决定访问哪个IIC器件。这是一个8位的数据,其中高7位是设置地址,最后1位是读写位,为1的话表示这是一个读操作,为0的话表示这是一个写操作。3)、IIC器件地址后面跟着一个读写位,为0表示写操作,为1表示读操作。4)、从机发送的ACK应答信号。5)、重新发送开始信号。6)、发送要写入数据的寄存器地址。7)、从机发送的ACK应答信号。8)、发送要写入寄存器的数据。9)、从机发送的ACK应答信号。10)、停止信号。
(3)读时序:
主机立即读从机数据,从第一个字节。
实际常用:
IC单字节读时序比写时序要复杂一点,读时序分为4大步:第一步:是发送设备地址;第二步:是发送要读取的寄存器地址;第三步:重新发送设备地址;第四步:就是IIC从器件输出要读取的寄存器值。具体步骤:1)、主机发送起始信号。2)、主机发送要读取的IIC从设备地址。3)、读写控制位,因为是向IIC从设备发送数据,因此是写信号。4)、从机发送的ACK应答信号。5)、重新发送START信号。6)、主机发送要读取的寄存器地址。7)、从机发送的ACK应答信号。8)、重新发送START信号。9)、重新发送要读取的IIC从设备地址。10)、读写控制位,这里是读信号,表示接下来是从IIC从设备里面读取数据。11)、从机发送的ACK应答信号。12)、从IIC器件里面读取到的数据。13)、主机发出NO ACK信号,表示读取完成,不需要从机再发送ACK信号了。14)、主机发出STOP信号,停止IIC通信。
了解完IIC协议后我们写一下IIC代码:
STC32单片机实现IIC协议基本信号传输首先我们要定义一些接口和一些函数如下图:
这里有添加一个库是intrins,这是我们用到了延迟函数_nop_();这个函数可以实现1us的延时。1)IIC初始化:这里IIC的初始化是把2条信号线都拉高使得状态为释放,这里最后加的延迟就是等待2条线处于稳定状态:
2)开始信号函数:
这里的第一次延迟,这里前2次出现延迟都是为了使信号线处于稳定状态,最后延迟是为了数据的传输需要的延迟。然后从代码可以看出,我们实现了当SCL时钟线为高电平(释放时),SDA数据线由高变为低(产生一个下降沿信号),也就是我们开始信号产生条件时序了。3) 结束信号函数:
这里的第一次延时是为了使2条信号线处于稳定状态,最后延迟是为了数据的传输需要的延迟。然后从代码可以看出,我们实现了当SCL时钟线为高电平(释放时),SDA数据线由低变高(产生一个上升沿信号),也就是我们结束信号产生条件时序了。4) 发送接收应答信号函数:发送应答信号:
这里是主机给从机一个应答信号,是在数据信号传输完紧接着传输的,也就是相当于8位数据位后第9位就是应答信号,这里的数据线拉低(置0)然后时钟SCL拉高后就是实现发送应答信号。发送非应答信号:
这里时主机给从机一个应答信号,时在数据信号传输完紧接着传输的,也就是相当于8位数据位后第9位就是应答信号,这里的数据线拉低(置1)然后时钟SCL拉高后就是实现发送非应答信号。接收应答信号:
这里的if语句时判断SDA的IO端口的电平,刚才我们在头文件里有通过宏定义来实现。然后这里时主机发送数据后从机会给一个应答信号,此时我们就需要接收是否应答,然后才能给出是否继续发送数据还是停止通信。5) 8位数据传输函数:发送一个字节数据:
这里需要注意的就是IIC协议的数据是从高位开始到低位的,所以用上面代码可以从高到低每一位数据取出来,然后就是SCL输出高电平(释放)即可发送一位数据,然后拉低时数据SDA可以实现变换,然后一直循环8次就能发送8位数据了。读取一个字节数据:
这里也是与发送数据函数差不多的,数据传输过来也是从高到低为传输过来的,然后我们就需要把数据从接收来后一位一位向高位移动(向左移动),然后就是判断SDA线上的数据是1函数0,然后实现数据的读取。
然后我们来看下OLED的驱动:
1.硬件简介1.1 OLED模块
就如图单色或者双色的,其实就是里面的LED颜色不同而已,所以不用过多在意,选择自己喜欢的颜色就行(一般为白色,蓝色,黄+蓝);
1.2 OLED组成这个模块是IIC协议4引脚的模块,其实这个0.96寸OLED还有SPI协议的7引脚的,这里用的是IIC协议所以用到4引脚的模块,其实这只是模块的电路设计有所不同,而这个不同取决于驱动芯片SSD1306的电路设计,而且这个模块重要的就是SSD1306,接下来我们的代码实现都将建立在这个驱动芯片上的数据传输来编程的。
1.3 OLED的引脚
这里可以看到我们IIC协议所需要的2条信号线,其余的2条电源线是为了给模块供电和通信使用。1.4 OLED的显存
准确来说是SSD1306的储存空间,由于这个0.96寸的OLED屏本身就已经集成了这个SSD1306驱动芯片,所以直接说成屏幕的显存了。
1.5 OLED屏幕原理
在主机内部建立一个缓存区(128*8byte),每次修改的时候就是修改主机上的缓存即可。一般用数组来做缓存,后面代码我们会看到。
2.代码实现2.1 首先就是OLED屏幕常用的指令
命令0X81: 设置对比度。包含两个字节,第一个0X81为命令,随后方法是的一个字节要设置这个对比度,值越大屏幕越亮。命令0XAE/0XAF: 0XAE为关闭显示命令,0XAF为开启显示命令0X8D: 包含两个字节,第一个为命令字,第二个为设置值,第二个字节的BIT2表示电荷泵的开关状态,该位为1开启电荷泵,为0则关闭。模块初始化的时候,这个必须要开启,否则看不到屏幕显示。命令0XB0~B7:用于设置页地址,其低三位的值对应GRAM页地址。命令0X00~0X0F:用于设置显示时的起始列地址低四位。命令0X10~0X1F: 用于设置显示时的起始列地址高四位。2.2 OLED屏幕显示一个点的思路OLED屏幕分为页寻址模式,水平地址模式,垂直地址模式常用的都是以页寻址模式,根据SSD1306驱动芯片里的一张介绍图:
可以看出把整个OLED屏幕分成了8页,说白了就是把OLED的屏幕把宽平均分成了八份。比如需要在第0列的第三行的开头显示一个点就是要按位来配置:0000 0100(0x04)
2.3 代码分析指令发送函数:
这里的代码是结合我们之前编写的iic协议代码的基础函数一起编写的。这个函数就已经结合了我们之前应用的一个逻辑时序:
这个逻辑就是上面的代码的基本逻辑,只不过是我们传输一个设备地址后紧接着传输的是一个关于传输的是指令的数据给从机(也就是OLED),这里地址后的第一个数据是告诉从机接下来我们传输的是指令,那为什么要传输0x00
这里就需要查看SSD1306驱动芯片手册:
这里看上面的图可以知道D/C是处于数据第7位0000 0000,然后就是看下面第5点的b解释可以看到如果当D/C位设置为0就是数据为指令模式,然后设置为1就是数据为数据模式。那由此可以看出写命令就是0x00
数据发送函数:
这个代码就和上面的发送指令函数的时序是一样的,不一样就是在传输地址后的第一个数据是要向从机说明接下来的数据是数据模式,那由于我们上面介绍的SSD1306驱动芯片可以知道,当D/C位设置为1就是数据模式,那第一个数据就需要传输0x40。清屏函数:
可以看到我们这里设置了几条指令然后就是发送数据,这里的也是也跟注释一样,首先就是我们OLED分成了8页然后i变量就是我们想要操作某一页。这里用了0xb0+i这个指令是由于SSD1306驱动芯片决定的:
数据手册这里提及到这个指令,就是用这个指令来确定我们0-7页。然后就是配置设置列的低4位和列的高4位,通过命令(00h-0Fh)设置列起始地址低位,通过命令(10h-1Fh)设置列起始地址高位这里都是设置成0说明是从0列开始:
这里都提及了设置低4位和高4位的列设置,最后的128次循环是每页里面都有128行然后列指针会每次操作都会加一,所以这里循环128次赋值就把每页都赋值了。这里可以看成开启列,然后就是把每页的128列都设置成0x00这就是清空数据,也相当于清屏。
开启显示和关闭显示函数:
这里可以看到我们应用了设置电荷泵指令,打开关闭电荷泵,打开关闭显示根据:
我们可以看到电荷设置需要2次指令第一次就是传输0x8D来提醒从机是要设置电荷泵,然后就是设置是否开启电荷泵,这里就是A2位,这就是我们看2个函数的第二个指令是0x10和0x14的区别就是当0x14时就是A2被置,所以就会开启电荷泵,然后如果是0x10时A2位就是被置0,那么就是关闭电荷泵,然后就是2个函数的第3个指令发送内容根据:
这里可以看到这个指令是取决于X0位,也就是最低位,那么这里的解释可以知道如果当X0位被置0的话就会关闭显示,如果置1的话就会开启显示。初始化函数:
初始化函数涉及的内容特别多,主要是一些参数的设置,都是指令设置。首先就是关闭显示的指令,这里前面已经介绍过了,这里就不再介绍了,可以到上个函数介绍里看看,然后就是时钟分频和震荡频率的设置:
手册可以看到这个时钟分频和震荡频率的设置是有2条指令的,第一条指令0x5D是固定的,告诉从机我们要设置时钟分频和震荡频率,然后就是第二条指令就是配置相关的参数,我们从上面手册截取的介绍可以看到低4位是来配置时钟分频的,然后高4位是配置相关的震荡频率的,这里就涉及到我们IIC对速率和时钟的设置了0x80,这里就说明我们震荡频率是设置了
根据这个数据手册我们可以看到时钟分频和震荡频率是这么计算的,我们上面设置低4位是0,所以D就为0+1=1,然后就是我们设置高4位是8,那么Fosk就是8,那么就可以计算DCLK和Ffrm了。然后就是0xA8和0x3F着2个指令,可见手册:
首先我们告诉从机要设置驱动路数。由此可以知道,这里是把这个驱动路数设置成为初始值111111;然后就是0xD3和0x00指令,手册介绍:
这里知道首先我们告诉从机要设置显示偏移;然后就是设置设置偏移量为0x00也就是不设置偏移。
然后就是0x00,0x10,0x40,手册介绍:
这里的0x00和0x10是上面有介绍过的,所以这里就不过多简述了;这里的0x40是设置起始线,这里可以看得出所有位都置0,这样的话就是初始值,也就是从0位开始。然后就是0x8D和0x14指令是设置电荷泵的,上面有介绍这里就不过多介绍了;然后就是0x20和0x02指令是设置内存寻址模式,手册介绍:
这里的0x20是告诉从机要设置内存寻址模式。然后0x02是设置成为页面寻址模式。从介绍里可以看到00是设置成:水平寻址模式;01是设置成:垂直寻址模式;11是设置成:无效;然后就是0xA1指令,手册介绍:
这里是设置段重映射,这里是设置列的127地址映射到SEG0;然后就是0xC8,0xDA,0x12的COM设置,手册介绍:
这里设置是从COM扫描到COM0,如果是设置成0xC0的话就是从然后就是COM0到COM;0xDA和0x12指令,手册介绍:
这里可以看到0xDA是告诉从机设置设置COM引脚硬件配置。 然后配置0x12根据手册是配置成COM的左右映射,也就是A4=0,A5=1,上面手册介绍是从COM0扫描到COM63;然后就是0x81和0xEF,这里是设置对比度,这个上面已经介绍了,所以这里就不过多介绍。然后就是0xD9和0xF1是设置预充电周期,手册介绍:
这里的0xD9是告诉从机要设置预充电周期,然后就是0xF1根据手册就是设置阶段1的预充电周期是1,阶段2的预充电周期是15;然后就是0xDB和0x30是设置Vcomh电压的倍率,手册介绍:
这里的0xDB是告诉从机要设置Vcomh电压的倍率,然后就是0x30就是设置成~0.83*Vcc然后就是0xA4,0xA6,0xAF都是设置显示的,手册介绍:
通过手册可以知道0xA4就是设置恢复到RAM内容显示就是默认配置;然后就是0xA6就是默认配置也就是ROM内容显示然后0就是正常显示,1就是反向显示;0xAF就是开启显示;填充函数:这里有2个填充函数,一个是全屏填充,还有一个是部分区域填充;由于这里有应用到显示一个点的函数,所以这里结合起来介绍;全屏填充函数:
这里可以看到这个函数和清屏函数是一样的,不同就是我们给了一个数据,就是我们要设置全屏的数据是什么由我们自己定义,而清屏函数是全部为0;设置光标位置函数:
这里的函数上面其实有涉及到,一开始我对于0x1x和0x0x这两个函数理解错了,一开始我以为是设置一个列从y轴角度看的8位数据,结果是选择哪一列,比如如果上面代码x是0x11的话就是高4位和低4位加起来的10进制数是17,那么这样的话就是第17列开始,上面对于这个命令如果一些地方理解未修改可以修改一下。显示一个字符的函数:
这里代码注释已经有解释,这里简单简述一下,首先就是要注意字符的偏移量,这里是因为我们数组定义时的顺序和ASCII码的相对数值不同,需要采用整体偏移。然后就是根据相关输入的字符的size来实现相关的显示,这里值得注意的时当size为8时就是只需要一页来显示即可,所以只用了一个循环,然后如果是size是16的话就需要2页来显示,所以就需要2次循环来实现。显示字符串的函数:
这里代码注释已经有解释,这里简单简述一下,上面首先就时判断你输入的字符是否在ASCII码里的内容,如果不是就不显示,然后就是判断我们输入的数据是否超过我们屏幕的长度和宽度,然后就是如果超过整个屏幕的宽度是清除然后显示,这里值得注意就是x +=size/2,因为我们如果是输入字符的话要不就是宽度是长度的一半,也就是说我们每显示一个字符后就需要向后移动一个字符宽度,然后就是p++,让字符指针加1,也就是下一个字符。显示汉字的函数:
这里代码注释已经有解释,这里简单简述一下,首先就是设置开始位置,然后就是我们一个汉字的长度和宽度都是16,也就是这里我们需要2页和16列来显示一个汉字,所以就有了2次循环来实现,这里值得注意的是这里的Hzk二维数组里的,第一维的数组宽度是16,刚好就是一页显示的长度,然后第二维的就需要每一个汉字都需要2个来拼接,第一个就是第一页的16列数据,第二个就是第二页的16列数据,所以这里我们可以看到2*bit_temp和2*bit_temp+1这个第二维位的运算。还有就是我们实现第一页后就需要把开始位置地位到第二页的x位置处。
显示图片的函数:
这里代码注释已经有解释,这里简单简述一下,这里我们需要注意的是我们的图片是每个点每个点显示的,而这里就是用页寻址模式来实现数据每一页宽度来输入,分成8页(y)和128列(x)来实现循环,也就是只要实现8次128个字节传输就是一整个屏幕数据的传输,这样理解上面的函数代码就比较有概念了。
显示数字函数:
这里代码注释已经有解释,这里简单简述一下,这里我们首先是先判断我们输入的数是否为负数,如果是的话就转为正数,然后在我们输入的位置前一个字符位显示一个负号,然后就是下面的提取每个进制位的数字,这里我们又定义了一个函数来实现m^n函数的运算,这个函数非常好,首先是定义一个result为1,这里不支持负数指数的运算,如果是指数为0的话就是1,这里加了一个判断如果指数是0就直接输出了,然后就是下面的循环李的运用,这里就是举个例子会比较形象,如果我们输入的是56,那么len是2,然后进入循环,这里
能很巧妙的取出十位个位的数据,因为一开始的话就是len-t-1=1,那么就是num/10%10=56/10%10=5%10=5,然后就是len-t-1 = 0,然后就是num/1%10=56/1%10=56%10=6,那么就是显示5和6,非常巧妙的提取了每个进制位上的数字。
字库和主函数大家可以下载代码去看一下,取模软件的取模步骤后面更新
2.3 实验结果
代码:
佬,我下载例程编译,出现警告参数类型不符,而且还有报错 zhu 发表于 2023-4-27 10:54
佬,我下载例程编译,出现警告参数类型不符,而且还有报错
解决了,我重新破解了一下编译软件,可能之前的有问题 大佬字模是怎么取的
【新提醒】STC USB-CDC虚拟串口驱动教程系列——虚拟OLED显示汉字 - OLED12864-GUI/U8g2-科学计算器 - 国芯论坛-STC全球32位8051爱好者互助交流社区 - STC全球32位8051爱好者互助交流社区 (stcaimcu.com)
zhu 发表于 2023-4-27 11:09
解决了,我重新破解了一下编译软件,可能之前的有问题
ok zhu 发表于 2023-4-27 11:31
大佬字模是怎么取的
字模教程后面出,也可以参考其他贴子先,或者网上查找一下资料 cxya 发表于 2023-4-27 12:44
字模教程后面出,也可以参考其他贴子先,或者网上查找一下资料
这个也解决了
zhu 发表于 2023-5-1 09:24
这个也解决了
ok 楼主,只用USB给串口供电带不动OLED屏,怎么解决啊