STM32 移植操作系统

一、SPI FLASH驱动

为了完成FATFs的移植,需要提前准备好W25Q64的驱动程序,MCU使用的stm32f103rct6,正点原子的mini板。驱动文件改自王维波老师的<<STM32CubeMX高效开发教程高级篇>>,在王老师的基础上进一步实现了任意地址可自动跨block写Flash的函数。

因为测试时使用的缓冲区比较大,为了保证读取正确,请先把startup_stm32f103xe.s中的Heap Size调大至0x3000,也就是3KB。

这个驱动我抄的乱七八糟的,正点原子野火普中CSDN抄了一大通,目前就测了一下W25Q64能用,其他没搞。

w25flash.h

/* 文件: w25flash.h
 * 功能描述: Flash 存储器W25Q128的驱动程序
 * 作者:王维波
 * 修改日期:2019-06-05
 * W25Q128 芯片参数: 16M字节,24位地址线
 * 分为256个Block,每个Block 64K字节
 * 一个Block又分为16个Sector,共4096个Sector,每个Sector 4K字节
 * 一个Sector又分为16个Page,共65536个Page,每个Page 256字节
 * 写数据操作的基本单元是Page,一次连续写入操作不能超过一个Page的范围。写的Page必须是擦除过的。
*W25Q64 8M字节 Block:128个,每个block 64KByte
*Sector:每个Block有16个,每个sector:  4Kbyte
*Page:每个Sector有16个page,每个page: 256 Byte
在王老师的基础上增加了Flash_write函数,可以任意地址连续写,支持跨block写,不检测边界
实验硬件:正点原子Mini开发板,stm32f103rct6,w25q64
软件片选,片选线PA2。调试信息串口使用UART1
 */

#ifndef _W25FLASH_H
#define _W25FLASH_H

#include 	"stm32f1xx_hal.h"
#include	"spi.h"		//使用其中的变量 hspi1,表示SPI1接口
#include <stdio.h>
//指令表
#define W25X_WriteEnable		0x06 
#define W25X_WriteDisable		0x04 
#define W25X_ReadStatusReg1		0x05 
#define W25X_ReadStatusReg2		0x35 
#define W25X_ReadStatusReg3		0x15 
#define W25X_WriteStatusReg1    0x01 
#define W25X_WriteStatusReg2    0x31 
#define W25X_WriteStatusReg3    0x11 
#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 
//0xAB is  Release Power down or HPM / Device ID
#define W25X_DeviceID			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 
#define W25X_Enable4ByteAddr    0xB7
#define W25X_Exit4ByteAddr      0xE9

/*  W25Q128硬件接口相关的部分:CS引脚和SPI接口 ,若电路不同,更改这部分配置即可   */
// Flash_CS -->PB14, 片选信号CS操作的宏定义函数
#define CS_PORT		GPIOA
#define CS_PIN		GPIO_PIN_2

#define SPI_HANDLE	 	hspi1		//SPI接口对象,使用spi.h中的变量 hspi1

#define	__Select_Flash()		HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)	//CS=0
#define	__Deselect_Flash()		HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)	//CS=1

//===========Flash存储芯片W25Q128的存储容量参数================
#define		EX_FLASH_PAGE_SIZE			256		//一个Page是256字节
#define		FLASH_SECTOR_SIZE		4096	//一个Sector是4096字节
#define		FLASH_SECTOR_COUNT		2048	//W25Q64总共2048个 Sector
#define FLASH_BLOCK_COUNT 128
#define FLASH_BLOCK_PER_SIZE 65536//每个block大小

#define W25Q64 0xEF16

//=======1. SPI 基本发送和接收函数,阻塞式传输============
HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData);	//SPI接口发送一个字节
HAL_StatusTypeDef SPI_TransmitBytes(uint8_t *pBuffer, uint16_t byteCount);//SPI接口发送多个字节

uint8_t SPI_ReceiveOneByte();	//SPI接口接收一个字节
HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t *pBuffer, uint16_t byteCount);//SPI接口接收多个字节

//=========2. W25Qxx 基本控制指令==========

// 0xEF17,表示芯片型号为W25Q128, Winbond,用过
// 0xC817,表示芯片型号为GD25Q128,ELM,用过
// 0x1C17,表示芯片型号为EN25Q128,台湾EON
// 0xA117,表示芯片型号为FM25Q128,复旦微电子
// 0x2018,表示芯片型号为N25Q128,美光
// 0x2017,表示芯片型号为XM25QH128,武汉新芯,用过

uint16_t Flash_ReadID(void); 	// Command=0x90, Manufacturer/Device ID
uint32_t Flash_ReadJedecID();
uint64_t Flash_ReadSerialNum(uint32_t *High32, uint32_t *Low32); //Command=0x4B, Read Unique ID, 64-bit

HAL_StatusTypeDef Flash_WriteVolatile_Enable(void); //Command=0x50: Write Volatile Enable

HAL_StatusTypeDef Flash_Write_Enable(void); //Command=0x06: Write Enable,    使WEL=1
HAL_StatusTypeDef Flash_Write_Disable(void);//Command=0x04, Write Disable,	  使WEL=0

uint8_t Flash_ReadSR1(void); //Command=0x05:  Read Status Register-1,	返回寄存器SR1的值
uint8_t Flash_ReadSR2(void); //Command=0x35:  Read Status Register-2,	返回寄存器SR2的值

void Flash_WriteSR1(uint8_t SR1); //Command=0x01:  Write Status Register,	只写SR1的值,禁止写状态寄存器

uint32_t Flash_Wait_Busy(void);  	//读状态寄存器SR1,等待BUSY变为0,返回值是等待时间
void Flash_PowerDown(void);   		//Command=0xB9: Power Down
void Flash_WakeUp(void);  			//Command=0xAB: Release Power Down

//========3. 计算地址的辅助功能函数========
//根据Block  绝对编号获取地址,共256个Block
uint32_t Flash_Addr_byBlock(uint8_t BlockNo);
//根据Sector 绝对编号获取地址,共4096个Sector
uint32_t Flash_Addr_bySector(uint16_t SectorNo);
//根据Page  绝对编号获取地址,共65536个Page
uint32_t Flash_Addr_byPage(uint16_t PageNo);

//根据Block编号,和内部Sector编号计算地址,一个Block有16个Sector,
uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo);
//根据Block编号,内部Sector编号,内部Page编号计算地址
uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo,
		uint8_t SubPageNo);
//将24位地址分解为3个字节
void Flash_SpliteAddr(uint32_t globalAddr, uint8_t *addrHigh, uint8_t *addrMid,
		uint8_t *addrLow);

//=======4. chip、Block,Sector擦除函数============
//Command=0xC7: Chip Erase, 擦除整个器件,大约25秒
void Flash_EraseChip(void);

//Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址,耗时大约150ms
void Flash_EraseBlock64K(uint32_t globalAddr);

//Command=0x20: Sector Erase(4KB) 扇区擦除, globalAddr是扇区的全局地址,耗时大约30ms
void Flash_EraseSector(uint32_t globalAddr);

//=========5. 数据读写函数=============
//Command=0x03,  读取一个字节,任意全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr);

//Command=0x03,  连续读取多个字节,任意全局地址
void Flash_ReadBytes(uint32_t globalAddr, uint8_t *pBuffer, uint16_t byteCount);

//Command=0x0B,  高速连续读取多个字节,任意全局地址, 速度大约是常规读取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t *pBuffer,
		uint16_t byteCount);

//Command=0x02: Page program 对一个Page写入数据(最多256字节), globalAddr是初始位置的全局地址,耗时大约3ms
void Flash_WriteInPage(uint32_t globalAddr, uint8_t *pBuffer,
		uint16_t byteCount);

//从某个Sector的起始地址开始写数据,数据可能跨越多个Page,甚至跨越Sector,总字节数byteCount不能超过64K,也就是一个Block的大小
void Flash_WriteSector(uint32_t globalAddr, const uint8_t *pBuffer,
		uint16_t byteCount);
//任意地址写,可以跨blcok写,自动擦除,正确工作依赖与Flash_WriteSector
void Flash_Write(uint32_t addr,uint8_t* pData, uint32_t size);
#endif

w25flash.c

/* 文件: w25flash.c
 * 功能描述: Flash 存储器W25Q128的驱动程序
 * 作者:王维波
 * 修改日期:2019-06-05
 */

#include "w25flash.h"

#define MAX_TIMEOUT   100		//SPI轮询操作时的最大等待时间,ms

