STM32——SPI 屏 · Flash · 高速

目录

  • [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 为例,初始化流程通常为:

  1. 拉高复位脚,延时 120 ms;
  2. 发送 SLPOUT(睡眠退出)命令;
  3. 等待 120 ms 后发送 DISPON(显示开启);
  4. 配置像素格式、扫描方向与显存访问窗口。
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)。

调优建议:

  1. 优先开启 DMA,这是性价比最高的优化;
  2. 确认 PCB 走线阻抗匹配,避免高速时钟下信号劣化;
  3. 若主控支持,将 SPI 时钟尽量逼近从机上限,但需留 10%~20% 裕量;
  4. 对 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)

提示:上述链接为官方或厂商公开资料,若个别链接失效,可在搜索引擎中按「资料名称 + 厂商型号」检索最新版本。

相关推荐
To_OC1 小时前
同样是让 AI 写全栈,为什么别人一句话就够了
前端·前端框架·next.js
wuminyu1 小时前
ForkJoinPool窃取线程在扫描其他WorkQueue时的随机采样算法与Phase线程挂起唤醒机制解析
java·linux·c语言·jvm·c++
Sarvartha1 小时前
内部类知识
java·开发语言
CDwenhuohuo1 小时前
electron pc项目打包成桌面端
前端·javascript·electron
前端大斗师1 小时前
「大于 1000」把 1000 元那单也算进去了:我在 Vue3 订单页对了 10 句话
前端·人工智能·typescript·大模型·原力计划
Knight_AL1 小时前
从 EasyExcel 到 Apache Fesod:Java Excel 处理生态的一次重构
java·apache·excel
yuniko-n1 小时前
【JUC】集合线程安全
java
Jmyd01232 小时前
用 Web VR 引擎搭建 3D 虚拟展厅:从素材上云到多端实时渲染的完整数据链路
前端·3d·vr·三维数字化
starzy19902 小时前
Flink SlotManager启动流程源码深度剖析:从ResourceManager到Slot分配的完整链路
java·大数据·flink