你的浏览器版本过低,可能导致网站不能正常访问!
为了你能正常使用网站功能,请使用这些浏览器。

STMCubeMx入门教程(6) SPI 读写FLAH的应用

[复制链接]
STMCU小助手 发布时间:2021-1-6 09:47
STMCubeMx入门教程(6) SPI 读写FLAH的应用
导语“本教程将使用CubeMX初始化SPI,使用SPI对W25Q64 FLASH进行读写操作,通过HAL库的读写应用来数据FLASH的操作细节。”
01
------------ 系统要求-----------------
•硬件
野火指南者开发板
•软件
CubeMx & MDK & 串口调试助手
•原理图
1.1.png
根据原理图,我们看到FLASH连接在SPI1上,我们不适用SPI1自带片选,使用PC0开进行软件片选。
02
------第二节 CubeMx配置-----------------
(1) 我们还是使用前面的USART串口(串口的配置没有变化)项目,在此基础上进行SPI1 的配置:
1.2.png
我们从配置的信息上看,使用了SPI1,主从全双工模式,8位数据传输,高字节在前,模式3,CRC不校验,软件控制。SPI的模式配置看下图,采样时刻是偶数边沿所以CLOCK Phase =2:
1.3.png
(2) 完成片选信号的GPIO配置
1.4.png
完成上述配置后点击代码生成。
03
------------第三节MDK 配置--------------
将CubeMx生成的代码使用MDK打开进行应用代码编写:
在spi.h 中进行FLASH操作的指令宏定义:
  1. //指令表
  2. #define W25X_WriteEnable                     0x06
  3. #define W25X_WriteDisable                     0x04
  4. #define W25X_ReadStatusReg                     0x05
  5. #define W25X_WriteStatusReg              0x01
  6. #define W25X_ReadData                            0x03
  7. #define W25X_FastReadData                     0x0B
  8. #define W25X_FastReadDual                     0x3B
  9. #define W25X_PageProgram                     0x02
  10. #define W25X_BlockErase                     0xD8
  11. #define W25X_SectorErase                     0x20
  12. #define W25X_ChipErase                            0xC7
  13. #define W25X_PowerDown                            0xB9
  14. #define W25X_ReleasePowerDown       0xAB
  15. #define W25X_DeviceID                     xAB
  16. #define W25X_ManufactDeviceID       0x90
  17. #define W25X_JedecDeviceID              0x9F
  18. // others defined
  19. #define sFLASH_ID    0XEF4017
  20. #define Dummy_Byte   0XFF
  21. #define SPI_FLASH_PageSize                    256
  22. #define SPI_FLASH_PerWritePageSize        256
  23. #define SPI1_TIME_OUT   
复制代码

并且申明应用的操作函数:
  1. void SPI_FLASH_SectorErase(uint32_t SectorAddr);
  2. uint32_t  SPI_Flash_ReadID(void);
  3. void SPI_Flash_Erase_Chip(void);
  4. void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer);
  5. void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
  6. void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
复制代码

下面我们在spi.c 中实现读写FLASH的相关函数:
(1) 对片选的信号引脚进行宏定义操作:
  1. #define SPI_FLASH_CS_H()   HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET)
  2. #define SPI_FLASH_CS_L()   HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)
复制代码

根据原理图使用的PC0引脚
(2)SPI 读写个字节函数
  1. /**function: SPI 读一个数据**/
  2. uint8_t SPI1_ReadByte(void)
  3. {                     
  4.        uint8_t RxData;      
  5.                         
  6.        HAL_SPI_Receive(&hspi1, &RxData, 1, SPI1_TIME_OUT);
  7.                                              
  8.        return RxData; //返回通过SPIx接收的数据                                       
  9. }
复制代码


我们使用了HAL封装的HALSPIReceive(&hspi1,&RxData, 1, SPI1TIMEOUT)函数来实现读一个字节。
(3)写一个字节
  1. /**function: SPI 写一个数据**/
  2. void SPI1_WriteByte(uint8_t TxData)
  3. {                                            
  4.        HAL_SPI_Transmit(&hspi1, &TxData, 1, SPI1_TIME_OUT);  //通过外设SPIx发送一个数据                                                                     
  5. }