//SPI接口发送一个字节,byteData是需要发送的数据
HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData) {
	return HAL_SPI_Transmit(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
}

//SPI接口发送多个字节, pBuffer是发送数据缓存区指针,byteCount是发送数据字节数,byteCount最大256
HAL_StatusTypeDef SPI_TransmitBytes(uint8_t *pBuffer, uint16_t byteCount) {
	return HAL_SPI_Transmit(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}

//SPI接口接收一个字节, 返回接收的一个字节数据
uint8_t SPI_ReceiveOneByte() {
	uint8_t byteData = 0;
	HAL_SPI_Receive(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
	return byteData;
}

//SPI接口接收多个字节, pBuffer是接收数据缓存区指针,byteCount是需要接收数据的字节数
HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t *pBuffer, uint16_t byteCount) {
	return HAL_SPI_Receive(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}

//Command=0x05:  Read Status Register-1,返回寄存器SR1的值
uint8_t Flash_ReadSR1(void) {
	uint8_t byte = 0;
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_ReadStatusReg1); //Command=0x05:  Read Status Register-1
	HAL_SPI_Receive(&SPI_HANDLE,&byte,1,0xFF);
	HAL_SPI_Receive(&SPI_HANDLE,&byte,1,0xFF);
	//byte = SPI_ReceiveOneByte();
	__Deselect_Flash();	//CS=1
	return byte;
}

//Command=0x35:  Read Status Register-2,返回寄存器SR2的值
uint8_t Flash_ReadSR2(void) {
	uint8_t byte = 0;
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_ReadStatusReg2); //Command=0x35:  Read Status Register-2
	byte = SPI_ReceiveOneByte();	//读取一个字节
	__Deselect_Flash();	//CS=1
	return byte;
}

//Command=0x01:  Write Status Register,	只写SR1的值
//耗时大约10-15ms
void Flash_WriteSR1(uint8_t SR1) {
	Flash_Write_Enable();       //必须使 WEL=1

	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x01);  //Command=0x01:  Write Status Register,	只写SR1的值
	SPI_TransmitOneByte(0x00);    //SR1的值
//	SPI_WriteOneByte(0x00);    //SR2的值, 只发送SR1的值,而不发送SR2的值, QE和CMP将自动被清零
	__Deselect_Flash();	//CS=1

	Flash_Wait_Busy(); 	   //耗时大约10-15ms
}

HAL_StatusTypeDef Flash_WriteVolatile_Enable(void) //Command=0x50: Write Volatile Enable
{
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result = SPI_TransmitOneByte(0x50);
	__Deselect_Flash();	//CS=1
	return result;
}

//Command=0x06: Write Enable,    使WEL=1
HAL_StatusTypeDef Flash_Write_Enable(void) {
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result = SPI_TransmitOneByte(W25X_WriteEnable); //Command=0x06: Write Enable,    使WEL=1
	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	//等待操作完成
	return result;
}

//Command=0x04, Write Disable,	  使WEL=0
HAL_StatusTypeDef Flash_Write_Disable(void) {
	__Select_Flash();	//CS=0
	HAL_StatusTypeDef result = SPI_TransmitOneByte(W25X_WriteDisable); //Command=0x04, Write Disable,	  使WEL=0
	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	   //
	return result;
}

//根据Block绝对编号获取地址, 共128个Block, BlockNo 取值范围0-127
//每个块64K字节,16位地址,块内地址范围0x0000-0xFFFF。
uint32_t Flash_Addr_byBlock(uint8_t BlockNo) {
//	uint32_t addr=BlockNo*0x10000;

	uint32_t addr = BlockNo;
	addr = addr << 16; //左移16位,等于乘以0x10000
	return addr;
}

//根据Sector绝对编号获取地址, 共2048个Sector, SectorNo取值范围0-2047
//每个扇区4K字节,12位地址,扇区内地址范围0x000-0xFFF
uint32_t Flash_Addr_bySector(uint16_t SectorNo) {
	if (SectorNo > 2047)	//不能超过4095
		SectorNo = 0;
//	uint32_t addr=SectorNo*0x1000;

	uint32_t addr = SectorNo;
	addr = addr << 12;		//左移12位,等于乘以0x1000
	return addr;
}

//根据Page绝对编号获取地址,共32768个Page,  PageNo取值范围0-32767
//每个页256字节,8位地址,页内地址范围0x00—0xFF
uint32_t Flash_Addr_byPage(uint16_t PageNo) {
//	uint32_t addr=PageNo*0x100;

	uint32_t addr = PageNo;
	addr = addr << 8;		//左移8位,等于乘以0x100
	return addr;
}

//根据Block编号和内部Sector编号计算地址,一个Block有16个Sector
//BlockNo取值范围0-127,  内部SubSectorNo取值范围0-15
uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo) {
	if (SubSectorNo > 15)	 //不能超过15
		SubSectorNo = 0;

//	uint32_t addr=BlockNo*0x10000;	//先计算Block的起始地址
	uint32_t addr = BlockNo;
	addr = addr << 16;	//先计算Block的起始地址

//	uint32_t offset=SubSectorNo*0x1000;	//计算Sector的偏移地址
	uint32_t offset = SubSectorNo;	//计算Sector的偏移地址
	offset = offset << 12;	//计算Sector的偏移地址

	addr += offset;

	return addr;
}

//根据Block编号,内部Sector编号,内部Page编号获取地址
//BlockNo取值范围0-127
//一个Block有16个Sector, 内部SubSectorNo取值范围0-15
//一个Sector有16个Page , 内部SubPageNo取值范围0-15
uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo,
		uint8_t SubPageNo) {
	if (SubSectorNo > 15)	//不能超过15
		SubSectorNo = 0;

	if (SubPageNo > 15)	//不能超过15
		SubPageNo = 0;

//	uint32_t addr=BlockNo*0x10000;		//先计算Block的起始地址
	uint32_t addr = BlockNo;
	addr = addr << 16;		//先计算Block的起始地址

//	uint32_t offset=SubSectorNo*0x1000;	//计算Sector的偏移地址
	uint32_t offset = SubSectorNo;	//计算Sector的偏移地址
	offset = offset << 12;	//计算Sector的偏移地址
	addr += offset;

//	offset=SubPageNo*0x100;	//计算Page的偏移地址
	offset = SubPageNo;
	offset = offset << 8;	//计算Page的偏移地址

	addr += offset;		//Page的起始地址
	return addr;
}

//将24位地址分解为3个字节
//globalAddr是全局24位地址, 返回 addrHigh高字节,addrMid中间字节,addrLow低字节
void Flash_SpliteAddr(uint32_t globalAddr, uint8_t *addrHigh, uint8_t *addrMid,
		uint8_t *addrLow) {
	*addrHigh = (globalAddr >> 16);	//addrHigh=高字节

	globalAddr = globalAddr & 0x0000FFFF;	//屏蔽高字节
	*addrMid = (globalAddr >> 8);	//addrMid=中间字节

	*addrLow = globalAddr & 0x000000FF;	//屏蔽中间字节, 只剩低字节,addrLow=低字节
}

//读取芯片ID
//返回值如下:
// 0xEF17,表示芯片型号为W25Q128, Winbond,用过
// 0xC817,表示芯片型号为GD25Q128,ELM,用过
// 0x1C17,表示芯片型号为EN25Q128,台湾EON
// 0xA117,表示芯片型号为FM25Q128,复旦微电子
// 0x2018,表示芯片型号为N25Q128,美光
// 0x2017,表示芯片型号为XM25QH128,武汉新芯,用过

//读取芯片的制造商和器件ID,高字节是Manufacturer ID,低字节是Device ID
uint16_t Flash_ReadID(void) {
	uint16_t Temp = 0;
	__Select_Flash();	//CS=0
	uint8_t cmd[4]={W25X_ManufactDeviceID,0x00,0x00,0x00};
	HAL_SPI_Transmit(&SPI_HANDLE,cmd,4,MAX_TIMEOUT);
	Temp = SPI_ReceiveOneByte() << 8;	//Manufacturer ID
	Temp |= SPI_ReceiveOneByte();	 	//Device ID, 与具体器件相关

	__Deselect_Flash();	//CS=1
	return Temp;
}
/*获取JedecID,0x9F*/
uint32_t Flash_ReadJedecID()
{
		uint32_t Temp = 0;
	__Select_Flash();	//CS=0
	uint8_t cmd[4]={W25X_JedecDeviceID,0x00,0x00,0x00};
	HAL_SPI_Transmit(&SPI_HANDLE,cmd,1,MAX_TIMEOUT);
	HAL_SPI_Receive(&SPI_HANDLE,cmd,3,MAX_TIMEOUT);
	Temp = (cmd[0]<<16)|(cmd[1]<<8)|cmd[2];
	__Deselect_Flash();	//CS=1
	return Temp;
}
// 参数High32和Low32分别返回64位序列号的高32位和低32位的值
// 函数返回值为64位序列号的值
uint64_t Flash_ReadSerialNum(uint32_t *High32, uint32_t *Low32)	//读取64位序列号,
{
	uint8_t Temp = 0;
	uint64_t SerialNum = 0;
	uint32_t High = 0, Low = 0;

	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0x4B);		//发送指令码, 4B=read Unique ID
	SPI_TransmitOneByte(0x00);		//发送4个Dummy字节数据
	SPI_TransmitOneByte(0x00);
	SPI_TransmitOneByte(0x00);
	SPI_TransmitOneByte(0x00);

	for (uint8_t i = 0; i < 4; i++)  //高32位
			{
		Temp = SPI_ReceiveOneByte();
		High = (High << 8);
		High = High | Temp;  //按位或
	}

	for (uint8_t i = 0; i < 4; i++)	//低32位
			{
		Temp = SPI_ReceiveOneByte();
		Low = (Low << 8);
		Low = Low | Temp;  //按位或
	}
	__Deselect_Flash();	//CS=1

	*High32 = High;
	*Low32 = Low;

	SerialNum = High;
	SerialNum = SerialNum << 32;  //高32位
	SerialNum = SerialNum | Low;

	return SerialNum;
}

