前言SPI 是嵌入式系统中最常用的同步串行通信协议之一。它速度快F407 的 SPI 可达 42MHz、全双工、硬件接线简单。更重要的是——你手头的墨水屏模块就是 SPI 接口的。学完这篇你就离驱动墨水屏只差一本 datasheet 的距离了。一、SPI 协议基础1.1 四根线信号全称方向作用SCKSerial Clock主机 → 从机时钟由主机产生MOSIMaster Out Slave In主机 → 从机主机发送数据给从机MISOMaster In Slave Out从机 → 主机从机发送数据给主机CSChip Select主机 → 从机片选信号低电平有效1.2 一主多从SPI 是多从机协议。增加从机只需要给每个从机分配一个独立的 CS 引脚主机 SCK ───┬─── 从机1 SCK └─── 从机2 SCK 主机 MOSI ───┬─── 从机1 MOSI └─── 从机2 MOSI 主机 MISO ◀──┬─── 从机1 MISO └─── 从机2 MISO 主机 CS1 ──────── 从机1 CS 主机 CS2 ──────── 从机2 CS同一时间只选中一个从机——其他从机的 MISO 会保持高阻态不影响总线。1.3 SPI 模式CPOL 和 CPHASPI 有 4 种模式由时钟极性CPOL和时钟相位CPHA决定模式CPOLCPHA说明000SCK 空闲低第一个边沿采样101SCK 空闲低第二个边沿采样210SCK 空闲高第一个边沿采样311SCK 空闲高第二个边沿采样最快的方法查器件 datasheet 的 SPI Timing Diagram上面会直接写 Mode 0/1/2/3 或 CPOL/CPHA 值。大部分常见 SPI 器件用Mode 0CPOL0, CPHA0包括你手头的墨水屏SSD1680。二、硬件接线以 F407 的SPI1为例挂在 APB2 上最快 42MHzSPI1 引脚STM32F407连接外部 SPI 器件SCKPA5CLKMOSIPA7MOSI / SDIN / DINMISOPA6MISO / SDO / DOUT有些器件不需要CS任意 GPIOCS / SS / DC视器件而定CS片选任意 GPIO 都可以不需要固定的硬件 CS。CubeMX 里把 CS 引脚配成普通的 GPIO_Output 即可。接线建议如果使用杜邦线长度不要超过 20cmSCK 走线远离电源/大电流走线如果通信距离长可以降低 SPI 时钟频率三、CubeMX 配置3.1 配置 SPI左侧Connectivity→SPI1ModeFull-Duplex Master全双工主机模式参数设置Frame FormatMotorolaData Size8 BitsFirst BitMSB FirstPrescaler32SPI 时钟 84MHz / 32 2.625MHz——保守值先让通信跑通CPOLLowMode 0CPHA1 EdgeMode 0NSSSoftware软件片选不要用硬件 CS自动配置了 PA5SCK、PA6MISO、PA7MOSI3.2 配置 CS 引脚CS 是一个普通的 GPIO不要选 SPI_NSS。找一个空闲引脚比如 PA4点击 PA4 → 选GPIO_Output命名CS_SPIOutput LevelHigh高电平 未选中3.3 生成代码四、代码SPI 收发基本操作4.1 CS 控制宏手动实现 CS 拉低/拉高#define SPI_CS_LOW() HAL_GPIO_WritePin(CS_SPI_GPIO_Port, CS_SPI_Pin, GPIO_PIN_RESET) #define SPI_CS_HIGH() HAL_GPIO_WritePin(CS_SPI_GPIO_Port, CS_SPI_Pin, GPIO_PIN_SET)4.2 发送一个字节uint8_t tx_data 0xAA; uint8_t rx_data 0; SPI_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx_data, rx_data, 1, 100); SPI_CS_HIGH();4.3 发送一串数据uint8_t cmd[] {0x01, 0x02, 0x03}; uint8_t rx_buf[3] {0}; SPI_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, cmd, rx_buf, 3, 100); SPI_CS_HIGH();HAL_SPI_TransmitReceive是全双工的——发一个字节的同时会收到一个字节。如果你只关心发送不关心收到的数据把接收缓冲区设一个占位变量即可。4.4 只发送忽略接收uint8_t data[] {0x01, 0x02, 0x03}; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, data, 3, 100); SPI_CS_HIGH();注意即使只调用 TransmitSPI 硬件仍然会在每个 SCK 周期从 MISO 接收数据只是 HAL 库会把接收结果扔掉。4.5 验证通信环回测试把 SPI1 的 MOSIPA7和 MISOPA6用杜邦线短接uint8_t tx 0xA5; uint8_t rx 0; SPI_CS_LOW(); HAL_SPI_TransmitReceive(hspi1, tx, rx, 1, 100); SPI_CS_HIGH(); if (rx tx) printf(SPI Loopback OK: sent 0x%02X, received 0x%02X\r\n, tx, rx); else printf(SPI Loopback FAIL: sent 0x%02X, received 0x%02X\r\n, tx, rx);如果环回测试通过说明 SPI 的物理连接和配置都是正确的。五、实战读取 SPI Flash 的 ID很多人手边会有一块W25Qxx系列 SPI Flash 模块W25Q16/W25Q32/W25Q64淘宝两三块钱。接线STM32F407W25Qxx 模块PA5 (SCK)CLKPA7 (MOSI)MOSI / DIPA6 (MISO)MISO / DOPA4 (CS)CS3.3VVCCGNDGND读 JEDEC ID0x9F很多 SPI 器件支持读 ID 命令发一个命令字节就能返回设备标识用来验证通信是否正常。W25Qxx 的 JEDEC ID 命令是0x9F会返回 3 个字节Manufacturer ID Memory Type Capacity。uint8_t cmd 0x9F; // JEDEC ID 命令 uint8_t id[3] {0}; SPI_CS_LOW(); HAL_SPI_Transmit(hspi1, cmd, 1, 100); HAL_SPI_Receive(hspi1, id, 3, 100); // 接收 3 字节 ID SPI_CS_HIGH(); printf(Flash ID: 0x%02X 0x%02X 0x%02X\r\n, id[0], id[1], id[2]);常见的返回值EF 40 15 → W25Q16 (2MB) EF 40 16 → W25Q32 (4MB) EF 40 17 → W25Q64 (8MB) EF 40 18 → W25Q128 (16MB)如果读到的是FF FF FF或00 00 00检查接线、CS 电平或 SPI 模式配置。六、SPI 通信注意事项6.1 SPI 速率F407 的 SPI1 挂在 APB2 上84MHzPrescaler 可选重要SPI1 挂在 APB2 上最高 42MHz84MHz / 2SPI2/SPI3 挂在 APB1 上最高 21MHz42MHz / 2。选 SPI 外设时注意时钟源差异SPI2/SPI3 如需高频建议用 SPI1。PrescalerSPI1 时钟APB284MHzSPI2/SPI3 时钟APB142MHz适用场景242MHz21MHz极限速度短走线421MHz10.5MHz高速通信810.5MHz5.25MHz大多数 SPI 器件165.25MHz2.625MHz保守值322.625MHz1.3125MHz推荐初始值641.3125MHz656kHz长线或高噪声128656kHz328kHz慢速兼容256328kHz164kHz最慢建议先降频到 1MHz 左右让通信跑通没问题再逐步提高。6.2 SPI 发送后要等待完成HAL_SPI_Transmit(hspi1, data, len, 100); // 重要等待 SPI 传输完全结束 while (HAL_SPI_GetState(hspi1) ! HAL_SPI_STATE_READY);如果两个 SPI 传输调用间隔太短上一个传输还没完成就会出问题。6.3 中断/DMA 方式和 UART、ADC 一样SPI 也支持中断和 DMA 方式适合大批量数据传输比如刷墨水屏的画面缓冲区。// SPI DMA 发送 HAL_SPI_Transmit_DMA(hspi1, buf, size); // 发送完成回调 void HAL_SPI_TxCpltCallback(SPI_HandleTypeDef *hspi) { if (hspi-Instance SPI1) { // 传输完成 } }七、常见问题排查现象原因一直读到 0xFFMOSI/MISO 接反了CS 没拉低器件没上电一直读到 0x00MISO 被拉死检查 CS或器件不支持发送数据错位CPOL/CPHA 和器件不匹配数据有时对有时错频率太高、线太长、接触不良第一个字节丢失片选拉低后需要一点延时再发尤其时序严格的器件// 对于时序敏感的器件CS 拉低后加个微小延时 SPI_CS_LOW(); HAL_Delay(1); // 部分器件需要 CS 建立时间 HAL_SPI_Transmit(hspi1, data, len, 100); SPI_CS_HIGH();八、SPI vs I2C 快速对比SPII2C接线4 线SCK/MOSI/MISO/CS×N2 线SCL/SDA速度最高 42MHzF407最高 400kHz标准~ 1MHz快速全双工是否多从机CS 线多地址寻址省引脚距离短 20cm 可靠稍长简单性更简单协议稍复杂有速度要求或者全双工需求 → SPI 引脚有限或需要挂很多设备 → I2C 你的墨水屏用 SPI 是对的——它需要一次传输大量像素数据速度很重要。九、下一步驱动墨水屏你现在已经掌握了驱动墨水屏所需的所有前置知识技能对应文章墨水屏中的应用GPIO第①篇CS、DC、RST 控制SPI第⑥篇发送命令和数据到 SSD1680定时器第③篇刷新时序控制ADC可选第⑤篇电池电压检测墨水屏的驱动本质就是通过 SPI 发送初始化命令序列从 datasheet 抄通过 SPI 发送图像数据每像素 1 bit黑白两色发送刷新命令→ 等待 BUSY 引脚变低 → 完成练习用 SPI 环回测试确认你的配置没问题如果有 W25Qxx Flash 模块读出它的 JEDEC ID尝试降低 SPI 分频从 32 到 8看看通信是否仍然正常系列回顾篇号内容状态①开发环境搭建 点灯✅②UART 串口 printf✅③定时器 PWM 呼吸灯✅④外部中断 按键消抖✅⑤ADC 模拟信号采集✅⑥SPI 通信✅⑦预告驱动 2.13 寸墨水屏六篇基础写完你已经具备了驱动大部分外设的能力。下一篇开始进入实战——用 SPI 驱动 Waveshare 2.13 寸墨水屏用它做一个单词卡