在智能硬件项目中,显示屏通常承担"设备表达能力"的角色。传感器采集到的数据、系统运行状态、任务执行进度、异常提示信息,如果只能停留在串口日志里,调试和展示都会受到限制。OLED 屏幕的价值就在于把程序内部状态直接显示到硬件界面上,让代码运行过程变成可观察、可交互、可演示的结果。
本实验使用 CanMV K210 开发板通过 I2C 总线驱动 SSD1306 OLED 屏幕,实现 128x64 分辨率下的综合显示效果。程序完成了 OLED 初始化、文本显示、居中文本、进度条绘制、状态面板展示、模拟传感器数据显示和滚动提示信息等功能。代码中的温度、湿度和光照数据为程序模拟生成,不是来自真实传感器,但整体结构已经接近智能硬件状态面板的基础形态。
| 学习目标 | 说明 |
|---|---|
| 理解 OLED 显示链路 | 掌握程序、显存缓冲区、I2C 总线和 OLED 屏幕之间的关系 |
| 掌握 SSD1306 基础驱动 | 学会通过初始化命令、显示数据和刷新函数控制 OLED |
| 理解 FrameBuffer 绘图 | 使用 framebuf 绘制文本、线条、矩形、填充区域和进度条 |
| 完成状态面板显示 | 将模拟温度、湿度、光照和系统状态显示到 OLED 屏幕 |
| 建立界面封装思路 | 把底层驱动、基础绘图和页面显示分层组织,便于后续扩展 |
文章目录
理论基础
OLED 显示屏和 LED、蜂鸣器这类简单输出模块不同。LED 通常只需要一个高低电平就能表现亮灭状态,而 OLED 需要接收显示命令和显存数据,才能在指定位置显示文字或图形。本实验使用的 SSD1306 OLED 屏幕分辨率为 128x64,程序需要先在内存中准备一块显示缓冲区,再通过 I2C 总线把缓冲区内容发送到屏幕。
I2C 通信可以理解成 CanMV K210 和 OLED 之间的一条数据通道。SCL 负责通信节拍,SDA 负责传输数据内容,设备地址用于区分总线上的不同设备。当前代码中 OLED 地址设置为 0x3C,SCL 使用 IO6,SDA 使用 IO7。程序通过这个地址向 SSD1306 发送初始化命令、地址范围命令和显示数据。
OLED 的显示过程并不是"调用 text() 后立刻出现在屏幕上"。代码中的文字、线条和矩形会先被绘制到 buffer 缓冲区里,framebuf 负责在这块内存区域中完成图形操作,最后由 show() 把整块缓冲区发送到 OLED。这个过程类似先在内存中准备一张画面,再统一刷新到屏幕。
Python 页面函数
启动页 / 信息页 / 状态面板
FrameBuffer 绘图
文字 / 线条 / 矩形 / 进度条
显存缓冲区
128 x 64 单色画面
SSD1306 驱动类
命令发送 / 数据发送 / 刷新
I2C 总线
SCL: IO6 / SDA: IO7
SSD1306 OLED
地址 0x3C
屏幕显示结果
状态面板 / 滚动提示 / 进度条
VCC / GND
屏幕供电与公共地
show()
统一刷新画面
这张流程图展示的是 OLED 显示的完整链路。页面函数只负责组织显示内容,FrameBuffer 负责在内存中绘图,SSD1306 驱动类负责发送命令和数据,I2C 总线负责把内容传到屏幕,OLED 最终显示出文字、进度条和状态信息。理解这条链路后,后续扩展传感器数据显示、AI 识别结果显示、菜单界面和设备调试面板都会更清晰。
需要注意的是,当前代码使用 framebuf.text() 绘制文本,适合显示英文、数字和简单符号。如果需要显示中文,一般需要额外的字库或点阵绘制函数。本文示例中的界面内容主要采用英文文本,方便直接在 SSD1306 的基础文本绘制能力上运行。
硬件设施
本实验围绕 CanMV K210 开发板和 SSD1306 OLED 显示屏展开。代码没有使用摄像头、按键、蜂鸣器、电机或真实传感器,因此这些模块不作为本节讲解重点。软件侧主要使用 machine.I2C 和 framebuf,前者负责 I2C 通信,后者负责在内存缓冲区中绘制文字、线条、矩形和进度条。
接线关系可以先通过下面这张图建立整体印象。OLED 只需要 SCL、SDA、VCC、GND 四类连接,其中 SCL 接 CanMV IO6,SDA 接 CanMV IO7,供电端按照模块标识连接 3.3V 或 5V,GND 接开发板 GND。