//在任意地址读取一个字节的数据,返回读取的字节数据
// globalAddr是24位全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr) {
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_ReadData);      //Command=0x03, read data
	SPI_TransmitOneByte(byte2);		//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	byte2 = SPI_ReceiveOneByte();	//接收1个字节
	__Deselect_Flash();	//CS=1

	return byte2;
}

//从任何地址开始读取指定长度的数据
//globalAddr:开始读取的地址(24bit), pBuffer:数据存储区指针,byteCount:要读取的字节数
void Flash_ReadBytes(uint32_t globalAddr, uint8_t *pBuffer, uint16_t byteCount) {
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_ReadData);      //Command=0x03, read data
	SPI_TransmitOneByte(byte2);		//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_ReceiveBytes(pBuffer, byteCount);	//接收byteCount个字节数据
	__Deselect_Flash();	//CS=1
}

//Command=0x0B,  高速连续读取flash多个字节,任意全局地址, 速度大约是常规读取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t *pBuffer,
		uint16_t byteCount) {
// 	uint16_t i;
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	__Select_Flash();	//CS=0

	SPI_TransmitOneByte(W25X_FastReadData);      //Command=0x0B, fast read data
	SPI_TransmitOneByte(byte2);		//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_TransmitOneByte(0x00);		//Dummy字节

	SPI_ReceiveBytes(pBuffer, byteCount);	//接收byteCount个字节数据
	__Deselect_Flash();	//CS=1

}

//Command=0xC7: Chip Erase, 擦除整个器件
// 擦除后,所有存储区内容为0xFF,耗时大约25秒
void Flash_EraseChip(void) {
	Flash_Write_Enable();   //使 WEL=1
	Flash_Wait_Busy();   	//等待空闲

	__Select_Flash();		//CS=0
	SPI_TransmitOneByte(W25X_ChipErase);  // Command=0xC7: Chip Erase, 擦除整个器件
	__Deselect_Flash();		//CS=1

	Flash_Wait_Busy();   //等待芯片擦除结束,大约25秒
}

// Command=0x02: Page program, 对一个页(256字节)编程, 耗时大约3ms,
// globalAddr是写入初始地址,全局地址
// pBuffer是要写入数据缓冲区指针,byteCount是需要写入的数据字节数
// 写入的Page必须是前面已经擦除过的,如果写入地址超出了page的边界,就从Page的开头重新写
void Flash_WriteInPage(uint32_t globalAddr, uint8_t *pBuffer,
		uint16_t byteCount) {
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	Flash_Write_Enable();   //SET WEL
	Flash_Wait_Busy();

	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_PageProgram);      //Command=0x02: Page program 对一个扇区编程
	SPI_TransmitOneByte(byte2);	//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);
	SPI_TransmitBytes(pBuffer, byteCount); 		//发送byteCount个字节的数据
	__Deselect_Flash();	//CS=1

	Flash_Wait_Busy(); 	   //耗时大约3ms
}

//从某个Sector的起始位置开始写数据,数据可能跨越多个Page,甚至跨越Sector,不必提前擦除
// globalAddr是写入初始地址,全局地址,是扇区的起始地址,
// pBuffer是要写入数据缓冲区指针
// byteCount是需要写入的数据字节数,byteCount不能超过64K,也就是一个Block(16个扇区)的大小,但是可以超过一个Sector(4K字节)
// 如果数据超过一个Page,自动分成多个Page,调用EN25Q_WriteInPage分别写入
void Flash_WriteSector(uint32_t globalAddr, const uint8_t *pBuffer,
		uint16_t byteCount) 
{
//需要先擦除扇区,可能是重复写文件
	uint8_t secCount = (byteCount / FLASH_SECTOR_SIZE);	//数据覆盖的扇区个数
	if ((byteCount % FLASH_SECTOR_SIZE) > 0)
		secCount++;

	uint32_t startAddr = globalAddr;
	for (uint8_t k = 0; k < secCount; k++) {
		Flash_EraseSector(startAddr);	//擦除扇区
		startAddr += FLASH_SECTOR_SIZE;	//移到下一个扇区
	}

//分成Page写入数据,写入数据的最小单位是Page
	uint16_t leftBytes = byteCount % EX_FLASH_PAGE_SIZE; //非整数个Page剩余的字节数,即最后一个Page写入的数据
	uint16_t pgCount = byteCount / EX_FLASH_PAGE_SIZE;  //前面整数个Page
	uint8_t *buff = pBuffer;
	for (uint16_t i = 0; i < pgCount; i++)	//写入前面pgCount个Page的数据,
			{
		Flash_WriteInPage(globalAddr, buff, EX_FLASH_PAGE_SIZE);  //写一整个Page的数据
		globalAddr += EX_FLASH_PAGE_SIZE;	//地址移动一个Page
		buff += EX_FLASH_PAGE_SIZE;		//数据指针移动一个Page大小
	}

	if (leftBytes > 0)
		Flash_WriteInPage(globalAddr, buff, leftBytes);  //最后一个Page,不是一整个Page的数据
}

//Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址
//清除后存储区内容全部为0xFF,  耗时大概150ms
void Flash_EraseBlock64K(uint32_t globalAddr) {
	Flash_Write_Enable();   //SET WEL
	Flash_Wait_Busy();

	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	__Select_Flash();	//CS=0

	SPI_TransmitOneByte(W25X_BlockErase);      //Command=0xD8, Block Erase(64KB)
	SPI_TransmitOneByte(byte2);	//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);

	__Deselect_Flash();	//CS=1
	Flash_Wait_Busy(); 	   //耗时大概150ms
}

//擦除一个扇区(4KB字节),Command=0x20, Sector Erase(4KB)
//globalAddr: 扇区的绝对地址,24位地址0x00XXXXXX
//擦除后,扇区内全部内容为0xFF, 耗时大约30ms,
void Flash_EraseSector(uint32_t globalAddr) {
	Flash_Write_Enable();   //SET WEL
	Flash_Wait_Busy();
	uint8_t byte2, byte3, byte4;
	Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);	//24位地址分解为3个字节

	__Select_Flash();	//CS=0

	SPI_TransmitOneByte(W25X_SectorErase);      //Command=0x20, Sector Erase(4KB)
	SPI_TransmitOneByte(byte2);		//发送24位地址
	SPI_TransmitOneByte(byte3);
	SPI_TransmitOneByte(byte4);

	__Deselect_Flash();		//CS=1
	Flash_Wait_Busy(); 	   //大约30ms
}

//检查寄存器SR1的BUSY位,直到BUSY位为0
uint32_t Flash_Wait_Busy(void) {
	uint8_t SR1 = 0;
	uint32_t delay = 0;
	SR1 = Flash_ReadSR1();	//读取状态寄存器SR1
	while ((SR1 & 0x01) == 0x01) {
		HAL_Delay(1);	//延时1ms
		delay++;
		SR1 = Flash_ReadSR1();	//读取状态寄存器SR1
	}
	return delay;
}

//进入掉电模式
//Command=0xB9: Power Down
void Flash_PowerDown(void) {
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(W25X_PowerDown);  //Command=0xB9: Power Down
	__Deselect_Flash();	//CS=1
	HAL_Delay(1); //等待TPD
}

//唤醒
//Command=0xAB: Release Power Down
void Flash_WakeUp(void) {
	__Select_Flash();	//CS=0
	SPI_TransmitOneByte(0xAB);		//Command=0xAB: Release Power Down
	__Deselect_Flash();	//CS=1
	HAL_Delay(1);     //等待TRES1
}

//W25Q64. 8M ,128个block 64kb/block,16 sector/block,4KB/sector
//16page/sector 256byte/page
void Flash_Write(uint32_t addr,uint8_t* pData, uint32_t size)
{
	if(addr>(8*1024*1024))
	{
		printf("Error Write address is over W25Q64!");
	}
	else if((addr+size-1)> (8*1024*1024))
	{
		printf("Error Write address+size is over W25Q64!");
	}
    uint8_t blockoffset = addr / (256 * 16 * 16);//一个block 64kb
    //根据地址范围,判断是否存放在同一个block
    int i = 1;
    for (; i < 128; i++) {
        if ((addr + size - 1) < ((blockoffset + i) * 65536))
        {
            break;
        }
    }
    //一个block就行
    if (i == 1)
    {
        Flash_WriteSector(addr, pData,size);
    }
    else
    {   //多个block
        uint32_t tAddr = addr, tDataoffset = 0,tpoffset = 0;
        //切分数据段,分成几个block,然后逐个sector写
        for (int j = 1; j <= i; j++)
        {
            //tsize分步传输时的大小,
            uint32_t tsize;
            if (j != i)
            {
                tsize = (blockoffset+j)*65536 - tAddr;
                Flash_WriteSector(tAddr,pData+tpoffset,tsize);
                tpoffset += tsize;
                tAddr += tsize;
            }
            else//最后一次
            {
                tsize = size + addr - tAddr;
                Flash_WriteSector(tAddr,pData+tpoffset, tsize);
                tpoffset = size - tpoffset;
            }
        }
    }
}

GPIO模拟SPI:

某些开发板不能使用硬件SPI,所以使用GPIO模拟SPI,这部分可以不看,博客里没用到。

//w25qxx.h
#include "stm32f4xx_hal.h"
#include "delay.h"
#include <stdio.h>

