前言

NodeMCU ESP8266 具有连接其他外设的 GPIO 引脚,并支持使用 SPI、IIC、UART 引脚进行串行通信。本文尝试基于 NodeMCU ESP8266 控制 OLED 显示屏。大学时利用 MSP430 单片机玩过 OLED 模块,当时是基于 IIC 通信方式驱动的,本次学习 ESP8266 的过程打算尝试一下 SPI,所以买了一块 SPI/IIC 兼容的 OLED 模块,发现默认是 SPI 模式,需要连接 7 个引脚;如果使用 IIC 模式,需要连接 4 个引脚,但是需要焊接显示屏上的电阻。如果想少用 MCU 上的引脚,可以直接选择 IIC 模式的 OLED 模块。

ESP8266 SPI 通信

SPI 通信原理

SPI 是串行外设接口(Serial Peripheral Interface)的缩写,是 Motorola 公司推出的一种同步串行接口技术,是一种高速的、全双工、同步的通信总线。SPI 作为一种总线通信方式,可以通过 SPI 接口连接多个从设备,并通过片选控制来选择对某一设备进行连接使用。SPI 接口只能有一个主机,但可以有一个或多个从机,注意同一时刻,只有一个主设备和一个从设备进行通信。双向传输需要 4 线,单向传输需要 3 线,如下图多从机 SPI 配置所示:
image.png
SPI 从机设备通常使⽤四线通信,分别为 SCLK、MOSI、MISO 和 CS。

名称 含义
SCLK 时钟线,用于通信同步的时钟信号,由主机产生
MOSI 数据线,主机输出,从机输⼊
MISO 数据线,主机输⼊,从机输出
CS 片选线,从机片选使能信号,由主机控制

MOSI 和 MISO 是数据线,MOSI 将数据从主机发送到从机,MISO 将数据从从机发送到主机;片选信号来自主机的用于选择从机,当有多个从机的时候,因为每个从机上都有一个 CS 引脚接入到主机中,当我们主机和某个从机通信时将需要将从机的 CS 引脚电平设置为低电平或者高电平。

ESP8266 HSPI

ESP8266 有两组 SPI 通信模块命名分别为 SPI 与 HSPI,其中 SPI 通常专⻔⽤于从⽚外 Flash 读取 CPU 程序代码,⽽ HSPI 则⽤于⽤户 SPI 设备的通信操作。HSPI 在主机通信模式下,硬件⽀持 3 个⽤户设备以及⼀个⽚外 Flash 读写操作。连接⽅式具体为:

模式 设备
HSPI Default IO ⽤户设备 1
SPI OVERLAP and CS1 ⽤户设备 2
SPI OVERLAP and CS2 ⽤户设备 3
SPI OVERLAP and CS0 Flash

此连接与 SPI 共⽤⼀个⽚外 Flash,除去程序与相关配置所使⽤的空间外,剩余的 Flash 空间均可⽤于⽤户数据的读写。
HSPI 主机三种不同的⽤户设备连接⽅法如下表所示:

HSPI 默认管脚 MTDO 对应 CS,MTCK 对应 MOSI,MTDI 对应 MISO,MTMS 对应 CLK。
SPI OVERLAP 加 CS1 U0TXD 对应 CS1,SD_CLK 对应 SCLK,SD_DATA0 对应 MISO,SD_DATA1 对 应 MOSI。
SPI OVERLAP 加 CS2 GPIO0 对应 CS2,SD_CLK 对应 SCLK,SD_DATA0 对应 MISO,SD_DATA1 对 应 MOSI。

image.png
image.png

SSD1306 OLED

