IIC 驱动OLED

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 灯组成的,因此它是自发光屏幕,它的优点是像素高,色彩还原度好,但是价格相对较高。

IIC 驱动OLED_第1张图片

常见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驱动原理是一样的。

IIC 驱动OLED_第2张图片

        OLED显示屏显示的原理很简单,假设把8*8像素大小看作8*8的正方形方块,整个128*64像素就分成16*8个8*8的正方形方块。那么在8*8的正方形方块中的每一列对应着一个字节的数据,每一个字节中的每一位对应着一个像素点,一列中的最上面一点对应字节中的最低位,最下面一点对应字节中最高位,那么这一列中的字节为1时就点亮,所以对应8*8区块只要8个字节就可以填充。假如发送的数据是0x00,则第一列都不亮,发送数据0xff,则第一列数据全亮。这样就可以根据想要的显示内容发送相对应的字节数据。

有关字符的C程序生成,我们一般是利用制字字模软件实现的,网上很多相关制字软件。下面以其中一个来做简要介绍

输出汉字模式:

IIC 驱动OLED_第3张图片IIC 驱动OLED_第4张图片

输出图片模式:

IIC 驱动OLED_第5张图片IIC 驱动OLED_第6张图片

 OLED参考原理图以及官方外接电路原理图:

IIC 驱动OLED_第7张图片

通过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)初始化屏幕

IIC 驱动OLED_第8张图片

根据官方提供的初始化流程图可以编写屏幕初始化函数:

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 驱动OLED_第9张图片

IIC总线基本原理参考:https://blog.csdn.net/weixin_51121577/article/details/127340974

你可能感兴趣的:(stm32,单片机,arm,嵌入式硬件)