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

MiniPro STM32H750 开发指南_V1.1-OLED显示实验

[复制链接]
STMCU小助手 发布时间:2022-10-7 16:49
OLED显示实验) C( t9 m. ^' E2 i, a
本章我们来学习使用OLED液晶显示屏,在开发板上我们预留了OLED模块接口,需要准备一个OLED显示模块。下面我们一起来点亮OLED,并实现ASCII字符的显示。( _# G( J& Y6 d" B

0 W' Z1 ^+ ^1 `" Z. K/ m23.1 OLED简介

1 ?5 T, _; |/ I5 oOLED,即有机发光二极管(Organic Light-Emitting Diode),又称为有机电激光显示(Organic Electroluminesence Display,OELD)。OLED由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被认为是下一代的平面显示器新兴应用技术。
; I  {; g" v. Y0 `: K0 K% R; DLCD都需要背光,而OLED不需要,因为它是自发光的。这样同样的显示,OLED效果要来得好一些。以目前的技术,OLED的尺寸还难以大型化,但是分辨率确可以做到很高。在本章中,我们使用的是ALINETEK的OLED显示模块,该模块有以下特点:
# f5 Z" W; C6 s% ~" K* z" J1)模块有单色和双色两种可选,单色为纯蓝色,而双色则为黄蓝双色。4 p" f( M) ^- H4 V+ E  [0 e
2)尺寸小,显示尺寸为0.96寸,而模块的尺寸仅为27mm26mm大小。( v1 X5 W' `- r! B/ b& B, y
3)高分辨率,该模块的分辨率为12864。- S& J3 h; c4 S' S, Y( k8 h
4)多种接口方式,该模块提供了总共4种接口包括:6800、8080两种并行接口方式、4线SPI接口方式以及IIC接口方式(只需要2根线就可以控制OLED了!)。  D& b  z. P# A+ n' B: Z) T9 ~" [
5)不需要高压,直接接3.3V就可以工作了。
/ c4 L2 i2 D7 [# K; |- @; s. s这里要提醒大家的是,该模块不和5.0V接口兼容,所以请大家在使用的时候一定要小心,别直接接到5V的系统上去,否则可能烧坏模块。以下4种模式通过模块的BS1和BS2设置,BS1和BS2的设置与模块接口模式的关系如表23.1.1所示:
' E1 S9 ^# e# f- y+ K8 A+ W0 l* q( ?1 J* H9 s0 _+ r0 P
d3059b9a2af74a9fa81b6eb79fd6d9f2.png # X  V, v4 c7 ]1 I1 Q3 A4 i
- z& M1 L$ d: G
                表23.1.1 OLED模块接口方式设置表
( L0 u# p  J4 E5 J; j; o
5 Q* i9 `% u+ j表23.1.1中:“1”代表接VCC,而“0”代表接GND。$ Y+ Y7 L$ n# H* v6 }
该模块的外观图如图23.1.1所示:
9 O5 H% @! d  S% o% L- C( y6 w+ Z9 }
8205988536514528a17099c8ce1651ff.png
4 p4 F/ F0 _' w) j- d6 ^. H, j% k3 b
图23.1.1 ALIENTEK OLED模块外观图+ b( W- t& E. F  Z
ALIENTEK OLED模块默认设置是:BS1和BS2接VCC ,即使用8080并口方式,如果你想要设置为其他模式,则需要在OLED的背面,用烙铁修改BS1和BS2的设置。! `9 s$ w6 q# F3 o2 l
模块的原理图如图23.1.2所示:4 }( ^& W9 d3 }% R9 E) P! q0 ]3 \

4 s2 }" M* x, X- ^) z' B a3f7c420de8d4e0abafa0816c073c4fb.png
& r" |; `' M' T- }4 W# i' o1 h8 P( M' u1 g
图23.1.2 ALIENTEK OLED模块原理图
& E. k4 f6 }1 A, M5 u- s& e2 g9 O该模块采用8*2的2.54排针与外部连接,总共有16个管脚,在16条线中,我们只用了15条,有一个是悬空的。15条线中,电源和地线占了2条,还剩下13条信号线。在不同模式下,我们需要的信号线数量是不同的,在8080模式下,需要全部13条,而在IIC模式下,仅需要2条线就够了!这其中有一条是共同的,那就是复位线RST(RES),RST上的低电平,将导致OLED复位,在每次初始化之前,都应该复位一下OLED模块。# L( o$ {5 _1 C0 T  n! @3 {0 `
ALIENTEK OLED模块的控制器是SSD1306,本章,我们将学习如何通过STM32H750来控制该模块显示字符和数字,本章的实例代码将可以支持两种方式与OLED模块连接,一种是8080的并口方式,另外一种是4线SPI方式。实际使用过程我们也通常只选用其中的一种来实现硬件上的连接,我们会分别介绍这两种模式,读者可以选择性阅读。2 b$ S: @, U* ]; d6 ]$ x
& h2 z; s# D  O( c  d
23.1.1 硬件驱动接口模式& s7 t3 L2 ~% k# \# w
1.8080并口模式
/ P. B1 o# T. ], a7 _& c首先我们介绍一下模块的8080并行接口,8080并行接口的发明者是INTEL,该总线也被广泛应用于各类液晶显示器,ALIENTEK OLED模块也提供了这种接口,使得MCU可以快速的访问OLED。ALIENTEK OLED模块的8080接口方式需要如下一些信号线:( R3 U& N4 a2 k0 v7 E
CS:OLED片选信号。% q5 N, f; C  [) a$ C# k( F
WR:向OLED写入数据。# r3 a: K6 {5 o8 e! k) c
RD:从OLED读取数据。: x: C1 K8 h* I# N% r
D[7:0]:8位双向数据线。
% Y" S7 T: S: N1 r* nRST(RES):硬复位OLED。+ T( M, w# J3 c! v2 {
DC:命令/数据标志(0,读写命令;1,读写数据)。6 D; P+ x7 Y5 k: ^+ W) E' p' u
模块的8080并口写的过程为:先根据要写入的数据的类型,设置DC为高(数据)/低(命令),设置WR起始电平为高,然后拉低片选,选中SSD1306,接着我们在整个读时序上保持RD为高电平,然后:
8 r( x9 i3 ^, G: `拉低WR的电平准备写入数据,向数据线(D[7:0])上输入要写的信息;
) D6 |% J2 n; o& |1 J拉高WR,这样得到一个WR的上升沿,在这个上升沿,使数据写入到SSD1306里面;7 V2 U# n. u" o( n
SSD1306的8080并口写时序图如图23.1.1.1所示:
( n" |9 K8 g4 G+ a
0 Y' O; Y& j. N 17d694ff33d44c24ba303b19ee561294.png " |. ], z) k5 R$ C: ?# C- E8 N

" X' g' q' b( K5 m$ m图23.1.1.1 8080并口写时序图+ w. ]- Z6 I6 a9 ~3 A) F
模块的8080并口读的过程为:先根据要写入的数据的类型,设置DC为高(数据)/低(命令),设置RD起始电平为高,然后拉低片选CS信号,选中SSD1306,接着我们在整个读时序上保持WR为高电平,然后类似写时序,同样的:0 u1 O+ `+ k0 a
在RD的上升沿, 使数据锁存到数据线(D[7:0])上;  h4 g7 g; o' T$ f  l8 A3 {9 R4 @
SSD1306的8080并口读时序图如图23.1.1.2所示:
. Q' Y0 f1 T" e) t( O3 K5 z1 y
" l: Q, \; i$ q ac981d53fb5f482084aeac512b35001c.png
: [+ r$ w' O! C; z
& q6 {4 i$ z8 Y; g, A图23.1.1.2 8080并口读时序图8 K; T3 n! _6 j+ f* }/ H- c/ s
SSD1306的8080接口方式下,控制脚的信号状态所对应的功能如表23.1.1.1:2 ]* g5 L1 n2 a) m; _, ?6 e

- w4 J$ ^/ n" `$ R ad77f00b8a7c44f39ce48157b8f7bac1.png 2 \2 v7 y) i3 Z- S- {7 p
4 J1 Q: v' I9 T$ @7 L$ p, Z
                         表23.1.1.1 控制脚信号状态功能表+ x' m0 ]: D3 w! F  K: T

  I' M- g0 K( p/ v7 h  Q在8080方式下读数据操作的时候,我们有时候(例如读显存的时候)需要一个假读命(Dummy Read),以使得微控制器的操作频率和显存的操作频率相匹配。在读取真正的数据之前,由一个的假读的过程。这里的假读,其实就是第一个读到的字节丢弃不要,从第二个开始,才是我们真正要读的数据。
