017 - STM32学习笔记 - SPI读写FLASH(二)-flash数据写入与读取

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.mfbz.cn/a/40536.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于深度学习的高精度线路板瑕疵目标检测系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度线路板瑕疵目标检测系统可用于日常生活中来检测与定位线路板瑕疵目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的线路板瑕疵目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5…

计算机网络 day6 arp病毒 - ICMP协议 - ping命令 - Linux手工配置IP地址

目录 arp协议 arp病毒\欺骗 arp病毒的运行原理 arp病毒产生的后果&#xff1a; 解决方法&#xff1a; ICMP协议 ICMP用在哪里&#xff1f; ICMP协议数据的封装过程 ​编辑 为什么icmp协议封装好数据后&#xff0c;还要加一个ip包头&#xff0c;再使用ip协议再次进…

Docker 基础知识解析:容器与传统虚拟化对比:资源利用、启动时间、隔离性和部署效率

&#x1f337;&#x1f341; 博主 libin9iOak带您 Go to New World.✨&#x1f341; &#x1f984; 个人主页——libin9iOak的博客&#x1f390; &#x1f433; 《面试题大全》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33…

国赛线下开赛!全国智能车百度智慧交通创意组区域赛今日正式拉开帷幕!

“全国大学生智能汽车竞赛”是教育部倡导的大学生科技A类竞赛&#xff0c;中国高等教育学会将其列为含金量最高的大学生竞赛之一&#xff0c;为《全国普通高校大学生竞赛排行榜》榜单内赛事。飞桨共承办了百度完全模型组和百度智慧交通组两大赛道。全国大学生智能汽车竞赛百度智…

SpringBoot(七)Filter的使用

思考一个问题&#xff0c;服务端对于客户端的请求&#xff0c;真的应该照单全收吗&#xff1f;不是的。比如拿我们之前实现的用户注册功能来看&#xff0c;如果用户的昵称带了一些不友好的字母或汉字&#xff0c;你是给他过滤掉呢还是让他注册呢&#xff1f;毫无疑问&#xff0…

HTTP 请求走私漏洞(HTTP Request Smuggling)

一、什么是Http 请求走私漏洞&#xff1f; HTTP请求走私漏洞&#xff08;HTTP Request Smuggling&#xff09;是一种安全漏洞&#xff0c;利用了HTTP协议中请求和响应的解析和处理方式的不一致性。攻击者通过构造特定的恶意请求&#xff0c;以欺骗服务器和代理服务器&#xff0…

五、DQL-2.基本查询

一、数据准备 1、删除表employee&#xff1a; drop table employee; 2、创建表emp&#xff1a; 3、添加数据&#xff1a; 4、查看表数据&#xff1a; 【代码】 -- 查询数据--------------------------------------------------------- drop table emp;-- 数据准备-----------…

Ubuntu 的安装及其设置

文章目录 安装 Ubuntu屏幕分辨率设置修改软件源服务器锁屏时间设置设置 dash跨系统拖拽复制文件的设置 安装 Ubuntu 首先安装 VMware 虚拟机&#xff0c;虚拟机的安装比较简单&#xff0c;一步步点击Next即可完成安装。 安装完成后启动虚拟机&#xff0c;点击创建新的虚拟机。…

个人博客系统(二)

该博客系统共有八个页面,即注册页面、登录页面、添加文章页面、修改文章页面、我的博客列表页面、主页、查看文章详情页面、个人中心页面。 1 注册页面 该页面如图所示: 首先,要先判断注册的用户名、密码、确认密码以及验证码是否为空,若有一个为空,点击提交,则会提醒 …

“探索图像处理的奥秘:使用Python和OpenCV进行图像和视频处理“

1、上传图片移除背景后下载。在线抠图软件_图片去除背景 | remove.bg – remove.bg 2、对下载的图片放大2倍。ClipDrop - Image upscaler 3、对放大后的下载照片进行编辑。 4、使用deepfacelive进行换脸。 1&#xff09;将第三步的照片复制到指定文件夹。C:\myApp\deepfakeliv…

