2. SPI Flash 硬件基础:SPI 协议简介与 Flash 芯片选型
做嵌入式 UI,图片资源动不动就几百 KB。LVGL 跑起来,内部 Flash 根本塞不下。这时候,外挂一颗 SPI Flash 就成了最实在的方案。
这一章,我们先把硬件底子打牢。说白了,就是搞明白两件事:SPI 总线怎么干活,以及Flash 芯片怎么挑。我以 W25Q64 为例,带你走一遍选型和理解的过程。
2.1 SPI 协议:四根线怎么聊天的?
SPI 全称 Serial Peripheral Interface,同步串行外设接口。它由 Motorola 公司推出,后来成了事实标准。
SPI 总线只需要 4 根信号线:
| 信号 | 全称 | 方向 | 作用 |
|---|---|---|---|
| SCLK | Serial Clock | 主机 → 从机 | 时钟信号,由主机产生 |
| MOSI | Master Out Slave In | 主机 → 从机 | 主机发送数据给从机 |
| MISO | Master In Slave Out | 从机 → 主机 | 从机发送数据给主机 |
| CS | Chip Select | 主机 → 从机 | 片选信号,低电平有效 |
SPI 是 全双工 的。什么意思?主机发数据的同时,也能收数据。这一点比 I2C 快不少。
通信过程很简单:
- 主机把 CS 拉低,选中从机。
- 主机在 SCLK 上产生时钟脉冲。
- 每个时钟周期,主机通过 MOSI 发 1 bit,同时从机通过 MISO 回 1 bit。
- 传输结束,主机把 CS 拉高。
我的习惯: 调试 SPI 时,先拿逻辑分析仪抓 CS 和 SCLK。CS 有没有拉低?SCLK 有没有波形?这两根线对了,基本就成功了一半。
2.2 SPI 的四种模式:别搞错时钟极性
SPI 有 4 种工作模式,由 时钟极性 (CPOL) 和 时钟相位 (CPHA) 决定。
- CPOL:空闲时 SCLK 的电平。0 是低电平,1 是高电平。
- CPHA:数据在哪个边沿采样。0 是第一个边沿,1 是第二个边沿。
| 模式 | CPOL | CPHA | 采样边沿 | 常用场景 |
|---|---|---|---|---|
| 模式 0 | 0 | 0 | 上升沿 | W25Q64、SD 卡 |
| 模式 1 | 0 | 1 | 下降沿 | 部分传感器 |
| 模式 2 | 1 | 0 | 下降沿 | 部分 LCD 驱动 |
| 模式 3 | 1 | 1 | 上升沿 | 部分射频芯片 |
我曾经踩过的坑: 第一次用 W25Q64 时,没仔细看数据手册,默认用了模式 3。结果读 ID 全错。折腾了半天,才发现 W25Q64 只支持 模式 0 和模式 3。后来我学乖了,每次拿到新芯片,第一件事就是翻 SPI 章节,确认 CPOL 和 CPHA。
2.3 W25Q64:一颗经典的 SPI Flash
W25Q64 是 Winbond 公司生产的 SPI Flash 芯片。容量 64 Mbit,也就是 8 MB。为什么选它?
- 性价比高:8 MB 容量,价格才一两块钱。
- 生态成熟:几乎所有 MCU 厂商的驱动库都支持它。
- 速度够用:支持最高 80 MHz 时钟频率,Quad SPI 模式下更快。
它的内部结构是这样的:
你想想看,8 MB 能存多少张图片?一张 320×240 的 16 位色 BMP 大约 150 KB。8 MB 能存 50 多张。对于 LVGL 的 UI 项目,绰绰有余。
2.4 芯片选型:除了 W25Q64,还有哪些选择?
市面上 SPI Flash 芯片很多。我列几个常见的,方便你对比:
| 型号 | 容量 | 最高频率 | Quad SPI | 价格 (参考) |
|---|---|---|---|---|
| W25Q16 | 2 MB | 80 MHz | 支持 | ¥1.5 |
| W25Q64 | 8 MB | 80 MHz | 支持 | ¥2.5 |
| W25Q128 | 16 MB | 80 MHz | 支持 | ¥4.0 |
| GD25Q64 | 8 MB | 80 MHz | 支持 | ¥2.0 |
| MX25L64 | 8 MB | 86 MHz | 支持 | ¥3.0 |
我的建议: 如果项目对成本敏感,选 GD25Q64,和 W25Q64 完全兼容。如果追求稳定,选 W25Q64,文档最全,社区支持最好。
2.5 硬件连接:怎么接到 MCU 上?
连接很简单。以 STM32 为例:
// W25Q64 与 STM32F407 的连接
// PA5 - SCLK
// PA7 - MOSI
// PA6 - MISO
// PB0 - CS
SPI_HandleTypeDef hspi1;
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_LOW; // CPOL = 0
hspi1.Init.CLKPhase = SPI_PHASE_1EDGE; // CPHA = 0
hspi1.Init.NSS = SPI_NSS_SOFT; // 软件控制 CS
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // 42 MHz / 4 = 10.5 MHz
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 10;
HAL_SPI_Init(&hspi1);
}
避坑指南: 硬件上,CS 引脚不要用硬件 NSS,用普通 GPIO 软件控制。为什么?因为硬件 NSS 在多从机场景下容易出问题。我习惯用 PB0 做 CS,手动拉高拉低,心里踏实。
2.6 读芯片 ID:验证通信的第一步
拿到新板子,怎么确认 SPI 通信正常?读芯片的 制造商 ID 和 设备 ID。
W25Q64 的读 ID 命令是 0x9F。发送流程:
- 拉低 CS。
- 发送命令字节
0x9F。 - 连续读取 3 个字节:制造商 ID、设备 ID 高字节、设备 ID 低字节。
- 拉高 CS。
uint8_t manufacturer_id;
uint8_t device_id_high;
uint8_t device_id_low;
void W25Q64_Read_ID(void)
{
uint8_t cmd = 0x9F;
uint8_t rx_buffer[3] = {0};
CS_LOW(); // 拉低 CS
HAL_SPI_Transmit(&hspi1, &cmd, 1, 100);
HAL_SPI_Receive(&hspi1, rx_buffer, 3, 100);
CS_HIGH(); // 拉高 CS
manufacturer_id = rx_buffer[0];
device_id_high = rx_buffer[1];
device_id_low = rx_buffer[2];
// W25Q64 的 ID 应该是:0xEF, 0x40, 0x17
if (manufacturer_id == 0xEF && device_id_high == 0x40 && device_id_low == 0x17)
{
// 通信正常
}
else
{
// 通信异常,检查硬件连接
}
}
我曾经遇到的坑: 读 ID 一直返回 0xFF。查了半天,发现是 CS 引脚没配置成推挽输出。默认是开漏,拉不低。嗯,硬件初始化一定要检查 GPIO 模式。
2.7 本章小结
这一章我们搞清楚了:
- SPI 协议的四根线和四种模式。
- W25Q64 的内部结构和选型要点。
- 硬件连接和读 ID 的代码实现。