! ~( d) Y' ]% C一个典型的读显存的时序图,如图23.1.1.3所示:
; V8 B! Y* Z, ~. G6 i, Y4 h) _7 ^, z8 y: M+ V$ ?; q; s5 z" t
eb1568e27a614b5da935580761f4a45f.png # {2 v, P' H+ T" X. d: z9 F
' e  P( P  \" N: }: A! |% ^
图23.1.1.3 读显存时序图
$ x! D4 X  J& I可以看到,在发送了列地址之后,开始读数据,第一个是Dummy Read,也就是假读,我们从第二个开始,才算是真正有效的数据。并行接口模式就介绍到这里。
2 S- Q$ G' F2 `7 ~9 f& E2. SPI模式$ U. P* M5 v  V' J0 }; G
我们的代码同时兼容SPI方式的驱动,如果你使用的是这种驱动方式,则应该把代码中的宏OLED_MODE设置为:
$ [# D" A6 H3 {/ p9 A. _# U#define OLED_MODE 0 /* 0: 4线串行模式 */7 r6 o6 u9 S2 [
我们接下来介绍一下4线串行(SPI)方式,4先串口模式使用的信号线有如下几条:3 Q8 i' ]/ s6 N* f$ F& \
CS:OLED片选信号。# T% y. C9 H* ~; Y9 R/ e# D
RST(RES):硬复位OLED。
2 P, I' }3 D0 L: k. j: ZDC:命令/数据标志(0,读写命令;1,读写数据)。
" J. l. X$ t1 d  T5 I9 J* o+ CSCLK:串行时钟线。在4线串行模式下,D0信号线作为串行时钟线SCLK。
: D+ f8 o% E2 j! j+ @3 q6 a, n' BSDIN:串行数据线。在4线串行模式下,D1信号线作为串行数据线SDIN。
; D; S+ e; |/ q' ^$ C1 t% h/ m% W% G模块的D2需要悬空,其他引脚可以接到GND。在4线串行模式下,只能往模块写数据而不能读数据。
" h5 A! `% ]9 C在4线SPI模式下,每个数据长度均为8位,在SCLK的上升沿,数据从SDIN移入到SSD1306,并且是高位在前的。DC线还是用作命令/数据的标志线。在4线SPI模式下,写操作的时序如图23.1.1.4所示:8 V7 E% |  ?; ]) A3 a' r
# R7 i) r. n. I& q$ W
08f615edf9204f20b2a3d4adca2c9ac5.png
% E8 g% ~$ ?+ I$ ~7 T" C% l
% C& }' {- Y- k图23.1.1.4 4线SPI写操作时序图
6 R' |2 T" q: Y: y2 [4线串行模式就为大家介绍到这里。其他还有几种模式,在SSD1306的数据手册上都有详细的介绍,我们把资料放到“开发板资料A盘->7,硬件资料\3,液晶资料\OLED资料\SSD1306-Revision 1.1 (Charge Pump).pdf”,如果要使用这些方式,请大家参考该手册并自行实现相应的功能代码。
; E1 F' `, s. n. a
. k/ Z$ `1 ^% H4 B+ g. I" E23.1.2 OLED显存& F# M$ L1 {4 M6 r, N0 t
接下来,我们介绍一下模块的显存,SSD1306的显存总共为128*64bit大小,SSD1306将这些显存分为了8页,不使用显存对应的行列的重映射,其对应关系如表23.1.2.1所示:/ T& Y! e& S! T# @: X
& [) w; `7 d, `/ \/ f
98e364308693471d8ca50613b5c95b92.png ( x* S% Y" ^* j8 [  S) t+ N4 a
' N) b& T* u$ W% J
表23.1.2.1 SSD1306显存与屏幕对应关系表" J, V" H# ~6 d6 a2 Q
可以看出,SSD1306的每页包含了128个字节,总共8页,这样刚好是128*64的点阵大小。当GRAM的写入模式为页模式时,需要设置低字节起始的列地址(0x000x0F)和高字节的起始列地址(0x100x1F),芯片手册中给出了写入GRAM与显示的对应关系,写入列地址在写完一字节后自动按列增长,如图23.1.2.2所示:. D4 _9 u+ G# T+ Q2 P8 `

. E3 e9 n* O; ^0 T8 I% A* d0 I. c' ]! f e9338a02870f4467ab59663920e7dc40.png 7 b9 V0 v/ z; c* N6 P5 T

5 _) o  m3 h; f& x: I6 R8 l图23.1.2.2 SSD1306页2显存写入字节与屏幕坐标的关系
0 Z' }4 v+ C: H7 j, j' S* a因为每次写入都是按字节写入的,这就存在一个问题,如果我们使用只写方式操作模块,那么,每次要写8个点,这样,我们在画点的时候,就必须把要设置的点所在的字节的每个位都搞清楚当前的状态(0/1?),否则写入的数据就会覆盖掉之前的状态,结果就是有些不需要显示的点,显示出来了,或者该显示的没有显示了。这个问题在能读的模式下,我们可以先读出来要写入的那个字节,得到当前状况,在修改了要改写的位之后再写进GRAM,这样就不会影响到之前的状况了。但是这样需要能读GRAM,对于4线SPI模式/IIC模式,模块是不支持读的,而且读改写的方式速度也比较慢。8 x/ K2 W7 ^5 z' `8 V6 h6 a2 K
所以我们采用的办法是在STM32H750的内部建立一个虚拟的OLED的GRAM(共128*8=1024个字节),在每次修改的时候,只是修改STM32H750上的GRAM(实际上就是SRAM),在修改完了之后,一次性把STM3F103上的GRAM写入到OLED的GRAM。当然这个方法也有坏处,一个是对于那些SRAM很小的单片机(比如51系列)不太友好,另一个是每次都写入全屏,屏幕刷新率会变低。! W( ~9 @) ?' ?  w/ {4 A- `
SSD1306的命令比较多,这里我们仅介绍几个比较常用的命令,这些命令如下表所示:
0 ?  a3 Z* t9 y0 }" h: [
& o6 j1 D: P0 b" y+ [ b27645c47fc144d2ba037e6dd597b116.png ' l. D8 x* w2 |% H

