跳到主要内容

Arduino 开发

本章节包含以下部分,请按需阅读:

Arduino 入门教程

初次接触 Arduino ESP32 开发,想要快速上手?我们为您准备了一套通用的 入门教程

请注意:该教程使用 ESP32-S3-Zero 作为教学示例,所有硬件代码均基于其引脚布局。在动手实践前,建议您对照手中的开发板引脚图,确认引脚配置无误。

配置开发环境

1. 安装和配置 Arduino IDE

请参考 安装和配置 Arduino IDE 教程 下载安装 Arduino IDE 并添加 ESP32 支持。

2. 安装库

要运行示例,需要安装对应的库。示例代码使用 GFX Library for Arduino 库驱动 ST7789V2 显示屏 ,并使用 Arduino_DriveBus 库驱动 CST816T 触摸芯片。

可从 此链接 下载 ESP32-S3-Touch-LCD-1.83 开发板的示例程序包。包内的 Arduino\libraries 目录已包含本教程所需的全部库文件。

库或文件名称说明版本安装方式
Arduino_DriveBusCST816 触摸芯片驱动库v1.0.1“离线”安装
GFX Library for ArduinoST7789 显示驱动图形库v1.4.9可“在线”或“离线”安装
SensorLibPCF85063、QMI8658 传感器驱动库v0.1.6可“在线”或“离线”安装
lvglLVGL 图形库v8.4.0“在线”安装后需复制 demos 文件夹至 src,建议使用“离线”安装
Mylibrary开发板引脚宏定义——“离线”安装
lv_conf.hLVGL 配置文件——“离线”安装
版本兼容性说明

LVGL 及其驱动库的版本之间存在较强的依赖关系。例如,为 LVGL v8 编写的驱动可能不兼容 LVGL v9。为确保示例能够稳定复现,推荐使用上表列出的特定版本。混合使用不同版本的库可能导致编译失败或运行时异常。

安装步骤:

  1. 解压已下载的 示例程序包

  2. 将其 Arduino\libraries 目录下的所有文件夹(Arduino_DriveBus、GFX_Library_for_Arduino 等)复制到 Arduino 的库文件夹中。

    信息

    Arduino 库文件夹的路径通常是:c:\Users\<用户名>\Documents\Arduino\libraries

    也可以在 Arduino IDE 中通过 文件 > 首选项,查看“项目文件夹位置”来定位。库文件夹就是此路径下的 libraries 文件夹。

  3. 其他安装方式请参考:Arduino 库管理教程

3. 其他提示

  1. ESP32-S3-Touch-LCD-1.83 支持在 Arduino IDE 中直接选择型号。

    ESP32-S3-Touch-LCD-1.83_Model_selection
  2. ESP32-S3-Touch-LCD-1.83 使用 ESP32-S3 原生 USB 接口,而非 UART 转 USB。对于串口通信:

    • printf() 函数可直接使用;

    • 若要使用 Serial.println() 函数,需要额外配置:在 IDE 工具菜单中启用"USB CDC On Boot"选项,或在代码中声明 HWCDC 对象处理 USB 串口通信。

      备注

      如下图所示,在 Arduino IDE 的"工具"选项中设置"USB CDC On Boot"

      ESP32-S3-Touch-LCD-1.83_parameter_setting

示例程序

Arduino 示例程序位于 示例程序包Arduino/examples 目录中。

示例程序基础例程说明依赖库
01_HelloWorld展示基本的图形库功能,也可以用于测试显示屏的基础性能以及随机文本显示效果GFX_Library_for_Arduino
02_Drawing_board展示基本的图形库功能,也可以用于测试显示屏的基础性能以及随机文本显示效果GFX_Library_for_Arduino,Arduino DriveBus
03_GFX_AsciiTable根据屏幕尺寸,在显示屏上按行列打印 ASCII 字符GFX_Library_for_Arduino
04_GFX_ESPWiFiAnalyzer在 ST7789 显示器上绘制 WiFi 频段信号强度GFX_Library_for_Arduino
05_GFX_Clock一个简单的 ST7789 时钟示例,通过简单的标记指针和时间管理实现时钟GFX_Library_for_Arduino
06_GFX_PCF85063_simpleTime显示当前时间SensorLib,GFX_Library_for_Arduino
07_LVGL_PCF85063_simpleTime在 LVGL 下使用 PCF85063 RTC 模块在 ST7789 显示屏上显示当前时间LVGL,SensorLib
08_LVGL_QMI8658_ui使用 LVGL 进行图形显示,与 QMI8658 IMU 通信以获取加速度计和陀螺仪数据LVGL,SensorLib
09_LVGL_ArduinoLVGL 演示LVGL,Arduino DriveBus

