SPI编程——读写Flash芯片(W25Q64JV)

FLASH芯片介绍——W25Q64JV

W25Q64JV的芯片手册是英文的,用软件翻译了一下,有些地方翻译得不准确,大概了解一下即可,例如芯片的工作电压在2.7V到3.6V的电源上,电流消耗地至断电1uA,每个页面256字节,一次最多可编程256字节,通信接口有SPI

W25Q64JV常用指令集

32单片机通过SPI总线与W25Q64JV芯片通信,可通过发送指令控制W25Q64JV的相关操作,例如读写Flash存储器,清除存储内容等

指令 作用
0x06 写使能
0x04 写禁止
0x05 读状态寄存器1,可判断芯片是否准备接收下一条指令
0x03 读数据
0x02 页编程
0x20 扇区擦除
0xC7 芯片擦除
0x9F 读设备ID信息

指令详细作用要看数据手册

页编程指令——0x02

说明:

第1段:一次写入字节不能超过256字节(一页),在写入之前要先执行0xFF指令擦除内存;发送任何指令之前,都要先拉低CS引脚

第2段:这一段就说明一页最多256个字节,如果前几次写入的字节长度小于256字节,则剩下的空间还能继续存数据,这对前面存入的数据没影响;如果某一次写入的字节长度大于剩余的空间,256个字节已经装满数据了,则多出来的部分就会将开头的数据覆盖掉,造成开头页面数据丢失

第3段:在写入一个字节的最后一位后,必须将CS引脚拉高,这里需要注意,STM32自带有硬件SPI接口,但硬件的SPI接口CS引脚在传输完数据之后并不会自动拉高,一直是低电平,这不符合W25Q64JV芯片的时序要求,所以在初始化时,不使用硬件SPI的CS引脚,使用普通的GPIO口功能驱动W25Q64JV的CS引脚,通过编程拉低或者拉高CS引脚,达到芯片的时序要求,W25Q64JV的CS引脚是接到32单片机的SPI3_NSS引脚的,只是不使用这个NSS功能,用普通IO口

读状态寄存器指令0x05可以判断数据是否已经完全写入Flash芯片,没写完时BUSY位是1,写完后BUSY位变为0,就可以发送下一条指令

内部框图

页编程时序图

通过看时序图可以知道,W25Q64JV芯片支持SPI通信的模式3和模式0,如果用模式1和模式2的话,就会出错

这里使用的是模式0,在CLK的上升沿采集数据,数据是先发高位再发低位的

读数据指令——0x03

读取Flash数据就比较简单,也是要先将CS引脚拉低,发送0x03指令,再发送要读取的地址,在主机接受完数据后,要将CS引脚拉高

读取一个地址的数据后,地址指针会自动增加到下一个地址,所以可以连续读取数据

要注意读数据指令要在BUSY位为0时发送,否则会被芯片自动忽略

读数据时序图

CubeMX配置

GPIO配置

与Flash芯片CS引脚连接的单片机引脚为PA15,可复用为SPI3_NSS,这次配置不开启SPI3_NSS功能,用普通IO口PA15来输出高低电平

PA15配置为推挽输出,默认输出电平为高电平

SPI3配置

根据硬件电路图,选择SPI3

在模式中选择全双工主机,因为上边GPIO引脚没有复用为NSS,所以第二个选择不启用

其中模式选择有多种,一般32单片机作为主机,需要根据通信双方来进行选择

参数配置

CPOL和CPHA的选择可以组合成4种模式,CPOL可以选择0或者1,CPHA的1Edge表示奇数边采集数据,2Edge表示偶数边采集数据,对应了SPI通信总线介绍的4种不同模式;

因为W25Q64JV芯片是只支持模式3和模式0的,所以32也要配置为模式3或者模式0,其他模式会通信失败

CPOL = Low,CPHA = 1 Edge 时表示模式0

CPOL = High,CPHA = 2 Edge 时表示模式3

程序

SPI_Flash.h

头文件中定义CS引脚,根据W25Q64JV芯片的数据手册编写指令宏定义

//定义CS引脚
#define SET_SPI_Flash_CS    HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port,SPI_Flash_CS_Pin,GPIO_PIN_SET)
#define CLR_SPI_Flash_CS    HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port,SPI_Flash_CS_Pin,GPIO_PIN_RESET)//指令宏定义
#define     W25X_WriteEnable        0x06        //写使能
#define     W25X_WriteDisable       0x04        //写禁止
#define     W25X_ReadStatusRg1      0x05        //读状态寄存器1
#define     W25X_ReadData           0x03        //读数据
#define     W25X_PageProgram        0x02        //页编程
#define     W25X_SectorErase        0x20        //扇区擦除
#define     W25X_ChipErase          0xC7        //芯片擦除
#define     W25X_ReadJedecID        0x9F         //读设备ID#define     SPI_FLASH_PageSize      256         //页面最大字节长度
#define     Flash_Status1_BUSY      BIT0        //忙碌标志位
#define     Dummy_Byte              0xFF        //假数据

