【SPI】驱动OLED液晶屏幕
这一节我们学习如何使用 ESP32 开发板,通过 SPI 控制 OLED 液晶屏
实验原理
1. SPI
SPI(Serial Peripheral Interface) 协议是由摩托罗拉公司提出的通讯协议,即串行外围设备接口,是一种同步、全双工、主从式接口,但并不是所有的 SPI 都是全双工。
来自主机或从机的数据在时钟上升沿或下降沿同步。主机和从机可以同时传输数据。
SPI 接口可以是 1 线、2 线 3 线式或 4 线式,这节课,我们用到的就是 3 线 SPI
产生时钟信号的器件称为 主机。主机和从机之间传输的数据与主机产生的时钟同步。 同 I2C 接口相比,SPI 器件支持更高的时钟频率。用户应查阅产品数据手册以了解 SPI 接口的时钟频率规格
标准 4 线 SPI 芯片的管脚上只占用四根线
MOSI: 主器件数据输出,从器件数据输入。MISO:主器件数据输入,从器件数据输出。SCK: 时钟信号,由主设备控制发出。CS(NSS): 从机设备选择信号,由主设备控制。当 CS 为低电平则选中从器件
3 线 SPI 没有 MISO,或者 MISO 与 MOSI 共线
SPI 接口只能有一个主机,但可以有一个或多个从机。下图显示了主机和从机之间的 SPI 连接。来自主机的片选信号用于选择从机。 这通常是一个低电平有效信号,拉高时从机与 SPI 总线断开连接。当使用多个从机时,主机需要为每个从机提供单独的片选信号。 MOSI 和 MISO 是数据线。MOSI 将数据从主机发送到从机,MISO将数据从从机发送到主机。

ESP32 集成了 4 个 SPI 外设
- 其中两个在内部用于访问 ESP32 所连接的闪存。两个控制器共享相同的 SPI 总线信号,并且有一个仲裁器来确定哪个可以访问该总线。
- 另外两个是通用 SPI 控制器,分别称为 HSPI 和 VSPI。它们向用户开放,具有独立的总线信号,分别具有相同的名称。每条总线具有 3 条 CS 线,最多能控制 6 个 SPI 从设备
I2C 和 SPI 的区别
I2C 只需两根信号线,而标准 SPI 至少四根信号,如果有多个从设备,信号需要更多。一些 SPI 变种虽然只使用三根线—— SCK、CS 和双向的 MISO/MOSI,但 CS 线还是要和从设备一对一根。 另外,如果 SPI 要实现多主设备结构,总线系统需额外的逻辑和线路。用 I2C 构建系统总线唯一的问题是有限的 7 位地址空间,但这个问题新标准已经解决 --- 使用 10 位地址
如果应用中必须使用高速数据传输,那么 SPI 是必然的选择。因为 SPI 是全双工,IIC 的不是。SPI 没有定义速度限制,一般的实现通常能达到甚至超过 10Mbps。 IIC 最高的速度也就快速+模式(1Mbps)和高速模式(3.4Mbps),后面的模式还需要额外的 I/O 缓冲区,还并不是总是容易实现的。 SPI 适合数据流应用,而 IIC 更适合“字节设备”的多主设备应用
SPI 有一个非常大的缺陷,主要是没有标准的协议,SPI 比较混乱,主要是没有标准的协议,只有moto的事实标准。所以衍生出多个版本,但没有本质的差异
2. OLED 液晶屏
OLED,即有机发光二极管(Organic Light Emitting Diode)。 OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、可用于挠曲性面板、使用温度范围广、构造及制程较简单等优异之特性,被称为是第三代显示技术
LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示 OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高

我们今天用到的屏幕是 0.96 寸的 SSD1306 芯片驱动的 OLED 屏幕。他的分辨率是 128*64,意思就是横向有 128 个像素点,纵向有 64 个
OLED 显示屏模块接口定义:
- GND:电源地。
- VCC:电源正(3.3~5V)。
- D0:OLED 的 D0 脚,在 SPI 通信中为时钟管脚。
- D1:OLED 的 D1 脚,在 SPI 通信中为数据管脚。
- RES:OLED 的 RES 脚,用来复位(低电平复位)。
- DC:数据和命令控制管脚。
- CS:片选管脚。

硬件电路设计
物料清单(BOM 表):
| 材料名称 | 数量 |
|---|---|
| 0.96寸 OLED 屏幕 | 1 |
| 按键 | 2 |
| 杜邦线(跳线) | 若干 |
| 面包板 | 1 |

软件程序设计
如果想要使用 Arduino 控制 SSD1306 驱动的 OLED 屏幕,有以下两种第三方库可以使用:
-
Adafruit_SSD1306 库:专门针对 SSD1306 驱动 OLED 屏幕的显示图形库 -
U8G2 库:目前 Arduino 平台上使用最广泛的 OLED 库
1. Adafruit_SSD1306 控制 OLED 屏幕
想要使用 Adafruit_SSD1306,还需要安装 Adafruit_GFX 第三方库。
Arduino 的 Adafruit_GFX 库为我们所有的 LCD 和 OLED 显示器提供了通用语法和图形功能集,也就是说这是一个通用图形库,并不针对特定的显示器型号
-
Adafruit_GFX定义了一系列的绘画方法(线,矩形,圆等等),属于基础类,并且最重要的一点,drawPixel 方法由子类来实现 -
Adafruit_SSD1306定义了一系列跟 SSD1306 有关的方法,并且重写了 drawPixel 方法,属于扩展类
首先,我们就需要先下载这两个第三方库,PlatformIO 已经为我们提供了方便的下载途径,我们可以直接在 PlatformIO 的 PIO HOME 页面中选择 Libraries 中分别搜索 Adafruit GFX Library 与 Adafruit_SSD1306,然后添加到项目中即可

