1 OLED简介

1.1 OLED的定义和优势

OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
        OLED显示技术具有自发光的特性,采用非常薄的有机材料涂层和玻璃基板,当有电流通过时,这些有机材料就会发光,而且OLED显示屏幕可视角度大,并且能够节省电能,从2003年开始这种显示设备在MP3播放器上得到了应用。
        LCD都需要背光,而OLED不需要,因为它是自发光的。这样同样的显示,OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。

1.2 ALINETEK的0.96寸OLED模块

1)模块有单色双色两种可选,单色为纯蓝色,而双色则为黄蓝双色。单色模块每个像素点只有亮与不亮两种情况,没有颜色区分;
        2)尺寸小,显示尺寸为0.96寸,而模块的尺寸仅为27mm*26mm大小;
        3)高分辨率,该模块的分辨率为128*64
        4)多种接口方式,该模块提供了总共4种接口包括:6800、8080两种并行接口方式、 4线的穿行SPI接口方式、IIC接口方式(只需要2根线就可以控制OLED了!);
        5)不需要高压,直接接3.3V就可以工作了。

这里要提醒大家的是,该模块不和 5.0V 接口兼容,所以请大家在使用的时候一定要小心, 别直接接到 5V 的系统上去,否则可能烧坏模块。

1.3 OLED模块工作模式选择

        4种模式通过模块的BS1/BS2设置(通过硬件来设置),BS1/BS2的设置与模块接口模式的关系如表所示:

接口方式 4线SPI IIC 8位6800 8位8080
BS1 0 1 0 1
BS2 0 0 1 1

该OLED模块的外观图如下图所示:

ALIENTEK OLED模块默认设置是BS0接GND,BS1和BS2接VCC(8080模式),即使用8080并口方式,如果想要设置成其他的模式,则需要在OLED的背面,用烙铁修改BS0-BS2的设置。

模块的原理图如下图所示:

该模块采用 8*2 的 2.54 排针与外部连接,总共有 16 个管脚,在 16 条线中,我们只用了 15 条,有一个是悬空的。15 条线中,电源和地线占了 2 条,还剩下 13 条信号线。在不同模式下, 我们需要的信号线数量是不同的,在 8080 模式下,需要全部 13 条,而在 IIC 模式下,仅需要 2 条线就够了!这其中有一条是共同的,那就是复位线 RST(RES),RST 上的低电平,将导致 OLED 复位,在每次初始化之前,都应该复位一下 OLED 模块。
        OLED控制器为SSD1306,也就是说:裸屏由SSD1306驱动,这也是一种较为广泛使用的led驱动芯片。

2 OLED的显示原理

2.1 OLED8080并行接口信号线说明

在上面,提到了本文中OLED采用8080的接口方式,8080 并行接口的发明者是 INTEL,该总线也被 广泛应用于各类液晶显示器,ALIENTEK OLED 模块也提供了这种接口,使得 MCU 可以快速的访问 OLED。其对应的并行接口图如下所示:

接下来,ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线:

                1)CS:OLED片选信号;
                2)WR:向OLED写入数据;
                3)RD:从OLED读取数据;
                4)D[7:0]:8位双向数据线;
                5)RST(RES):硬复位OLED;
                6)DC(RS):命令/数据标志(0,读写命令;1,读写数据)。

2.2 OLED8080并口读写过程

模块的8080并口读/写的过程为:

                1)将数据放到数据口;
                2)根据要写入/读取的数据的类型,设置D/C(RS)为高(数据)/低(命令);
                3)拉低片选,选中SSD1306;
                4)接着我们根据是读数据,还是要写数据置RD/WR为低;
                5)读数据过程:在RD的上升沿, 使数据锁存到数据线(D[7:0])上;
                6)写数据过程:在WR的上升沿,使数据写入到SSD1306里面;
                7)拉高CS和DC(RS)。

2.3 并口写时序图

2.4 并口读时序图