SPI_Flash.c

Flash写入一个字节函数

/*
* @name   SPI_Flash_WriteByte
* @brief  Flash写入一个字节
* @param  None
* @retval None
*/
static void SPI_Flash_WriteByte(uint8_t Byte)
{uint8_t SendByte = Byte;//等待模式写入一个字节HAL_SPI_Transmit(&hspi3,&SendByte,1,0x0A);
}

Flash读取一个字节函数

/*
* @name   SPI_Flash_ReadByte
* @brief  从Flash读取一个字节
* @param  None
* @retval 返回读到的字节
*/
static uint8_t SPI_Flash_ReadByte()
{uint8_t ReceiveByte;//等待模式读取一个字节,并判断函数执行是否正确,正确则返回读取到的字节,错误则返回错误数据if(HAL_SPI_Receive(&hspi3,&ReceiveByte,1,0x0A) != HAL_OK){ReceiveByte = Dummy_Byte;       //错误数据 }return ReceiveByte;
}

Flash写使能,在写入数据之前,要先调用该函数使能写操作

/*
* @name   SPI_Flash_WriteEnable
* @brief  Flash写使能
* @param  None
* @retval None
*/
static void SPI_Flash_WriteEnable()
{//选择Flash芯片:CS引脚输出低电平CLR_SPI_Flash_CS;//发送命令:写使能0x06SPI_Flash_WriteByte(W25X_WriteEnable);//禁用Flash芯片:CS引脚输出高电平SET_SPI_Flash_CS;
}

等待SPI数据写入完成,是通过判断读状态寄存器1的BUSY位,当芯片在读数据或者写数据时,BUSY位是1,当读完数据或者写完数据后,BUSY位是0

/*
* @name   SPI_Flash_WaitForWriteEnd
* @brief  等待SPI写入完成
* @param  None
* @retval None
*/
static void SPI_Flash_WaitForWriteEnd()
{uint8_t Flash_Status = 0;//选择Flash芯片:CS引脚输出低电平CLR_SPI_Flash_CS;//写入命令:读取状态寄存器1SPI_Flash_WriteByte(W25X_ReadStatusRg1);//等待数据写入完成,不断读取BUSY位状态,如果为1,则继续读,如果为0,则退出Timer6.usDelay_Timer = 0;do{Flash_Status = SPI_Flash_ReadByte();if(Timer6.usDelay_Timer >= TIMER_10s){break;}} while((Flash_Status&Flash_Status1_BUSY) == Flash_Status1_BUSY);//禁用Flash芯片:CS引脚输出高电平SET_SPI_Flash_CS;
}

扇区擦除函数,在写入数据之前,要先执行擦除操作

/*
* @name   SPI_Flash_EraseSector
* @brief  扇区擦除
* @param  SectorAddr:待擦除的地址
* @retval None
*/
static void SPI_Flash_EraseSector(uint32_t SectorAddr)
{//检测Flash是否处于忙碌状态SPI_Flash_WaitForWriteEnd();//Flash写使能,允许擦除SPI_Flash_WriteEnable();//选择Flash芯片:CS引脚输出低电平CLR_SPI_Flash_CS;//发送扇区擦除指令SPI_Flash_WriteByte(W25X_SectorErase);//发送擦除扇区地址的高字节SPI_Flash_WriteByte((SectorAddr & 0xFF0000) >> 16);//发送擦除扇区地址的中字节SPI_Flash_WriteByte((SectorAddr & 0x00FF00) >> 8);//发送擦除扇区地址的低字节SPI_Flash_WriteByte((SectorAddr & 0x0000FF));//禁用Flash芯片:CS引脚输出高电平SET_SPI_Flash_CS;//等待擦除完毕SPI_Flash_WaitForWriteEnd();printf("扇区擦除成功!\r\n");
}

写入页数据,一个页面存储的数据最多256个字节

因此该函数使用有风险

因为每一页最多只能写256个字节,如果某一次写入数据后,页里的字节超过了256,则超过的部分会将这一页最开始的地址数据覆盖,造成数据丢失