下载完以上两个第三方库之后,打开 platformio.ini 文件,可以看到 lib_deps 中出现了 SSD1306 与 Adafruit GFX Library 两个依赖

在学习 Adafruit_SSD1306 之前,你需要明白无论什么 OLED 屏幕,最终都可以抽象为像素点阵,想显示什么内容就把具体位置的像素点亮起来。
比如 SSD1306-12864 就是一个 128X64 像素点阵,这个点阵拥有自己的一套坐标系,在坐标系中,左上角是原点,向右是X轴,向下是Y轴

接下来,我们就可以深入学习该库了
SSD1306 包括 IIC 和 SPI 总线版本,所以针对不同版本又有对应的构造器方法,因为我们的 OLED 是 SPI 版本的,因此,我们只讲 SPI 总线的构造方法。以下代码是
/*!
@brief Constructor for SPI SSD1306 displays, using software (bitbang)
SPI.(软件SPI总线)
@param w
Display width in pixels
@param h
Display height in pixels
@param mosi_pin
MOSI (master out, slave in) pin (using Arduino pin numbering).
This transfers serial data from microcontroller to display.
@param sclk_pin
SCLK (serial clock) pin (using Arduino pin numbering).
This clocks each bit from MOSI.
@param dc_pin
Data/command pin (using Arduino pin numbering), selects whether
display is receiving commands (low) or data (high).
@param rst_pin
Reset pin (using Arduino pin numbering), or -1 if not used
(some displays might be wired to share the microcontroller's
reset pin).
@param cs_pin
Chip-select pin (using Arduino pin numbering) for sharing the
bus with other devices. Active low.
@return Adafruit_SSD1306 object.
@note Call the object's begin() function before use -- buffer
allocation is performed there!
*/
Adafruit_SSD1306(uint8_t w, uint8_t h, int8_t mosi_pin, int8_t sclk_pin,
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin);
/*!
@brief Constructor for SPI SSD1306 displays, using native hardware SPI.(硬件SPI总线)
@param w
Display width in pixels
@param h
Display height in pixels
@param spi
Pointer to an existing SPIClass instance (e.g. &SPI, the
microcontroller's primary SPI bus).
@param dc_pin
Data/command pin (using Arduino pin numbering), selects whether
display is receiving commands (low) or data (high).
@param rst_pin
Reset pin (using Arduino pin numbering), or -1 if not used
(some displays might be wired to share the microcontroller's
reset pin).
@param cs_pin
Chip-select pin (using Arduino pin numbering) for sharing the
bus with other devices. Active low.
@param bitrate
SPI clock rate for transfers to this display. Default if
unspecified is 8000000UL (8 MHz).
@return Adafruit_SSD1306 object.
@note Call the object's begin() function before use -- buffer
allocation is performed there!
*/
Adafruit_SSD1306(uint8_t w, uint8_t h, SPIClass *spi,
int8_t dc_pin, int8_t rst_pin, int8_t cs_pin, uint32_t bitrate=8000000UL);
软件 SPI 总线用法,代码如下:
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
// 软件SPI总线
// Declaration for SSD1306 display connected using software SPI (default case):
#define OLED_MOSI 13
#define OLED_CLK 18
#define OLED_DC 2
#define OLED_CS 4
#define OLED_RESET 15
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT,
OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);
使用硬件 SPI 总线,代码如下:
#include <SPI.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>
#define SCREEN_WIDTH 128 // OLED display width, in pixels
#define SCREEN_HEIGHT 64 // OLED display height, in pixels
#define OLED_DC 2
#define OLED_CS 4
#define OLED_RESET 15
Adafruit_SSD1306 oled(SCREEN_WIDTH, SCREEN_HEIGHT,
&SPI, OLED_DC, OLED_RESET, OLED_CS);
接下来的方法(函数)无论是 I2C 还是 SPI 总线构建的,用法都是一致的:
display:显示内容,这个方法才是真正把绘制内容画在 OLED 屏幕上(非常重要);drawCircle:绘制空心圆;fillCircle:绘制实心圆;drawTriangle:绘制空心三角形;fillTriangle:绘制实心三角形;drawRoundRect:绘制空心圆角方形;fillRoundRect:绘制实心圆角方形;drawBitmap:绘制 Bitmap 图形;drawXBitmap:绘制 XBitmap 图形;drawChar:绘制单个字符;getTextBounds:计算字符串在当前字体大小下的像素大小,返回左上角坐标以及宽度高度像素值;setTextSize:设置字体大小;setFont:设置字体;setCursor:设置光标位置;setTextColor:设置字体颜色;setTextWrap:设置是否自动换行;drawPixel:绘制像素点;drawFastHLine:绘制水平线;drawFastVLine:绘制垂直线;startscrollright:滚动到右边;startscrollleft:滚动到左边;startscrolldiagright:沿着对角线滚动到右边;startscrolldiagleft:沿着对角线滚动到左边;stopscroll:停止滚动: