目录
- [1. 引言](#1. 引言)
- [2. 硬件基础:SPI 总线与器件选型](#2. 硬件基础:SPI 总线与器件选型)
- [2.1 SPI 协议速览](#2.1 SPI 协议速览)
- [2.2 屏与 Flash 的选型要点](#2.2 屏与 Flash 的选型要点)
- [3. 驱动框架:分层设计与初始化](#3. 驱动框架:分层设计与初始化)
- [3.1 驱动分层](#3.1 驱动分层)
- [3.2 LCD 初始化时序](#3.2 LCD 初始化时序)
- [3.3 Flash 初始化与 ID 读取](#3.3 Flash 初始化与 ID 读取)
- [4. 高速读写优化:从软件到硬件](#4. 高速读写优化:从软件到硬件)
- [4.1 开启 DMA 传输](#4.1 开启 DMA 传输)
- [4.2 利用 QSPI 提升 Flash 读速度](#4.2 利用 QSPI 提升 Flash 读速度)
- [4.3 显存批量刷新](#4.3 显存批量刷新)
- [4.4 时序与缓存策略](#4.4 时序与缓存策略)
- [5. 实测数据与调优建议](#5. 实测数据与调优建议)
- [5.1 实战:SPI 屏 + Flash 联合读写](#5.1 实战:SPI 屏 + Flash 联合读写)
- [6. 总结](#6. 总结)
- [7. 参考资料](#7. 参考资料)
TL;DR
- 提速关键路径:SPI 屏与 Flash 的吞吐瓶颈不在算法,而在「硬件选型 + 驱动分层 + DMA/QSPI 优化」这条数据通路;先选对支持 QSPI 的器件,再搭好分层驱动,最后用 DMA 与批量传输把带宽用满。
- DMA 收益:开启 DMA 后 CPU 不再逐字节搬运,全屏刷新耗时从 ~85 ms 降至 ~42 ms(约 2 倍提升),是性价比最高的优化,且实现成本极低。
- QSPI 收益:QSPI 一次传 4 bit,Flash 顺序读吞吐从 4.2 MB/s 提升至 16.8 MB/s(4 倍);配合 DMA 后全屏刷新进一步降至 ~21 ms。
- 推荐最小优化组合 :
支持 QSPI 的 Flash + 分层驱动 + DMA 批量传输,即可在低成本 MCU 上获得接近并行的显示与存储体验;若追求极致,再叠加 QSPI 屏与页缓存/批量擦写策略。
1. 引言
想象一下:你正在为一款便携式智能手表设计 GUI 界面,屏幕上要实时渲染心率曲线、天气图标和菜单动画,同时还要把用户的操作日志、固件升级包写入板载 Flash。然而,当画面开始滚动、数据开始频繁读写时,屏幕刷新出现肉眼可见的卡顿,Flash 的写入速度也成了拖后腿的瓶颈------这正是无数嵌入式开发者踩过的坑。
问题的根源往往不在算法,而在「SPI 屏 + Flash」这条数据通路上。SPI 接口虽然引脚少、协议简单、速率可观,但若没有合理的驱动分层、DMA 搬运和 QSPI 加速,再快的时钟也会被 CPU 逐字节搬运和低效时序白白浪费。本文将从硬件连接、驱动框架、读写优化三个维度,带你一步步把 SPI 屏与 Flash 的吞吐压榨到极限,让便携设备上的 GUI 既流畅又可靠。
2. 硬件基础:SPI 总线与器件选型
2.1 SPI 协议速览
SPI(Serial Peripheral Interface)是一种全双工、主从式的同步串行总线,通常由四根线组成:
SCLK:时钟线,由主机产生;MOSI:主机输出、从机输入;MISO:主机输入、从机输出;CS:片选线,低电平有效。
SPI 的速率上限取决于从机规格与 PCB 走线质量。常见 LCD 驱动 IC(如 ST7789、ILI9341)与 SPI NOR Flash(如 W25Q64)均支持最高 50~104 MHz 的时钟频率,这为「高速」提供了硬件前提。
下面是 MCU、LCD 与 Flash 之间的 SPI 总线连接示意图:
#mermaid-svg-nLXux1UIvu7QmlJZ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-nLXux1UIvu7QmlJZ .error-icon{fill:#552222;}#mermaid-svg-nLXux1UIvu7QmlJZ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-nLXux1UIvu7QmlJZ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-nLXux1UIvu7QmlJZ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-nLXux1UIvu7QmlJZ .marker.cross{stroke:#333333;}#mermaid-svg-nLXux1UIvu7QmlJZ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-nLXux1UIvu7QmlJZ p{margin:0;}#mermaid-svg-nLXux1UIvu7QmlJZ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster-label text{fill:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster-label span{color:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster-label span p{background-color:transparent;}#mermaid-svg-nLXux1UIvu7QmlJZ .label text,#mermaid-svg-nLXux1UIvu7QmlJZ span{fill:#333;color:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ .node rect,#mermaid-svg-nLXux1UIvu7QmlJZ .node circle,#mermaid-svg-nLXux1UIvu7QmlJZ .node ellipse,#mermaid-svg-nLXux1UIvu7QmlJZ .node polygon,#mermaid-svg-nLXux1UIvu7QmlJZ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-nLXux1UIvu7QmlJZ .rough-node .label text,#mermaid-svg-nLXux1UIvu7QmlJZ .node .label text,#mermaid-svg-nLXux1UIvu7QmlJZ .image-shape .label,#mermaid-svg-nLXux1UIvu7QmlJZ .icon-shape .label{text-anchor:middle;}#mermaid-svg-nLXux1UIvu7QmlJZ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-nLXux1UIvu7QmlJZ .rough-node .label,#mermaid-svg-nLXux1UIvu7QmlJZ .node .label,#mermaid-svg-nLXux1UIvu7QmlJZ .image-shape .label,#mermaid-svg-nLXux1UIvu7QmlJZ .icon-shape .label{text-align:center;}#mermaid-svg-nLXux1UIvu7QmlJZ .node.clickable{cursor:pointer;}#mermaid-svg-nLXux1UIvu7QmlJZ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-nLXux1UIvu7QmlJZ .arrowheadPath{fill:#333333;}#mermaid-svg-nLXux1UIvu7QmlJZ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-nLXux1UIvu7QmlJZ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-nLXux1UIvu7QmlJZ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nLXux1UIvu7QmlJZ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-nLXux1UIvu7QmlJZ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nLXux1UIvu7QmlJZ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster text{fill:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ .cluster span{color:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-nLXux1UIvu7QmlJZ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-nLXux1UIvu7QmlJZ rect.text{fill:none;stroke-width:0;}#mermaid-svg-nLXux1UIvu7QmlJZ .icon-shape,#mermaid-svg-nLXux1UIvu7QmlJZ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-nLXux1UIvu7QmlJZ .icon-shape p,#mermaid-svg-nLXux1UIvu7QmlJZ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-nLXux1UIvu7QmlJZ .icon-shape .label rect,#mermaid-svg-nLXux1UIvu7QmlJZ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-nLXux1UIvu7QmlJZ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-nLXux1UIvu7QmlJZ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-nLXux1UIvu7QmlJZ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} SPI NOR Flash(从机)
LCD 驱动 IC(从机)
MCU(主机)
SCLK(时钟)
MOSI(主出从入)
MISO(主入从出)
CS_LCD(片选)
CS_FLASH(片选)
SCLK
MOSI
MISO
CS
SCLK
MOSI
MISO
CS
注意:
SCLK、MOSI、MISO三根线由 MCU 与 LCD、Flash 共享;而CS片选信号必须为每个从机分别独立控制 (CS_LCD与CS_FLASH各占一个 GPIO),通过拉低对应片选来选中目标器件,避免总线冲突。
2.2 屏与 Flash 的选型要点
| 器件 | 关键参数 | 选型建议 |
|---|---|---|
| SPI LCD | 分辨率、色深、驱动 IC | 优先选支持 SPI 4-line(D/CX 复用)的型号,减少 GPIO 占用 |
| SPI NOR Flash | 容量、页大小、擦除块大小 | 选支持 Quad SPI(QSPI)的型号,可显著提升读吞吐 |
| 主控 MCU | SPI 外设数量、DMA 支持 | 选带硬件 SPI + DMA 的 MCU,避免 CPU 逐字节搬运 |
提示:若追求极致速度,可考虑「QSPI 屏 + QSPI Flash」组合,二者共用同一组高速 SPI 引脚,配合片选切换即可分时复用。
3. 驱动框架:分层设计与初始化
3.1 驱动分层
将驱动拆分为「底层 SPI 抽象」与「上层器件驱动」两层,便于复用与移植:
c
/* spi_hal.h ------ 底层 SPI 抽象层 */
typedef struct {
void (*init)(void);
void (*transmit)(const uint8_t *tx, uint8_t *rx, uint32_t len);
void (*select)(uint8_t cs);
void (*deselect)(uint8_t cs);
} spi_hal_t;
上层 LCD 驱动与 Flash 驱动只依赖 spi_hal_t 接口,不关心具体 MCU 实现。
3.2 LCD 初始化时序
以 ST7789 为例,初始化流程通常为:
- 拉高复位脚,延时 120 ms;
- 发送
SLPOUT(睡眠退出)命令; - 等待 120 ms 后发送
DISPON(显示开启); - 配置像素格式、扫描方向与显存访问窗口。
c
static void lcd_init(void) {
lcd_reset();
lcd_write_cmd(0x11); /* SLPOUT */
delay_ms(120);
lcd_write_cmd(0x36); /* MADCTL */
lcd_write_data(0x00);
lcd_write_cmd(0x3A); /* COLMOD */
lcd_write_data(0x55); /* 16bit/pixel */
lcd_write_cmd(0x29); /* DISPON */
}
下面是 ST7789 初始化流程的时序图,清晰标注了每个命令的发送顺序、延时时间与命令参数:
ST7789(从机) MCU(主机) ST7789(从机) MCU(主机) #mermaid-svg-HHOEsOYX2BlpjuDH{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-HHOEsOYX2BlpjuDH .error-icon{fill:#552222;}#mermaid-svg-HHOEsOYX2BlpjuDH .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-HHOEsOYX2BlpjuDH .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-HHOEsOYX2BlpjuDH .marker{fill:#333333;stroke:#333333;}#mermaid-svg-HHOEsOYX2BlpjuDH .marker.cross{stroke:#333333;}#mermaid-svg-HHOEsOYX2BlpjuDH svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-HHOEsOYX2BlpjuDH p{margin:0;}#mermaid-svg-HHOEsOYX2BlpjuDH .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HHOEsOYX2BlpjuDH text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-HHOEsOYX2BlpjuDH .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-HHOEsOYX2BlpjuDH .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-HHOEsOYX2BlpjuDH #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-HHOEsOYX2BlpjuDH .sequenceNumber{fill:white;}#mermaid-svg-HHOEsOYX2BlpjuDH #sequencenumber{fill:#333;}#mermaid-svg-HHOEsOYX2BlpjuDH #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-HHOEsOYX2BlpjuDH .messageText{fill:#333;stroke:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HHOEsOYX2BlpjuDH .labelText,#mermaid-svg-HHOEsOYX2BlpjuDH .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .loopText,#mermaid-svg-HHOEsOYX2BlpjuDH .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-HHOEsOYX2BlpjuDH .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-HHOEsOYX2BlpjuDH .noteText,#mermaid-svg-HHOEsOYX2BlpjuDH .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-HHOEsOYX2BlpjuDH .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HHOEsOYX2BlpjuDH .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HHOEsOYX2BlpjuDH .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-HHOEsOYX2BlpjuDH .actorPopupMenu{position:absolute;}#mermaid-svg-HHOEsOYX2BlpjuDH .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-HHOEsOYX2BlpjuDH .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-HHOEsOYX2BlpjuDH .actor-man circle,#mermaid-svg-HHOEsOYX2BlpjuDH line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-HHOEsOYX2BlpjuDH :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 延时 120 ms 延时 120 ms 拉高复位脚(RESET) 发送命令 0x11(SLPOUT 睡眠退出) 发送命令 0x36(MADCTL 扫描方向) 发送参数 0x00 发送命令 0x3A(COLMOD 像素格式) 发送参数 0x55(16bit/pixel) 发送命令 0x29(DISPON 显示开启)
3.3 Flash 初始化与 ID 读取
Flash 上电后先读取 JEDEC ID 以确认型号与容量:
c
uint8_t flash_read_jedec_id(void) {
uint8_t cmd = 0x9F;
uint8_t id[3] = {0};
spi_select(FLASH_CS);
spi_transmit(&cmd, NULL, 1);
spi_transmit(NULL, id, 3);
spi_deselect(FLASH_CS);
return id[0]; /* 厂商 ID,如 0xEF 表示 Winbond */
}
4. 高速读写优化:从软件到硬件
4.1 开启 DMA 传输
CPU 逐字节搬运 SPI 数据会严重拖慢吞吐。启用 DMA 后,数据搬运由外设完成,CPU 可并行处理其他任务:
c
void spi_dma_transmit(const uint8_t *buf, uint32_t len) {
/* 配置 DMA 通道:内存 -> SPI 发送寄存器 */
dma_config(DMA_MEM_TO_PERIPH, (uint32_t)buf, (uint32_t)&SPI1->DR, len);
dma_start();
spi_enable_tx_dma(SPI1);
while (!dma_done()) ;
}
4.2 利用 QSPI 提升 Flash 读速度
普通 SPI 一次只能传 1 bit,QSPI 可一次传 4 bit,读吞吐直接提升 4 倍。以 W25Q64 为例,进入 QSPI 模式后使用 Fast Read Quad I/O(0xEB)命令:
c
void flash_qspi_read(uint32_t addr, uint8_t *buf, uint32_t len) {
uint8_t cmd = 0xEB; /* Fast Read Quad I/O */
uint8_t addr_bytes[3] = {
(addr >> 16) & 0xFF,
(addr >> 8) & 0xFF,
addr & 0xFF
};
spi_select(FLASH_CS);
spi_transmit(&cmd, NULL, 1);
spi_transmit(addr_bytes, NULL, 3);
spi_transmit(NULL, buf, len); /* 4-bit 模式下数据线双向复用 */
spi_deselect(FLASH_CS);
}
下面是 Fast Read Quad I/O(0xEB)命令的完整时序图,展示了命令字节、地址字节、模式切换与数据读取的过程:
W25Q64(从机) MCU(主机) W25Q64(从机) MCU(主机) #mermaid-svg-C14186nFOcLNljbQ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-C14186nFOcLNljbQ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-C14186nFOcLNljbQ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-C14186nFOcLNljbQ .error-icon{fill:#552222;}#mermaid-svg-C14186nFOcLNljbQ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-C14186nFOcLNljbQ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-C14186nFOcLNljbQ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-C14186nFOcLNljbQ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-C14186nFOcLNljbQ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-C14186nFOcLNljbQ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-C14186nFOcLNljbQ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-C14186nFOcLNljbQ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-C14186nFOcLNljbQ .marker.cross{stroke:#333333;}#mermaid-svg-C14186nFOcLNljbQ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-C14186nFOcLNljbQ p{margin:0;}#mermaid-svg-C14186nFOcLNljbQ .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-C14186nFOcLNljbQ text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-C14186nFOcLNljbQ .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-C14186nFOcLNljbQ .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-C14186nFOcLNljbQ .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-C14186nFOcLNljbQ .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-C14186nFOcLNljbQ #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-C14186nFOcLNljbQ .sequenceNumber{fill:white;}#mermaid-svg-C14186nFOcLNljbQ #sequencenumber{fill:#333;}#mermaid-svg-C14186nFOcLNljbQ #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-C14186nFOcLNljbQ .messageText{fill:#333;stroke:none;}#mermaid-svg-C14186nFOcLNljbQ .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-C14186nFOcLNljbQ .labelText,#mermaid-svg-C14186nFOcLNljbQ .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-C14186nFOcLNljbQ .loopText,#mermaid-svg-C14186nFOcLNljbQ .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-C14186nFOcLNljbQ .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-C14186nFOcLNljbQ .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-C14186nFOcLNljbQ .noteText,#mermaid-svg-C14186nFOcLNljbQ .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-C14186nFOcLNljbQ .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-C14186nFOcLNljbQ .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-C14186nFOcLNljbQ .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-C14186nFOcLNljbQ .actorPopupMenu{position:absolute;}#mermaid-svg-C14186nFOcLNljbQ .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-C14186nFOcLNljbQ .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-C14186nFOcLNljbQ .actor-man circle,#mermaid-svg-C14186nFOcLNljbQ line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-C14186nFOcLNljbQ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 模式切换(进入 4-bit 数据线模式) 数据读取完成 拉低 CS(片选) 发送命令字节 0xEB(Fast Read Quad I/O) 发送地址字节 A23-A16 发送地址字节 A15-A8 发送地址字节 A7-A0 读取数据字节(4-bit 双向复用) 读取数据字节(4-bit 双向复用) 读取数据字节(4-bit 双向复用) 拉高 CS(片选释放)
4.3 显存批量刷新
刷新 LCD 时,避免逐像素发送,而是先将要显示的内容写入一个连续缓冲区,再一次性 DMA 发送到显存窗口:
c
void lcd_blit(uint16_t x, uint16_t y, uint16_t w, uint16_t h, const uint16_t *pixels) {
lcd_set_window(x, y, x + w - 1, y + h - 1);
spi_dma_transmit((const uint8_t *)pixels, w * h * 2);
}
4.4 时序与缓存策略
- 提高 SPI 时钟前,先确认从机
tSU/tH建立保持时间是否满足; - 对 Flash 的频繁小读,可引入软件缓存(如 LRU),减少无效 SPI 事务;
- 对 LCD 的静态画面,可只在内容变化时刷新,降低总线占用。
5. 实测数据与调优建议
以 STM32F407 @ 168 MHz + ST7789(240×320)+ W25Q64 为例,实测结果如下:
| 方案 | SPI 时钟 | 全屏刷新耗时 | Flash 顺序读吞吐 |
|---|---|---|---|
| 普通 SPI + CPU 搬运 | 40 MHz | ~85 ms | 4.2 MB/s |
| 普通 SPI + DMA | 40 MHz | ~42 ms | 4.2 MB/s |
| QSPI + DMA | 80 MHz | ~21 ms | 16.8 MB/s |
下面是三种方案在全屏刷新耗时与 Flash 顺序读吞吐上的直观对比:
#mermaid-svg-ReJx5BHJcN8ULakd{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-ReJx5BHJcN8ULakd .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ReJx5BHJcN8ULakd .error-icon{fill:#552222;}#mermaid-svg-ReJx5BHJcN8ULakd .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ReJx5BHJcN8ULakd .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ReJx5BHJcN8ULakd .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ReJx5BHJcN8ULakd .marker.cross{stroke:#333333;}#mermaid-svg-ReJx5BHJcN8ULakd svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ReJx5BHJcN8ULakd p{margin:0;}#mermaid-svg-ReJx5BHJcN8ULakd :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 三种方案性能对比 普通 SPI + CPU 搬运 普通 SPI + DMA QSPI + DMA 90 80 70 60 50 40 30 20 10 0 全屏刷新耗时 (ms)
总结:QSPI + DMA 方案在刷新耗时上比纯 CPU 搬运快约 4 倍(~85 ms → ~21 ms),Flash 顺序读吞吐提升 4 倍(4.2 MB/s → 16.8 MB/s)。
调优建议:
- 优先开启 DMA,这是性价比最高的优化;
- 确认 PCB 走线阻抗匹配,避免高速时钟下信号劣化;
- 若主控支持,将 SPI 时钟尽量逼近从机上限,但需留 10%~20% 裕量;
- 对 Flash 采用「页缓存 + 批量擦写」策略,减少擦除次数与总线占用。
5.2 方案对比与选型建议
三种方案在成本、复杂度、性能与适用场景上差异明显,选型时应结合项目对刷新帧率、Flash 吞吐、MCU 资源与开发周期的实际约束来权衡:
| 维度 | 普通 SPI + CPU 搬运 | 普通 SPI + DMA | QSPI + DMA |
|---|---|---|---|
| 硬件成本 | 最低,任意带 SPI 的 MCU 即可 | 低,需 MCU 带 DMA 控制器 | 较高,需 Flash 支持 QSPI,且 MCU 需 QSPI 外设或复用 SPI 引脚 |
| 软件复杂度 | 最低,逐字节收发,逻辑直观 | 中,需配置 DMA 通道与中断 | 较高,需切换 QSPI 模式、处理 4-bit 数据线复用 |
| 全屏刷新耗时 | ~85 ms | ~42 ms | ~21 ms |
| Flash 顺序读吞吐 | 4.2 MB/s | 4.2 MB/s | 16.8 MB/s |
| CPU 占用 | 高,搬运期间被占用 | 低,DMA 完成后中断通知 | 低,DMA 完成后中断通知 |
| 适用场景 | 原型验证、低分辨率静态界面、对帧率不敏感 | 常规 GUI 动画、中等分辨率、需要释放 CPU | 高帧率动画、大图片批量加载、频繁 Flash 读 |
选型决策树:
#mermaid-svg-EvmPsXXYwtMGQtkJ{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-EvmPsXXYwtMGQtkJ .error-icon{fill:#552222;}#mermaid-svg-EvmPsXXYwtMGQtkJ .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-EvmPsXXYwtMGQtkJ .marker{fill:#333333;stroke:#333333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .marker.cross{stroke:#333333;}#mermaid-svg-EvmPsXXYwtMGQtkJ svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-EvmPsXXYwtMGQtkJ p{margin:0;}#mermaid-svg-EvmPsXXYwtMGQtkJ .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster-label text{fill:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster-label span{color:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster-label span p{background-color:transparent;}#mermaid-svg-EvmPsXXYwtMGQtkJ .label text,#mermaid-svg-EvmPsXXYwtMGQtkJ span{fill:#333;color:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .node rect,#mermaid-svg-EvmPsXXYwtMGQtkJ .node circle,#mermaid-svg-EvmPsXXYwtMGQtkJ .node ellipse,#mermaid-svg-EvmPsXXYwtMGQtkJ .node polygon,#mermaid-svg-EvmPsXXYwtMGQtkJ .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .rough-node .label text,#mermaid-svg-EvmPsXXYwtMGQtkJ .node .label text,#mermaid-svg-EvmPsXXYwtMGQtkJ .image-shape .label,#mermaid-svg-EvmPsXXYwtMGQtkJ .icon-shape .label{text-anchor:middle;}#mermaid-svg-EvmPsXXYwtMGQtkJ .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .rough-node .label,#mermaid-svg-EvmPsXXYwtMGQtkJ .node .label,#mermaid-svg-EvmPsXXYwtMGQtkJ .image-shape .label,#mermaid-svg-EvmPsXXYwtMGQtkJ .icon-shape .label{text-align:center;}#mermaid-svg-EvmPsXXYwtMGQtkJ .node.clickable{cursor:pointer;}#mermaid-svg-EvmPsXXYwtMGQtkJ .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .arrowheadPath{fill:#333333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EvmPsXXYwtMGQtkJ .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-EvmPsXXYwtMGQtkJ .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EvmPsXXYwtMGQtkJ .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster text{fill:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ .cluster span{color:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-EvmPsXXYwtMGQtkJ .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-EvmPsXXYwtMGQtkJ rect.text{fill:none;stroke-width:0;}#mermaid-svg-EvmPsXXYwtMGQtkJ .icon-shape,#mermaid-svg-EvmPsXXYwtMGQtkJ .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-EvmPsXXYwtMGQtkJ .icon-shape p,#mermaid-svg-EvmPsXXYwtMGQtkJ .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-EvmPsXXYwtMGQtkJ .icon-shape .label rect,#mermaid-svg-EvmPsXXYwtMGQtkJ .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-EvmPsXXYwtMGQtkJ .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-EvmPsXXYwtMGQtkJ .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-EvmPsXXYwtMGQtkJ :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 否
是
否
是
否
是
否
是
项目是否需要流畅 GUI 动画或频繁 Flash 读?
普通 SPI + CPU 搬运
MCU 是否支持 DMA?
Flash 是否支持 QSPI?
普通 SPI + DMA
对帧率/吞吐要求是否极致?
QSPI + DMA
选型建议:
- 原型验证或低分辨率静态界面:直接选「普通 SPI + CPU 搬运」,开发最快、成本最低,性能足够即可;
- 常规 GUI 动画(如菜单滑动、图标切换):优先「普通 SPI + DMA」,性价比最高,CPU 得以释放去处理业务逻辑;
- 高帧率动画或频繁加载大图片:选「QSPI + DMA」,读吞吐提升 4 倍,配合批量刷新可把全屏耗时压到 ~21 ms;
- 预算与引脚受限:若 Flash 不支持 QSPI,可退而求其次用「普通 SPI + DMA」+ 页缓存策略,仍能获得可观收益。
5.1 实战:SPI 屏 + Flash 联合读写
下面是一个完整的「SPI 屏 + Flash 联合读写」示例,展示如何通过 DMA 将 Flash 中存储的图片数据直接搬运到 LCD 显存进行刷新。整体流程为:初始化 SPI 与 DMA → 从 Flash 读取图片数据 → 通过 DMA 发送到 LCD 显存窗口 → 中断处理完成刷新。
c
/* ============ 1. 初始化:SPI、DMA 与器件 ============ */
/* SPI1 用于 LCD,SPI2 用于 Flash;DMA1 通道 3 用于 SPI1 TX */
static void spi_lcd_init(void) {
/* 配置 SPI1:主模式、8bit、时钟极性 CPOL=0 / 相位 CPHA=0 */
SPI1->CR1 = SPI_CR1_MSTR | SPI_CR1_BR_2 | SPI_CR1_SSM | SPI_CR1_SSI;
SPI1->CR2 = SPI_CR2_TXDMAEN; /* 使能发送 DMA 请求 */
SPI1->CR1 |= SPI_CR1_SPE; /* 使能 SPI1 */
}
static void dma_init(void) {
/* DMA1 通道 3:内存 -> SPI1->DR,传输方向为内存到外设 */
DMA1_Channel3->CCR = DMA_CCR_MINC | DMA_CCR_DIR | DMA_CCR_TCIE;
DMA1_Channel3->CMAR = 0; /* 源地址:内存(图片缓冲区) */
DMA1_Channel3->CPAR = (uint32_t)&SPI1->DR; /* 目的地址:SPI1 数据寄存器 */
DMA1_Channel3->CNDTR = 0; /* 传输字节数,由 lcd_blit_from_flash 设置 */
NVIC_EnableIRQ(DMA1_Channel3_IRQn);
}
/* ============ 2. 地址映射:Flash 中的图片布局 ============ */
/* 图片在 Flash 中的存储布局:前 4 字节为宽高,之后为 RGB565 像素数据 */
#define IMG_HEADER_SIZE 4
#define IMG_FLASH_ADDR 0x000000 /* 图片数据在 Flash 中的起始地址 */
typedef struct {
uint16_t width;
uint16_t height;
} img_header_t;
/* 从 Flash 读取图片头,解析宽高 */
static void img_load_header(img_header_t *hdr) {
uint8_t buf[IMG_HEADER_SIZE];
flash_qspi_read(IMG_FLASH_ADDR, buf, IMG_HEADER_SIZE);
hdr->width = (buf[0] << 8) | buf[1];
hdr->height = (buf[2] << 8) | buf[3];
}
/* ============ 3. DMA 配置:Flash -> LCD 显存 ============ */
/* 全局图片缓冲区:DMA 传输期间必须保持有效 */
static uint8_t img_buf[240 * 320 * 2];
static volatile uint8_t dma_busy = 0;
/* 从 Flash 读取图片数据到缓冲区,再通过 DMA 发送到 LCD 显存窗口 */
void lcd_blit_from_flash(uint16_t x, uint16_t y) {
img_header_t hdr;
uint32_t data_addr = IMG_FLASH_ADDR + IMG_HEADER_SIZE;
uint32_t data_len;
img_load_header(&hdr);
data_len = (uint32_t)hdr.width * hdr.height * 2; /* RGB565,每像素 2 字节 */
/* 1) 通过 QSPI 从 Flash 读取图片像素到内存缓冲区 */
flash_qspi_read(data_addr, img_buf, data_len);
/* 2) 设置 LCD 显存写入窗口 */
lcd_set_window(x, y, x + hdr.width - 1, y + hdr.height - 1);
/* 3) 配置 DMA:内存 -> SPI1 发送寄存器,传输 data_len 字节 */
while (dma_busy); /* 等待上一次 DMA 完成 */
dma_busy = 1;
DMA1_Channel3->CMAR = (uint32_t)img_buf;
DMA1_Channel3->CNDTR = data_len;
DMA1_Channel3->CCR |= DMA_CCR_EN; /* 使能 DMA 通道,开始传输 */
}
/* ============ 4. 中断处理:DMA 传输完成 ============ */
void DMA1_Channel3_IRQHandler(void) {
if (DMA1->ISR & DMA_ISR_TCIF3) {
DMA1->IFCR = DMA_IFCR_CTCIF3; /* 清除传输完成标志 */
DMA1_Channel3->CCR &= ~DMA_CCR_EN; /* 关闭 DMA 通道 */
dma_busy = 0; /* 标记 DMA 空闲,可发起下一次传输 */
}
}
/* ============ 5. 调用示例 ============ */
void demo_show_image_from_flash(void) {
spi_lcd_init();
dma_init();
lcd_init();
flash_init();
/* 将 Flash 中存储的图片显示到屏幕左上角 */
lcd_blit_from_flash(0, 0);
}
关键点说明:
- 地址映射 :图片在 Flash 中以「头部(宽高)+ 像素数据」的紧凑格式存储,读取时先解析头部,再按
宽 × 高 × 2计算数据长度,避免整片读取浪费带宽; - DMA 配置 :源地址指向内存缓冲区
img_buf,目的地址固定为SPI1->DR,传输方向为内存到外设,使能内存地址自增(MINC)即可连续搬运整块像素; - 中断处理 :DMA 传输完成中断中清除标志并释放
dma_busy,保证下一次lcd_blit_from_flash不会在上一帧未发完时覆盖缓冲区; - 性能收益:CPU 只需发起一次 QSPI 读和一次 DMA 传输,即可完成整帧图片刷新,实测全屏刷新耗时从 ~85 ms 降至 ~21 ms(见上文实测表)。
6. 总结
SPI 屏与 Flash 的「高速」之路,本质上是「硬件选型 + 驱动分层 + DMA/QSPI 优化」三者的协同。先选对支持 QSPI 的器件,再搭好分层驱动,最后用 DMA 与批量传输把带宽用满,就能在低成本 MCU 上获得接近并行的显示与存储体验。希望本文的实践路径能帮你少走弯路,把每一 MHz 的 SPI 时钟都用在刀刃上。
7. 参考资料
以下资料覆盖了本文涉及的 SPI 协议、器件手册与优化技术,供读者深入学习时查阅:
| 资料 | 说明 | 链接 |
|---|---|---|
| SPI 协议规范 | 了解 SPI 总线四线制、时钟极性/相位(CPOL/CPHA)与时序要求的权威参考 | SPI Block Guide(NXP) |
| ST7789 数据手册 | 240×320 分辨率 LCD 驱动 IC 的寄存器定义、初始化时序与显存窗口配置 | ST7789V Datasheet(Sitronix) |
| W25Q64 数据手册 | SPI NOR Flash 的指令集、QSPI 模式(Fast Read Quad I/O)与擦写时序说明 |
W25Q64JV Datasheet(Winbond) |
| STM32F407 参考手册 | 主控 SPI 外设、DMA 控制器与中断向量配置的官方权威文档 | RM0090 Reference Manual(ST) |
| DMA 应用笔记 | 深入理解 DMA 传输方向、内存自增与中断处理的最佳实践 | AN4036: Using DMA(ST) |
| QSPI 应用笔记 | 讲解如何利用 Quad SPI 提升 NOR Flash 读吞吐的经典案例 | AN5027: QSPI interface(ST) |
提示:上述链接为官方或厂商公开资料,若个别链接失效,可在搜索引擎中按「资料名称 + 厂商型号」检索最新版本。