017 - STM32学习笔记 - SPI读写FLASH(二)

news2025/1/21 10:09:08

016 - STM32学习笔记 - SPI访问Flash(二)

上节内容学习了通过SPI读取FLASH的JEDEC_ID,在flash资料的指令表中,还看到有很多指令可以使用,这节继续学习使用其他指令,程序模板采用上节的模板。
在这里插入图片描述
在这里插入图片描述

为了方便起见,把这节需要用到的指令都可以宏定义出来:

/*FLASH 常用命令*/
#define WriteEnable 0x06			/* 写使能 */
#define WriteDisable 0x04			/* 写失能 */
#define ReadStatusReg 0x05			/* 读状态寄存器 */
#define WriteStatusReg 0x01			/* 写状态寄存器 */
#define ReadData 0x03			    /* 读数据 */
#define FastReadData 0x0B			/* 快速的读数据 */
#define FastReadDual 0x3B			/* 双倍速快读 */
#define PageProgram 0x02			/* 页写入 */
#define BlockErase 0xD8				/* 块擦除 */
#define SectorErase 0x20			/* 扇区擦除 */
#define ChipErase 0xC7				/* 芯片擦除 */
#define PowerDown 0xB9				/* flash掉电 */
#define ReleasePowerDown 0xAB	     /* 掉电复位 */
#define DeviceID 0xAB				/* 设备ID */
#define ManufactDeviceID 0x90		 /* 制造商ID */
#define JedecDeviceID 0x9F			 /* JedecDeviceID */

#define sFLASH_ID 0XEF4018			 /* JedecDeviceID宏定义 */
#define Dummy 0xFF					/* 任意数据 */

1、Flash上电、掉电

/**
  * @brief Flash进入掉电模式
  * @param 无
  * @retval 无返回值
  */
void SPI_Flash_PowerDown(void)
{
    SPI_FLASH_CS_LOW();								/* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(PowerDown);		 			 /* 发送掉电信号 */
    SPI_FLASH_CS_HIGH();							/* 开始通讯: CS 高电平 */
}
/**
  * @brief 将Flash从掉电模式唤醒
  * @param 无
  * @retval 无返回值
  */
void SPI_Flash_WakeUp()
{
    SPI_FLASH_CS_LOW();									/* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(ReleasePowerDown);	  			  /* 发送掉电复位信号 */
    SPI_FLASH_CS_HIGH();								/* 开始通讯: CS 高电平 */
}

2、擦除、读取数据

/**
  * @brief 写使能
  * @param 无
  * @retval 无
  */
void SPI_FLASH_Write_Enable(void)
{
    SPI_FLASH_CS_LOW(); 				/* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(WriteEnable);	 /* 发送写使能信号 */
    SPI_FLASH_CS_HIGH(); 				/* 停止通讯: CS 高电平 */
}
/**
  * @brief 擦除数据
  * @param 地址
  * @retval 无返回值
  */
void SPI_Flash_Erase(u32 addr)
{
    SPI_FLASH_Write_Enable();					/* 下发指令前,先写使能 */
    WateForReady();							   /* 等待Flash内部时序完成,主要是读芯片的状态字 */
    SPI_FLASH_CS_LOW();						    /* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(SectorErase);			 /* 发送擦除指令 */
    SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 ); /* 取16-23位 */
    SPI_FLASH_SendByte((addr & 0xFF00) >> 8);     /* 取8-15位 */
    SPI_FLASH_SendByte(addr & 0xFF);              /* 取0-7位 */
    SPI_FLASH_CS_HIGH(); 				         /* 停止通讯: CS 高电平 */
    WateForReady();
}
/**
  * @brief 整片擦除数据
  * @param 地址
  * @retval 无返回值
  * @attention 整片擦除时间比较耗时,具体擦除需要时间根据芯片容量大小而定
  */
void SPI_Flash_BulkErasse(void)
{
    SPI_FLASH_Write_Enable();           //写使能
    SPI_FLASH_CS_LOW();                 //开始通讯
    SPI_FLASH_SendByte(ChipErase);      //发送正片擦除指令
    SPI_FLASH_CS_HIGH();                //结束通讯
    
}
/**
  * @brief 读取数据
  * @param pdata:读取数据缓存
           addr:读取起始地址
           numByteToRead:读取数据数量
  * @retval 无返回值
  */
