STM32F1 - SPI读写Flash

2024-03-05 16:36
文章标签 读写 flash spi stm32f1

本文主要是介绍STM32F1 - SPI读写Flash,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

Serial peripheral interface

  • 1> 实验概述
  • 2> SPI硬件框图
    • 初始化程序
  • 3> STM32的SPI通信时序
    • 3.1> 时序图
    • 3.2> 文字描述
    • 3.3> 注意事项
    • 3.4> 流程图表示
    • 3.5> 程序表示
      • 接收程序:
      • 发送程序:
  • 4> SPI的4种模式
  • 5> W25Q128存储结构
    • 块 > 扇区 > 页
  • 6> W25Q128常用命令
    • 6.1> 读状态寄存器
      • 检测忙程序
    • 6.2> 写使能
      • 写使能-程序
    • 6.3> 擦除1个扇区
      • 擦除1个扇区-程序
    • 6.4> 写入1页Page数据
      • 写1页数据-程序
    • 6.5> 读数据
  • 7> 测试程序
    • 7.1> 逻辑分析仪 抓波形


1> 实验概述

使用STM32的SPI硬件模块,读写Flash


2> SPI硬件框图

2

MOSI : Master Output Slave Input;
MISO: Master Input Slave Output;

初始化程序

/*** @brief SPI硬件模块配置,全双工, 高位优先* @note  SPI2, CS-PB12, SCK-PB13,  MISO-PB14, MOSI-PB15;*/
void NorFLASH_Init(void)
{GPIO_InitTypeDef GPIO_InitStruct;SPI_InitTypeDef SPI_InitStruct;/* 首先开启时钟 */RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOB | RCC_APB2Periph_AFIO, ENABLE);RCC_APB1PeriphClockCmd(RCC_APB1Periph_SPI2, ENABLE);/* GPIO参数配置 */// CS-PB12GPIO_InitStruct.GPIO_Pin = GPIO_Pin_12;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_Out_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// SCK-PB13GPIO_InitStruct.GPIO_Pin = GPIO_Pin_13;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// MISO-PB14GPIO_InitStruct.GPIO_Pin = GPIO_Pin_14;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_IN_FLOATING;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);// MOSI-PB15GPIO_InitStruct.GPIO_Pin = GPIO_Pin_15;GPIO_InitStruct.GPIO_Mode = GPIO_Mode_AF_PP;GPIO_InitStruct.GPIO_Speed = GPIO_Speed_50MHz;GPIO_Init(GPIOB, &GPIO_InitStruct);/* SPI2参数配置 */SPI_InitStruct.SPI_Mode = SPI_Mode_Master;SPI_InitStruct.SPI_CPOL = SPI_CPOL_High;SPI_InitStruct.SPI_CPHA = SPI_CPHA_2Edge; // Mode 3;SPI_InitStruct.SPI_Direction = SPI_Direction_2Lines_FullDuplex;SPI_InitStruct.SPI_DataSize = SPI_DataSize_8b;SPI_InitStruct.SPI_FirstBit = SPI_FirstBit_MSB;SPI_InitStruct.SPI_NSS = SPI_NSS_Soft;SPI_InitStruct.SPI_BaudRatePrescaler = SPI_BaudRatePrescaler_4;SPI_InitStruct.SPI_CRCPolynomial = 0x07; // 复位值,无用SPI_Init(SPI2, &SPI_InitStruct);SPI_CalculateCRC(SPI2, DISABLE);	//  关闭硬件CRC校验/* 使能SPI2 */SPI_Cmd(SPI2, ENABLE);
}

3> STM32的SPI通信时序


3.1> 时序图

3


3.2> 文字描述

Step 1> 写【第1】字节数据到SPI_DR;
Step 2> 等待【TXE == 1】, 写【第2】字节到SPI_DR;
Step 3> 等待【RXNE == 1】, 读SPI_DR, 得到【第1】字节数据;
Step 4> 如果要读写多个字节【循环重复】第2步和第3步;
Step 5> 等待【RXNE == 1】, 读SPI_DR, 得到【最后】字节数据;
Step 6> 等待【TXE == 1】,完成读写;


3.3> 注意事项

1> 接收数据时,SPI也必须发送数据,这样才能产生SCK时钟;(这点设计的感觉不好)
2> 在MISO线上输出完8bit数据后,才会存储到SPI_DR, 所以他相等于落后了一个字节;
3> TXE的标志由硬件置1,写SPI_DR可以清除;
4> RXNE 标志是由硬件置1,读SPI_DR可以清除;


3.4> 流程图表示

32


3.5> 程序表示


接收程序:

接收数据,也需要发送数据,通常发送无意义的0xFF

