你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STM32F103系列实验--OLED显示实验

[复制链接]
beautiful阿朱 发布时间:2015-3-11 18:27
OLED 显示实验
前面所有的介绍都没有涉及到液晶显示,从这一节开始,我们将陆续向大家介绍几款液晶
显示模块。本节我们将向大家介绍相对简单的 OLED。本节分为如下几个部分:
1 OLED 简介
OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic
Electroluminesence  Display,  OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、
厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优
异之特性,被认为是下一代的平面显示器新兴应用技术。
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要
来得好一些。OLED 的尺寸难以大型化,但是分辨率确可以做到很高。这一节,我们使用的是
ALINETEK 的 OLED 显示模块,该模块有以下特点:
1)模块有单色和双色两种可选,单色为纯白色,而双色则为黄蓝双色。
2)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mm*26mm 大小。
3)高分辨率,该模块的分辨率为 128*64。
4)多种接口方式,该模块提供了总共 5 种接口包括:6800、8080 两种并行接口方式、3
线或 4 线的穿行 SPI 接口方式,、IIC 接口方式(只需要 2 根线就可以控制 OLED 了!)。
5)不需要高压,直接接 3.3V 就可以工作了。
这里要提醒大家的是,该模块不和 5.0V 接口兼容,所以请大家在使用的时候一定要小心,
别接到 5V 的系统上去,否则可能烧坏模块。以上 5 种模式通过模块的 BS0~2 设置,BS0~2 的
设置与模块接口模式的关系如下表: 360截图20150311181531677.jpg
上表中:“1”代表接 VCC,而“0”代表接 GND。该模块的外观图如下: 360截图20150311181613436.jpg
360截图20150311181659203.jpg
该模块采用 8*2 的 2.54 排针与外部连接,其引线图如上图所示,总共有 16 个管脚,在 16
条线中,我们只用了 15 条,有一个是悬空的。15 条线中,电源和地线占了 2 条,还剩下 13 条
信号线。在不同模式下,我们需要的信号线数量是不同的,在 8080 模式下,需要全部 13 条,
而在 IIC 模式下,仅需要 2 条线就够了!这其中有一条是共同的,那就是复位线 RST(RES),
该线我们可以直接接在 MCU 的复位上(要先确认复位方式一样),这样可以省掉一条线。
ALIENTEK  OLED 模块的控制器是 SSD1306,这一节,我们将学习如何通过 STM32 来控
制该模块显示字符和数字,本节实例将可以支持 2 种方式与 OLED 模块连接,一种是 8080 的
并口方式,另外一种是 4 线 SPI 方式。
首先我们介绍一下模块的 8080 并行接口,8080 并行接口的发明者是 INTEL,该总线也被
广泛应用于各类液晶显示器,ALIENTEK OLED 模块也提供了这种接口,使得 MCU 可以快速
的访问 OLED。ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线:
CS:OLED 片选信号。
WR:向 OLED 写入数据。
RD:从 OLED 读取数据。
D[7:0]:8 位双向数据线。
RST(RES):硬复位 OLED。
DC:命令/数据标志(0,读写命令;1,读写数据)。
模块的 8080 并口读/写的过程为:先根据要写入/读取的数据的类型,设置 DC 为高(数据)
/低(命令),然后拉低片选,选中 SSD1306,接着我们根据是读数据,还是要写数据置 RD/WR
为低,然后:
在 RD 的上升沿,  使数据锁存到数据线(D[7:0])上;
在 WR 的上升沿,使数据写入到 SSD1306 里面;
SSD1306 的 8080 并口写时序图如下: 360截图20150311181815642.jpg
360截图20150311181824323.jpg 360截图20150311181830682.jpg
在 8080 方式下读数据操作的时候,我们有时候(例如读显存的时候)需要一个假读命
(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之
前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,
才是我们真正要读的数据。
一个典型的读显存的时序图,如下图所示:
360截图20150311182021513.jpg
可以看到,在发送了列地址之后,开始读数据,第一个是 Dummy  Read,也就是假读,我
们从第二个开始,才算是真正有效的数据。
并行接口模式就介绍到这里,我们接下来介绍一下 4 线串行(SPI)方式,4 先串口模式使
用的信号线有如下几条:
CS:OLED 片选信号。
RST(RES):硬复位 OLED。
DC:命令/数据标志(0,读写命令;1,读写数据)。
SCLK:串行时钟线。在 4 线串行模式下,D0 信号线作为串行时钟线 SCLK。
SDIN:串行数据线。在 4 线串行模式下,D1 信号线作为串行数据线 SDIN。
模块的 D2 需要悬空,其他引脚可以接到 GND。在 4 线串行模式下,只能往模块写数据而
不能读数据。
在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到
SSD1306,并且是高位在前的。DC 线还是用作命令/数据的标志线。在 4 线 SPI 模式下,写操
作的时序如下: 360截图20150311182328656.jpg
4 线串行模式就为大家介绍到这里。其他还有几种模式,在 SSD1306 的数据手册上都有详
细的介绍,如果要使用这些方式,请大家参考该手册。
接下来,我们介绍一下模块的显存,SSD1306 的显存总共为 128*64bit 大小,SSD1306 将
这些显存分为了 8 页,其对应关系如下: 360截图20150311182402473.jpg
可以看出,SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128*64 的点阵大
小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,
那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位
都搞清楚当前的状态(0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要
显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读
出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会
影响到之前的状况了。但是这样需要能读 GRAM,对于 3 线或 4 线 SPI 模式,模块是不支持读
的,而且读->改->写的方式速度也比较慢。
所以我们采用的办法是在 STM32 的内部建立一个 OLED 的 GRAM (共 128 个字节),在每
次修改的时候,只是修改 STM32 上的 GRAM (实际上就是 SRAM),在修改完了之后,一次性
把 STM32 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,就是对于那些 SRAM
很小的单片机(比如 51 系列)就比较麻烦了。
SSD1306 的命令比较多,这里我们仅介绍几个比较常用的命令,这些命令如下表:
360截图20150311182439701.jpg
第一个命令为 0X81,用于设置对比度的,这个命令包含了两个字节,第一个 0X81 为命令,
随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。
第二个命令为 0XAE/0XAF。0XAE 为关闭显示命令;0XAF 为开启显示命令。
第三个命令为 0X8D,该指令也包含 2 个字节,第一个为命令字,第二个为设置值,第二
个字节的 BIT2 表示电荷泵的开关状态,该位为 1,则开启电荷泵,为 0 则关闭。在模块初始化
的时候,这个必须要开启,否则是看不到屏幕显示的。
第四个命令为 0XB0~B7,该命令用于设置页地址,其低三位的值对应着 GRAM 的页地址。
第五个指令为 0X00~0X0F,该指令用于设置显示时的起始列地址低四位。
第六个指令为 0X10~0X1F,该指令用于设置显示时的起始列地址高四位。
其他命令,我们就不在这里一一介绍了,大家可以参考 SSD1306  datasheet 的第 28 页。从
这页开始,对 SSD1306 的指令有详细的介绍。OLED 的介绍就到此为止,我们重点向大家介绍了 ALIENTEK OLED 模块的相关知识,接
下来我们将使用这个模块来显示字符和数字。通过以上介绍,我们可以得出 OLED 显示需要的
相关设置步骤如下:
1)设置 STM32 与 OLED 模块相连接的 IO。
这一步,先将我们与 OLED 模块相连的 IO 口设置为输出,具体使用哪些 IO 口,这里需要根据
连接电路以及 OLED 模块所设置的通讯模式来确定。这些将在硬件设计部分向大家介绍。
2)初始化 OLED 模块。
其实这里就是上面的初始化框图的内容,通过对 OLED 相关寄存器的初始化,来启动 OLED 的
显示。为后续显示字符和数字做准备。
3)通过函数将字符和数字显示到 OLED 模块上。
这里就是通过我们设计的程序,将要显示的字符送到 OLED 模块就可以了,这些函数将在软件
设计部分向大家介绍。
通过以上三步,我们就可以使用 ALIENTEK OLED 模块来显示字符和数字了,在后面我们还将
会给大家介绍显示汉字的方法。这一部分就先介绍到这里。