void SPI_Flash_ReadDate(u8* pdata,u32 addr,u32 numByteToRead)
{
    WateForReady();					        		 /* 等待Flash内部时序完成,主要是读芯片的状态字 */
    SPI_FLASH_CS_LOW();						  		 /* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(ReadData);					  /* 发送读取指令 */
    SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 );       /* 取16-23位 */
    SPI_FLASH_SendByte((addr & 0xFF00) >> 8);           /* 取8-15位 */
    SPI_FLASH_SendByte(addr & 0xFF);                    /* 取0-7位 */
    while(numByteToRead--)						      /* 循环读取数据 */
    {
        *pdata = SPI_FLASH_SendByte(Dummy);			   /* 发送Dummy任意数据,返回的数据就是读取到的数据 */
        pdata++;
    }
    SPI_FLASH_CS_HIGH(); 				       		  /* 停止通讯: CS 高电平 */
}

3、写入数据

/**
  * @brief 写入数据
  * @param pdata:写入数据缓存
           addr:写入起始地址
           numByteToWrite:写入数据数量
  * @retval 无
  */
void SPI_Flash_WriteData(u8* pdata,u32 addr,u32 numByteToWrite)
{
    WateForReady();					        		 /* 等待Flash内部时序完成,主要是读芯片的状态字 */
    SPI_FLASH_Write_Enable();						 /* 开始写入前先写使能 */
    SPI_FLASH_CS_LOW();						  		 /* 开始通讯: CS 低电平 */
    SPI_FLASH_SendByte(PageProgram);				  /* 下发写指令(页) */
    SPI_FLASH_SendByte((addr & 0xFF0000) >> 16 );       /* 取16-23位 */
    SPI_FLASH_SendByte((addr & 0xFF00) >> 8);           /* 取8-15位 */
    SPI_FLASH_SendByte(addr & 0xFF);                    /* 取0-7位 */
    while(numByteToWrite--)						      /* 循环写入数据 */
    {
        SPI_FLASH_SendByte(*pdata);			   		   /* 下发写入数据 */
        pdata++;
    }
    SPI_FLASH_CS_HIGH(); 				       		  /* 停止通讯: CS 高电平 */
}

4、测试例程

#include "stm32f4xx.h"
#include "bsp_led.h"
#include "bsp_systick.h"
#include "bsp_usart_dma.h"
#include "bsp_spi_flash.h"
#include <stdio.h>

u8 ReadBuffer[4096] = {0x00};			//读取数据缓冲区
u8 WriteBuffer[256] = {0x00};			//写入数据缓冲区
int main(void)
{
    u32 device_id = 0;
    u32 i = 0;
    LED_Config();
    DEBUG_USART1_Config();
    SysTick_Init();  
    SPI_GPIO_Config();
    printf("\r\n这是SPI读取FLASH_Device_ID的测试实验!\r\n");
    SPI_Flash_WakeUp();
    /* *********************** 读取Flash ID ************************** */
    device_id = SPI_FLASH_ReadID();
    printf("\r\ndevice_id = 0x%X\r\n",device_id);
    Delay_ms(1000);
    /* *********************** 擦除扇区 ************************** */
    SPI_Flash_Erase(0x00);								/* 擦除扇区 */
    SPI_Flash_ReadDate(ReadBuffer,0x00,4096);			  /* 擦除扇区后读取扇区内的数据,擦除动作是将扇区内寄存器全部置1 */
    printf("\r\n**************读出擦除后的数据****************\r\n");
    for(i = 0;i<4096;i++)
    {
        printf("0x%02x ",ReadBuffer[i]);				 /* 若擦除成功,则读取到的数据应该全部为oxFF */
    }
    for(i = 0;i<256;i++)								/* 向写入缓冲区数据写入数据 */
    {
        WriteBuffer[i] = i;
    }
    SPI_Flash_WriteData(WriteBuffer,0x00,256);		  /* 这里执行的是PageProgram指令,为页写入,一次只能写入256个数据 */
    SPI_Flash_ReadDate(ReadBuffer,0x00,256);		      /* 写入完成后,再读取出来 */
    printf("\r\n**************读出写入后的数据****************\r\n");
    for(i = 0;i<256;i++)
    {
        printf("0x%02x ",ReadBuffer[i]);				 /* 若写入成功,这里读取到的数据应该为0x00 ~ 0xFF */
    }
    SPI_Flash_PowerDown();								/* 操作完成后,发送掉电指令 */
    while(1)
    {
    }
}

