一、W25Q128相关理论

  1. W25Q128存储大小为128M-bit=16MB,可编程位(地址)为Flash_Size=16*1024*1024=16777216 B。
  2. W25Q128包含256个块、每个块(64KB)16个扇区(4096个扇区)、每个扇区(4KB)有16页、每一页有256个字节(Byte)。
  3. 写数据:一次最多写一页不能跨页写入;擦除:可以选择擦除一个扇区(4KB)、擦除半个块(32KB)、擦除一个块(64KB)、擦除整个芯片。
  4. Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。
  5. 可以理解为将W25Q128看成一本电子书,这本书有256个章节,每个章节有16个小节,每个小节有16页,每页有256个字。

编程即对这本电子书进行编辑:

        读取数据:可以从指定位置开始一直读完这本书。

        写入数据:一次最多只能写一页256个字,不能翻页写,需要等待上一页写完才能翻页。写入数据前需要保证写入的位置是擦除状态,才能正确写入数据,因为只能1写成0,不能0写成1。

        擦除操作:有几种选择,可以每个小节、半个章节、整个章节、一本书进行擦除。

二、前期准备

1.硬件确定

我用到的是ALIENTEK战舰STM32F1 V3开发板,关于W25Q128硬件连接如下图:

2.STM32 Cube IDE配置

配置好之后生成代码就行了,然后再自己编辑一个驱动测试的c文件。

三、指令解析

1、用到的相关指令

指令 名称 解释
02h Page Program 页编程,在一页上写字
03h Read Data 读取数据
05h Read Status Register 读取寄存器状态
06h Write Enable 将状态寄存器中的写启用闩锁(WEL)位设置为1。 
20h Sector Erase  扇区擦除
C7h/60h Chip Erase 整个芯片擦除

2、读取设备ID(举一个例子,其他指令对照芯片手册看即可)

读取设备ID指令根据数据手册,发送0X90+24位地址之后,就可以接收到0XEF + ID,W25Q128的ID为0X17。read_W25Q128_ID()函数可以通过串口打印ID,或者通过单步调试Debug直接查看读到的ID值。

可以理解为MCU向W25Q128发送命令0x90 0x00 0x00 0x00 ,然后就可以接收到W25Q128的两个字节 0xEF 0x17

// 读取 ID 测试 OK 0xEF 0X17
void read_W25Q128_ID()
{uint8_t _RxData[2]={0x00};W25Q128_Enable();//发送指令spi2_Transmit_one_byte(0x90);spi2_Transmit_one_byte(0x00);spi2_Transmit_one_byte(0x00);spi2_Transmit_one_byte(0x00);//接收数据_RxData[0] = spi2_Receive_one_byte();_RxData[1] = spi2_Receive_one_byte();W25Q128_Disable();printf("%s\r\n",_RxData);  //串口打印 ID
}

2、读、写、擦除操作

读、写、擦除操作的24位地址取值范围是0-16777216,因为读可以从指定地址一直读到最后,而写,一次最多写一页,擦除的最小单位为一个扇区4096个字即16页,当然也可以一不做二不休整个芯片擦除,这个擦除时间比较长十几秒,因为是自学,所以总得做点什么。比如:

1、写10个数,卡在第一页和第二页之间,即第一页写5个数第二页写5个数。

2、写10个数,卡在第一个扇区和第二个扇区之间,即第255页写5个数第256页写5个                        数。

 问题点:第一个问题,就要考虑翻页写的问题,第二个问题就要考虑擦除两个扇区和翻页写的问题。

  解决思路:

1、通过地址定位到当页还剩下多少个字可以写,通过要写字的个数,分为几次写,写完当页后再翻页写到下一页,直到写完。相关函数:

Write_Page()        Write_Word()

2、因为要先进行擦除,然后再写数据,才能保证写入数据的准确性,可以直接擦除整个芯片,要想时间最快,擦除部分应该是最小的,所以选择擦除扇区。通过地址和要写字的个数,就可以判断,要写的地方在哪几个扇区,然后执行擦除即可。相关函数:

Erase_Write_data_Sector()        Erase_one_Sector()

四、驱动代码

W25Q28.c文件