3  软件设计
软件设计我们依旧在之前的工程上面增加,首先在 HARDWARE 文件夹下新建一个 OLED
的文件夹。然后打开 USER 文件夹下的工程,新建一个 oled.c 的文件和 oled.h 的头文件,保存
在 OLED 文件夹下,并将 OLED 文件夹加入头文件包含路径。
打开 oled.c,输入如下代码:
#include "oled.h"
#include "stdlib.h"
#include "font.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],OLED_DATA);
}   
}
#if OLED_MODE==1
//向 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 ON
OLED_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 OFF
OLED_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]=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:选择字体  16/12
void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode)
{            
u8 temp,t,t1;
u8 y0=y;
chr=chr-' ';//得到偏移后的值      
for(t=0;t<size;t++)
{   
if(size==12)temp=asc2_1206[chr][t];   //调用 1206 字体
else temp=asc2_1608[chr][t];      //调用 1608 字体                        
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:起点坐标
//*p:字符串起始地址
//用 16 字体
void OLED_ShowString(u8 x,u8 y,const u8 *p)
{
#define MAX_CHAR_POSX 122
#define MAX_CHAR_POSY 58         
while(*p!='\0')
{      
if(x>MAX_CHAR_POSX){x=0;y+=16;}
if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();}
OLED_ShowChar(x,y,*p,16,1);
x+=8;
p++;
}  
}   
//初始化 SSD1303           
void OLED_Init(void)
{                             
RCC->APB2ENR|=1<<3;     //使能 PORTB 时钟
RCC->APB2ENR|=1<<4;     //使能 PORTC 时钟     
#if OLED_MODE==1         
RCC->APB2ENR|=1<<0;     //开启辅助时钟
AFIO->MAPR=0X04000000; //关闭 JTAG
GPIOB->CRL=0X33333333;
GPIOB->ODR|=0XFFFF;
GPIOC->CRH&=0XFFFFFF00;
GPIOC->CRL&=0X00FFFFFF;
GPIOC->CRH|=0X00000033;
GPIOC->CRL|=0X33000000;
GPIOC->ODR|=0X03C0;
#else
GPIOB->CRL&=0XFFFFFF00;
GPIOB->CRL|=0XF0000033;
GPIOB->ODR|=0X03;
GPIOC->CRH&=0XFFFFFF00;
GPIOC->CRH|=0X00000033;
GPIOC->ODR|=3<<8;
#endif         
//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); //默认为 0
OLED_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();
}  
这里代码明显比之前的例程多了,函数也比较多,这里我们仅针对几个比较重要的函数进
行介绍。
首先要介绍的是我们定义在 STM32 内部的 GRAM,u8  OLED_GRAM[128][8];此部分
GRAM 对应 OLED 模块上的 GRAM。在操作的时候,我们只要修改 STM32 内部的 GRAM 就
可以了,然后通过 OLED_Refresh_Gram 函数把 GRAM 一次刷新到 OLED  的 GRAM 上。该函
数代码如下:
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],OLED_DATA);
}     
}
函数先设置页地址,然后写入列地址(也就是纵坐标),然后从 0 开始写入 128 个字节,写
满该页,最后循环把 8 页的内容都写入,就实现了整个从 STM32 显存到 OLED 显存的拷贝。   。
OLED_Refresh_Gram 函数还用到了一个外部函数 OLED_WR_Byte,该函数直接和硬件相关,
该函数代码如下:
#if OLED_MODE==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
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
这里有 2 个一样的函数,通过宏定义 OLED_MODE 来决定使用哪一个。如果
OLED_MODE=1,就定义为并口模式,选择第一个函数,而如果为 0,则为 4 线串口模式,选
择第二个函数。这两个函数输入参数均为 2 个:dat 和 cmd,dat 为要写入的数据,cmd 则表明
该数据是命令还是数据。这两个函数的时序操作就是根据上面我们对 8080 接口以及 4 线 SPI
接口的时序来编写的。
OLED_GRAM[128][8]中的 128 代表列数,也就是 x 坐标,而 8 代表的是页,每个代表 8
个列,从高到底对应列数从小到大。比如,我们要在 x=100,y=29 这个点写入 1,则可以用这
个句子实现:
OLED_GRAM[100][4]|=1<<2;
一个通用的在点(x,y)置 1 表达式为:
OLED_GRAM[x][y/8]|=1<<(7-y%8);
因此,我们可以得出下一个画点函数, void OLED_DrawPoint(u8 x, u8 y, u8 t);代码如下:
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;   
}



收藏 3 评论8 发布时间:2015-3-11 18:27

举报

8个回答
sxdahss 回答时间:2015-3-11 19:01:51
过来看看
wu1169668869 回答时间:2015-3-11 19:13:51
谢谢分享,
qianfan 回答时间:2015-3-11 19:24:14
试过I2C的方式没?I2C刷屏快不?
32F 回答时间:2015-12-1 11:50:11


好文章,值得学习
缘,妙不可言 回答时间:2017-5-20 14:55:36
看看
秋狂言 回答时间:2017-6-4 15:29:51
……
tangyuang 回答时间:2017-9-16 11:21:30
路过!~学习学习!~感谢分享!~
lzhus 回答时间:2017-9-24 16:20:36
标题不到ucos么

所属标签

相似分享

官网相关资源

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版