• STM32物联网项目-SPI FLASH编程


    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        //假数据
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

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

    /*
    * @name   SPI_Flash_WriteEnable
    * @brief  Flash写使能
    * @param  None
    * @retval None   
    */
    static void SPI_Flash_WriteEnable()
    {
        //选择Flash芯片:CS引脚输出低电平
        CLR_SPI_Flash_CS;
        //发送命令:写使能0x06
        SPI_Flash_WriteByte(W25X_WriteEnable);
        //禁用Flash芯片:CS引脚输出高电平
        SET_SPI_Flash_CS;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    等待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;
        //写入命令:读取状态寄存器1
        SPI_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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

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

    /*
    * @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");
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    写入页数据,一个页面存储的数据最多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();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

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

    /*
    * @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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    系统运行函数,调用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);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47

    实验效果

    在这里插入图片描述

  • 相关阅读:
    sftp 从windows10向linux(centos7)传输文件
    [附源码]JAVA毕业设计仁爱公益网站(系统+LW)
    一文详解爬楼梯
    近八成中国程序员起薪过万人民币,你过了么?
    编程大师-分布式
    webpack5基础--08_处理字体图标资源
    Linux ARM平台开发系列讲解(ADC) 2.9.2 ADC驱动调试方法
    基于西门子PLC的自动门控制装置设计
    k8s之创建基于sa的访问凭据kubeconfig文件
    一个c程序的内存分布
  • 原文地址:https://blog.csdn.net/weixin_46251230/article/details/126750744