01_HelloWorld

本示例演示了如何使用 Arduino GFX 库和 Arduino DriveBus 库控制 ST7789 显示屏,通过动态变化的文本展示了基本的图形库功能。该代码也可以用于测试显示屏的基础性能以及随机文本显示效果。

硬件连接

  • 使用 USB 线把板子接入电脑
    ESP32-S3-Touch-LCD-1.83_connect

代码解释

  • 显示初始化 :

    if (!gfx->begin()) {
    USBSerial.println("gfx->begin() failed!");
    }
  • 清屏并显示文本 :

     gfx->fillScreen(BLACK);
    gfx->setCursor(10, 10);
    gfx->setTextColor(RED);
    gfx->println("Hello World!");
  • 动图显示 :

     gfx->setCursor(random(gfx->width()), random(gfx->height()));
    gfx->setTextColor(random(0xffff), random(0xffff));
    gfx->setTextSize(random(6), random(6), random(2));
    gfx->println("Hello World!");

运行效果

  • 本示例演示了如何使用 Arduino GFX 库和 Arduino DriveBus 库控制 ST7789 显示屏,通过动态变化的文本展示了基本的图形库功能。该代码也可以用于测试显示屏的基础性能以及随机文本显示效果。
    ESP32-S3-Touch-LCD-1.83_demo_1

02_Drawing_board

本示例演示了如何使用 ESP32 通过 I2C 接口控制 CST816 触摸控制器和 TCA9554 GPIO 扩展器,同时使用 Arduino GFX 库来驱动 ST7789 显示屏。

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • 显示屏初始化与亮度渐变动画 :

     gfx->begin();
    gfx->fillScreen(WHITE);
    for (int i = 0; i <= 255; i++) {
    gfx->Display_Brightness(i);
    gfx->setCursor(30, 150);
    gfx->setTextColor(BLUE);
    gfx->setTextSize(4);
    gfx->println("Loading board");
    delay(3);
    }

运行效果

ESP32-S3-Touch-LCD-1.83_demo_2

03_GFX_AsciiTable

本示例展示如何用 ST7789 显示屏结合 Arduino GFX 库,创建一个 ASCII 表格的显示效果。具体而言,代码初始化显示屏后,按照预定义的颜色和位置在屏幕上显示 ASCII 字符索引。首先,“Arduino_GFX AsciiTable example” 字样通过 USBSerial 打印到串口监视器,然后在屏幕上按照两种颜色(绿色和蓝色)固定显示索引,接着用白色在黑色背景上显示完整 ASCII 字符表。

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • 初始化显示屏 :

    if (!gfx->begin()) {
    USBSerial.println("gfx->begin() failed!");
    }
  • 计算行列并标注编号 :

    这里根据显示屏的尺寸计算出可以显示的列数和行数。然后分别使用两个循环,设置不同的文本颜色,在显示屏上打印出行和列的编号,以便在后续绘制 ASCII 字符时可以方便地确定字符的位置。

    int numCols = LCD_WIDTH / 8;
    int numRows = LCD_HEIGHT / 10;

    // 标注行编号
    gfx->setTextColor(GREEN);
    for (int x = 0; x < numRows; x++) {
    gfx->setCursor(10 + x * 8, 2);
    gfx->print(x, 16);
    }

    // 标注列编号
    gfx->setTextColor(BLUE);
    for (int y = 0; y < numCols; y++) {
    gfx->setCursor(2, 12 + y * 10);
    gfx->print(y, 16);
    }
  • 绘制 ASCII 字符表 :

    char c = 0;
    for (int y = 0; y < numRows; y++) {
    for (int x = 0; x < numCols; x++) {
    gfx->drawChar(10 + x * 8, 12 + y * 10, c++, WHITE, BLACK);
    }
    }

