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

控制/MCU

1876人已加入

描述

导语“本教程将使用CubeMX初始化SPI,使用SPI对W25Q64 FLASH进行读写操作,通过HAL库的读写应用来数据FLASH的操作细节。”

01系统要求

•硬件

野火指南者开发板

•软件

CubeMx & MDK & 串口调试助手

•原理图

HAL库

根据原理图,我们看到FLASH连接在SPI1上,我们不适用SPI1自带片选,使用PC0开进行软件片选。

02第二节 CubeMx配置

(1) 我们还是使用前面的USART串口(串口的配置没有变化)项目,在此基础上进行SPI1 的配置:

HAL库

我们从配置的信息上看,使用了SPI1,主从全双工模式,8位数据传输,高字节在前,模式3,CRC不校验,软件控制。SPI的模式配置看下图,采样时刻是偶数边沿所以CLOCK Phase =2:

HAL库

(2) 完成片选信号的GPIO配置

HAL库

完成上述配置后点击代码生成。

03第三节MDK 配置

将CubeMx生成的代码使用MDK打开进行应用代码编写:

在spi.h 中进行FLASH操作的指令宏定义:

//指令表
#define W25X_WriteEnable                     0x06 
#define W25X_WriteDisable                     0x04 
#define W25X_ReadStatusReg                     0x05 
#define W25X_WriteStatusReg              0x01 
#define W25X_ReadData                            0x03 
#define W25X_FastReadData                     0x0B 
#define W25X_FastReadDual                     0x3B 
#define W25X_PageProgram                     0x02 
#define W25X_BlockErase                     0xD8 
#define W25X_SectorErase                     0x20 
#define W25X_ChipErase                            0xC7 
#define W25X_PowerDown                            0xB9 
#define W25X_ReleasePowerDown       0xAB 
#define W25X_DeviceID                     xAB 
#define W25X_ManufactDeviceID       0x90 
#define W25X_JedecDeviceID              0x9F
// others defined
#define sFLASH_ID    0XEF4017
#define Dummy_Byte   0XFF
#define SPI_FLASH_PageSize                    256
#define SPI_FLASH_PerWritePageSize        256
#define SPI1_TIME_OUT                              0xFFFF

并且申明应用的操作函数:

void SPI_FLASH_SectorErase(uint32_t SectorAddr);
uint32_t  SPI_Flash_ReadID(void);
void SPI_Flash_Erase_Chip(void);
void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer);
void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);
void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer);

下面我们在spi.c 中实现读写FLASH的相关函数:

(1) 对片选的信号引脚进行宏定义操作:

#define SPI_FLASH_CS_H()   HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_SET) 
#define SPI_FLASH_CS_L()   HAL_GPIO_WritePin(GPIOC, GPIO_PIN_0, GPIO_PIN_RESET)

根据原理图使用的PC0引脚

(2)SPI 读写个字节函数

/**function: SPI 读一个数据**/
uint8_t SPI1_ReadByte(void)
{                     
       uint8_t RxData;       

       HAL_SPI_Receive(&hspi1, &RxData, 1, SPI1_TIME_OUT);

       return RxData; //返回通过SPIx接收的数据                                       
}

我们使用了HAL封装的HALSPIReceive(&hspi1, &RxData, 1, SPI1TIMEOUT)函数来实现读一个字节。

(3)写一个字节

/**function: SPI 写一个数据**/
void SPI1_WriteByte(uint8_t TxData)
{                                            
       HAL_SPI_Transmit(&hspi1, &TxData, 1, SPI1_TIME_OUT);  //通过外设SPIx发送一个数据                                                                      
}