有机发光二极管(Organic Light Emitting Diode,OLED)是一种发光二极管,其中由有机化合物制成的发光层在供应电流时发光。OLED 由于同时具备自发光,不需背光源、对比度高、厚度薄、视角广、反应速度快、使用温度范围广等优异之特性,被认为是下一代的平面显示器新兴应用技术。OLED 显示技术具有自发光的特性,采用非常薄的有机材料涂层和玻璃基板,当有电流通过时,这些有机材料就会发光,而且 OLED 显示屏幕可视角度大,并且能够节省电能, LCD 都需要背光,而 OLED 不需要,因为它是自发光的。这样同样的显示,OLED 效果要来得好一些。以目前的技术,OLED 的尺寸还难以大型化,但是分辨率确可以做到很高。OLED 在手机子屏,MP3,计算器以及穿戴产品中广泛应用。
市场上有各种类型的 OLED 显示器,显示器按照颜色,引脚数,控制器 IC 和屏幕尺寸进行分类。根据颜色,OLED 有单色蓝色,单色白色和黄色/蓝色可供选择。根据通信方式,主要有两种类型的 OLED 可用,3pin和 7pin,3 引脚 OLED 可以用于 I2C 通信模式,而 7 引脚 OLED 可以用于 SPI 模式或 I2C 模式。
本文 7 针 SSD1306 0.96寸 OLED 显示屏,其宽 128 像素,长 64 像素。
截屏2021-01-10 上午12.02.25.png
SSD1306 OLED 驱动板电路:

截屏2021-01-09 下午10.43.05.png

NodeMCU + OLED 实战

电路图

本文基于 NodeMCU ESP8266 HSPI 默认管脚连接 7针 SSD1306 0.96寸 OLED 显示屏,连接如下:
Circuit-Diagram-for-Interfacing-OLED-Display-with-NodeMCU-ESP8266.png
下表显示了 OLED 显示屏和 NodeMCU ESP8266 之间的连接。GND 引脚连接到 NodeMCU GND,VDD 引脚可以连接到 3.3V 或 5V,SCK 是 OLED 显示器上的时钟引脚,它连接到 NodeMCU 的 D5 用于 SPI 时钟,SPI 接口 OLED 上的 MOSI 引脚 SDA 引脚转到 NodeMCU 的 D7,RESE T引脚转到 D3,DC 数据命令引脚连接到NodeMCU 的 D2,最后一个引脚是 CS 进入 D8。

SSD1306 OLED NodeMCU
GND GND
VDD 3.3V
SCK D5
MOSI (SPI) or SDA (I2C) D7
RESET D3
DC D2
CS D8

代码分析

依赖库:Adafruit SSD1306、Adafruit GFX、Adafruit BusIO

Adafruit_SSD1306 基础

我们的 OLED 尺寸为 128x64,因此我们将屏幕宽度和高度分别设置为 128 和 64。因此定义连接到 NodeMCU以进行 SPI 通信的 OLED 引脚的变量。

  1. #define SCREEN_WIDTH 128 // OLED display width, in pixels
  2. #define SCREEN_HEIGHT 64 // OLED display height, in pixels
  3. // Declaration for SSD1306 display connected using software SPI (default case):
  4. #define OLED_MOSI D7
  5. #define OLED_CLK D5
  6. #define OLED_DC D2
  7. #define OLED_CS D8
  8. #define OLED_RESET D3
  9. Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

