控制/MCU
导语“本教程将使用CubeMX初始化SPI,使用SPI对W25Q64 FLASH进行读写操作,通过HAL库的读写应用来数据FLASH的操作细节。”
•硬件
野火指南者开发板
•软件
CubeMx & MDK & 串口调试助手
•原理图
根据原理图,我们看到FLASH连接在SPI1上,我们不适用SPI1自带片选,使用PC0开进行软件片选。
(1) 我们还是使用前面的USART串口(串口的配置没有变化)项目,在此基础上进行SPI1 的配置:
我们从配置的信息上看,使用了SPI1,主从全双工模式,8位数据传输,高字节在前,模式3,CRC不校验,软件控制。SPI的模式配置看下图,采样时刻是偶数边沿所以CLOCK Phase =2:
(2) 完成片选信号的GPIO配置
完成上述配置后点击代码生成。
将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发送一个数据
}
(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的读写测试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");
我是使用串口将写入FLASH的数据进行读出来,比较数据是否一致,同时独处FLASH的ID看是否一致:
我们可以可以看到FLASH的ID是0xef4017和产品手册中W25Q64的ID是一致的,读写测试也是正常的,我们使用了C库函数,需要包含头文件。
全部0条评论
快来发表一下你的评论吧 !