/*** @brief 接收多字节数据* @param pRxData 接收数据缓冲区* @param size 接收size字节数据*/
static void SPI2_Receive(uint8_t *pRxData, uint16_t size)
{// Step 1> 发送第1字节数据SPI_I2S_ReceiveData(SPI2); 	  // 清除RXNE标志, 清空接收缓冲区数据SPI_I2S_SendData(SPI2, 0xFF); // 发送任意值, 目的只是产生CLKwhile (size > 1) { // Step 2>  /* 等待TXE==1,然后写入第2字节, 要发送的数据 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/*wati*/;}SPI_I2S_SendData(SPI2, 0xFF);/* Step 3> 等待RXNE==1, 读出SPI_DR寄存器, 得到第1字节数据, 读的同时会清除RXNE标志 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET) {/* 等待RXNE标志为1, 接收数据 */;}*pRxData = SPI_I2S_ReceiveData(SPI2);*pRxData++;size--;}/* Step 4> 等待RXNE==1, 读出SPI_DR寄存器, 得到最后1字节数据 */while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_RXNE) != SET) {/* 等待RXNE标志为1, 接收数据 */;}*pRxData = SPI_I2S_ReceiveData(SPI2);/ *Step 5> 等待发送完成*/ while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/* 等待最后1字节发送完成,方便片选信号拉高 */;}
}

发送程序:

1

/*** @brief 发送多字节数据, 轮询方式* @param pData, 发送数据缓冲区* @param size 发送size字节数据*/
static void SPI2_Transmit(uint8_t *pData, uint16_t Size)
{	while (Size > 0) {SPI_I2S_SendData(SPI2, *pData);while (SPI_I2S_GetFlagStatus(SPI2, SPI_I2S_FLAG_TXE) != SET) {/* 等待TXE标志为1,发送数据 */;}	pData++;Size--;	}	
}

4> SPI的4种模式


4种模式表格:
4


4种模式-时序图:
42


5> W25Q128存储结构

5
Block(块):64KByte;
Sector(扇区): 4KByte;
Page(页):256Byte;

128Mbit = 16MByte = 256个Block = 4096个Sector;

块 > 扇区 > 页

ff

1个块 = 16个扇区;
1个扇区 = 16个页;


6> W25Q128常用命令

Flash存储器:写之前要先擦除;

写入时只能写0, 不能写1;
写1是靠擦除命令实现的。

5


6.1> 读状态寄存器

51

主机读写过程:

Step 1> 主机发送0x05命令, 从机无数据;
Step 2> 主机发送任意值,目的是产生CLK时钟,从机才能回数据;

BUSY位:
522


检测忙程序

/*** @brief 检测Flash忙不忙*/
void NorFLASH_ReadBusy(void)
{uint8_t cmd = 0x05;uint8_t reg = 0x00;GPIO_ResetBits(GPIOB, GPIO_Pin_12); 	// CS拉低SPI2_Transmit(&cmd, 1);SPI2_Receive(&reg, 1);while ((reg & 0x01) == 0x01) {SPI2_Receive(&reg, 1);	// 等待busy}GPIO_SetBits(GPIOB, GPIO_Pin_12);		// CS拉高	
}

6.2> 写使能

62

63


写使能-程序

/*** @brief 写使能*/
void NorFLASH_WriteEnable(void)
{uint8_t cmd = 0x06;NorFLASH_ReadBusy();					// 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); 	// CS拉低SPI2_Transmit(&cmd, 1);GPIO_SetBits(GPIOB, GPIO_Pin_12);		// CS拉高
}

6.3> 擦除1个扇区

52

硬件设计,最少只能擦除1个扇区4KByte;

24位地址:3字节;

擦除0#扇区, Adress 为【0x00 00 00】;
擦除4080#扇区,Adress为【0xFF 00 00】;


擦除1个扇区-程序

/*** @brief 擦除1个扇区数据, 4KByte* @param num 扇区序号*/
void NorFLASH_EraseSector(uint32_t num)
{uint8_t cmd[4];uint32_t addr;addr = num * 4096;// 构建数据cmd[0] = 0x20;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_WriteEnable();					// 写使能NorFLASH_ReadBusy();					// 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); 	// CS拉低SPI2_Transmit(cmd, 4);GPIO_SetBits(GPIOB, GPIO_Pin_12);		// CS拉高
}

6.4> 写入1页Page数据

63

1页Page = 256Byte;

64

循环写入,超过256字节,会覆盖开始的字节;


写1页数据-程序

/*** @brief 写1页数据,256Byte* @param PData 发送数据缓冲区* @param num 页序号*/
void NorFLASH_WritePage(uint8_t *pData, uint32_t num)
{uint8_t cmd[4];uint32_t addr;addr = num * 256;	// 1页256个字节// 构建数据cmd[0] = 0x02;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_WriteEnable();					// 写使能NorFLASH_ReadBusy();					// 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); 	// CS拉低SPI2_Transmit(cmd, 4);					// 写命令SPI2_Transmit(pData, 256);				// 写数据GPIO_SetBits(GPIOB, GPIO_Pin_12);		// CS拉高
}