2.5 控制脚信号状态功能表

        在 8080 方式下读数据操作的时候,我们有时候(例如读显存的时候)需要一个假读命(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,才是我们真正要读的数据。
        一个典型的读显存的时序图,如下图所示:

可以看到,在发送了列地址之后,开始读数据,第一个是 Dummy Read,也就是假读,我 们从第二个开始,才算是真正有效的数据。

2.6 4线串行(SPI)读写过程

ALIENTEK OLED 模块的4线串行(SPI) 模式需要如下一些信号线:

                1)CS:OLED 片选信号。
                2)RST(RES):硬复位 OLED。
                3)DC:命令/数据标志(0,读写命令;1,读写数据)。
                4)SCLK:串行时钟线。在 4 线串行模式下,D0 信号线作为串行时钟线 SCLK。
                5)SDIN:串行数据线。在 4 线串行模式下,D1 信号线作为串行数据线 SDIN。
        模块的 D2 需要悬空,其他引脚可以接到 GND。在 4 线串行模式下,只能往模块写数据而不能读数据。

在 4 线 SPI 模式下,写操作的时序如下图所示:

        在4线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到SSD1306,并且是高位在前的。DC 线还是用作命令/数据的标志线。

2.7 OLED模块显存

 OLED本身是没有显存的,它的显存是依赖于SSD1306提供的(之后讲解的TFTLCD是本身自带显存,利用FSMC来进行控制)。而SSD1306提供一块显存,芯片具体的讲解见下文。
        SSD1306的显存总共为128*64bit大小,SSD1306将这些显存分为了8页。每页包含了128个字节,总共8页,这样刚好是128*64的点阵大小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位都搞清楚当前的状态(0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会影响到之前的状况了。但是这样需要能读 GRAM,对于 3 线或 4 线 SPI 模式,模块是不支持读的,而且读->改->写的方式速度也比较慢。

在STM32的内部建立一个缓存(共128*8个字节),在每次修改的时候,只是修改STM32上的缓存(实际上就是SRAM),在修改完了之后,一次性把STM32上的缓存数据写入到OLED的GRAM。当然这个方法也有坏处,就是对于那些SRAM很小的单片机(比如51系列)就比较麻烦了。

3 SSD1306芯片

3.1 SSD1306芯片简介

SSD1306是一个单片CMOS、OLED/PLED驱动芯片可以驱动有机/聚合发光二极管点阵图形显示系统。由128 segments 和64 Commons组成。该芯片专为共阴极OLED面板设计。 
        SSD1306中嵌入了对比度控制器、显示RAM和晶振,并因此减少了外部器件和功耗。有256级亮度控制。数据/命令的发送有三种接口可选择:6800/8000串口,I2C接口或SPI接口。适用于多数简介的应用,注入移动电话的屏显,MP3播放器和计算器等。

3.2 SSD1306芯片特性

1)分辨率:128 * 64 点阵面板;
        2)电源:
                ①VDD = 1.65V to 3.3V,用于IC逻辑;
                ②VCC = 7V to 15V,用于面板驱动;
        3)点阵显示:
                ①OLED驱动输出电压,最大15V;
                ②Segment最大电流:100uA;
                ③常见最大反向电流:15mA;
                ④256级对比亮度电流控制;
        4)嵌入式128 * 64位SRAM显示缓存;
        5)引脚选择MCU接口:
                ①8位6800/8000串口;
                ②3/4线SPI接口;
                ③I2C接口。

3.3 SSD1306芯片命令

1)命令0X81设置对比度。包含两个字节,第一个0X81为命令,随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。
        2)命令0XAE/0XAF0XAE为关闭显示命令;0XAF为开启显示命令
        3)命令0X8D包含2个字节,第一个为命令字,第二个为设置值,第二个字节的BIT2表示电荷泵的开关状态,该位为1,则开启电荷泵,为0则关闭。在模块初始化的时候,这个必须要开启,否则是看不到屏幕显示的。
        4)命令0XB0~B7:用于设置页地址,其低三位的值对应着GRAM的页地址。
        5)命令0X00~0X0F:用于设置显示时的起始列地址低四位。
        6)命令0X10~0X1F:用于设置显示时的起始列地址高四位。

3.4 OLED模块的初始化

下面我们介绍一下 OLED 模块的初始化过程,SSD1306 的典型初始化框图如下图所示:

驱动 IC 的初始化代码,我们直接使用厂家推荐的设置就可以了,只要对细节部分进行一些修改,使其满足我们自己的要求即可,其他不需要变动。

4 STM32控制OLED

4.1 硬件连接

