ESMC — 外部串行存储器控制器(QSPI Flash 读写)
固件库: PY32F403_Firmware V1.4.5
芯片: PY32F403
源文件: PUYA/PY32F403_Firmware_V1.4.5/Projects/PY32F403-STK/Example/ESMC/ESMC_ReadWrite_Polling/Src/main.c
功能简介
PY32F403 带有 ESMC(External Serial Memory Controller,外部串行存储器控制器),可硬件驱动 QSPI / Octo-SPI / HyperBus 等串行存储器。相比用 GPIO 软件模拟,ESMC 由硬件时序控制器直接发指令/地址/数据,支持单线、双线、四线(Quad)模式,速度快且 CPU 负担小。
本示例演示用 ESMC 驱动板载 P25Q64(容量 8MB 的 QSPI NOR Flash),完成完整写读流程:
- 准备 4096 字节测试数据(0,1,2,…,255 循环);
- ESMC 初始化(时钟分频、单线模式、关闭双 Flash);
- 写使能 → 等状态寄存器 BUSY 清零;
- 扇区擦除(4KB);
- 开启 QE(Quad Enable) 位,使能四线模式;
- 四线页编程(按 256 字节/页写入);
- 四线快读 回读;
- 校验一致则点亮绿灯。
这是 F403 的“特色外设”示例:把外置 Flash 当大容量存储用,典型用于字库、图片、音频、固件备份。
硬件准备
- PY32F403 开发板(板载或外接 P25Q64 QSPI Flash,接 ESMC 的 IO0~IO3 / CLK / CS)
- 板载 LED(指示结果)
- 无需串口,结果用 LED 表示
完整代码(核心流程)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
|
#include "main.h"
#define BUFFERSIZE 4096
ESMC_HandleTypeDef hesmc;
ESMC_InitTypeDef ESMC_initTypeDef;
uint8_t aRxBuffer[BUFFERSIZE];
uint8_t aTxBuffer[BUFFERSIZE];
int main(void)
{
uint32_t i = 0, cnt = 0, progAddr = 0;
uint8_t *pData = NULL;
uint32_t address = 0;
HAL_Init();
BSP_LED_Init(LED_GREEN);
for (i = 0; i < BUFFERSIZE; i++) aTxBuffer[i] = i % 256; /* 测试数据 */
APP_EsmcInit(); /* 1. ESMC 初始化 */
APP_WriteEnable(); /* 2. 写使能 */
while (APP_GetFlashStatus(0x05) & 0x01) {} /* 等 BUSY 清 */
APP_EraseSector(address); /* 3. 扇区擦除 */
while (APP_GetFlashStatus(P25Q64_READ_STATUS_REG_CMD) & 0x01) {}
/* 4. 开启 QE 位(四线使能) */
flashStatus = APP_GetFlashStatus(P25Q64_READ_STATUS_REG_1_CMD);
flashStatus |= 0x02;
APP_WriteEnable();
while (APP_GetFlashStatus(0x05) & 0x01) {}
APP_SetFlashStatus(flashStatus, P25Q64_WRITE_STATUS_REG_1_CMD);
while (APP_GetFlashStatus(P25Q64_READ_STATUS_REG_CMD) & 0x01) {}
/* 5. 分页编程(每页 P25Q64_PAGE_SIZE 字节) */
cnt = BUFFERSIZE; progAddr = address; pData = aTxBuffer;
while (cnt / P25Q64_PAGE_SIZE > 0)
{
APP_PageProgram(pData, progAddr, P25Q64_PAGE_SIZE);
while (APP_GetFlashStatus(P25Q64_READ_STATUS_REG_CMD) & 0x01) {}
cnt -= P25Q64_PAGE_SIZE;
progAddr += P25Q64_PAGE_SIZE;
pData += P25Q64_PAGE_SIZE;
}
if (cnt > 0) { APP_PageProgram(pData, progAddr, cnt);
while (APP_GetFlashStatus(P25Q64_READ_STATUS_REG_CMD) & 0x01) {} }
/* 6. 读取并校验 */
APP_DataRead(aRxBuffer, address, BUFFERSIZE);
if (HAL_OK != APP_DataCheck(aRxBuffer, aTxBuffer, BUFFERSIZE))
{
BSP_LED_Off(LED_GREEN);
APP_ErrorHandler();
}
BSP_LED_On(LED_GREEN); /* 成功:绿灯 */
while (1) {}
}
/* ESMC 初始化:开时钟,配分频/模式 */
static void APP_EsmcInit(void)
{
__HAL_RCC_ESMC_CLK_ENABLE();
hesmc.Instance = ESMC;
ESMC_initTypeDef.ClockPrescaler = 0x8; /* 时钟分频 */
ESMC_initTypeDef.ClockMode = ESMC_CLOCK_MODE_0;
ESMC_initTypeDef.DualFlash = ESMC_DUALFLASH_DISABLE;
hesmc.Init = ESMC_initTypeDef;
HAL_ESMC_Init(&hesmc);
}
/* 发一条 QSPI 指令:先 HAL_ESMC_Command 描述“指令/地址/数据”格式,再收发数据 */
static void APP_PageProgram(uint8_t *pData, uint32_t addr, uint32_t nBytes)
{
ESMC_CommandTypeDef cmdStc = {0};
APP_WriteEnable();
cmdStc.TransferFormat = ESMC_TRANSFER_FORMAT_QUAD; /* 四线传输 */
cmdStc.InstructionMode = ESMC_INSTRUCTION_SINGLE_LINE; /* 指令仍单线 */
cmdStc.Instruction = P25Q64_QUAD_INPUT_PAGE_PROG_CMD;
cmdStc.AddressMode = ESMC_ADDRESS_SINGLE_LINE;
cmdStc.AddressSize = ESMC_ADDRESS_24_BITS;
cmdStc.Address = addr;
cmdStc.DataMode = ESMC_DATA_WRITE;
cmdStc.NbData = nBytes;
cmdStc.CSPinSel = ESMC_SELECT_PIN_CS0;
HAL_ESMC_Command(&hesmc, &cmdStc, 0x0000FFFF);
HAL_ESMC_Transmit(&hesmc, pData, 0x0000FFFF); /* 送出数据 */
}
|
代码分段讲解
1. ESMC 的命令模型
ESMC 把一次 Flash 访问拆成 “命令描述 + 数据传输” 两步:
HAL_ESMC_Command():用 ESMC_CommandTypeDef 描述本次操作的指令码、指令线数(单/双/四)、是否带地址、地址位数、数据方向(读/写)、数据线数、数据长度、CS 引脚等。
- 随后的
HAL_ESMC_Transmit / Receive:按上面的描述真正搬数据。
这种“描述符 + 传输”模型非常灵活,一套 API 即可适配各种 QSPI 器件。
2. 单线 vs 四线
- 指令阶段:几乎所有 Flash 都用单线发指令码(
INSTRUCTION_SINGLE_LINE),成本低、兼容好。
- 数据阶段:开启 QE 后可用
ESMC_TRANSFER_FORMAT_QUAD 四线读写,速度翻多倍。
- 读数据用
P25Q64_1I4O_FAST_READ_CMD(指令单线、数据四线快读),写用 P25Q64_QUAD_INPUT_PAGE_PROG_CMD。
3. 写使能与 BUSY 轮询
QSPI Flash 有写保护:任何擦/写前必须先发 Write Enable(06h)指令;操作期间状态寄存器 BUSY 位为 1,必须轮询等到清零(while (GetFlashStatus & 0x01))才能下一步。这是所有 SPI NOR Flash 的通用约定。
4. QE 位与页编程
- QE(Quad Enable):状态寄存器 1 的 bit1,置 1 后 IO2/IO3 才用于四线传输;首次使用需写状态寄存器开启。
- 页编程:P25Q64 每页 256 字节,一次最多写一页;跨页要分多次,且每次写前地址所在扇区需已擦除。
实验现象
- 程序完成“擦除→写→读→校验”后 绿灯常亮;
- 任一环节失败则卡在
APP_ErrorHandler(绿灯不亮或熄灭),可结合调试定位。
常见问题
-
绿灯不亮 / 校验失败?
- 确认板载 Flash 型号确为 P25Q64(指令宏
P25Q64_* 匹配);不同型号指令/页大小不同。
- 确认 CS/CLK/IO0~IO3 接线与 ESMC 引脚映射一致。
- QE 位未成功开启会导致四线读写失败——检查
APP_SetFlashStatus 流程。
-
写不进去?
- 写前必须
WriteEnable 且等待上一次 BUSY 清零;目标区必须已擦除(全 0xFF)。
-
想用 DMA/中断?
- 同目录有
ESMC_ReadWrite_DMA、ESMC_ReadWriteQuad_IT,大批量数据用 DMA 更高效。
小结
- ESMC 通过“命令描述符 + 传输”统一驱动 QSPI/Octo 串行存储器。
- QSPI Flash 写流程固定:WriteEnable → 等 BUSY → 擦除/编程 → 等 BUSY → 校验。
- 开 QE 后用四线模式大幅提速;指令通常仍单线发送。
拓展应用
- 存放字库/图片/UI 资源,MCU 内部 Flash 只放代码。
- 做“外部数据记录仪”(日志、采样缓存)。
- 配合 XIP(片上执行)把代码放到外置 Flash 运行,扩展程序空间。
- DMA 方式搬运大块音频/视频资源到 LCD/Codec。