MFC第十六天 CFileDialog、CEdit简介、(线程)进程的启动,以及Notepad的开发(托盘技术-->菜单功能)

文章目录 CCommonDialogCFileDialogCEdit托盘技术进程的启动附录1:启动线程方式附录2:MFC对话框的退出过程 CCommonDialog 通用对话框 CCommonDialog 这些对话框类封装 Windows 公共对话框。 它们提供了易于使用的复杂对话框实现。 CFileDialog 提供用于打开或保存文件的标准对…

【前端】自制密码展示隐藏按钮

效果 一、前期准备 使用的图片是iconfront上拿的svg代码环境是Vue2 Element 二、创建组件 showPasswordAndclose <template><span class"show-password-container"><span v-if"chooseType CLOSE" click"changeType"><…

手机图片怎么转pdf格式?这几个图片转换方式了解一下

手机图片怎么转pdf格式&#xff1f;将图片转换为PDF的应用场景非常广泛。例如&#xff0c;你可以将多张照片转换为PDF&#xff0c;然后将其作为一本电子相册保存。你也可以将多张图片转换为PDF&#xff0c;然后将其作为一份报告或文档的附件发送给他人。此外&#xff0c;许多人…

数据结构双向链表,实现增删改查

一、双向链表的描述 在单链表中&#xff0c;查找直接后继结点的执行时间为O(1)&#xff0c;而查找直接前驱的执行时间为O(n)。为克服单链表这种单向性的缺点&#xff0c;可以用双向链表。 在双向链表的结点中有两个指针域&#xff0c;一个指向直接后继&#xff0c;另一个指向直…

Python应用实例(二)数据可视化(二)

数据可视化&#xff08;二&#xff09; 1.随机漫步1.1 创建RandomWalk类1.2 选择方向1.3 绘制随机漫步图1.4 模拟多次随机漫步1.5 设置随机漫步图的样式 1.随机漫步 使用Python来生成随机漫步数据&#xff0c;再使用Matplotlib以引人瞩目的方式将这些数据呈现出来。随机漫步是…

vscode远程连接提示:过程试图写入的管道不存在(删除C:\Users\<用户名>\.ssh\known_hosts然后重新连接)

文章目录 复现过程原因解决方法总结 复现过程 我是在windows上用vscode远程连接到我的ubuntu虚拟机上&#xff0c;后来我的虚拟机出了点问题&#xff0c;我把它回退了&#xff0c;然后再连接就出现了这个问题 原因 本地的known_hosts文件记录服务器信息与现服务器的信息冲突了…

reggie优化06-项目部署

1、部署架构 2、部署环境 3、部署前端 4、部署后端 修改图片位置&#xff0c;并push至仓库

【System Verilog and UVM基础入门17】Using get_next_item()

从小父亲就教育我&#xff0c;做一个对社会有用的人&#xff01; 关于握手协议的文章&#xff0c;网上有很多很多&#xff0c;这篇文章是最原滋原味的介绍&#xff0c;希望可以帮助到有缘人&#xff01; uvm_driver #(REQ,RSP) The base class for drivers that initiate req…

k8s如何访问 pod 元数据

如何访问 pod 元数据 **我们在 pod 中运行容器的时候&#xff0c;是否也会有想要获取当前 pod 的环境信息呢&#xff1f;**咱们写的 yaml 清单写的很简单&#xff0c;实际上部署之后&#xff0c; k8s 会给我们补充在 yaml 清单中没有写的字段&#xff0c;那么我们的 pod 环境信…

Python:基于matplotlib与mayavi的3D可视化(点云+等值面)

文章目录 一、3D可视化常用方法二、三维图像在numpy、cv2、以及tifffile.imread中通道的区别三、项目实战 1、基于matplotlib的3D可视化&#xff08;体素体&#xff09; 2、基于mayavi的3D可视化2.0、mayavi使用指南&#xff08;鼠标&#xff09;2.1、mlab.points3d()参数详解…