
OLED 显示实验4 `/ u' B, i% U/ _) A5 {6 I1 h 前面所有的介绍都没有涉及到液晶显示,从这一节开始,我们将陆续向大家介绍几款液晶: e8 }# B* w" c2 v$ C, Y: W4 o! I( [ 显示模块。本节我们将向大家介绍相对简单的 OLED。本节分为如下几个部分: 1 OLED 简介 OLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display, OELD)。OLED 由于同时具备自发光,不需背光源、对比度高、. v; l, m7 o$ } c 厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优! Z% X1 f& c {2 K0 ~ 异之特性,被认为是下一代的平面显示器新兴应用技术。1 w7 F6 L6 Q z. H LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要% R4 S7 |8 Z- z, j- l- }. n; O1 H 来得好一些。OLED 的尺寸难以大型化,但是分辨率确可以做到很高。这一节,我们使用的是 ALINETEK 的 OLED 显示模块,该模块有以下特点: 1)模块有单色和双色两种可选,单色为纯白色,而双色则为黄蓝双色。 2)尺寸小,显示尺寸为 0.96 寸,而模块的尺寸仅为 27mm*26mm 大小。 3)高分辨率,该模块的分辨率为 128*64。 4)多种接口方式,该模块提供了总共 5 种接口包括:6800、8080 两种并行接口方式、3 线或 4 线的穿行 SPI 接口方式,、IIC 接口方式(只需要 2 根线就可以控制 OLED 了!)。+ Q% f9 k1 f7 {, f 5)不需要高压,直接接 3.3V 就可以工作了。: I3 M1 r; a* C* @0 ]5 k 这里要提醒大家的是,该模块不和 5.0V 接口兼容,所以请大家在使用的时候一定要小心, 别接到 5V 的系统上去,否则可能烧坏模块。以上 5 种模式通过模块的 BS0~2 设置,BS0~2 的 设置与模块接口模式的关系如下表: ![]() 上表中:“1”代表接 VCC,而“0”代表接 GND。该模块的外观图如下: ![]() ![]() 该模块采用 8*2 的 2.54 排针与外部连接,其引线图如上图所示,总共有 16 个管脚,在 167 T& c) f8 f) c1 Q5 E 条线中,我们只用了 15 条,有一个是悬空的。15 条线中,电源和地线占了 2 条,还剩下 13 条0 g5 T9 `/ U$ v: q+ U H 信号线。在不同模式下,我们需要的信号线数量是不同的,在 8080 模式下,需要全部 13 条, 而在 IIC 模式下,仅需要 2 条线就够了!这其中有一条是共同的,那就是复位线 RST(RES),: f, B4 E# a! g4 S9 H' @: a 该线我们可以直接接在 MCU 的复位上(要先确认复位方式一样),这样可以省掉一条线。/ z! U, J% w" S, r9 h ALIENTEK OLED 模块的控制器是 SSD1306,这一节,我们将学习如何通过 STM32 来控 制该模块显示字符和数字,本节实例将可以支持 2 种方式与 OLED 模块连接,一种是 8080 的0 {/ s' A3 m, D3 V: D 并口方式,另外一种是 4 线 SPI 方式。3 L! q3 r) q1 q( w 首先我们介绍一下模块的 8080 并行接口,8080 并行接口的发明者是 INTEL,该总线也被1 a- H2 \: ^/ u# ?/ j! T1 J 广泛应用于各类液晶显示器,ALIENTEK OLED 模块也提供了这种接口,使得 MCU 可以快速% w' v O! ~" O6 b( o 的访问 OLED。ALIENTEK OLED 模块的 8080 接口方式需要如下一些信号线: U2 ?* U C% u: O: k( y' N: `, l I; l CS:OLED 片选信号。 WR:向 OLED 写入数据。/ _) u( u, }. S2 f RD:从 OLED 读取数据。 D[7:0]:8 位双向数据线。8 @: C: A" Z k! Z( M! | RST(RES):硬复位 OLED。 , b* z; d. e) f8 S DC:命令/数据标志(0,读写命令;1,读写数据)。9 K4 j6 Z t/ ^$ I 模块的 8080 并口读/写的过程为:先根据要写入/读取的数据的类型,设置 DC 为高(数据) /低(命令),然后拉低片选,选中 SSD1306,接着我们根据是读数据,还是要写数据置 RD/WR( |2 G6 L3 D/ R: W& V% G' z 为低,然后: c' m5 w3 m _% v1 n b 在 RD 的上升沿, 使数据锁存到数据线(D[7:0])上; 在 WR 的上升沿,使数据写入到 SSD1306 里面;$ K* W3 T% G/ {; r SSD1306 的 8080 并口写时序图如下: ![]() ![]() ![]() 在 8080 方式下读数据操作的时候,我们有时候(例如读显存的时候)需要一个假读命; R8 d2 v; q2 a( P; c# G) C (Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之 前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,. {, n3 D$ o# c% P 才是我们真正要读的数据。 一个典型的读显存的时序图,如下图所示: 8 i/ y; M/ J) ~+ r ![]() 可以看到,在发送了列地址之后,开始读数据,第一个是 Dummy Read,也就是假读,我 们从第二个开始,才算是真正有效的数据。 并行接口模式就介绍到这里,我们接下来介绍一下 4 线串行(SPI)方式,4 先串口模式使 用的信号线有如下几条:! e$ B: X( l& d& V: W6 ] CS:OLED 片选信号。 RST(RES):硬复位 OLED。 DC:命令/数据标志(0,读写命令;1,读写数据)。( x! u. {2 A7 a SCLK:串行时钟线。在 4 线串行模式下,D0 信号线作为串行时钟线 SCLK。 SDIN:串行数据线。在 4 线串行模式下,D1 信号线作为串行数据线 SDIN。 模块的 D2 需要悬空,其他引脚可以接到 GND。在 4 线串行模式下,只能往模块写数据而 不能读数据。 在 4 线 SPI 模式下,每个数据长度均为 8 位,在 SCLK 的上升沿,数据从 SDIN 移入到 SSD1306,并且是高位在前的。DC 线还是用作命令/数据的标志线。在 4 线 SPI 模式下,写操 作的时序如下: ![]() 4 线串行模式就为大家介绍到这里。其他还有几种模式,在 SSD1306 的数据手册上都有详 细的介绍,如果要使用这些方式,请大家参考该手册。2 o/ G) M' b" r% f: N! x 接下来,我们介绍一下模块的显存,SSD1306 的显存总共为 128*64bit 大小,SSD1306 将; L* y5 {) \# g 这些显存分为了 8 页,其对应关系如下: ![]() 可以看出,SSD1306 的每页包含了 128 个字节,总共 8 页,这样刚好是 128*64 的点阵大 小。因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,3 v( {9 o3 B# \. u 那么,每次要写 8 个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位 都搞清楚当前的状态(0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要# [' o' y6 ?5 x+ _" ?6 Y% G7 _! ?3 L 显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读 出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进 GRAM,这样就不会: ^. W K2 k- Q( [ 影响到之前的状况了。但是这样需要能读 GRAM,对于 3 线或 4 线 SPI 模式,模块是不支持读 的,而且读->改->写的方式速度也比较慢。3 j/ U! q1 ^4 L# G& q: w 所以我们采用的办法是在 STM32 的内部建立一个 OLED 的 GRAM (共 128 个字节),在每! D9 A7 S0 h& e. l. | 次修改的时候,只是修改 STM32 上的 GRAM (实际上就是 SRAM),在修改完了之后,一次性& d% a: j# A/ {. |: B8 ?2 T6 A5 x' b 把 STM32 上的 GRAM 写入到 OLED 的 GRAM。当然这个方法也有坏处,就是对于那些 SRAM 很小的单片机(比如 51 系列)就比较麻烦了。. u& e* W. X: C2 k7 U SSD1306 的命令比较多,这里我们仅介绍几个比较常用的命令,这些命令如下表: ![]() 第一个命令为 0X81,用于设置对比度的,这个命令包含了两个字节,第一个 0X81 为命令, 随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。& R) n3 H V6 C+ q1 ]5 w 第二个命令为 0XAE/0XAF。0XAE 为关闭显示命令;0XAF 为开启显示命令。 第三个命令为 0X8D,该指令也包含 2 个字节,第一个为命令字,第二个为设置值,第二; |" _, u& L6 b6 u5 d/ j+ m" v7 O 个字节的 BIT2 表示电荷泵的开关状态,该位为 1,则开启电荷泵,为 0 则关闭。在模块初始化 的时候,这个必须要开启,否则是看不到屏幕显示的。 第四个命令为 0XB0~B7,该命令用于设置页地址,其低三位的值对应着 GRAM 的页地址。 第五个指令为 0X00~0X0F,该指令用于设置显示时的起始列地址低四位。 第六个指令为 0X10~0X1F,该指令用于设置显示时的起始列地址高四位。 t( {- @* x0 \* d 其他命令,我们就不在这里一一介绍了,大家可以参考 SSD1306 datasheet 的第 28 页。从 这页开始,对 SSD1306 的指令有详细的介绍。OLED 的介绍就到此为止,我们重点向大家介绍了 ALIENTEK OLED 模块的相关知识,接 下来我们将使用这个模块来显示字符和数字。通过以上介绍,我们可以得出 OLED 显示需要的4 e6 r4 B4 t! s" ^; z* j, v2 w 相关设置步骤如下:3 k3 K/ y& Z, j l/ b 1)设置 STM32 与 OLED 模块相连接的 IO。0 I: w- L' V6 k9 s; B 这一步,先将我们与 OLED 模块相连的 IO 口设置为输出,具体使用哪些 IO 口,这里需要根据 连接电路以及 OLED 模块所设置的通讯模式来确定。这些将在硬件设计部分向大家介绍。 2)初始化 OLED 模块。- f" L# y% u* R& [) r" M C( s: l 其实这里就是上面的初始化框图的内容,通过对 OLED 相关寄存器的初始化,来启动 OLED 的 显示。为后续显示字符和数字做准备。 3)通过函数将字符和数字显示到 OLED 模块上。8 o. W- F, `1 e+ c; S/ i: l 这里就是通过我们设计的程序,将要显示的字符送到 OLED 模块就可以了,这些函数将在软件 设计部分向大家介绍。 通过以上三步,我们就可以使用 ALIENTEK OLED 模块来显示字符和数字了,在后面我们还将4 _! t% u4 v7 o9 B/ f 会给大家介绍显示汉字的方法。这一部分就先介绍到这里。 $ y& T. e( }: x 3 软件设计, M( }/ R, c$ p& Q4 B$ S8 H 软件设计我们依旧在之前的工程上面增加,首先在 HARDWARE 文件夹下新建一个 OLED i' r4 v+ v7 n+ ]& w 的文件夹。然后打开 USER 文件夹下的工程,新建一个 oled.c 的文件和 oled.h 的头文件,保存 在 OLED 文件夹下,并将 OLED 文件夹加入头文件包含路径。 打开 oled.c,输入如下代码:. }) ~( G* e' _ F& k4 S& N" N #include "oled.h" #include "stdlib.h"6 [- ?/ W* ~) w# O% V. [ #include "font.h" #include "delay.h"5 _0 N0 i6 U4 ]" d //OLED 的显存 //存放格式如下. //[0]0 1 2 3 ... 127 , ^: z/ @8 r9 v. s //[1]0 1 2 3 ... 127 //[2]0 1 2 3 ... 127 : M& d8 a A& w6 j5 y7 B) i //[3]0 1 2 3 ... 127 //[4]0 1 2 3 ... 127 3 ^. V v, a1 ]3 n //[5]0 1 2 3 ... 127 & k, s% T$ i. t: E, i9 [ //[6]0 1 2 3 ... 127 # w: p0 `1 c) ]- o. ^4 o0 ~ //[7]0 1 2 3 ... 127 * w7 X( _" {8 | u8 OLED_GRAM[128][8]; //更新显存到 LCD . M$ w, e8 G. F5 g5 J3 r void OLED_Refresh_Gram(void)& J! r: n" q" Z { u8 i,n; for(i=0;i<8;i++) { & R4 F2 O! z5 O5 [6 \5 e/ \* \! }! E OLED_WR_Byte (0xb0+i,OLED_CMD); //设置页地址(0~7)6 D# H' _' @) ]; E( p, i* R OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址 4 j+ b; m1 _9 t0 D OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n],OLED_DATA); . o- N, g+ [# W4 J } 8 k) i9 h) @5 @# s3 T6 v$ t9 I% o }, u( k& g+ C' A2 ]4 e- Z0 ~1 W #if OLED_MODE==1/ x, p$ _0 B$ Q //向 SSD1306 写入一个字节。0 x; k. ~8 z5 X- _ //dat:要写入的数据/命令 //cmd:数据/命令标志 0,表示命令;1,表示数据;; d+ ]: N2 L- h8 h/ f |1 b void OLED_WR_Byte(u8 dat,u8 cmd) { DATAOUT(dat);$ u1 T' ^) D7 u% V& b# |8 B( N OLED_RS=cmd; OLED_CS=0; OLED_WR=0; OLED_WR=1; OLED_CS=1; OLED_RS=1; 4 P3 E# L; b/ u } #else //向 SSD1306 写入一个字节。( u/ n, @) M% r' q& ?+ U$ c1 ?4 W //dat:要写入的数据/命令8 y3 R5 u) S' S9 ?% _ //cmd:数据/命令标志 0,表示命令;1,表示数据; void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; / `+ Z: _) g" [0 @ OLED_RS=cmd; //写命令# t3 [& N0 o8 x; ?5 k2 D OLED_CS=0; " [; \& @( f* C1 h- n for(i=0;i<8;i++) { OLED_SCLK=0;$ |9 O7 b- C- Q# ~1 e1 U5 ^ if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0; OLED_SCLK=1; dat<<=1; 1 J, ?% z0 L( f' S! p } OLED_CS=1; OLED_RS=1; 5 ?" T7 q" L8 `+ o( E6 G, V } 2 a. l/ f ^' z7 N: c& m! h5 Y/ | #endif //开启 OLED 显示 3 }, G' U) Q' ]4 d$ p. x# r void OLED_Display_On(void)9 E% S {( s8 Z8 g5 }" j { OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC 命令 OLED_WR_Byte(0X14,OLED_CMD); //DCDC ON/ P% R! W5 o" U* o& ]" r OLED_WR_Byte(0XAF,OLED_CMD); //DISPLAY ON$ h4 ?- `, P8 p% v }9 |& _" A" L1 | //关闭 OLED 显示 & Q9 ~$ P" y; p: R3 E- _$ ~& a7 N" } void OLED_Display_Off(void) {: Q3 S% [6 u2 {1 }1 \) f OLED_WR_Byte(0X8D,OLED_CMD); //SET DCDC 命令& [/ C9 Y( r" y' s) y/ l OLED_WR_Byte(0X10,OLED_CMD); //DCDC OFF2 f! X" C' c2 C* B7 r* y9 ? OLED_WR_Byte(0XAE,OLED_CMD); //DISPLAY OFF$ k/ `; W7 \& ~- o9 C) Q4 S$ { } : l; }7 [' H) L3 U/ g( U8 W; [8 f* H; _ //清屏函数,清完屏,整个屏幕是黑色的!和没点亮一样!!! 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();//更新显示0 Y3 s4 d) d# l; t1 {) n' X } * o0 X# i7 R+ I4 V6 l& ~ //画点 2 j5 s9 @0 T; i/ S* E3 \1 d //x:0~127 //y:0~63 //t:1 填充 0,清空 0 J8 g2 \ X6 a. ] void OLED_DrawPoint(u8 x,u8 y,u8 t) {4 ?- }7 G$ ?& t0 u u8 pos,bx,temp=0;. T! h: `) ^) J; T8 W if(x>127||y>63)return;//超出范围了./ q& e% J# y( Y" G4 q; J9 R pos=7-y/8;0 c: q+ H L/ s& m bx=y%8;. S2 ^: Q7 D$ Q: @- I temp=1<<(7-bx); if(t)OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; * u* n5 f! N- x. Q3 b3 B }1 C. P' @. Q' t$ D8 u$ S //x1,y1,x2,y2 填充区域的对角坐标 //确保 x1<=x2;y1<=y2 0<=x1<=127 0<=y1<=63 % c, c; K4 ?* b0 r1 R: P& F# w //dot:0,清空;1,填充 7 K8 \/ g) Y3 {9 I" E& f) \( k! H void OLED_Fill(u8 x1,u8 y1,u8 x2,u8 y2,u8 dot) { ! }6 P A! @7 h( D K u8 x,y; for(x=x1;x<=x2;x++)) a, e2 p/ M5 T8 o {3 G4 L {% ^, e: r2 X for(y=y1;y<=y2;y++)OLED_DrawPoint(x,y,dot);/ z( y1 p: \+ {# ?5 h# \ } % j0 j& }. R0 H4 n @ OLED_Refresh_Gram();//更新显示 } //在指定位置显示一个字符,包括部分字符9 _; d ~7 b" q //x:0~127 //y:0~63: I, V y3 }2 A3 j //mode:0,反白显示;1,正常显示 % l5 s1 t* l" R/ N4 r) k //size:选择字体 16/12 ! v% w: ]/ z& @% Q. U void OLED_ShowChar(u8 x,u8 y,u8 chr,u8 size,u8 mode) { u8 temp,t,t1; u8 y0=y;) k! R( ?* M$ w+ |- i chr=chr-' ';//得到偏移后的值 for(t=0;t<size;t++), T3 G% K9 C) i: l3 l { if(size==12)temp=asc2_1206[chr][t]; //调用 1206 字体 else temp=asc2_1608[chr][t]; //调用 1608 字体 : f+ d* S& S7 ^5 K S" \/ B$ I! o! P6 e4 O for(t1=0;t1<8;t1++) { }; ]9 i7 y4 f2 Y4 `* k if(temp&0x80)OLED_DrawPoint(x,y,mode); 8 t- h( x% h* ~& \ else OLED_DrawPoint(x,y,!mode); temp<<=1; y++;& x* w( V2 A9 C if((y-y0)==size) {& C' D! V1 m9 K; d8 u0 }/ Z7 [* i y=y0; x++; break; }9 K. x4 B1 {/ \ Y } } } //m^n 函数7 O5 p* Y! m( i! g u32 mypow(u8 m,u8 n) { u32 result=1; # m- X1 Y9 n: W while(n--)result*=m; ( {9 A U6 m5 {* c. p5 K8 g0 Q9 B return result;+ N5 h3 o, w% x5 ^0 T3 h$ p' V } //显示 2 个数字' a/ O2 _' u |' {. l //x,y :起点坐标 //len :数字的位数- ~) m3 V8 S2 n& u& D5 ^ //size:字体大小 //mode:模式 0,填充模式;1,叠加模式 //num:数值(0~4294967295); void OLED_ShowNum(u8 x,u8 y,u32 num,u8 len,u8 size) { 6 R% s* h, q7 @( `- ~ u8 t,temp; u8 enshow=0; 5 ~8 g2 _; T5 [9 t for(t=0;t<len;t++) { temp=(num/mypow(10,len-t-1))%10; if(enshow==0&&t<(len-1))- ^! K, s( D7 k/ }' H" [+ K { if(temp==0) { OLED_ShowChar(x+(size/2)*t,y,' ',size,1);2 e, T j7 h* N; I- y% L* B( Y y# l continue;' S- a K6 C9 o1 L: ] }else enshow=1; - i% H- L- V0 {" J& d } OLED_ShowChar(x+(size/2)*t,y,temp+'0',size,1); }' i9 U( d# B N n1 G" c3 j } 6 ~/ b" @; _( {: { //显示字符串 //x,y:起点坐标 . Q5 J2 f& u: u7 ~! y$ ^ //*p:字符串起始地址3 n" Z- \* ~* n9 T //用 16 字体 void OLED_ShowString(u8 x,u8 y,const u8 *p)+ I0 a8 ~8 N7 E- w3 ~& U {7 R6 E' D6 R6 C: O1 k4 e1 O8 n #define MAX_CHAR_POSX 122' {7 w" z: _- P# H #define MAX_CHAR_POSY 58 8 k' Z, R( V: d7 [$ N9 ` while(*p!='\0') { if(x>MAX_CHAR_POSX){x=0;y+=16;}$ k1 {) V6 x5 l( p! L$ h if(y>MAX_CHAR_POSY){y=x=0;OLED_Clear();} OLED_ShowChar(x,y,*p,16,1); 6 r2 S7 Z2 C/ P$ v* u* V W! I x+=8; p++;/ M/ `+ A3 n4 p/ }2 b6 [ } . E! D9 `% c1 S) ^$ Y+ G6 U w } //初始化 SSD1303 void OLED_Init(void)! o+ w" Q& L# n+ f { RCC->APB2ENR|=1<<3; //使能 PORTB 时钟 RCC->APB2ENR|=1<<4; //使能 PORTC 时钟 #if OLED_MODE==1 RCC->APB2ENR|=1<<0; //开启辅助时钟 AFIO->MAPR=0X04000000; //关闭 JTAG+ m9 u6 @! `: _9 }4 P* v( ]& k6 p GPIOB->CRL=0X33333333;0 C) u- D% k( V0 l+ ~2 ]. x GPIOB->ODR|=0XFFFF; 1 C* _5 c; k3 f4 M* ~8 W+ B GPIOC->CRH&=0XFFFFFF00; GPIOC->CRL&=0X00FFFFFF; GPIOC->CRH|=0X00000033; GPIOC->CRL|=0X33000000; GPIOC->ODR|=0X03C0;' C2 Y( S! m+ \$ \: H #else9 y7 I( X$ P$ J GPIOB->CRL&=0XFFFFFF00;5 }% _* R! `$ r+ [ ~ GPIOB->CRL|=0XF0000033; GPIOB->ODR|=0X03;& J3 a7 C" Q# R) O$ L5 U% \' A GPIOC->CRH&=0XFFFFFF00;7 b. R$ M2 [& ]' b- k& T* q" Y GPIOC->CRH|=0X00000033; 0 p H& h% {7 }. o GPIOC->ODR|=3<<8; #endif * @. S- \- ]8 {6 w( t //OLED_RST=0; delay_ms(100); OLED_RST=1;) z3 S# {; d; ` OLED_WR_Byte(0xAE,OLED_CMD); //关闭显示% D K+ W% E! L+ u9 F/ M$ d 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/ y- q# o `, q OLED_WR_Byte(0x40,OLED_CMD); //设置显示开始行 [5:0],行数.6 H5 u9 Q! Z6 P# D OLED_WR_Byte(0x8D,OLED_CMD); //电荷泵设置 w0 [9 f0 v5 i9 v0 ?; ^' G- C2 r OLED_WR_Byte(0x14,OLED_CMD); //bit2,开启/关闭 OLED_WR_Byte(0x20,OLED_CMD); //设置内存地址模式5 r; L- ]7 W- o5 @ 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); Z2 e- e9 r$ e3 t //设置 COM 扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 OLED_WR_Byte(0xDA,OLED_CMD); //设置 COM 硬件引脚配置2 w' f0 j% p$ f m6 w OLED_WR_Byte(0x12,OLED_CMD); //[5:4]配置4 V: L1 g3 k: p- y8 a: [ OLED_WR_Byte(0x81,OLED_CMD); //对比度设置! J6 y/ J0 o& i# ~" ` OLED_WR_Byte(0xEF,OLED_CMD); //1~255;默认 0X7F (亮度设置,越大越亮) H: |. R+ X, M, {' h) N OLED_WR_Byte(0xD9,OLED_CMD); //设置预充电周期! r @4 T9 u8 N- L OLED_WR_Byte(0xf1,OLED_CMD); //[3:0],PHASE 1;[7:4],PHASE 2;% y/ a Y" a7 g ?6 v+ H 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,关闭;0 v" v/ P; H' F2 @. K2 S* a# k$ P OLED_WR_Byte(0xA6,OLED_CMD); //设置显示方式;bit0:1,反相显示;0,正常显示 OLED_WR_Byte(0xAF,OLED_CMD); //开启显示 OLED_Clear();9 N" Z8 b. q. I. C } 这里代码明显比之前的例程多了,函数也比较多,这里我们仅针对几个比较重要的函数进4 N. a5 ^7 l p 行介绍。' L6 F) _, Z B, V% s 首先要介绍的是我们定义在 STM32 内部的 GRAM,u8 OLED_GRAM[128][8];此部分 Z% R' S4 L6 k1 v O5 z" ~# @. h' ^7 F* n GRAM 对应 OLED 模块上的 GRAM。在操作的时候,我们只要修改 STM32 内部的 GRAM 就 可以了,然后通过 OLED_Refresh_Gram 函数把 GRAM 一次刷新到 OLED 的 GRAM 上。该函 数代码如下: void OLED_Refresh_Gram(void), ^( S4 c! Q: w3 W& V/ D3 z( T {6 o: p x) Y$ T, d u8 i,n; 0 x3 y h( P% t: p8 D8 S for(i=0;i<8;i++) { 2 l- u5 k+ C! k# w OLED_WR_Byte (0xb0+i,OLED_CMD);//设置页地址(0~7)7 Y. l4 ?3 }/ t3 o: @# H OLED_WR_Byte (0x00,OLED_CMD); //设置显示位置—列低地址1 @9 R$ M+ t9 w. N4 }& V OLED_WR_Byte (0x10,OLED_CMD); //设置显示位置—列高地址 for(n=0;n<128;n++)OLED_WR_Byte(OLED_GRAM[n],OLED_DATA); } }- A9 P0 Y" i' R; z 函数先设置页地址,然后写入列地址(也就是纵坐标),然后从 0 开始写入 128 个字节,写' R {* P$ q* N. r4 R 满该页,最后循环把 8 页的内容都写入,就实现了整个从 STM32 显存到 OLED 显存的拷贝。 。 OLED_Refresh_Gram 函数还用到了一个外部函数 OLED_WR_Byte,该函数直接和硬件相关, 该函数代码如下: #if OLED_MODE==1 : v+ V+ A6 P4 V. X- | void OLED_WR_Byte(u8 dat,u8 cmd) { DATAOUT(dat); OLED_RS=cmd;' g+ k' _+ {* g2 a' f OLED_CS=0; 4 h5 [4 T) M2 T% y6 y OLED_WR=0; OLED_WR=1;9 ~4 V( f1 n% @ K, h OLED_CS=1; OLED_RS=1; } , \4 j/ C: k0 \ #else void OLED_WR_Byte(u8 dat,u8 cmd) { u8 i; $ D; r2 A8 }4 M OLED_RS=cmd; //写命令3 i+ e1 e0 k, R OLED_CS=0; for(i=0;i<8;i++)" m/ K: J+ u4 D1 ]& u# `. { { - V8 I$ y$ e, t5 ^ OLED_SCLK=0;0 C, X; W' x1 W i if(dat&0x80)OLED_SDIN=1; else OLED_SDIN=0;4 h$ t, W q$ S OLED_SCLK=1;6 B0 ?% S9 {( S+ x, p- R dat<<=1; } 0 {$ b3 Q* |# }- u; ] OLED_CS=1; OLED_RS=1; 0 {( h$ R* z C3 s; O9 c } * J' Q* B1 {% A, y #endif9 ^0 j0 T/ A; M 这里有 2 个一样的函数,通过宏定义 OLED_MODE 来决定使用哪一个。如果 OLED_MODE=1,就定义为并口模式,选择第一个函数,而如果为 0,则为 4 线串口模式,选) G8 R- c4 `: o/ d8 n0 \9 W 择第二个函数。这两个函数输入参数均为 2 个:dat 和 cmd,dat 为要写入的数据,cmd 则表明/ N! X6 ]' Z0 Z) m/ L 该数据是命令还是数据。这两个函数的时序操作就是根据上面我们对 8080 接口以及 4 线 SPI1 ~$ ~4 x! [, F4 w 接口的时序来编写的。4 L4 \8 d/ S4 V1 j# {4 L4 z OLED_GRAM[128][8]中的 128 代表列数,也就是 x 坐标,而 8 代表的是页,每个代表 8/ |6 W. Y+ j, \6 u) O 个列,从高到底对应列数从小到大。比如,我们要在 x=100,y=29 这个点写入 1,则可以用这* M& L8 ~/ S; Z# b) ` 个句子实现: OLED_GRAM[100][4]|=1<<2; 一个通用的在点(x,y)置 1 表达式为: OLED_GRAM[x][y/8]|=1<<(7-y%8);( [5 a; J; m6 H8 b& B& R! r5 Q. z 因此,我们可以得出下一个画点函数, void OLED_DrawPoint(u8 x, u8 y, u8 t);代码如下:6 Y; W! b1 e- W void OLED_DrawPoint(u8 x,u8 y,u8 t)( q0 o6 ]/ S0 X% Z' {+ s {- u! y) q# I0 u& H5 \# j* P% H1 I u8 pos,bx,temp=0; if(x>127||y>63)return;//超出范围了.1 d- N( L& R0 {. j" ?' x/ k* j6 y pos=7-y/8; bx=y%8; temp=1<<(7-bx); if(t)OLED_GRAM[x][pos]|=temp; else OLED_GRAM[x][pos]&=~temp; 6 c9 D4 d( R4 J% y# u } & S9 |6 p- g9 q, O/ u+ a1 R $ {% |" V! I: ?- \ |
5 F. x5 @* E" T) T: W2 K
好文章,值得学习