复制代码
(4)FLASH的写使能和非使能
  1. /**function: SPI_FLASH写使能,将WEL置位**/
  2. void SPI_FLASH_Write_Enable(void)   
  3. {
  4.        SPI_FLASH_CS_L();                            //使能器件   
  5.        SPI1_WriteByte(W25X_WriteEnable);      //发送写使能  
  6.        SPI_FLASH_CS_H();                            //取消片选                  
  7. }

  8. /**function: SPI_FLASH写禁止,将WEL清零**/
  9. void SPI_FLASH_Write_Disable(void)   
  10. {  
  11.        SPI_FLASH_CS_L();                            //使能器件   
  12.        SPI1_WriteByte(W25X_WriteDisable);     //发送写禁止指令   
  13.        SPI_FLASH_CS_H();                           //取消片选                  
  14. }
复制代码

(5) 读取SPI_FLASH的状态寄存器,通过这个函数我们可以判断FLASH的状态,一般用到的忙和空闲两种状态,这儿也实现了等待空闲函数。
  1. /**
  2. function: 读取SPI_FLASH的状态寄存器
  3. **/
  4. //
  5. //BIT7  6   5   4   3   2   1   0
  6. //SPR   RV  TB BP2 BP1 BP0 WEL BUSY
  7. //SPR:默认0,状态寄存器保护位,配合WP使用
  8. //TB,BP2,BP1,BP0:FLASH区域写保护设置
  9. //WEL:写使能锁定
  10. //BUSY:忙标记位(1,忙;0,空闲)
  11. //默认:0x00
  12. uint8_t SPI_Flash_ReadSR(void)   
  13. {  
  14.        uint8_t byte=0;   
  15.        SPI_FLASH_CS_L();                       //使能器件   
  16.        SPI1_WriteByte(W25X_ReadStatusReg);           //发送读取状态寄存器命令   
  17.        byte = SPI1_ReadByte();                           //读取一个字节  
  18.        SPI_FLASH_CS_H();                       //取消片选     
  19.        return byte;   
  20. }

  21. /**
  22. function: 等待空闲
  23. **/
  24. void SPI_Flash_Wait_Busy(void)   
  25. {   
  26.        while ((SPI_Flash_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
  27. }
复制代码
(6)读取芯片ID W25Q64的ID函数,这个函数也是一般判断是否FLASH正常的方式。查看手册可以知道ID是0XEF4017
  1. /**
  2. function: 读取芯片ID W25Q64的ID
  3. **/
  4. uint32_t  SPI_Flash_ReadID(void)
  5. {
  6.        uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
  7.        SPI_FLASH_Write_Enable();
  8.        SPI_FLASH_CS_L();               
  9.        SPI1_WriteByte(W25X_JedecDeviceID);//发送读取ID命令           
  10.        Temp0 = SPI1_ReadByte();      
  11.        Temp1 = SPI1_ReadByte();              
  12.        Temp2 = SPI1_ReadByte();      
  13.   /*把数据组合起来,作为函数的返回值*/
  14.        Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
  15.        SPI_FLASH_CS_H();              
  16.        return Temp;
  17. }
复制代码

(7) flash 扇区擦除函数,FLASH和EEPROM不同,在写之前一定要进行擦除,否则不能正常写入。这儿实现了扇区擦除和正片擦除两个函数:
  1. /**
  2. function: 擦除整个芯片
  3. **/
  4. //整片擦除时间:
  5. //W25X16:25s
  6. //W25X32:40s
  7. //W25X64:40s
  8. //等待时间超长...
  9. void SPI_Flash_Erase_Chip(void)   
  10. {                                             
  11.        SPI_FLASH_Write_Enable();               //SET WEL
  12.        SPI_Flash_Wait_Busy();   
  13.        SPI_FLASH_CS_L();                      //使能器件   
  14.        SPI1_WriteByte(W25X_ChipErase);        //发送片擦除命令  
  15.        SPI_FLASH_CS_H();                      //取消片选                  
  16.        SPI_Flash_Wait_Busy();                                                //等待芯片擦除结束
  17. }  
  18. /**
  19. function: SPI_FLASH_SectorErase
  20. annotation:Flash的一个扇区是4K,所以输入的地址要和4K对其。
  21. **/
  22. void SPI_FLASH_SectorErase(uint32_t SectorAddr)
  23. {
  24.        SPI_FLASH_Write_Enable();
  25.        SPI_Flash_Wait_Busy();      
  26.        SPI_FLASH_CS_L();
  27.        SPI1_WriteByte(W25X_SectorErase);
  28.        SPI1_WriteByte((SectorAddr & 0xFF0000) >> 16);
  29.        SPI1_WriteByte((SectorAddr & 0xFF00) >> 8);
  30.        SPI1_WriteByte(SectorAddr & 0xFF);
  31.        SPI_FLASH_CS_H();      
  32.        SPI_Flash_Wait_Busy();
  33. }
复制代码
(8) 读取FLASH函数,FLASH的读操作步骤很简单
  1. /**
  2. function: 读取SPI FLASH  
  3. **/
  4. //在指定地址开始读取指定长度的数据
  5. //pBuffer:数据存储区
  6. //ReadAddr:开始读取的地址(24bit)
  7. //NumByteToRead:要读取的字节数(最大65535)
  8. void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer)   
  9. {
  10.        uint16_t i;                                                                                            
  11.        SPI_FLASH_CS_L();                          //使能器件   
  12.        SPI1_WriteByte(W25X_ReadData);                       //发送读取命令   
  13.        SPI1_WriteByte((uint8_t)((ReadAddr)>>16));  //发送24bit地址   
  14.        SPI1_WriteByte((uint8_t)((ReadAddr)>>8));
  15.        SPI1_WriteByte((uint8_t)ReadAddr);
  16.        for(i=0;i
  17.        {
  18.               pBuffer[i] = SPI1_ReadByte();   //循环读数  
  19.        }
  20.        SPI_FLASH_CS_H();       //取消片选
  21. }
复制代码

(9) flash 的按页写入数据,FLASH的数据的写入不是随机可以任意写入,按页写入,一页最大的数据是256个字节。
  1. /**
  2. function: SPI在一页(0~65535)内写入少于256个字节的数据
  3. annotation:一页最大256个字节
  4. **/
  5. //在指定地址开始写入最大256字节的数据
  6. //pBuffer:数据存储区
  7. //WriteAddr:开始写入的地址(24bit)
  8. //NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
  9. void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
  10. {
  11.          uint16_t i;
  12.        SPI_FLASH_Write_Enable();                                       //SET WEL
  13.        SPI_FLASH_CS_L();                                         //使能器件
  14.        SPI1_WriteByte(W25X_PageProgram);                                  //发送写页命令
  15.        SPI1_WriteByte((uint8_t)((WriteAddr)>>16));               //发送24bit地址
  16.        SPI1_WriteByte((uint8_t)((WriteAddr)>>8));
  17.        SPI1_WriteByte((uint8_t)WriteAddr);
  18.        for(i=0;i<NumByteToWrite;i++) SPI1_WriteByte(pBuffer[i]);       //循环写数
  19.        SPI_FLASH_CS_H();                                                        //取消片选
  20.        SPI_Flash_Wait_Busy();                                                                        //等待写入结束                                                                                    
  21. }
复制代码

(10)flash 的随机的多字节写入,通过按页写入实现的函数来实现
  1. /**
  2. @function 不定量的写入数据,先确保写入前擦出扇区
  3. @annotation
  4. @param
  5. @retval
  6. **/
  7. void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
  8. {
  9.        uint8_t NumOfPage =0, NumOfSingle=0, Addr =0, count =0, temp =0;
  10.        /*计算Addr,写入的地址是否和PageSize对齐*/
  11.        Addr = WriteAddr % SPI_FLASH_PageSize;
  12.        /*count 为剩余的地址*/
  13.        count = SPI_FLASH_PageSize - Addr;
  14.        /*计算能写入多少整数页*/
  15.        NumOfPage = NumByteToWrite % SPI_FLASH_PageSize;
  16.        /*计算不满一页的数据*/
  17.        NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
  18.        /* Addr=0,则 WriteAddr 刚好按页对齐 aligned */
  19.        if(Addr == 0)
  20.        {
  21.               /*NumByteToWrite < SPI_FLASH_PageSize,一页能写完*/
  22.               if(NumOfPage == 0)
  23.               {
  24.                      SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
  25.               }
  26.               else/* NumByteToWrite > SPI_FLASH_PageSize,一页写不完,先写整数页,在写剩下的 */
  27.               {
  28.                      /*先把整数页都写了*/
  29.                      while (NumOfPage--)
  30.                      {
  31.                             SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
  32.                             WriteAddr+=SPI_FLASH_PageSize; // flash 的地址加一页的大小
  33.                             pBuffer+=SPI_FLASH_PageSize;   // 写缓存数据的地址加一页的大小
  34.                      }
  35.                      /*若有多余的不满一页的数据,把它写完*/
  36.                      SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
  37.               }
  38.        }
  39.        else/* 若地址与 SPI_FLASH_PageSize 不对齐 */
  40.        {
  41.                      /* NumByteToWrite < SPI_FLASH_PageSize */
  42.               if (NumOfPage == 0)
  43.               {
  44.                      /*当前页剩余的 count 个位置比 NumOfSingle 小,一页写不完*/
  45.                      if(NumOfSingle >count)
  46.                      {
  47.                             temp = NumOfSingle -count;
  48.                             /*先写满当前页*/
  49.                             SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
  50.                             WriteAddr += count;
  51.                             pBuffer += count;
  52.                             /*再写剩余的数据*/
  53.                             SPI_Flash_Write_Page(WriteAddr,temp,pBuffer);
  54.                      }
  55.                      else/*当前页剩余的 count 个位置能写完 NumOfSingle 个数据*/
  56.                      {
  57.                             SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
  58.                      }
  59.               }
  60.               else/* NumByteToWrite > SPI_FLASH_PageSize */
  61.               {
  62.                      /*地址不对齐多出的 count 分开处理,不加入这个运算*/
  63.                      NumByteToWrite -= count;
  64.                      NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
  65.                      NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
  66.                      /* 先写完 count 个数据,为的是让下一次要写的地址对齐 */
  67.                      SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
  68.                      /* 接下来就重复地址对齐的情况 */
  69.                      WriteAddr += count;
  70.                      pBuffer += count;
  71.                      /*把整数页都写了*/
  72.                      while (NumOfPage--)
  73.                      {
  74.                             SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
  75.                             WriteAddr += SPI_FLASH_PageSize;
  76.                             pBuffer += SPI_FLASH_PageSize;
  77.                      }
  78.                      /*若有多余的不满一页的数据,把它写完*/
  79.                      if (NumOfSingle != 0)
  80.                      {
  81.                             SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
  82.                      }
  83.               }
  84.        }              
  85. }
复制代码

通过上面的函数实现,我们就可以基本的来完成FLASH的读写操作了。有些函数参考了野火标准库的操作步骤。
在main.c 的主函数中对FLASH的读写地址进行宏定义:
  1. #define  FLASH_WriteAddress     0x00000
  2. #define  FLASH_ReadAddress      FLASH_WriteAddress
  3. #define  FLASH_SectorToErase    FLASH_WriteAddress
  4. #define FLASH_SPI hspi1
复制代码

在mian函数中定义变量:
  1. uint32_t flash_ID = 0;
  2. /* 获取缓冲区的长度 */
  3. #define countof(a)      (sizeof(a) / sizeof(*(a)))
  4. uint8_t Tx_Buffer[] = "现在进行FLASH的读写测试\r\n";
  5. #define  BufferSize (countof(Tx_Buffer)-1)
  6. uint8_t Rx_Buffer[BufferSize];
复制代码

在main函数中进行测试代码:
  1. printf("*************this is test for coding...**********\t\n");
  2.        printf("this is test code for spi1 read and write flash w25Q64 \r\n");
  3.        flash_ID = SPI_Flash_ReadID();
  4.        printf("\r\n flash ID is 0X%x\r\n",flash_ID);
  5.        SPI_FLASH_SectorErase(FLASH_SectorToErase);      
  6.        SPI_Flash_Write(FLASH_WriteAddress,BufferSize,Tx_Buffer);
  7.        printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);
  8.        SPI_Flash_Read(FLASH_ReadAddress,BufferSize,Rx_Buffer);
  9.        printf("\r\n 读的数据为:%s \r\t", Rx_Buffer);
  10.        /* 检查写入的数据与读出的数据是否相等 */
  11.        if(memcmp(Tx_Buffer, Rx_Buffer, BufferSize)==0)
  12.        {
  13.               printf("写入的和读出的数据是正常的!\r\n");
  14.        }
  15.        printf("SPI 试验结束......!\r\n");
  16.        printf("*************this is test end....**********\t\n");
复制代码

04
------------第四节 效果演示--------------
我是使用串口将写入FLASH的数据进行读出来,比较数据是否一致,同时独处FLASH的ID看是否一致:
1.5.png
我们可以可以看到FLASH的ID是0xef4017和产品手册中W25Q64的ID是一致的,读写测试也是正常的,我们使用了C库函数,需要包含头文件<stdio.h>。
文章出处: 小鸟的早晨
收藏 1 评论0 发布时间:2021-1-6 09:47

举报

0个回答

所属标签

关于
我们是谁
投资者关系
意法半导体可持续发展举措
创新与技术
意法半导体官网
联系我们
联系ST分支机构
寻找销售人员和分销渠道
社区
媒体中心
活动与培训
隐私策略
隐私策略
Cookies管理
行使您的权利
官方最新发布
STM32N6 AI生态系统
STM32MCU,MPU高性能GUI
ST ACEPACK电源模块
意法半导体生物传感器
STM32Cube扩展软件包
关注我们
st-img 微信公众号
st-img 手机版