运行效果

ESP32-S3-Touch-LCD-1.83_demo_3

04_GFX_ESPWiFiAnalyzer

本示例演示了在 ST7789 显示器上绘制 WiFi 频段信号强度示例,实现 WiFi 分析器的功能。

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • setup()

    • 初始化串口通信;
    • 设置 WiFi 为站点模式并断开连接;
    • 初始化显示屏,获取屏幕尺寸并计算各种绘图参数;
    • 设置屏幕背景为黑色,绘制标题栏。
  • loop()

    • 扫描 WiFi 网络并获取网络信息,包括信道、RSSI、BSSID 和 SSID;
    • 统计每个信道上的网络数量、噪声水平和峰值信号强度;
    • 清除旧的图形并根据扫描结果绘制新的图形,包括信号强度椭圆和网络信息文本;
    • 打印扫描到的网络数量和噪声最小的信道;
    • 绘制图形基线和信道编号;
    • 根据条件进入低功耗模式。

运行效果

ESP32-S3-Touch-LCD-1.83_demo_04

05_GFX_Clock

本示例演示了一个简单的 ST7789 时钟示例,通过简单的标记指针和时间管理实现时钟示例。

硬件连接

  • 使用 USB 线把板子接入电脑
05_GFX_Clock.ino
#include <Arduino.h>
#include "Arduino_GFX_Library.h"
#include "pin_config.h"
#include <Wire.h>
#include "HWCDC.h"

HWCDC USBSerial;
// SensorPCF85063 rtc;

Arduino_DataBus *bus = new Arduino_ESP32SPI(LCD_DC, LCD_CS, LCD_SCK, LCD_MOSI);

Arduino_GFX *gfx = new Arduino_ST7789(bus, LCD_RST /* RST */,
0 /* rotation */, true /* IPS */, LCD_WIDTH, LCD_HEIGHT, 0, 20, 0, 0);

#define BACKGROUND BLACK
#define MARK_COLOR WHITE
#define SUBMARK_COLOR DARKGREY // LIGHTGREY
#define HOUR_COLOR WHITE
#define MINUTE_COLOR BLUE // LIGHTGREY
#define SECOND_COLOR RED

#define SIXTIETH 0.016666667
#define TWELFTH 0.08333333
#define SIXTIETH_RADIAN 0.10471976
#define TWELFTH_RADIAN 0.52359878
#define RIGHT_ANGLE_RADIAN 1.5707963

static uint8_t conv2d(const char *p)
{
uint8_t v = 0;
return (10 * (*p - '0')) + (*++p - '0');
}

static int16_t w, h, center;
static int16_t hHandLen, mHandLen, sHandLen, markLen;
static float sdeg, mdeg, hdeg;
static int16_t osx = 0, osy = 0, omx = 0, omy = 0, ohx = 0, ohy = 0; // Saved H, M, S x & y coords
static int16_t nsx, nsy, nmx, nmy, nhx, nhy; // H, M, S x & y coords
static int16_t xMin, yMin, xMax, yMax; // redraw range
static int16_t hh, mm, ss;
static unsigned long targetTime; // next action time

static int16_t *cached_points;
static uint16_t cached_points_idx = 0;
static int16_t *last_cached_point;

void setup(void)
{
USBSerial.begin(115200);
USBSerial.println("Arduino_GFX Clock example");

// Init Display
if (!gfx->begin())
{
USBSerial.println("gfx->begin() failed!");
}
gfx->fillScreen(BACKGROUND);

pinMode(LCD_BL, OUTPUT);
digitalWrite(LCD_BL, HIGH);

// init LCD constant
w = gfx->width();
h = gfx->height();
if (w < h)
{
center = w / 2;
}
else
{
center = h / 2;
}
hHandLen = center * 3 / 8;
mHandLen = center * 2 / 3;
sHandLen = center * 5 / 6;
markLen = sHandLen / 6;
cached_points = (int16_t *)malloc((hHandLen + 1 + mHandLen + 1 + sHandLen + 1) * 2 * 2);

// Draw 60 clock marks
draw_round_clock_mark(
// draw_square_clock_mark(
center - markLen, center,
center - (markLen * 2 / 3), center,
center - (markLen / 2), center);

hh = conv2d(__TIME__);
mm = conv2d(__TIME__ + 3);
ss = conv2d(__TIME__ + 6);

targetTime = ((millis() / 1000) + 1) * 1000;
}

