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

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么

所属标签

STM32团队

意法半导体微控制器和微处理器拥有广泛的产品线,包含低成本的8位单片机和基于ARM® Cortex®-M0、M0+、M3、M4、M33、M7及A7内核并具备丰富外设选择的32位微控制器及微处理器


最新内容

相似技术帖

官网相关资源

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