提要:外部FLASH芯片据说通常用来放置字库,图形库,反正它的大内存让我可以避免在写程序时因为其中的数据过大而无法编译成功。
软件:KEIL C51、stc-isp-15xx-v6.87C
硬件:STC8A8K64S4A12、M25P80、0.96寸OLED

说明:本文章纯属个人学习笔记,当然也欢迎大家的指正,只不过一方面,外部FLASH的使用在CSDN上有不少文章,我这篇既不够深入,也可能讲错;再者我看大多数文章使用STM32和W25QXX系列为例进行讲解,而我由于硬件所限,开发板上是M25P80,我只好也用它,(不过这个影响倒是不大,原理都是一样的)。所以大家在学习外部FLASH的时候最好还是不要找这篇文章作为入门了。

最后,还是那样,若是有侵犯到相关权益,请联系我进行删改。

参考学习资料:
1,http://www.shendongmcu.com/texts/spi-bus-and-flash/spi-bus-and-flash.htm
2,https://blog.csdn.net/lalala098/article/details/81302579/
3,https://blog.csdn.net/zxh1592000/article/details/78759736
4,https://blog.csdn.net/qq_38405680/article/details/83048201
5,https://m.tb.cn/h.V8U6kox

目录:
1,M25P80的简单介绍
2,软件SPI和硬件SPI
3,M25P80的几个指令
4,读写M25P80的一般过程

1,M25P80的简单介绍
外部FLASH芯片在储存空间上一般有几个层级上的划分:整体(BULK)、块(BLOCK)、扇区(SECTOR)、页(PAGE)、字节(Byte)、位(bit)。而我们今天介绍的这款M25P80整体空间1Mbyte,也就是8Mbits(一字节有八位嘛),这1M字节没有进行块的划分,直接分成16个扇区(SECTOR),每个扇区256页(PAGE),每页256字节(Byte),然后你乘一乘,看看是不是1MByte。

在读写方面,FLASH很有意思,每次写入数据之前,应该要把相应的区间擦除,即全部变成“1”,因为写入数据的时候,只能写入“0”,而无法写入“1”;另外,擦除数据也是要一次擦除一大片,而这个“片”的量级示芯片而定,有的有块擦除、整体擦除、扇区擦除;有的只有扇区擦除、整体擦除,比如今天要介绍的这块M25P80。

在数据传输方面,M25P80采用SPI协议。
注:这种使用SPI协议的好像是串行FLASH,还有一种并行FLASH,引脚较多,请自行研究。

整块芯片的引脚如下:

/S:也叫CS,片选信号,低电平有效,在上电之后,在执行一条新的指令之前,必须让/CS管脚先有一个下降沿。
Q(MISO):为串行数据输出引脚,(maser input slave output)
/W:写保护管脚,,有效电平为低电平。高电平可读可写,低电平仅仅可读。
VSS:地
VCC:电源(2.7----3.6v)
/HOLD:保持管脚,低电平有效。当CS为低电平,并且把HOLD拉低时,数据输出管脚将保持高阻态,并且会忽略数据输入管脚和时钟管脚上的信号。把HOLD管脚拉高,器件恢复正常工作。
C:也即CLK,时钟引脚,为输入输出提供时钟脉冲。
D(MOSI): 为串行数据输入引脚,数据、地址和命令从D引脚输入到芯片内部。(master output slave input)

常见到的硬件原理图:

2,软件SPI和硬件SPI
这两种方法各有利弊,软件SPI不受引脚功能限制,普通的I/O口就可以,甚至我觉着可能只要满足输入或输出单方面的功能就行,比如CLK引脚只要输出就可以了。但是软件模拟SPI的程序较复杂一点,而且速度会比较慢。相比之下,硬件SPI引脚受限,不过速度快,程序简单。

软硬件SPI都要配置的几个点是:空闲时的时钟电平、在什么时候读取数据,又在什么时候发送数据。这个要引入“时钟极性 CPOL”和“时钟相位CPHA”的概念。详细内容请自查资料。而对于M25P80而言,支持CPOL和CPHA为“0 0”以及“1 1”的这两种情况,具体说来,就是单片机在下降沿接收FLASH发来的数据,在上升沿给FLASH发送数据,而空闲的时候,时钟电平可高可低。

软件模拟SPI:

