STM32 OLED屏初始化教程(一)

本章介绍STM32与OLED屏幕通信,写入数据实现清屏功能。

单片机选择STM32F103C8T6最小系统板

        1.1   STM32+OLED屏初始化(一) 

        1.2  STM32+OLED屏显示字符串、汉字、图片(二)

        1.3  STM32+OLED屏多级菜单显示(三)

        1.4  STM32+OLED屏(软件IIC+位带+帧缓冲区)刷新速率优化(四) 

1.OLED屏幕介绍

         OLED,及有机发光二极管(Organic Light-Emitting Diode),OLED由于同时具备自发光,不需要背光光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制造比较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。

市场上常见 0.96 寸 OLED 显示屏有以下特点:
        1)0.96 寸 OLED 有黄蓝、白、蓝三种颜色可选,其中黄蓝是屏上 1/4 部分为黄光,下 3/4 为蓝;而且是固定区域显示固定颜色,颜色和显示区域均不能修改;白光则为纯白,也就是黑底白字;蓝色则为纯蓝,也就是黑底蓝字。
        2)分辨率为 128*64
        3)多种接口方式,OLED 裸屏总共种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的串行 SPI 接口方式(通信速度快,但占用引脚多)2线 IIC 接口方式(通信速度慢,但占用引脚少),这里使用的四针 0.96 寸 OLED 显示屏,该显示器采用2线IIC总线协议通信。

        1.1 驱动IC

        屏幕的驱动IC为SSD1306,是一个单片CMOS、OLED/PLED驱动芯片可以驱动有机/聚合发光二极管点阵图形显示系统。由128Segments和64Commons组成。该芯片专为共阴极OLED面板设计。SSD1306中还嵌入了对比度控制器、显示RAM和晶振,并因此减少了外部器件和功耗。由256级亮度控制。数据/命令的发送有三种接口可以选择:6800/8000串口、IIC接口和SPI接口(STM32与屏幕通信其实就是与驱动IC通信)。

        SSD1306其具有内部升压功能,所以在设计的时候不需要再专一设计升压电路,当然也可以选用外部升压,具体设计可参考数据手册。SSD1306芯片的显存总共为128*64bit大小,SSD1306将这些显存分为了8页,每页包含了128个字节,这样刚好128*64的点阵大小。

        1.2 寻址方式

         OLED屏幕有三种寻址方式,页寻址方式水平寻址方式垂直寻址方式,默认选择页寻址方式,其他寻址方式因为不使用所以暂时不讲,到了第四篇优化刷新速率时用到时会讲到,有兴趣可以到第四篇看看。

        页地址模式:使用页寻址方式对GRAM进行操作时,列地址指针会自动递增。当列地址指针到达列结束地址时,重置为开始地址,但页地址指针不变。用户必须设置新的页面和列地址,以便访问下一页GRAM内容。

        将整个OLED屏幕一分为八,也就是8页,每页包含128字节,一个字节有8位(0000 0000 ~ 1111 1111) ,低位在前。如果想要在第0列第3行显示一个点,可配置0x08(0000 0100)写入。

        

2.常用指令

        SSD1306芯片的指令用于控制OLED的显示功能和效果。SSD1306指令比较多,这里介绍几个常用指令即可,第一个指令是显示开关指令,第二个和其余两个指令是用于设置SSD1306的GRAM写入位置(可参考下面的代码)。三种设置内存地址模式:页地址模式,水平地址模式和垂直地址模式。

        指令0xAE/0xAF:0xAE为关闭显示命令,0xAF为开启显示命令。

        指令0xB0~0xB7:用于设置页地址,其低三位的值对应着GRAM页地址

        指令0x00~0x0F:用于设置显示时的起始地址低四位。

        指令0x10~0x1F:用于设置显示时的起始地址高四位。

用指令设置写位置的起始光点: 

/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_Write_Command(0xB0 | Y);					//设置Y位置
	OLED_Write_Command(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_Write_Command(0x00 | (X & 0x0F));			//设置X位置低4位
}

        图形显示数据RAM(GRAM):是一个位映射静态RAM,保存要显示的位模式。内存大小为128*64位,可分为8页,从页0到页7,用于黑白128*64点阵显示。写入指令0xA0/0xA1和指令0xC0/0xC8定义显示方式。

        清屏函数(GRAM写0):

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i = 0, j = 0;
	for (j = 0; j < 8; j++) {0 ~ 7页
		OLED_SetCursor(j, i);//选择起始光点
		for(i = 0; i < 128; i++) {0 ~ 127列
			OLED_Write_Data(0x00);//写入数据
		}
	}
}

