前言
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 配置所示:
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。 |
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 像素。
SSD1306 OLED 驱动板电路:
NodeMCU + OLED 实战
电路图
本文基于 NodeMCU ESP8266 HSPI 默认管脚连接 7针 SSD1306 0.96寸 OLED 显示屏,连接如下:
下表显示了 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 引脚的变量。
#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);
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! 字符。 ```cinclude
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);
// 初始化 OLED 显示器
if (!display.begin(SSD1306_SWITCHCAPVCC))
{
Serial.println(F("SSD1306 allocation failed"));
// Don't proceed, loop forever
for (;;) {}
}
// 清除屏幕
display.clearDisplay();
// 设置字体颜色
display.setTextColor(WHITE);
// 设置光标位置
display.setCursor(30, 30);
// 打印函数
display.println("Hello, world!");
// 显示
display.display();
}
void loop() { }
<a name="4gHFS"></a>
#### 绘制图像和中文
我们想显示中文或图片,需要将其转换成位图数组。我们可以使用在线工具进行转换:
- 图片转位图数组:[http://javl.github.io/image2cpp/](http://javl.github.io/image2cpp/)
 <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);
需要注意取模方式:高位在前。
// 匠 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);