#define FLASH_CS_PORT GPIOC
#define FLASH_CS_PIN  GPIO_PIN_10
#define FLASH_MISO_PORT GPIOA
#define FLASH_MISO_PIN 	GPIO_PIN_15
#define FLASH_MOSI_PORT GPIOC
#define FLASH_MOSI_PIN  GPIO_PIN_11
#define FLASH_CLK_PORT GPIOC
#define FLASH_CLK_PIN  GPIO_PIN_12


#define W25QXX_CS_H()         HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_SET)
#define W25QXX_CS_L()         HAL_GPIO_WritePin(FLASH_CS_PORT, FLASH_CS_PIN, GPIO_PIN_RESET)

#define W25QXX_SCK_H()        HAL_GPIO_WritePin(FLASH_CLK_PORT, FLASH_CLK_PIN, GPIO_PIN_SET)
#define W25QXX_SCK_L()        HAL_GPIO_WritePin(FLASH_CLK_PORT, FLASH_CLK_PIN, GPIO_PIN_RESET)

#define W25QXX_MOSI_H()       HAL_GPIO_WritePin(FLASH_MOSI_PORT, FLASH_MOSI_PIN, GPIO_PIN_SET)
#define W25QXX_MOSI_L()       HAL_GPIO_WritePin(FLASH_MOSI_PORT, FLASH_MOSI_PIN, GPIO_PIN_RESET)

#define W25QXX_MISO_GET()      (HAL_GPIO_ReadPin(FLASH_MISO_PORT, FLASH_MISO_PIN) == GPIO_PIN_SET ? 1 : 0)

#define W25Q80 	0XEF13 	
#define W25Q16 	0XEF14
#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
#define W25Q128	0XEF17

extern uint16_t W25QXX_TYPE;					//定义W25QXX芯片型号		   
extern uint32_t W25QXX_SIZE;                    //容量
extern uint8_t  W25QXX_UID[];                   //唯一ID

//指令表
#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			0xAB 
#define W25X_ManufactDeviceID	0x90 
#define W25X_JedecDeviceID		0x9F 


//初始化SPI FLASH的IO口
void W25QXX_Init(void);
uint8_t W25QXX_SPI_ReadWriteByte(uint8_t TxData);
uint16_t  W25QXX_ReadID(void);  	    		//读取FLASH ID
void W25QXX_ReadUniqueID(uint8_t UID[8]);       //读取唯一ID
uint32_t W25QXX_ReadCapacity(void);             //读取容量
uint8_t	 W25QXX_ReadSR(void);        		//读取状态寄存器 
void W25QXX_Write_SR(uint8_t sr);  			//写状态寄存器
void W25QXX_Write_Enable(void);  		//写使能 
void W25QXX_Write_Disable(void);		//写保护
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead);   //读取flash
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite);//写入flash
void W25QXX_Erase_Chip(void);    	  	//整片擦除
void W25QXX_Erase_Sector(uint32_t Dst_Addr);	//扇区擦除
void W25QXX_Wait_Busy(void);           	//等待空闲
void W25QXX_PowerDown(void);        	//进入掉电模式
void W25QXX_WAKEUP(void);				//唤醒
//w25qxx.c
#include "w25qxx.h"
uint16_t W25QXX_TYPE;					//定义W25QXX芯片型号		   
uint32_t W25QXX_SIZE;                    //容量
uint8_t  W25QXX_UID[];                   //唯一ID
void W25QXX_SPI_Init()
{
	GPIO_InitTypeDef gpio_spi={0};
	gpio_spi.Pin = FLASH_CS_PIN;
	gpio_spi.Mode = GPIO_MODE_OUTPUT_PP;
	gpio_spi.Pull = GPIO_NOPULL;
	gpio_spi.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(FLASH_CS_PORT,&gpio_spi);
	
	gpio_spi.Pin = FLASH_CLK_PIN;
	HAL_GPIO_Init(FLASH_CLK_PORT,&gpio_spi);
	
	gpio_spi.Pin = FLASH_MOSI_PIN;
	HAL_GPIO_Init(FLASH_MOSI_PORT,&gpio_spi);
	
	gpio_spi.Pin = FLASH_MISO_PIN;
	gpio_spi.Mode = GPIO_MODE_INPUT;
	HAL_GPIO_Init(FLASH_MISO_PORT,&gpio_spi);
}

uint8_t W25QXX_SPI_ReadWriteByte(uint8_t TxData)
{
    int i = 0;
    uint8_t RxData = 0;
    W25QXX_SCK_L();
    for(i = 7; i >= 0; i--)
    {
        W25QXX_SCK_L();
        if(TxData & (1 << i))
        {
            W25QXX_MOSI_H();
        }
        else
        {
            W25QXX_MOSI_L();
        }
        delay_us(1);
        W25QXX_SCK_H();
        RxData <<= 1;
        RxData |= W25QXX_MISO_GET();
        delay_us(1);
    }
    W25QXX_SCK_L();
    return RxData;
}

//4Kbytes为一个Sector
//16个扇区为1个Block
//W25Q128
//容量为16M字节,共有128个Block,4096个Sector

//初始化SPI FLASH的IO口
void W25QXX_Init(void)
{
	W25QXX_SPI_Init();
	W25QXX_CS_H();                // 取消片选
	W25QXX_SCK_L();               // 时钟空闲为低电平
  W25QXX_CS_L();
  W25QXX_SPI_ReadWriteByte(0XFF);
  W25QXX_CS_H();
  W25QXX_TYPE=W25QXX_ReadID();             //读取FLASH ID
	//printf("FLASH ID:0x%X\r\n",W25QXX_TYPE);
	W25QXX_SIZE=W25QXX_ReadCapacity();       //读取容量
	W25QXX_ReadUniqueID(W25QXX_UID);         //读取唯一ID
}
uint32_t W25QXX_ReadCapacity(void)
{
	int i = 0;
	uint8_t arr[4] = {0,0,0,0};
    W25QXX_CS_L();
    W25QXX_SPI_ReadWriteByte(0x5A);
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x84);
	W25QXX_SPI_ReadWriteByte(0x00);
	for(i = 0; i < sizeof(arr); i++)
	{
		arr[i] = W25QXX_SPI_ReadWriteByte(0xFF);
	}
    W25QXX_CS_H();
    return ((((*(uint32_t *)arr)) + 1) >> 3);
}

void W25QXX_ReadUniqueID(uint8_t UID[8])
{
	int i = 0;
	W25QXX_CS_L();
    W25QXX_SPI_ReadWriteByte(0x4B);
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x00);
	W25QXX_SPI_ReadWriteByte(0x00);
    for(i = 0; i < 8; i++)
	{
		UID[i] = W25QXX_SPI_ReadWriteByte(0xFF);
	}
	W25QXX_CS_H();
}

//读取W25QXX的状态寄存器
//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 W25QXX_ReadSR(void)
{
    uint8_t byte=0;
    W25QXX_CS_L();                            //使能器件
    W25QXX_SPI_ReadWriteByte(W25X_ReadStatusReg); //发送读取状态寄存器命令
    byte=W25QXX_SPI_ReadWriteByte(0Xff);          //读取一个字节
    W25QXX_CS_H();                            //取消片选
    return byte;
}
//写W25QXX状态寄存器
//只有SPR,TB,BP2,BP1,BP0(bit 7,5,4,3,2)可以写!!!
void W25QXX_Write_SR(uint8_t sr)
{
    W25QXX_CS_L();                            //使能器件
    W25QXX_SPI_ReadWriteByte(W25X_WriteStatusReg);//发送写取状态寄存器命令
    W25QXX_SPI_ReadWriteByte(sr);               	//写入一个字节
    W25QXX_CS_H();                            //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{
    W25QXX_CS_L();                          	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_WriteEnable); 	//发送写使能
    W25QXX_CS_H();                           	//取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{
    W25QXX_CS_L();                            //使能器件
    W25QXX_SPI_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令
    W25QXX_CS_H();                            //取消片选
}
//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
uint16_t W25QXX_ReadID(void)
{
    uint16_t Temp = 0;
    W25QXX_CS_L();
    W25QXX_SPI_ReadWriteByte(0x90);//发送读取ID命令
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x00);
    W25QXX_SPI_ReadWriteByte(0x00);
    Temp|=W25QXX_SPI_ReadWriteByte(0xFF)<<8;
    Temp|=W25QXX_SPI_ReadWriteByte(0xFF);
    W25QXX_CS_H();
    return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(uint8_t* pBuffer,uint32_t ReadAddr,uint16_t NumByteToRead)
{
    uint16_t i;
    W25QXX_CS_L();                            	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_ReadData);         	//发送读取命令
    W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr)>>16));  	//发送24bit地址
    W25QXX_SPI_ReadWriteByte((uint8_t)((ReadAddr)>>8));
    W25QXX_SPI_ReadWriteByte((uint8_t)ReadAddr);
    for(i=0; i<NumByteToRead; i++)
    {
        pBuffer[i]=W25QXX_SPI_ReadWriteByte(0XFF);   	//循环读数
    }
    W25QXX_CS_H();
}
//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    uint16_t i;
    W25QXX_Write_Enable();                  	//SET WEL
    W25QXX_CS_L();                            	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_PageProgram);      	//发送写页命令
    W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr)>>16)); 	//发送24bit地址
    W25QXX_SPI_ReadWriteByte((uint8_t)((WriteAddr)>>8));
    W25QXX_SPI_ReadWriteByte((uint8_t)WriteAddr);
    for(i=0; i<NumByteToWrite; i++)W25QXX_SPI_ReadWriteByte(pBuffer[i]); //循环写数
    W25QXX_CS_H();                            	//取消片选
    W25QXX_Wait_Busy();					   		//等待写入结束
}
//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    uint16_t pageremain;
    pageremain=256-WriteAddr%256; //单页剩余的字节数
    if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节
    while(1)
    {
        W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);
        if(NumByteToWrite==pageremain)break;//写入结束了
        else //NumByteToWrite>pageremain
        {
            pBuffer+=pageremain;
            WriteAddr+=pageremain;

            NumByteToWrite-=pageremain;			  //减去已经写入了的字节数
            if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节
            else pageremain=NumByteToWrite; 	  //不够256个字节了
        }
    };
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
uint8_t W25QXX_BUFFER[4096];
void W25QXX_Write(uint8_t* pBuffer,uint32_t WriteAddr,uint16_t NumByteToWrite)
{
    uint32_t secpos;
    uint16_t secoff;
    uint16_t secremain;
    uint16_t i;
    uint8_t * W25QXX_BUF;
    W25QXX_BUF=W25QXX_BUFFER;
    secpos=WriteAddr/4096;//扇区地址
    secoff=WriteAddr%4096;//在扇区内的偏移
    secremain=4096-secoff;//扇区剩余空间大小
    if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节
    while(1)
    {
        W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容
        for(i=0; i<secremain; i++) //校验数据
        {
            if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除
        }
        if(i<secremain)//需要擦除
        {
            W25QXX_Erase_Sector(secpos);		//擦除这个扇区
            for(i=0; i<secremain; i++)	   		//复制
            {
                W25QXX_BUF[i+secoff]=pBuffer[i];
            }
            W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区

        }
        else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain); //写已经擦除了的,直接写入扇区剩余区间.
        if(NumByteToWrite==secremain)break;//写入结束了
        else//写入未结束
        {
            secpos++;//扇区地址增1
            secoff=0;//偏移位置为0

            pBuffer+=secremain;  				//指针偏移
            WriteAddr+=secremain;				//写地址偏移
            NumByteToWrite-=secremain;			//字节数递减
            if(NumByteToWrite>4096)secremain=4096;//下一个扇区还是写不完
            else secremain=NumByteToWrite;		//下一个扇区可以写完了
        }
    };
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{
    W25QXX_Write_Enable();                 	 	//SET WEL
    W25QXX_Wait_Busy();
    W25QXX_CS_L();                            	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_ChipErase);        	//发送片擦除命令
    W25QXX_CS_H();                            	//取消片选
    W25QXX_Wait_Busy();   				   		//等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个山区的最少时间:150ms
