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

news2024/12/23 15:03:53

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/772613.html

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

相关文章

为何异地销号这么难?这些注意事项要熟记!

最近有不少小伙伴私信小编&#xff0c;他们在网上办理的大流量手机号卡&#xff0c;用了一段时间之后想换其他的卡&#xff0c;所以想注销当前用的卡&#xff0c;但是注销的时候确实屡屡碰壁&#xff0c;程序还比较繁琐&#xff0c;有的甚至申请注销了几个月还注销不掉&#xf…

在Microsoft Excel中如何合并多个表格

如果你问那些处理数据的人,你会知道合并 Excel 文件或合并工作簿是他们日常工作的一部分。 Power Query 是将多个 Excel 文件中的数据合并或组合到一个文件中的最佳方式。你需要将所有文件存储在一个文件夹中,然后使用该文件夹将这些文件中的数据加载到高级查询编辑器中。它…

了解kubernetes部署:namespace和Node设置

节点及namespace的设置 kubectlcreate-f/opt/kubernetes/namespaces.yaml 通过此命令我们创建了如下namespace: ns-elasticsearch:elasticsearch相关  ns-rabbitmq:rabbitmq相关  ns-javashop&#xff1a;javashop应用相关 接下来我们要根据具体情况安排各个节点的部署规划…

CSS科技感四角边框

