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]);
    }

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

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

相关文章

Java:控制流程 + 数组 详解(原理 + 用法 + 例子)

目录 控制流程块作用域if 条件语句for while 循环switch 多重选择break continue 中断控制流程语句 大数值数组多维数组字符串类型数组Array.sort() 数组排序for each 循环 控制流程 块作用域 块&#xff08;即复合语句&#xff09;是指由一对大括号{}括起来的若干条简单的 Ja…

断路器绝缘电阻试验

断路器 绝缘电阻试验 试验目的 检验断路器合闸后灭弧室、 主绝缘和提升杆是否发生受潮&#xff0c; 劣化变质等缺陷。 试验设备 绝缘电阻测试仪 厂家&#xff1a; 湖北众拓高试 试验接线 相对地 端口间 试验步骤 真空断路器本体与断口的绝缘电阻 试验前对兆欧表本身进行检…

C++服务器框架开发11——编译调试1/cmake学习

该专栏记录了在学习一个开发项目的过程中遇到的疑惑和问题。 其教学视频见&#xff1a;[C高级教程]从零开始开发服务器框架(sylar) 上一篇&#xff1a;C服务器框架开发10——日志系统1~9代码 C服务器框架开发11——编译调试1/cmake学习 目前进度ubuntu下的cmake学习简单样例同…

使用Django数据库模型中的ForeignKey()形成数据表记录的父子层次结构

可以把ForeignKey()的第1个参数设置为值 “self” 实际形成数据表记录的父子层次结构。 下面是一个简单的实例&#xff1a; 在文件 E:\Python_project\P_001\myshop-test\myshop\app1\models.py 中写入下面的代码&#xff1a; from django.db import models# Create your mod…

创建型模式

创建型模式&#xff08;Creational Pattern&#xff09;关注对象的创建过程&#xff0c;是一类最常用的设计模式&#xff0c;在软件开发中应用非常广泛。创建型模式将对象的创建和使用分离&#xff0c;在使用对象时无须关心对象的创建细节&#xff0c;从而降低系统的耦合度&…

叮,您有一份《C语言思维导图》,请注意查收

目录导航 &#x1f680; 前言&#x1f4fa;配套教程推荐&#x1f530;文章列表&#x1f4da;Part 1&#xff1a;初识C语言&#x1f4da;Part 2&#xff1a;分支和循环语句&#x1f4da;Part 3&#xff1a;函数&#x1f4da;Part 4&#xff1a;数组&#x1f4da;Part 5&#xff…

【数据结构】手撕排序NO.1----排序初识

目录 一. 前言 二. 排序的概念及运用 2.1 排序的概念 2.2 排序的运用 2.3 常见的排序算法 三. 冒泡and选择排序 3.1 冒泡排序 3.2 选择排序 四. 各大排序算法的复杂度和稳定性 一. 前言 从本期开始&#xff0c;我们的数据结构将迎来一个新的篇章&#xff1a;排序篇&#xff…

基于RASC的keil电子时钟制作(瑞萨RA)(1)----安装RASC

基于RASC的keil电子时钟制作_瑞萨RA_1安装RASC 概述硬件准备视频教程RA Smart Configurator软件下载RASC安装Keil下Renesas RA pack包安装 概述 RA Smart Configurator"是一种基于"灵活组合软件"概念的代码生成辅助工具。它可以自动生成微控制器的初始配置程序…

看见未来:定位咨询如何预测行业趋势

商业竞争时代&#xff0c;变化无处不在。科技日新月异&#xff0c;消费者需求日益多元&#xff0c;市场环境更加动态不定。在这个快速发展的时代&#xff0c;如果企业想要继续领先&#xff0c;就必须有能力预见未来&#xff0c;适应并驾驭这些变化&#xff0c;这就是定位咨询的…

【ElasticSearch】ES集群搭建、监控、故障转移

文章目录 1、ES集群介绍2、搭建ES集群3、集群状态监控4、集群职责及脑裂5、分布式新增和查询流程6、ES故障转移 1、ES集群介绍 单机的ES做数据存储与搜索&#xff0c;必然面临两个问题&#xff1a; 海量数据存储问题单点故障问题 因此&#xff0c;考虑使用ES集群&#xff1a…

