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);
}
}
作者:空语尤樱