1)单片机:STM32F103ZET6
        2)模块:OLED显示模块
        3)硬件资源:指示灯DS0、OLED模块
        4)引脚连接:OLED 模块的电路在前面已有详细说明了,这里我们介绍 OLED 模块与战舰 STM32F103 的连接,开发板底板的 OLED/CAMERA 接口(P6 接口)和 ALIENTEK OLED 模块直接可 以对插(靠左插!),连接如下图所示:

图中圈出来的部分就是连接 OLED 的接口,这里在硬件上,OLED 与战舰 STM32 开发板的 IO 口对应关系如下:
                1)OLED_CS 对应 PD6;
                2)OLED_RST 对应 PG15;
                3)OLED_RS 对应 PD3;
                4)OLED_WR 对应 PG14;
                5)OLED_RD 对应 PG13;
                6)OLED_D[7:0]对应 PC[7:0];

4.2 STM32控制程序

设置STM32与OLED模块相连接的IO(设置与OLED相连的IO口设置为输出);
        初始化OLED模块(硬复位SSD1306、驱动IC初始化程序、开启显示、清零显存、开始显示);
        通过函数将字符和数字显示到OLED模块上。

#ifndef __OLED_H
#define __OLED_H
#include "sys.h"
#include "stdlib.h"       //OLED模式设置
//0: 4线串行模式  (模块的BS1,BS2均接GND)
//1: 并行8080模式 (模块的BS1,BS2均接VCC)
#define OLED_MODE   1 //---------------------------OLED端口定义--------------------------
#define OLED_CS  PDout(6)
#define OLED_RST PGout(15)
#define OLED_RS  PDout(3)
#define OLED_WR  PGout(14)
#define OLED_RD  PGout(13)
//PC0~7,作为数据线#define DATAOUT(x) GPIO_Write(GPIOC,x);//输出  //使用4线串行接口时使用
#define OLED_SCLK PCout(0)
#define OLED_SDIN PCout(1)#define OLED_CMD  0   //写命令
#define OLED_DATA 1 //写数据
//OLED控制用函数
void OLED_WR_Byte(u8 dat,u8 cmd);
void OLED_Display_On(void);
void OLED_Display_Off(void);
void OLED_Refresh_Gram(void);
void OLED_Init(void);
void OLED_Clear(void);
void OLED_DrawPoint(u8 x,u8 y,u8 t);
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot);
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size);
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size);
#endif 
#include "oled.h"
#include "stdlib.h"
#include "oledfont.h"
#include "delay.h"//OLED的显存
//存放格式如下.
//[0]0 1 2 3 ... 127
//[1]0 1 2 3 ... 127
//[2]0 1 2 3 ... 127
//[3]0 1 2 3 ... 127
//[4]0 1 2 3 ... 127
//[5]0 1 2 3 ... 127
//[6]0 1 2 3 ... 127
//[7]0 1 2 3 ... 127
u8 OLED_GRAM[128][8];    //更新显存到LCD
void OLED_Refresh_Gram(void)
{u8 i,n;            for(i=0;i<8;i++)  {  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA); }
}#if OLED_MODE==1 //8080并口
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{DATAOUT(dat);      OLED_RS=cmd;OLED_CS=0;       OLED_WR=0;   OLED_WR=1;OLED_CS=1;   OLED_RS=1;
}
#else
//向SSD1306写入一个字节。
//dat:要写入的数据/命令
//cmd:数据/命令标志 0,表示命令;1,表示数据;
void OLED_WR_Byte(u8 dat,u8 cmd)
{   u8 i;             OLED_RS=cmd; //写命令 OLED_CS=0;         for(i=0;i<8;i++){             OLED_SCLK=0;if(dat&0x80)OLED_SDIN=1;else OLED_SDIN=0;OLED_SCLK=1;dat<<=1;   }               OLED_CS=1;          OLED_RS=1;
}
#endif              //开启OLED显示
void OLED_Display_On(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X14,OLED_CMD);  //DCDC ONOLED_WR_Byte(0XAF,OLED_CMD);  //DISPLAY ON
}
//关闭OLED显示
void OLED_Display_Off(void)
{OLED_WR_Byte(0X8D,OLED_CMD);  //SET DCDC命令OLED_WR_Byte(0X10,OLED_CMD);  //DCDC OFFOLED_WR_Byte(0XAE,OLED_CMD);  //DISPLAY OFF
}//清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!!
void OLED_Clear(void)
{  u8 i,n;  for(i=0;i<8;i++)for(n=0;n<128;n++)OLED_GRAM[n][i]=0X00;  OLED_Refresh_Gram();//更新显示
}//画点
//x:0~127
//y:0~63
//t:1 填充 0,清空
void OLED_DrawPoint(u8 x,u8 y,u8 t)
{u8 pos,bx,temp=0;if(x>127||y>63)return;//超出范围了.pos=7-y/8;bx=y%8;temp=1<<(7-bx);if(t)OLED_GRAM[x][pos]|=temp;else OLED_GRAM[x][pos]&=~temp;
}//x1,y1,x2,y2 填充区域的对角坐标
//确保x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63
//dot:0,清空;1,填充
void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot)
{  u8 x,y;  for(x=x1;x<=x2;x++){for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);}                                                      OLED_Refresh_Gram();//更新显示
}//在指定位置显示一个字符,包括部分字符
//x:0~127
//y:0~63
//mode:0,反白显示;1,正常显示
//size:选择字体 12/16/24
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{                   u8 temp,t,t1;u8 y0=y;u8 csize=(size/8+((size%8)?1:0))*(size/2);      //得到字体一个字符对应点阵集所占的字节数chr=chr-' ';//得到偏移后的值        for(t=0;t<csize;t++){   if(size==12)temp=asc2_1206[chr][t];       //调用1206字体else if(size==16)temp=asc2_1608[chr][t];   //调用1608字体else if(size==24)temp=asc2_2412[chr][t];   //调用2412字体else return;                              //没有的字库for(t1=0;t1<8;t1++){if(temp&0x80)OLED_DrawPoint(x,y,mode);else OLED_DrawPoint(x,y,!mode);temp<<=1;y++;if((y-y0)==size){y=y0;x++;break;}}      }
}//m^n函数
u32 mypow(u8 m,u8 n)
{u32 result=1;  while(n--)result*=m;    return result;
}//显示2个数字
//x,y :起点坐标
//len :数字的位数
//size:字体大小
//mode:模式   0,填充模式;1,叠加模式
//num:数值(0~4294967295);
void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size)
{           u8 t,temp;u8 enshow=0;                        for(t=0;t<len;t++){temp=(num/mypow(10,len-t-1))%10;if(enshow==0&&t<(len-1)){if(temp==0){OLED_ShowChar(x+(size/2)*t,y,' ',size,1);continue;}else enshow=1; }OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); }
} //显示字符串
//x,y:起点坐标
//size:字体大小
//*p:字符串起始地址
void OLED_ShowString(u8 x,u8 y,const u8 *p,u8 size)
{   while((*p<='~')&&(*p>=' '))//判断是不是非法字符!{       if(x>(128-(size/2))){x=0;y+=size;}if(y>(64-size)){y=x=0;OLED_Clear();}OLED_ShowChar(x,y,*p,size,1);    x+=size/2;p++;}  }    //初始化SSD1306
void OLED_Init(void)
{   GPIO_InitTypeDef  GPIO_InitStructure;RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOC|RCC_APB2Periph_GPIOD|RCC_APB2Periph_GPIOG, ENABLE);     //使能PC,D,G端口时钟GPIO_InitStructure.GPIO_Pin = GPIO_Pin_3|GPIO_Pin_6;     //PD3,PD6推挽输出  GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP;        //推挽输出GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;    //速度50MHzGPIO_Init(GPIOD, &GPIO_InitStructure);        //初始化GPIOD3,6GPIO_SetBits(GPIOD,GPIO_Pin_3|GPIO_Pin_6); //PD3,PD6 输出高#if OLED_MODE==1GPIO_InitStructure.GPIO_Pin =0xFF;   //PC0~7 OUT推挽输出GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_SetBits(GPIOC,0xFF);      //PC0~7输出高GPIO_InitStructure.GPIO_Pin = GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15;      //PG13,14,15 OUT推挽输出GPIO_Init(GPIOG, &GPIO_InitStructure);GPIO_SetBits(GPIOG,GPIO_Pin_13|GPIO_Pin_14|GPIO_Pin_15);  //PG13,14,15 OUT  输出高#elseGPIO_InitStructure.GPIO_Pin = GPIO_Pin_0|GPIO_Pin_1;    //PC0,1 OUT推挽输出GPIO_Init(GPIOC, &GPIO_InitStructure);GPIO_SetBits(GPIOC,GPIO_Pin_0|GPIO_Pin_1);    //PC0,1 OUT  输出高GPIO_InitStructure.GPIO_Pin = GPIO_Pin_15;     //PG15 OUT推挽输出      RSTGPIO_Init(GPIOG, &GPIO_InitStructure);GPIO_SetBits(GPIOG,GPIO_Pin_15);     //PG15 OUT  输出高#endifOLED_CS=1;OLED_RS=1;  OLED_RST=0;delay_ms(100);OLED_RST=1; OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示OLED_WR_Byte(0xD5,OLED_CMD); //设置时钟分频因子,震荡频率OLED_WR_Byte(80,OLED_CMD);   //[3:0],分频因子;[7:4],震荡频率OLED_WR_Byte(0xA8,OLED_CMD); //设置驱动路数OLED_WR_Byte(0X3F,OLED_CMD); //默认0X3F(1/64) OLED_WR_Byte(0xD3,OLED_CMD); //设置显示偏移OLED_WR_Byte(0X00,OLED_CMD); //默认为0OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式OLED_WR_Byte(0x02,OLED_CMD); //[1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10;OLED_WR_Byte(0xA1,OLED_CMD); //段重定义设置,bit0:0,0->0;1,0->127;OLED_WR_Byte(0xC0,OLED_CMD); //设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数OLED_WR_Byte(0xDA,OLED_CMD); //设置COM硬件引脚配置OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置OLED_WR_Byte(0x81,OLED_CMD); //对比度设置OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认0X7F (亮度设置,越大越亮)OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;OLED_WR_Byte(0xDB,OLED_CMD); //设置VCOMH 电压倍率OLED_WR_Byte(0x30,OLED_CMD); //[6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc;OLED_WR_Byte(0xA4,OLED_CMD); //全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏)OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示                                OLED_WR_Byte(0xAF,OLED_CMD); //开启显示   OLED_Clear();
}  
int main(void){  u8 t;delay_init();           //延时函数初始化    NVIC_PriorityGroupConfig(NVIC_PriorityGroup_2);    //设置NVIC中断分组2:2位抢占优先级,2位响应优先级LED_Init();                 //LED端口初始化OLED_Init();         //初始化OLED      OLED_ShowString(0,0,"ALIENTEK",24);  OLED_ShowString(0,24, "0.96' OLED TEST",16);  OLED_ShowString(0,40,"ATOM 2015/1/14",12);  OLED_ShowString(0,52,"ASCII:",12);  OLED_ShowString(64,52,"CODE:",12);  OLED_Refresh_Gram();       //更新显示到OLED t=' ';  while(1) {       OLED_ShowChar(48,48,t,16,1);//显示ASCII字符    OLED_Refresh_Gram();t++;if(t>'~')t=' ';OLED_ShowNum(103,48,t,3,16);//显示ASCII字符的码值 delay_ms(500);LED0=!LED0;}    }

4.3 STM32控制程序分析

OLED_Refresh_Gram()函数:更新显存到OLED。
        在STM32内部定义了一个块GRAM:

u8 OLED_GRAM[128][8];

此部分GRAM对应OLED模块上的GRAM。在操作的时候,我们只需要修改STM32内部的GRAM,然后通过OLED_Refresh_Gram()函数将GRAM一次性刷新到OLED的GRAM中。

for(i=0;i<8;i++)
{  OLED_WR_Byte (0xb0+i,OLED_CMD);    //设置页地址(0~7)OLED_WR_Byte (0x00,OLED_CMD);      //设置显示位置—列低地址OLED_WR_Byte (0x10,OLED_CMD);      //设置显示位置—列高地址   for(n=0;n<128;n++) OLED_WR_Byte(OLED_GRAM[n][i],OLED_DATA);
} 

函数的具体内容先设置页地址,然后写入列地址,然后从0开始写入128个字节,这样就将一页的内容刷新过去。重复8次,将8页的内容全部刷新过去。
        OLED_WR_Byte()函数:向SSD1306写入数据或命令(参数cmd为1时表示数据,为0时表示命令)。这里的步骤是和上文中8080并口写时序图的步骤基本类似。具体为:

void OLED_WR_Byte(u8 dat,u8 cmd)
{DATAOUT(dat);      OLED_RS=cmd;OLED_CS=0;       OLED_WR=0;   OLED_WR=1;OLED_CS=1;   OLED_RS=1;
}   

首先通过DATAOUT()函数将数据放到数据口,其中DATAOUT()是一个宏定义:

#define DATAOUT(x) GPIO_Write(GPIOC,x);//输出  

其次,在判断cmd参数是命令还是数据,如果是命令,DC置高;如果是数据,DC置低。接下来,拉低片选,将WR拉低再拉高产生一个上升沿。这样数据就写入到了控制器。最后,拉高片选、D/C。
        OLED_DrawPoint()函数:画点函数,这里有一个对应关系需要理解。
        OLED_GRAM[128][8]中的128代表列数(x坐标),而8代表的是页,每页又包含8行,总共是64行(y坐标)。从高到低对应行数从小到大。比如,我们要在x=100,y=29这个点写入1,则可以用这个句子实现:

OLED_GRAM[100][4]=1<<2;

一个通用的点(x,y)置1的表达式为:

OLED_GRAM[x][7-y/8]=1<<(7-y%8);

其中,x的取值范围为0-127;y的取值范围为0-63。
        OLED_ShowChar()函数:显示字符。这里的字符采用16*8的显示方式,也就是说在OLED上16*8数目大小的点阵表示一个字符,即128个点。
        下面截取了一部分16*8的字符库的内容,一个字符用16个u8类型的数字表示:

{0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00},/*" ",0*/
{0x00,0x00,0x00,0x00,0x00,0x00,0x1F,0xCC,0x00,0x0C,0x00,0x00,0x00,0x00,0x00,0x00},/*"!",1*/
{0x00,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x08,0x00,0x30,0x00,0x60,0x00,0x00,0x00},/*""",2*/
{0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x03,0xFC,0x1E,0x20,0x02,0x20,0x00,0x00},/*"#",3*/
{0x00,0x00,0x0E,0x18,0x11,0x04,0x3F,0xFF,0x10,0x84,0x0C,0x78,0x00,0x00,0x00,0x00},/*"$",4*/
{0x0F,0x00,0x10,0x84,0x0F,0x38,0x00,0xC0,0x07,0x78,0x18,0x84,0x00,0x78,0x00,0x00},/*"%",5*/
{0x00,0x78,0x0F,0x84,0x10,0xC4,0x11,0x24,0x0E,0x98,0x00,0xE4,0x00,0x84,0x00,0x08},/*"&",6*/

具体的显示方式如下图所示:

       从上到下,从左到右,高位在前。就是这样的取模方式,将字符集按照16*8的大小取模出来。1表示亮,0表示暗。
        显示字符函数的具体实现:

for(t1=0;t1<8;t1++)
{if(temp&0x80)OLED_DrawPoint(x,y,mode);else OLED_DrawPoint(x,y,!mode);temp<<=1;y++;if((y-y0)==size){y=y0;x++;break;}
}  

这里也是按照从上到下,从左到右的取模方式来进行的。先得到最高位,然后判断是写1还是0,画点;接着读第二位,如此循环,直到一个字符的点阵全部取完为止。这里涉及到的列地址和行地址的自增,不难理解。

5  字模软件的使用

5.1 OLED 打点方式

首先我们讲一下 OLED 点阵的点亮方式。 举个显示“P” 的例子, 注意其数据的写入值: 左边为最高位 D7……

由上图可以看出,要显示“P” ,首先写入 0x1f, 则第一列显示一个竖杠,之后控制器自动水平右移到下一列, 再写入 0x05, 则出现两个小横杆, 这个两个横杆就是 0x05 中 00000101 中两个 1 所处的位置, 写完第二列后, 控制器自动跳到第三列, 再写入 0x07, 第四列写入 0x00 后, P 就显示出来了。 这也说明,即使你只想在一列的最上端显示一个小点, 你也得控制写入一个 8 位的二进制数据将其他你没有想用的位置设置好, 即写入 0x01。 即你不能一次性控制一个点阵, 只能一次性控制 8 位点阵, 即一列点阵。 这也决定了字模选择的取模方式要为“列行式” 。

5.2 汉字取模

通过讲述 PCtoLCD 字模软件在 OLED 取字模上的使用方法。 下面介绍一下取模软件的设置:

这里以取一个 14*16(宽*高) 的点阵“北” 字的设置为例说明。
                ①点开①的设置按钮, 弹开图中设置界面
                ②按照框中的设置, 设置好就可以了
                ③字模设置完成后, 输入“北” 字, 调整字宽为 14, 字高为 16(见图中绿框标出部分) , 注意英文字要看上面一点的显示, 图中“对应英文长高比”
                ④取模方式要选择“列行式” , 原因已经在上面说明了
                ⑤图中标出③的部分的设置, 为什么选择 14, 这个在下面的说明里会提到, 这个跟软件有关

5.3 数字及英文字母的取模方法

取小写字母“c” ,宽 6, 高 8,
                ①注意图中用红色标出的部分, 选择框中显示的是字宽 13 和字高 8, 这个是中文的字宽字高, 现在是英文要看上一行
                ②根据图中标出的, 设置一下取模设置
                ③每行显示的点阵数据个数, 怎么设置, 将在下面做详细描述, 这跟取到的数组大小有关,跟打点顺序有关。 取模设置中“每行显示点阵数据个数” 的设置

①主界面中的“字宽” 与“字高” 的设置才是真正对显示到液晶屏上的字体的大小的设置。
                ②对话框中“点阵” 的值会对字模的编码产生影响。
        因为 OLED 是从第一列水平向右写入数据, 取模时也是从左往右的那样的取, 所以就拿一个 16*16 的汉字来讲, 将汉字的上半截 16 列数据, 可以分成很多份, 而点阵=字宽/
份数即 16*16 的汉字, 字宽为 16, 若将其分为两份, 则每份存 8 列的数据若将其分为一份,则每份存 16 列的数据。
        例“元” 字宽 16 字高 16:

//点阵=8
{0xDF,0xDF,0xDD,0xDD,0xDD,0x1D,0xDD,0xDD},
{0xDD,0x1D,0xDD,0xDD,0xDD,0xDF,0xDF,0xFF},
{0x7F,0xBF,0xDF,0xEF,0xF3,0xFC,0xFF,0xFF},
{0xFF,0xC0,0xBF,0xBF,0xBF,0xBF,0x87,0xFF}
//点阵=16
{0xDF,0xDF,0xDD,0xDD,0xDD,0x1D,0xDD,0xDD,0xDD,0x1D,0xDD,0xDD,0xDD,0xDF,0xDF,0xFF},
{0x7F,0xBF,0xDF,0xEF,0xF3,0xFC,0xFF,0xFF,0xFF,0xC0,0xBF,0xBF,0xBF,0xBF,0x87,0xFF}

可以看出, 点阵 8 和点阵 16 的数据是完全一样的, 只不过就是这个二维数组中的每个一维的数据位数不同罢了。 点阵 8 和点阵 16 都可以显示这个“元” 字, 只是程序上会有些不同。 推荐字宽*字高配置:()8*8——点阵8(2)16*16——点阵16(3)24*24——点阵24(4)32*32——点阵32(5)40*40——点阵40

5.4 图像取模

①图片初级处理:因为整个 OLED 的屏幕点阵为 128*64 个, 所以所有的图片的像素大小都要为宽 128, 高 64, 我指的是全屏的图片。 尺寸可以比 128*64 小。 并且由于OLED 为单色屏, 所以要把图片转成黑白色。 所以第一步处理图片。
        ②导入图片, 模式选择图形模式
        ③设置取模, 参考汉字取模, 这里不再赘述。

STM32-OLED显示相关推荐

  1. STM32——OLED显示实验

    一.关于OLED 1.OLED(有机发光二极管)又称有机电激光显示.OLED同时具备自发光,不需背光源.对比度高.厚度薄.视角广.反应速度快.可用于挠曲面板.使用温度范围广.构造及制作过程简单等特性. ...

  2. STM32 OLED显示字符汉字

    本文代码使用 HAL 库. 文章目录 前言 一. OLED 接线: 二.点阵: 三.写数据,命令函数 四.显示字符,字符串: 总结 前言 OLED 屏幕是一种高对比度.高亮度.低功耗.灵活可弯曲的显示 ...

  3. 基于STM32进行OLED显示

    目录 一.SPI接口简介 1.SPI的简介 2. SPI 接口4条线通信: 3.SPI的特征 4.接口框图 5.SPI整体的通讯过程 二.OLED的原理 1.OLED模块参数 2.该模块的特点: 3. ...

  4. 【嵌入式基础】基于IIC和SPI协议的温湿度采集与OLED显示

    本文主要介绍IIC总线通信协议和SPI协议,并使用STM32系列芯片基于IIC协议实现AHT20温湿度传感器上位机数据采集,基于SPI协议实现OLED显示. 目录 一.IIC总线通信协议 1.IIC协 ...

  5. 【STM32】OLED 显示实验代码详解

    文章目录 main.c oled.c oled.h main.c #include "led.h" #include "delay.h" #include &q ...

  6. STM32中断中调用OLED显示出现OLED花屏

    STM32中断中调用OLED显示出现OLED花屏 在TIM5中断处理函数中,我写了OLED显示,经过DEBUG发现程序执行到OLED显示语句的时候,OLED就会花屏. //中断服务处理函数 void ...

  7. STM32通过I2C接口实现温湿度(AHT20)的采集与OLED显示及显示姓名学号

    文章目录 一.了解I2C总线协议 二.实现AHT20采集程序 三.温湿度采集--OLED显示 三.总结 四.参考链接 一.了解I2C总线协议 1.什么是I2C协议 I2C 通讯协议(Inter-Int ...

  8. 【STM32】OLED显示程序

    00. 目录 文章目录 00. 目录 01. OLED概述 02. OLED初始化 03. 硬件设计 04. 程序示例 05. 结果显示 06. 附录 07. 声明 01. OLED概述 OLED,即 ...

  9. 基于stm32与陀螺仪(mpu6050)的PID角度环算法,角度用OLED显示,使得智能车能在长时间跑直线和转直角弯,减小误差

    首先,我做智能车用的是stm32f103c8t6作为主控芯片,得到小车自身对于开始位置的三维变换角度所用的是mpu6050模块,其与主控芯片采用I2C通信.此通信原理接下来会加入介绍资料.其次还有一个 ...

  10. 基于STM32和ATH20实现OLED显示温湿度

    基于STM32和ATH20实现OLED显示温湿度 什么是OLED 一.主要代码 二. 硬件连接及结果 1.硬件连接 2.烧录显示 总结 参考文献 什么是OLED 有机电致发光器件(OLED)属于低电压 ...

最新文章

  1. 数据科学中的6个基本算法,掌握它们要学习哪些知识
  2. SQL Server 常用分页SQL
  3. 句法依存分析_复旦大学邱锡鹏教授:词法、句法分析研究进展综述
  4. 走近伏羲,谈5000节点集群调度与性能优化
  5. POJ 1011 Sticks
  6. java中的关键字有哪些_java关键字有哪些?java关键字大全
  7. 前馈控制、反馈控制及前馈-反馈控制的对比
  8. zoom会议背景设置,zoom手机背景怎么设置
  9. 大神详细的ACM训练计划
  10. 计算机固态加机械硬盘,在台式机中添加固态/机械硬盘驱动器,让我与这篇文章一起教你...
  11. 【IoT】 产品设计:结构设计之PCB设计(三)
  12. google skia
  13. Echarts 3D地图图表
  14. 习题5-4 使用函数求素数和 (20 分)
  15. java swing 嵌入地图_Java学习笔记之SWING — 基本SWING程序(实现外部地图文件导入并显示) | 学步园...
  16. 22/10/08 vue2项目,登录注册路由守卫
  17. VScode中配置 C/C++ 环境,超级详细,问题分析全面,绝对好用
  18. 【博客546】组播IP地址与组播MAC地址的联系与转换
  19. 阿里云产品推荐——网关
  20. 在packet tracer模拟器中创建拓扑并重置密码

热门文章

  1. 怎么在地图上画圆 php,SOSO地图API使用(一)在地图上画圆实现思路与代码
  2. 计算机维修法宝,计算机的三大法宝
  3. Linux文件名中加入时间
  4. 考研政治——选择题判断原则
  5. docker删除mongo数据库库_Docker 搭建MongoDB环境
  6. python程序的运行速度在所有计算机语言中最快_【编程专题】实测四种编程语言的运算速度,最快的竟然是它!...
  7. python骰子游戏分析_python 用python写一个骰子游戏
  8. 在C语言中如何让常量起作用,解析C语言中如何正确使用const
  9. 为什么 jmeter 分布式测试,一定要设置 java.rmi.server.hostname
  10. Linux CA证书与https讲解