(4FLASH的写使能和非使能


/**function: SPI_FLASH写使能,将WEL置位**/
void SPI_FLASH_Write_Enable(void)   
{
       SPI_FLASH_CS_L();                            //使能器件   
       SPI1_WriteByte(W25X_WriteEnable);      //发送写使能  
       SPI_FLASH_CS_H();                            //取消片选                  
} 


/**function: SPI_FLASH写禁止,将WEL清零**/
void SPI_FLASH_Write_Disable(void)   
{  
       SPI_FLASH_CS_L();                            //使能器件   
       SPI1_WriteByte(W25X_WriteDisable);     //发送写禁止指令    
       SPI_FLASH_CS_H();                           //取消片选                  
}

(5) 读取SPI_FLASH的状态寄存器,通过这个函数我们可以判断FLASH的状态,一般用到的忙和空闲两种状态,这儿也实现了等待空闲函数。

/**
function: 读取SPI_FLASH的状态寄存器
**/
//
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
uint8_t SPI_Flash_ReadSR(void)   
{  
       uint8_t byte=0;   
       SPI_FLASH_CS_L();                       //使能器件   
       SPI1_WriteByte(W25X_ReadStatusReg);           //发送读取状态寄存器命令    
       byte = SPI1_ReadByte();                           //读取一个字节  
       SPI_FLASH_CS_H();                       //取消片选     
       return byte;   
} 


/**
function: 等待空闲
**/
void SPI_Flash_Wait_Busy(void)   
{   
       while ((SPI_Flash_ReadSR()&0x01)==0x01);   // 等待BUSY位清空
}

(6)读取芯片ID W25Q64的ID函数,这个函数也是一般判断是否FLASH正常的方式。查看手册可以知道ID是0XEF4017

/**
function: 读取芯片ID W25Q64的ID
**/
uint32_t  SPI_Flash_ReadID(void)
{
       uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0; 
       SPI_FLASH_Write_Enable(); 
       SPI_FLASH_CS_L();                
       SPI1_WriteByte(W25X_JedecDeviceID);//发送读取ID命令           
       Temp0 = SPI1_ReadByte();       
       Temp1 = SPI1_ReadByte();              
       Temp2 = SPI1_ReadByte();       
  /*把数据组合起来,作为函数的返回值*/
       Temp = (Temp0 < < 16) | (Temp1 < < 8) | Temp2;
       SPI_FLASH_CS_H();              
       return Temp;
}

(7) flash 扇区擦除函数,FLASH和EEPROM不同,在写之前一定要进行擦除,否则不能正常写入。这儿实现了扇区擦除和正片擦除两个函数:

/**
function: 擦除整个芯片
**/
//整片擦除时间:
//W25X16:25s 
//W25X32:40s 
//W25X64:40s 
//等待时间超长...
void SPI_Flash_Erase_Chip(void)   
{                                             
       SPI_FLASH_Write_Enable();               //SET WEL 
       SPI_Flash_Wait_Busy();   
       SPI_FLASH_CS_L();                      //使能器件   
       SPI1_WriteByte(W25X_ChipErase);        //发送片擦除命令  
       SPI_FLASH_CS_H();                      //取消片选                  
       SPI_Flash_Wait_Busy();                                                //等待芯片擦除结束
}  
/**
function: SPI_FLASH_SectorErase
annotation:Flash的一个扇区是4K,所以输入的地址要和4K对其。
**/
void SPI_FLASH_SectorErase(uint32_t SectorAddr)
{
       SPI_FLASH_Write_Enable(); 
       SPI_Flash_Wait_Busy();       
       SPI_FLASH_CS_L(); 
       SPI1_WriteByte(W25X_SectorErase); 
       SPI1_WriteByte((SectorAddr & 0xFF0000) > > 16); 
       SPI1_WriteByte((SectorAddr & 0xFF00) > > 8); 
       SPI1_WriteByte(SectorAddr & 0xFF);
       SPI_FLASH_CS_H();      
       SPI_Flash_Wait_Busy();
}

(8) 读取FLASH函数,FLASH的读操作步骤很简单

/**
function: 读取SPI FLASH  
**/
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void SPI_Flash_Read(uint32_t ReadAddr,uint16_t NumByteToRead,uint8_t* pBuffer)   
{ 
       uint16_t i;                                                                                            
       SPI_FLASH_CS_L();                          //使能器件   
       SPI1_WriteByte(W25X_ReadData);                       //发送读取命令   
       SPI1_WriteByte((uint8_t)((ReadAddr) > >16));  //发送24bit地址    
       SPI1_WriteByte((uint8_t)((ReadAddr) > >8));
       SPI1_WriteByte((uint8_t)ReadAddr);
       for(i=0;i
       { 
              pBuffer[i] = SPI1_ReadByte();   //循环读数  
       }
       SPI_FLASH_CS_H();       //取消片选
}

(9) flash 的按页写入数据,FLASH的数据的写入不是随机可以任意写入,按页写入,一页最大的数据是256个字节。

/**
function: SPI在一页(0~65535)内写入少于256个字节的数据 
annotation:一页最大256个字节
**/
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void SPI_Flash_Write_Page(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
{
         uint16_t i;
       SPI_FLASH_Write_Enable();                                       //SET WEL
       SPI_FLASH_CS_L();                                         //使能器件
       SPI1_WriteByte(W25X_PageProgram);                                  //发送写页命令
       SPI1_WriteByte((uint8_t)((WriteAddr) > >16));               //发送24bit地址
       SPI1_WriteByte((uint8_t)((WriteAddr) > >8));
       SPI1_WriteByte((uint8_t)WriteAddr);
       for(i=0;i< NumByteToWrite;i++) SPI1_WriteByte(pBuffer[i]);       //循环写数
       SPI_FLASH_CS_H();                                                        //取消片选 
       SPI_Flash_Wait_Busy();                                                                        //等待写入结束                                                                                     
}

(10)flash 的随机的多字节写入,通过按页写入实现的函数来实现

/**
@function 不定量的写入数据,先确保写入前擦出扇区
@annotation
@param
@retval
**/
void SPI_Flash_Write(uint32_t WriteAddr,uint16_t NumByteToWrite,uint8_t* pBuffer)
{
       uint8_t NumOfPage =0, NumOfSingle=0, Addr =0, count =0, temp =0;
       /*计算Addr,写入的地址是否和PageSize对齐*/
       Addr = WriteAddr % SPI_FLASH_PageSize;
       /*count 为剩余的地址*/
       count = SPI_FLASH_PageSize - Addr;
       /*计算能写入多少整数页*/
       NumOfPage = NumByteToWrite % SPI_FLASH_PageSize;
       /*计算不满一页的数据*/
       NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
       /* Addr=0,则 WriteAddr 刚好按页对齐 aligned */
       if(Addr == 0)
       {
              /*NumByteToWrite < SPI_FLASH_PageSize,一页能写完*/
              if(NumOfPage == 0)
              {
                     SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
              }
              else/* NumByteToWrite > SPI_FLASH_PageSize,一页写不完,先写整数页,在写剩下的 */
              {
                     /*先把整数页都写了*/
                     while (NumOfPage--)
                     {
                            SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
                            WriteAddr+=SPI_FLASH_PageSize; // flash 的地址加一页的大小
                            pBuffer+=SPI_FLASH_PageSize;   // 写缓存数据的地址加一页的大小
                     }
                     /*若有多余的不满一页的数据,把它写完*/
                     SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
              }
       }
       else/* 若地址与 SPI_FLASH_PageSize 不对齐 */
       {
                     /* NumByteToWrite < SPI_FLASH_PageSize */
              if (NumOfPage == 0)
              {
                     /*当前页剩余的 count 个位置比 NumOfSingle 小,一页写不完*/
                     if(NumOfSingle >count)
                     {
                            temp = NumOfSingle -count;
                            /*先写满当前页*/
                            SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
                            WriteAddr += count;
                            pBuffer += count;
                            /*再写剩余的数据*/
                            SPI_Flash_Write_Page(WriteAddr,temp,pBuffer);
                     }
                     else/*当前页剩余的 count 个位置能写完 NumOfSingle 个数据*/
                     {
                            SPI_Flash_Write_Page(WriteAddr,NumByteToWrite,pBuffer);
                     }
              }
              else/* NumByteToWrite > SPI_FLASH_PageSize */
              {
                     /*地址不对齐多出的 count 分开处理,不加入这个运算*/
                     NumByteToWrite -= count;
                     NumOfPage = NumByteToWrite / SPI_FLASH_PageSize;
                     NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;
                     /* 先写完 count 个数据,为的是让下一次要写的地址对齐 */
                     SPI_Flash_Write_Page(WriteAddr,count,pBuffer);
                     /* 接下来就重复地址对齐的情况 */
                     WriteAddr += count;
                     pBuffer += count;
                     /*把整数页都写了*/
                     while (NumOfPage--)
                     {
                            SPI_Flash_Write_Page(WriteAddr,SPI_FLASH_PageSize,pBuffer);
                            WriteAddr += SPI_FLASH_PageSize;
                            pBuffer += SPI_FLASH_PageSize;
                     }
                     /*若有多余的不满一页的数据,把它写完*/
                     if (NumOfSingle != 0)
                     {
                            SPI_Flash_Write_Page(WriteAddr,NumOfSingle,pBuffer);
                     }
              }
       }              
}

通过上面的函数实现,我们就可以基本的来完成FLASH的读写操作了。有些函数参考了野火标准库的操作步骤。

在main.c 的主函数中对FLASH的读写地址进行宏定义:

#define  FLASH_WriteAddress     0x00000
#define  FLASH_ReadAddress      FLASH_WriteAddress
#define  FLASH_SectorToErase    FLASH_WriteAddress
#define FLASH_SPI hspi1

在mian函数中定义变量:

uint32_t flash_ID = 0;
/* 获取缓冲区的长度 */
#define countof(a)      (sizeof(a) / sizeof(*(a)))
uint8_t Tx_Buffer[] = "现在进行FLASH的读写测试rn";
#define  BufferSize (countof(Tx_Buffer)-1)
uint8_t Rx_Buffer[BufferSize];

在main函数中进行测试代码:

printf("*************this is test for coding...**********tn");
       printf("this is test code for spi1 read and write flash w25Q64 rn");
       flash_ID = SPI_Flash_ReadID();
       printf("rn flash ID is 0X%xrn",flash_ID);
       SPI_FLASH_SectorErase(FLASH_SectorToErase);       
       SPI_Flash_Write(FLASH_WriteAddress,BufferSize,Tx_Buffer);
       printf("rn 写入的数据为:%s rt", Tx_Buffer);
       SPI_Flash_Read(FLASH_ReadAddress,BufferSize,Rx_Buffer);
       printf("rn 读的数据为:%s rt", Rx_Buffer);
       /* 检查写入的数据与读出的数据是否相等 */
       if(memcmp(Tx_Buffer, Rx_Buffer, BufferSize)==0)
       {
              printf("写入的和读出的数据是正常的!rn");
       }
       printf("SPI 试验结束......!rn");
       printf("*************this is test end....**********tn");

04第四节 效果演示

我是使用串口将写入FLASH的数据进行读出来,比较数据是否一致,同时独处FLASH的ID看是否一致:

HAL库

我们可以可以看到FLASH的ID是0xef4017和产品手册中W25Q64的ID是一致的,读写测试也是正常的,我们使用了C库函数,需要包含头文件。

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分