void W25QXX_Erase_Sector(uint32_t Dst_Addr)
{
    //监视falsh擦除情况,测试用
    Dst_Addr*=4096;
    W25QXX_Write_Enable();                  	//SET WEL
    W25QXX_Wait_Busy();
    W25QXX_CS_L();                            	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_SectorErase);      	//发送扇区擦除指令
    W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr)>>16));  	//发送24bit地址
    W25QXX_SPI_ReadWriteByte((uint8_t)((Dst_Addr)>>8));
    W25QXX_SPI_ReadWriteByte((uint8_t)Dst_Addr);
    W25QXX_CS_H();                            	//取消片选
    W25QXX_Wait_Busy();   				   		//等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{
    while((W25QXX_ReadSR()&0x01)==0x01);  		// 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{
    W25QXX_CS_L();                           	 	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_PowerDown);        //发送掉电命令
    W25QXX_CS_H();                            	//取消片选
    delay_us(3);                               //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{
    W25QXX_CS_L();                            	//使能器件
    W25QXX_SPI_ReadWriteByte(W25X_ReleasePowerDown);	//  send W25X_PowerDown command 0xAB
    W25QXX_CS_H();                            	//取消片选
    delay_us(3);                            	//等待TRES1
}

一、FATFS

FATFS一般是配合Flash使用的。

方法一使用CubeMX 移植

方法二:手动移植

1.下载后解压:

"document"文件夹存放的是官方的API参考
"source"文件夹存放的是源文件

2.用CubeMX配置好SPI端口

主频72Mhz,用UART1和SPI1,具体请看本文末的工程连接。

3、复制文件

将FATFS中的“source”文件夹下的“ff.c”、“ff.h”、“ffconf.h”、“diskio.c”、“diskio.h”的5个文件复制到工程文件夹下的"FATFS"子文件夹(没有就新建)中。“source”文件夹下的其他文件可以不用移植。在这个5个文件中,我们需要修改其中的“ffconf.h”配置文件和“diskio.c”,“ff.c"底层存储设备驱动文件。

4.移植文件添加到工程

4.11、打开工程,在工程分组中创建一个“FATFS”分组,并将上述3个.c文件添加到工程,并设置头文件路径

4.2、编译工程

首先哪里报错删哪里。

查看错误或警告信息,正常为13个错误和17个警告

关于get_fattime() 这个错误是默认FATFS是使能RTC功能的。在ffconf.h中,将宏定义FF_FS_NORTC 设为1 可以将其关闭.。

此时编译工程不应该出现错误。

5.修改diskio.c

5.1、定义一个物理驱动器号

这个是定义在diskio.c的接近开头位置,原来的定义删除掉就好

这里我使用外部FLASH,如有多个存储设备,在这定义即可,物理驱动器号从“0”开始


/* 为每个设备定义一个物理编号 */
#define ATA			    0     // 预留SD卡使用
#define SPI_FLASH		1     // 外部SPI Flash

5.2、修改disk_status()函数

这个是获取驱动器状态的,大家可以根据返回状态自己编写,基本上能读取到ID就是正常的。

/*-----------------------------------------------------------------------*/
/* Get Drive Status                                                      */
/*-----------------------------------------------------------------------*/

DSTATUS disk_status (
	BYTE pdrv		/* Physical drive nmuber to identify the drive */
)
{
	DSTATUS status = STA_NOINIT;
	
	switch (pdrv) {
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:      
      /* SPI Flash状态检测:读取SPI Flash 设备ID ,这里的W25Q64是宏定义的0xEF16*/
      if(W25Q64 == Flash_ReadID())
      {
        /* 设备ID读取结果正确 */
        status &= ~STA_NOINIT;
				//printf("flashID:%X",sFLASH_ID);
      }
      else
      {
        /* 设备ID读取结果错误 */
        status = STA_NOINIT;;
      }
			break;

		default:
			status = STA_NOINIT;
	}
	return status;
}

5.3、修改disk_initialize()函数

这里对硬件进行初始化,注意包含相应的头文件,使用CubeMX,和硬件SPI不用初始化。

DSTATUS disk_initialize (
	BYTE pdrv				/* Physical drive nmuber to identify the drive */
)
{
	uint16_t i;
	DSTATUS status = STA_NOINIT;	
	switch (pdrv) {
		case ATA:	         /* SD CARD */
			break;
    
		case SPI_FLASH:    /* SPI Flash */ 
      /* 初始化SPI Flash */
			//SPI_FLASH_Init();
      /* 延时一小段时间 */
      i=500;
	    while(--i);	
      /* 唤醒SPI Flash */
	    //SPI_Flash_WAKEUP();
      /* 获取SPI Flash芯片状态 */
      status=disk_status(SPI_FLASH);
			break;
      
		default:
			status = STA_NOINIT;
	}
	return status;
}

5.4、修改disk_read()函数

这个函数的功能是从外部FLASH中读取数据 

/*-----------------------------------------------------------------------*/
/* Read Sector(s)                                                        */
/*-----------------------------------------------------------------------*/