/*
* @name   SPI_Flash_WritePage
* @brief  写入页(256Bytes),写入长度不超过256字节
* @param  pWriteBuffer:待写入数据的指针WriteAddr:待写入的地址WriteLength:待写入的长度
* @retval None
*/
static void SPI_Flash_WritePage(uint8_t* pWriteBuffer,uint32_t WriteAddr,uint16_t WriteLength)
{//检测Flash是否处于忙碌状态SPI_Flash_WaitForWriteEnd();//Flash写使能,允许写入数据SPI_Flash_WriteEnable();//选择Flash芯片:CS引脚输出低电平CLR_SPI_Flash_CS;//发送扇区擦除指令SPI_Flash_WriteByte(W25X_PageProgram);//发送24位的地址//发送待写入地址的高字节SPI_Flash_WriteByte((WriteAddr & 0xFF0000) >> 16);//发送待写入地址的中字节SPI_Flash_WriteByte((WriteAddr & 0x00FF00) >> 8);//发送待写入地址的低字节SPI_Flash_WriteByte((WriteAddr & 0x0000FF));//判断数据长度if(WriteLength > SPI_FLASH_PageSize){WriteLength = SPI_FLASH_PageSize;printf("Flash每次写入的数据长度不能超过256个字节");}//写入数据while(WriteLength--){//写入一个字节SPI_Flash_WriteByte(*pWriteBuffer);//指向下一个字节缓冲区pWriteBuffer++;}//禁用Flash芯片:CS引脚输出高电平SET_SPI_Flash_CS;//等待写入数据完毕SPI_Flash_WaitForWriteEnd();
}

读取不固定长度数据,还有个写入不固定长度数据函数,代码篇幅太长,所以不展示

/*
* @name   SPI_Flash_ReadUnfixed
* @brief  读取不固定长度数据
* @param  pWriteBuffer:存放读取数据的缓存指针WriteAddr:待读取的地址WriteLength:读取数据的长度
* @retval None
*/
static void SPI_Flash_ReadUnfixed(uint8_t* pReadBuffer,uint32_t ReadAddr,uint32_t ReadLength)
{//检测Flash是否处于忙碌状态SPI_Flash_WaitForWriteEnd();//选择Flash芯片:CS引脚输出低电平CLR_SPI_Flash_CS;//发送命令,读取数据SPI_Flash_WriteByte(W25X_ReadData);//发送24位地址SPI_Flash_WriteByte((ReadAddr & 0xFF0000) >> 16);SPI_Flash_WriteByte((ReadAddr & 0x00FF00) >> 8);SPI_Flash_WriteByte((ReadAddr & 0x0000FF));//开始读取数据while(ReadLength--){//读取一个字节*pReadBuffer = SPI_Flash_ReadByte();//指向下一个字节缓冲区pReadBuffer++;}//禁用Flash芯片:CS引脚输出高电平SET_SPI_Flash_CS;
}

系统运行函数,调用SPI的相关函数,完成写入不定长数据和读取不定长数据的功能

如果写入数据的地址和读取数据的地址不一致则会读不到写入的数据

/*
* @name   Run
* @brief  系统运行
* @param  None
* @retval None
*/
static void Run()
{uint8_t i;uint8_t CMP_Flag = TRUE;//定义读写缓存uint8_t Tx_Buffer[] = "SPI通信——Flash芯片读写测试";const uint8_t BufferSize = sizeof(Tx_Buffer)/sizeof(Tx_Buffer[0]);uint8_t Re_Buffer[BufferSize] = {0};/********Flash芯片读写测试********///扇区擦除SPI_Flash.SPI_Flash_EraseSector(0x00000000);//写入不定长数据SPI_Flash.SPI_Flash_WriteUnfixed(Tx_Buffer,0x00000088,BufferSize);printf("写入的数据为:%s\r\n",Tx_Buffer);//读取不定长数据SPI_Flash.SPI_Flash_ReadUnfixed(Re_Buffer,0x00000088,BufferSize);printf("读取的数据为:%s\r\n",Re_Buffer);//读出的数据与写入的作比较for(i=0;i<BufferSize;i++){if(Tx_Buffer[i] != Re_Buffer[i]){CMP_Flag = FALSE;     //如果有不相同的数据,则比较标志位清零break;}}//输出比较结果if(CMP_Flag == TRUE){printf("Flash芯片读写数据测试成功!\r\n");}else{printf("Flash芯片读写数据测试失败!\r\n");}//延时HAL_Delay(1000);
}

实验效果