在这里为了测试方便,我将第一次读取的数据亮也改成256个,方便截图,效果如下:
在这里插入图片描述

这里需要注意的是,在写入数据的时候,我们用的是PageProgram指令,该指令为页写入,每次最多只能写入1页数据,且数据最多为256个,而且这里写入是只能在单页写入,不能跨页写入,我测试过,起始地址改为0x20,写入256个数据,按道理最后一个写入地址应该是0x1FF,但是写入后再读取数据不对,后来查了一下,这里遇到的问题和I2C读写EEPROM的是一样的,我大概总结了一下,对Flash的数据写入分为以下几种:

1、写入首地址与页首地址相同:

       a、写入数据 ≤ 256 byte;

       b、写入数据 =  (n * 256) + m (n为页数,m为不满1页数据量,m < 256)

2、写入首地址与页首地址不同:

       a、写入数据 ≤ 256 byte (一页可以写完);

       b、写入数据 =  x+ (n * 256) + m(x为前端不满一页的数据量,x = 0时,表示字节对齐,n为页数,m为不满1页数据量,m < 256)

所以综上所述,写入数据量最终公式应该为:
W r i t e B u f f e r = x + ( n ∗ 256 ) + m WriteBuffer = x+ (n * 256) + m WriteBuffer=x+(n256)+m
因此这里将上面的void SPI_Flash_WriteData(u8* pdata,u32 addr,u32 numByteToWrite)在完善以下,写一个进阶版的void SPI_Flash_WriteBuffer(u8* pdata,u32 addr,u32 numByteToWrite)

void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{
    /* NumOfPage:   计算需要写入的页数;
     * NumOfSingle: 计算出不满一页时剩余的数据量
     * Addr:        写入地址与SPI_FLASH_PageSize求余,为0则与页首对齐;
     * count:       计算前端差多少数据可以与页首对齐;
     */
    u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
    Addr = WriteAddr % SPI_FLASH_PageSize;
    count = SPI_FLASH_PageSize - Addr;	
    NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
    NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;

    /* 情况1:页首对齐  */
    if (Addr == 0) 
    {
        /* 情况1.a :写入首地址与页首地址相同,写入数据 ≤ 256 byte */
        if (NumOfPage == 0) 
        {
            SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
        }
        else /* 情况1.b :写入首地址与页首地址相同 ,写入数据超过1页 */
        {
            while (NumOfPage--)     /* 将整页数据逐页写完 */
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            if(NumOfSingle !=0 )     /* 若尾端仍有数据,将剩余数据写完 */
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
        }
    }
    else /* 情况2:页首不对齐  */
    {
        if (NumOfPage == 0) /* 情况2.a :页首不对齐,写入数据 ≤ 256 byte */
        {
            /* 数据不超过256个,但是跨页,情况可在细分 */
            if (NumOfSingle > count)        /* 数据不超过256,但当首地址当页不能写完 */
            {
                temp = NumOfSingle - count;
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);     /* 先将首地址页数据写完 */
                WriteAddr +=  count;
                pBuffer += count;
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);      /* 下一页数据在写入 */
            }
            else /*数据不超过256个,且首地址当页能将所有数据写完 */
            {				
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
            }
        }
        else /* 情况2.b 首地址不对齐,且数据量超256个 */
        {
            NumByteToWrite -= count;
            NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;
            NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
            SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);
            WriteAddr +=  count;
            pBuffer += count;
            while (NumOfPage--)         /* 先写整页 */
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);
                WriteAddr +=  SPI_FLASH_PageSize;
                pBuffer += SPI_FLASH_PageSize;
            }
            if (NumOfSingle != 0)       /* 再写多出来的数据 */
            {
                SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);
            }
        }
    }
}

这里的写入数据实现和I2C向EEPROM写入数据基本是一致的,不懂得可以看一下I2C的内容。