DRESULT disk_read (
	BYTE pdrv,		/* Physical drive nmuber to identify the drive */
	BYTE *buff,		/* Data buffer to store read data */
	LBA_t sector,	/* Start sector in LBA */
	UINT count		/* Number of sectors to read */
)
{
	DRESULT status = RES_PARERR;
	switch (pdrv) {
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:
      /* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */
      sector+=512;      
			Flash_ReadBytes( sector<<12,buff,count<<12);

      status = RES_OK;
		
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}

5.5定义以下宏

不用定义

5.6、修改disk_write()函数

这个函数的功能是向外部FLASH中写入数据

DRESULT disk_write (
	BYTE pdrv,			/* Physical drive nmuber to identify the drive */
	const BYTE *buff,	/* Data to be written */
	LBA_t sector,		/* Start sector in LBA */
	UINT count			/* Number of sectors to write */
)
{
	uint32_t write_addr; 
	DRESULT status = RES_PARERR;
	if (!count) {
		return RES_PARERR;		/* Check parameter */
	}

	switch (pdrv) {
		case ATA:	/* SD CARD */      
		break;

		case SPI_FLASH:
      /* 扇区偏移2MB,外部Flash文件系统空间放在SPI Flash后面6MB空间 */
			sector+=512;
          write_addr = sector<<12;
    /*调用任意位置写函数*/    
	      Flash_Write(write_addr,(uint8_t *)buff,count<<12);
          status = RES_OK;
		  break;
		default:
			status = RES_PARERR;
	}
	return status;
}

5.7 修改disk_ioctl()函数

这个函数是控制设备实现指定功能,用于辅助FATFS中其他API。


DRESULT disk_ioctl (
	BYTE pdrv,		/* Physical drive nmuber (0..) */
	BYTE cmd,		/* Control code */
	void *buff		/* Buffer to send/receive control data */
)
{
DRESULT status = RES_PARERR;
	switch (pdrv) {
		case ATA:	/* SD CARD */
			break;
    
		case SPI_FLASH:
			switch (cmd) {
				 /* 扇区数量:2048*4096/1024/1024=8(MB) ,这个是以W25Q64为例*/
        /* 扇区数量:1536*4096/1024/1024=6(MB),这里是使用了后6M空间 */
        case GET_SECTOR_COUNT:
          *(DWORD * )buff = 1536;		
        break;
        /* 扇区大小,操作系统是按扇区读写的,写不够一个扇区下次也不会再用了,读写都用同一个大小。Flash写的时候需要先擦除再写入,W25Q64最小可以写一个page,256byte。但是最小擦除却是一个sector,所以这里选4Kb*/
        case GET_SECTOR_SIZE :
          *(WORD * )buff = 4096;
        break;
        /* 同时擦除扇区个数 */
        case GET_BLOCK_SIZE :
          *(DWORD * )buff = 1;
        break;        
      }
      status = RES_OK;
		break;
    
		default:
			status = RES_PARERR;
	}
	return status;
}

文件修改完成。

8、配置“ffconf.h”文件

设置"FF_USE_STRFUNC" 宏为1,这个开启之后可以使用字符串辅助函数,例如f_gets()、f_putc()、f_puts() 、 f_printf() 等等

"FF_CODE_PAGE"为936,即为简体中文编码,设置不正确可能会导致打开文件失败 

3、"FF_VOLUMES"这个宏是设置有几个卷,这里我的只有2个,填此处预留了SD卡为0,FLASH为1。

/*---------------------------------------------------------------------------/
/  Configurations of FatFs Module
/---------------------------------------------------------------------------*/

#define FFCONF_DEF	5380	/* Revision ID */

/*---------------------------------------------------------------------------/
/ Function Configurations
/---------------------------------------------------------------------------*/

#define FF_FS_READONLY	0
/* This option switches read-only configuration. (0:Read/Write or 1:Read-only)
/  Read-only configuration removes writing API functions, f_write(), f_sync(),
/  f_unlink(), f_mkdir(), f_chmod(), f_rename(), f_truncate(), f_getfree()
/  and optional writing functions as well. */


#define FF_FS_MINIMIZE	0
/* This option defines minimization level to remove some basic API functions.
/
/   0: Basic functions are fully enabled.
/   1: f_stat(), f_getfree(), f_unlink(), f_mkdir(), f_truncate() and f_rename()
/      are removed.
/   2: f_opendir(), f_readdir() and f_closedir() are removed in addition to 1.
/   3: f_lseek() function is removed in addition to 2. */


#define FF_USE_FIND		0
/* This option switches filtered directory read functions, f_findfirst() and
/  f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */


#define FF_USE_MKFS		1
/* This option switches f_mkfs(). (0:Disable or 1:Enable) */


#define FF_USE_FASTSEEK	1
/* This option switches fast seek feature. (0:Disable or 1:Enable) */


#define FF_USE_EXPAND	0
/* This option switches f_expand(). (0:Disable or 1:Enable) */


#define FF_USE_CHMOD	0
/* This option switches attribute control API functions, f_chmod() and f_utime().
/  (0:Disable or 1:Enable) Also FF_FS_READONLY needs to be 0 to enable this option. */


#define FF_USE_LABEL	0
/* This option switches volume label API functions, f_getlabel() and f_setlabel().
/  (0:Disable or 1:Enable) */


#define FF_USE_FORWARD	0
/* This option switches f_forward(). (0:Disable or 1:Enable) */


#define FF_USE_STRFUNC	0
#define FF_PRINT_LLI	0
#define FF_PRINT_FLOAT	0
#define FF_STRF_ENCODE	3
/* FF_USE_STRFUNC switches the string API functions, f_gets(), f_putc(), f_puts()
/  and f_printf().
/
/   0: Disable. FF_PRINT_LLI, FF_PRINT_FLOAT and FF_STRF_ENCODE have no effect.
/   1: Enable without LF - CRLF conversion.
/   2: Enable with LF - CRLF conversion.
/
/  FF_PRINT_LLI = 1 makes f_printf() support long long argument and FF_PRINT_FLOAT = 1/2
/  makes f_printf() support floating point argument. These features want C99 or later.
/  When FF_LFN_UNICODE >= 1 with LFN enabled, string API functions convert the character
/  encoding in it. FF_STRF_ENCODE selects assumption of character encoding ON THE FILE
/  to be read/written via those functions.
/
/   0: ANSI/OEM in current CP
/   1: Unicode in UTF-16LE
/   2: Unicode in UTF-16BE
/   3: Unicode in UTF-8
*/


/*---------------------------------------------------------------------------/
/ Locale and Namespace Configurations
/---------------------------------------------------------------------------*/

#define FF_CODE_PAGE	936
/* This option specifies the OEM code page to be used on the target system.
/  Incorrect code page setting can cause a file open failure.
/
/   437 - U.S.
/   720 - Arabic
/   737 - Greek
/   771 - KBL
/   775 - Baltic
/   850 - Latin 1
/   852 - Latin 2
/   855 - Cyrillic
/   857 - Turkish
/   860 - Portuguese
/   861 - Icelandic
/   862 - Hebrew
/   863 - Canadian French
/   864 - Arabic
/   865 - Nordic
/   866 - Russian
/   869 - Greek 2
/   932 - Japanese (DBCS)
/   936 - Simplified Chinese (DBCS)
/   949 - Korean (DBCS)
/   950 - Traditional Chinese (DBCS)
/     0 - Include all code pages above and configured by f_setcp()
*/


#define FF_USE_LFN		1
#define FF_MAX_LFN		255
/* The FF_USE_LFN switches the support for LFN (long file name).
/
/   0: Disable LFN. FF_MAX_LFN has no effect.
/   1: Enable LFN with static working buffer on the BSS. Always NOT thread-safe.
/   2: Enable LFN with dynamic working buffer on the STACK.
/   3: Enable LFN with dynamic working buffer on the HEAP.
/
/  To enable the LFN, ffunicode.c needs to be added to the project. The LFN feature
/  requiers certain internal working buffer occupies (FF_MAX_LFN + 1) * 2 bytes and
/  additional (FF_MAX_LFN + 44) / 15 * 32 bytes when exFAT is enabled.
/  The FF_MAX_LFN defines size of the working buffer in UTF-16 code unit and it can
/  be in range of 12 to 255. It is recommended to be set 255 to fully support the LFN
/  specification.
/  When use stack for the working buffer, take care on stack overflow. When use heap
/  memory for the working buffer, memory management functions, ff_memalloc() and
/  ff_memfree() exemplified in ffsystem.c, need to be added to the project. */


#define FF_LFN_UNICODE	0
/* This option switches the character encoding on the API when LFN is enabled.
/
/   0: ANSI/OEM in current CP (TCHAR = char)
/   1: Unicode in UTF-16 (TCHAR = WCHAR)
/   2: Unicode in UTF-8 (TCHAR = char)
/   3: Unicode in UTF-32 (TCHAR = DWORD)
/
/  Also behavior of string I/O functions will be affected by this option.
/  When LFN is not enabled, this option has no effect. */


#define FF_LFN_BUF		255
#define FF_SFN_BUF		12
/* This set of options defines size of file name members in the FILINFO structure
/  which is used to read out directory items. These values should be suffcient for
/  the file names to read. The maximum possible length of the read file name depends
/  on character encoding. When LFN is not enabled, these options have no effect. */


#define FF_FS_RPATH		0
/* This option configures support for relative path.
/
/   0: Disable relative path and remove related API functions.
/   1: Enable relative path. f_chdir() and f_chdrive() are available.
/   2: f_getcwd() is available in addition to 1.
*/


/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/

#define FF_VOLUMES		2
/* Number of volumes (logical drives) to be used. (1-10) */


#define FF_STR_VOLUME_ID	0
#define FF_VOLUME_STRS		"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/  When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/  number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
/  logical drive. Number of items must not be less than FF_VOLUMES. Valid
/  characters for the volume ID strings are A-Z, a-z and 0-9, however, they are
/  compared in case-insensitive. If FF_STR_VOLUME_ID >= 1 and FF_VOLUME_STRS is
/  not defined, a user defined volume string table is needed as:
/
/  const char* VolumeStr[FF_VOLUMES] = {"ram","flash","sd","usb",...
*/


#define FF_MULTI_PARTITION	0
/* This option switches support for multiple volumes on the physical drive.
/  By default (0), each logical drive number is bound to the same physical drive
/  number and only an FAT volume found on the physical drive will be mounted.
/  When this feature is enabled (1), each logical drive number can be bound to
/  arbitrary physical drive and partition listed in the VolToPart[]. Also f_fdisk()
/  will be available. */

/*W25Q64的Sector大小是4096所以FF_MAX_SS配成4096*/
#define FF_MIN_SS		512
#define FF_MAX_SS		4096
/* This set of options configures the range of sector size to be supported. (512,
/  1024, 2048 or 4096) Always set both 512 for most systems, generic memory card and
/  harddisk, but a larger value may be required for on-board flash memory and some
/  type of optical media. When FF_MAX_SS is larger than FF_MIN_SS, FatFs is
/  configured for variable sector size mode and disk_ioctl() needs to implement
/  GET_SECTOR_SIZE command. */


#define FF_LBA64		0
/* This option switches support for 64-bit LBA. (0:Disable or 1:Enable)
/  To enable the 64-bit LBA, also exFAT needs to be enabled. (FF_FS_EXFAT == 1) */


#define FF_MIN_GPT		0x10000000
/* Minimum number of sectors to switch GPT as partitioning format in f_mkfs() and 
/  f_fdisk(). 2^32 sectors maximum. This option has no effect when FF_LBA64 == 0. */


#define FF_USE_TRIM		0
/* This option switches support for ATA-TRIM. (0:Disable or 1:Enable)
/  To enable this feature, also CTRL_TRIM command should be implemented to
/  the disk_ioctl(). */



/*---------------------------------------------------------------------------/
/ System Configurations
/---------------------------------------------------------------------------*/

#define FF_FS_TINY		0
/* This option switches tiny buffer configuration. (0:Normal or 1:Tiny)
/  At the tiny configuration, size of file object (FIL) is shrinked FF_MAX_SS bytes.
/  Instead of private sector buffer eliminated from the file object, common sector
/  buffer in the filesystem object (FATFS) is used for the file data transfer. */


#define FF_FS_EXFAT		0
/* This option switches support for exFAT filesystem. (0:Disable or 1:Enable)
/  To enable exFAT, also LFN needs to be enabled. (FF_USE_LFN >= 1)
/  Note that enabling exFAT discards ANSI C (C89) compatibility. */


#define FF_FS_NORTC		0
#define FF_NORTC_MON	11
#define FF_NORTC_MDAY	1
#define FF_NORTC_YEAR	2024
/* The option FF_FS_NORTC switches timestamp feature. If the system does not have
/  an RTC or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable the
/  timestamp feature. Every object modified by FatFs will have a fixed timestamp
/  defined by FF_NORTC_MON, FF_NORTC_MDAY and FF_NORTC_YEAR in local time.
/  To enable timestamp function (FF_FS_NORTC = 0), get_fattime() need to be added
/  to the project to read current time form real-time clock. FF_NORTC_MON,
/  FF_NORTC_MDAY and FF_NORTC_YEAR have no effect.
/  These options have no effect in read-only configuration (FF_FS_READONLY = 1). */


#define FF_FS_NOFSINFO	0
/* If you need to know correct free space on the FAT32 volume, set bit 0 of this
/  option, and f_getfree() at the first time after volume mount will force
/  a full FAT scan. Bit 1 controls the use of last allocated cluster number.
/
/  bit0=0: Use free cluster count in the FSINFO if available.
/  bit0=1: Do not trust free cluster count in the FSINFO.
/  bit1=0: Use last allocated cluster number in the FSINFO if available.
/  bit1=1: Do not trust last allocated cluster number in the FSINFO.
*/


#define FF_FS_LOCK		0
/* The option FF_FS_LOCK switches file lock function to control duplicated file open
/  and illegal operation to open objects. This option must be 0 when FF_FS_READONLY
/  is 1.
/
/  0:  Disable file lock function. To avoid volume corruption, application program
/      should avoid illegal open, remove and rename to the open objects.
/  >0: Enable file lock function. The value defines how many files/sub-directories
/      can be opened simultaneously under file lock control. Note that the file
/      lock control is independent of re-entrancy. */


#define FF_FS_REENTRANT	0
#define FF_FS_TIMEOUT	1000
/* The option FF_FS_REENTRANT switches the re-entrancy (thread safe) of the FatFs
/  module itself. Note that regardless of this option, file access to different
/  volume is always re-entrant and volume control functions, f_mount(), f_mkfs()
/  and f_fdisk(), are always not re-entrant. Only file/directory access to
/  the same volume is under control of this featuer.
/
/   0: Disable re-entrancy. FF_FS_TIMEOUT have no effect.
/   1: Enable re-entrancy. Also user provided synchronization handlers,
/      ff_mutex_create(), ff_mutex_delete(), ff_mutex_take() and ff_mutex_give(),
/      must be added to the project. Samples are available in ffsystem.c.
/
/  The FF_FS_TIMEOUT defines timeout period in unit of O/S time tick.
*/



/*--- End of configuration options ---*/

其他宏可根据需求选择开启。

一般最简单的就是FF_USE_MKFS        1。就是flash文件系统的格式化。

FF_USE_LEN 1支持长文件名

FF_MAX_SS        4096。最大sector大小,只要比最小的大运行起来后就会自动获取到。

FF_FS_REENTRANT    0//这个是线程安全方面的配置,主要用在RTOS上边。

ff.c

由于在ffconf.h中设置了FF_FS_NORTC 0,就是启用了系统时间戳,所以需要实现一个get_fattime函数,这里简单低返回一个固定时间。

/* Timestamp */
#if FF_FS_NORTC == 1
#if FF_NORTC_YEAR < 1980 || FF_NORTC_YEAR > 2107 || FF_NORTC_MON < 1 || FF_NORTC_MON > 12 || FF_NORTC_MDAY < 1 || FF_NORTC_MDAY > 31
#error Invalid FF_FS_NORTC settings
#endif
#define GET_FATTIME()	((DWORD)(FF_NORTC_YEAR - 1980) << 25 | (DWORD)FF_NORTC_MON << 21 | (DWORD)FF_NORTC_MDAY << 16)
#else
#define GET_FATTIME()	get_fattime()
DWORD get_fattime (void)
{
  /* 在这里添加实际的获取时间的方法... */
  
  
  /* 对应的要修改返回值 */
  return ((2021-1980)<<25) | ((1)<<21) | ((1)<<16) | ((1)<<11) | ((1)<<5) | ((1)<<0);
}
#endif

重点 &疑难杂症:

main.c文件测试用例:

/* USER CODE BEGIN Header */
/**
  ******************************************************************************
  * @file           : main.c
  * @brief          : Main program body
  ******************************************************************************
  * @attention
  *
  * Copyright (c) 2024 STMicroelectronics.
  * All rights reserved.
  *
  * This software is licensed under terms that can be found in the LICENSE file
  * in the root directory of this software component.
  * If no LICENSE file comes with this software, it is provided AS-IS.
  *
  ******************************************************************************
  */
/* USER CODE END Header */
/* Includes ------------------------------------------------------------------*/
#include "main.h"
#include "spi.h"
#include "usart.h"
#include "gpio.h"

/* Private includes ----------------------------------------------------------*/
/* USER CODE BEGIN Includes */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "w25flash.h"
#include "ff.h"
/* USER CODE END Includes */

/* Private typedef -----------------------------------------------------------*/
/* USER CODE BEGIN PTD */
 int fputc(int ch, FILE *f)
{
//这里假设UART1是要打印的串口
  HAL_UART_Transmit(&huart1, (uint8_t *)&ch, 1, 10);
  return ch;
}

BYTE work[FF_MAX_SS];//格式化设备工作区,注意这个空间太大,不能放到栈里,否则卡死。或者malloc一个也行(注意修改startup_stm32f103xe.s中的堆大小)

FATFS fs;													/* FatFs文件系统对象 */
FIL fnew;													/* 文件对象 */
FRESULT res_flash;                /* 文件操作结果 */
UINT fnum;            					  /* 文件成功读写数量 */
BYTE ReadBuffer[1024]={0};        /* 读缓冲区 */
BYTE WriteBuffer[] = "图片较大请等待\r\n"; 
/* USER CODE END PTD */

/* Private define ------------------------------------------------------------*/
/* USER CODE BEGIN PD */
//串口接收中断处理函数
char USART_RX_BUF[255]="";//字符串缓冲区
uint8_t aRxBuffer;			//单个字符接收缓冲区
uint8_t Uart1_Rx_Cnt = 0;		//接收字符计数器
void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart)
{
	if(Uart1_Rx_Cnt >= 255)  //溢出了
	{
		Uart1_Rx_Cnt = 0;
		memset(USART_RX_BUF,0x00,sizeof(USART_RX_BUF));
		//HAL_UART_Transmit(&huart1, (uint8_t *)"串口接收缓冲区溢出,size>255", strlen(""串口接收缓冲区溢出,size>255""),0xFFFF); 	
        
	}
	else
	{
		USART_RX_BUF[Uart1_Rx_Cnt++] = aRxBuffer;   //接收到的单个字符放到缓冲区
	
		if((USART_RX_BUF[Uart1_Rx_Cnt-1] == 0x0A)&&(USART_RX_BUF[Uart1_Rx_Cnt-2] == 0x0D)) //是否接收到了回车换行符,判断发送完成了吗
		{
			HAL_UART_Transmit(&huart1, (uint8_t *)"receive:\n", strlen("receive:\n"),0xFFFF);
			while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX)
			{
			}
			HAL_UART_Transmit(&huart1, (uint8_t *)&USART_RX_BUF, Uart1_Rx_Cnt,0xFFFF);
			while(HAL_UART_GetState(&huart1) == HAL_UART_STATE_BUSY_TX)
			{
			}
			Uart1_Rx_Cnt = 0;
			memset(USART_RX_BUF,0x00,sizeof(USART_RX_BUF));
		}
	}
	
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
}
/* USER CODE END PD */