#include "W25Q128.h"
#include "spi.h"
#include "usart.h"
#include <stdio.h>uint32_t FLASH_SIZE=16*1024*1024;  //FLASH 大小为16M字节
uint32_t Data_Address = 4090; //测试地址 250(地址在两页之间) 和 4090 (地址在两扇区并且两页之间)//要写的数据
uint8_t Write_data[]={0x30,0x31,0x32,0x33,0x34,0x35,0x36,0x37,0x38,0x41};
#define Write_data_SIZE sizeof(Write_data)//要读的数据
uint8_t Read_data[100] = {0};
#define Read_data_SIZE sizeof(Read_data)/* Nicky ******************************************************************* */
//器件使能
void W25Q128_Enable()
{HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, RESET); // Chip select
}/* Nicky ******************************************************************* */
//器件失能
void W25Q128_Disable()
{HAL_GPIO_WritePin(SPI_CS_GPIO_Port, SPI_CS_Pin, SET); // Chip disselect
}/* Nicky ******************************************************************* */
//SPI2 发送 1 个字节数据
void spi2_Transmit_one_byte(uint8_t _dataTx)
{HAL_SPI_Transmit(&hspi2,(uint8_t*) &_dataTx,1,HAL_MAX_DELAY);
}/* Nicky ******************************************************************* */
//SPI2 接收 1 个字节数据
uint8_t spi2_Receive_one_byte()
{uint16_t _dataRx;HAL_SPI_Receive(&hspi2,(uint8_t*) &_dataRx, 1, HAL_MAX_DELAY);return _dataRx;
}/* Nicky ******************************************************************* */
//W25Q128写使能,将WEL置1
void W25Q128_Write_Enable()
{W25Q128_Enable();                            //使能器件   spi2_Transmit_one_byte(0x06); W25Q128_Disable();                            //取消片选
}/* Nicky ******************************************************************* */
//W25Q128写失能,将WEL置0
void W25Q128_Write_Disable()
{W25Q128_Enable();                            //使能器件   spi2_Transmit_one_byte(0x04); W25Q128_Disable();                            //取消片选
}/* Nicky ******************************************************************* */
//读取寄存器状态
uint8_t W25Q128_ReadSR(void)
{  uint8_t byte=0;   W25Q128_Enable();                            //使能器件   spi2_Transmit_one_byte(0x05);    //发送读取状态寄存器命令byte=spi2_Receive_one_byte();             //读取一个字节W25Q128_Disable();                           //取消片选     return byte;
} /* Nicky ******************************************************************* */
//等待空闲
void W25Q128_Wait_Busy()
{   while((W25Q128_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}/* Nicky ******************************************************************* */
//擦除地址所在的一个扇区
void Erase_one_Sector(uint32_t Address)
{W25Q128_Write_Enable();                  //SET WEL      W25Q128_Wait_Busy();       W25Q128_Enable();                            //使能器件 spi2_Transmit_one_byte(0x20);      //发送扇区擦除指令 spi2_Transmit_one_byte((uint8_t)((Address)>>16));  //发送24bit地址    spi2_Transmit_one_byte((uint8_t)((Address)>>8));   spi2_Transmit_one_byte((uint8_t)Address);  W25Q128_Disable();                            //取消片选            W25Q128_Wait_Busy();                 //等待擦除完成
}/* Nicky ******************************************************************* */
//擦除地址所在的扇区
void Erase_Write_data_Sector(uint32_t Address,uint32_t Write_data_NUM)
{//总共4096个扇区//计算 写入数据开始的地址 + 要写入数据个数的最后地址 所处的扇区    uint16_t Star_Sector,End_Sector,Num_Sector;Star_Sector = Address / 4096;                       //数据写入开始的扇区End_Sector = (Address + Write_data_NUM) / 4096;        //数据写入结束的扇区Num_Sector = End_Sector - Star_Sector;              //数据写入跨几个扇区//开始擦除扇区for(uint16_t i=0;i <= Num_Sector;i++){Erase_one_Sector(Address);Address += 4095;}}/* Nicky ******************************************************************* */
//擦除整个芯片 等待时间超长... 10-20S
void Erase_W25Q128_Chip(void)
{                                   W25Q128_Write_Enable();                  //SET WEL W25Q128_Wait_Busy();   W25Q128_Enable();                            //使能器件   spi2_Transmit_one_byte(0x60);        //发送片擦除命令  W25Q128_Disable();                            //取消片选              W25Q128_Wait_Busy();                     //等待芯片擦除结束
} /* Nicky ******************************************************************* */
//读取W25Q128数据
void Read_W25Q128_data(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{ uint16_t i=0;                                            W25Q128_Enable();                     //使能器件   spi2_Transmit_one_byte(0x03);         //发送读取命令   spi2_Transmit_one_byte((uint8_t)((ReadAddr)>>16));  //发送24bit地址    spi2_Transmit_one_byte((uint8_t)((ReadAddr)>>8));   spi2_Transmit_one_byte((uint8_t)ReadAddr);   for(;i<NumByteToRead;i++){ pBuffer[i]=spi2_Receive_one_byte();   //循环读数  }W25Q128_Disable();
}/* Nicky ******************************************************************* */
//写字,一次最多一页
void Write_Word(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{uint16_t i; W25Q128_Write_Enable();                  //SET WELW25Q128_Enable();                            //使能器件spi2_Transmit_one_byte(0x02);spi2_Transmit_one_byte((uint8_t)((WriteAddr) >> 16)); //写入的目标地址   spi2_Transmit_one_byte((uint8_t)((WriteAddr) >> 8));   spi2_Transmit_one_byte((uint8_t)WriteAddr);   for (i = 0; i < NumByteToWrite; i++)spi2_Transmit_one_byte(pBuffer[i]);//循环写入字节数据  W25Q128_Disable();W25Q128_Wait_Busy();       //写完之后需要等待芯片操作完。
}/* Nicky ******************************************************************* */
//定位到页
void Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{uint16_t Word_remain;Word_remain=256-WriteAddr%256;   //定位页剩余的字数  if(NumByteToWrite <= Word_remain)Word_remain=NumByteToWrite;       //定位页能一次写完while(1){Write_Word(pBuffer,WriteAddr,Word_remain);   if(NumByteToWrite==Word_remain){break;    //判断写完就 break}  else //没写完,翻页了{pBuffer += Word_remain;     //直针后移当页已写字数WriteAddr += Word_remain; NumByteToWrite -= Word_remain; //减去已经写入了的字数if(NumByteToWrite>256)Word_remain=256;      //一次可以写入256个字else Word_remain=NumByteToWrite;  //不够256个字了}}
} /* Nicky ******************************************************************* */
// 读取 ID 测试 OK 0xEF 0X17
void read_W25Q128_ID()
{uint8_t _RxData[2]={0x00};W25Q128_Enable();//发送指令spi2_Transmit_one_byte(0x90);spi2_Transmit_one_byte(0x00);spi2_Transmit_one_byte(0x00);spi2_Transmit_one_byte(0x00);//接收数据_RxData[0] = spi2_Receive_one_byte();_RxData[1] = spi2_Receive_one_byte();W25Q128_Disable();printf("%s\r\n",_RxData);  //串口打印 ID
}/* Nicky ******************************************************************* */
//测试程序
void W25Q128_test()
{//读数据,看原始存在的数据Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE); for(uint8_t i=0;i<Write_data_SIZE;i++)printf("%c",Read_data[i]);printf("\r\n");   //擦除需要写数据所在的扇区Erase_Write_data_Sector(Data_Address,Write_data_SIZE);Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE);for(uint8_t i=0;i<Write_data_SIZE;i++)printf("%c",Read_data[i]);printf("\r\n");//写数据Write_Page(Write_data,Data_Address,Write_data_SIZE);Read_W25Q128_data(Read_data,Data_Address,Read_data_SIZE); //串口打印数据for(uint8_t i=0;i<Write_data_SIZE;i++)printf("%c",Read_data[i]);printf("\r\n");
}

W25Q28.h文件

#include "main.h"void read_W25Q128_ID();
void W25Q128_test();

五、测试结果

中间为擦除后读取的数据。

STM32 Cube IDE HAL库驱动 W25Q128 进行读、写、擦除操作相关推荐

  1. 【STM32】CubeMX+HAL库之 硬件IIC+DMA控制OLED(兼容SSD1306SH1106驱动)

    [STM32]CubeMX+HAL库之 硬件IIC+DMA控制1.3寸OLED 前言 目前网上大多数驱动OLED屏都采用软件IIC,因为HAL库的升级使得硬件IIC的稳定性得到了保障,所以想采用硬件I ...

  2. STM32 HAL库 驱动 MT6701 磁编码器

    写在前面: MT6701 是 MagnTek 推出的新一代基于差分霍尔感应原理的磁性角度编码器芯片.值得一提的是 MT6701不仅提供 0~360° 的角度信号,而且还提供了一个"按压&qu ...

  3. STM32如何配置HAL库

    STM32如何配置HAL库 前言   相比较早几年使用标准库开发来讲,最近几年HAL库的使用是越来越多,那么我们开发应当使用哪一种呢,本文着重介绍常用的几种开发方式及相互之间的区别,白猫也好.黑猫也好 ...

  4. stm32——手动移植HAL库以及错误解决方案(以STM32F103ZE为例)

    寄存器编程的缺点:代码可读性差,二次开发难度大,而且要每次都查阅用户手册,非常麻烦 HAL库:HAL库封装出了一层通用性的接口,标准化了一套通用性的接口,大大提高了代码的通用性 stm32CubeMX ...

  5. STM32CubeMX | STM32 F1系列HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例)

    STM32CubeMX | STM32 F1系列HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例) 目录 STM32CubeMX | STM32 F1系列HAL库低功耗 ...

  6. 【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题)

    [STM32笔记]HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒.串口唤醒和回调无法一起使用.接收数据不全的问题) [STM32笔记]低功耗模式配置及避坑汇总 前文: blog.csd ...

  7. 【STM32笔记】HAL库低功耗模式配置(ADC唤醒无法使用、低功耗模式无法烧录解决方案)

    [STM32笔记]HAL库低功耗模式配置(ADC唤醒无法使用.低功耗模式无法烧录解决方案) [STM32笔记]低功耗模式配置及避坑汇总 一.低功耗模式简介 系统提供了多个低功耗模式,可在 CPU 不需 ...

  8. RT-Thread studio配置can hal库 驱动GM6020

    RT-Thread studio配置can hal库 驱动GM6020 最近由裸机系统转战rt-thread,在刚入手RT-Thread的过程中记录一些东西吧,可能是刚做的原因,软件还不是那么完整好用 ...

  9. 【STM32】标准库与HAL库对照学习教程六--位带操作

    [STM32]标准库与HAL库对照学习教程六--位带操作 一.前言 二.准备工作 三.位带介绍 1.位带操作 2.STM32位带及位带别名区域 四.位带区与位带别名区地址转换 五.GPIO的位带操作 ...

最新文章

  1. antlr-2.7.6.jar的作用
  2. ArcGIS实现在线与线交叉处打断线(批量)
  3. Python基础教程(十一):多线程、XML解析
  4. 漫画算法:如何判断链表有环
  5. sweetalert php,SweetAlert插件
  6. mysql 数据复制停止工作_linux – Mysql GTID复制停止工作
  7. 易宝典——玩转O365中的EXO服务 之四十六 如何启用审核功能
  8. php判断浏览器和语言
  9. FPS游戏自动枪械识别+压枪(以PUBG为例)
  10. CSR是什么样的公司?CSR蓝牙芯片有何过人之处?
  11. iacr是什么会议_计算机国际安全顶级会议
  12. 睿智的目标检测32——TF2搭建YoloV4目标检测平台(tensorflow2)
  13. 文字点选验证码的破解方法~
  14. es拼音分词 大帅哥_elasticsearch实现中文分词和拼音分词混合查询+CompletionSuggestion...
  15. 复杂稀缺类分析:稀缺与不重要能否划等号?
  16. SQL员工信息表题目及答案
  17. 10款常用的原型设计工具,包含一键生成原型工具
  18. 目前最经典有效的“阅读方法”——SQ3R阅读法
  19. 欲望、外界、标签、天才、时间、经历、人生目标、后悔、和现实。转自特种兵—AK47
  20. 与门或门非门是计算机语言吗,什么叫与门、非门、或门

热门文章

  1. 【高速接口-RapidIO】6、Xilinx RapidIO核仿真与包时序分析
  2. 小学生学机器人编程有意义吗
  3. GD32E230开发环境搭建 keil无法识别芯片 jlink无法烧录
  4. iOS设置Label行间距和字间距
  5. 2023年美赛五大热点问题(赛前准备、报名、选题、评审、查询)全面解读!
  6. 快速构建智慧社区场景的一种技术方案
  7. asp前端日历_asp日历-和asp日历相关的内容-阿里云开发者社区
  8. return true return false 的区别
  9. 前端开发:项目运行npm install 提示XXX packages are looking for funding run `npm fund` for details的解决方法
  10. nvim代码格式化插件formatter.nvim