|
@@ -9,166 +9,391 @@ created: 2026-07-15
|
|
|
|
|
|
|
|
# SPI通信与FSMC总线
|
|
# SPI通信与FSMC总线
|
|
|
|
|
|
|
|
-> **用生活理解**:SPI 是全双工的同步通信,就像两个人用两根线同时说话和听。四条线各司其职:SCK(节奏)、MOSI(你说)、MISO(我听)、NSS(点名)。FSMC 就是芯片的外部数据总线,把外部设备(Flash/SRAM/LCD)映射到 STM32 的地址空间,像访问内部变量一样访问外部设备。
|
|
|
|
|
|
|
+> **用生活理解**:SPI 是全双工同步通信,就像两个人用两根电话线同时说话和听——一根你说(MOSI),一根你听(MISO),SCK 是节拍器控制节奏,NSS 是点名器(叫到谁谁回答)。FSMC 就像给芯片外接了一个"内存扩展槽"——外部 SRAM/Flash/LCD 映射到 CPU 的地址空间,访问它们就像访问内部变量一样直接。
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## SPI 通信协议
|
|
## SPI 通信协议
|
|
|
|
|
|
|
|
-SPI = Serial Peripheral Interface(串行外设接口)。
|
|
|
|
|
|
|
+SPI = Serial Peripheral Interface(串行外设接口),**同步、全双工**。
|
|
|
|
|
|
|
|
-### 四线制
|
|
|
|
|
|
|
+### 四线制信号
|
|
|
|
|
|
|
|
-| 信号 | 功能 |
|
|
|
|
|
-|------|------|
|
|
|
|
|
-| SCK | 时钟线(由主机产生) |
|
|
|
|
|
-| MOSI | 主机输出/从机输入 |
|
|
|
|
|
-| MISO | 主机输入/从机输出 |
|
|
|
|
|
-| NSS/CS | 从机选择(低电平有效) |
|
|
|
|
|
|
|
+| 信号 | 全称 | 功能 |
|
|
|
|
|
+|------|------|------|
|
|
|
|
|
+| **SCK** | Serial Clock | 时钟线,由主机产生 |
|
|
|
|
|
+| **MOSI** | Master Out Slave In | 主机输出/从机输入 |
|
|
|
|
|
+| **MISO** | Master In Slave Out | 主机输入/从机输出 |
|
|
|
|
|
+| **NSS/CS** | Chip Select | 从机选择线,低电平有效 |
|
|
|
|
|
|
|
|
-### 四种模式(CPOL/CPHA)
|
|
|
|
|
|
|
+**多设备拓扑**:
|
|
|
|
|
+```
|
|
|
|
|
+主机(SCK) ──── SCK ────┬── 从机1 CS1
|
|
|
|
|
+ ├── 从机2 CS2
|
|
|
|
|
+ └── 从机3 CS3
|
|
|
|
|
+ (MOSI) ── MOSI ────┼── 所有从机共享
|
|
|
|
|
+ (MISO) ── MISO ────┼── 所有从机共享
|
|
|
|
|
+```
|
|
|
|
|
+> 各从机独立片选(CS),SCK/MOSI/MISO 共享
|
|
|
|
|
+
|
|
|
|
|
+### SPI 四种工作模式
|
|
|
|
|
+
|
|
|
|
|
+SPI 模式由 CPOL(时钟极性)和 CPHA(时钟相位)决定:
|
|
|
|
|
+
|
|
|
|
|
+| 模式 | CPOL | CPHA | 空闲 SCK | 数据采集边沿 | NSS 有效后数据变化边沿 |
|
|
|
|
|
+|------|------|------|----------|-------------|---------------------|
|
|
|
|
|
+| **0** | 0 | 0 | 低电平 | 上升沿(第1个) | 下降沿 |
|
|
|
|
|
+| 1 | 0 | 1 | 低电平 | 下降沿(第2个) | 上升沿 |
|
|
|
|
|
+| 2 | 1 | 0 | 高电平 | 下降沿(第1个) | 上升沿 |
|
|
|
|
|
+| 3 | 1 | 1 | 高电平 | 上升沿(第2个) | 下降沿 |
|
|
|
|
|
+
|
|
|
|
|
+> **W25Q64 Flash** 支持模式 0 和模式 3。配置时注意主从必须一致。
|
|
|
|
|
+> **参考**:参考手册 §23(SPI 寄存器描述)、W25Q64 数据手册
|
|
|
|
|
+
|
|
|
|
|
+### SPI 关键寄存器(SPIx)
|
|
|
|
|
+
|
|
|
|
|
+| 寄存器 | 地址偏移 | 功能 |
|
|
|
|
|
+|--------|---------|------|
|
|
|
|
|
+| **CR1** | 0x00 | 控制1(CPOL、CPHA、BR[2:0]波特率分频、MSTR主从、SPE使能、LSBFIRST位序、SSI、SSM) |
|
|
|
|
|
+| **CR2** | 0x04 | 控制2(SSOE、TXEIE/RXNEIE中断使能、DMA 使能) |
|
|
|
|
|
+| **SR** | 0x08 | 状态(BSY忙、TXE发送空、RXNE接收非空、MODF模式错误、OVR溢出) |
|
|
|
|
|
+| **DR** | 0x0C | 数据寄存器(读写共用,写 = 发送缓冲区,读 = 接收缓冲区) |
|
|
|
|
|
+| **CRCPR** | 0x10 | CRC 多项式寄存器 |
|
|
|
|
|
+
|
|
|
|
|
+**CR1.BR[2:0] 波特率分频**:
|
|
|
|
|
+| BR[2:0] | 分频系数 | SPI1(72MHz) | SPI2/3(36MHz) |
|
|
|
|
|
+|---------|---------|-------------|--------------|
|
|
|
|
|
+| 000 | /2 | 36MHz | 18MHz |
|
|
|
|
|
+| 001 | /4 | 18MHz | 9MHz |
|
|
|
|
|
+| 010 | /8 | 9MHz | 4.5MHz |
|
|
|
|
|
+| 011 | /16 | 4.5MHz | 2.25MHz |
|
|
|
|
|
+| 100 | /32 | 2.25MHz | 1.125MHz |
|
|
|
|
|
+| 101 | /64 | 1.125MHz | 562.5KHz |
|
|
|
|
|
+| 110 | /128 | 562.5KHz | 281.25KHz |
|
|
|
|
|
+| 111 | /256 | 281.25KHz | 140.625KHz |
|
|
|
|
|
+
|
|
|
|
|
+### SPI 收发原理
|
|
|
|
|
+
|
|
|
|
|
+SPI 数据寄存器 DR 是**双缓冲**结构:
|
|
|
|
|
+```
|
|
|
|
|
+写 DR → TX 缓冲区 → 移位寄存器(8位) → MOSI 逐位输出
|
|
|
|
|
+ ↓
|
|
|
|
|
+MISO 逐位输入 ← 移位寄存器(8位) ← RX 缓冲区 → 读 DR
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-| 模式 | CPOL | CPHA | 特性 |
|
|
|
|
|
-|------|------|------|------|
|
|
|
|
|
-| 0 | 0 | 0 | SCK空闲低,第一个边沿采集 |
|
|
|
|
|
-| 1 | 0 | 1 | SCK空闲低,第二个边沿采集 |
|
|
|
|
|
-| 2 | 1 | 0 | SCK空闲高,第一个边沿采集 |
|
|
|
|
|
-| 3 | 1 | 1 | SCK空闲高,第二个边沿采集 |
|
|
|
|
|
|
|
+**发送 1 字节的时序**:
|
|
|
|
|
+```
|
|
|
|
|
+1. 写 DR (CPU) → 数据进入 TX 缓冲区
|
|
|
|
|
+2. TX 缓冲区 → 移位寄存器 (TXE=1, 可写入下一字节)
|
|
|
|
|
+3. 移位寄存器逐位移出 (SCK 控制)
|
|
|
|
|
+4. 同时逐位移入 MISO 数据
|
|
|
|
|
+5. 8 位完成后数据进入 RX 缓冲区 (RXNE=1)
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-> 与 W25Q64 Flash 通信常用**模式 0**(CPOL=0, CPHA=0)或**模式 3**(CPOL=1, CPHA=3)
|
|
|
|
|
|
|
+**读写对称性**:SPI 是**环形移位**——发 1 字节的同时必定收到 1 字节。
|
|
|
|
|
+要读取从机数据,主机必须同时发送 1 字节(通常发 0x00 或 0xFF 占位)。
|
|
|
|
|
|
|
|
-### SPI 关键寄存器
|
|
|
|
|
|
|
+---
|
|
|
|
|
|
|
|
-| 寄存器 | 功能 |
|
|
|
|
|
-|--------|------|
|
|
|
|
|
-| CR1 | 控制(CPOL/CPHA、BR波特率分频、MSTR主从、SPE使能、LSBFIRST) |
|
|
|
|
|
-| CR2 | 控制(SSOE、TXEIE/RXNEIE中断使能) |
|
|
|
|
|
-| SR | 状态(BSY忙、TXE发送空、RXNE接收非空) |
|
|
|
|
|
-| DR | 数据寄存器(发送/接收共用) |
|
|
|
|
|
|
|
+## 软件 SPI 实现
|
|
|
|
|
|
|
|
-> **参考**:参考手册 §23(SPI 寄存器描述)
|
|
|
|
|
|
|
+### 软件设计(寄存器版 — 软件模拟 SPI)
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
+**项目路径**:`stm32_base/36_spi_software_register`
|
|
|
|
|
|
|
|
-## W25Q64 Flash
|
|
|
|
|
|
|
+**文件:`stm32_base/36_spi_software_register/Hardware/SPI/spi.h`**
|
|
|
|
|
|
|
|
-| 参数 | 值 |
|
|
|
|
|
-|------|-----|
|
|
|
|
|
-| 容量 | 8MB(64Mbit) |
|
|
|
|
|
-| 接口 | SPI |
|
|
|
|
|
-| 页 | 256 字节 |
|
|
|
|
|
-| 扇区 | 4KB(16 页) |
|
|
|
|
|
-| 擦除 | 扇区擦除(20ms)、块擦除、全擦除 |
|
|
|
|
|
-| 写寿命 | 100,000 次 |
|
|
|
|
|
|
|
+```c
|
|
|
|
|
+#ifndef __SPI_H
|
|
|
|
|
+#define __SPI_H
|
|
|
|
|
|
|
|
-**常用指令**:
|
|
|
|
|
-| 指令 | 代码 | 功能 |
|
|
|
|
|
-|------|------|------|
|
|
|
|
|
-| WREN | 0x06 | 写使能 |
|
|
|
|
|
-| RDSR | 0x05 | 读状态寄存器 |
|
|
|
|
|
-| PAGE_PROG | 0x02 | 页编程(256字节) |
|
|
|
|
|
-| READ | 0x03 | 读数据 |
|
|
|
|
|
-| SECTOR_ERASE | 0xD8 | 扇区擦除(4KB) |
|
|
|
|
|
|
|
+#include "stm32f10x.h"
|
|
|
|
|
|
|
|
----
|
|
|
|
|
|
|
+// GPIO 引脚宏定义
|
|
|
|
|
+// PA5=SCK, PA6=MISO, PA7=MOSI, PA4=CS(片选)
|
|
|
|
|
+#define SPI_SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5) // |= SCK置1
|
|
|
|
|
+#define SPI_SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5) // &=~ SCK清0
|
|
|
|
|
+#define SPI_MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7) // |= MOSI置1
|
|
|
|
|
+#define SPI_MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7) // &=~ MOSI清0
|
|
|
|
|
+#define SPI_MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6) // & 读取MISO电平
|
|
|
|
|
+#define SPI_CS_HIGH (GPIOA->ODR |= GPIO_ODR_ODR4) // |= CS置1
|
|
|
|
|
+#define SPI_CS_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR4) // &=~ CS清0
|
|
|
|
|
|
|
|
-## 软件设计(软件模拟 SPI 写 Flash)
|
|
|
|
|
|
|
+void SPI_Init(void);
|
|
|
|
|
+uint8_t SPI_SendByte(uint8_t byte);
|
|
|
|
|
|
|
|
-**文件:`stm32_base/36_spi_software_register/Hardware/SPI/spi.h`**
|
|
|
|
|
|
|
+#endif
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
-```c
|
|
|
|
|
-#define SCK_HIGH (GPIOA->ODR |= GPIO_ODR_ODR5)
|
|
|
|
|
-#define SCK_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR5)
|
|
|
|
|
-#define MOSI_HIGH (GPIOA->ODR |= GPIO_ODR_ODR7)
|
|
|
|
|
-#define MOSI_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR7)
|
|
|
|
|
-#define MISO_READ (GPIOA->IDR & GPIO_IDR_IDR6)
|
|
|
|
|
-#define CS_HIGH (GPIOA->ODR |= GPIO_ODR_ODR4)
|
|
|
|
|
-#define CS_LOW (GPIOA->ODR &= ~GPIO_ODR_ODR4)
|
|
|
|
|
|
|
+**文件:`stm32_base/36_spi_software_register/Hardware/SPI/spi.c`**
|
|
|
|
|
|
|
|
-// SPI 模式0:CPOL=0, CPHA=0
|
|
|
|
|
-// 空闲时SCK=低, 上升沿采集数据
|
|
|
|
|
|
|
+```c
|
|
|
|
|
+#include "spi.h"
|
|
|
|
|
|
|
|
|
|
+// SPI 初始化(模式0: CPOL=0, CPHA=0)
|
|
|
void SPI_Init(void)
|
|
void SPI_Init(void)
|
|
|
{
|
|
{
|
|
|
|
|
+ // 1. 开启 GPIOA 时钟
|
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
|
RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
|
|
- // PA5(SCK)+PA7(MOSI): 推挽输出
|
|
|
|
|
- // PA6(MISO): 浮空输入
|
|
|
|
|
- // PA4(CS): 推挽输出
|
|
|
|
|
|
|
+
|
|
|
|
|
+ // 2. 配置 PA4(CS), PA5(SCK), PA7(MOSI) 为推挽输出 50MHz
|
|
|
|
|
+ // PA4: CRL 控制,Pin4 的 MODE 在 CRL 的第 18~19 位
|
|
|
|
|
+ GPIOA->CRL |= GPIO_CRL_MODE4; // MODE4=11: 50MHz输出
|
|
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF4; // CNF4=00: 通用推挽
|
|
|
|
|
+
|
|
|
|
|
+ // PA5(SCK)
|
|
|
|
|
+ GPIOA->CRL |= GPIO_CRL_MODE5;
|
|
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF5;
|
|
|
|
|
+
|
|
|
|
|
+ // PA7(MOSI)
|
|
|
|
|
+ GPIOA->CRL |= GPIO_CRL_MODE7;
|
|
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF7;
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 配置 PA6(MISO) 为浮空输入
|
|
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_MODE6; // MODE6=00: 输入
|
|
|
|
|
+ GPIOA->CRL &= ~GPIO_CRL_CNF6_1; // CNF6[1]=0
|
|
|
|
|
+ GPIOA->CRL |= GPIO_CRL_CNF6_0; // CNF6[0]=1 → 浮空输入
|
|
|
|
|
+
|
|
|
|
|
+ // 4. 初始状态: SCK=低(CPOL=0), CS=高(不选中)
|
|
|
|
|
+ SPI_SCK_LOW;
|
|
|
|
|
+ SPI_CS_HIGH;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+// SPI 模式0: CPOL=0(空闲SCK低), CPHA=0(上升沿采集)
|
|
|
|
|
+// 发送 1 字节的同时接收 1 字节
|
|
|
uint8_t SPI_SendByte(uint8_t byte)
|
|
uint8_t SPI_SendByte(uint8_t byte)
|
|
|
{
|
|
{
|
|
|
- for (uint8_t i = 0; i < 8; i++)
|
|
|
|
|
|
|
+ uint8_t i;
|
|
|
|
|
+
|
|
|
|
|
+ // 循环 8 次,每次处理 1 位
|
|
|
|
|
+ for (i = 0; i < 8; i++)
|
|
|
{
|
|
{
|
|
|
- if (byte & 0x80) MOSI_HIGH; else MOSI_LOW; // MSB first
|
|
|
|
|
- byte <<= 1;
|
|
|
|
|
- SCK_HIGH; // 上升沿 → 从机采集
|
|
|
|
|
- if (MISO_READ) byte |= 1; // 读取MISO
|
|
|
|
|
- SCK_LOW; // 下降沿 → 从机改变数据
|
|
|
|
|
|
|
+ // 第 1 步: 在 SCK 低电平时改变 MOSI 数据
|
|
|
|
|
+ // MSB first (先发最高位)
|
|
|
|
|
+ if (byte & 0x80) // &测试: 最高位=1?
|
|
|
|
|
+ SPI_MOSI_HIGH; // 输出高
|
|
|
|
|
+ else
|
|
|
|
|
+ SPI_MOSI_LOW; // 输出低
|
|
|
|
|
+
|
|
|
|
|
+ byte <<= 1; // 左移1位,准备下一位
|
|
|
|
|
+
|
|
|
|
|
+ // 第 2 步: SCK 上升沿 → 从机采集 MOSI
|
|
|
|
|
+ SPI_SCK_HIGH; // SCK上升沿
|
|
|
|
|
+
|
|
|
|
|
+ // 同时,主机在上升沿采集 MISO(从机的数据)
|
|
|
|
|
+ if (SPI_MISO_READ) // 读取MISO电平
|
|
|
|
|
+ byte |= 1; // 如果MISO=高,将当前位(已左移后的bit0)置1
|
|
|
|
|
+
|
|
|
|
|
+ // 第 3 步: SCK 下降沿 → 从机改变 MISO 数据
|
|
|
|
|
+ SPI_SCK_LOW; // SCK下降沿
|
|
|
}
|
|
}
|
|
|
- return byte;
|
|
|
|
|
|
|
+
|
|
|
|
|
+ return byte; // 返回接收到的数据
|
|
|
}
|
|
}
|
|
|
```
|
|
```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
|
|
+## 硬件 SPI(stm32_base/37_spi_hardware_register)
|
|
|
|
|
+
|
|
|
|
|
+硬件 SPI 使用 STM32 片内外设,更高效(硬件自动移位):
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+void SPI1_Init(void)
|
|
|
|
|
+{
|
|
|
|
|
+ // 1. 开启 SPI1 + GPIOA 时钟
|
|
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_SPI1EN; // SPI1在APB2
|
|
|
|
|
+ RCC->APB2ENR |= RCC_APB2ENR_IOPAEN;
|
|
|
|
|
+
|
|
|
|
|
+ // 2. PA5(SCK) 复用推挽, PA6(MISO) 浮空输入, PA7(MOSI) 复用推挽
|
|
|
|
|
+
|
|
|
|
|
+ // 3. 配置 SPI1
|
|
|
|
|
+ // CR1: CPOL=0, CPHA=0, BR=011(72/16=4.5MHz), MSTR=1(主机), SPE=1(使能)
|
|
|
|
|
+ SPI1->CR1 = 0; // 先清0
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_CPOL & 0; // CPOL=0
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_CPHA & 0; // CPHA=0
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_BR_1 | SPI_CR1_BR_0; // BR=011: fPCLK/16
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_MSTR; // MSTR=1: 主机模式
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_SSI; // SSI=1: 软件NSS高电平
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_SSM; // SSM=1: 软件NSS管理
|
|
|
|
|
+ SPI1->CR1 |= SPI_CR1_SPE; // SPE=1: SPI使能
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+// 硬件 SPI 收发
|
|
|
|
|
+uint8_t SPI1_SendByte(uint8_t byte)
|
|
|
|
|
+{
|
|
|
|
|
+ while (!(SPI1->SR & SPI_SR_TXE)) {} // 等待 TX 缓冲区空
|
|
|
|
|
+ SPI1->DR = byte; // 写入数据 → 开始发送
|
|
|
|
|
+ while (!(SPI1->SR & SPI_SR_RXNE)) {} // 等待 RX 缓冲区非空
|
|
|
|
|
+ return SPI1->DR; // 读取接收数据
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
|
|
+## W25Q64 Flash 驱动
|
|
|
|
|
+
|
|
|
|
|
+| 参数 | 值 |
|
|
|
|
|
+|------|-----|
|
|
|
|
|
+| 容量 | 8MB (64Mbit) |
|
|
|
|
|
+| 页(Page) | 256 字节 |
|
|
|
|
|
+| 扇区(Sector) | 4KB (16 页) |
|
|
|
|
|
+| 块(Block) | 64KB (16 扇区) |
|
|
|
|
|
+| 擦除时间(扇区) | 典型 45ms |
|
|
|
|
|
+| 写寿命 | 100,000 次 |
|
|
|
|
|
+
|
|
|
|
|
+### 常用指令集
|
|
|
|
|
+
|
|
|
|
|
+| 指令 | 代码 | 功能 | 后接参数 |
|
|
|
|
|
+|------|------|------|---------|
|
|
|
|
|
+| WREN | 0x06 | 写使能(每次写前必须发) | — |
|
|
|
|
|
+| WRDI | 0x04 | 写禁止 | — |
|
|
|
|
|
+| RDSR | 0x05 | 读状态寄存器 | 1字节返回 |
|
|
|
|
|
+| WRSR | 0x01 | 写状态寄存器 | 1字节 |
|
|
|
|
|
+| **READ** | **0x03** | **读数据** | 3字节地址 + N字节数据 |
|
|
|
|
|
+| **PAGE_PROG** | **0x02** | **页编程(≤256字节)** | 3字节地址 + 数据 |
|
|
|
|
|
+| **SECTOR_ERASE** | **0xD8** | **扇区擦除(4KB)** | 3字节地址 |
|
|
|
|
|
+| BLOCK_ERASE_32 | 0x52 | 32KB 块擦除 | 3字节地址 |
|
|
|
|
|
+| BLOCK_ERASE_64 | 0xD8 | 64KB 块擦除 | 3字节地址 |
|
|
|
|
|
+| CHIP_ERASE | 0xC7 | 全片擦除 | — |
|
|
|
|
|
+| RDID | 0x9F | 读芯片 ID | 3字节返回 |
|
|
|
|
|
+
|
|
|
|
|
+### W25Q64 擦写(读写前必须擦除)
|
|
|
|
|
+
|
|
|
|
|
+```c
|
|
|
|
|
+// W25Q64 写入流程:
|
|
|
|
|
+// 1. 读状态寄存器,检查 BUSY 位(正在擦除/写入?)
|
|
|
|
|
+// 2. 发送 WREN (0x06) 使能写
|
|
|
|
|
+// 3. 发送 SECTOR_ERASE (0xD8) + 3字节地址 → 等待 BUSY=0
|
|
|
|
|
+// 4. 再次 WREN → PAGE_PROG (0x02) + 3字节地址 + 数据(≤256字节)
|
|
|
|
|
+// 5. 等待 BUSY=0
|
|
|
|
|
+
|
|
|
|
|
+uint8_t W25Q64_ReadSR(void)
|
|
|
|
|
+{
|
|
|
|
|
+ uint8_t sr;
|
|
|
|
|
+ CS_LOW;
|
|
|
|
|
+ SPI_SendByte(RDSR); // 发指令 0x05
|
|
|
|
|
+ sr = SPI_SendByte(0xFF); // 发占住位,收状态
|
|
|
|
|
+ CS_HIGH;
|
|
|
|
|
+ return sr;
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void W25Q64_WaitBusy(void)
|
|
|
|
|
+{
|
|
|
|
|
+ while (W25Q64_ReadSR() & 0x01); // BUSY=1 表示忙
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+void W25Q64_WritePage(uint32_t addr, uint8_t *data, uint16_t len)
|
|
|
|
|
+{
|
|
|
|
|
+ W25Q64_WaitBusy(); // 等上次完成
|
|
|
|
|
+ CS_LOW;
|
|
|
|
|
+ SPI_SendByte(WREN); // 写使能
|
|
|
|
|
+ CS_HIGH;
|
|
|
|
|
+
|
|
|
|
|
+ CS_LOW;
|
|
|
|
|
+ SPI_SendByte(PAGE_PROG); // 页编程指令
|
|
|
|
|
+ SPI_SendByte(addr >> 16); // 地址高8位
|
|
|
|
|
+ SPI_SendByte(addr >> 8); // 地址中8位
|
|
|
|
|
+ SPI_SendByte(addr); // 地址低8位
|
|
|
|
|
+ for (uint16_t i = 0; i < len; i++) {
|
|
|
|
|
+ SPI_SendByte(data[i]); // 发送数据
|
|
|
|
|
+ }
|
|
|
|
|
+ CS_HIGH;
|
|
|
|
|
+ W25Q64_WaitBusy(); // 等写入完成
|
|
|
|
|
+}
|
|
|
|
|
+```
|
|
|
|
|
+
|
|
|
|
|
+> **注意**:W25Q64 不支持"写覆盖"——必须先擦除再写。最小擦除单位是扇区(4KB)。
|
|
|
|
|
+> 页编程不能跨页(256 字节边界)。如果数据跨页,需要分多次写入。
|
|
|
|
|
+
|
|
|
|
|
+---
|
|
|
|
|
+
|
|
|
## FSMC 总线
|
|
## FSMC 总线
|
|
|
|
|
|
|
|
FSMC = Flexible Static Memory Controller(灵活的静态存储器控制器)。
|
|
FSMC = Flexible Static Memory Controller(灵活的静态存储器控制器)。
|
|
|
|
|
|
|
|
### 地址映射
|
|
### 地址映射
|
|
|
|
|
|
|
|
-| Bank | 地址范围 | 目标设备 |
|
|
|
|
|
|
|
+| Bank | 地址范围 | 容量 | 目标设备 |
|
|
|
|
|
+|------|---------|------|---------|
|
|
|
|
|
+| **Bank1** | **0x60000000~0x6FFFFFFF** | **256MB** | **NOR Flash / PSRAM / SRAM / LCD** |
|
|
|
|
|
+| Bank2 | 0x70000000~0x7FFFFFFF | 128MB | NAND Flash |
|
|
|
|
|
+| Bank3 | 0x80000000~0x8FFFFFFF | 128MB | NAND Flash |
|
|
|
|
|
+| Bank4 | 0x90000000~0x9FFFFFFF | 128MB | PC Card |
|
|
|
|
|
+
|
|
|
|
|
+Bank1 分为 4 个子区(片选 NE1~NE4),各 64MB:
|
|
|
|
|
+
|
|
|
|
|
+| 子区 | 片选引脚 | 地址范围 |
|
|
|
|------|---------|---------|
|
|
|------|---------|---------|
|
|
|
-| Bank1 | 0x60000000~0x6FFFFFFF | NOR Flash / PSRAM / SRAM / LCD |
|
|
|
|
|
-| Bank2 | 0x70000000~0x7FFFFFFF | NAND Flash |
|
|
|
|
|
-| Bank3 | 0x80000000~0x8FFFFFFF | NAND Flash |
|
|
|
|
|
-| Bank4 | 0x90000000~0x9FFFFFFF | PC Card |
|
|
|
|
|
|
|
+| NE1 | FSMC_NE1 | 0x60000000~0x63FFFFFF(最常用) |
|
|
|
|
|
+| NE2 | FSMC_NE2 | 0x64000000~0x67FFFFFF |
|
|
|
|
|
+| NE3 | FSMC_NE3 | 0x68000000~0x6BFFFFFF |
|
|
|
|
|
+| NE4 | FSMC_NE4 | 0x6C000000~0x6FFFFFFF |
|
|
|
|
|
|
|
|
-Bank1 又分为 4 个子区(NE1~NE4),每个 64MB:
|
|
|
|
|
-```
|
|
|
|
|
-NE1: 0x60000000~0x63FFFFFF
|
|
|
|
|
-NE2: 0x64000000~0x67FFFFFF
|
|
|
|
|
-NE3: 0x68000000~0x6BFFFFFF
|
|
|
|
|
-NE4: 0x6C000000~0x6FFFFFFF
|
|
|
|
|
-```
|
|
|
|
|
|
|
+### FSMC 信号线
|
|
|
|
|
+
|
|
|
|
|
+| 信号 | 功能 |
|
|
|
|
|
+|------|------|
|
|
|
|
|
+| FSMC_A[25:0] | 地址总线 |
|
|
|
|
|
+| FSMC_D[15:0] | 数据总线(16 位模式) |
|
|
|
|
|
+| FSMC_NE[4:1] | 片选(低电平有效) |
|
|
|
|
|
+| FSMC_NOE | 读使能(低电平有效) |
|
|
|
|
|
+| FSMC_NWE | 写使能(低电平有效) |
|
|
|
|
|
|
|
|
### FSMC 关键寄存器
|
|
### FSMC 关键寄存器
|
|
|
|
|
|
|
|
| 寄存器 | 功能 |
|
|
| 寄存器 | 功能 |
|
|
|
|--------|------|
|
|
|--------|------|
|
|
|
-| BC1~4 | 存储片控制寄存器(MTYP设备类型、MWID数据宽度) |
|
|
|
|
|
-| BT1~4 | 存储片时序寄存器(ADDSET地址建立、DATAST数据保持) |
|
|
|
|
|
-
|
|
|
|
|
-**LCD 应用**:将 LCD 的 D0~D15 数据线接 FSMC_D0~D15,RS 接 A0(地址线),
|
|
|
|
|
-这样:
|
|
|
|
|
-- 写 `*(uint16_t*)0x60000000 = data` → 写命令(RS=0)
|
|
|
|
|
-- 写 `*(uint16_t*)0x60020000 = data` → 写数据(RS=1,A0=1)
|
|
|
|
|
-
|
|
|
|
|
-> **参考**:参考手册 §19(FSMC 寄存器描述)
|
|
|
|
|
|
|
+| **BCRx** | SRAM/NOR 控制寄存器(MTYP 设备类型、MWID 数据宽度) |
|
|
|
|
|
+| **BTRx** | SRAM/NOR 时序寄存器(ADDSET 地址建立时间、DATAST 数据保持时间) |
|
|
|
|
|
+| BWTRx | 写时序寄存器(写操作时序,与读独立) |
|
|
|
|
|
+
|
|
|
|
|
+**控制寄存器 (BCR1~BCR4)**:
|
|
|
|
|
+| 位 | 名称 | 说明 |
|
|
|
|
|
+|----|------|------|
|
|
|
|
|
+| 0 | MBKEN | 存储区使能 |
|
|
|
|
|
+| 1:2 | MTYP | 设备类型:00=SRAM, 01=PSRAM, 10=NOR |
|
|
|
|
|
+| 3:4 | MWID | 数据宽度:00=8位, 01=16位 |
|
|
|
|
|
+| 12 | WEN | 写使能 |
|
|
|
|
|
+| 14 | FACCEN | Flash 访问使能(NOR Flash 时用) |
|
|
|
|
|
+
|
|
|
|
|
+**时序寄存器 (BTR1~BTR4)**:
|
|
|
|
|
+| 位 | 名称 | 说明 |
|
|
|
|
|
+|----|------|------|
|
|
|
|
|
+| 0:3 | ADDSET | 地址建立时间(0~15个HCLK周期) |
|
|
|
|
|
+| 8:15 | DATAST | 数据保持时间(1~255个HCLK周期) |
|
|
|
|
|
+| 16:19 | BUSTURN | 总线周转时间 |
|
|
|
|
|
+
|
|
|
|
|
+### FSMC + LCD 应用
|
|
|
|
|
+
|
|
|
|
|
+将 LCD 连接到 FSMC,利用地址线 A0 区分命令和数据:
|
|
|
|
|
+```
|
|
|
|
|
+写 (uint16_t *)0x60000000 = cmd → RS=0 → 写命令
|
|
|
|
|
+写 (uint16_t *)0x60020000 = data → RS=1(A0=1) → 写数据
|
|
|
|
|
+```
|
|
|
|
|
|
|
|
---
|
|
---
|
|
|
|
|
|
|
|
## 核心速查表
|
|
## 核心速查表
|
|
|
|
|
|
|
|
-| SPI操作 | 代码 |
|
|
|
|
|
-|---------|------|
|
|
|
|
|
-| 使能SPI | `CR1 |= SPE` |
|
|
|
|
|
-| 发送1字节 | `DR = byte; while(!(SR & TXE))` |
|
|
|
|
|
-| 接收1字节 | `while(!(SR & RXNE)); byte = DR` |
|
|
|
|
|
-| 波特率分频 | `CR1.BR[2:0]` = fPCLK/2~(fPCLK/256) |
|
|
|
|
|
|
|
+| SPI 操作 | 软件模拟 | 硬件 SPI |
|
|
|
|
|
+|---------|---------|---------|
|
|
|
|
|
+| 初始化 | GPIO 开漏/推挽 | `CR1 |= MSTR + SPE` + GPIO 复用 |
|
|
|
|
|
+| 发 1 字节 | 8 次 GPIO 位操作 | `DR = byte; while(!RXNE); val = DR` |
|
|
|
|
|
+| 片选 | GPIO 位操作 | 同(NSS 由软件控制) |
|
|
|
|
|
+| 速度调整 | 修改延时 | `CR1.BR[2:0]` |
|
|
|
|
|
|
|
|
-| FSMC操作 | 代码 |
|
|
|
|
|
|
|
+| FSMC 操作 | 代码 |
|
|
|
|----------|------|
|
|
|----------|------|
|
|
|
-| 写SRAM | `*(uint16_t*)0x60000000 = data` |
|
|
|
|
|
-| 读SRAM | `data = *(uint16_t*)0x60000000` |
|
|
|
|
|
-| 写LCD命令 | `*(uint16_t*)0x60000000 = cmd` |
|
|
|
|
|
-| 写LCD数据 | `*(uint16_t*)0x60020000 = data` |
|
|
|
|
|
|
|
+| 写 SRAM | `*(uint16_t *)0x60000000 = data` |
|
|
|
|
|
+| 读 SRAM | `data = *(uint16_t *)0x60000000` |
|
|
|
|
|
+| 写 LCD 命令 | `*(uint16_t *)0x60000000 = cmd` |
|
|
|
|
|
+| 写 LCD 数据 | `*(uint16_t *)0x60020000 = data` |
|
|
|
|
|
|
|
|
## 常见问题与避坑
|
|
## 常见问题与避坑
|
|
|
|
|
|
|
|
-1. **SPI 接收数据为 0xFF** → 检查 MISO 连接、从机是否选中(CS)、时钟是否匹配
|
|
|
|
|
-2. **W25Q64 写入失败** → 必须先发 WREN(0x06)、扇区必须先擦除(不能写覆盖)
|
|
|
|
|
-3. **FSMC 读写时序不对** → 参考 SRAM/LCD 数据手册的时序参数配置 ADDSET/DATAST
|
|
|
|
|
-4. **多个 SPI 设备冲突** → 各设备独立 CS、SCK/MOSI/MISO 共享
|
|
|
|
|
-5. **FSMC 地址线错位** → 16位模式下 HADDR[0] 不连接到 FSMC_A[0](内部是对齐的)
|
|
|
|
|
|
|
+1. **SPI 接收数据为 0xFF** → MISO 连接断开、从机未选中(CS 拉低后才有输出)、从机忙于内部操作
|
|
|
|
|
+2. **W25Q64 写入失败** → 每次写操作前必须发 WREN(0x06);扇区必须事先擦除(Flash 不能写覆盖)
|
|
|
|
|
+3. **FSMC 读写时序不对** → 查 SRAM/LCD 数据手册的时序参数(ADDSET 和 DATAST),STM32 的 HCLK 对应关系
|
|
|
|
|
+4. **软件 SPI 速度太慢** → 软件 SPI 受限于 GPIO 翻转速度(约 2~4MHz),大批量数据建议用硬件 SPI
|
|
|
|
|
+5. **NSS 软件管理模式** → 多从机时必须用 SSM=1 软件管理 NSS,否则硬件自动管理可能产生冲突
|