017 - STM32学习笔记 - SPI读写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.hqwc.cn/news/28412.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Python 算法基础篇:深度优先搜索( DFS )和广度优先搜索( BFS )

Python 算法基础篇&#xff1a;深度优先搜索&#xff08; DFS &#xff09;和广度优先搜索&#xff08; BFS &#xff09; 引言 1. 深度优先搜索&#xff08; DFS &#xff09;算法概述2. 深度优先搜索&#xff08; DFS &#xff09;算法实现实例1&#xff1a;图的 DFS 遍历实例…

MonoDTR Monocular 3D Object Detection with Depth-Aware Transformer 论文学习

论文链接&#xff1a;MonoDTR: Monocular 3D Object Detection with Depth-Aware Transformer 1. 解决了什么问题&#xff1f; 单目 3D 目标检测对于自动驾驶很重要&#xff0c;也很有挑战性。 现有的一些方法通过深度预测网络得到深度信息&#xff0c;然后辅助 3D 检测&…

MySQL约束和查询

约束和查询 1. 约束1.1 约束类型1.2 常用的约束 2. 查询2.1 聚合查询2.1.1 聚合函数2.1.2 GROUP BY2.1.3 HAVING 2.2 联合查询2.2.1 内连接2.2.2 外连接 2.3 合并查询 1. 约束 1.1 约束类型 NOT NULL - 指示某列不能存储 NULL 值。UNIQUE - 保证某列的每行必须有唯一的值。DE…

Python自动获取字母站视频

如果有疑问的话可以在我的谈论群&#xff1a;706128290 来找我 目录 前言 二、编写代码 1.引入库 2.编写主类 3. 自动获取cookies值和生成headers 4.获取命令行参数 运行效果 前言 browser_cookie3 第三方模块 browser_cookie3是browser_cookie模块的分支&#xff0c;…

小白到运维工程师的自学之路 第五十四集 (ansible自动化运维工具)

一、概述 Ansible是一种开源的自动化工具&#xff0c;用于自动化任务的执行、配置管理和应用部署。它采用基于Python编写的简单、轻量级的语法&#xff0c;可以通过SSH协议远程管理和配置多台计算机。 Ansible的主要特点包括&#xff1a; 1、简单易用&#xff1a;设计简单&a…

Redis数据结构 — List

目录 链表结构设计 ​编辑链表节点结构设计 链表的优势与缺陷 Redis 的 List 对象的底层实现之一就是链表。C 语言本身没有链表这个数据结构的&#xff0c;所以 Redis 自己设计了一个链表数据结构。 链表结构设计 typedef struct list {//链表头节点listNode *head;//链表…

顺序结构

基本概念顺序表实现Arraylist类使用&#xff1a;小小练习 基本概念 数据结构&#xff1a;描述和组织数据的方式。 数据结构的两种分类方式逻辑结构和物理结构&#xff1a; 逻辑结构&#xff1a;数据元素之间的相互关系&#xff1b;逻辑结构分以下四种。 1&#xff1a;集合&…

Ubuntu查找并安装指定版本包

命令如下: #查看指定包信息 apt-cache show kubeadm | grep 1.23.2 #安装指定版本 apt-get install kubeadm1.23.2-00

浙大数据结构第四周之04-树5 Root of AVL Tree

题目详情&#xff1a; An AVL tree is a self-balancing binary search tree. In an AVL tree, the heights of the two child subtrees of any node differ by at most one; if at any time they differ by more than one, rebalancing is done to restore this property. Fi…

安卓进程间通信浅谈

Case: /Users/lucas/AndroidStudioProjects/aidldemo-master 一&#xff1a;操作系统 从操作系统原理去看&#xff0c;进程通信主要有三个方法&#xff1a;共享存储、消息传递、管道通信。 二&#xff1a;安卓中的IPC 进程间通信的几种方式&#xff1a;Intent&#xff08;Bu…

计网笔记--应用层

目录 1--网络程序的组织方式和关系 2--动态主机配置协议&#xff08;DHCP&#xff09; 3--域名系统DNS 4--文件传输协议FTP 5--电子邮件 7--万维网WWW 7-1--HTTP的报文格式 7-2--Cookie 7-3--万维网缓存和代理服务器 1--网络程序的组织方式和关系 网络应用程序在各种…

7.12~7.13学习总结

public static void main(String[] args){File dirnew File("D:\\小花花");boolean flag dir.mkdir();System.out.println(flag);File dirsnew File("D:\\小花花\\你爸爸");dirs.mkdirs();String[]adir.list();//列出下一级&#xff1b;字符串数组for(Stri…