IIC总线可以驱动很多器件,比较常见的有OLED、EEPROM存储器(AT24C02)、温度传感器(LM75A)、温湿度传感器(DHT11)等。有关IIC总线协议的基本原理可以看我之前的文章介绍。这一次总结一下IIC驱动OLED的实现过程,实现简单的中英文和图片显示。(有关IIC的时序基本函数参考我之前的博客)
嵌入式开发中最常用的显示屏主要有一下几种:
(1)TFTLCD,它的特点是屏幕可以做到很大,性价比高,而且色彩丰富, 适合显示一些视觉方面的内容,比如手机屏幕,笔记本屏幕等。
(2)字符液晶屏,如 LCD1206(12*6 的像素),LCD12864(128*64 像素),如下 图,其特点是单色,像素粗糙,但是价格低廉,体积小,适合显示一些数据用,常用于仪器仪表。
(3)数码管,它的特点是价格非常低,几毛钱一片, 但是驱动电路复杂,需要的引脚很多,而且如果用单片机来驱动很占用单片机的 CPU 资源, 通常使用专门的 LED 驱动芯片如 TM1640或者使用移位寄存器进行驱动。
(4)OLED 显示屏,现在流行的高端显示器和手机屏幕都是用 的 OLED 屏幕,其实它的内部是由非常多的小 LED 灯组成的,因此它是自发光屏幕,它的优点是像素高,色彩还原度好,但是价格相对较高。
常见OLED尺寸示意图(图片来源telesky旗舰店)
我所使用的是 0.96 寸 OLED 屏幕,分辨率为 132*64,可显示图片,字符,中西方文字等。其中英文和数字最小可用8*8像素,但屏幕尺寸小显示不清楚,所以通常使用8*16像素,汉字的最小显示单位是16*16像素。其内部使用SH1106 驱动屏幕SH1106 芯片为我们提供了 132*8 字节的显存空间,即 132*64 个位,但是实际上使用有效的是128*64位就可以了,每个位对应着屏幕上 的一个像素点。还有比较常用的驱动屏幕芯片SSD1306,与SH1106不同的是其分辨率为128*64。它们在软件模拟IIC驱动原理是一样的。
OLED显示屏显示的原理很简单,假设把8*8像素大小看作8*8的正方形方块,整个128*64像素就分成16*8个8*8的正方形方块。那么在8*8的正方形方块中的每一列对应着一个字节的数据,每一个字节中的每一位对应着一个像素点,一列中的最上面一点对应字节中的最低位,最下面一点对应字节中最高位,那么这一列中的字节为1时就点亮,所以对应8*8区块只要8个字节就可以填充。假如发送的数据是0x00,则第一列都不亮,发送数据0xff,则第一列数据全亮。这样就可以根据想要的显示内容发送相对应的字节数据。
有关字符的C程序生成,我们一般是利用制字字模软件实现的,网上很多相关制字软件。下面以其中一个来做简要介绍
输出汉字模式:
输出图片模式:
OLED参考原理图以及官方外接电路原理图:
通过4线驱动OLED,具体连接方式:
OLED_SCL--> PB6 : OLED IIC 时钟
OLED_SDA--> PB7 : OLED IIC 数据
3V-->VCC
GND-->系统地
本次利用STM32C8T6最小系统板外接扩展口连接外围设备OLED以及温度传感器所用到的相关GPIO。
使用 IO 口模拟 IIC 的好处有三点:
1. 使用 IO 模拟 IIC 协议可以让大家把之前学过的 GPIO 知识再进行深度的理解和扩展
2. 加深对 IIC 时序流程的认识(相关IIC时序流程看我之前的博客文章)
3. 方便移植到 STM32 的任何一个引脚,如再做修改可以移植到其他 MCU 平台
但是假如在程序运行时会有执行时间很长的中断服务函数打断 IIC 时序,造成 IIC 写失败或者读失败。如果存在这种情况,在进行 IIC 操作之前关闭全局中断,使用后再打开。
IIC软件驱动OLED流程:
(1)初始化屏幕
根据官方提供的初始化流程图可以编写屏幕初始化函数:
void OLED_DISPLAY_ON (void){//OLED屏初始值设置并开显示
u8 buf[28]={
0xae,//0xae:关显示,0xaf:开显示
0x00,0x10,//开始地址(双字节)
0xd5,0x80,//显示时钟频率
0xa8,0x3f,//复用率
0xd3,0x00,//显示偏移
0XB0,//写入页位置(0xB0~7)
0x40,//显示开始线
0x8d,0x14,//VCC电源
0xa1,//设置段重新映射
0xc8,//COM输出方式
0xda,0x12,//COM输出方式
0x81,0xff,//对比度,指令:0x81,数据:0~255(255最高)
0xd9,0xf1,//充电周期
0xdb,0x30,//VCC电压输出
0x20,0x00,//水平寻址设置
0xa4,//0xa4:正常显示,0xa5:整体点亮
0xa6,//0xa6:正常显示,0xa7:反色显示
0xaf//0xae:关显示,0xaf:开显示
}; //
I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,28);
}
(2)开机显示初始化
//头文件定义相关接口函数和一些宏定义
#include "oled0561.h"
#include "ASCII_8x16.h" //引入字体 ASCII
#include "CHS_16x16.h" //引入汉字字体
#include "PIC1.h" //引入图片
void OLED0561_Init (void){//OLED屏开显示初始化
OLED_DISPLAY_OFF(); //OLED关显示
OLED_DISPLAY_CLEAR(); //清空屏幕内容
OLED_DISPLAY_ON(); //OLED屏初始值设置并开显示
}
void OLED_DISPLAY_OFF (void){//OLED屏关显示
u8 buf[3]={
0xae,//0xae:关显示,0xaf:开显示
0x8d,0x10,//VCC电源
}; //
I2C_SAND_BUFFER(OLED0561_ADD,COM,buf,3);
}
void OLED_DISPLAY_CLEAR(void){//清屏操作
u8 j,t;
for(t=0xB0;t<0xB8;t++){ //设置起始页地址为0xB0,一行一行的清除
I2C_SAND_BYTE(OLED0561_ADD,COM,t); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD,COM,0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD,COM,0x00); //起始列地址的低4位
for(j=0;j<132;j++){ //整页内容填充
I2C_SAND_BYTE(OLED0561_ADD,DAT,0x00);//写入数据0x00清屏
}
}
}
void OLED_DISPLAY_LIT (u8 x){//OLED屏亮度设置(0~255)
I2C_SAND_BYTE(OLED0561_ADD,COM,0x81);
I2C_SAND_BYTE(OLED0561_ADD,COM,x);//亮度值
}
(3)显示英文数字的8*16ASCII码
void OLED_DISPLAY_8x16(u8 x, //显示的页坐标(从0到7)(此处不可修改)
u8 y, //显示的列坐标(从0到128)
u16 w){ //要显示汉字的编号
u8 j,t,c=0;
y=y+2; //因OLED屏的内置驱动芯片是从0x02列作为屏上最左一列,所以要加上偏移量
for(t=0;t<2;t++){
I2C_SAND_BYTE(OLED0561_ADD,COM,0xb0+x); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD,COM,y/16+0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD,COM,y%16); //起始列地址的低4位
for(j=0;j<8;j++){ //整页内容填充
I2C_SAND_BYTE(OLED0561_ADD,DAT,ASCII_8x16[(w*16)+c-512]);//为了和ASCII表对应要减512
c++;}x++; //页地址加1
}
}
//发送一个字符串,长度64字符之内。
void OLED_DISPLAY_8x16_BUFFER(u8 row,u8 *str){
u8 r=0;
while(*str != '\0'){
OLED_DISPLAY_8x16(row,r*8,*str++);
r++;
}
}
(4)显示汉字16*16
void OLED_DISPLAY_16x16(u8 x, //显示汉字的页坐标(从0xB0到0xB7)
u8 y, //显示汉字的列坐标(从0到63)//128/2=64
u16 w){ //要显示汉字的编号
u8 j,t,c=0;
for(t=0;t<2;t++){//相当于2行
I2C_SAND_BYTE(OLED0561_ADD,COM,0xb0+x); //页地址(从0xB0到0xB7)
I2C_SAND_BYTE(OLED0561_ADD,COM,y/16+0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD,COM,y%16); //起始列地址的低4位
for(j=0;j<16;j++){ //整页内容填充,16是因为一个汉字占16列
I2C_SAND_BYTE(OLED0561_ADD,DAT,GB_16[(w*32)+c]);//数组就是显示汉字的c代码
c++;}x++; //页地址加1
}
I2C_SAND_BYTE(OLED0561_ADD,COM,0xAF); //开显示
}
(5)显示图片
void OLED_DISPLAY_PIC1(void){ //显示全屏图片
u8 m,i;
for(m=0;m<8;m++){//
I2C_SAND_BYTE(OLED0561_ADD,COM,0xb0+m);
I2C_SAND_BYTE(OLED0561_ADD,COM,0x10); //起始列地址的高4位
I2C_SAND_BYTE(OLED0561_ADD,COM,0x02); //起始列地址的低4位
for(i=0;i<128;i++){//送入128次图片显示内容
I2C_SAND_BYTE(OLED0561_ADD,DAT,PIC1[i+m*128]);}//数组是显示图片的c代码,可以用制字字模生成
}
}
(6)主函数
#include "stm32f10x.h" //STM32头文件
#include "sys.h"
#include "delay.h"
#include "lm75a.h"
#include "rtc.h"
#include "oled0561.h"
int main (void){//主程序
u8 buffer[3];
delay_ms(100); //上电时等待其他器件就绪
RCC_Configuration(); //系统时钟初始化
RTC_Config(); //RTC初始化
I2C_Configuration();//I2C初始化
LM75A_GetTemp(buffer); //读取LM75A的温度数据
OLED0561_Init(); //OLED初始化
OLED_DISPLAY_LIT(100);//亮度设置
OLED_DISPLAY_PIC1();//显示全屏图片
delay_ms(5000); //延时
OLED_DISPLAY_CLEAR();
OLED_DISPLAY_8x16_BUFFER(0,"IIC Driver OLED"); //显示字符串
// OLED_DISPLAY_8x16_BUFFER(4,"RTC:"); //显示字符串
OLED_DISPLAY_8x16_BUFFER(6," Temp:"); //显示字符串
OLED_DISPLAY_8x16_BUFFER(4," :"); //显示字符串
OLED_DISPLAY_16x161(4,0*16,0);//汉字显示 实时间
OLED_DISPLAY_16x161(4,1*16,1);
// OLED_DISPLAY_16x161(4,2*16,2);
OLED_DISPLAY_16x161(4,2*16,3);
// OLED_DISPLAY_16x161(4,3*16,4);
OLED_DISPLAY_16x16(2,0*16,0);//汉字显示 真的学无止境!
OLED_DISPLAY_16x16(2,1*16,1);
OLED_DISPLAY_16x16(2,2*16,2);
OLED_DISPLAY_16x16(2,3*16,3);
OLED_DISPLAY_16x16(2,4*16,4);
OLED_DISPLAY_16x16(2,5*16,5);
OLED_DISPLAY_16x16(2,6*16,6);
while(1){
LM75A_GetTemp(buffer); //读取LM75A的温度数据
if(buffer[0])OLED_DISPLAY_8x16(6,7*8,'-'); //如果第1组为1即是负温度
OLED_DISPLAY_8x16(6,8*8,buffer[1]/10+0x30);//显示温度值,0x30可以是数值和ASCII码表数值相对应
OLED_DISPLAY_8x16(6,9*8,buffer[1]%10+0x30);//
OLED_DISPLAY_8x16(6,10*8,'.');//
OLED_DISPLAY_8x16(6,11*8,buffer[2]/10+0x30);//
OLED_DISPLAY_8x16(6,12*8,buffer[2]%10+0x30);//
OLED_DISPLAY_8x16(6,13*8,'C');//
if(RTC_Get()==0){ //读出RTC时间
OLED_DISPLAY_8x16(4,7*8,rhour/10+0x30); //时
OLED_DISPLAY_8x16(4,8*8,rhour%10+0x30);
OLED_DISPLAY_8x16(4,9*8,':');
OLED_DISPLAY_8x16(4,10*8,rmin/10+0x30); //分
OLED_DISPLAY_8x16(4,11*8,rmin%10+0x30);
OLED_DISPLAY_8x16(4,12*8,':');//
OLED_DISPLAY_8x16(4,13*8,rsec/10+0x30); //秒
OLED_DISPLAY_8x16(4,14*8,rsec%10+0x30);
}
delay_ms(200); //延时
}
}
其中主函数加入了一个温度传感器LM75A实时读取外界温度并显示,一个实时时钟显示当前时间两个函数功能。
调试结果:
IIC总线基本原理参考:https://blog.csdn.net/weixin_51121577/article/details/127340974