3.点亮思路

        列地址指针自增,低位在前:

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i = 0, j = 0;
	for (j = 0; j < 8; j++) {
		OLED_SetCursor(j, 0);//列地址指针会自动递增写0也可以
		for(i = 0; i < 128; i++) {
			OLED_Write_Data(0x80);//1000 0000 对高位写1
		}
	}
}

对高位写1,反而显示后面的图像,所以低位在前。 

4.通信协议

STM32与OLED屏幕通信需要使用IIC总线协议,简单介绍一下IIC总线协议。

        IIC(Inter – Integrated Circuit)总线是一种PHILIPS公司2线式串行总线,用于连接微控制器及其外围设备。它由数据线(SDA)时钟线(SCL)构成的串行总线,可以发送数据和接收数据。在CPU与被控IC之间、IC与IC之间进行双向传输,高速IIC总线一般可以达到400kbps以上

        IIC总线在传输数据过程中共有三种类型信号,它们分别是:起始信号、停止信号和应答信号。两个注意点:数据有效性、数据传输顺序。一个状态:空闲状态

        起始信号:SCL为高电平时,SDA由高电平向低电平跳变,开始传输数据

        停止信号:SCL为高电平时,SDC由低电平向高电平跳变,结束传输数据

        应答信号:接收数据的IC在接收8bit数据后,向发送数据的IC发出特定的低电平脉冲,表示已经接收数据。CPU向受控单元发出一个信号后,等待受控单元发出一个应答信号,CPU接收到应答信号后,根据实际情况作出是否继续传递信号的判断。若为收到应答信号,由判断为受控单元出现故障。

这些信号中除了起始信号是必要的,其他两信号都可以不要。

        数据传输格式:放在SDA行上的每个字节必须是8位长。每次传输可以传输的字节数不受限制。每个字节后面必须有一个确认位。首先使用最重要位(MSB)传输数据。如果从属服务器不能接收或传输另一个完整的数据字节,直到它执行了一些其他功能,例如服务一个内部中断,它可以保持时钟线SCL LOW,以迫使主服务器进入等待状态。当从服务器准备处理另一个字节并释放时钟线SCL时,继续数据传输。

        空闲状态:高电平 

        STM32内部有自带硬件IIC总线接口,优点配置简单速度快,缺点不稳定,且不方便移植;通常使用软件模拟,只需要任意2个GPIO引脚即可,移植简单不需要硬件支持,且可以在不同的硬件平台上使用;所以选择软件IIC。

软件IIC配置如下:

        选择任意两个GPIO引脚,通过上下拉模式IIC时序。这里选择的两个引脚是PB14时钟线(SCL)和PB15数据线(SDA)。

void MyI2C_W_SCL(uint8_t BitValue)
{
	MYIIC_W_SCL(BitValue);
	delay_us(2);
}//对SCL线封装,便于操作,控制时钟线
void MyI2C_W_SDA(uint8_t BitValue)
{
	MYIIC_W_SDA(BitValue);
	delay_us(2);
}//对SDA线封装,便于操作,发送主机值
uint8_t MyI2C_R_SDA(void)
{
	uint8_t BitValue;
	BitValue = MYIIC_R_SDA();
	delay_us(2);
	return BitValue; 
}//对SDA线封装,便于操作,读取从机发送的值


/**
  * @brief  软件IIC初始化
  * @param  I2C总线在传递数据的过程中共有三种类型的信号,
  *			它们分别是:开始信号、结束信号、应答信号
  * @retval 无
  */
void MyIIC_Init(void)
{
	GPIO_InitTypeDef GPIO_InitStructure;					//定义GPIO结构体
	RCC_APB2PeriphClockCmd(IIC_CLOCK, ENABLE); 				//开启时钟

	GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_OD;		//通用开漏
	GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;		//输出频率
	GPIO_InitStructure.GPIO_Pin = IIC_SCL_PIN;				//PB14引脚
	GPIO_Init(IIC_SCL_GPIO, &GPIO_InitStructure);			//结构体配置完成初始化
	GPIO_InitStructure.GPIO_Pin = IIC_SDA_PIN;				//PB15引脚
	GPIO_Init(IIC_SDA_GPIO, &GPIO_InitStructure);			//结构体配置完成初始化
	
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}
/* 宏替换 -- 方便移植 */
#define IIC_CLOCK		RCC_APB2Periph_GPIOB
#define IIC_SCL_GPIO	GPIOB
#define IIC_SDA_GPIO	GPIOB
#define IIC_SCL_PIN		GPIO_Pin_14
#define IIC_SDA_PIN		GPIO_Pin_15