/* Private macro -------------------------------------------------------------*/
/* USER CODE BEGIN PM */
//要写入到W25Q64的字符串数组
const uint8_t TEXT_Buffer[]={"MyTest FAT TeST"};
#define SIZE sizeof(TEXT_Buffer)
/* USER CODE END PM */

/* Private variables ---------------------------------------------------------*/

/* USER CODE BEGIN PV */

/* USER CODE END PV */

/* Private function prototypes -----------------------------------------------*/
void SystemClock_Config(void);
/* USER CODE BEGIN PFP */

/* USER CODE END PFP */

/* Private user code ---------------------------------------------------------*/
/* USER CODE BEGIN 0 */

/* USER CODE END 0 */

/**
  * @brief  The application entry point.
  * @retval int
  */
int main(void)
{

  /* USER CODE BEGIN 1 */

  /* USER CODE END 1 */

  /* MCU Configuration--------------------------------------------------------*/

  /* Reset of all peripherals, Initializes the Flash interface and the Systick. */
  HAL_Init();

  /* USER CODE BEGIN Init */

  /* USER CODE END Init */

  /* Configure the system clock */
  SystemClock_Config();

  /* USER CODE BEGIN SysInit */

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_SPI1_Init();
  MX_USART1_UART_Init();
  /* USER CODE BEGIN 2 */
	HAL_UART_Receive_IT(&huart1, (uint8_t *)&aRxBuffer, 1);
	printf("****** 这是一个SPI FLASH 文件系统实验 ******\r\n");
  
	//在外部SPI Flash挂载文件系统,文件系统挂载时会对SPI设备初始化
	//初始化函数调用流程如下
	//f_mount()->find_volume()->disk_initialize->SPI_FLASH_Init()
	res_flash = f_mount(&fs,"1:",1);
	/*----------------------- 格式化测试 -----------------*/  
	/* 如果没有文件系统就格式化创建创建文件系统 */
	if(res_flash == FR_NO_FILESYSTEM)
	{
		printf("》FLASH还没有文件系统,即将进行格式化...\r\n");
    /* 格式化 */
		res_flash=f_mkfs("1:", 0, work, sizeof(work));						
		
		if(res_flash == FR_OK)
		{
			printf("》FLASH已成功格式化文件系统。\r\n");
      /* 格式化后,先取消挂载 */
			res_flash = f_mount(NULL,"1:",1);			
      /* 重新挂载	*/			
			res_flash = f_mount(&fs,"1:",1);
		}
		else
		{
			printf("《《格式化失败。》》\r\n");
			while(1);
		}
	}
  else if(res_flash!=FR_OK)
  {
    printf("!!外部Flash挂载文件系统失败。(%d)\r\n",res_flash);
    printf("请下载 SPI—读写串行FLASH 例程测试,如果正常,在该例程f_mount语句下if语句前临时多添加一句 res_flash = FR_NO_FILESYSTEM; 让重新直接执行格式化流程\r\n");
		while(1);
  }
  else
  {
    printf("》文件系统挂载成功,可以进行读写测试\r\n");
  }
	/*----------------------- 文件系统测试:写测试 -------------------*/
	/* 打开文件,每次都以新建的形式打开,属性为可写 */
	printf("\r\n****** 即将进行文件写入测试... ******\r\n");	
	res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",FA_CREATE_ALWAYS | FA_WRITE );
	if ( res_flash == FR_OK )
	{
		printf("》打开/创建FatFs读写测试文件.txt文件成功,向文件写入数据。\r\n");
    /* 将指定存储区内容写入到文件内 */
		res_flash=f_write(&fnew,WriteBuffer,sizeof(WriteBuffer),&fnum);
    if(res_flash==FR_OK)
    {
      printf("》文件写入成功,写入字节数据:%d\n",fnum);
      printf("》向文件写入的数据为:\r\n%s\r\n",WriteBuffer);
    }
    else
    {
      printf("!!文件写入失败:(%d)\n",res_flash);
    }    
		/* 不再读写,关闭文件 */
    f_close(&fnew);
	}
	else
	{	
		printf("!!打开/创建文件失败。\r\n");
	}
	
/*------------------- 文件系统测试:读测试 --------------------------*/
	printf("****** 即将进行文件读取测试... ******\r\n");
	res_flash = f_open(&fnew, "1:FatFs读写测试文件.txt",FA_OPEN_EXISTING | FA_READ); 	 
	if(res_flash == FR_OK)
	{
		printf("》打开文件成功。\r\n");
		res_flash = f_read(&fnew, ReadBuffer, sizeof(ReadBuffer), &fnum); 
    if(res_flash==FR_OK)
    {
      printf("》文件读取成功,读到字节数据:%d\r\n",fnum);
      printf("》读取得的文件数据为:\r\n%s \r\n", ReadBuffer);	
    }
    else
    {
      printf("!!文件读取失败:(%d)\n",res_flash);
    }		
	}
	else
	{
		printf("!!打开文件失败。\r\n");
	}
	/* 不再读写,关闭文件 */
	f_close(&fnew);	
  
	/* 不再使用文件系统,取消挂载文件系统 */
	f_mount(NULL,"1:",1);
  printf("测试完成进入while循环\r\n");
	
  /* USER CODE END 2 */

  /* Infinite loop */
  /* USER CODE BEGIN WHILE */
  while (1)
  {
		HAL_Delay(1000);
    /* USER CODE END WHILE */

    /* USER CODE BEGIN 3 */
  }
  /* USER CODE END 3 */
}

