STM32 Cube IDE HAL库驱动 W25Q128 进行读、写、擦除操作
一、W25Q128相关理论
- W25Q128存储大小为128M-bit=16MB,可编程位(地址)为Flash_Size=16*1024*1024=16777216 B。
- W25Q128包含256个块、每个块(64KB)16个扇区(4096个扇区)、每个扇区(4KB)有16页、每一页有256个字节(Byte)。
- 写数据:一次最多写一页不能跨页写入;擦除:可以选择擦除一个扇区(4KB)、擦除半个块(32KB)、擦除一个块(64KB)、擦除整个芯片。
- Flash 有一个特点,就是可以将 1 写成 0,但是不能将 0 写成 1,要想将 0 写成 1,必须进行擦除操作。如果要改变数据,就需要先擦除后写数据。
- 可以理解为将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 进行读、写、擦除操作相关推荐
- 【STM32】CubeMX+HAL库之 硬件IIC+DMA控制OLED(兼容SSD1306SH1106驱动)
[STM32]CubeMX+HAL库之 硬件IIC+DMA控制1.3寸OLED 前言 目前网上大多数驱动OLED屏都采用软件IIC,因为HAL库的升级使得硬件IIC的稳定性得到了保障,所以想采用硬件I ...
- STM32 HAL库 驱动 MT6701 磁编码器
写在前面: MT6701 是 MagnTek 推出的新一代基于差分霍尔感应原理的磁性角度编码器芯片.值得一提的是 MT6701不仅提供 0~360° 的角度信号,而且还提供了一个"按压&qu ...
- STM32如何配置HAL库
STM32如何配置HAL库 前言 相比较早几年使用标准库开发来讲,最近几年HAL库的使用是越来越多,那么我们开发应当使用哪一种呢,本文着重介绍常用的几种开发方式及相互之间的区别,白猫也好.黑猫也好 ...
- stm32——手动移植HAL库以及错误解决方案(以STM32F103ZE为例)
寄存器编程的缺点:代码可读性差,二次开发难度大,而且要每次都查阅用户手册,非常麻烦 HAL库:HAL库封装出了一层通用性的接口,标准化了一套通用性的接口,大大提高了代码的通用性 stm32CubeMX ...
- STM32CubeMX | STM32 F1系列HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例)
STM32CubeMX | STM32 F1系列HAL库低功耗STOP和STANDBY模式唤醒(RTC时钟唤醒+外部中断唤醒示例) 目录 STM32CubeMX | STM32 F1系列HAL库低功耗 ...
- 【STM32笔记】HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒、串口唤醒和回调无法一起使用、接收数据不全的问题)
[STM32笔记]HAL库低功耗STOP停止模式的串口唤醒(解决进入以后立马唤醒.串口唤醒和回调无法一起使用.接收数据不全的问题) [STM32笔记]低功耗模式配置及避坑汇总 前文: blog.csd ...
- 【STM32笔记】HAL库低功耗模式配置(ADC唤醒无法使用、低功耗模式无法烧录解决方案)
[STM32笔记]HAL库低功耗模式配置(ADC唤醒无法使用.低功耗模式无法烧录解决方案) [STM32笔记]低功耗模式配置及避坑汇总 一.低功耗模式简介 系统提供了多个低功耗模式,可在 CPU 不需 ...
- RT-Thread studio配置can hal库 驱动GM6020
RT-Thread studio配置can hal库 驱动GM6020 最近由裸机系统转战rt-thread,在刚入手RT-Thread的过程中记录一些东西吧,可能是刚做的原因,软件还不是那么完整好用 ...
- 【STM32】标准库与HAL库对照学习教程六--位带操作
[STM32]标准库与HAL库对照学习教程六--位带操作 一.前言 二.准备工作 三.位带介绍 1.位带操作 2.STM32位带及位带别名区域 四.位带区与位带别名区地址转换 五.GPIO的位带操作 ...
最新文章
- antlr-2.7.6.jar的作用
- ArcGIS实现在线与线交叉处打断线(批量)
- Python基础教程(十一):多线程、XML解析
- 漫画算法:如何判断链表有环
- sweetalert php,SweetAlert插件
- mysql 数据复制停止工作_linux – Mysql GTID复制停止工作
- 易宝典——玩转O365中的EXO服务 之四十六 如何启用审核功能
- php判断浏览器和语言
- FPS游戏自动枪械识别+压枪(以PUBG为例)
- CSR是什么样的公司?CSR蓝牙芯片有何过人之处?
- iacr是什么会议_计算机国际安全顶级会议
- 睿智的目标检测32——TF2搭建YoloV4目标检测平台(tensorflow2)
- 文字点选验证码的破解方法~
- es拼音分词 大帅哥_elasticsearch实现中文分词和拼音分词混合查询+CompletionSuggestion...
- 复杂稀缺类分析:稀缺与不重要能否划等号?
- SQL员工信息表题目及答案
- 10款常用的原型设计工具,包含一键生成原型工具
- 目前最经典有效的“阅读方法”——SQ3R阅读法
- 欲望、外界、标签、天才、时间、经历、人生目标、后悔、和现实。转自特种兵—AK47
- 与门或门非门是计算机语言吗,什么叫与门、非门、或门
热门文章
- 【高速接口-RapidIO】6、Xilinx RapidIO核仿真与包时序分析
- 小学生学机器人编程有意义吗
- GD32E230开发环境搭建 keil无法识别芯片 jlink无法烧录
- iOS设置Label行间距和字间距
- 2023年美赛五大热点问题(赛前准备、报名、选题、评审、查询)全面解读!
- 快速构建智慧社区场景的一种技术方案
- asp前端日历_asp日历-和asp日历相关的内容-阿里云开发者社区
- return true return false 的区别
- 前端开发:项目运行npm install 提示XXX packages are looking for funding run `npm fund` for details的解决方法
- nvim代码格式化插件formatter.nvim