实现效果:使用before和after就可以实现,代码量不多,长度颜色都可以自己调整 <!DOCTYPE html> <html lang="en"> <head><meta charset="UTF-8"><title>Title</title><style>*{margin:0;padding:0;}html,body{…

OBS录制双屏

1.设置视频分辨率&#xff0c;假如要录制两个1920x1080分辨率的屏幕&#xff0c;那就把需要录制的分辨率改为3840x10802. 添加显示器采集 3.点击开始录制 4.最终效果

python_PyQt5开发股票指定区间K线操作工具_裸K

目录 写在前面&#xff1a; 工具使用演示&#xff1a; 代码&#xff1a; 导入包 横坐标控件、K线控件、带查询下拉列表控件 K线图控件 主界面代码 执行代码 写在前面&#xff1a; 继前面文章提到筛出低位股票后&#xff0c;想逐一查看这些股票今年的K线走势&#xff…

香港视频直播服务器需要多大的带宽(带宽计算方式)

​  香港视频直播服务器需要多大的带宽(怎么计算带宽大小)。目前短视频行业兴起&#xff0c;有许多人也想利用香港服务器搭建一个直播平台&#xff0c;但无奈不知道怎么选择资源大小&#xff0c;或者说什么样的配置能够满足直播的需求。关于直播的带宽大小和流量消耗的计算同…

记录一次抓取WiFi驱动日志以及sniffer日志

起因 路由器桥接一个WiFi&#xff0c;然后设备连接这个路由器的WiFi&#xff0c;发现网络不可用&#xff0c;而手机或者电脑连接就没问题&#xff0c;与供应商沟通问题&#xff0c;需要抓取日志&#xff0c;记录一下 抓取WLAN DRIVER WLAN FW3日志 进入开发者模式打开启动WL…

hive常用方法

日期类 Date_sub 日期进行加减 &#xff0c;正的减&#xff0c;负的加 select current_date -- 当前日期,date_sub(current_date,1) -- 前一日,date_sub(current_date,-1) -- 后一日 from edw.test;字符类 split 该函数是分割字符串 &#xff0c;按照…

2023 年中国大学生计算机设计大赛上海决赛区正式开启!

中国大学生计算机设计大赛&#xff08;下文简称“大赛”&#xff09;是由教育部认证、我国高校面向本科生最早的赛事之一&#xff0c;自 2008 年开赛起&#xff0c;至今已是第十六届。大赛属于全国普通高校大学生竞赛排行榜榜单赛事&#xff0c;由教育部高校与计算机相关的教指…

结构型模式 - 组合模式

概述 对于这个图片肯定会非常熟悉&#xff0c;上图我们可以看做是一个文件系统&#xff0c;对于这样的结构我们称之为树形结构。在树形结构中可以通过调用某个方法来遍历整个树&#xff0c;当我们找到某个叶子节点后&#xff0c;就可以对叶子节点进行相关的操作。可以将这颗树理…

MySql 优化实例:修改 cross join 方式为子查询方式,以求改变执行计划

MySql 优化实例:修改 cross join 方式为子查询方式,以求改变执行计划 问题来源问题的追溯尝试使用索引排除日志表,验证查询速度变形查询指令修改程序中的调用指令对原有查询条件进行位置调整事后总结in 的使用初学者建议执行计划问题来源 问题内容出自问答:https://ask.cs…

【Hydro】HBV-light模型介绍及下载

HBV-light模型 HBV模型是一种模拟流域径流的半分布式水文模型。 什么是HBV-light&#xff1f; HBV模型软件除了原版&#xff08;版本由S. Bergstrm1976年开发&#xff09;之外还有很多不同版本。HBV-light在其先前版本中已在乌普萨拉大学开发&#xff08;并在俄勒冈州州立大…

业务开发“银弹” ——低代码开发平台

一、现状 低代码开发平台要让每个人&#xff0c;包括开发者和普通业务人员&#xff0c;都能够成为企业数字化过程中的主导者和构建者&#xff01;让普通人更容易上手&#xff01; 基于这一目标&#xff0c;应用需求多的云服务商成为低代码投资的主要来源。一家云服务商如谷歌云…

性能测试需求分析怎么做?(中)

本系列文章我们为大家系统地介绍一下性能测试需求分析&#xff0c;让大家全面掌握性能测试的第一个环节。本系列文章将会从性能测试需求分析整体概述、性能测试需求分析内容、性能测试需求分析方法这三个方面进行展开。在&#xff08;上&#xff09;部分中&#xff0c;我们为大…

linux之Ubuntu系列(六)用户管理 终端命令 which 查看执行命令所在的位置

提示 /etc/passwd 是用于保存用户信息的文件 可以用cat 命令查看 cat /etc/passwd/usr/bin/passwd 是用于修改用户密码的 程序 &#xff0c;是程序 程序 &#xff0c; which 命令 可以查看执行命令所在的位置 # 输出 /bin/ls which ls # 输出 /usr/sbin/useradd which useradd…

安达发|某大厂使用APS计划排程真实成功案例

在很多群里、朋友圈、公众号上可以看到&#xff0c;很多精益咨询老师认为&#xff0c;不仅ERP不啥用&#xff0c;APS更是无聊之举&#xff0c;而且肯定是用不好的。但&#xff0c;事实上可能还真不是这样的。 一个深圳的客户&#xff0c;用了APS以后&#xff0c;不仅装配的齐套…

【AI绘画】AI绘画乐趣:稳定增强扩散技术展现

目录 前言一、Stable Diffusion是什么&#xff1f;二、安装stable-diffusion-webui1. python安装2. 下载模型3. 开始安装&#xff1a;4. 汉化&#xff1a;5. 模型使用&#xff1a;6. 下载新模型&#xff1a;7. 基础玩法 三、总结 前言 本文将借助stable-diffusion-webui项目来…

[sinlinx-v3s]mke2fs

简介 mke2fs命令是Linux中的一个磁盘格式化命令&#xff0c;用于创建一个新的ext2、ext3或ext4文件系统。它可以将一个分区或者一个整个磁盘设备格式化为ext2、ext3或ext4文件系统&#xff0c;以便在Linux系统中进行数据存储和管理。 mke2fs命令的作用是按照指定的文件系统类…

java项目之足球赛会管理系统(ssm+mysql+jsp)

风定落花生&#xff0c;歌声逐流水&#xff0c;大家好我是风歌&#xff0c;混迹在java圈的辛苦码农。今天要和大家聊的是一款基于ssm的足球赛会管理系统。技术交流和部署相关看文章末尾&#xff01; 项目地址&#xff1a; https://download.csdn.net/download/sinat_26552841…