跳到主要内容

AMOLED

微雪 ESP32-S3-Touch-AMOLED-2.16 模块

AMOLED(Active-Matrix OLED,有源矩阵有机发光二极管)是一类自发光屏幕:对比度高、全彩、高分辨率、响应快,智能手表普遍采用这类屏幕。近年小尺寸 AMOLED 模组价格下降,在 ESP32 平台上构建手表级显示效果的设备已较为常见。

本章介绍 AMOLED 的原理与驱动方法,重点是高分辨率带来的带宽与内存问题。

1. 显示原理

AMOLED 与 OLED 章节 介绍的 PMOLED(Passive-Matrix OLED,被动矩阵有机发光二极管)同源:每个像素都是自发光的有机发光二极管。区别在驱动方式:

PMOLED 逐行扫描与 AMOLED 有源矩阵驱动方式对比

  • PMOLED(被动矩阵):行列共用电极、逐行扫描,任一时刻只有一行发光。行数越多,每行分到的点亮时间越短,要维持亮度就得拉高瞬时电流,亮度和分辨率因此受限,常用于小尺寸单色屏。
  • AMOLED(有源矩阵):在面板上集成一层 TFT(Thin Film Transistor,薄膜晶体管)背板,每个像素配有独立的开关管和存储电容,被写入后可以持续发光,直到下次更新。这解除了扫描方式对规模的限制,使高分辨率、高亮度的全彩显示成为可能。

自发光的特性也随之继承:黑色像素完全不发光,对比度极高;功耗随画面内容变化,深色界面显著省电,这也是智能手表多采用黑底表盘的原因。

2. 特性与局限

优势:

  • 对比度和色彩表现出色,支持 16.7M 色(RGB888 级别)。
  • 高分辨率、高像素密度,画面精细。
  • 响应快,无拖影。
  • 面板轻薄,支持圆形、圆角等异形切割,适合手表类产品。
  • 深色画面省电,适合黑底 UI 的常显表盘。

局限:

  • 成本高,同尺寸下高于 LCD。
  • 有烧屏风险:与 OLED 相同,长期显示固定内容会留下痕迹,长时间运行的 UI 应避免完全静止的高亮元素。
  • 对主控要求高:高分辨率意味着大帧缓冲和高传输带宽,通常需要 ESP32-S3 搭配 PSRAM(Pseudo Static RAM,伪静态随机存取存储器)的配置。

3. 常见驱动 IC 与接口

ESP32 生态常见的 AMOLED 驱动 IC 有 SH8601、CO5300、RM690B0 等,均内置 GRAM(Graphics RAM,图形随机存取存储器),主流接口为 QSPI(Quad SPI,四线 SPI)。QSPI 成为主流接口的原因,可通过带宽估算说明(估算方法见 显示基础与接口):

以 2.16 英寸 480×480 屏、RGB565 格式为例,一帧约 450 KB(3.7 Mbit):

接口带宽(80 MHz 时钟)理论帧率上限
SPI(1 根数据线)80 Mbps约 22 帧
QSPI(4 根数据线)320 Mbps约 87 帧

单线 SPI 仅能满足静态显示,运行流畅 GUI 则带宽不足;QSPI 在协议上与 SPI 相近,仅增加三根数据线,带宽提升至 4 倍,因此成为高分辨率 AMOLED 的标准接口。分辨率更高的产品(如 720×1280)则会采用 MIPI-DSI(MIPI Display Serial Interface;仅 ESP32-P4 支持)。

SPI 与 QSPI 的数据线数量与帧率对比

内存方面同理:LVGL(Light and Versatile Graphics Library)这类 GUI 库通常需要一到两个整帧缓冲,480×480 RGB565 单帧约 450 KB,远超 ESP32-S3 的片内 RAM,必须使用 PSRAM。选购开发板时应确认其 PSRAM 容量是否满足帧缓冲需求,并在工程选项中开启 OPI PSRAM

另外两点与 LCD 不同:

  • 没有背光引脚:AMOLED 自发光,亮度通过向驱动 IC 发送命令调节,而不是 PWM 控制 BL 引脚。
  • 部分模组的初始化序列包含厂商调校的伽马与电源参数,直接使用微雪示例包中的驱动配置即可。

4. Arduino + Arduino_GFX 驱动示例

关于开发板

本节以 微雪 ESP32-S3-Touch-AMOLED-2.16 为例。该开发板板载 2.16 英寸 480×480 电容触摸 AMOLED 屏(驱动 IC 为 CO5300,触摸 IC 为 CST9217),并集成电源管理、IMU(Inertial Measurement Unit,惯性测量单元)、RTC(Real-Time Clock,实时时钟)、音频等外设,详见 产品页