0 [# g9 R: I4 p9 K/ m- X' j表23.1.2.3 SSD1306常用命令表( H/ ]6 {* u7 @) r( Z$ s
第0个命令为0X81,用于设置对比度的,这个命令包含了两个字节,第一个0X81为命令,随后发送的一个字节为要设置的对比度的值。这个值设置得越大屏幕就越亮。& W  y& c0 @# N/ p7 q
第1个命令为0XAE/0XAF。0XAE为关闭显示命令;0XAF为开启显示命令。
$ d. C! X3 z. `第2个命令为0X8D,该指令也包含2个字节,第一个为命令字,第二个为设置值,第二个字节的BIT2表示电荷泵的开关状态,该位为1,则开启电荷泵,为0则关闭。在模块初始化的时候,这个必须要开启,否则是看不到屏幕显示的。# a7 p2 k% x7 v% |2 ?9 E. m
第3个命令为0XB0~B7,该命令用于设置页地址,其低三位的值对应着GRAM的页地址。
( I8 n. g- R7 m5 C, a0 I- C第4个指令为0X00~0X0F,该指令用于设置显示时的起始列地址低四位。" ~3 \  T3 K6 P5 T) _9 Y
第6个指令为0X10~0X1F,该指令用于设置显示时的起始列地址高四位。
3 T2 r  m5 E/ t) n3 x其他命令,我们就不在这里一一介绍了,大家可以参考SSD1306 datasheet的第28页。从这页开始,对SSD1306的指令有详细的介绍。
; z' E" n: i% u% M! ?最后,我们再来介绍一下OLED模块的初始化过程,SSD1306的典型初始化框图如图23.1.2.4所示:3 G! k& a9 j% e1 }4 m
7 j( u& m, H3 ^7 G- A
f21ed10c54534223b93f9074f172ca2a.png : A/ {, s; g) }2 ^4 T, _

% N$ e" f4 u3 u图23.1.2.4 SSD1306初始化框图5 j8 N* Z; Z# d$ e% Q' m) e
驱动IC的初始化代码,我们直接使用厂家推荐的设置就可以了,只要对细节部分进行一些修改,使其满足我们自己的要求即可,其他不需要变动。4 s, r1 n+ k' I: r0 D, U
OLED的介绍就到此为止,我们重点向大家介绍了ALIENTEK OLED模块的相关知识,接下来我们将使用这个模块来显示字符和数字。通过以上介绍,我们可以得出OLED显示需要的相关设置步骤如下:, j* t  @  u' M% B$ z" p; j
1)设置STM32F103与OLED模块相连接的IO。
2 e7 ^' W0 W/ @# L& I这一步,先将我们与OLED模块相连的IO口设置为输出,具体使用哪些IO口,这里需要根据连接电路以及OLED模块所设置的通讯模式来确定。这些将在硬件设计部分向大家介绍。
$ R  [  e9 C  ]+ U' x; R: F2)初始化OLED模块。+ q  z0 g3 I, Z- B4 c  n- n' i' ^
其实这里就是上面的初始化框图的内容,通过对OLED相关寄存器的初始化,来启动OLED的显示。为后续显示字符和数字做准备。2 ]0 A3 @0 Y6 `+ N9 k
3)通过函数将字符和数字显示到OLED模块上。
+ ~) ]$ ^4 q! u0 l这里就是通过我们设计的程序,将要显示的字符送到OLED模块就可以了,这些函数将在软件设计部分向大家介绍。5 }; t. L! ^$ o9 w
通过以上三步,我们就可以使用ALIENTEK OLED模块来显示字符和数字了,在后面我们还将会给大家介绍显示汉字的方法。这一部分就先介绍到这里。
9 H/ r- b4 {8 R! n1 x* S0 Z( J" F
9 k* h) V6 l* `) v23.2 硬件设计. C/ ]7 q5 c: F9 i
1.例程功能
! K2 o3 J" u0 ~5 a- B9 ?5 s使用8080并口模式驱动或者使用4线SPI串口模式,驱动OLED模块,不停的显示ASCII码和码值。LED0闪烁,提示程序运行。$ s' r3 ^6 s1 F2 p! K, |
2.硬件资源
  Y+ F' p8 D1 k- p1)RGB灯
0 }' \. `4 o  X- E9 cRED : LED0 - PB4
) D) j# G6 f1 N/ ]2)ALIENTEK 0.96寸OLED模块,在硬件上,OLED与开发板的IO口对应关系如下:& D# Q( s! p- b5 C% f- ?1 M! N
OLED_CS对应DCMI_VSYNC,即:PB7;
( h. K8 S3 @, ]7 j% `1 M* @/ e* VOLED_RS对应DCMI_SCL,即:PB10;
& J8 a0 p5 b8 kOLED_WR对应DCMI_HREF,即:PA4;- c$ S% Y* Z$ \5 Q  d" }
OLED_RD对应DCMI_SDA,即:PB11;
$ s# S: |8 r# P7 z6 q8 O7 VOLED_RST对应DCMI_RESET,即:PA7;
: C8 c/ w# }/ O7 k" p1 gOLED_D[7:0]对应DCMI_D[7:0],即:PB9/PB8/PD3/PC11/PC9/PC8/PC7/PC6;6 ?( @# U% x9 t" F6 O, I; d' K
3.原理图
+ Z/ [* P: Q' k% b# Q* VOLED模块的原理图在前面已有详细说明了,这里我们介绍OLED模块与我们开发板的连接,开发板上有一个OLED/CAMERA的接口(P2接口)可以和ALIENTEK OLED模块直接对插(靠左插!),连接如图23.2.1所示:
* e4 k( D. m& u, Z9 i$ {9 T  ?
. i5 S% _# H! |( g9 N7 q: ^ dbe49a735add4af8af33e9b3cd2572b0.png 6 o3 a0 z* {- Y' w- ~$ X- ?
- s$ j9 E3 S& K
图23.2.1 OLED模块与开发板连接示意图
, _' ], m1 l! `7 b$ r% K+ W/ ?这些线的连接,开发板的内部已经连接好了,我们只需要将OLED模块插上去就好了,注意,这里的OLED_D[7:0]因为不是接的连续的IO,所以得用拼凑的方式去组合一下,后续会介绍。0 {: X, {2 A* ]6 @5 S* J; E
23.3 程序设计& V9 Z6 E- J& @
OLED只是用到HAL库中GPIO外设的驱动代码,在前面跑马灯实验已经介绍了。) N1 M7 x  O4 k! U: {+ H0 Q
23.3.1 程序流程图
6 }6 Q+ R0 y" A
( o3 [2 Q  o" |* r9 f/ w$ B 10a1086a28894c1f8af029cda380f925.png
7 w& E$ o& ]( g  {5 z2 H* w- ], D6 d& @) K' ^
图23.3.1.1 OLED实验程序流程图
' g) \7 w* y( R  h4 B! q1 q2 [2 `5 A
23.3.2 程序解析
3 H: }( q8 ]3 Z; M) f1.OLED驱动代码1 D! `$ r4 Z! B" E# ~& N1 R$ K
这里我们只讲解核心代码,详细的源码请大家参考光盘本实验对应源码。OLED驱动源码包括三个文件:oled.c、oled.h和oledfont.h。oledfont.h头文件存放的是ASCII字符集,oled.h存放的是引脚接口宏定义和函数声明等,oled.c则是驱动代码。+ a; Z3 w% Y2 D( R0 l
首先看oledfont.h头文件的ASCII字符集内容:* o# t* {2 {# ?  S5 V4 Y( f
  1. /* 常用ASCII表
    * R! S) Z( X5 v0 N' f
  2. * 偏移量32
    $ y8 [/ V) h- B) ^# O
  3. * ASCII字符集: !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]
    3 |/ J) \/ `, i$ i+ w/ Q) S4 O
  4. ^_`abcdefghijklmnopqrstuvwxyz{|}~
    ) K3 A0 ?5 @( n. `1 Y
  5. * PC2LCD2002取模方式设置:阴码+逐列式+顺向+C51格式
    & Z# M  M4 d" O( X1 W8 {# s
  6. * 总共:3个字符集(12*12、16*16和24*24),用户可以自行新增其他分辨率的字符集。; X4 ]% m7 h- A; X; L- F: J
  7. * 每个字符所占用的字节数为:(size/8+((size%8)?1:0))*(size/2),
    + I. q6 o7 E: f0 o; u0 j; d) v
  8. 其中size:是字库生成时的点阵大小(12/16/24...)
      C( ?5 x3 p' f/ h9 c+ |
  9. */0 r. l6 [0 J7 `9 r

  10. 0 P3 A" X) t: P/ J7 _; N2 F
  11. /* 12*12 ASCII字符集点阵 */9 Y6 u% w3 \1 X1 S1 I
  12. const unsigned char oled_asc2_1206[95][12]={ ...这里省略字符集库... };
    9 F) M: h6 L% w2 B& H$ |& u) T( [. m
  13. ( ^* e: ]4 a$ G( z8 P
  14. /* 16*16 ASCII字符集点阵 */
    ) k0 P, M5 L1 T: g5 F6 D; r6 u& C3 G
  15. const unsigned char oled_asc2_1608[95][16]={ ...这里省略字符集库... }; * H6 o0 Q6 }* o; D
  16. 8 X' V; M) E# `" N  u$ h: `$ F
  17. /* 24*24 ASICII字符集点阵 */2 {1 Y2 _, ~/ u3 Y8 l
  18. const unsigned char oled_asc2_2412[95][36]={ ...这里省略字符集库... };
复制代码
4 s2 a/ A6 v$ v  Q
该头文件中包含三个大小不同的ASCII字符集点阵,其中包括:1212 ASCII字符集点阵、1616 ASCII字符集点阵、2424 ASICII字符集点阵。每个字符集点阵都包含95个常用的ASCII字符集,从空格符开始,分别为: !"#$%&'()+,-0123456789:;<=>?@ABCDEFGHIJKLMNOPQR3 j. Q7 t0 Q, `- u" J2 G$ ?
STUVWXYZ[]^_`abcdefghijklmnopqrstuvwxyz{|}~。5 ?- c) C! @! {- |; _
上面的ASCII字符集,我们可以使用一个款很好的字符提取软件来制作获取。字符提取软件为:PCtoLCD2002完美版,该软件可以提供各种字符,包括汉字(字体和大小都可以自己设置)阵提取,且取模方式可以设置好几种,常用的取模方式,该软件都支持。该软件还支持图形模式,也就是用户可以自己定义图片的大小,然后画图,根据所画的图形再生成点阵数据,这功能在制作图标或图片的时候很有用。4 y1 h/ {* V/ C. L
该软件的界面如图23.3.2.1所示:
+ m- d/ g4 O% z
& P& S" u; v1 w0 @) c2 s 49111753b3cd458d9606f69a58976ae0.png 1 E' c$ C5 l4 O, ^2 v7 C

" S! A. a( l! V2 A, C' z5 T! m图23.3.2.1 PCtoLCD2002软件界面
7 I( k. i* n8 }" x* S( p" {然后我们选择设置,在设置里面设置取模方式如图23.3.2.2所示:+ k* H, {& I3 ]% s. [5 W& v
  i- @! M& c; }- h7 h5 X% ~  `
c0eeff577668477c9ac4bc98c5c94801.png : b: T& X, h# f# m) l! v6 J, W
' _' P' X) q2 s. @. r/ H
图23.3.2.2 设置取模方式0 n, j% ?% M7 _7 f  Y7 z, [
上图设置的取模方式,在右上角的取模说明里面有,即:从第一列开始向下每取8个点作为一个字节,如果最后不足8个点就补满8位。取模顺序是从高到低,即第一个点作为最高位。如*-------取为10000000。其实就是按如图23.3.2.3所示的这种方式:4 s$ _7 ]6 ^' g' ~. c, D" {

( X# ^; S$ J$ D2 Z' f1 S 164bcd6601af417ba95aa1666363cac8.png
- s% V; q  s. @0 p* L
. j- |  n9 @6 u/ ]6 F+ B% S2 i图23.3.2.3 取模方式图解! R5 t! Z2 W, h2 q: U+ Q; i% R3 \
从上到下,从左到右,高位在前。我们按这样的取模方式,然后把ASCII字符集按126大小、168和2412大小取模出来(对应汉字大小为1212、1616和2424,字符的只有汉字的一半大!),每个126的字符占用12个字节,每个168的字符占用16个字节,每个24*12的字符占用36个字节。" }6 {$ N( p, l& o- S
oled.c和oled.h文件的代码可以帮助显示我们制作好的字符集。我们还是先看oled.h文件的宏定义,首先是OLED模式设置宏定义:
# D/ j8 g) Z; _4 C6 R( Q% C) }" d( o5 C; h+ ]" S; l# @( V- u
  1. /* OLED模式设置! Y0 O9 U5 T4 ~# k9 S9 u" m
  2. * 0: 4线串行模式          (模块的BS1,BS2均接GND)
    + H8 \0 ~: `$ v
  3. * 1: 并行8080模式         (模块的BS1,BS2均接VCC)! k+ N1 o/ S1 d6 ?2 l" F& e  J; y/ S
  4. */
    5 u$ ?8 X( S' E3 x: o
  5. #define OLED_MODE       1   /* 默认使用8080并口模式 */
    . U, k. S  r6 _4 P6 c1 P) a
  6. 通过宏定义OLED_MODE来决定使用4线串行模式(0)还是并行8080模式(1),默认使用8080并口模式。5 C8 ^9 ?! l0 k3 D
  7. 关于OLED 80并口模式和SPI模式的引脚定义就不列出来了,请看源码。% p$ M. Q  j2 I9 Y# C
  8. 还有两个关于向OLED写入选择命令或者数据的宏定义,后面讲的oled_wr_byte函数用到。
    # T( L5 W7 x- O) C; p
  9. /* 命令/数据 定义 */
    + q( _4 ?6 [) r2 D5 T8 ~$ X. e" u
  10. #define OLED_CMD        0       /* 写命令 */! k  C' k# n0 k4 \* ?# b5 ?' X
  11. #define OLED_DATA       1       /* 写数据 */
    1 b/ Y: Z; Y: ?( w  E
  12. 最后就是oled.c文件的驱动源码介绍。先是OLED(SSD1306)的初始化函数,其定义如下:& |0 T3 N! q3 Q, u2 g3 G( p2 [" p
  13. /*** d9 W; d6 e; A# |* I
  14. * @brief       初始化OLED(SSD1306)/ L: V3 N% ~. z8 x2 a
  15. * @param       无
    1 W" `( L0 X8 l- E
  16. * @retval      无
      q9 o0 t' P& l
  17. */6 u$ Z1 q$ _, H. M( V3 e+ V
  18. void oled_init(void)# z6 p4 Z1 ^" z. \& x
  19. {. X: [9 h2 S8 S* C8 i% ]
  20.     GPIO_InitTypeDef  gpio_init_struct;
    ; X! z7 p- ?% M' f

  21. 7 f% e* R, f0 i8 \8 n8 ?: P
  22.     __HAL_RCC_GPIOA_CLK_ENABLE();' {. R5 o. @1 Q
  23.     __HAL_RCC_GPIOB_CLK_ENABLE();
    + ~+ Y8 @0 O. Y
  24.     __HAL_RCC_GPIOC_CLK_ENABLE();4 ?- @( Q8 f& z) L2 ^6 ?
  25.     __HAL_RCC_GPIOD_CLK_ENABLE();) N0 _1 [  j0 Y. t
  26. 8 H" U3 W. N$ W7 `, z9 c
  27. #if OLED_MODE==1         /* 使用8080并口模式 */8 o$ t) C" |' G! K4 U9 j
  28. /* PA4,6,7,8设置 */
    # y6 Y5 r" G+ t& m0 ?
  29.     gpio_init_struct.Pin=GPIO_PIN_4|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8;6 Q, I  X$ y. k1 V% v/ ^& Y
  30.     gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;                   /* 推挽输出 */" W2 C. h4 D6 C3 j
  31.     gpio_init_struct.Pull=GPIO_PULLUP;                             /* 上拉 */
    7 W7 l7 A- E1 _% ^$ U& }
  32.     gpio_init_struct.Speed=GPIO_SPEED_FREQ_VERY_HIGH;           /* 高速 */( S0 }/ ~0 N) ?$ |
  33.     HAL_GPIO_Init(GPIOA,&gpio_init_struct);
    , R1 V- D, D* E8 v7 T1 ?
  34.   A' s) S- `- [
  35.     gpio_init_struct.Pin=GPIO_PIN_7|GPIO_PIN_8|GPIO_PIN_9|GPIO_PIN_10
    / Q2 Z, b5 z+ A5 R
  36. |GPIO_PIN_11;       /* PB7,8,9,10,11设置 */3 }2 {- N8 Q5 e' L" H7 _
  37.     HAL_GPIO_Init(GPIOB,&gpio_init_struct);
    5 H0 X+ C1 Y1 _! u/ r) Y3 x
  38.   P- q7 }* P8 c3 B" q0 X: j9 V
  39.     gpio_init_struct.Pin=GPIO_PIN_4|GPIO_PIN_6|GPIO_PIN_7|GPIO_PIN_8
    # D2 O; M. s* X" G  d
  40. |GPIO_PIN_9|GPIO_PIN_11; /* PC4,6~9,PC11设置 */% a+ x7 h3 n, Z* g- }
  41.     HAL_GPIO_Init(GPIOC,&gpio_init_struct);
    & y6 `- V& H, H% c7 {/ ~7 T
  42. " G# q% d! f' Y$ S& }3 i2 z5 i5 @
  43.     gpio_init_struct.Pin=GPIO_PIN_3;                 /* PD3 设置 */$ D  @8 U7 ~4 _$ x6 Z
  44.     HAL_GPIO_Init(GPIOD,&gpio_init_struct);1 R# u' B$ n9 E' k% x( Z

  45. " f8 y, h6 r5 C* z
  46.     OLED_WR(1);
    ; b- S. R1 P' ?0 N/ X8 f
  47.     OLED_RD(1);
    : z1 H* h9 B; ~8 i

  48. , M' a/ x' ?* A. g. s
  49. #else               /* 使用4线SPI 串口模式 */
    6 m8 O" G6 v+ y# i; a- S% H- X
  50. 5 D6 K- z3 q  t  L
  51.     gpio_init_struct.Pin=OLED_SPI_RST_PIN;9 ~$ X" Y' `" w: _
  52.     gpio_init_struct.Mode=GPIO_MODE_OUTPUT_PP;                       /* 推挽输出 */
    ' Y0 g& [# N& B% U  u4 k
  53.     gpio_init_struct.Pull=GPIO_PULLUP;                                 /* 上拉 */
    # W' ~. J' ^9 D0 K, N/ }+ `
  54.     gpio_init_struct.Speed=GPIO_SPEED_FREQ_VERY_HIGH;              /* 高速 */* z, V$ o( U8 L  N6 j- f9 [8 i, O
  55.     HAL_GPIO_Init(OLED_SPI_RST_PORT,&gpio_init_struct);            /* RST引脚模式设置 */  T( K- z  S4 a  H
  56. 6 A7 l2 C7 n; z
  57.     gpio_init_struct.Pin=OLED_SPI_CS_PIN;( K, R% C# Y8 W) T/ i7 T
  58.     HAL_GPIO_Init(OLED_SPI_CS_PORT,&gpio_init_struct);             /* CS引脚模式设置 */& [# K- ]; ?" e0 V8 O

  59. ) a+ e+ L3 O3 D) B: u8 H
  60.     gpio_init_struct.Pin=OLED_SPI_RS_PIN;" S- f' [6 u, O
  61.     HAL_GPIO_Init(OLED_SPI_RS_PORT,&gpio_init_struct);             /* RS引脚模式设置 */+ ]4 v! m( r3 z8 i4 C! G

  62. 9 X6 \, J0 c- Y- n4 v3 p8 `; j
  63.     gpio_init_struct.Pin=OLED_SPI_SCLK_PIN;& ~( U' E, [/ k1 r
  64.     HAL_GPIO_Init(OLED_SPI_SCLK_PORT,&gpio_init_struct);           /* SCLK引脚模式设置 */
    , C( ]5 _$ L* H
  65. * r3 G6 [0 s% u; t/ X! m
  66.     gpio_init_struct.Pin=OLED_SPI_SDIN_PIN;5 Z' z4 k4 g" Y8 u) ^6 R. [
  67.     HAL_GPIO_Init(OLED_SPI_SDIN_PORT,&gpio_init_struct);           /* SDIN引脚模式设置 */
    ! E4 ?' l1 S3 E: k$ i9 L
  68.   g7 S: D& N: G/ ~/ d! l
  69.     OLED_SDIN(1);
    6 M' t  ~1 N3 v+ S9 s
  70.     OLED_SCLK(1);9 m; V" B$ {- `, u. V7 t
  71. #endif2 ]% a1 s: e) F. s
  72.     OLED_CS(1);. n/ f; N) ]! o
  73.     OLED_RS(1);8 p2 w: {3 m* F+ ]4 U) N, I

  74. * o* b$ K  p$ d7 _. k5 S8 |, d
  75.     OLED_RST(0);
      k2 \' y: X3 L/ p
  76.     delay_ms(100);* Q% l+ n8 g5 F1 @
  77.     OLED_RST(1);
    + f2 E8 J+ P( _# j. Q

  78. 8 n+ {( C. M+ i
  79.     oled_wr_byte(0xAE, OLED_CMD);   /* 关闭显示 */
    9 U9 h# c  ?; x5 d8 i3 c
  80.     oled_wr_byte(0xD5, OLED_CMD);   /* 设置时钟分频因子,震荡频率 */
    - z* \. E7 N/ U0 \* E- }
  81.     oled_wr_byte(80, OLED_CMD);             /* [3:0],分频因子;[7:4],震荡频率 */
    - C/ r- o" _: a  D7 k  E
  82.     oled_wr_byte(0xA8, OLED_CMD);   /* 设置驱动路数 */3 ?; R+ o; q1 V; R
  83.     oled_wr_byte(0X3F, OLED_CMD);   /* 默认0X3F(1/64) *// I! L' B9 p) g. ]% k
  84.     oled_wr_byte(0xD3, OLED_CMD);   /* 设置显示偏移 */; G+ o' G0 |  d
  85.     oled_wr_byte(0X00, OLED_CMD);   /* 默认为0 */6 c4 k* G' R3 S$ F0 @4 H5 w
  86. 3 c+ ^* _! a  Z; o
  87.     oled_wr_byte(0x40, OLED_CMD);   /* 设置显示开始行 [5:0],行数. */
    7 S) G9 D0 f8 I, Z, t" }9 i
  88. 6 I$ _: S* ~9 x  k0 [
  89.     oled_wr_byte(0x8D, OLED_CMD);   /* 电荷泵设置 */7 U- z+ k% ~, a6 n: k" e
  90.     oled_wr_byte(0x14, OLED_CMD);   /* bit2,开启/关闭 */
    * Y) P9 K6 C* y
  91. oled_wr_byte(0x20, OLED_CMD);   /* 设置内存地址模式 */' l, x0 ^6 W: k) c. U
  92. /* [1:0],00,列地址模式;01,行地址模式;10,页地址模式;默认10; */
    $ b8 k$ Q9 V, ?9 A( n; G8 p$ r: {
  93.     oled_wr_byte(0x02, OLED_CMD);   : r) S8 b6 B9 B0 `. n* K. `
  94.     oled_wr_byte(0xA1, OLED_CMD);   /* 段重定义设置,bit0:0,0->0;1,0->127; */
    : \4 @, e: S* u# Y' z& G9 t  y2 x
  95. /* 设置COM扫描方向;bit3:0,普通模式;1,重定义模式 COM[N-1]->COM0;N:驱动路数 */   
    ; q0 Z) Z* m, m, r3 b
  96.     oled_wr_byte(0xC8, OLED_CMD);   * {( v; M" y0 O' A  U, p
  97.     oled_wr_byte(0xDA, OLED_CMD);   /* 设置COM硬件引脚配置 */5 @  l3 |3 P: E, @7 V: b6 r
  98.     oled_wr_byte(0x12, OLED_CMD);   /* [5:4]配置 */0 V$ j( G9 @) T2 O, N, W& K
  99. / E, ~! J2 J% b
  100.     oled_wr_byte(0x81, OLED_CMD);   /* 对比度设置 */' k7 R) X3 T9 m  `# h8 j
  101.     oled_wr_byte(0xEF, OLED_CMD);   /* 1~255;默认0X7F (亮度设置,越大越亮) *// V: W6 ~. D, C8 Q6 O$ L: D) D; v
  102.     oled_wr_byte(0xD9, OLED_CMD);   /* 设置预充电周期 */" J! J- |: ?8 Y2 y9 s0 N, Y/ i
  103.     oled_wr_byte(0xf1, OLED_CMD);   /* [3:0],PHASE 1;[7:4],PHASE 2; */
    2 d4 d; h& u# `) ^0 _
  104. oled_wr_byte(0xDB, OLED_CMD);   /* 设置VCOMH 电压倍率 */( M+ a! m0 T: \! j
  105. /* [6:4] 000,0.65*vcc;001,0.77*vcc;011,0.83*vcc; */# l2 T; _) \2 C/ S% ~& c
  106.     oled_wr_byte(0x30, OLED_CMD);   ! g! H4 B/ F) n5 J, U" p
  107. 7 B# c6 S( [5 B4 U
  108.     oled_wr_byte(0xA4, OLED_CMD);   /* 全局显示开启;bit0:1,开启;0,关闭;(白屏/黑屏) */
    4 e- L6 k' ?. f' E
  109.     oled_wr_byte(0xA6, OLED_CMD);   /* 设置显示方式;bit0:1,反相显示;0,正常显示 */5 A  G6 b6 [2 b/ L9 v5 w
  110.     oled_wr_byte(0xAF, OLED_CMD);   /* 开启显示 */
    5 |3 u6 G% ~  E: e9 W9 r3 c
  111.     oled_clear();
    5 m7 u) ~0 ]3 u8 |. T/ q- z* m- Q
  112. }
复制代码

2 j8 U  q' o5 P该函数的结构比较简单,开始是对GPIO口的初始化,这里我们用了宏定义OLED_MODE来决定要设置的IO口,后面的就是一些初始化序列了,我们按照厂家提供的资料来做就可以。值得注意一点的是,因为OLED是无背光的,在初始化之后,我们把显存都清空了,所以我们在屏幕上是看不到任何内容的,就像没通电一样,不要以为这就是初始化失败,要写入数据模块才会显示的。
- r& n6 F- k4 L( J0 C接着,要介绍的是oled_refresh_gram更新显存到OLED函数,该函数的作用是把我们在程序中定义的二维数组g_oled_gram的值一次性刷新到OLED的显存GRAM中。我们在oled.c文件开头定义了如下一个二维数组:
' L, m& r7 v; h/ l5 T0 P4 F/ S
9 r0 n- X5 X& A) o$ b3 e) A
  1. /* 0 ]3 z5 V; }6 \
  2. * OLED的显存* H- Q4 I, S8 M4 C/ d8 b
  3. * 每个字节表示8个像素, 128,表示有128列, 8表示有64行, 高位表示高行数. ' k) X) M5 _1 b5 e* }! _1 [
  4. * 比如:g_oled_gram[0][0],包含了第一列,第1~8行的数据. g_oled_gram[0][0].0,即表示坐标(0,0)# E0 M" g. U1 a9 x* i( B+ V
  5. * 类似的: g_oled_gram[1][0].1,表示坐标(1,1), g_oled_gram[10][1].2,表示坐标(10,10), 9 q( ~( {! Y+ ^' e8 @! h0 T
  6. * * I* V3 w0 E8 u- v4 M; A
  7. * 存放格式如下(高位表示高行数).3 I1 m3 ]; c6 G, Z; F
  8. * [0]0 1 2 3 ... 127
    5 t+ |4 Z  b3 L: G! A# I# S3 q
  9. * [1]0 1 2 3 ... 127
    # s) v: T2 W" O$ `' P9 {
  10. * [2]0 1 2 3 ... 127. U4 P2 w7 x1 {! ~6 H
  11. * [3]0 1 2 3 ... 127
      Z0 N' G- }, g$ V( x8 i( `
  12. * [4]0 1 2 3 ... 127
    / v: h1 n) G% b1 W' o. t
  13. * [5]0 1 2 3 ... 127: {! _: n8 ^5 k! ]
  14. * [6]0 1 2 3 ... 1273 Q# r( U( }& P/ E2 c
  15. * [7]0 1 2 3 ... 127
    , P: D% `- G# n3 [2 [  ~5 |( S
  16. */
      C( z9 h( J8 M5 v
  17. static uint8_t g_oled_gram[128][8];
复制代码
% ~$ Y1 h, Y4 f: T4 c  |0 B
该数组值与OLED显存GRAM值一一对应。在操作的时候我们只需要先修改该数组的值,然后再通过调用oled_refresh_gram函数把数组的值一次性刷新到OLED 的GRAM上即可。oled_refresh_gram函数定义如下:4 |+ r5 O3 |9 E  T3 A

# h- p- D7 E3 h4 k, \
  1. /**
    ; K' `) P7 b7 ~" ]' @$ i
  2. * @brief               更新显存到OLED
    / I: e! G7 |( k% ^& c* V
  3. * @param               无- o; M) Y  |: ]6 c# T5 {  H
  4. * @retval              无
    : E, M4 |' l# g' Y* i, S6 L, h
  5. */# A$ |- G% V' O. x- s3 m
  6. void oled_refresh_gram(void)
    ; Q$ e+ E# e( n. m
  7. {
    " W/ g1 ?; I: q. A
  8.     uint8_t i, n;
    0 T2 G7 p" M9 O( {: m. r
  9. ) Q& V& C% e6 F: ]" v) Z# K
  10.     for (i = 0; i < 8; i++)
    0 E5 s9 m+ ?/ F3 U
  11.     {
    2 B' \0 @4 `; u: u$ n& O
  12.         oled_wr_byte (0xb0 + i, OLED_CMD); /* 设置页地址(0~7) */4 d, [$ a# u( T( _2 ?7 t
  13.         oled_wr_byte (0x00, OLED_CMD);      /* 设置显示位置—列低地址 */, ^7 h0 c! P5 o) ]% K& }
  14.         oled_wr_byte (0x10, OLED_CMD);      /* 设置显示位置—列高地址 */9 r& b4 q' G& i. c  E0 M9 w5 u6 @
  15.         for (n = 0; n < 128; n++); L  p8 ?; F* [; f# q
  16.         {
    , V  Z" {5 Q+ y9 h! t! d9 n; E
  17.             oled_wr_byte(g_oled_gram[n]<i>, OLED_DATA);2 @% w  S0 `/ t6 ?: N
  18.         }( D, x& m0 V% J) ^/ T  f
  19.     }
    % t! H" A8 z& J
  20. }</i>
复制代码

5 j) b2 M4 ?3 |- Eoled_refresh_gram函数先设置页地址,然后写入列地址(也就是纵坐标),然后从0开始写入128个字节,写满该页,最后循环把8页的内容都写入,就实现了整个从STM32显存到OLED显存的拷贝。
/ J' ]+ W7 s4 P; M% qoled_refresh_gram函数还调用了oled_wr_byte这个函数,也就是我们接着要介绍的函数:该函数和硬件相关,8080并口模式下该函数定义如下:8 N% i9 t7 ?- |5 z

7 b7 x5 D3 y3 ^' B9 E: I5 q
  1. /**, D1 b0 j0 i0 D$ M9 [- u
  2. * @brief               向OLED写入一个字节
    5 j$ O* O1 q0 _& @) t
  3. * @param               data: 要输出的数据
    6 [' K# @2 u- [& @; n5 J4 v
  4. * @param               cmd: 数据/命令标志 0,表示命令;1,表示数据;
    ) G' _* w+ H# {/ s* j8 t
  5. * @retval              无
    0 U* i9 U+ M3 U$ S, P9 J& a& R
  6. */
    ! b0 V7 V$ @9 j6 ^
  7. static void oled_wr_byte(uint8_t data, uint8_t cmd). Q( ^/ z! B" U; ^* f4 [2 d1 L* y
  8. {
    4 I! r% s3 k6 J8 b
  9.     oled_data_out(data);$ }2 |& _' C2 H$ N
  10.     OLED_RS(cmd);
    , P8 r# F+ t: w  B2 G
  11.     OLED_CS(0);( T+ j" @1 R9 }, b& @5 R
  12.     OLED_WR(0);
    4 f2 F) `' k) P; q8 k+ L
  13.     OLED_WR(1);7 h3 J1 `3 W9 B' J3 f4 `& v
  14.     OLED_CS(1);
    & U( t) {4 F6 l$ N: G, o6 D# F
  15.     OLED_RS(1);1 d% k7 \* p' M9 y0 ?. d
  16. }0 V) r$ ^7 e7 b7 ]$ p
  17. 8080并口模式下的oled_wr_byte函数还调用oled_data_out函数,其定义如下:0 }: ?! {* A2 u( Z1 B' ?+ p7 n
  18. /**
    0 |& @* W' J' q( R( Y; p
  19. * @brief               通过拼凑的方法向OLED输出一个8位数据
      O$ Y+ V- S3 o
  20. * @param               data: 要输出的数据
    % x' ~$ t7 [, {! l2 m- Q
  21. * @retval              无  X8 T/ v5 C$ S. `% u) r; F- a
  22. */
    ; i& F. C5 F7 U0 h) D  h
  23. static void oled_data_out(uint8_t data)
    1 A. O3 ?9 p& e" X
  24. {  |* ~( _' r3 d, D; z) Z
  25.     uint16_t dat = data & 0X0F;6 ~/ C5 j4 E) m. H
  26.     GPIOC->ODR &= ~(0XF << 6);              /* 清空6~9 */" S' U0 N7 H% A% X6 q3 ?
  27.     GPIOC->ODR |= dat << 6;                 /* D[3:0]-->PC[9:6] */: @4 [; C, V; d, K

  28. 7 p6 K3 L- ^6 c( e9 ?# L8 G1 j" K
  29.     GPIOC->ODR &= ~(0X1 << 11);             /* 清空11 */
    ! T9 X# e* W1 b" g& R, z/ G4 O
  30.     GPIOC->ODR |= ((data >> 4) & 0x01) << 11;        /* D4 */4 ?! b5 _, L. O/ K  y
  31. ) n7 O0 {/ r2 V, ]0 {5 j
  32.     GPIOD->ODR &= ~(0X1 << 3);                          /* 清空3 */! z) g" I/ k1 p' H7 S
  33.     GPIOD->ODR |= ((data >> 5) & 0x01) << 3;         /* D5 */; j' u% f/ r( x8 c- w$ c8 M

  34. ! p+ f$ l5 H& J! f! ~; b2 ~
  35.     GPIOB->ODR &= ~(0X3<<8);                                   /* 清空8,9 */
    : ^6 g. R0 C8 M, h# Q
  36.     GPIOB->ODR |= ((data >> 6) & 0x01) << 8;         /* D6 */7 x% C- i% W$ l3 ~
  37.     GPIOB->ODR |= ((data >> 7) & 0x01) << 9;         /* D7 */
    " b9 ^. R2 e+ @" Q) p1 s7 ~
  38. }
复制代码

! {, c1 b) g) S1 Foled_data_out函数的处理方法,就是我们前面说的,因为OLED的D0~D7不是接的连续IO,所以必须将数据,拆分到各个IO,以实现一次完整的数据传输,该函数就是根据我们OLED_D[7:0]具体连接的IO,对数据进行拆分,然后输出给对应位的各个IO,实现并口数据输出。这种方式会降低并口速度,但是我们OLED模块,是单色的,数据量不是很大,所以这种方式也不会造成视觉上的影响,大家可以放心使用,但是如果是TFTLCD,就不推荐了。
+ y3 t/ C& O; _2 f0 mSPI模式下该函数定义如下:3 e) u2 E& ?5 Z+ [/ O' y
0 S/ B. i' ?1 m6 ?; q$ w0 D! I
  1. /**! E2 R; f& _. j/ W$ N
  2. * @brief               向OLED写入一个字节
    1 O% T$ K5 l. p& \* X* l
  3. * @param               data: 要输出的数据
    % F+ A% c' F/ Q: D, {
  4. * @param               cmd: 数据/命令标志 0,表示命令;1,表示数据;
    3 J4 M; _/ V! F- u- u! n; {
  5. * @retval              无! |" v7 d) O0 q7 X9 u; p
  6. */+ l% m- ?4 d4 Q# t7 g8 p1 z* t
  7. static void oled_wr_byte(uint8_t data, uint8_t cmd)7 G3 ]  C  d7 ?: g
  8. {5 @% `, M, K) |+ `( ^/ S
  9.     uint8_t i;8 D. C' s/ ?+ \4 \; Y/ F2 C+ R
  10.     OLED_RS(cmd);   /* 写命令 */
    7 L4 \( l+ ^1 x% a' Z
  11.     OLED_CS(0);7 ^6 Z0 u1 b" @/ j& q

  12. " u( W! A# v. B% j  w) B. O4 c
  13.     for (i = 0; i < 8; i++), U9 c  G$ G; [$ T' b
  14.     {
    0 v7 V& u( a$ ?$ j4 P& V
  15.         OLED_SCLK(0);4 u5 |! {% c8 a, h8 y5 r

  16. 8 L* v5 X$ r) P+ v3 O
  17.         if (data & 0x80). d7 M/ U4 t; N" j
  18.         {
    ; ]0 w5 K% ?2 q( [+ u
  19.             OLED_SDIN(1);2 Q- x5 n& {. v
  20.         }
    3 r- ~' ~8 q. c% u6 \
  21.         else
    3 L( W0 j7 O8 g3 F4 ^
  22.         {
    ) m3 I4 ~; v& s1 k- O& y" [/ P9 c
  23.             OLED_SDIN(0);
    ' v, k0 M. ^! ~3 b. X
  24.         }: a! }: k. T0 V0 Q
  25.         OLED_SCLK(1);4 h  t2 k9 @  G+ o
  26.         data <<= 1;. r: @2 t* R5 Z+ l6 A; g
  27.     }: [2 R1 b2 F, u/ N
  28. " `- h$ d/ e# H  S
  29.     OLED_CS(1);6 j# w+ a6 ]( L1 \8 f' L/ h
  30.     OLED_RS(1);( H: j2 n( b0 l* b1 Y7 M- R9 F" |
  31. }  F- b7 e( I4 @* Q6 \' L4 D
复制代码

9 _9 G2 |$ Y5 F2 i4 `两种模式下的oled_wr_byte函数形参是一样的。第一个形参data就是要写的内容。第二个形参cmd是通过选用OLED_CMD和OLED_DATA两个宏定义的其中一个,控制选择写命令还是写数据。两种模式下的oled_wr_byte函数的时序操作就是根据上面我们对8080接口以及4线SPI接口的时序来编写的。' x0 O' Q" M3 `7 k/ q
g_oled_gram [128][8]二维数组中的128代表列数(x坐标),而8代表的是页,每页又包含8行,总共64行(y坐标),从高到低对应行数从小到大,如表23.3.2.1所示:
2 R! Q6 e4 J& N7 O+ I, q( R; h7 C+ X/ C7 p2 c( c1 r/ K9 n
81cce940a19844069841ef46fab67f2b.png % ]' C4 a% q) [$ n: s7 Q; W

: ]* l# _6 _0 `: F) W! u! _  C" f表23.3.2.1 OLED_GRAM和OLED屏坐标对应关系" {8 B# S. V- b( U+ V% U5 `7 n$ c
上表中G代表OLED_GRAM,G[0][0]就表示OLED_GRAM[0][0]。比如,我们要在x=3,y=9这个点写入1,则可以用这个句子实现:7 ]5 M1 c: D! c1 ~
OLED_GRAM[3][1]|=1<<1;9 ^2 r6 @8 p9 F( p; `! ^8 C2 Y2 N/ ~
一个通用的在点(x,y)置1表达式为:+ s" r! C  {2 @7 ?& D3 N
OLED_GRAM[x][y/8]|=1<<(y%8);
/ X  c2 \( C2 V6 t0 X0 h2 y; V' V其中x的范围为:0127;y的范围为:063。  z7 r$ r& y  i1 V) N" b0 O
因此,我们可以得出接下来介绍的这个比较重要的函数:OLED画点函数,其定义如下:
. M0 B6 D4 q  v0 \$ Y3 t8 L3 h! O0 L0 ]7 x% Y
  1. /**/ G# O, X3 I% f1 Z& p  _
  2. * @brief       OLED画点 1 W5 {# q; c# Q  y
  3. * @param       x  : 0~127% U; W1 D' H: r
  4. * @param       y  : 0~63  r8 y* |: j) t) [7 e
  5. * @param       dot: 1 填充 0,清空
    # `) C/ B) Y& M( ^& I+ |
  6. * @retval      无( Z1 Q, p, j/ {# s  R1 {
  7. */ 6 ]" o- n5 x$ ]7 ^' V9 y' N
  8. void oled_draw_point(uint8_t x, uint8_t y, uint8_t dot)
    : G# J! h2 a3 f/ X! P! S
  9. {
    1 Z/ ]8 \& q; m4 t4 c5 ~( W
  10.     uint8_t pos, bx, temp = 0;! }/ S2 c! @! ?5 ~
  11. 7 C# e2 N4 ]( i
  12.     if (x > 127 || y > 63) return;  /* 超出范围了 */
    2 I2 k' q, o( L) }, {1 B; r
  13.   s' [# _5 e& e5 J
  14.     pos = y / 8;     /* 计算GRAM里面的y坐标所在的字节, 每个字节可以存储8个行坐标 */
    / P9 ^# b' K, x
  15. 0 F1 ?5 O# ], ~2 O( S
  16.     bx = y % 8;                /* 取余数,方便计算y在对应字节里面的位置,及行(y)位置 */
    2 L& w8 v6 G* ]. O) I
  17.     temp = 1 << bx;         /* 高位表示高行号, 得到y对应的bit位置,将该bit先置1 */, ~4 {' h6 @0 f, C5 X* R

  18. : e- W7 f' r: Q
  19.     if (dot)    /* 画实心点 */
    # u6 F- S. V" J' `8 X5 B4 g
  20.     {6 ?$ l2 [8 A$ f9 x8 A9 a
  21.         g_oled_gram[x][pos] |= temp;
    4 m/ E0 G" s" M( n
  22.     }' J! D6 H6 k7 a) A7 X
  23.     else        /* 画空点,即不显示 */
    & X+ r* u8 b1 I% h8 \: L' M) n+ ~
  24.     {& X1 E$ L  H) g2 X
  25.         g_oled_gram[x][pos] &= ~temp;
    6 M. ]+ ~3 j9 a9 W
  26.     }
    6 l/ l- c' F. b' K) t
  27. }
复制代码

0 `1 U: j! V. r该函数有3个形参,前两个是横纵坐标,第三个为要写入1还是0。该函数实现了我们在OLED模块上任意位置画点的功能。
6 c6 _% m7 a1 h4 C' I1 z9 P. h6 |9 }前面我们知道取模方式是:从上到下,从左到右,高位在前。下面根据取模的方式来编写显示字符oled_show_char函数,其定义如下:
7 n9 o. W2 [$ @( Z8 k. X/ m, @( W+ K: }
  1. /**4 K% T" x, i2 d% J9 C  q
  2. * @brief       在指定位置显示一个字符,包括部分字符
    * z- B# K3 D0 I! k4 r9 }. ?
  3. * @param       x   : 0~127
    2 P! f0 Z0 t2 j  ^  R/ K2 r' Y9 y
  4. * @param       y   : 0~63
    % a" y+ O+ c+ U' f3 O
  5. * @param       size: 选择字体 12/16/24
    9 B8 z" ^% u4 d
  6. * @param       mode: 0,反白显示;1,正常显示7 a. v8 D8 Z7 j0 B
  7. * @retval      无/ j. f+ u7 M) V  g$ x
  8. */ 6 s: o' v; A% F% J6 L) ~3 x( I6 k8 Q. ?9 {
  9. void oled_show_char(uint8_t x,uint8_t y,uint8_t chr,uint8_t size,uint8_t mode)6 r9 T: N; o6 C, M1 @6 J  p
  10. {7 T9 B& [' f0 }
  11.     uint8_t temp, t, t1;
    $ \* z5 |$ M; O7 \
  12.     uint8_t y0 = y;
    " q8 M6 Q, L8 v1 m/ e+ F' t2 }$ k
  13. uint8_t *pfont = 0;
    * s% P" [7 o* Y' v5 j/ G" a9 Z, o
  14. /* 得到字体一个字符对应点阵集所占的字节数 */' O9 h+ Y( b' W5 _
  15.     uint8_t csize = (size / 8 + ((size % 8) ? 1 : 0)) * (size / 2); ) o8 g3 a/ X9 `7 A
  16.     chr = chr - ' ';      /* 得到偏移后的值,因为字库是从空格开始存储的,第一个字符是空格 */
    - M) u' t5 |6 |& z
  17. 0 S1 }+ G+ k4 }
  18.     if (size == 12)       /* 调用1206字体 */$ G+ P* K) n+ F
  19.     {
    $ r$ g7 L. y; W( V
  20.         pfont = (uint8_t *)oled_asc2_1206[chr];        
    9 _" X' q" W- O0 N
  21.     }
    6 N  i( H  d. {
  22.     else if (size == 16) /* 调用1608字体 */ ! [) k' y; }" A2 u) M7 n; Q
  23.     {
    ) M# S0 G' `) o5 J2 O( g7 V
  24.         pfont = (uint8_t *)oled_asc2_1608[chr];; G3 j4 j# K) f) ]6 t6 G
  25.     }' H5 c' J3 l! d( j6 l* S7 n4 l1 I( A
  26.     else if (size == 24) /* 调用2412字体 */
    , V3 V( |+ d4 O! r4 k3 X
  27.     {# D6 m$ T0 |2 O( s$ j
  28.         pfont = (uint8_t *)oled_asc2_2412[chr];# A- r. `' V0 r, K9 n4 w
  29.     }  J2 D' h9 z) }3 x
  30.     else                    /* 没有的字库 */$ w, a& J" K# e, @
  31.     {# h# M# U$ [, j* g8 E8 N
  32.         return;     l& `* P: o6 N* U+ E, P
  33.     }& N2 C! ?  M5 B

  34. + c6 `% F% ^  r" ]
  35.     for (t = 0; t < csize; t++)
    4 [5 B$ v. i+ G! U( E
  36.     {
    2 [: M. O, s$ ~2 ]; R6 s. Z0 y. q7 P
  37.         temp = pfont[t];
    8 F- n/ a# t! d4 ^$ _' A
  38.         for (t1 = 0; t1 < 8; t1++)* D3 ]- j; z  S2 g. s
  39.         {# Q( ^0 B* k( ~- q2 ~
  40.             if (temp & 0x80)oled_draw_point(x, y, mode);1 j( z6 q$ c& M
  41.             else oled_draw_point(x, y, !mode);' l# `/ y" M: ^; n7 x1 _2 Q. |

  42. 8 b7 h/ Z) [6 [  f: k& F9 l# a
  43.             temp <<= 1;
    " N" k+ r6 J' b$ W7 o. v
  44.             y++;
    6 l. W6 Q1 }- T5 V
  45.   k. ]  Y6 k3 O! k
  46.             if ((y - y0) == size)5 ?8 v5 u. T9 N+ }
  47.             {, h( k! u4 h6 X6 c5 m
  48.                 y = y0;. [. _# R, }/ N- F' V
  49.                 x++;
    : k/ \  A' Y- b8 F
  50.                 break;& |4 N: v! }9 B$ m" |
  51.             }% v: F/ k7 G4 X9 r" v4 z; l6 i
  52.         }
    - d9 `1 }* t2 Y# ?
  53.     }  v7 z+ d$ y4 H: F7 Z
  54. }
复制代码

) Z( ^  W0 Q! C: \该函数为字符以及字符串显示的核心部分,函数中chr = chr - ’ ';这句是要得到在字符点阵数据里面的实际地址,因为我们的取模是从空格键开始的,例如oled_asc2_1206 [0][0],代表的是空格符开始的点阵码。在接下来的代码,我们也是按照从上到小(先y++),从左到右(再x++)的取模方式来编写的,先得到最高位,然后判断是写1还是0,画点;接着读第二位,如此循环,直到一个字符的点阵全部取完为止。这其中涉及到列地址和行地址的自增,根据取模方式来理解,就不难了。: F' j2 Z3 Q/ C& T+ u
oled.c的内容比较多,其他的函数请大家自行理解,下面开始main.c文件的介绍。
2 }0 e# {+ y0 m, _- Y$ I2. main.c代码3 T& X' H8 r7 _- t
在main.c里面编写如下代码:
1 {; h1 x9 ]0 Q5 t% Q: k1 T2 G& J2 }' @- \$ I! b  P6 c
  1. int main(void)7 J8 a0 P) m% r
  2. {  y- h, b4 m7 d8 }
  3.     uint8_t t = 0;) y- L3 H6 y2 _+ r3 N, `
  4.     sys_cache_enable();                             /* 打开L1-Cache */, q2 B$ ~7 G  T
  5.     HAL_Init();                                       /* 初始化HAL库 */
    2 n6 `! {7 ^$ m/ o
  6.     sys_stm32_clock_init(240, 2, 2, 4);          /* 设置时钟, 480Mhz */
      B4 C0 b/ \* \% a, f/ j7 V
  7.     delay_init(480);                                 /* 延时初始化 */
    " T. b% c6 y& k
  8.     usart_init(115200);                             /* 串口初始化为115200 *// g/ S  M! H; S" C8 A! J
  9.     led_init();                                       /* 初始化LED */
    . g9 k" B0 V* @9 C
  10.     oled_init();                                      /* 初始化OLED */
    $ h% x, y9 w9 k9 I  M
  11.     oled_show_string(0, 0, "ALIENTEK", 24);0 @9 n) E: S( A
  12.     oled_show_string(0, 24, "0.96' OLED TEST", 16);& f  e, y3 f4 B" f4 @- q4 h
  13.     oled_show_string(0, 40, "ATOM 2020/3/22", 12);
    % p, n8 M: A0 `# O$ @* E! ]
  14.     oled_show_string(0, 52, "ASCII:", 12);
      H' X$ {( M5 ?* d; p0 {
  15.     oled_show_string(64, 52, "CODE:", 12);
    0 k& j' w+ ?, i2 `, l" O$ ^
  16.     oled_refresh_gram();    /* 更新显示到OLED */
    9 z  c% [" @& y# N  g- U
  17. t = ' '; / k) o  M, y! w* S
  18.     while (1). `8 x1 ~( {7 P. D% E
  19.     {
    % Y8 S! ^' X" ^* E1 F
  20.         oled_show_char(36, 52, t, 12, 1);        /* 显示ASCII字符 */
    ! V6 j; M! V$ I7 Y6 Q; `3 H; V
  21.         oled_show_num(94, 52, t, 3, 12);         /* 显示ASCII字符的码值 */
    5 p7 B5 I& R* w1 G
  22.         oled_refresh_gram();                       /* 更新显示到OLED */
    4 w, @- g; V3 f; M1 k5 i
  23.         t++;
    5 t7 W3 _1 l) k+ P
  24.         if (t > '~')t = ' ';! n+ m: h) G9 H. o8 U  i5 t3 a
  25.         delay_ms(500);6 P8 K6 n8 X8 k9 E
  26.         LED0_TOGGLE();  /* LED0闪烁 */! I! G$ q" b/ `( V
  27.     }( Y* C: {+ D3 _* y
  28. }
复制代码

7 R$ t. Y9 w2 K4 u) P$ x6 d+ Q1 J- HMain.c主要功能就是在OLED上显示一些实验信息字符,然后开始从空格键开始不停的循环显示ASCII字符集,并显示该字符的ASCII值。最后LED0闪烁提示程序正在运行。9 A) k& e9 r* Y- F  h8 A: i
23.4 下载验证# e3 o. M& X0 {) R' y" M# H$ r- ?
下载代码后,LED0不停的闪烁,提示程序已经在运行了。同时OLED模块显示ASCII字符集等信息,如图23.4.1所示:( d4 k* L7 X! m

3 r- G! I# o: f4 E9 W 6c845089d4294fb18cd1ab691a871a50.png
7 u5 f9 e7 G. I& z3 H
* Y0 g: K2 C: l1 `$ l5 S' f图23.4.1 OLED显示效果2 j9 Q. k. J9 I( v
OLED显示了三种尺寸的字符:2412(ALIENTEK)、168(0.96’ OLED TEST)和12*6(剩下的内容)。说明我们的实验是成功的,实现了三种不同尺寸ASCII字符的显示,在最后一行不停的显示ASCII字符以及其码值。; J7 C, ?" z  u" ^% X8 r* P
通过这一章的学习,我们学会了ALIENTEK OLED模块的使用,在调试代码的时候,又多了一种显示信息的途径,在以后的程序编写中,大家可以好好利用。% D5 ?' @' P4 ?/ J* e7 b( e9 D
————————————————) ]( s9 P# y) i( L" Y& o9 o
版权声明:正点原子5 @4 X( d4 {" T( S6 n
6 Z) I" u/ W- U5 U: q) A

" t" a$ L$ h+ F6 L: D% `
收藏 评论0 发布时间:2022-10-7 16:49

举报

0个回答

所属标签

相似分享

官网相关资源

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