6.5> 读数据

64
存储器地址范围内,任意地址都可以读数据;

/*** @brief 读Flash数据* @param PRxData 接收数据缓冲区* @param addr Flash起始地址* @param size 读size字节数据*/
void NorFLASH_Read(uint8_t *pRxData, uint32_t addr, uint32_t size)
{uint8_t cmd[4];// 构建数据cmd[0] = 0x03;cmd[1] = addr >> 16;cmd[2] = addr >> 8;cmd[3] = addr >> 0;NorFLASH_ReadBusy();					// 忙检测GPIO_ResetBits(GPIOB, GPIO_Pin_12); 	// CS拉低SPI2_Transmit(cmd, 4);					// 写命令SPI2_Receive(pRxData, size);GPIO_SetBits(GPIOB, GPIO_Pin_12);		// CS拉高
}

7> 测试程序

int main(void)
{ 	uint32_t i = 0;USART1_Init();NorFLASH_Init();GPIO_SetBits(GPIOB, GPIO_Pin_12);delay_ms();// 擦NorFLASH_EraseSector(0);for (i = 0; i < 4096; i++) {Wbuf[i] = 0x11;}// 写NorFLASH_WritePage(Wbuf, 0); // 写1个扇区,16页// 读NorFLASH_Read(Rbuf, 0x00, 256);// 串口打印for (i = 0; i < 256; i++) {UART_Putchar(Rbuf[i]);}while ( 1 ) {/* Nothing */;}}

7.1> 逻辑分析仪 抓波形

71

理解使用1个新外设时,看手册描述,例程,实验调试;
这把 逻辑分析仪 立了头功;

这篇关于STM32F1 - SPI读写Flash的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/777121

相关文章

C#读写文本文件的多种方式详解

《C#读写文本文件的多种方式详解》这篇文章主要为大家详细介绍了C#中各种常用的文件读写方式,包括文本文件,二进制文件、CSV文件、JSON文件等,有需要的小伙伴可以参考一下... 目录一、文本文件读写1. 使用 File 类的静态方法2. 使用 StreamReader 和 StreamWriter二、二进

MySQL主从复制与读写分离的用法解读

《MySQL主从复制与读写分离的用法解读》:本文主要介绍MySQL主从复制与读写分离的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、主从复制mysql主从复制原理实验案例二、读写分离实验案例安装并配置mycat 软件设置mycat读写分离验证mycat读

Redis分片集群、数据读写规则问题小结

《Redis分片集群、数据读写规则问题小结》本文介绍了Redis分片集群的原理,通过数据分片和哈希槽机制解决单机内存限制与写瓶颈问题,实现分布式存储和高并发处理,但存在通信开销大、维护复杂及对事务支持... 目录一、分片集群解android决的问题二、分片集群图解 分片集群特征如何解决的上述问题?(与哨兵模

一文彻底搞懂Java 中的 SPI 是什么

《一文彻底搞懂Java中的SPI是什么》:本文主要介绍Java中的SPI是什么,本篇文章将通过经典题目、实战解析和面试官视角,帮助你从容应对“SPI”相关问题,赢得技术面试的加分项,需要的朋... 目录一、面试主题概述二、高频面试题汇总三、重点题目详解✅ 面试题1:Java 的 SPI 是什么?如何实现一个

ShardingSphere之读写分离方式

《ShardingSphere之读写分离方式》:本文主要介绍ShardingSphere之读写分离方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录ShardingSphere-读写分离读写分离mysql主从集群创建 user 表主节点执行见表语句项目代码读写分

Dubbo之SPI机制的实现原理和优势分析

《Dubbo之SPI机制的实现原理和优势分析》:本文主要介绍Dubbo之SPI机制的实现原理和优势,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Dubbo中SPI机制的实现原理和优势JDK 中的 SPI 机制解析Dubbo 中的 SPI 机制解析总结Dubbo中

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Python实现高效地读写大型文件

《Python实现高效地读写大型文件》Python如何读写的是大型文件,有没有什么方法来提高效率呢,这篇文章就来和大家聊聊如何在Python中高效地读写大型文件,需要的可以了解下... 目录一、逐行读取大型文件二、分块读取大型文件三、使用 mmap 模块进行内存映射文件操作(适用于大文件)四、使用 pand

C# 读写ini文件操作实现

《C#读写ini文件操作实现》本文主要介绍了C#读写ini文件操作实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录一、INI文件结构二、读取INI文件中的数据在C#应用程序中,常将INI文件作为配置文件,用于存储应用程序的