| 硬件 / 软件 | 作用 | 说明 |
|---|---|---|
| CanMV K210 开发板 | 实验运行平台 | 负责执行 MicroPython 程序,并通过 I2C 总线向 OLED 发送显示指令和显存数据 |
| SSD1306 OLED 屏幕 | 显示输出模块 | 用于显示启动页、设备信息、状态面板、模拟数据和滚动提示文本 |
| SCL 时钟线 | I2C 时钟信号 | OLED 的 SCL 接到 CanMV IO6,用于同步 I2C 通信时序 |
| SDA 数据线 | I2C 数据信号 | OLED 的 SDA 接到 CanMV IO7,用于传输控制命令和显示数据 |
| VCC / GND | 电源连接 | OLED VCC 接 3.3V 或 5V,GND 接开发板 GND,具体电压以模块标注为准 |
machine.I2C |
I2C 通信模块 | 用于创建 I2C 主机对象,并向 OLED 地址 0x3C 写入命令和数据 |
framebuf |
图形缓冲模块 | 用于在内存中绘制文字、线条、矩形、填充区域等图形元素 |
time |
延时控制模块 | 通过 time.sleep_ms() 控制启动动画、刷新节奏和滚动速度 |
实验中使用的 OLED 模块如下。SSD1306 OLED 的优势是体积小、显示清晰、功耗较低,适合用作智能硬件项目中的状态面板。它不能像 LCD 那样直接显示复杂彩色图像,但非常适合显示文字、数值、进度条、图标和简单界面。

接线关系来自代码注释和常量定义。代码中 OLED_SCL = 6、OLED_SDA = 7,并在创建 I2C 对象时传入 scl=scl、sda=sda,因此可以确定 OLED 的 I2C 时钟线和数据线分别连接到 CanMV 的 IO6 与 IO7。OLED 地址设置为 0x3C,这是 SSD1306 模块中常见的 I2C 地址。
| OLED 引脚 | CanMV 引脚 | 代码变量 | 对应功能 | 说明 |
|---|---|---|---|---|
| SCL | IO6 | OLED_SCL = 6 |
I2C 时钟线 | 负责 I2C 通信时钟 |
| SDA | IO7 | OLED_SDA = 7 |
I2C 数据线 | 负责传输命令和显示数据 |
| VCC | 3.3V / 5V | 无 | OLED 电源 | 根据 OLED 模块标注选择供电电压 |
| GND | GND | 无 | 公共地 | OLED 与开发板需要共地,保证通信电平基准一致 |
| I2C 地址 | 0x3C |
OLED_ADDR |
OLED 设备地址 | 程序通过该地址向 SSD1306 发送命令和显存数据 |
完成接线后的整体状态如下。运行程序后,OLED 会先显示启动界面和加载进度条,然后显示接线信息和屏幕参数,之后进入模拟状态面板循环刷新。若屏幕完全不亮,排查重点应优先放在供电、SCL/SDA 是否接反、I2C 地址是否正确,而不是直接修改显示页面函数。