LCD-STM32液晶显示中英文-(5.字符编码)

目录 字符编码 字符编码说明参考网站 字符编码 ASCII编码 ASCII编码介绍 ASCII编码表 中文编码 1. GB2312标准 区位码 2. GBK编码 3. GB18030 各个标准的对比说明 4. Big5编码 字符编码 字符编码说明参考网站 字符编码及转换测试&#xff1a;导航菜单 - 千千秀字 …

学习AJAX

AJAX &#x1f680; HTTP请求报文响应报文 &#x1f684; express框架&#x1f6ac; express基本使用 &#x1f692; 原生AJAX&#x1f6ac; GET.HTML&#x1f6ac; POST.HTML&#x1f6ac; JSON.HTML&#x1f6ac; nodemon工具可以帮助重启服务&#x1f6ac; IE缓存问题&#…

Devops系列五(CI篇之pipeline libraray)jenkins将gitlab helm yaml和argocd 串联,自动部署到K8S

一、说在前面的话 本文是CI篇的上文&#xff0c;因为上一篇已经作了总体设计&#xff0c;就不再赘述&#xff0c;有需要的请看前文。 我们将演示&#xff0c;使用CI工具–jenkins&#xff0c;怎么和CD工具–argocd串联&#xff0c;重点是在Jenkins该怎么做。准备工作和argocd等…

Java springBoot项目报LDAP health check failed

报错内容如下&#xff1a; 在bootstrap.yml文件里加 management:health:ldap:enabled: false 配置。 或者在application.properties文件里加&#xff1a; management.health.ldap.enabledfalse 参考答案&#xff1a;LDAP health check failed 难道没有人遇到这样的问题吗&…

TCP/IP基础知识笔记

应用层&#xff1a;为用户提供应用功能&#xff0c;比如 HTTP、FTP、Telnet、DNS、SMTP等。 应用层是工作在操作系统中的用户态&#xff0c;传输层及以下则工作在内核态。 传输层&#xff1a;为应用层提供网络支持。 *TCP包含众多特性比如流量控制、超时重传、拥塞控制等因此可…

【CPU】关于x86、x86_64/x64、amd64和arm64/aarch64

为什么叫x86和x86_64和AMD64? 为什么大家叫x86为32位系统&#xff1f; 为什么软件版本会注明 for amd64版本&#xff0c;不是intel64呢&#xff1f; x86是指intel的开发的一种32位指令集&#xff0c;从386开始时代开始的&#xff0c;一直沿用至今&#xff0c;是一种cisc指令…

LinkNet分割模型搭建

原论文&#xff1a;LinkNet: Exploiting Encoder Representations for Efficient Semantic Segmentation 直接步入正题~~~ 一、LinkNet 1.decoder模块 class DecoderBlock(nn.Module):def __init__(self, in_channels, n_filters): #512, 256super(DecoderBlock, self).__in…

linux kernel单独编译某项驱动

linux内核经常涉及编译某一项驱动代码的场景&#xff0c;本次以网卡驱动e1000为例说明整个步骤流程。 首先编译内核驱动不必要编译整个内核&#xff0c;但编译的驱动代码必须要和要安装的内核版本保持一致&#xff0c;否则经常会出现无法加载模块。 在编译驱动前&#xff0c;最…

大坝安全监测中需要做好检查监测

大坝安全监测是人们了解大坝运行状态和安全状况的有效手段和方法。它的目的主要是了解大坝安全状况及其发展态势&#xff0c;是一个包括由获取各种环境、水文、结构、安全信息到经过识别、计算、判断等步骤&#xff0c;最终给出一个大坝安全 程度的全过程。 此过程包括&#xf…

layui增删改查的实现

前言 在前三篇layui博客的基础上继续完善&#xff0c;这篇博客增加了数据表格来实现增删改查 这里要注意layui需要使用2.6以上的版本 dao方法的编写 package com.zking.dao;import java.util.List; import java.util.Map;import com.zking.entity.User; import com.zking.uti…