目标:SPI1 驱动 W25Q64(外置 Flash),实现按键切换 LED 状态并断电保存
SPI 是 全双工、同步 串行通信协议,核心四线制:
数据传输过程中,一个时钟周期同时完成一位数据的发送和接收:
MOSI: 主机数据线 → 每个 SCK 周期移出一位
MISO: 从机数据线 → 每个 SCK 周期移出一位
全双工:收和发同时进行,互不干扰
SPI 有 5 个可配置参数(铁头山羊课程中的 5 参数模型):
| # | 参数 | 说明 |
|---|---|---|
| 1 | 波特率 | SCK 频率,由主机分频产生 |
| 2 | 数据位顺序 | MSB First / LSB First |
| 3 | 数据位长度 | 8 位 / 16 位 |
| 4 | 时钟极性 (CPOL) | 空闲时 SCK 电平:0=低, 1=高 |
| 5 | 时钟相位 (CPHA) | 数据采样边沿:0=第一边沿, 1=第二边沿 |
CPOL(时钟极性):
CPHA(时钟相位) —— 数据传输分为"发送"和"采集"两个阶段:
4 种模式的完整时序:
| 模式 | CPOL | CPHA | 说明 |
|---|---|---|---|
| Mode 0 | 0 | 0 | 空闲低 → 上升沿采样(最常用) |
| Mode 1 | 0 | 1 | 空闲低 → 下降沿采样 |
| Mode 2 | 1 | 0 | 空闲高 → 下降沿采样 |
| Mode 3 | 1 | 1 | 空闲高 → 上升沿采样 |
| 函数 | 说明 |
|---|---|
HAL_SPI_Transmit(&hspi, pData, Size, Timeout) |
发送数据 |
HAL_SPI_Receive(&hspi, pData, Size, Timeout) |
接收数据(内部发 0xFF 产生时钟) |
HAL_SPI_TransmitReceive(&hspi, pTx, pRx, Size, Timeout) |
同时收发 |
/* CS 控制 */
#define W25Q64_CS_LOW() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_RESET)
#define W25Q64_CS_HIGH() HAL_GPIO_WritePin(GPIOA, GPIO_PIN_4, GPIO_PIN_SET)
/* 写使能 (0x06) */
static void W25Q64_WriteEnable(void)
{
uint8_t cmd = 0x06;
W25Q64_CS_LOW();
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
}
/* 读状态寄存器 (0x05) */
static uint8_t W25Q64_ReadSR(void)
{
uint8_t cmd[2] = {0x05, 0xFF};
uint8_t rx[2] = {0};
W25Q64_CS_LOW();
HAL_SPI_TransmitReceive(&hspi1, cmd, rx, 2, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
return rx[1];
}
/* 等待 BUSY 位清零(读状态寄存器 bit0) */
static void W25Q64_WaitBusy(void)
{
uint8_t sr;
do {
uint8_t cmd[2] = {0x05, 0xFF}; // 读状态寄存器 1
uint8_t rx[2] = {0};
W25Q64_CS_LOW();
HAL_SPI_TransmitReceive(&hspi1, cmd, rx, 2, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
sr = rx[1];
} while (sr & 0x01); // bit0=1 表示正在忙
}
/* 扇区擦除 (4KB, 0x20) — 地址自动按 4KB 对齐 */
static void W25Q64_SectorErase(uint32_t addr)
{
uint8_t cmd[4] = {0x20, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF};
W25Q64_WriteEnable();
W25Q64_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
W25Q64_WaitBusy(); // 比固定延时更可靠
}
/* 页写入 (256字节以内, 0x02) — 地址必须按 256 字节对齐 */
static void W25Q64_PageProgram(uint32_t addr, uint8_t *data, uint16_t len)
{
uint8_t cmd[4] = {0x02, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF};
if (len > 256) len = 256; // 页编程最多 256 字节
W25Q64_WriteEnable();
W25Q64_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
HAL_SPI_Transmit(&hspi1, data, len, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
W25Q64_WaitBusy();
}
/* 读取数据 (0x03) */
static void W25Q64_ReadData(uint32_t addr, uint8_t *buf, uint16_t len)
{
uint8_t cmd[4] = {0x03, (addr>>16)&0xFF, (addr>>8)&0xFF, addr&0xFF};
W25Q64_CS_LOW();
HAL_SPI_Transmit(&hspi1, cmd, 4, HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi1, buf, len, HAL_MAX_DELAY);
W25Q64_CS_HIGH();
}
uint8_t pre_key = 1, cur_key = 1;
uint8_t led_status;
led_status = LoadLEDState(); // 上电从 Flash 读取
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13,
led_status ? GPIO_PIN_SET : GPIO_PIN_RESET);
while (1)
{
pre_key = cur_key;
cur_key = (HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_0) == GPIO_PIN_RESET) ? 0 : 1;
if (pre_key != cur_key)
{
HAL_Delay(10);
if (cur_key == 1) // 松开沿
{
led_status = !led_status;
HAL_GPIO_WritePin(GPIOC, GPIO_PIN_13,
led_status ? GPIO_PIN_SET : GPIO_PIN_RESET);
SaveLEDState(led_status); // 保存到 Flash
}
}
}