简介:STM32F429是一款基于Cortex-M4内核的高性能低功耗微控制器,支持通过SPI接口与外部设备通信。本文详细讲解如何利用STM32F429通过SPI与SDRAM和W25Q128进行大量数据的读写操作。内容涵盖SPI协议基础、STM32F429的SPI和FMC接口配置、SDRAM与W25Q128的通信时序,以及使用HAL或LL库开发嵌入式程序的实现方法。项目文件包含完整示例代码,适用于开发者快速掌握大容量数据在不同存储设备间的高效迁移技巧。
1. STM32F429微控制器架构概述
STM32F429是ST公司基于ARM Cortex-M4内核的高性能微控制器,广泛应用于工业控制、人机交互及物联网等领域。其架构优势在于集成了浮点运算单元(FPU),支持DSP指令集,主频高达180MHz,显著提升复杂算法的执行效率。
1.1 Cortex-M4内核特性与优势
Cortex-M4内核具备高效的32位RISC架构,支持Thumb-2指令集,优化了中断响应机制,具备低延迟中断处理能力。其内置的嵌套向量中断控制器(NVIC)支持最多240个可配置优先级中断,适合多任务实时系统。
特性 | 描述 |
---|---|
架构 | 32位RISC |
主频 | 最高180MHz |
FPU | 单精度浮点运算单元 |
DSP指令 | 支持 |
中断控制器 | NVIC(支持240个中断) |
1.2 内存结构与主频配置
STM32F429内置高达2MB的Flash和256KB的SRAM,并支持外部存储器扩展。其系统结构采用多层AHB总线矩阵,实现CPU、DMA、FMC等模块的并行访问,减少总线冲突。
主频配置通过RCC(Reset and Clock Control)模块实现,核心时钟(SYSCLK)可由HSE(高速外部晶振)、HSI(高速内部RC)或PLL(锁相环)提供。典型配置如下:
RCC_OscInitTypeDef RCC_OscInitStruct = {0};
RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
RCC_OscInitStruct.HSEState = RCC_HSE_ON;
RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
RCC_OscInitStruct.PLL.PLLM = 8;
RCC_OscInitStruct.PLL.PLLN = 360; // 8MHz * 45 = 360MHz VCO
RCC_OscInitStruct.PLL.PLLP = RCC_PLLP_DIV2; // 360/2 = 180MHz
HAL_RCC_OscConfig(&RCC_OscInitStruct);
1.3 外设资源分布与多存储设备支持
STM32F429具备丰富的外设接口,包括SPI、I2C、USB OTG、Ethernet MAC、LCD-TFT控制器等,尤其突出的是其支持FMC(Flexible Memory Controller)和多个SPI接口,使得其可同时连接SDRAM、NOR Flash、SPI Flash等多类存储设备。
- FMC接口 :用于连接并行接口的外部存储器,如SDRAM、SRAM、NAND Flash等;
- SPI接口 :用于连接高速串行存储器,如W25Q128等SPI Flash;
其多总线架构允许FMC与SPI模块并发运行,配合DMA技术,实现高吞吐量的数据搬运。
1.4 FMC与SPI模块在系统架构中的角色定位
在多存储设备系统中,FMC负责高速并行访问外部SDRAM,适用于图像缓存、大容量数据暂存等场景;SPI模块则负责与SPI Flash等串行存储器通信,适用于固件存储、参数保存等需求。
例如:
- FMC角色 :将图像帧缓冲区映射到外部SDRAM,供LCD控制器直接读取;
- SPI角色 :从W25Q128读取系统配置参数或固件更新包;
两者结合,使得STM32F429在资源受限的嵌入式系统中能够实现多存储器协同工作,提升系统灵活性与性能。后续章节将深入探讨SPI协议、FMC接口配置及多存储器协同操作的实现方法。
2. SPI协议原理及四种工作模式
SPI(Serial Peripheral Interface)是一种广泛应用于嵌入式系统中的高速同步串行通信协议,尤其适合主从设备之间的点对点数据传输。它具有结构简单、通信速率高、接口灵活等优点,广泛应用于ADC、DAC、Flash、传感器等外设与主控制器之间的通信。本章将从SPI协议的基本构成出发,逐步深入分析其四种工作模式、波特率计算与通信稳定性策略,帮助读者全面理解SPI通信机制。
2.1 SPI协议的基本构成
SPI协议基于主从结构,通常由一个主设备(Master)和一个或多个从设备(Slave)组成。通信过程中,主设备控制时钟(SCK),并决定数据传输的速率和时序。
2.1.1 主从模式与通信信号(SCK、MOSI、MISO、CS)
SPI通信主要依赖以下四个信号线:
信号名称 | 含义 | 方向 | 描述 |
---|---|---|---|
SCK | Serial Clock | Output(主) | 主设备提供的时钟信号 |
MOSI | Master Out Slave In | Output(主) | 主设备发送数据 |
MISO | Master In Slave Out | Input(主) | 从设备发送数据 |
CS(SS) | Chip Select | Output(主) | 选择从设备使能信号 |
逻辑分析:
- SCK 是整个SPI通信的节拍器,决定了数据传输的速率和同步方式。
- MOSI 和 MISO 分别用于主设备向从设备发送数据、从设备向主设备发送数据,二者为全双工通信。
- CS 为低电平时,从设备被选中,开始通信;高电平时,从设备不响应。
2.1.2 数据传输机制与同步时序
SPI通信是基于同步时序的串行传输,数据在SCK的上升沿或下降沿进行采样和输出,具体取决于配置参数(CPOL和CPHA)。
示例:SPI通信数据传输代码(使用LL库)
#include "stm32f4xx_ll_spi.h"
void SPI_WriteByte(SPI_TypeDef* SPIx, uint8_t data) {
// 等待发送缓冲区空
while (!LL_SPI_IsActiveFlag_TXE(SPIx));
// 写入数据到DR寄存器
LL_SPI_TransmitData8(SPIx, data);
// 等待传输完成
while (!LL_SPI_IsActiveFlag_RXNE(SPIx));
// 读取接收到的数据(用于清空接收寄存器)
uint8_t dummy = LL_SPI_ReceiveData8(SPIx);
}
代码逐行分析:
-
while (!LL_SPI_IsActiveFlag_TXE(SPIx));
:等待SPI发送缓冲区为空,确保可以写入新数据。 -
LL_SPI_TransmitData8(SPIx, data);
:将一字节数据写入SPI数据寄存器。 -
while (!LL_SPI_IsActiveFlag_RXNE(SPIx));
:等待接收缓冲区非空,即接收完成。 -
uint8_t dummy = LL_SPI_ReceiveData8(SPIx);
:读取接收寄存器以清除标志,避免下次读取错误。
时序示意图(mermaid流程图)
sequenceDiagram
participant Master
participant Slave
Master->>Slave: CS低电平使能从设备
loop 每个bit
Master->>Slave: SCK上升沿或下降沿触发
Master->>Slave: MOSI发送数据位
Slave-->>Master: MISO返回数据位
end
Master->>Slave: CS恢复高电平,通信结束
2.2 SPI的四种工作模式
SPI的四种工作模式由两个关键参数决定:CPOL(Clock Polarity)和CPHA(Clock Phase)。
2.2.1 CPOL与CPHA参数定义
模式 | CPOL | CPHA | 说明 |
---|---|---|---|
Mode 0 | 0 | 0 | SCK空闲为低电平,数据在上升沿采样 |
Mode 1 | 0 | 1 | SCK空闲为低电平,数据在下降沿采样 |
Mode 2 | 1 | 0 | SCK空闲为高电平,数据在下降沿采样 |
Mode 3 | 1 | 1 | SCK空闲为高电平,数据在上升沿采样 |
参数说明:
- CPOL 决定SCK在空闲状态的电平:
- CPOL=0:SCK空闲为低电平
- CPOL=1:SCK空闲为高电平
- CPHA 决定采样点:
- CPHA=0:在SCK第一个边沿(上升或下降)采样数据
- CPHA=1:在SCK第二个边沿采样数据
2.2.2 模式0至模式3的切换与应用场景
不同从设备支持的SPI模式不同,需根据从设备规格书配置主设备的CPOL和CPHA参数。
示例:配置SPI为Mode 3(CPOL=1, CPHA=1)
#include "stm32f4xx_hal.h"
void MX_SPI1_Init(SPI_HandleTypeDef *hspi) {
hspi->Instance = SPI1;
hspi->Init.Mode = SPI_MODE_MASTER;
hspi->Init.Direction = SPI_DIRECTION_2LINES;
hspi->Init.DataSize = SPI_DATASIZE_8BIT;
hspi->Init.CLKPolarity = SPI_POLARITY_HIGH; // CPOL = 1
hspi->Init.CLKPhase = SPI_PHASE_2EDGE; // CPHA = 1
hspi->Init.NSS = SPI_NSS_SOFT;
hspi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
hspi->Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi->Init.TIMode = SPI_TIMODE_DISABLE;
hspi->Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
if (HAL_SPI_Init(hspi) != HAL_OK) {
// Initialization Error
}
}
代码逻辑分析:
-
hspi->Init.CLKPolarity = SPI_POLARITY_HIGH;
:设置SCK空闲状态为高电平(CPOL=1)。 -
hspi->Init.CLKPhase = SPI_PHASE_2EDGE;
:设置在SCK第二个边沿采样数据(CPHA=1)。 - 这两个参数共同决定了SPI工作在Mode 3。
应用场景:
- Mode 0 :常用于ADC、DAC等设备。
- Mode 1 :部分Flash、传感器使用。
- Mode 2 :某些通信模块使用。
- Mode 3 :W25Q系列Flash芯片常用。
2.3 SPI通信的速率与数据完整性
SPI通信速率由主设备的SCK频率决定,但实际速率受从设备最大支持频率限制。
2.3.1 波特率的计算与主设备时钟配置
SPI的波特率(SCK频率)由主设备的系统时钟(如STM32的APB2或APB1)和预分频系数决定。
示例:STM32F429 SPI波特率配置
假设APB2时钟为84 MHz,SPI时钟预分频设为16:
hspi->Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_16;
计算公式如下:
SCK频率 = APB2_CLK / 分频系数
= 84 MHz / 16
= 5.25 MHz
参数说明:
- BaudRatePrescaler 可选值为2、4、8、16、32、64、128、256。
- 需确保SCK频率不超过从设备的最大支持频率。
2.3.2 常见通信问题与解决策略
常见问题及解决方法:
问题 | 原因 | 解决策略 |
---|---|---|
数据错乱 | CPOL或CPHA配置错误 | 核对从设备手册,正确设置模式 |
通信失败 | CS未正确拉低 | 检查GPIO控制逻辑 |
速率不匹配 | 主设备SCK频率过高 | 降低预分频值或使用DMA |
数据丢失 | 未使用DMA或中断 | 启用DMA或中断传输 |
噪声干扰 | 引线过长或未屏蔽 | 缩短引线,加屏蔽或使用差分信号 |
示例:使用DMA提升SPI传输效率
#include "stm32f4xx_hal_dma.h"
void SPI_DMA_Transmit(SPI_HandleTypeDef *hspi, uint8_t *pData, uint16_t Size) {
// 启动DMA发送
HAL_SPI_Transmit_DMA(hspi, pData, Size);
}
逻辑分析:
- 使用DMA可以避免CPU频繁中断,提升传输效率。
-
HAL_SPI_Transmit_DMA()
将数据通过DMA发送,主程序可继续执行其他任务。
流程图:SPI DMA通信流程
graph TD
A[初始化SPI和DMA] --> B[准备发送数据缓冲区]
B --> C[调用HAL_SPI_Transmit_DMA]
C --> D[DMA自动搬运数据到SPI寄存器]
D --> E[SPI发送数据到从设备]
E --> F[传输完成,触发DMA中断]
F --> G[处理中断回调函数]
本章系统地讲解了SPI协议的基本构成、四种工作模式的原理与配置方法,以及波特率计算与通信稳定性策略。通过代码示例、参数说明和图表展示,帮助读者深入理解SPI通信的底层机制和实际应用。下一章将继续深入讲解STM32F429中SPI接口的寄存器配置与软件实现方法。
3. STM32F429 SPI接口配置(CPOL、CPHA、波特率等)
3.1 SPI外设寄存器结构与功能
3.1.1 控制寄存器(SPI_CR1、SPI_CR2)
在STM32F429中,SPI模块的核心控制逻辑通过SPI_CR1和SPI_CR2两个寄存器实现。这两个寄存器分别用于控制SPI的基本模式和高级功能。
SPI_CR1寄存器结构与功能
位段 | 名称 | 功能说明 |
---|---|---|
0 | CPHA | 时钟相位选择:0表示数据在第一个边沿采样,1表示在第二个边沿采样 |
1 | CPOL | 时钟极性选择:0表示空闲时SCK为低电平,1表示为高电平 |
2 | MSTR | 主/从模式选择:1表示主模式,0表示从模式 |
3 | BR[2:0] | 波特率控制位,用于主模式下设置SCK时钟频率 |
7 | LSBFIRST | 数据位传输顺序:1表示先传LSB,0表示先传MSB |
8 | SPE | SPI使能位:1表示使能SPI,0表示关闭 |
11 | SSM | 软件从机管理:1表示使用软件控制NSS引脚,0表示由硬件控制 |
SPI_CR2寄存器功能简介
SPI_CR2主要用于控制中断和DMA功能,其关键位包括:
- RXDMAEN :接收DMA使能
- TXDMAEN :发送DMA使能
- SSI :内部从设备选择(仅在SSM=1时有效)
- ERRIE :错误中断使能
- RXNEIE :接收缓冲区非空中断使能
- TXEIE :发送缓冲区空中断使能
代码示例:手动配置SPI_CR1寄存器
SPI1->CR1 |= (SPI_CR1_CPHA | SPI_CR1_CPOL); // 设置CPHA=1,CPOL=1
SPI1->CR1 |= SPI_CR1_MSTR; // 设置为主模式
SPI1->CR1 &= ~SPI_CR1_LSBFIRST; // MSB first
SPI1->CR1 |= SPI_CR1_SSM; // 软件控制NSS
SPI1->CR1 |= SPI_CR1_SPE; // 启用SPI
逻辑分析:
- 第一行设置CPHA和CPOL参数,配置SPI为模式3(CPOL=1, CPHA=1)
- 第二行设置为主设备模式
- 第三行设置数据传输顺序为MSB优先
- 第四行启用软件控制NSS引脚
- 第五行启用SPI模块
3.1.2 状态寄存器与数据寄存器
SPI模块的状态和数据处理通过两个关键寄存器完成: SPI_SR (状态寄存器)和 SPI_DR (数据寄存器)。
SPI_SR状态寄存器功能位
位段 | 名称 | 功能说明 |
---|---|---|
0 | RXNE | 接收缓冲区非空标志 |
1 | TXE | 发送缓冲区为空标志 |
5 | BSY | 总线忙标志,SPI正在进行通信 |
6 | OVR | 接收溢出标志 |
7 | MODF | 模式错误标志(仅从模式) |
SPI_DR数据寄存器
该寄存器用于读写SPI发送和接收的数据。发送时写入SPI_DR,接收时从中读取。
代码示例:读取SPI状态并发送数据
while(!(SPI1->SR & SPI_SR_TXE)); // 等待发送缓冲区为空
SPI1->DR = 0xA5; // 发送数据
逻辑分析:
- 第一行检查TXE标志,确保发送缓冲区为空
- 第二行将0xA5写入SPI_DR,启动SPI传输
状态流程图(mermaid)
graph TD
A[SPI初始化] --> B{TXE状态?}
B -- 空 --> C[写入SPI_DR]
B -- 非空 --> B
C --> D{BSY状态?}
D -- 忙 --> D
D -- 空闲 --> E[数据发送完成]
3.2 SPI接口的配置流程
3.2.1 配置CPOL和CPHA参数以匹配从设备
SPI的四种模式由CPOL和CPHA两个参数决定,它们分别控制时钟极性和采样边沿。
四种SPI模式定义
模式编号 | CPOL | CPHA | 说明 |
---|---|---|---|
Mode 0 | 0 | 0 | SCK空闲为低,上升沿采样 |
Mode 1 | 0 | 1 | SCK空闲为低,下降沿采样 |
Mode 2 | 1 | 0 | SCK空闲为高,下降沿采样 |
Mode 3 | 1 | 1 | SCK空闲为高,上升沿采样 |
配置代码示例
void SPI_SetMode(SPI_TypeDef* SPIx, uint8_t mode) {
if (mode == 0) {
SPIx->CR1 &= ~(SPI_CR1_CPOL | SPI_CR1_CPHA);
} else if (mode == 1) {
SPIx->CR1 &= ~SPI_CR1_CPOL;
SPIx->CR1 |= SPI_CR1_CPHA;
} else if (mode == 2) {
SPIx->CR1 |= SPI_CR1_CPOL;
SPIx->CR1 &= ~SPI_CR1_CPHA;
} else if (mode == 3) {
SPIx->CR1 |= (SPI_CR1_CPOL | SPI_CR1_CPHA);
}
}
逻辑分析:
- 函数根据传入的
mode
参数,设置CPOL和CPHA的组合 - 使用位操作清除或设置对应的寄存器位
- 确保SPI模式与从设备一致,避免通信错误
3.2.2 设置波特率预分频与主频同步
SPI的通信速率由主时钟分频决定,通过SPI_CR1的BR[2:0]位控制波特率预分频值。
分频系数与主频关系表
BR[2:0] | 分频系数 | 通信速率(假设主频=42MHz) |
---|---|---|
000 | 2 | 21 MHz |
001 | 4 | 10.5 MHz |
010 | 8 | 5.25 MHz |
011 | 16 | 2.625 MHz |
100 | 32 | 1.3125 MHz |
101 | 64 | 656.25 kHz |
110 | 128 | 328.125 kHz |
111 | 256 | 164.0625 kHz |
代码示例:设置SPI波特率
void SPI_SetBaudRate(SPI_TypeDef* SPIx, uint8_t baud_div) {
SPIx->CR1 &= ~SPI_CR1_BR; // 清除原有分频设置
SPIx->CR1 |= (baud_div << 3); // 设置新的分频值
}
逻辑分析:
- 先清除原有的BR位
- 然后将传入的
baud_div
左移3位后写入对应位置 - 分频值范围为0~7,对应8种波特率设置
3.2.3 中断与DMA使能配置
为了提升SPI通信效率,可以启用中断和DMA传输机制。
中断使能配置
- RXNEIE :接收缓冲区非空中断
- TXEIE :发送缓冲区空中断
- ERRIE :错误中断(溢出、模式错误等)
DMA使能配置
- RXDMAEN :启用接收DMA通道
- TXDMAEN :启用发送DMA通道
代码示例:配置DMA与中断
SPI1->CR2 |= (SPI_CR2_RXDMAEN | SPI_CR2_TXDMAEN); // 启用DMA
SPI1->CR2 |= SPI_CR2_RXNEIE; // 启用接收中断
逻辑分析:
- 第一行启用SPI1的DMA接收和发送功能
- 第二行启用接收缓冲区非空中断,用于处理接收数据
3.3 SPI通信的软件实现示例
3.3.1 使用HAL库初始化SPI模块
HAL库提供标准化的SPI初始化接口,适用于快速开发。
初始化结构体配置
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_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_32;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
HAL_SPI_Init(&hspi1);
}
逻辑分析:
-
Mode
设置为主模式 -
CLKPolarity
和CLKPhase
设定为SPI模式3 -
NSS
使用软件控制 -
BaudRatePrescaler
设置为32分频 - 数据大小为8位,MSB优先
代码说明与参数解释
参数 | 说明 |
---|---|
Mode | 主/从模式设置 |
Direction | 通信方向(单线/双线) |
DataSize | 数据位宽(8位或16位) |
CLKPolarity | 时钟极性 |
CLKPhase | 时钟相位 |
NSS | 片选信号方式 |
BaudRatePrescaler | 波特率分频 |
FirstBit | 数据传输顺序 |
TIMode | TI模式支持 |
CRCCalculation | CRC校验功能 |
3.3.2 使用LL库实现低层SPI数据传输
LL库(Low Layer)提供更底层的寄存器级访问方式,适合性能敏感场景。
LL库初始化代码示例
LL_SPI_InitTypeDef SPI_InitStruct;
SPI_InitStruct.TransferDirection = LL_SPI_FULL_DUPLEX;
SPI_InitStruct.Mode = LL_SPI_MODE_MASTER;
SPI_InitStruct.DataWidth = LL_SPI_DATAWIDTH_8BIT;
SPI_InitStruct.ClockPolarity = LL_SPI_POLARITY_HIGH;
SPI_InitStruct.ClockPhase = LL_SPI_PHASE_2EDGE;
SPI_InitStruct.NSS = LL_SPI_NSS_SOFT;
SPI_InitStruct.BaudRate = LL_SPI_BAUDRATEPRESCALER_DIV32;
SPI_InitStruct.BitOrder = LL_SPI_MSB_FIRST;
SPI_InitStruct.CRCCalculation = LL_SPI_CRCCALCULATION_DISABLE;
LL_SPI_Init(SPI1, &SPI_InitStruct);
逻辑分析:
- 结构体配置与HAL库类似,但使用LL库特有的枚举值
-
LL_SPI_Init
函数直接操作寄存器,效率更高
LL库发送数据代码示例
while(!LL_SPI_IsActiveFlag_TXE(SPI1)); // 等待发送缓冲区空
LL_SPI_TransmitData8(SPI1, 0xA5); // 发送一个字节数据
逻辑分析:
- 使用
LL_SPI_IsActiveFlag_TXE
检测TXE标志 - 调用
LL_SPI_TransmitData8
发送8位数据
本章节系统地讲解了STM32F429中SPI接口的配置方法,从寄存器结构、模式配置、波特率设置到HAL与LL库的实现方式,均提供了详尽的说明与代码示例。后续章节将继续深入讲解如何将SPI接口用于具体设备如W25Q128 Flash的通信。
4. SDRAM工作原理及时序规范
在嵌入式系统中,SDRAM(Synchronous Dynamic Random Access Memory)因其高容量、低成本和较高的访问速度,被广泛应用于需要大量临时数据存储的场景。STM32F429系列微控制器通过其FMC(Flexible Memory Controller)接口支持外部SDRAM连接,为图像处理、数据缓存等高性能应用提供了硬件支持。本章将深入剖析SDRAM的工作原理、关键时序参数及其与STM32F429的接口要求,为后续章节中SDRAM的配置与使用打下理论基础。
4.1 SDRAM的基本结构与工作原理
4.1.1 存储单元组织与行列地址访问
SDRAM的存储单元以矩阵形式组织,通常由多个Bank组成,每个Bank又划分为行(Row)和列(Column)。在访问某个存储单元时,首先需要激活对应的行(Row Activate),然后通过列地址访问具体的数据。
- 行列地址复用(Multiplexed Addressing) :为了减少引脚数量,SDRAM使用相同的地址线传输行地址和列地址,通过RAS(Row Address Strobe)和CAS(Column Address Strobe)信号控制地址线上传输的是行地址还是列地址。
- Bank选择 :现代SDRAM芯片通常包含4个Bank,控制器通过BA0和BA1信号选择当前操作的Bank。
例如,一个容量为16MB的SDRAM芯片,其地址空间可以划分为:
参数 | 数值 |
---|---|
数据宽度 | 16位 |
Bank数量 | 4 |
行地址数 | 13位 |
列地址数 | 9位 |
每行容量 | 512字节 |
每Bank容量 | 4MB |
总容量 | 16MB |
4.1.2 刷新机制与突发传输模式
由于SDRAM使用电容存储电荷,数据会随着时间衰减,因此需要周期性地刷新(Refresh)以维持数据完整性。
- 刷新周期(Refresh Cycle) :通常为64ms,SDRAM控制器必须在该周期内完成所有行的刷新操作。
- 自动刷新(Auto Refresh) :由控制器自动发起,适用于大多数应用场景。
- 自刷新(Self Refresh) :在低功耗模式下由芯片自身维持刷新。
此外,SDRAM支持突发传输(Burst Mode),即一次行激活后连续访问多个列地址的数据,从而提升数据传输效率。
// 示例:SDRAM突发读取模式的配置(以STM32 HAL库为例)
void SDRAM_BurstRead_Config(void) {
FMC_SDRAM_CommandTypeDef cmd;
cmd.CommandMode = FMC_SDRAM_CMD_PRECHARGE; // 预充电
cmd.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
cmd.AutoRefreshNumber = 1;
cmd.ModeRegisterDefinition = 0;
HAL_SDRAM_SendCommand(&hsdram, &cmd, 0x1000); // 发送命令
}
代码解释:
- CommandMode
:设置为预充电模式,用于准备下一次读写操作。
- CommandTarget
:指定操作的Bank。
- AutoRefreshNumber
:设置自动刷新次数。
- HAL_SDRAM_SendCommand
:调用HAL库函数发送SDRAM命令。
4.2 SDRAM的时序参数分析
4.2.1 tRC、tRP、tRCD、tAA等关键参数
SDRAM的访问速度受限于多个时序参数,这些参数决定了SDRAM控制器的配置方式,以确保数据可靠读写。
参数 | 含义 | 单位 | 典型值 |
---|---|---|---|
tRC | 行周期时间 | ns | 10ns |
tRP | 行预充电时间 | ns | 5.4ns |
tRCD | 行到列延迟 | ns | 5.4ns |
tAA | 地址访问时间 | ns | 5.4ns |
tRCR | 刷新周期 | ms | 64ms |
这些参数决定了SDRAM控制器在发送命令、切换地址、等待数据稳定等过程中的等待周期数。
4.2.2 初始化与模式寄存器设置
SDRAM在上电后需要进行初始化流程,包括预充电、刷新、模式寄存器设置等步骤。模式寄存器(Mode Register)控制SDRAM的工作模式,如突发长度、CAS延迟、操作模式等。
// 初始化SDRAM模式寄存器
void SDRAM_ModeRegister_Config(void) {
uint32_t modeReg = 0;
modeReg = (0 << 9) | // Burst Length: 1
(0x2 << 4) | // CAS Latency: 2
(0 << 3) | // Op Mode: Standard
(0 << 2); // Write Burst Mode: Not Supported
FMC_SDRAM_CommandTypeDef cmd;
cmd.CommandMode = FMC_SDRAM_CMD_LOAD_MODE;
cmd.CommandTarget = FMC_SDRAM_CMD_TARGET_BANK1;
cmd.ModeRegisterDefinition = modeReg;
HAL_SDRAM_SendCommand(&hsdram, &cmd, 0x1000);
}
代码逻辑分析:
- modeReg
:根据SDRAM芯片手册配置模式寄存器。
- Burst Length
:设置为1,即单次传输。
- CAS Latency
:设置为2,即两个时钟周期延迟。
- HAL_SDRAM_SendCommand
:发送加载模式寄存器命令。
流程图说明:
mermaid graph TD A[上电] --> B[预充电] B --> C[刷新] C --> D[模式寄存器配置] D --> E[正常读写操作]
4.3 SDRAM与STM32F429的接口要求
4.3.1 地址线、数据线与时钟线的连接
STM32F429通过FMC接口连接SDRAM,其接口信号如下:
引脚类型 | 说明 |
---|---|
地址线(A0~A12) | 提供行/列地址复用信号 |
数据线(D0~D15) | 16位数据总线 |
时钟线(CLK) | SDRAM工作时钟 |
控制信号(nWE、nOE、nCS) | 写使能、输出使能、片选 |
SDRAM控制器通过这些信号与外部存储器进行同步通信。
4.3.2 控制信号(RAS、CAS、WE等)配置
FMC接口通过多个控制信号协调SDRAM的操作:
- RAS(Row Address Strobe) :行地址选通信号。
- CAS(Column Address Strobe) :列地址选通信号。
- WE(Write Enable) :写使能信号。
- CS(Chip Select) :片选信号。
在STM32F429中,这些信号由FMC模块自动控制,用户只需配置FMC_SDRAM_InitTypeDef结构体即可:
// STM32F429 SDRAM初始化结构体配置示例
void MX_SDRAM_Init(void)
{
hsdram.Instance = FMC_SDRAM_DEVICE;
hsdram.Init.MemoryType = FMC_SDRAM_MEM_TYPE_SDRAM;
hsdram.Init.MemoryDataWidth = FMC_SDRAM_DATABUS_WIDTH_16;
hsdram.Init.BurstLength = 1;
hsdram.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
hsdram.Init.MemoryDataWidth = FMC_SDRAM_DATABUS_WIDTH_16;
hsdram.Init.InternalBankNumber = FMC_SDRAM_INTERN_BANKS_NUM_4;
hsdram.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2;
hsdram.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram.Init.ReadBurst = FMC_SDRAM_RBURST_DISABLE;
hsdram.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
HAL_SDRAM_Init(&hsdram);
}
参数说明:
- MemoryType
:设置为SDRAM类型。
- BurstLength
:突发长度设置为1。
- CASLatency
:设置CAS延迟为2个时钟周期。
- SDClockPeriod
:FMC时钟周期配置为2分频。
- ReadBurst
:禁用突发读取模式。
接口信号流程图:
mermaid graph LR A[FMC控制器] --> B[地址线A0-A12] A --> C[数据线D0-D15] A --> D[时钟CLK] A --> E[控制信号RAS/CAS/WE/CS] B --> F[SDRAM芯片] C --> F D --> F E --> F
本章系统性地介绍了SDRAM的基本工作原理、关键时序参数及其与STM32F429的接口要求。通过深入分析行列地址访问机制、刷新流程、突发传输特性,以及FMC接口的配置方法,为后续章节中SDRAM的初始化与读写操作提供了坚实的理论基础和实践指导。
5. STM32F429 FMC接口与SDRAM连接配置
在嵌入式系统中,STM32F429微控制器通过FMC(Flexible Memory Controller)接口连接SDRAM(Synchronous Dynamic Random Access Memory),可以实现对大容量高速内存的访问,广泛应用于图像处理、数据缓存等需要高速数据吞吐的场景。本章将深入讲解FMC接口的功能特性、SDRAM的连接配置步骤,并结合HAL库实现SDRAM的初始化与数据访问。
5.1 FMC接口的基本功能与结构
STM32F429的FMC接口是一个高度灵活的内存控制器,支持多种异步和同步存储器类型,包括SRAM、NOR Flash、NAND Flash和SDRAM。通过FMC接口,MCU可以直接访问外部存储器,如同访问内部内存一样,极大提升了系统性能。
5.1.1 FMC支持的存储器类型与引脚定义
FMC接口支持以下类型的外部存储器:
存储器类型 | 支持模式 | 说明 |
---|---|---|
SRAM | 异步/同步 | 支持复用/非复用总线 |
NOR Flash | 异步/同步 | 支持页模式访问 |
NAND Flash | 同步 | 支持ECC校验 |
SDRAM | 同步动态内存 | 支持16/32位数据总线 |
FMC接口的主要引脚如下:
- 地址线 :FMC_A[0:25],用于访问存储器地址。
- 数据线 :FMC_D[0:31],用于数据读写。
- 控制信号 :
- FMC_NE(NAND使能)
- FMC_NOE(输出使能)
- FMC_NWE(写使能)
- FMC_NWAIT(等待信号)
- FMC_SDCKE(SDRAM时钟使能)
- FMC_SDNE(SDRAM芯片使能)
- FMC_SDNRAS / FMC_SDNCAS / FMC_SDNWE(SDRAM行、列、写控制)
- FMC_SDNBA(Bank地址)
- FMC_SDCLK(SDRAM时钟)
📌 注意 :当使用SDRAM时,FMC将自动产生SDRAM所需的时序信号,如刷新、预充电、激活等。
5.1.2 外部存储器映射与访问机制
FMC将外部存储器映射到MCU的地址空间中,通常为:
- BANK1 NOR/PSRAM :0x6000 0000 ~ 0x6FFF FFFF
- BANK2 NAND/PC卡 :0x7000 0000 ~ 0x7FFF FFFF
- BANK3 NAND/PC卡 :0x8000 0000 ~ 0x8FFF FFFF
- BANK4 SDRAM :0xC000 0000 ~ 0xDFFF FFFF(具体地址取决于配置)
通过FMC控制器的配置,MCU可以直接通过指针访问外部存储器,例如:
uint32_t *sdram_ptr = (uint32_t *)0xC0000000;
*sdram_ptr = 0x12345678;
这将向SDRAM的起始地址写入一个32位数据。
5.2 SDRAM与FMC的连接配置步骤
连接SDRAM到FMC接口需要精确配置时序参数和控制寄存器,以确保与SDRAM芯片的时序兼容。
5.2.1 FMC_Bank配置与寄存器设置
FMC控制器通过一组寄存器来配置Bank的访问方式,主要涉及以下寄存器:
- FMC_BCRx(Bank Control Register) :控制Bank启用、存储器类型、数据宽度等。
- FMC_BTRx(Timing Register) :配置访问时序参数(如地址建立时间、数据保持时间等)。
- FMC_SDCR(SDRAM Control Register) :SDRAM模式控制,包括行列位数、CAS延迟等。
- FMC_SDTR(SDRAM Timing Register) :SDRAM时序参数(如tRC、tRCD、tRP等)。
- FMC_SDCMR(SDRAM Command Mode Register) :发送SDRAM命令(初始化、刷新、预充电等)。
示例:配置FMC_Bank4用于SDRAM
// 启用FMC时钟
RCC->AHB3ENR |= RCC_AHB3ENR_FMCEN;
// 配置Bank4控制寄存器
FMC_Bank5_6->SDCR[0] = (
FMC_SDCR_RPIPE_1 |
FMC_SDCR_SDCLK_2 | // SDCLK = HCLK / 2
FMC_SDCR_CAS_2 | // CAS Latency = 2
FMC_SDCR_NB_1 | // 4 banks
FMC_SDCR_MWID_0 | // 数据宽度32位
FMC_SDCR_NR_1 | // 行地址位数A0~A12 (13位)
FMC_SDCR_NC_0 // 列地址位数A0~A8 (9位)
);
// 配置SDRAM时序寄存器
FMC_Bank5_6->SDTR[0] = (
(0x0A << FMC_SDTR_TMRD_Pos) | // Mode Register Set delay
(0x0C << FMC_SDTR_TXSR_Pos) | // 自刷新退出时间
(0x0C << FMC_SDTR_TRAS_Pos) | // 激活到预充电时间
(0x04 << FMC_SDTR_TRC_Pos) | // 行周期时间
(0x02 << FMC_SDTR_TWR_Pos) | // 写恢复时间
(0x04 << FMC_SDTR_TRP_Pos) | // 行预充电时间
(0x02 << FMC_SDTR_TRCD_Pos) // 行到列延迟
);
📌 参数说明:
-
FMC_SDCR_SDCLK_2
:SDRAM时钟频率为HCLK/2,假设HCLK为180MHz,则SDCLK为90MHz。 -
CAS Latency = 2
:数据在第2个时钟周期后有效。 -
tRC = 4 clock cycles
:行周期时间应大于60ns。 -
tRP = 4 clock cycles
:行预充电时间应大于10ns。
5.2.2 SDRAM初始化流程与时序匹配
SDRAM上电后必须经过一系列初始化步骤,包括:
- 预充电所有Bank
- 自动刷新
- 设置模式寄存器(Mode Register)
- 进入正常操作模式
初始化流程示例(使用FMC命令寄存器):
// 1. 发送预充电命令
FMC_Bank5_6->SDCMR = (
FMC_SDCMR_MODE_COMMAND |
FMC_SDCMR_BANKS_BANK4 |
FMC_SDCMR_CTB1 |
FMC_SDCMR_NRFS_3
);
// 2. 发送自动刷新命令
FMC_Bank5_6->SDCMR = (
FMC_SDCMR_MODE_AUTOREFRESH |
FMC_SDCMR_BANKS_BANK4 |
FMC_SDCMR_CTB1 |
FMC_SDCMR_NRFS_3
);
// 3. 设置模式寄存器
FMC_Bank5_6->SDCMR = (
FMC_SDCMR_MODE_LOAD_MODE_REGISTER |
FMC_SDCMR_BANKS_BANK4 |
FMC_SDCMR_CTB1 |
FMC_SDCMR_MR((0x220)) // 模式寄存器值,CAS=2, Burst=4
);
// 4. 启动SDRAM正常操作
FMC_Bank5_6->SDCMR = (
FMC_SDCMR_MODE_NORMAL |
FMC_SDCMR_BANKS_BANK4 |
FMC_SDCMR_CTB1
);
📌 逻辑分析:
-
FMC_SDCMR_MODE_COMMAND
:发送预充电命令。 -
FMC_SDCMR_MODE_AUTOREFRESH
:执行自动刷新,确保所有行刷新完成。 -
FMC_SDCMR_MODE_LOAD_MODE_REGISTER
:加载模式寄存器,设置Burst长度和CAS延迟。 -
FMC_SDCMR_MODE_NORMAL
:进入正常模式,SDRAM准备好接收读写操作。
5.3 使用HAL库实现SDRAM访问
STM32 HAL库提供了对FMC接口的封装,开发者可以通过调用标准API完成SDRAM的初始化和访问。
5.3.1 SDRAM控制器初始化函数调用
FMC_SDRAM_TimingTypeDef Timing;
FMC_SDRAM_CommandTypeDef Command;
// 初始化FMC SDRAM接口
hsdram.Instance = FMC_SDRAM_DEVICE;
hsdram.Init.MemoryDataWidth = FMC_SDRAM_MEM_BUS_WIDTH_32;
hsdram.Init.BurstLength = FMC_SDRAM_BURST_LENGTH_4;
hsdram.Init.ColumnBitsNumber = FMC_SDRAM_COLUMN_BITS_NUM_9;
hsdram.Init.RowBitsNumber = FMC_SDRAM_ROW_BITS_NUM_13;
hsdram.Init.CASLatency = FMC_SDRAM_CAS_LATENCY_2;
hsdram.Init.WriteProtection = FMC_SDRAM_WRITE_PROTECTION_DISABLE;
hsdram.Init.SDClockPeriod = FMC_SDRAM_CLOCK_PERIOD_2;
hsdram.Init.ReadBurst = FMC_SDRAM_RBURST_ENABLE;
hsdram.Init.ReadPipeDelay = FMC_SDRAM_RPIPE_DELAY_1;
Timing.LoadToActiveDelay = 2;
Timing.ExitSelfRefreshDelay = 12;
Timing.SelfRefreshTime = 12;
Timing.RowCycleDelay = 4;
Timing.WriteRecoveryTime = 2;
Timing.RPDelay = 4;
Timing.RCDDelay = 2;
if (HAL_SDRAM_Init(&hsdram, &Timing) != HAL_OK) {
Error_Handler();
}
📌 参数说明:
-
MemoryDataWidth
:数据总线宽度为32位。 -
BurstLength
:突发长度为4。 -
CASLatency
:CAS延迟为2个时钟周期。 -
SDClockPeriod
:SDRAM时钟为HCLK/2。 -
LoadToActiveDelay
:从加载模式到激活命令的延迟。
5.3.2 数据读写操作与地址映射
初始化完成后,可以通过指针或HAL库函数进行SDRAM的读写操作。
示例:使用指针读写SDRAM
#define SDRAM_BASE_ADDR 0xC0000000
uint32_t *sdram_base = (uint32_t *)SDRAM_BASE_ADDR;
// 写入数据
sdram_base[0] = 0xAABBCCDD;
// 读取数据
uint32_t data = sdram_base[0];
示例:使用HAL库函数读写(适用于需要缓存控制的情况)
uint32_t buffer[1024];
uint32_t address = SDRAM_BASE_ADDR + 0x1000;
// 写入数据
memcpy((void *)address, buffer, sizeof(buffer));
// 读取数据
memcpy(buffer, (void *)address, sizeof(buffer));
📌 注意事项:
- 如果使用了DMA或缓存(Cache),需进行缓存一致性维护(如
SCB_CleanInvalidateDCache()
)。 - 对SDRAM的访问应避免频繁的小数据量访问,建议使用DMA或批量访问以提高效率。
总结
本章详细介绍了STM32F429中FMC接口的结构与功能,并以SDRAM为例,展示了如何配置FMC控制器、初始化SDRAM并实现数据访问。通过合理设置FMC寄存器和时序参数,可以充分发挥STM32F429对高速外部存储器的访问能力,为嵌入式系统提供高性能的数据处理支持。后续章节将进一步探讨如何将SDRAM中的数据高效传输到SPI Flash中,构建完整的多存储设备协同操作机制。
6. W25Q128 SPI Flash读写操作流程
6.1 W25Q128芯片功能概述
6.1.1 存储容量与扇区划分
W25Q128是Winbond公司推出的一款SPI接口的串行Flash存储器芯片,容量为128Mbit(即16MB)。该芯片广泛应用于嵌入式系统中,作为非易失性存储介质,用于存储程序代码、配置数据或用户数据。
其内部结构采用扇区划分方式,具体如下:
扇区编号 | 容量 | 地址范围(16进制) |
---|---|---|
0 ~ 255 | 4KB | 0x000000 - 0x00FFFF |
256 | 32KB | 0x010000 - 0x017FFF |
257 ~ 263 | 64KB | 0x018000 - 0x03FFFF |
该结构支持灵活的擦除操作,包括页擦除(4KB)、扇区擦除(32KB或64KB)以及整片擦除。写入前必须先进行擦除操作,这是SPI Flash的通用特性。
6.1.2 支持的操作模式(标准SPI、QPI等)
W25Q128支持多种通信模式,主要包括:
- Standard SPI :单线数据输入(DI)和输出(DO),SCLK和CS控制时序。
- Dual SPI :使用DI和DO分别作为数据线,实现双倍传输速率。
- Quad SPI (QPI) :使用四根数据线同时传输数据,显著提高数据吞吐量。
这些模式可以通过发送特定指令切换。例如,进入QPI模式需发送 0x38
指令,退出QPI模式则发送 0xFF
。
6.2 W25Q128的指令集与通信流程
6.2.1 常用指令(读ID、读数据、写使能、擦除等)
W25Q128支持丰富的指令集,以下是部分常用指令及其功能说明:
指令名称 | 指令码(HEX) | 功能描述 |
---|---|---|
Read ID | 0x90 / 0x9F | 读取芯片制造商和设备ID |
Read Data | 0x03 | 从指定地址连续读取数据 |
Fast Read | 0x0B | 快速读取模式,带SCLK延迟 |
Write Enable | 0x06 | 使能写操作 |
Write Disable | 0x04 | 禁止写操作 |
Sector Erase (4KB) | 0x20 | 擦除4KB扇区 |
Block Erase (32KB) | 0x52 | 擦除32KB块 |
Block Erase (64KB) | 0xD8 | 擦除64KB块 |
Chip Erase | 0xC7 | 整片擦除 |
Page Program | 0x02 | 写入最多256字节数据 |
Read Status Register 1 | 0x05 | 读取状态寄存器1 |
Write Status Register | 0x01 | 设置状态寄存器 |
通信流程如下图所示,以读取ID为例:
sequenceDiagram
participant MCU
participant W25Q128
MCU->>W25Q128: CS低电平
MCU->>W25Q128: 发送指令 0x90
MCU->>W25Q128: 发送地址 0x00 0x00 0x00
W25Q128-->>MCU: 返回 Manufacturer ID (0xEF)
W25Q128-->>MCU: 返回 Device ID (0x40)
MCU->>W25Q128: CS高电平
6.2.2 写保护与状态寄存器控制
W25Q128通过状态寄存器(Status Register)控制写保护、擦除/写操作状态以及块保护设置。状态寄存器1(SR1)的部分字段如下:
位 | 名称 | 功能 |
---|---|---|
7 | BUSY | 1表示芯片正忙(擦除或写入) |
6 | WEL | 1表示写使能已激活 |
5~2 | BP[3:0] | 块保护设置 |
1 | AAI | Auto Address Increment编程使能 |
0 | 保留 | - |
例如,设置写保护可以通过写入状态寄存器指令 0x01
并传入对应的保护位组合,从而防止误擦除或误写入。
6.3 W25Q128的读写实践
6.3.1 使用HAL库实现单字节与批量数据读写
单字节读写示例
以下代码使用STM32 HAL库实现对W25Q128的单字节读取操作:
uint8_t read_byte(uint32_t address) {
uint8_t tx_data[4], rx_data[1];
tx_data[0] = 0x03; // Read Data 指令
tx_data[1] = (address >> 16) & 0xFF; // 地址高位
tx_data[2] = (address >> 8) & 0xFF; // 中位
tx_data[3] = address & 0xFF; // 低位
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET); // CS低电平
HAL_SPI_Transmit(&hspi1, tx_data, 4, HAL_MAX_DELAY); // 发送指令和地址
HAL_SPI_Receive(&hspi1, rx_data, 1, HAL_MAX_DELAY); // 读取一个字节
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET); // CS高电平
return rx_data[0];
}
逐行分析:
-
tx_data[0] = 0x03;
:设置读取数据指令。 -
tx_data[1~3]
:将地址拆分为3字节,用于寻址。 -
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
:选中W25Q128芯片。 -
HAL_SPI_Transmit(...)
:发送指令和地址。 -
HAL_SPI_Receive(...)
:接收返回的数据。 - 最后将CS置高,结束通信。
批量数据写入示例
以下代码实现向W25Q128的指定地址写入一段数据:
void write_page(uint32_t address, uint8_t *data, uint16_t length) {
uint8_t tx_cmd[4];
// 1. 发送写使能指令
uint8_t cmd_wren = 0x06;
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, &cmd_wren, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
// 2. 发送写入指令和地址
tx_cmd[0] = 0x02;
tx_cmd[1] = (address >> 16) & 0xFF;
tx_cmd[2] = (address >> 8) & 0xFF;
tx_cmd[3] = address & 0xFF;
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1, tx_cmd, 4, HAL_MAX_DELAY);
// 3. 发送数据
HAL_SPI_Transmit(&hspi1, data, length, HAL_MAX_DELAY);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
}
逻辑分析:
- 写入前必须先发送
0x06
(写使能)指令。 - 使用
0x02
指令进行页写入操作,最大写入长度为256字节。 - 每次写入后需要等待芯片内部完成操作(可通过轮询状态寄存器BUSY位实现)。
6.3.2 数据校验与错误处理机制
在进行SPI Flash操作时,为了确保数据完整性,通常采用以下策略:
- CRC校验 :在写入数据后,计算CRC值并保存,读取时进行比对。
- 状态寄存器轮询 :写入或擦除操作后,轮询SR1的
BUSY
位,确保操作完成。 - 错误重试机制 :如果通信失败或校验失败,重新执行操作。
- 地址边界检查 :确保写入地址不越界,避免破坏保留区域。
以下是一个状态寄存器轮询函数示例:
void wait_for_flash_ready(void) {
uint8_t status = 0x01;
while (status & 0x01) {
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_RESET);
uint8_t cmd = 0x05; // Read Status Register 1
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi1, &status, 1, HAL_MAX_DELAY);
HAL_GPIO_WritePin(CS_GPIO_Port, CS_Pin, GPIO_PIN_SET);
HAL_Delay(1); // 短暂延时
}
}
逐行解释:
-
while (status & 0x01)
:当BUSY位为1时,继续等待。 -
HAL_SPI_Transmit(&hspi1, &cmd, 1, HAL_MAX_DELAY);
:发送读取状态寄存器指令。 -
HAL_SPI_Receive(...)
:获取状态寄存器值。 - 若仍忙,延时1ms后再次检查。
总结
本章系统介绍了W25Q128 SPI Flash芯片的结构、通信指令集、读写操作流程,并通过HAL库实现了具体的读写代码。同时,介绍了状态寄存器的使用和错误处理机制,为后续章节中从SDRAM读取数据并写入SPI Flash打下坚实基础。
下一章将深入探讨如何实现从SDRAM高效读取大量数据,并通过SPI接口写入W25Q128 Flash,包括缓冲区设计、DMA传输、性能优化等关键内容。
7. 大量数据从SDRAM读取并写入W25Q128的实现方法
7.1 数据传输的整体流程设计
7.1.1 任务划分与状态机设计
在STM32F429系统中,从SDRAM读取大量数据并写入W25Q128 SPI Flash的过程,需要合理划分任务流程。建议采用状态机模型来管理数据传输过程,状态包括:
- IDLE :等待启动命令
- READ_SDRAM :从SDRAM中读取指定大小的数据块
- PREPARE_WRITE :准备写入Flash的地址和指令
- WRITE_FLASH :将数据写入W25Q128 Flash
- VERIFY :可选状态,用于校验写入数据是否正确
- ERROR :错误处理状态
状态机流程图如下:
graph TD
A[IDLE] -->|Start| B[READ_SDRAM]
B --> C[PREPARE_WRITE]
C --> D[WRITE_FLASH]
D -->|Success| E[VERIFY]
E --> F[IDLE]
D -->|Error| G[ERROR]
G --> H[RETRY or ABORT]
7.1.2 多阶段传输与中断协调机制
由于数据量较大,传输过程需分阶段进行,避免阻塞主程序。建议使用DMA配合中断机制,实现非阻塞式传输:
-
SDRAM读取阶段 :
- 使用FMC的DMA通道将数据从SDRAM搬运到内存缓冲区。
- 配置DMA中断,在传输完成时触发下一步操作。 -
Flash写入阶段 :
- 使用SPI的DMA通道将缓冲区数据写入W25Q128。
- 每次写入前需先发送写使能指令(0x06)并等待写使能标志置位。
- 写入完成后等待Flash内部完成操作(约几毫秒),再进行下一次写入。
7.2 数据缓冲区设计与批量处理机制
7.2.1 缓冲区大小与内存分配策略
为了提升传输效率,应合理设计缓冲区大小。考虑到W25Q128的页写入限制(每页最多256字节),建议采用以下策略:
缓冲区大小 | 说明 |
---|---|
1KB ~ 4KB | 推荐大小,平衡性能与内存占用 |
双缓冲机制 | 交替使用两个缓冲区,提升效率 |
动态分配 | 使用 malloc() 在SDRAM中分配,释放后不影响系统其他功能 |
代码示例(双缓冲区初始化):
#define BUFFER_SIZE 4096
uint8_t bufferA[BUFFER_SIZE];
uint8_t bufferB[BUFFER_SIZE];
uint8_t* currentBuffer = bufferA;
uint8_t* nextBuffer = bufferB;
7.2.2 DMA辅助的高效数据搬移
DMA是实现高速数据搬移的关键。以下为SPI写入阶段使用DMA的配置示例:
// SPI发送DMA配置
hdma_spi_tx.Instance = DMA1_Stream5;
hdma_spi_tx.Init.Channel = DMA_CHANNEL_3;
hdma_spi_tx.Init.Direction = DMA_MEMORY_TO_PERIPH;
hdma_spi_tx.Init.PeriphInc = DMA_PINC_DISABLE;
hdma_spi_tx.Init.MemInc = DMA_MINC_ENABLE;
hdma_spi_tx.Init.PeriphDataAlignment = DMA_PDATAALIGN_BYTE;
hdma_spi_tx.Init.MemDataAlignment = DMA_MDATAALIGN_BYTE;
hdma_spi_tx.Init.Mode = DMA_NORMAL;
hdma_spi_tx.Init.Priority = DMA_PRIORITY_HIGH;
hdma_spi_tx.Init.FIFOMode = DMA_FIFOMODE_DISABLE;
调用DMA发送函数:
HAL_SPI_Transmit_DMA(&hspi1, currentBuffer, BUFFER_SIZE);
7.3 传输性能优化与稳定性保障
7.3.1 SPI通信速率与吞吐量优化
为了提升写入速度,应尽可能提升SPI时钟频率。W25Q128支持高达80MHz的SPI时钟频率(QPI模式),但标准SPI模式下一般建议不超过60MHz。
配置SPI波特率预分频:
hspi1.Instance = SPI1;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_4; // SPI时钟 = 168MHz / 4 = 42MHz
预分频值 | SPI时钟频率 | 说明 |
---|---|---|
2 | 84 MHz | 超出W25Q128 SPI标准支持范围 |
4 | 42 MHz | 推荐值,稳定且高效 |
8 | 21 MHz | 适用于长距离通信或干扰较大环境 |
7.3.2 错误检测机制(CRC校验、超时重试)
为了保障数据传输的稳定性,应加入错误检测与重试机制:
- CRC校验 :使用STM32F429内置CRC模块,对每个传输块计算CRC值,并在写入后读回验证。
- 超时重试 :在写入Flash后等待状态寄存器就绪标志,若超时则重试。
示例代码(CRC校验):
uint32_t CalculateCRC(uint8_t *data, uint32_t length) {
HAL_CRC_Start(&hcrc, (uint32_t*)data, length);
return HAL_CRC_GetValue(&hcrc);
}
重试机制伪代码:
for (retry = 0; retry < MAX_RETRY; retry++) {
SendWriteEnable();
WriteDataToFlash(currentBuffer, BUFFER_SIZE);
if (WaitForFlashReady(100)) { // 等待100ms
break;
}
}
7.4 嵌入式系统中多存储设备协同操作的实践技巧
7.4.1 FMC与SPI外设的并发使用策略
在STM32F429中,FMC与SPI外设可并发使用,但需注意以下几点:
- DMA优先级配置 :设置FMC和SPI的DMA通道优先级,确保高优先级任务不被阻塞。
- 内存带宽管理 :避免同时进行大量FMC和SPI访问,防止总线冲突。
- 中断嵌套 :启用中断优先级分组,允许SPI中断在FMC DMA传输期间打断处理。
7.4.2 实时系统中的任务调度与资源管理
在RTOS环境下(如FreeRTOS),可以采用以下策略:
- 任务划分 :
-
read_sram_task
:负责从SDRAM读取数据 -
write_flash_task
:负责写入Flash -
control_task
:协调两者,并管理缓冲区切换 -
资源同步 :
- 使用信号量(Semaphore)控制缓冲区的访问权限
- 使用队列(Queue)传递数据块地址和大小
示例代码(FreeRTOS中使用信号量):
SemaphoreHandle_t bufferSemaphore;
void read_sram_task(void *pvParameters) {
while (1) {
if (xSemaphoreTake(bufferSemaphore, portMAX_DELAY)) {
ReadFromSDRAM(currentBuffer, BUFFER_SIZE);
xSemaphoreGive(bufferSemaphore);
}
}
}
7.4.3 低功耗与数据完整性的权衡设计
在电池供电或低功耗系统中,应在数据完整性和功耗之间取得平衡:
策略 | 说明 |
---|---|
动态调整时钟频率 | 传输时启用高速时钟,空闲时降低频率 |
启用待机模式 | 在数据传输间隙启用低功耗模式 |
写入前保存状态 | 记录当前传输位置,防止掉电后丢失进度 |
示例代码(进入低功耗模式):
void EnterLowPowerMode(void) {
__HAL_RCC_PWR_CLK_ENABLE();
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
}
简介:STM32F429是一款基于Cortex-M4内核的高性能低功耗微控制器,支持通过SPI接口与外部设备通信。本文详细讲解如何利用STM32F429通过SPI与SDRAM和W25Q128进行大量数据的读写操作。内容涵盖SPI协议基础、STM32F429的SPI和FMC接口配置、SDRAM与W25Q128的通信时序,以及使用HAL或LL库开发嵌入式程序的实现方法。项目文件包含完整示例代码,适用于开发者快速掌握大容量数据在不同存储设备间的高效迁移技巧。