4.1 准备工作

  1. 安装 Arduino IDE 并添加 ESP32 支持(参考 Arduino IDE 开发环境搭建教程)。
  2. 在库管理器(工具 > 管理库)中搜索并安装 GFX Library for ArduinoU8g2。前者驱动屏幕,后者提供英文字体。本节代码使用 Arduino_GFX v1.6.6 和 U8g2 v2.36.19,并已在 arduino-esp32 v3.3.10 下通过编译。
  3. 在 Arduino IDE 的开发板选择器中搜索并选择 Waveshare ESP32-S3-Touch-AMOLED-2.16。如果搜索不到该型号,请在开发板管理器中将 esp32 by Espressif Systems 更新至 v3.3.10 及以上。本节示例使用 200×40 像素的局部画布,约占用 16 KB RAM,无需启用 PSRAM;启用 PSRAM 不影响运行。运行 LVGL 等需要全屏帧缓冲的示例时,建议启用 PSRAM。

本章仅涉及屏幕显示,因此仅需上述两个库。触摸、IMU、RTC、音频等外设有各自的驱动库,请参考对应的外设章节,或从 产品资料页 获取官方示例。

4.2 确认引脚

ESP32-S3-Touch-AMOLED-2.16 的屏幕已直连主控:

信号GPIO说明
LCD_SDIO0GPIO4QSPI 数据线 0
LCD_SDIO1GPIO5QSPI 数据线 1
LCD_SDIO2GPIO6QSPI 数据线 2
LCD_SDIO3GPIO7QSPI 数据线 3
LCD_SCLKGPIO38QSPI 时钟
LCD_CSGPIO12片选
LCD_RESETGPIO39复位

AMOLED 自发光,因此没有 LCD_BL 背光控制引脚。

4.3 点亮屏幕

AMOLED 的驱动代码与 LCD 章节 的分层完全一致,只是总线换成 QSPI、驱动类换成对应 IC。下面的示例在纯黑背景上绘制一排饱和色块,再叠加一行文字,用饱和色紧贴纯黑的画面直观展示 AMOLED 的高对比度与色彩表现:

#include <U8g2lib.h>
#include <Arduino_GFX_Library.h>

// 引脚定义(ESP32-S3-Touch-AMOLED-2.16)
#define LCD_SDIO0 4
#define LCD_SDIO1 5
#define LCD_SDIO2 6
#define LCD_SDIO3 7
#define LCD_SCLK 38
#define LCD_CS 12
#define LCD_RESET 39

#define LCD_WIDTH 480
#define LCD_HEIGHT 480

// 色卡布局:7 个 50×120 像素色块,水平居中
#define SWATCH_COUNT 7
#define SWATCH_WIDTH 50
#define SWATCH_HEIGHT 120
#define SWATCH_X ((LCD_WIDTH - SWATCH_COUNT * SWATCH_WIDTH) / 2)
#define SWATCH_Y 150

// 一排饱和色块,配合纯黑背景展示 AMOLED 的对比度与色彩
const uint16_t SWATCH_COLORS[SWATCH_COUNT] = {
RGB565_RED,
RGB565_ORANGE,
RGB565_YELLOW,
RGB565_GREEN,
RGB565_CYAN,
RGB565_BLUE,
RGB565_MAGENTA
};

// 文字画布尺寸
#define TEXT_CANVAS_WIDTH 200
#define TEXT_CANVAS_HEIGHT 40

// 文字画布水平居中,放在色卡下方
#define TEXT_CANVAS_X \
((LCD_WIDTH - TEXT_CANVAS_WIDTH) / 2)
#define TEXT_CANVAS_Y \
(SWATCH_Y + SWATCH_HEIGHT + 40)

// 1. 总线层:QSPI,1 根时钟 + 4 根数据线
Arduino_DataBus *bus = new Arduino_ESP32QSPI(
LCD_CS, LCD_SCLK, LCD_SDIO0, LCD_SDIO1, LCD_SDIO2, LCD_SDIO3);

// 2. 驱动 IC 层:CO5300,480×480
Arduino_CO5300 *gfx = new Arduino_CO5300(
bus, LCD_RESET, 0, LCD_WIDTH, LCD_HEIGHT,
0, 0, 0, 0);

// 3. 在 RAM 中建立局部画布,输出位置为屏幕中央
Arduino_Canvas *textCanvas = new Arduino_Canvas(
TEXT_CANVAS_WIDTH, TEXT_CANVAS_HEIGHT, gfx,
TEXT_CANVAS_X, TEXT_CANVAS_Y);

void setup() {
if (!gfx->begin()) {
while (true) {
delay(1000);
}
}

// 设置显示方向(MADCTL 寄存器):库初始化后的默认方向未必与面板一致,
// 这里沿用官方示例的 0xA0
bus->writeC8D8(0x36, 0xA0);

gfx->fillScreen(RGB565_BLACK);

// AMOLED 没有背光引脚,亮度通过命令设置(0~255)
gfx->setBrightness(255);

// 直接在屏幕上绘制一排色块(大面积纯色填充无需经过画布)
for (int i = 0; i < SWATCH_COUNT; i++) {
gfx->fillRect(
SWATCH_X + i * SWATCH_WIDTH,
SWATCH_Y,
SWATCH_WIDTH,
SWATCH_HEIGHT,
SWATCH_COLORS[i]);
}

// 分配画布内存,但不重复初始化屏幕
if (!textCanvas->begin(GFX_SKIP_OUTPUT_BEGIN)) {
while (true) {
delay(1000);
}
}

// 使用 U8g2 的 Helvetica Regular 18 英文字体绘制到 RAM
textCanvas->fillScreen(RGB565_BLACK);
textCanvas->setFont(u8g2_font_helvR18_tr);
textCanvas->setTextSize(1);
textCanvas->setTextColor(RGB565_WHITE);
textCanvas->setCursor(13, 30);
textCanvas->print("Hello, AMOLED!");

// 将整个 200×40 像素画布一次性发送到屏幕
textCanvas->flush();
}