void loop()
{
unsigned long cur_millis = millis();
if (cur_millis >= targetTime)
{
targetTime += 1000;
ss++; // Advance second
if (ss == 60)
{
ss = 0;
mm++; // Advance minute
if (mm > 59)
{
mm = 0;
hh++; // Advance hour
if (hh > 23)
{
hh = 0;
}
}
}
}

// Pre-compute hand degrees, x & y coords for a fast screen update
sdeg = SIXTIETH_RADIAN * ((0.001 * (cur_millis % 1000)) + ss); // 0-59 (includes millis)
nsx = cos(sdeg - RIGHT_ANGLE_RADIAN) * sHandLen + center;
nsy = sin(sdeg - RIGHT_ANGLE_RADIAN) * sHandLen + center;
if ((nsx != osx) || (nsy != osy))
{
mdeg = (SIXTIETH * sdeg) + (SIXTIETH_RADIAN * mm); // 0-59 (includes seconds)
hdeg = (TWELFTH * mdeg) + (TWELFTH_RADIAN * hh); // 0-11 (includes minutes)
mdeg -= RIGHT_ANGLE_RADIAN;
hdeg -= RIGHT_ANGLE_RADIAN;
nmx = cos(mdeg) * mHandLen + center;
nmy = sin(mdeg) * mHandLen + center;
nhx = cos(hdeg) * hHandLen + center;
nhy = sin(hdeg) * hHandLen + center;

// redraw hands
redraw_hands_cached_draw_and_erase();

ohx = nhx;
ohy = nhy;
omx = nmx;
omy = nmy;
osx = nsx;
osy = nsy;

delay(1);
}
}

void draw_round_clock_mark(int16_t innerR1, int16_t outerR1, int16_t innerR2, int16_t outerR2, int16_t innerR3, int16_t outerR3)
{
float x, y;
int16_t x0, x1, y0, y1, innerR, outerR;
uint16_t c;

for (uint8_t i = 0; i < 60; i++)
{
if ((i % 15) == 0)
{
innerR = innerR1;
outerR = outerR1;
c = MARK_COLOR;
}
else if ((i % 5) == 0)
{
innerR = innerR2;
outerR = outerR2;
c = MARK_COLOR;
}
else
{
innerR = innerR3;
outerR = outerR3;
c = SUBMARK_COLOR;
}

mdeg = (SIXTIETH_RADIAN * i) - RIGHT_ANGLE_RADIAN;
x = cos(mdeg);
y = sin(mdeg);
x0 = x * outerR + center;
y0 = y * outerR + center;
x1 = x * innerR + center;
y1 = y * innerR + center;

gfx->drawLine(x0, y0, x1, y1, c);
}
}

void draw_square_clock_mark(int16_t innerR1, int16_t outerR1, int16_t innerR2, int16_t outerR2, int16_t innerR3, int16_t outerR3)
{
float x, y;
int16_t x0, x1, y0, y1, innerR, outerR;
uint16_t c;

for (uint8_t i = 0; i < 60; i++)
{
if ((i % 15) == 0)
{
innerR = innerR1;
outerR = outerR1;
c = MARK_COLOR;
}
else if ((i % 5) == 0)
{
innerR = innerR2;
outerR = outerR2;
c = MARK_COLOR;
}
else
{
innerR = innerR3;
outerR = outerR3;
c = SUBMARK_COLOR;
}

if ((i >= 53) || (i < 8))
{
x = tan(SIXTIETH_RADIAN * i);
x0 = center + (x * outerR);
y0 = center + (1 - outerR);
x1 = center + (x * innerR);
y1 = center + (1 - innerR);
}
else if (i < 23)
{
y = tan((SIXTIETH_RADIAN * i) - RIGHT_ANGLE_RADIAN);
x0 = center + (outerR);
y0 = center + (y * outerR);
x1 = center + (innerR);
y1 = center + (y * innerR);
}
else if (i < 38)
{
x = tan(SIXTIETH_RADIAN * i);
x0 = center - (x * outerR);
y0 = center + (outerR);
x1 = center - (x * innerR);
y1 = center + (innerR);
}
else if (i < 53)
{
y = tan((SIXTIETH_RADIAN * i) - RIGHT_ANGLE_RADIAN);
x0 = center + (1 - outerR);
y0 = center - (y * outerR);
x1 = center + (1 - innerR);
y1 = center - (y * innerR);
}
gfx->drawLine(x0, y0, x1, y1, c);
}
}

