STM32 SPI Flash读写实验手册
实验名称
基于SPI Flash的断电状态保存系统
实验目的
-
掌握SPI Flash的基本读写操作
-
实现设备状态断电保存功能
-
学习STM32 HAL库的SPI驱动开发
硬件要求
-
STM32开发板
-
按键
-
SPI Flash模块
-
杜邦线若干
-
面包板
硬件连接
SPI Flash引脚 | STM32引脚 |
---|---|
CLK | SPI_SCK (PA5) |
DI | SPI_MISO (PA7) |
DO | SPI_MOSI (PA6) |
CS | NSS(PA4) |
VCC | 3.3V |
GND | GND |
按键连接:
按键一端接PA0,另一端接GND(上拉模式)
软件配置(STM32CubeMX)
-
SPI配置
-
GPIO配置
时钟配置
系统时钟设置为系统配置的8分(因实验硬件限制)
按钮及其灯亮代码实现
uint8_t pre = 1;//按钮按下的前标志
uint8_t cur = 1;//按钮按下的后标志
uint8_t ledState = 0;//板载LED的状态标志
//读取flsh数据并进行判断,函数实现在fish读数据实现
ledState = LoadledState();
if(ledState == 1){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);
}else{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);
}
while (1)
{
pre = cur;将前后标志重置为1,既按钮未被按下的状态
if(HAL_GPIO_ReadPin(GPIOA,GPIO_PIN_0) == GPIO_PIN_SET){//获取按钮引脚电平
cur = 1;//引脚高电平
}else{
cur = 0;//引脚低电平
}
if(pre != cur){//按钮有动作
HAL_Delay(10);//延迟消抖
if(cur == 0){//按下按钮(图2)
}else{//按钮弹起(图3)
if(ledState == 1){
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_SET);//板载LED熄灭
ledState = 0;//设置LED灯状态为0
}else{
HAL_GPIO_WritePin(GPIOC,GPIO_PIN_13,GPIO_PIN_RESET);//板载LED点亮
ledState = 1;//设置LED灯状态为1
}
SaveLEDState(ledState);//flsh写入数据的方法在后面实现
}
}
未按下
图1
按钮按下
图2
按钮弹起
图3
SPI Flash读写原理
读写函数
TypeDef HAL_SPI_Transmit
HAL_SPI_Transmit函数通过SPI接口以阻塞(轮询)模式发送指定长度的数据。
HAL_StatusTypeDef HAL_SPI_Transmit(
SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout
);
参数说明
参数 | 类型 | 说明 |
---|---|---|
hspi |
SPI_HandleTypeDef* | 指向SPI外设句柄的指针,包含SPI配置信息(如SPI1、SPI2等) |
pData |
uint8_t* | 待发送数据的缓冲区指针 |
Size |
uint16_t | 待发送数据的字节数 |
Timeout |
uint32_t | 超时时间(单位:毫秒),若超时,函数返回HAL_TIMEOUT |
返回值
返回值 | 说明 |
---|---|
HAL_OK |
传输成功完成 |
HAL_ERROR |
发生错误(如SPI未初始化、DMA错误等) |
HAL_BUSY |
SPI外设正忙(上一次传输未完成) |
HAL_TIMEOUT |
传输超时(未在指定时间内完成) |
HAL_SPI_Receive
HAL_SPI_Receive通过SPI接口以阻塞(轮询)模式接收指定长度的数据。
HAL_StatusTypeDef HAL_SPI_Receive(
SPI_HandleTypeDef *hspi,
uint8_t *pData,
uint16_t Size,
uint32_t Timeout
);
参数说明
参数 | 类型 | 说明 |
---|---|---|
hspi |
SPI_HandleTypeDef* | 指向SPI外设句柄的指针,包含SPI配置信息(如SPI1、SPI2等) |
pData |
uint8_t* | 接收数据的缓冲区指针(用于存储从设备返回的数据) |
Size |
uint16_t | 待接收数据的字节数 |
Timeout |
uint32_t | 超时时间(单位:毫秒),若超时,函数返回HAL_TIMEOUT |
返回值
返回值 | 说明 |
---|---|
HAL_OK |
接收成功完成 |
HAL_ERROR |
发生错误(如SPI未初始化、DMA错误等) |
HAL_BUSY |
SPI外设正忙(上一次传输未完成) |
HAL_TIMEOUT |
接收超时(未在指定时间内完成) |
HAL_SPI_TransmitReceive
全双工同步通信:在发送数据的同时接收数据,每个发送字节对应一个接收字节。高效数据交换,适用于需要高速双向数据交换的场景(如传感器数据读取、通信协议交互)。
HAL_StatusTypeDef HAL_SPI_TransmitReceive(
SPI_HandleTypeDef *hspi,
uint8_t *pTxData,
uint8_t *pRxData,
uint16_t Size,
uint32_t Timeout
);
参数说明
参数 | 类型 | 说明 |
---|---|---|
hspi |
SPI_HandleTypeDef* | 指向SPI外设句柄的指针 |
pTxData |
uint8_t* | 发送数据缓冲区指针(需发送的数据) |
pRxData |
uint8_t* | 接收数据缓冲区指针(用于存储接收的数据) |
Size |
uint16_t | 发送/接收数据的字节数(需确保发送和接收数据长度一致) |
Timeout |
uint32_t | 超时时间(单位:毫秒) |
返回值
返回值 | 说明 |
---|---|
HAL_OK |
全双工传输成功完成 |
HAL_ERROR |
参数错误或SPI初始化失败 |
HAL_BUSY |
SPI外设正忙(上一次操作未完成) |
HAL_TIMEOUT |
超时未完成传输 |
flsh结构
存储单元 | 大小 | 操作特性 |
---|---|---|
页(Page) | 256字节 | 最小编程单位 |
扇区(Sector) | 4KB | 最小擦除单位 |
块(Block) | 64KB | 大容量擦除单位 |
整片(Chip) | 8MB(64Mbit) | 全片擦除操作 |
存储特性:
写入前必须擦除(1→0需要擦除,0→1可直接写)
每个位只能从1变为0,擦除操作会将整个扇区恢复为全1(0xFF)
典型擦写寿命:10万次(需注意磨损均衡)
flsh写入数据过程
根据数据写入过程函数设计如下
static void SaveLEDState(uint8_t ledState){
//写使能
//扇区删除
HAL_Delay(100)
//写使能
//扇区删除
HAL_Delay(10)
}
写使能
操作时序:
CS拉低 → 发送0x06 → CS拉高
作用:开启写操作许可(每次写操作前必须执行)
实现代码
uint8_t writeEnableCmd[] = {0x06};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspil,writeEnableCmd,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
扇区擦除
操作时序:
CS拉低 →
发送0x20 →
发送24位地址(仅使用高16位确定扇区)→
CS拉高
擦除时间:典型值400ms
实现代码
uint8_t sectorEraseCmd[] = {0x20,0x00,0x00,0x00};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspil1,sectorEraseCmd,4,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
页编程
操作时序:
CS拉低 →
发送0x02 →
发送24位地址(A23-A0)→
发送数据(最多256字节)→
CS拉高
注意:地址必须页对齐(如0x000000, 0x000100等)
实现代码
uint8_t pageProgCmd[5];
pageProgCmd[0] = 0x02;
pageProgCmd[1] = 0;
pageProgCmd[2] = 0;
pageProgCmd[3] = 0;
pageProgCmd[4] = ledState;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,pageProgCmd,5,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
实现代码
static void SaveLEDState(uint8_t ledState){
//写使能
uint8_t writeEnableCmd[] = {0x06};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,writeEnableCmd,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//扇区擦除
uint8_t sectorEraseCmd[] = {0x20,0x00,0x00,0x00};
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,sectorEraseCmd,4,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//延迟
HAL_Delay(100);
//写使能
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,writeEnableCmd,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//页编程
uint8_t pageProgCmd[5];
pageProgCmd[0] = 0x02;
pageProgCmd[1] = 0x00;
pageProgCmd[2] = 0x00;
pageProgCmd[3] = 0x00;
pageProgCmd[4] = ledState;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,pageProgCmd,5,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
//延迟
HAL_Delay(10);
}
上述方法加到判断按钮状态的if语句中去。
flsh读数据
操作时序:
CS拉低 →
发送0x03 →
发送24位地址 →
连续读取数据 →
CS拉高
特点:支持跨页连续读取
实现代码
static uint8_t LoadledState(){
uint8_t readDateCmd[] = {0x03,0x00,0x00,0x00};
uint8_t ledState = 0xff;
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET);
HAL_SPI_Transmit(&hspi1,readDateCmd,4,HAL_MAX_DELAY);
HAL_SPI_Receive(&hspi1,&ledState ,1,HAL_MAX_DELAY);
HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET);
return ledState ;
}
读取flsh内容函数要在程序加载前执行并对返回的数据进行判断并进行亮灯或没灯操作。
实验步骤
-
按照硬件连接图接线
-
使用STM32CubeMX生成工程
-
实现主程序逻辑
-
编译下载程序到开发板
-
测试流程:
-
首次上电:LED默认状态
-
按下按键 → LED状态切换并保存
-
断电后重新上电 → 恢复上次保存的状态
-
再次按键 → 状态切换并保存
作者:ElePower9527