void loop() {
}

与 LCD 示例对比,显示驱动有三处不同:Arduino_ESP32SPI 换成 Arduino_ESP32QSPI、驱动类换成 Arduino_CO5300、背光的 digitalWrite(BL, HIGH) 换成 setBrightness() 命令。U8g2 在此只提供字体数据,屏幕仍由 Arduino_GFX 驱动。u8g2_font_helvR18_tr 包含常用英文字符,按 18 像素原始尺寸绘制,因此 setTextSize() 保持为 1

示例里的色块是用 fillRect 直接画到屏幕的——大面积纯色填充没有问题;但文字没有直接画,而是先在 RAM 中建立一块 200×40 像素的 Arduino_Canvas,绘制完成后用 flush() 整块刷新。在 AMOLED 上绘制细小内容时,推荐都采用这种“先合成、再整块发送”的方式:CO5300 的直接绘制路径对单像素、1 像素粗的元素支持不好,而 U8g2 字体在原始尺寸下正含有这类细笔画,直接输出容易缺笔画甚至整行不显示(参见 Arduino_GFX issue #780)。先画到画布再刷新即可绕开这一限制,同时也减少了对屏幕的零散写入。

画布的开销很小:RGB565 每像素 2 字节,200×40 的画布仅占用 200 × 40 × 2 = 16000 字节 RAM,无需 PSRAM。需要显示更多内容时,按实际尺寸调整画布大小和输出坐标即可;其他画布格式见 Arduino_GFX 的 Canvas Class 页面。

CO5300 要求窗口起点与宽高均为偶数

上文“细笔画缺笔”的根源在于 CO5300 的硬件限制:每次绘制的窗口起点和宽高必须为偶数。Arduino_GFX 不会自动取整(直接将坐标写入 CASET/PASET 寄存器),奇数坐标或奇数宽高会被面板截断、错位甚至不显示。因此,在 CO5300 上需注意以下两点:

  • 矩形填充可直接绘制fillRect 等 API 将起点和宽高取偶数即可(如上文色块示例)。
  • 圆、斜线、文字、带透明色的位图等:这些 API 会逐像素或按奇数窗口写入,直接绘制通常会出现问题。可靠的做法是先在 RAM 画布或帧缓冲中用完整绘图 API 合成,再整块 flush() 上屏。

此外,setRotation 对 CO5300 仅切换 MADCTL 的镜像位,不执行 90°/270° 的行列交换;需要横竖屏切换时,在应用层或 GUI 框架中处理。

4.4 运行官方示例

官方仓库的 Arduino 示例位于 examples/arduino 目录,建议按顺序运行:

  • 01_HelloWorld:基础绘图与随机文本显示,用于验证屏幕基础显示能力。
  • 02_GFX_AsciiTable:按行列打印 ASCII 字符表,用于验证坐标和显示方向。
  • 03_LVGL_AXP2101_ADC_Data:LVGL 结合 AXP2101 显示电源信息,验证 PMU(Power Management Unit,电源管理单元)与 LVGL 基础显示。
  • 04_LVGL_QMI8658_ui:LVGL 图表实时显示 IMU 加速度曲线。
  • 05_LVGL_Widgets:较完整的 LVGL Demo,包含触摸控制器读取(触摸原理详见 触摸控制章节)与根据 IMU 姿态自动旋转屏幕,是黑底 UI 设计的直观参考。

4.5 常见问题

现象常见原因处理
编译通过但屏幕不亮引脚定义或驱动类错误核对 LCD_RESETLCD_CS 等引脚是否与 产品页引脚表 一致
内存分配失败 / LVGL 启动崩溃未开启 PSRAM工程选项开启 OPI PSRAM
画面亮度为 0未调用亮度命令初始化后调用 setBrightness()
setTextSize(1) 时 U8g2 字体没有显示CO5300 直接绘制路径不能正常显示单像素及 1 像素粗元素 (Arduino_GFX issue #780先绘制到局部 Arduino_Canvas,再调用 flush()
圆、斜线等非矩形图形边缘被填平或不显示绘制窗口起点或宽高为奇数,不满足 CO5300 偶数对齐起点与宽高取偶数;非矩形图形在 RAM 画布中合成后整块 flush()
长期显示固定界面出现残像烧屏深色 UI、避免静止高亮元素、空闲降亮度或熄屏