/**
  * @brief System Clock Configuration
  * @retval None
  */
void SystemClock_Config(void)
{
  RCC_OscInitTypeDef RCC_OscInitStruct = {0};
  RCC_ClkInitTypeDef RCC_ClkInitStruct = {0};

  /** Initializes the RCC Oscillators according to the specified parameters
  * in the RCC_OscInitTypeDef structure.
  */
  RCC_OscInitStruct.OscillatorType = RCC_OSCILLATORTYPE_HSE;
  RCC_OscInitStruct.HSEState = RCC_HSE_ON;
  RCC_OscInitStruct.HSEPredivValue = RCC_HSE_PREDIV_DIV1;
  RCC_OscInitStruct.HSIState = RCC_HSI_ON;
  RCC_OscInitStruct.PLL.PLLState = RCC_PLL_ON;
  RCC_OscInitStruct.PLL.PLLSource = RCC_PLLSOURCE_HSE;
  RCC_OscInitStruct.PLL.PLLMUL = RCC_PLL_MUL9;
  if (HAL_RCC_OscConfig(&RCC_OscInitStruct) != HAL_OK)
  {
    Error_Handler();
  }

  /** Initializes the CPU, AHB and APB buses clocks
  */
  RCC_ClkInitStruct.ClockType = RCC_CLOCKTYPE_HCLK|RCC_CLOCKTYPE_SYSCLK
                              |RCC_CLOCKTYPE_PCLK1|RCC_CLOCKTYPE_PCLK2;
  RCC_ClkInitStruct.SYSCLKSource = RCC_SYSCLKSOURCE_PLLCLK;
  RCC_ClkInitStruct.AHBCLKDivider = RCC_SYSCLK_DIV1;
  RCC_ClkInitStruct.APB1CLKDivider = RCC_HCLK_DIV2;
  RCC_ClkInitStruct.APB2CLKDivider = RCC_HCLK_DIV1;

  if (HAL_RCC_ClockConfig(&RCC_ClkInitStruct, FLASH_LATENCY_2) != HAL_OK)
  {
    Error_Handler();
  }
}

/* USER CODE BEGIN 4 */

/* USER CODE END 4 */

/**
  * @brief  This function is executed in case of error occurrence.
  * @retval None
  */
void Error_Handler(void)
{
  /* USER CODE BEGIN Error_Handler_Debug */
  /* User can add his own implementation to report the HAL error return state */
  __disable_irq();
  while (1)
  {
  }
  /* USER CODE END Error_Handler_Debug */
}

#ifdef  USE_FULL_ASSERT
/**
  * @brief  Reports the name of the source file and the source line number
  *         where the assert_param error has occurred.
  * @param  file: pointer to the source file name
  * @param  line: assert_param error line source number
  * @retval None
  */
void assert_failed(uint8_t *file, uint32_t line)
{
  /* USER CODE BEGIN 6 */
  /* User can add his own implementation to report the file name and line number,
     ex: printf("Wrong parameters value: file %s on line %d\r\n", file, line) */
  /* USER CODE END 6 */
}
#endif /* USE_FULL_ASSERT */

缓冲区work,不能放到main函数体里,程序会卡死,没有反应也没有输出,因为在分配栈空间时就卡死了,没有机会发串口数据。如果你和我一样把它当全局变量定义那就没问题了,stm32f103rct6的RAM大小有20K,可以完全载入内存中,在c语言里未初始化的全局变量即不在栈空间也不在堆空间里,而是在bss段中,不受堆栈空间大小限制。

野火的示例还是用HAL的封装,按标准库流程进行编程,感觉差点意思,所以就封装了一下。

运行结果:

参考文献:

主要成功是参考了王维波的W25Q64驱动程序,然后是参考野火F103霸道开发板示例。

公益项目,免费分享工程压缩包:

通过网盘分享的文件:FatFs.rar
链接: https://pan.baidu.com/s/177kN9Q-kCFZIQZM-xJ3hcQ?pwd=rhsz 提取码: rhsz
--来自百度网盘超级会员v5的分享

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值