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

相关文章

10. 文件的读写

10.1 文本文件 操作文件三大类: ofstream:写操作ifstream:读操作fstream:读写操作 打开方式解释ios::in为了读文件而打开文件ios::out为了写文件而打开文件,如果当前文件存在则清空当前文件在写入ios::app追加方式写文件ios::trunc如果文件存在先删除,在创建ios::ate打开文件之后令读写位置移至文件尾端ios::binary二进制方式

【STM32】SPI通信-软件与硬件读写SPI

SPI通信-软件与硬件读写SPI 软件SPI一、SPI通信协议1、SPI通信2、硬件电路3、移位示意图4、SPI时序基本单元(1)开始通信和结束通信(2)模式0---用的最多(3)模式1(4)模式2(5)模式3 5、SPI时序(1)写使能(2)指定地址写(3)指定地址读 二、W25Q64模块介绍1、W25Q64简介2、硬件电路3、W25Q64框图4、Flash操作注意事项软件SPI读写W2

什么是 Flash Attention

Flash Attention 是 由 Tri Dao 和 Dan Fu 等人在2022年的论文 FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness 中 提出的, 论文可以从 https://arxiv.org/abs/2205.14135 页面下载,点击 View PDF 就可以下载。 下面我

STM32内部闪存FLASH(内部ROM)、IAP

1 FLASH简介  1 利用程序存储器的剩余空间来保存掉电不丢失的用户数据 2 通过在程序中编程(IAP)实现程序的自我更新 (OTA) 3在线编程(ICP把整个程序都更新掉) 1 系统的Bootloader写死了,只能用串口下载到指定的位置,启动方式也不方便需要配置BOOT引脚触发启动  4 IAP(自己写的Bootloader,实现程序升级) 1 比如蓝牙转串口,

STM32 ADC+DMA导致写FLASH失败

最近用STM32G070系列的ADC+DMA采样时,遇到了一些小坑记录一下; 一、ADC+DMA采样时进入死循环; 解决方法:ADC-dma死循环问题_stm32 adc dma死机-CSDN博客 将ADC的DMA中断调整为最高,且增大ADCHAL_ADC_Start_DMA(&hadc1, (uint32_t*)adc_buffer, ADC_Buffer_Size); 的ADC_Bu

关于使用cspreadsheet读写EXCEL表格数据的问题

前几天项目有读写EXCEL表格的需求,我就找了大概有几种,大致分为:COM方法、ODBC方法、OLE方法、纯底层格式分析方法。由于COM方法要求必须安装有OFFICE的EXCEL组件,纯底层格式分析方法又很多功能需要自行去完善,所有最终选择了数据库的方法,用数据库的方法去存取xls格式的数据。网上有一个高手写的CSpreedSheet,看了一下提供的接口,感觉挺好用的。在使用的过程中发现几个

linux 内核提权总结(demo+exp分析) -- 任意读写(四)

hijack_modprobe_path篇 本文转自网络文章,内容均为非盈利,版权归原作者所有。 转载此文章仅为个人收藏,分享知识,如有侵权,马上删除。 原文作者:jmpcall 专栏地址:https://zhuanlan.kanxue.com/user-815036.htm     原理同hijack_prctl, 当用户执行错误格式的elf文件时内核调用call_usermod

linux 内核提权总结(demo+exp分析) -- 任意读写(三)

hijack_prctl篇 本文转自网络文章,内容均为非盈利,版权归原作者所有。 转载此文章仅为个人收藏,分享知识,如有侵权,马上删除。 原文作者:jmpcall 专栏地址:https://zhuanlan.kanxue.com/user-815036.htm   prctl函数: 用户态函数,可用于定制进程参数,非常适合和内核进行交互 用户态执行prctl函数后触发prctl系统

linux 内核提权总结(demo+exp分析) -- 任意读写(二)

hijack_vdso篇 本文转自网络文章,内容均为非盈利,版权归原作者所有。 转载此文章仅为个人收藏,分享知识,如有侵权,马上删除。 原文作者:jmpcall 专栏地址:https://zhuanlan.kanxue.com/user-815036.htm     vdso: 内核实现的一个动态库,存在于内核,然后映射到用户态空间,可由用户态直接调用 内核中的vdso如果被修改

linux 内核提权总结(demo+exp分析) -- 任意读写(一)

cred篇 本文转自网络文章,内容均为非盈利,版权归原作者所有。 转载此文章仅为个人收藏,分享知识,如有侵权,马上删除。 原文作者:jmpcall 专栏地址:https://zhuanlan.kanxue.com/user-815036.htm   每个线程在内核中都对应一个线程结构块thread_infothread_info中存在task_struct类型结构体 struct t