//正确的应该是每发送一位,就要收回一位数据。然后还有一个要注意的
//的是,发送数据发生在上升沿,接收数据发生在下降沿,空闲时时钟电
//平是高还是低这个无所谓,FLASH都可以。
unsigned char SPI_WRITE_BYTE(unsigned char d)
{unsigned char val=0x80;unsigned char back_data = 0;unsigned char i = 0;FLASH_CLK_Set();//时钟空闲设为高电平while(val){       FLASH_CLK_Clr();  //时钟电平拉低,产生一个下降沿,主芯片接收到一位数据。//要注意的是,在这个while前,有一个高电平,这样在发送//数据的之前就接收了一位数据。这跟FLASH的读写特性有关。//有说这个地方要再加一个延时。if(d&val){FLASH_MOSI_Set();}else{FLASH_MOSI_Clr();}FLASH_CLK_Set();//时钟电平拉高,这时候产生一个上升沿,发出去一位数据val >>= 1;back_data = back_data << 1;back_data |= FLASH_MISO;}return back_data;
}

我之前写的函数是全部发送完之后再全部接收,现在看来显然不行。

硬件SPI:

//SPI初始化
//然后初始化SPI总线
void Init_SPI()
{P_SW1=0X00;                 //选择用哪套SPI引脚,STC8A8K64S4A12一共有四组SPI可以使用。
SPDAT = 0;
SPSTAT=0xc0;             //SPDAT.7和SPDAT.6写11,可以将中断标志清零。注意是写1才清零
SPCTL = 0xd0;            //  SSIG 1 开启主机模式 SPEN 1 SPI使能  DORD 0 先发最高位   MSTR 1 主机模式//  CPOL 0  SPICLK空闲时为低  CPHA  0 数据在SPICLK的前时钟沿驱动  时钟CPU_CLK/4
}//SPI为全双工通讯  所以在发送的同时可以接收到数
unsigned char SPI_WRITE_BYTE(unsigned char SPI_SendData)
{SPDAT= SPI_SendData;     // 将数据 写入
while((SPSTAT&0x80)==0); //等待写入完成
SPSTAT = 0xc0;           //清除中断标志,和写冲突标志,注意是对应位写1才能清零
return  SPDAT;           //返回得到的数据}//SPI时钟速率设置
void SPI_Speed(unsigned char speed)
{switch(speed)                  //每一次降速 都要先清为最高 在进行降速{case 0:   SPCTL&=0xFC;break;               //SysClk/4,SPR1=0,SPR0=0 case 1:    SPCTL&=0xFC; SPCTL|=0x01;break;   //SysClk/16,SPR1=0,SPRO=1case 2:  SPCTL&=0xFC; SPCTL|=0x02;break;     //SysClk/64,SPR1=1,SPR0=0case 3:    SPCTL&=0xFC; SPCTL|=0x03;break;     //SysClk/128,SPR1=1,SPR0=1}
}

这个说实话我也没有认真研究,详细内容需要查看芯片的技术手册,看看每个寄存器是干什么的就可以了。

3,M25P80的几个指令
M25P80的指令与常常提到的W25Q64几乎没什么区别,一定程度上还简化了,具体如下:

这里强烈建议看这篇推文(上面的参考学习资料里也有):https://blog.csdn.net/lalala098/article/details/81302579/
讲得十分详细了。

4,读写M25P80的一般过程
最后叙述一下使用FLASH的基本操作:就是简单的往里面写一点数据,再读出来,由于我还没有学习串口,因此还不懂得用电脑查看。暂时是将得到的数据显示到OLED上。

最开始一般把FLASH的指令宏定义一下,用起来简单直观,如下:

//M25P80常用指令表
#define WRITE_ENABLE 0x06
#define WRITE_DISABLE 0x04
#define READ_STATUS_REGISTER 0x05
#define WRITE_STATUS_REGISTER 0x01
#define READ_DATA_BYTES 0x03
#define READ_DATA_at_HIGHER_SPEED 0x0B
#define PAGE_PROGRAM 0x02
#define SECTOR_ERASE 0xD8
#define BULK_ERASE 0xC7
#define DEEP_POWER_DOWN 0xB9
#define RELEASE_from_DEEP_POWER_DOWN 0xAB
#define PAGE_SIZE 256
#define DUMMY_BYTE 0xFF //随便的

用到的几个基本函数
1,写使能函数

//写使能函数
void FLASH_WRITE_ENABLE(void)
{FLASH_CS_Clr();           //CS拉低SPI_WRITE_BYTE(WRITE_ENABLE);FLASH_CS_Set();           //CS拉高
}

在“擦除”指令、“页编程”指令、“写状态寄存器“指令之前,都要先用一下这个“写使能函数”。
逻辑:拉低CS,送入“写使能”指令,拉高CS。

2,读取状态寄存器的值

//读取FLASH状态寄存器的值
unsigned char FLASH_READ_STATUS_REGISTER(void)
{unsigned byte = 0;FLASH_CS_Clr();SPI_WRITE_BYTE(READ_STATUS_REGISTER);byte = SPI_WRITE_BYTE(DUMMY_BYTE);FLASH_CS_Set();return byte;
}


逻辑:拉低CS,送入“读取状态寄存器”指令,接收数据,拉高CS。
要注意的是,送完指令后立马返还回来的并不是我们所需要的,只有再随便发一字节,接收回来的才是状态寄存器的值。这个可以参考上面的时序图。

3,等待FLASH内部工作完成。

//等待FLASH内部工作完成
void FLASH_WAIT_for_END(void)
{while((FLASH_READ_STATUS_REGISTER() & 0x01) == 0x01);
}

这个“等待”函数是很有必要的,因为我们对FLASH芯片的每个操作都是要花时间的,如果上个指令还没有结束就送下个指令或是数据,怕是会出问题。而判断是否完成一般通过状态寄存器的第0位:“WIP”位(有的也叫BUSY)。其描述如下:

WIP bit. The Write In Progress (WIP) bit indicates whether the memory is busy with a Write Status Register, Program or Erase cycle. When set to 1, such a cycle is in progress, when reset to 0 no such cycle is in progress.

其中“1”表示正忙;“0”表示不忙。

4,擦除某一扇区

//擦除FLASH某一扇区
void FLASH_ERASE_SECTOR(unsigned long SECTOR_ADDRESS)
{FLASH_WAIT_for_END();FLASH_WRITE_ENABLE();FLASH_WAIT_for_END();FLASH_CS_Clr();SPI_WRITE_BYTE(SECTOR_ERASE);SPI_WRITE_BYTE((unsigned char)(SECTOR_ADDRESS >> 16));SPI_WRITE_BYTE((unsigned char)(SECTOR_ADDRESS >> 8));SPI_WRITE_BYTE((unsigned char)(SECTOR_ADDRESS));FLASH_CS_Set();FLASH_WAIT_for_END();
}

逻辑:等待前面工作完成,写使能,等待工作完成,拉低CS,送入“扇区擦除”指令,而后马上送入要擦除的扇区地址,拉高CS,等待工作完成。

关于这个地址,许多文章都说有24位,不过我觉着M25P8020位就够了,因为:1×16×256×256=1048576=2^20,他们提到的W25Q64可是有8MByte,多了一些。

5,擦除整体FLASH

//擦除整体FLASH
void FLASH_ERASE_BULK(void)
{FLASH_WAIT_for_END();FLASH_WRITE_ENABLE();FLASH_WAIT_for_END();FLASH_CS_Clr();SPI_WRITE_BYTE(BULK_ERASE);FLASH_CS_Set();FLASH_WAIT_for_END();
}

这个要花费的时间比较长。

6,单页写入

//单页写入,使用本函数前要擦除扇区,一次写入不得超过256字节
//WRITE_DATA_LENGTH:要写入的字节数
void FLASH_PROGRAM_PAGE(unsigned long WRITE_ADDRESS,unsigned char* WRITE_DATA,unsigned int WRITE_DATA_LENGTH)
{unsigned int i = 0;FLASH_WAIT_for_END();FLASH_WRITE_ENABLE();FLASH_WAIT_for_END();FLASH_CS_Clr();SPI_WRITE_BYTE(PAGE_PROGRAM);SPI_WRITE_BYTE((unsigned char)(WRITE_ADDRESS >> 16));SPI_WRITE_BYTE((unsigned char)(WRITE_ADDRESS >> 8));SPI_WRITE_BYTE((unsigned char)(WRITE_ADDRESS));if(WRITE_DATA_LENGTH > PAGE_SIZE){WRITE_DATA_LENGTH = PAGE_SIZE;}for(i = 0;i < WRITE_DATA_LENGTH;i ++){SPI_WRITE_BYTE(WRITE_DATA[i]);}FLASH_CS_Set();FLASH_WAIT_for_END();
}

逻辑:等待前面工作完成,写使能,等待完成,拉低CS,送入“页编程”指令,马上送入要写入数据的地址,再马上写入数据,拉高CS,等待工作完成。

7,任意页数写入

//上面那个函数是比较极端的情况,要求字节数小于你从开始地址到结束所包含的字
//节,因此还需一个普适的函数,可以在任意地址写入任意字节。(当然,也是要有限度的)。
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//WRITE_DATA:数据存储区
//WRITE_ADDRESS:开始写入的地址(24bit)
//WRITE_DATA_LENGTH:要写入的字节数(最大65535)
void FLASH_ANY_PROGRAM(unsigned long WRITE_ADDRESS,unsigned char* WRITE_DATA,unsigned int WRITE_DATA_LENGTH)
{                    unsigned int pageremain;      pageremain = 256 - WRITE_ADDRESS%256; //单页剩余的字节数  页写入是把整个空间用256个字节平分的   //如果给出一个地址 在当前页之中 那么要算出当前页后面没有写的字节数 先写入这部分字节if(WRITE_DATA_LENGTH <= pageremain){pageremain = WRITE_DATA_LENGTH;//不大于256个字节}while(1){      FLASH_PROGRAM_PAGE(WRITE_ADDRESS,WRITE_DATA,pageremain);if(WRITE_DATA_LENGTH == pageremain){break;//写入结束了}else {WRITE_DATA += pageremain;WRITE_ADDRESS += pageremain;  WRITE_DATA_LENGTH -= pageremain;             //减去已经写入了的字节数if(WRITE_DATA_LENGTH > 256){pageremain=256;   //一次可以写入256个字节}else {pageremain = WRITE_DATA_LENGTH;        //不够256个字节了}}}
}

8,读数据

//读数据函数
//READ_DATA_LENGTH:要读取的字节数
void FLASH_READ_DATA(unsigned long READ_ADDRESS,unsigned char* READ_DATA,unsigned int READ_DATA_LENGTH)
{unsigned int i = 0;FLASH_WAIT_for_END();FLASH_CS_Clr();SPI_WRITE_BYTE(READ_DATA_BYTES);SPI_WRITE_BYTE((unsigned char)(READ_ADDRESS >> 16));SPI_WRITE_BYTE((unsigned char)(READ_ADDRESS >> 8));SPI_WRITE_BYTE((unsigned char)(READ_ADDRESS));for(i=0;i<READ_DATA_LENGTH;i++){READ_DATA[i] = SPI_WRITE_BYTE(DUMMY_BYTE);   //循环读数  }FLASH_CS_Set();
}

读数据也很有意思,一旦开始,只要CS电平为低,它就一直读下去,直至把整片FLASH芯片数据读完(不过读完会怎样我也没试过)。所以一般读到我们想要的数据之后拉高CS电平就好了。

9,main.c

int main(void)
{unsigned char ccc[300];unsigned char ddd[300] = 0;unsigned char *p;unsigned char *qq;int t;for(t = 300;t >= 0;t --){ccc[300 - t] = t;}p = ccc;//或者是p = &ccc[0];qq = ddd;Init_SPI();OLED_Init();OLED_Clear();//FLASH_ERASE_BULK();FLASH_ERASE_SECTOR(0xF0000);FLASH_ANY_PROGRAM(0xF0000,p,300);FLASH_READ_DATA(0xF0000,qq,300);while(1){OLED_Clear();OLED_ShowChar(2,2,*(qq + 299) + 45);OLED_ShowChar(40,4,*(qq + 288) + 45);OLED_ShowChar(80,6,*(qq + 289) + 45);}
}

逻辑:准备两个数组:ccc和ddd,分别用来存放要发送的和要接收的数据。从得到的数据中挑几个显示在OLED上,结合OLED原有的字库,验证是否准确。

关于外部FLASH芯片的初步使用—以M25P80为例相关推荐

  1. STM32F103标准库开发---SPI实验---W25Qxx系列外部Flash芯片

    STM32F103标准库开发----目录 STM32F103标准库开发----SPI实验----基本原理 STM32F103标准库开发----SPI实验----底层驱动程序 W25Qxx全系列---- ...

  2. 【单片机笔记】基于STM32F103C8的 USB 外部flash虚拟U盘

    学习stm32已经很长时间了,但是一直没有过多的学习stm32的USB部分,因为实际工作还是用的比较少.说起USB那就有的说了,因为USB的功能很强大,这里主要重点记录一下STM32的USB部分,这个 ...

  3. STM32CubeMX学习笔记(48)——USB接口使用(MSC基于外部Flash模拟U盘)

    一.USB简介 USB(Universal Serial BUS)通用串行总线,是一个外部总线标准,用于规范电脑与外部设备的连接和通讯.是应用在 PC 领域的接口技术.USB 接口支持设备的即插即用和 ...

  4. STM32 IAP升级--内部FLASH和外部FLASH两种方式实现

    芯片型号STM32F103RET6,flash大小512K,起始地址0x08000000 一般说STM32内部FLASH就是指主存储器区域 [注]此实验中启动方式设置为复位后从主闪存存储器启动(BOO ...

  5. STM32F103标准库开发---SPI实验---读写 W25Q128 外部 Flash

    STM32F103标准库开发----目录 W25Q128读写----程序源码----点击下载 W25Qxx全系列数据手册-点击下载 一.实验前期准备 本次实验的 MCU 是 STM32F103C8T6 ...

  6. 使用 STM32 的 SPI 来读取外部 SPI FLASH 芯片(W25Qxx)

    SPI简介 SPI 是英语 Serial Peripheral interface 的缩写,顾名思义就是串行外围设备接口.是 Motorola 首先在其 MC68HCXX 系列处理器上定义的.SPI ...

  7. STM32H743+CubeMX-QSPI读写外部FLASH(W25Q128JVSQ)

    文章目录 一.前言 二.硬件电路 三.CubeMX 3.1.Clock Configuration 3.2.QUADSPI Parameter Settings 3.3.QSPI GPIO Setti ...

  8. 智能设备逆向工程之外部Flash读取与分析篇

    唐朝实验室 · 2015/10/19 11:19 author: rayxcp 0x00 前言 目前智能家居设备的种类很多,本文内容以某智能豆浆机为例完成对其的固件提取和分析. 究其分析内部逻辑的原因 ...

  9. 镁光256Gb NAND Flash芯片介绍

    总体概述 该芯片是一款典型的大容量NAND Flash存储颗粒,支持Open NAND Flash Interface (ONFI) 2.1的接口标准,采用ONFI NANDFlash的操作协议.该芯 ...

最新文章

  1. 新产品发布与A轮2000万美元 双喜临门后GrowingIO还要做什么
  2. 微生物组-宏基因组分析第9期(报名直播课免费参加线下2020.10本年最后一期)
  3. Linux 查看并删除.svn目录
  4. python 笔记 haversine (两个经纬度坐标之间的距离)
  5. Maven最全教程,还有哪里对maven不解的地方看过来!
  6. 【LeetCode笔记】20.有效的括号(Java、栈) 21. 合并两个有序链表(Java)
  7. 临床外显子组测序分析中的那些坑(下)
  8. 漫步数理统计五——条件概率与独立(上)
  9. debian关闭开机自动启动时候的gui
  10. 谁说AI看不懂视频?
  11. 使用脚本快速查看Linux系统信息
  12. android activity 测试,android – 最快的方法来创建一个模拟Activity来进行测试
  13. mysql触发器实例
  14. vs2018 设置了包含路径还是提示说打不开头文件
  15. 正则表达式lookahead and lookbehind zero-length assertions
  16. 微型计算机什么样子,微型计算机的组成有哪些 -价格怎么样?
  17. Cent OS 7 的日常操作
  18. 运行中的Docker容器添加映射端口
  19. conda安装包时提示当前用户没有权限
  20. 基于Java毕业设计疫情社区志愿者组织的资源管理平台源码+系统+mysql+lw文档+部署软件

热门文章

  1. 【调剂】淮阴工学院2020年硕士专业学位研究生调剂信息
  2. 『yeka』打开心灵——SD2.0大会更显大家风范
  3. STM32驱动LCD12864(串行模式)
  4. 绘声绘影快而省时的方法:使用小日本输出
  5. Hack TheGame 11关通关攻略
  6. 微信小程序 - - 地图及导航
  7. javaScript基础面试题 --数据类型和考题
  8. 图解 mysql 运行原理
  9. 铃木dl250参数_大为不同 #豪爵铃木DL250 ABS测评-基础篇
  10. 【020】翼辉信息董事长韩辉先生受北京工业大学邀请发表学术演讲