| 实验现象 | 正常表现 | 异常提示 |
|---|---|---|
| 程序启动 | OLED 显示 CanMV K210、OLED SSD1306 和启动进度条 | 完全不亮时检查 VCC、GND、SCL、SDA |
| 基础信息页 | 屏幕显示 SCL、SDA、地址和尺寸 | 地址错误时屏幕可能无响应 |
| 状态面板页 | 显示温度、湿度、光照进度条、状态和计数编号 | 数据为模拟生成,不代表真实传感器数据 |
| 任务完成页 | 显示 Sensor Ready、Display Ready、System Running | 文本显示错位时检查屏幕尺寸和坐标 |
| 滚动提示页 | 提示文本从右向左滚动 | 刷新过快或过慢可调整 time.sleep_ms(80) |
| 手动停止 | 程序退出时清屏并关闭 OLED | 如果强制断开电源,屏幕状态取决于最后一次刷新内容 |
软件代码
本实验代码围绕 SSD1306 OLED 的 I2C 驱动和图形显示展开。程序先定义屏幕尺寸、设备地址和接线引脚,再封装 SSD1306OLED 类,把初始化、命令发送、数据发送、清屏、文字、线条、矩形、进度条和标题栏等操作集中管理。主流程通过启动页、设备信息页、智能状态面板、任务完成页和滚动日志页,连续展示 OLED 在智能硬件项目中的常见显示方式。
| 软件环境 | 作用 | 检查重点 |
|---|---|---|
| CanMV IDE | 编辑、运行和调试 K210 程序 | 能识别开发板串口,并能运行基础 print() 测试 |
| CanMV / MaixPy 固件 | 提供 machine.I2C 和 framebuf 等模块 |
固件环境需要支持当前 I2C 与 FrameBuffer 写法 |
| USB 串口驱动 | 让电脑识别开发板串口 | 串口工具或 IDE 中能看到对应端口 |
machine.I2C |
I2C 通信模块 | 能通过 IO6、IO7 与 OLED 通信 |
framebuf |
图形缓冲模块 | 能绘制文本、线条、矩形和填充图形 |
time |
延时模块 | 控制启动动画、面板刷新和滚动文本速度 |
python
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
CanMV K210 OLED SSD1306 综合显示 Demo
接线说明:
OLED SCL -> CanMV IO6
OLED SDA -> CanMV IO7
OLED VCC -> 3.3V / 5V
OLED GND -> GND
功能说明:
1. OLED 初始化
2. 文字显示
3. 居中文本
4. 进度条
5. 状态面板
6. 模拟传感器数据显示
7. 滚动提示信息
"""
import time
from machine import I2C
import framebuf
OLED_WIDTH = 128
OLED_HEIGHT = 64
OLED_ADDR = 0x3C
OLED_SCL = 6
OLED_SDA = 7
class SSD1306OLED:
def __init__(self, scl=OLED_SCL, sda=OLED_SDA, width=OLED_WIDTH, height=OLED_HEIGHT, addr=OLED_ADDR):
self.width = width
self.height = height
self.pages = height // 8
self.addr = addr
self.i2c = I2C(
I2C.I2C0,
mode=I2C.MODE_MASTER,
freq=400000,
scl=scl,
sda=sda,
addr_size=7
)
self.buffer = bytearray(self.width * self.pages)
self.fb = framebuf.FrameBuffer(
self.buffer,
self.width,
self.height,
framebuf.MONO_VLSB
)
self.init_display()
self.clear()
def write_cmd(self, cmd):
self.i2c.writeto_mem(self.addr, 0x00, bytes([cmd]), mem_size=8)
def write_data(self, data):
self.i2c.writeto_mem(self.addr, 0x40, data, mem_size=8)
def init_display(self):
init_cmds = [
0xAE, # 关闭显示
0x20, 0x00, # 水平寻址模式
0x40, # 设置显示起始行
0xA1, # 左右镜像
0xC8, # 上下扫描方向
0x81, 0xFF, # 对比度
0xA6, # 正常显示
0xA8, 0x3F, # 1/64 duty
0xD3, 0x00, # 显示偏移
0xD5, 0x80, # 显示时钟
0xD9, 0xF1, # 预充电周期
0xDA, 0x12, # COM 引脚配置
0xDB, 0x40, # VCOMH
0x8D, 0x14, # 电荷泵
0xA4, # 使用显存内容
0xAF # 开启显示
]
for cmd in init_cmds:
self.write_cmd(cmd)
def show(self):
self.write_cmd(0x21)
self.write_cmd(0)
self.write_cmd(self.width - 1)
self.write_cmd(0x22)
self.write_cmd(0)
self.write_cmd(self.pages - 1)
for i in range(0, len(self.buffer), 16):
self.write_data(self.buffer[i:i + 16])
def clear(self, refresh=True):
self.fb.fill(0)
if refresh:
self.show()
def power_on(self):
self.write_cmd(0x8D)
self.write_cmd(0x14)
self.write_cmd(0xAF)
def power_off(self):
self.write_cmd(0x8D)
self.write_cmd(0x10)
self.write_cmd(0xAE)
def contrast(self, value):
if value < 0:
value = 0
if value > 255:
value = 255
self.write_cmd(0x81)
self.write_cmd(value)
def text(self, x, y, content, color=1):
self.fb.text(str(content), x, y, color)
def center_text(self, y, content, color=1):
content = str(content)
x = (self.width - len(content) * 8) // 2
if x < 0:
x = 0
self.text(x, y, content, color)
def line(self, x1, y1, x2, y2, color=1):
self.fb.line(x1, y1, x2, y2, color)
def rect(self, x, y, w, h, color=1):
self.fb.rect(x, y, w, h, color)
def fill_rect(self, x, y, w, h, color=1):
self.fb.fill_rect(x, y, w, h, color)
def progress_bar(self, x, y, w, h, value, max_value=100):
if value < 0:
value = 0
if value > max_value:
value = max_value
fill_width = int((w - 2) * value / max_value)
self.rect(x, y, w, h, 1)
self.fill_rect(x + 1, y + 1, fill_width, h - 2, 1)
def draw_header(self, title):
self.fill_rect(0, 0, self.width, 12, 1)
self.fb.text(str(title), 4, 2, 0)
self.line(0, 13, self.width - 1, 13, 1)
def show_boot_screen(oled):
oled.clear(False)
oled.center_text(4, "CanMV K210")
oled.center_text(20, "OLED SSD1306")
oled.center_text(36, "System Boot")
oled.rect(0, 54, 128, 10, 1)
for value in range(0, 101, 5):
oled.fill_rect(2, 56, int(124 * value / 100), 6, 1)
oled.show()
time.sleep_ms(50)
time.sleep_ms(500)
def show_basic_info(oled):
oled.clear(False)
oled.draw_header("OLED DEMO")
oled.text(0, 18, "SCL : IO6")
oled.text(0, 28, "SDA : IO7")
oled.text(0, 38, "ADDR: 0x3C")
oled.text(0, 48, "SIZE: 128x64")
oled.show()
time.sleep_ms(2000)
def show_dashboard(oled, count, temp, humi, light, state):
oled.clear(False)
oled.draw_header("SMART PANEL")
oled.text(0, 18, "TEMP:")
oled.text(48, 18, str(temp) + " C")
oled.text(0, 28, "HUMI:")
oled.text(48, 28, str(humi) + " %")
oled.text(0, 38, "LIGHT:")
oled.progress_bar(48, 38, 76, 8, light, 100)
oled.text(0, 52, "STATE:")
oled.text(56, 52, state)
oled.text(96, 18, "#" + str(count))
oled.show()
def show_message_page(oled, title, line1, line2, line3):
oled.clear(False)
oled.draw_header(title)
oled.text(0, 20, line1)
oled.text(0, 32, line2)
oled.text(0, 44, line3)
oled.show()
def show_scroll_text(oled, message):
text_width = len(message) * 8
for x in range(128, -text_width, -4):
oled.clear(False)
oled.draw_header("LOG MESSAGE")
oled.text(x, 28, message)
oled.show()
time.sleep_ms(80)
def loop(oled):
show_boot_screen(oled)
show_basic_info(oled)
while True:
for count in range(1, 31):
temp = 24 + count % 8
humi = 45 + count % 20
light = (count * 7) % 100
if light < 30:
state = "LOW"
elif light < 75:
state = "NORMAL"
else:
state = "HIGH"
show_dashboard(oled, count, temp, humi, light, state)
time.sleep_ms(500)
show_message_page(
oled,
"TASK DONE",
"Sensor Ready",
"Display Ready",
"System Running"
)
time.sleep_ms(2000)
show_scroll_text(oled, "CanMV OLED display demo is running...")
if __name__ == "__main__":
oled = None
try:
oled = SSD1306OLED()
loop(oled)
except KeyboardInterrupt:
print("OLED demo stopped")
except Exception as e:
print("OLED demo error:", e)
finally:
if oled:
oled.clear()
oled.power_off()
这段代码可以分成硬件参数配置、OLED 驱动类、页面显示函数和主循环调度几个部分。OLED_WIDTH、OLED_HEIGHT、OLED_ADDR、OLED_SCL、OLED_SDA 是整块屏幕的基础参数。128x64 表示 OLED 的显示区域为 128 列、64 行,0x3C 是程序访问 OLED 的 I2C 设备地址,IO6 和 IO7 分别承担 SCL 与 SDA。把这些参数独立写成常量,后续更换屏幕尺寸、I2C 地址或接线引脚时,不需要在代码中到处查找。
SSD1306OLED 类负责底层显示控制。构造函数中创建 I2C 主机对象,通信频率设置为 400kHz;buffer 保存屏幕显存数据;FrameBuffer 把这块字节数组包装成可以绘图的画布。init_display() 负责发送 SSD1306 初始化命令,show() 负责把缓冲区内容发送到 OLED。这样一来,页面函数只需要调用 text()、rect()、fill_rect()、progress_bar() 等方法,不需要直接处理底层 I2C 数据。
| 函数名 | 功能 | 对应现象 |
|---|---|---|
__init__() |
初始化 OLED 驱动对象 | 创建 I2C、显存缓冲区和 FrameBuffer,并启动 OLED |
write_cmd() |
发送 SSD1306 控制命令 | 设置显示模式、地址范围、对比度、电源状态等 |
write_data() |
发送 OLED 显示数据 | 把缓冲区中的图形内容写入屏幕 |
init_display() |
初始化屏幕参数 | OLED 从上电状态进入正常显示状态 |
show() |
刷新屏幕内容 | 内存中的文字和图形真正显示到 OLED 上 |
clear() |
清空画面 | OLED 显示区域被清空,可选择是否立即刷新 |
power_on() |
打开屏幕显示 | OLED 恢复显示输出 |
power_off() |
关闭屏幕显示 | OLED 熄屏,适合停止程序或低功耗场景 |
contrast() |
设置屏幕对比度 | 改变 OLED 显示亮度强弱 |
text() |
绘制文本 | 在指定坐标显示字符串 |
center_text() |
居中显示文本 | 启动画面中的标题居中显示 |
progress_bar() |
绘制进度条 | 根据数值显示光照进度或启动进度 |
draw_header() |
绘制顶部标题栏 | 形成统一的页面头部样式 |
show_boot_screen() |
显示启动页 | 屏幕显示系统启动文字和加载进度条 |
show_basic_info() |
显示基础信息页 | 显示 SCL、SDA、地址和屏幕尺寸 |
show_dashboard() |
显示状态面板 | 显示温度、湿度、光照进度、状态和计数编号 |
show_message_page() |
显示消息页面 | 显示任务完成和系统运行提示 |
show_scroll_text() |
显示滚动文本 | 提示信息从右向左滚动 |
loop() |
主程序循环 | 持续刷新启动页、信息页、状态面板和滚动日志 |
主流程中,程序先创建 SSD1306OLED() 对象,再显示启动界面和基础信息页。进入循环后,程序用 count 模拟运行次数,用简单表达式生成温度、湿度和光照数据。light 数值会被判断成 LOW、NORMAL、HIGH 三种状态,并在面板中通过进度条和状态文本显示出来。这里的数据虽然是模拟生成,但页面结构可以直接迁移到真实传感器项目中,只需要把 temp、humi、light 的来源替换成传感器读取结果即可。
滚动文本函数 show_scroll_text() 展示了 OLED 显示中常见的信息提示方式。它根据字符串长度计算文本宽度,再让 x 坐标从屏幕右侧逐步移动到屏幕左侧之外。每移动一次就清屏、绘制标题、绘制文本、刷新屏幕,最终形成横向滚动效果。这个写法适合显示较长日志、联网状态、任务提示或设备运行说明。
扩展应用
OLED 显示实验的排查重点集中在供电、I2C 接线、设备地址、初始化命令和刷新逻辑。屏幕不亮时,不应只检查显示函数,而要先确认通信链路是否建立;画面错位或不刷新时,需要检查分辨率、缓冲区、地址范围和 show() 调用位置。
| 问题现象 | 可能原因 | 处理思路 |
|---|---|---|
| OLED 完全不亮 | 供电异常、SCL/SDA 接反、I2C 地址不匹配 | 检查 VCC 和 GND,确认 SCL 接 IO6、SDA 接 IO7,常见地址为 0x3C,部分模块可能为 0x3D |
| 程序运行但屏幕无内容 | 初始化命令未正确发送、show() 未执行 |
确认 init_display() 被调用,绘制内容后需要调用 oled.show() 刷新 |
| 屏幕显示乱码或错位 | 分辨率参数、扫描方向或寻址方式不匹配 | 核对 OLED 是否为 128x64 SSD1306,必要时调整初始化命令中的镜像和扫描方向 |
| 文字超出屏幕 | 内容长度超过 128 像素宽度 | 英文字符按约 8 像素宽度估算,较长文本适合使用滚动显示 |
| 中文无法正常显示 | framebuf.text() 默认更适合英文、数字和简单符号 |
需要显示中文时,应额外准备中文字库或点阵绘制函数 |
| 进度条显示异常 | 数值超出范围或宽度计算不合理 | progress_bar() 已限制数值范围,调用时建议让 value 与 max_value 保持一致 |
| 刷新时画面闪烁 | 清屏和全屏刷新频率较高 | 降低刷新频率,或只在数据变化明显时刷新页面 |
| 停止程序后屏幕未熄灭 | 程序没有进入 finally 收尾逻辑,或被强制断电 |
正常通过 IDE 停止运行时,代码会调用 clear() 和 power_off() |
| 模拟数据显示不是真实环境数据 | 当前代码没有接入真实传感器 | 将 temp、humi、light 替换为真实传感器读取值,显示函数可以继续复用 |
OLED 综合显示实验的价值不只在于让屏幕亮起来,更重要的是建立"设备状态可视化"的编程思维。很多硬件项目都需要一个轻量显示界面,用来展示运行状态、连接信息、采集数据、任务进度和异常提醒。当前代码已经把底层驱动、基础绘图和页面展示拆开,后续接入真实传感器、网络通信或 AI 识别结果时,只需要修改数据来源,显示层可以继续复用。
| 应用场景 | 实现思路 | 可扩展能力 |
|---|---|---|
| 设备启动界面 | 使用 show_boot_screen() 显示系统名称和加载进度条 |
可扩展固件版本、设备编号、初始化状态检测 |
| 智能环境面板 | 将模拟温湿度和光照替换为真实传感器数据 | 后续可接入 DHT、光敏、电压采集等模块 |
| 任务状态显示 | 使用 show_message_page() 展示任务完成、系统运行、模块就绪等信息 |
可扩展为模型推理结果、采集任务状态、自动化流程状态 |
| 调试信息输出 | 将串口日志中的关键状态同步显示到 OLED | 可减少对电脑串口工具的依赖,方便离线调试 |
| 进度类界面 | 使用 progress_bar() 显示进度、亮度、占比或信号强度 |
可扩展为联网进度、文件处理进度、采样完成度 |
| 滚动通知栏 | 使用 show_scroll_text() 显示较长提示信息 |
可用于显示设备日志、联网状态、报警信息或教学提示 |
| AI 硬件交互界面 | 把识别结果、置信度、类别名称显示到 OLED | 摄像头识别、目标检测属于后续课程可扩展方向 |
从工程结构看,这份代码已经具备模块化项目的雏形。SSD1306OLED 类负责屏幕驱动,页面函数负责界面布局,主循环负责数据更新和页面调度。新增页面时,不需要重新编写 I2C 通信和初始化代码,只需要继续组合 text()、rect()、fill_rect()、progress_bar() 等基础方法。新增真实传感器时,也不需要重写显示逻辑,只要把模拟数据替换成真实采样值,就能形成完整的"采集---处理---显示"流程。
总结
本实验通过 CanMV K210 开发板驱动 SSD1306 OLED 屏幕,完成了初始化、文本绘制、居中显示、进度条、状态面板、模拟数据显示和滚动文本等综合效果。代码涉及 I2C 通信、OLED 地址、显存缓冲区、FrameBuffer 绘图、类封装、页面函数、循环刷新和状态判断等核心能力。屏幕上看到的每一行文字和每一个进度条,本质上都是程序先在内存中组织画面,再通过 I2C 总线发送到 OLED 的结果。
这类实验非常适合作为智能硬件显示课程的基础案例。串口日志只能面向开发调试,OLED 面板则能让设备具备独立展示能力。后续课程可以在这套结构上继续扩展真实温湿度采集、光照检测、按键切换页面、蜂鸣器报警、摄像头 AI 识别结果显示、网络状态提示和 Web 远程控制等功能。理解了 I2C 通信、缓冲区绘图和页面封装之后,很多智能硬件项目都可以从"能运行"进一步升级到"能展示、能反馈、能交互"。