最后,将在main函数中调用的测试程序贴出来:

	SPI_Flash_Erase(0x20);
    SPI_Flash_ReadDate(ReadBuffer,0x20,4096);
    printf("\r\n**************读出擦除后的数据****************\r\n");
    for(i = 0;i<4096;i++)
    {
        printf("0x%02x ",ReadBuffer[i]);
    }
    for(i = 0;i<4096;i++)
    {
        WriteBuffer[i] = i;
    }
    SPI_FLASH_BufferWrite(WriteBuffer,0x20,4096);
    SPI_Flash_ReadDate(ReadBuffer,0x20,4096);
    printf("\r\n**************读出写入后的数据****************\r\n");
    for(i = 0;i<4096;i++)
    {
        printf("0x%02x ",ReadBuffer[i]);
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/768254.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

uni-app中a标签下载文件跳转后左上角默认返回键无法继续返回

1.首先使用的是onBackPress //跟onShow同级别 onBackPress(option){ uni.switchTab({ url:/pages/....... return true }) }发现其在uni默认头部中使用是可以的 但是h5使用了"navigationStyle":"custom"后手机默认的返回并不可以&#xff0c; 2.经过查询…

autok3s k3d rancher研究

参考 功能介绍 | Rancher文档AutoK3s 是用于简化 K3s 集群管理的轻量级工具&#xff0c;您可以使用 AutoK3s 在任何地方运行 K3s 服务。http://docs.rancher.cn/docs/k3s/autok3s/_index 什么是 AutoK3s k3s是经过完全认证的 Kubernetes 产品&#xff0c;在某些情况下可以替…

Centos 7 使用国内镜像源更新内核

内核选择参考 此博文 &#xff1a;https://blog.csdn.net/alwaysbefine/article/details/108931626 elrepo官网介绍的内核升级方式为&#xff1a; 一、按文档执行引入 elrepo库&#xff1b; # 1、引入公钥 rpm --import https://www.elrepo.org/RPM-GPG-KEY-elrepo.org# 2、安…

数据中心水浸事件,该如何找回安全?

数据中心是现代企业和组织中不可或缺的基础设施&#xff0c;承载着大量的敏感数据和关键业务运作。然而&#xff0c;水浸事件可能成为数据中心的巨大威胁&#xff0c;可能导致设备故障、数据丢失以及业务中断&#xff0c;给组织带来严重的损失和风险。 因此&#xff0c;为了保护…

机器学习(十六):决策树

全文共18000余字&#xff0c;预计阅读时间约36~60分钟 | 满满干货&#xff0c;建议收藏&#xff01; 一、介绍 树模型是目前机器学习领域最为重要的模型之一&#xff0c;同时它也是集成学习中最常用的基础分类器。 与线性回归、逻辑回归等算法不同&#xff0c;树模型并不只是…

力扣707设计链表

你可以选择使用单链表或者双链表&#xff0c;设计并实现自己的链表。 单链表中的节点应该具备两个属性&#xff1a;val 和 next 。val 是当前节点的值&#xff0c;next 是指向下一个节点的指针/引用。 如果是双向链表&#xff0c;则还需要属性 prev 以指示链表中的上一个节点…

Spring Security OAuth2.0(5):Spring Security工作原理

文章目录 工作原理结构总览 认证流程授权流程AuthenticationProviderUserDetailsServicePasswordEncoder如何使用BCryptPasswordEncoder 授权流程授权流程授权决策 工作原理 结构总览 \qquad Spring Security 所解决的问题就是安全访问控制&#xff0c;而安全访问控制功能其实…

3.12 Bootstrap 超大屏幕(Jumbotron)

文章目录 Bootstrap 超大屏幕&#xff08;Jumbotron&#xff09; Bootstrap 超大屏幕&#xff08;Jumbotron&#xff09; 下面将讲解 Bootstrap 支持的另一个特性&#xff0c;超大屏幕&#xff08;Jumbotron&#xff09;。顾名思义该组件可以增加标题的大小&#xff0c;并为登陆…

MySQL-多表设计-一对一多对多

一对一 案例&#xff1a;用户 与身份证信息 的关系关系&#xff1a;一对一关系&#xff0c;多用于单表拆分&#xff0c;将一张表的基础字段放在一张表中&#xff0c;其它字段放在另一张表中&#xff0c;以提高操作效率实现&#xff1a;在任意一方加入外键&#xff0c;关联另一…

Linux小程序:倒计时和进度条

Linux小程序 在Linux中我们实现两个小程序来体会\r和\n的区别&#xff0c;以及缓冲区是什么&#xff1f; 文章目录 Linux小程序前言回车和换行的区别缓冲区 小程序的实现倒计时程序进度条程序 总结 前言 回车和换行的区别 对于 \r 和 \n 的理解&#xff1a; \n 表示换行且回…

创建git仓库连接上传全过程记录

1.初始化仓库 使用git init命令在一个新文件夹里初始化仓库 2.在github创建仓库 3.连接git仓库 采用命令是 git remote add origin 仓库地址4.添加文件进行测试 5.选择要上传的文件 一般选择git add .命令 6.提交文件到本地仓库 git commit -m "备注信息"7.…

RISCV - 1 RV32/64G指令集清单

RISCV - 1 RV32/64G指令集清单 1 RV32/64G指令类型2 RV32I 基本指令集3 RV64I基础指令集&#xff08;除了RV32I)4 RV32/RV64 Zifencei标准扩展5 RV32/RV64 Zicsr标准扩展6 RV32M标准扩展7 RV64M标准扩展&#xff08;除了RV32M)8 RV32A标准扩展9 RV64A标准扩展&#xff08;除了R…

php实现站群软件权限管理功能示例

1.管理员页面RBAC.php <!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title>权限管理</title> <script src"bootstrap/js/jquery-1.11.2.min.js"></script> </head>…

jenkins war包 centos启动安装指导

文章目录 步骤1&#xff1a;进入官网&#xff0c;下载到Jenkins的war包1.1 放置在指定位置1.2 放置安装包和创建文件放置路径1.3 检查环境1.4 配置启动命令和结束命令 步骤2&#xff1a; 启动后进入到Jenkins页面2.1 安装插件&#xff0c;例如流水线2.2 依然出现安装插件失败的…

ReWorks系统加载启动

1、通过网络或本地加载启动 配置tftp网络 网卡属性配置为100Mbps全双工 配置串口 目标板上电进入uboot 设置PC机IP、目标机IP、目标机MAC地址 加载文件并启动 固化系统镜像至SD卡 需支持挂载文件系统&#xff0c;并启动ftp服务 选择SD卡、文件系统、ftp服务 挂接SD卡 也可以…

动态规划---最大字段和

描述 给出 n 个整数序列&#xff08;可能为负数&#xff09;组成的序列 a1​, a2​,...,an​&#xff0c;求该序列形如 的子段和的最大值。当所有整数均为负数时&#xff0c;定义最大子段和为 0 。 输入描述 多测试用例。每个测试用例 2 行&#xff1a; 第一行是序列的个数…

Day977.除了授权码许可类型,OAuth 2.0还支持什么授权流程? -OAuth 2.0

除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f; Hi&#xff0c;我是阿昌&#xff0c;今天学习记录的是关于除了授权码许可类型&#xff0c;OAuth 2.0还支持什么授权流程&#xff1f;的内容。 授权码许可的流程最完备、最安全没错儿&#xff0c;但它…

将大模型集成到语音识别系统中的例子

概述 本文旨在探索将大型语言模型&#xff08;LLMs&#xff09;集成到自动语音识别&#xff08;ASR&#xff09;系统中以提高转录准确性的潜力。 文章介绍了目前的ASR方法及其存在的问题&#xff0c;并对使用LLMs的上下文学习能力来改进ASR系统的性能进行了合理的动机论证。 本…

【分布式缓存】springboot整合jetcache使用详解

目录 一、前言 二、多级缓存问题 2.1 缓存分类 2.1.1 本地缓存 2.1.2 分布式缓存 2.2 独立缓存的问题 2.2.1 缓存雪崩问题 2.2.2 对宽带压力大 2.2.3 运行效率低 2.3 多级缓存方案 2.3.1 多级缓存实践方案推荐 三、jetcache介绍 3.1 jetcache概述 3.2 jetcache 特…

手写代码系列

(1)手写clearfix .clearfix:after{content:; display:table;clear:both;} (2) 手写圣杯模型 (3)手写深拷贝 递归 const obj3={age:20,name:xxx,address:{} }, arr:[a,b,c] function deeepClone(obj={}){} (4)手写画图解释原型链(class的原型和本质)