STMCubeMx入门教程(6) SPI 读写FLAH的应用 导语“本教程将使用CubeMX初始化SPI,使用SPI对W25Q64 FLASH进行读写操作,通过HAL库的读写应用来数据FLASH的操作细节。” 01 ------------ 系统要求----------------- •硬件 野火指南者开发板 •软件 CubeMx & MDK & 串口调试助手 •原理图 根据原理图,我们看到FLASH连接在SPI1上,我们不适用SPI1自带片选,使用PC0开进行软件片选。 02 ------第二节 CubeMx配置----------------- (1) 我们还是使用前面的USART串口(串口的配置没有变化)项目,在此基础上进行SPI1 的配置: 我们从配置的信息上看,使用了SPI1,主从全双工模式,8位数据传输,高字节在前,模式3,CRC不校验,软件控制。SPI的模式配置看下图,采样时刻是偶数边沿所以CLOCK Phase =2: (2) 完成片选信号的GPIO配置 完成上述配置后点击代码生成。 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
复制代码
并且申明应用的操作函数: - 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发送一个数据
- }
复制代码 (4)FLASH的写使能和非使能 - /**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的读写测试\r\n";
- #define BufferSize (countof(Tx_Buffer)-1)
- uint8_t Rx_Buffer[BufferSize];
复制代码
在main函数中进行测试代码: - printf("*************this is test for coding...**********\t\n");
- printf("this is test code for spi1 read and write flash w25Q64 \r\n");
- flash_ID = SPI_Flash_ReadID();
- printf("\r\n flash ID is 0X%x\r\n",flash_ID);
- SPI_FLASH_SectorErase(FLASH_SectorToErase);
- SPI_Flash_Write(FLASH_WriteAddress,BufferSize,Tx_Buffer);
- printf("\r\n 写入的数据为:%s \r\t", Tx_Buffer);
- SPI_Flash_Read(FLASH_ReadAddress,BufferSize,Rx_Buffer);
- printf("\r\n 读的数据为:%s \r\t", Rx_Buffer);
- /* 检查写入的数据与读出的数据是否相等 */
- if(memcmp(Tx_Buffer, Rx_Buffer, BufferSize)==0)
- {
- printf("写入的和读出的数据是正常的!\r\n");
- }
- printf("SPI 试验结束......!\r\n");
- printf("*************this is test end....**********\t\n");
复制代码
04 ------------第四节 效果演示-------------- 我是使用串口将写入FLASH的数据进行读出来,比较数据是否一致,同时独处FLASH的ID看是否一致: 我们可以可以看到FLASH的ID是0xef4017和产品手册中W25Q64的ID是一致的,读写测试也是正常的,我们使用了C库函数,需要包含头文件<stdio.h>。 文章出处: 小鸟的早晨 |