void redraw_hands_cached_draw_and_erase()
{
gfx->startWrite();
draw_and_erase_cached_line(center, center, nsx, nsy, SECOND_COLOR, cached_points, sHandLen + 1, false, false);
draw_and_erase_cached_line(center, center, nhx, nhy, HOUR_COLOR, cached_points + ((sHandLen + 1) * 2), hHandLen + 1, true, false);
draw_and_erase_cached_line(center, center, nmx, nmy, MINUTE_COLOR, cached_points + ((sHandLen + 1 + hHandLen + 1) * 2), mHandLen + 1, true, true);
gfx->endWrite();
}

void draw_and_erase_cached_line(int16_t x0, int16_t y0, int16_t x1, int16_t y1, int16_t color, int16_t *cache, int16_t cache_len, bool cross_check_second, bool cross_check_hour)
{
#if defined(ESP8266)
yield();
#endif
bool steep = _diff(y1, y0) > _diff(x1, x0);
if (steep)
{
_swap_int16_t(x0, y0);
_swap_int16_t(x1, y1);
}

int16_t dx, dy;
dx = _diff(x1, x0);
dy = _diff(y1, y0);

int16_t err = dx / 2;
int8_t xstep = (x0 < x1) ? 1 : -1;
int8_t ystep = (y0 < y1) ? 1 : -1;
x1 += xstep;
int16_t x, y, ox, oy;
for (uint16_t i = 0; i <= dx; i++)
{
if (steep)
{
x = y0;
y = x0;
}
else
{
x = x0;
y = y0;
}
ox = *(cache + (i * 2));
oy = *(cache + (i * 2) + 1);
if ((x == ox) && (y == oy))
{
if (cross_check_second || cross_check_hour)
{
write_cache_pixel(x, y, color, cross_check_second, cross_check_hour);
}
}
else
{
write_cache_pixel(x, y, color, cross_check_second, cross_check_hour);
if ((ox > 0) || (oy > 0))
{
write_cache_pixel(ox, oy, BACKGROUND, cross_check_second, cross_check_hour);
}
*(cache + (i * 2)) = x;
*(cache + (i * 2) + 1) = y;
}
if (err < dy)
{
y0 += ystep;
err += dx;
}
err -= dy;
x0 += xstep;
}
for (uint16_t i = dx + 1; i < cache_len; i++)
{
ox = *(cache + (i * 2));
oy = *(cache + (i * 2) + 1);
if ((ox > 0) || (oy > 0))
{
write_cache_pixel(ox, oy, BACKGROUND, cross_check_second, cross_check_hour);
}
*(cache + (i * 2)) = 0;
*(cache + (i * 2) + 1) = 0;
}
}

void write_cache_pixel(int16_t x, int16_t y, int16_t color, bool cross_check_second, bool cross_check_hour)
{
int16_t *cache = cached_points;
if (cross_check_second)
{
for (uint16_t i = 0; i <= sHandLen; i++)
{
if ((x == *(cache++)) && (y == *(cache)))
{
return;
}
cache++;
}
}
if (cross_check_hour)
{
cache = cached_points + ((sHandLen + 1) * 2);
for (uint16_t i = 0; i <= hHandLen; i++)
{
if ((x == *(cache++)) && (y == *(cache)))
{
return;
}
cache++;
}
}
gfx->writePixel(x, y, color);
}

代码解释

  • 时针、分针、秒针的绘制 :

    void redraw_hands_cached_draw_and_erase() {
    gfx->startWrite();
    draw_and_erase_cached_line(center, center, nsx, nsy, SECOND_COLOR, cached_points, sHandLen + 1, false, false);
    draw_and_erase_cached_line(center, center, nhx, nhy, HOUR_COLOR, cached_points + ((sHandLen + 1) * 2), hHandLen + 1, true, false);
    draw_and_erase_cached_line(center, center, nmx, nmy, MINUTE_COLOR, cached_points + ((sHandLen + 1 + hHandLen + 1) * 2), mHandLen + 1, true, true);
    gfx->endWrite();
    }