STM32物联网项目-SPI FLASH编程相关推荐

  1. STM32开发 -- W25Q32JV SPI FlASH详解

    如需转载请注明出处:https://juyou.blog.csdn.net/article/details/103168687 flash这部分也是很重要的一部分了. 我们将利用 STM32F1 自带 ...

  2. stm32 cubemx usb spi flash w25q128 u盘调试笔记

    真的太简单了,十分钟就搞定 参考文章 我卡住了几天,最后发现delay函数的问题,去掉就好了.(评论大佬解释了这一现象) 步骤如下 使用cube mx 生成基本代码 调试spi flash 调试usb ...

  3. STM32物联网项目-GPRS模块通信编程

    GPRS模块通信-编程 实验目的 32单片机通过串口2发送AT指令控制SIM800C检测GPRS网络,连接TCP服务器,连接服务器成功后,通过Doit.am远程信息转发服务将上传至公网服务器的温湿度值 ...

  4. STM32物联网项目-RTC时钟

    RTC时钟 RTC简介 实时时钟是一个独立的定时器.RTC模块拥有一组连续计数的计数器,在相应软件配置下,可提供时钟日历的功能.修改计数器的值可以重新设置系统当前的时间和日期. RTC模块和时钟配置系 ...

  5. STM32物联网项目-ADC采集实验板板温度(NTC热敏电阻)

    STM32 ADC采集板载温度 STM32 ADC简介 ​ STM32 拥有 1~3 个 ADC(STM32F101/102 系列只有 1 个 ADC),这些 ADC 可以独立使用, 也可以使用双重模 ...

  6. STM32物联网项目-GPRS模块介绍

    GPRS模块 SIM800C模块介绍 SIM800C模块可支持4频GSM/GPRS,工作的频段为:GSM850.EGSM900.DCS1800和PCS1900 MHz. 模块的尺寸只有17.6 * 1 ...

  7. STM32物联网项目-通过ESP12S模块连接TCP服务器

    通过ESP12S模块连接TCP服务器 可参考STC15实战的WiFi通信:http://t.csdn.cn/Aw0Uc ESP-12S模块 引脚功能定义 实验目标 STM32通过串口与ESP-12S模 ...

  8. STM32物联网项目-低功耗模式

    低功耗模式 电源框图 VDD供电区域一般为2V ~ 3.6V,经过电压调节器可降压到1.8V给CPU核心.存储器和内置数字外设供电,为了降低CPU的功耗, 后备供电区域可由电池供电,输入引脚为VBAT ...

  9. STM32物联网项目-DAC输出模拟量以及正弦波

    DAC输出正弦波 DAC介绍 ​ STM32 的 DAC 模块(数字/模拟转换模块)是 12 位数字输入,电压输出型的 DAC.DAC 可以配置为 8 位或 12 位模式,也可以与 DMA 控制器配合 ...

最新文章

  1. 计算机计算各科及格率,某两个班数学考试成绩如下,要求计算分析指标,用..._投资分析考试_帮考网...
  2. GPS定位系统源码二次开发就选专为二次开发而生的GPSBD...
  3. Socket通信---网络通信学习笔记(一)
  4. LeetCode 826. 安排工作以达到最大收益(map)
  5. MATLAB GUI的CreateFcn如何创建
  6. 对extern C的一点小认识
  7. 64位windows系统如何显示32位dcom组件配置
  8. extra 实现 别名,条件,排序等
  9. (44)System Verilog 类中变量随机激励约束
  10. php如何安装源码包,php源码包安装步骤是什么
  11. java从入门到精髓 - IO输入输出
  12. yytext显示html并编辑,YYText的使用
  13. (2)机械臂Simscape建模:模型导入MATLAB
  14. python工业机器人_工业机器人编程语言汇总!
  15. java int64 类型_详解 Java 的八大基本类型,写得非常好!
  16. UFS Host Controller工作流程
  17. 所谓五福临门中的五福
  18. Java工具类,随机生成(姓名,年龄,性别,密码,邮箱,地址,)
  19. 类似携程,飞猪机票列表滚动的日期带价格
  20. 阿里云科学家丁险峰:万物互联的价值在哪里?

热门文章

  1. 2022届秋招面经--秋招面试(4)
  2. 蓝牙耳机冷知识科普:蓝牙耳机版本对音质有什么影响吗?
  3. OpenHarmony-RK3568开发板操作流程
  4. 基于Matlab的脑功能网络工具箱 BCT FCLab
  5. 【 uniapp 】打包Android的apk(原生APP-云打包),及发布测试
  6. FFmpeg系列(二)—— 音视频裸流转换:mp3转pcm、h264转YUV
  7. 去除前后空白字符(包含半角空格,全角空格)
  8. CSS系列之文本换行
  9. pb 修改数据窗口种指定字段位置_PB数据窗口对象之字段的修改属性
  10. 考研跨考计算机推荐学校,跨考计算机有哪些院校推荐