Adafruit_SSD1306 常用函数:

  • display.clearDisplay:清除 OLED 屏幕显示
  • display.setTextSize:设置文本字体大小
  • display.**setTextColor:**设置文本颜色
  • display.**setCursor:**设置光标位置
  • display.startscrollright|display.startscrollleft设置文本滚动
  • display.stopscroll:停止滚动文本
  • display.drawPixel:绘制像素
  • display.drawLine:绘制直线
  • display.drawRect | **display.fillRect:**绘制或填充矩形
  • display.drawRoundRect** | display.fillRoundRect:**绘制或填充圆角矩形
  • display.drawCircle** | display.fillCircle**:绘制或填充圆圈
  • display.drawTriangle** | display.fillTriangle:**绘制或填充三角形
  • display.drawBitmap:绘制图像
  • dispaly.drawChar:绘制字符
  • display.display:将数据传输到 SSD1306 控制器的内部存储器

    Hello, world

    我们可以通过下面的代码在 OLED 上显示 Hello, world! 字符。 ```c

    include

    include

    include

    include

define SCREEN_WIDTH 128 // OLED display width, in pixels

define SCREEN_HEIGHT 64 // OLED display height, in pixels

// Declaration for SSD1306 display connected using software SPI (default case):

define OLED_MOSI D7

define OLED_CLK D5

define OLED_DC D2

define OLED_CS D8

define OLED_RESET D3

Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, OLED_MOSI, OLED_CLK, OLED_DC, OLED_RESET, OLED_CS);

void setup() { Serial.begin(74880);

  1. // 初始化 OLED 显示器
  2. if (!display.begin(SSD1306_SWITCHCAPVCC))
  3. {
  4. Serial.println(F("SSD1306 allocation failed"));
  5. // Don't proceed, loop forever
  6. for (;;) {}
  7. }
  8. // 清除屏幕
  9. display.clearDisplay();
  10. // 设置字体颜色
  11. display.setTextColor(WHITE);
  12. // 设置光标位置
  13. display.setCursor(30, 30);
  14. // 打印函数
  15. display.println("Hello, world!");
  16. // 显示
  17. display.display();

}

void loop() { }

<a name="4gHFS"></a>
#### 绘制图像和中文
我们想显示中文或图片,需要将其转换成位图数组。我们可以使用在线工具进行转换:

- 图片转位图数组:[http://javl.github.io/image2cpp/](http://javl.github.io/image2cpp/) 

![截屏2021-01-09 下午11.36.29.png](https://cdn.nlark.com/yuque/0/2021/png/696445/1610206579155-5f93950a-8641-4b9e-a0ec-106652802deb.png "截屏2021-01-09 下午11.36.29.png")      <br />绘制图像逻辑如下:
```c
// 位图数组
const unsigned char avatarBitmap[] PROGMEM = {
    0xff, 0xfb, 0xff, 0xff, 0xff, 0xf9, 0xff, 0xff, 0xff, 0x98, 0x7f, 0xff, 0xff, 0x80, 0x07, 0xff, 
    0xff, 0x00, 0x00, 0xff, 0xfe, 0x00, 0x00, 0x7f, 0xfe, 0x00, 0x00, 0x3f, 0xfc, 0x00, 0x00, 0x3f, 
    0xfc, 0x00, 0x00, 0x3f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x00, 0x00, 0x1f, 
    0xf8, 0x00, 0x00, 0x1f, 0xf8, 0x30, 0x04, 0x1f, 0xfc, 0x60, 0x9c, 0x1f, 0xfc, 0x47, 0xe0, 0x3f, 
    0xfc, 0x7f, 0xee, 0x3f, 0xf8, 0x47, 0xe2, 0x1f, 0xfa, 0x7f, 0xfe, 0x5f, 0xfb, 0x7f, 0xfe, 0xdf, 
    0xfd, 0x7f, 0xfe, 0xbf, 0xfe, 0x7f, 0xfe, 0x7f, 0xff, 0xbf, 0xfd, 0xff, 0xff, 0x9f, 0x79, 0xff, 
    0xff, 0xcf, 0xf3, 0xff, 0xff, 0xe7, 0xe7, 0xff, 0xff, 0xc0, 0x03, 0xff, 0xff, 0xdf, 0xfb, 0xff, 
    0xff, 0x95, 0xa9, 0xff, 0xff, 0xb7, 0x8d, 0xff, 0xff, 0xb7, 0x85, 0xff, 0xff, 0x27, 0xe5, 0xff
};

// 绘制图像
display.drawBitmap(20, 20, avatarBitmap, 32, 32, BLACK, WHITE);

截屏2021-01-10 上午1.10.39.png
需要注意取模方式:高位在前。

// 匠 12x12
const unsigned char jiangBitmap[] PROGMEM = {
    0x00,0x40,0x7F,0xE0,0x40,0x00,0x5F,0x00,0x50,0x40,0x5F,0xE0,0x51,0x00,0x51,0x00,
    0x51,0x00,0x61,0x00,0x40,0x20,0x7F,0xF0,
};

// 心 12x12
const unsigned char xinBitmap[] PROGMEM = {
    0x04,0x00,0x02,0x00,0x02,0x00,0x08,0x00,0x08,0x40,0x48,0x20,0x48,0x10,0x48,0x10,
    0x88,0x40,0x08,0x40,0x07,0xC0,0x00,0x00 
};

display.drawBitmap(0, 0, jiangBitmap, 12, 12, WHITE);
display.drawBitmap(12, 0, xinBitmap, 12, 12, WHITE);

参考