运行效果

ESP32-S3-Touch-LCD-1.83_demo_05

06_GFX_PCF85063_simpleTime

本示例演示了使用 PCF85063 RTC 模块在 ST7789 显示屏上显示当前时间,每秒检索时间并仅在时间发生变化时更新显示。

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • loop()
    • 首先获取当前时间,如果当前时间与上一次显示的时间不同,则进行以下操作:
    • 清除上一次显示时间的区域,通过填充一个矩形实现,以便更新时间显示时不会出现重叠。
    • 设置文本颜色为黑色,并设置文本大小为 3。
    • 调用 getCenteredX 函数计算当前时间字符串在屏幕上居中显示的 X 坐标。
    • 设置光标位置并打印当前时间字符串,实现时间的更新显示。
    • 将当前时间字符串复制到 previousTimeString,以便下次判断时间是否变化。

运行效果

ESP32-S3-Touch-LCD-1.83_demo_06

07_LVGL_PCF85063_simpleTime

本示例演示了在 LVGL 下使用 PCF85063 RTC 模块在 ST7789 显示屏上显示当前时间,每秒检索时间并仅在时间发生变化时更新显示,对比时间刷新效果更佳

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • setup()

    • 初始化串口通信,以 115200 的波特率准备可能的串口调试;
    • 尝试连接 PCF85063 实时时钟芯片,如果连接失败则进入死循环;
    • 设置实时时钟的初始时间为 2025 年 10 月 23 日 15 时 23 分 49 秒;
    • 初始化显示屏,设置屏幕亮度;
    • 初始化 LVGL,并注册日志输出函数(如果启用了日志功能;
    • 配置 LVGL 的显示驱动和绘图缓冲区,以及初始化输入设备驱动(虽然是一个虚拟的指针输入设备驱动)
    • 创建定时器用于定期触发 LVGL 的时钟更新
    • 创建一个标签并设置初始文本为 “Initializing...”
  • loop()

  • 调用 lv_timer_handler 让 LVGL 处理图形界面的任务;

  • 每秒钟检查一次时间是否更新,如果是,则获取实时时钟的当前时间,通过串口输出,并将时间格式化为特定格式后更新标签的文本内容,同时设置标签的字体为 lv_font_montserrat_40

运行效果

ESP32-S3-Touch-LCD-1.83_demo_07

08_LVGL_QMI8658_ui

本示例演示了使用 LVGL 进行图形显示,与 QMI8658 IMU 通信以获取加速度计和陀螺仪数据

硬件连接

  • 使用 USB 线把板子接入电脑

代码解释

  • my_disp_flush()

    • 这个函数是 LVGL 显示驱动的刷新函数。它负责将 LVGL 的绘图缓冲区内容刷新到显示屏上;
    • 根据不同的颜色格式设置,调用 gfx 对象的相应函数来绘制位图到特定的区域;
    • 最后通知 LVGL 显示刷新已完成。
  • loop()

    • 调用 lv_timer_handler 让 LVGL 处理图形界面的任务;
    • 检查 qmi(QMI8658 传感器对象)是否有新数据准备好。如果有,尝试获取加速度数据和陀螺仪数据,并通过串口输出;
    • 同时,将加速度数据更新到 LVGL 的图表上,以便实时显示加速度在三个轴上的变化情况;
    • 通过 delay(20) 增加数据轮询的频率,以确保及时获取传感器数据并更新显示。

运行效果

ESP32-S3-Touch-LCD-1.83_demo_08

09_LVGL_Arduino

本示例演示了 LVGL Widgets 示例,动态状态下帧率可达 20~30 帧

硬件连接

  • 使用 USB 线把板子接入电脑

运行效果

  • 本示例演示了 LVGL Widgets 示例,动态状态下帧率可达 20~30 帧

    示例 11 图示

  • 使用 LVGL 框架开发时可以依据 lvgl 官方文档提供的组件说明来调用组件 LVGL8.3 Documents

  • 下面是 Arduino IDE 的 LVGL 实际组件调研案例

    示例 11 图示 2