/*引脚配置*/
#define MYIIC_W_SCL(x)		GPIO_WriteBit(IIC_SCL_GPIO, IIC_SCL_PIN, (BitAction)(x))
#define MYIIC_W_SDA(x)		GPIO_WriteBit(IIC_SDA_GPIO, IIC_SDA_PIN, (BitAction)(x))
#define MYIIC_R_SDA()		GPIO_ReadInputDataBit(IIC_SDA_GPIO, IIC_SDA_PIN)

        起始信号:IIC开启需要在SCL(1)为高的时候拉低SDA(0)

/**
  * @brief  软件IIC起始信号
  * @param  无
  * @retval 无
  * @explain IIC开启需要在SCL(1)为高的时候拉低SDA(0)
  */
void MyIIC_Start(void)
{
	MyI2C_W_SDA(1);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(0);
}

         停止信号:IIC结束需要在SCL(1)为高的时候拉高SDA(1)

/**
  * @brief  软件IIC停止信号
  * @param  无
  * @retval 无
  * @explain IIC结束需要在SCL(1)为高的时候拉高SDA(1)
  */
void MyIIC_Stop(void)
{
	MyI2C_W_SDA(0);
	MyI2C_W_SCL(1);
	MyI2C_W_SDA(1);
}

        应答信号:OLED只需要写入命令和数据,可以不用接收,自然也不用接收应答

/**
  * @brief  软件IIC发送应答信号
  * @param  AckBit 有无应答
  * @retval 无
  */
void MyIIC_SentAck(uint8_t AckBit)
{
	MyI2C_W_SDA(AckBit);//当发送完一个数据以后,SCL本身就是低的,所以前面不需要再给SCL低了
	MyI2C_W_SCL(1);
	MyI2C_W_SCL(0);
}
/**
  * @brief  软件IIC接收应答信号
  * @param  无
  * @retval 无
  */
uint8_t MyIIC_ReceiveAck(void)
{
	uint8_t AckBit;
	
	MyI2C_W_SDA(1);//主机主动空出SDA,从机会立刻占据,发送应答或者非应答信号
	MyI2C_W_SCL(1);//SCL拉高以后,主机便可以去读取从机给的信号
	AckBit = MyI2C_R_SDA();
	MyI2C_W_SCL(0);//放手SDA
	
	return AckBit;
}

         发送字节: 主机在SCL(0)低的时候只会发送一位,从机在SCL(1)为高的时候一次也只接收一位

/**
  * @brief  软件IIC发送一个字节
  * @param  Byte 要发送的字节
  * @retval 无
  * @explain 主机在SCL(0)低的时候只会发送一位,从机在SCL(1)为高的时候一次也只接收一位
  */
void MyIIC_SendByte(uint8_t Byte)
{
	uint8_t i;
	
	for (i=0; i<8; i++) {
		MyI2C_W_SDA(Byte & (0x80 >> i));//0x80 --> 1000 0000
		MyI2C_W_SCL(1);
		MyI2C_W_SCL(0);
	}
}

         接收字节:OLED只需要写入命令和数据,可以不用接收

/**
  * @brief  软件IIC接收一个字节
  * @param  无
  * @retval 无
  * @explain
  */
uint8_t MyIIC_ReceiveByte(void)
{
	uint8_t i, Byte=0x00;
	
	MyI2C_W_SDA(1);
	for(i=0; i<8; i++) { 
		MyI2C_W_SCL(1);
		if(MyI2C_R_SDA() == 1) {
			Byte |= (0x80 >> i);//高位先行,所以右移
		}
		MyI2C_W_SCL(0);
	}
	
	return Byte;
}

         IIC写入数据:数据传输遵循如下所示的格式。在起始信号(S)之后,将发送一个从属地址。这个地址是7位长,后面跟着8位,它是数据方向位(R/W)—  “0”表示传输(W),“1”表示数据请求(R)。数据传输始终由主服务器生成的停止信号(P)终止。但是,如果主服务器仍然希望在总线上通信,它可以生成一个重复的起始信号(S),并处理另一个从服务器,而不首先生成停止信号(P)。在这种传输中,就可以实现读/写格式的各种组合。

/**
  * @brief  OLED 写命令
  * @param  IIC_Byte 写入命令
  * @retval 无
  * @explain 无
  */
void OLED_Write_Command(uint8_t Command)
{
	MyIIC_Start();
	MyIIC_SendByte(0x78);  	//写入从机地址
	MyIIC_SentAck(0);		//不需要应答
	MyIIC_SendByte(0x00);  	//写入命令
	MyIIC_SentAck(0);
	MyIIC_SendByte(Command); //写命令
	MyIIC_SentAck(0);
	MyIIC_Stop();      		//发送停止信号
}
 
/**
  * @brief  OLED 写数据
  * @param  Data 写入数据
  * @retval 无
  * @explain 无
  */
void OLED_Write_Data(uint8_t Data)
{
	MyIIC_Start();
	MyIIC_SendByte(0x78);  	//写入从机地址
	MyIIC_SentAck(0);
	MyIIC_SendByte(0x40);  	//写入命令
	MyIIC_SentAck(0);
	MyIIC_SendByte(Data); //写命令
	MyIIC_SentAck(0);
	MyIIC_Stop();      		//发送停止信号
}

        协议部分就完成了,STM32通过IIC总线往OLED屏发送指令和写数据。

5.配置OLED屏幕

        OLED初始化写入一些指令,指令详细可参考厂家的数据手册。

void OLED_Init(void)
{
	delay_ms(200);	//上电延时
	MyIIC_Init();	//软件IIC协议初始化
	
	OLED_Write_Command(0xAE);	//关闭显示
	OLED_Write_Command(0xD5);	//设置显示时钟分频比/振荡器频率
	OLED_Write_Command(0x80);
	OLED_Write_Command(0xA8);	//设置多路复用率
	OLED_Write_Command(0x3F);
	OLED_Write_Command(0xD3);	//设置显示偏移
	OLED_Write_Command(0x00);
	OLED_Write_Command(0x40);	//设置显示开始行
	OLED_Write_Command(0xA1);	//设置左右方向,0xA1正常 0xA0左右反置
	OLED_Write_Command(0xC8);	//设置上下方向,0xC8正常 0xC0上下反置
	OLED_Write_Command(0xDA);	//设置COM引脚硬件配置
	OLED_Write_Command(0x12);
	OLED_Write_Command(0x81);	//设置对比度控制
	OLED_Write_Command(0xCF);
	OLED_Write_Command(0xD9);	//设置预充电周期
	OLED_Write_Command(0xF1);
	OLED_Write_Command(0xDB);	//设置VCOMH取消选择级别
	OLED_Write_Command(0x30);
	OLED_Write_Command(0xA4);	//设置整个显示打开/关闭
	OLED_Write_Command(0xA6);	//设置正常/倒转显示
	OLED_Write_Command(0x8D);	//设置充电泵
	OLED_Write_Command(0x14);
	OLED_Write_Command(0xAF);	//开启显示
	
	OLED_Clear();//清屏
}
/**
  * @brief  OLED设置光标位置
  * @param  Y 以左上角为原点,向下方向的坐标,范围:0~7
  * @param  X 以左上角为原点,向右方向的坐标,范围:0~127
  * @retval 无
  */
void OLED_SetCursor(uint8_t Y, uint8_t X)
{
	OLED_Write_Command(0xB0 | Y);					//设置Y位置
	OLED_Write_Command(0x10 | ((X & 0xF0) >> 4));	//设置X位置高4位
	OLED_Write_Command(0x00 | (X & 0x0F));			//设置X位置低4位
}

/**
  * @brief  OLED清屏
  * @param  无
  * @retval 无
  */
void OLED_Clear(void)
{  
	uint8_t i, j;
	for (j = 0; j < 8; j++) {
		OLED_SetCursor(j, 0);
		for(i = 0; i < 128; i++) {
			OLED_Write_Data(0xFF);//0xFF -- 全亮
		}                         //0x00 -- 全熄
	}
}

6.主函数

最后是main.c函数

//#include "main.c" 

int main(void)
{
//	LED_Init();
	OLED_Init();
	Usart_Init();

	printf("完成软件IIC总线协议\r\n");
	while (1) {
		
		delay_ms(1000);
	}
}

运行效果:

初始化清屏(全亮),看来初始化很成功

最后,编写一个“十”在屏幕上测试,也没有问题。

测试代码 :

/**
  * @brief  OLED测试
  * @param  无
  * @retval 无
  */
void OLED_Test1(void)
{  
	uint8_t j;
	for (j = 0; j < 8; j++) {
		OLED_SetCursor(j, 64);//j -- 表示页 64 -- 表示中间列
		OLED_Write_Data(0xFF);
	}
}
void OLED_Test2(void)
{  
	uint8_t i;
	for (i = 0; i < 128; i++) {
		OLED_SetCursor(4, i);//4 -- 表示中间页 i-- 表示列
		OLED_Write_Data(0x01);
	}
}

作者:空语尤樱

物联沃分享整理
物联沃-IOTWORD物联网 » STM32 OLED屏初始化教程(一)

发表评论