本实验通过STM32F103 的SPI功能,实现对W25Q64JVSSIQ (Flash芯片)芯片擦除,读数据,写数据等操作。
本实验内容知识点:
1、SPI通信协议介绍
2、闪存 W25Q64JVSS 手册 解析
准备好了吗?开始实战show time。
1、硬件开发准备
主控:STM32F103ZET6
Flash芯片:W25Q64JVSSIQ
F_CLK:PB3 F_MISO:PB4 F_MOSI:PB5 F_CS:PA15
2、软件开发准备
软件开发使用虚拟机 + VScode + STM32Cube 开发STM32,在虚拟机中直接完成编译下载。
该部分可参考:软件开发环境构建
1、STM32CubeMX基本配置
本实验基于CubeMX详解构建基本框架 进行开发。
2、STM32CubeMX SPI相关配置
(1)GPIO配置
(2)spi配置
spi相关配置需要查看从设备手册,结合配置。
1、flash相关硬件引脚以及控制命令初始化
#define Myspi_Flash_CS_Enable HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_RESET);
#define Myspi_Flash_CS_Disable HAL_GPIO_WritePin(SPI_Flash_CS_GPIO_Port, SPI_Flash_CS_Pin, GPIO_PIN_SET);
#define W25_Write_Enable 0x06
#define W25_Write_Disable 0x04
#define W25_Read_Status_Register 0x05
#define W25_Read_Device_ID 0x9F
#define W25_Sector_Erase 0x20
#define W25_Chip_Erase 0xc7
#define W25_Read_Data 0x03
#define W25_Page_Program 0x02
#define SPI_FLASH_PageSize 256
2、构建spi相关结构体
// 定义
typedef struct Myspi_s
{
void (*SPI_Read_Device_ID)(void);
void (*SPI_EraseSector)(uint32_t);
void (*SPI_ChipSector)(void);
void (*SPI_ReadDataUnfixed)(uint32_t, uint8_t*, uint16_t );
void (*SPI_WriteDataUnfixed)(uint32_t, uint8_t*, uint16_t);
} Myspi_t;
// 下面详细讲解
static void SPI_Read_Device_ID(void);
static void SPI_EraseSector(uint32_t);
static void SPI_ChipSector(void);
static void SPI_ReadDataUnfixed(uint32_t ReadAddr, uint8_t* pReadBuffer, uint16_t len);
static void SPI_WriteDataUnfixed(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len);
// 初始化
Myspi_t Myspi = {
SPI_Read_Device_ID,
SPI_EraseSector,
SPI_ChipSector,
SPI_ReadDataUnfixed,
SPI_WriteDataUnfixed
};
3、spi基础函数
(1)读写一个字节函数
// SPI读一个字节
static uint8_t SPI_Read_One_Byte(void)
{
uint8_t Recvice_Byte;
// 读取1个字节
if( HAL_OK != (HAL_SPI_Receive(&hspi3, &Recvice_Byte, 1, 0x0A) ))
Recvice_Byte = 0xFF;
// 返回读取到的值
return Recvice_Byte;
}
// SPI写一个字节
static void SPI_Write_One_Byte(uint8_t data)
{
uint8_t tmp_data = data;
// 写一个字节
HAL_SPI_Transmit(&hspi3, &tmp_data, 1, 0x0A);
}
(2)写使能、读状态寄存器,根据闪存 W25Q64JVSS 手册 解析中时序编写代码
// 写使能
static void SPI_Flash_WriteEnable()
{
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送命令:写使能
SPI_Write_One_Byte(W25_Write_Enable);
//片选拉高释放
Myspi_Flash_CS_Disable;
}
// 读状态寄存器,判断是否处于忙碌状态
static void SPI_Read_Flash_Status(void)
{
uint8_t Chip_Status;
// 片选拉低使能
Myspi_Flash_CS_Enable;
// 发送读取状态寄存器地址0x05
SPI_Write_One_Byte(W25_Read_Status_Register);
Timer6.uRun_Timer = 0;
do
{
// 读取芯片状态值
Chip_Status = SPI_Read_One_Byte();
// 2s监测时间
if ( (Timer6.uRun_Timer++) >= 2000 ) break;
}
while ( (Chip_Status & BIT0) == BIT0); // 判断芯片是否处于忙碌状态
// 片选拉高释放
Myspi_Flash_CS_Disable;
}
4、对Flash控制具体函数实现,这部分时序可参考闪存 W25Q64JVSS 手册 解析
(1)读取设备制造商ID、内存类型、容量信息函数:SPI_Read_Device_ID
static void SPI_Read_Device_ID(void)
{
uint8_t buf[3];
uint32_t Device_Type;
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送命令:读取JEDEC ID(设备标识符 -> 制造商+内存类型+容量)
SPI_Write_One_Byte(W25_Read_Device_ID);
buf[0] = SPI_Read_One_Byte();
buf[1] = SPI_Read_One_Byte();
buf[2] = SPI_Read_One_Byte();
//片选拉高释放
Myspi_Flash_CS_Disable;
Device_Type = (buf[0] << 16) + (buf[1] << 8) + buf[2];
printf(" ID of SPI flash is 0x%x\r\n",Device_Type);
switch(Device_Type)
{
case 0xEF4015: printf("Flash芯片型号为W25Q16JV-IQ/JQ, 16M-bit /2M-byte\r\n"); break;
case 0xEF4017: printf("Flash芯片型号为W25Q64JV-IQ/JQ, 64M-bit /8M-byte\r\n"); break;
case 0xEF4018: printf("Flash芯片型号为W25Q128JV-IQ/JQ,128M-bit/16M-byte\r\n"); break;
default: printf("Flash芯片型号未知\r\n");
}
}
(2)擦除扇区空间数据(4K)函数:SPI_EraseSector
static void SPI_EraseSector(uint32_t SectorAddr)
{
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
// 写使能,允许芯片擦除
SPI_Flash_WriteEnable();
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送命令擦除指令
SPI_Write_One_Byte(W25_Sector_Erase);
//发送要擦除的地址
//地址高8位
SPI_Write_One_Byte((SectorAddr & 0xFF0000) >> 16);
//地址中8位
SPI_Write_One_Byte((SectorAddr & 0xFF00) >> 8);
//地址低8位
SPI_Write_One_Byte(SectorAddr & 0xFF);
//片选拉高释放
Myspi_Flash_CS_Disable;
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
}
(3)擦除整个芯片函数:SPI_ChipSector
static void SPI_ChipSector(void)
{
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
// 写使能,允许芯片擦除
SPI_Flash_WriteEnable();
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送命令擦除指令
SPI_Write_One_Byte(W25_Chip_Erase);
//片选拉高释放
Myspi_Flash_CS_Disable;
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
}
(4)写入一页数据(256Byte)函数:SPI_Page_Program
static void SPI_Page_Program(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len)
{
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
// 写使能,允许芯片擦除
SPI_Flash_WriteEnable();
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送页编程命令
SPI_Write_One_Byte(W25_Page_Program);
//发送要读取的数据起始地址
//地址高8位
SPI_Write_One_Byte((WriteAddr & 0xFF0000) >> 16);
//地址中8位
SPI_Write_One_Byte((WriteAddr & 0xFF00) >> 8);
//地址低8位
SPI_Write_One_Byte(WriteAddr & 0xFF);
if(len > SPI_FLASH_PageSize)
{
len = SPI_FLASH_PageSize;
printf("Error: Flash每次写入数据不能超过256字节! \n");
}
// 写入数据
while(len--)
{
SPI_Write_One_Byte(*pWriteBuffer);
pWriteBuffer++;
}
//片选拉高释放
Myspi_Flash_CS_Disable;
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
}
(5)读出不固定长度数据函数:SPI_ReadDataUnfixed
static void SPI_ReadDataUnfixed(uint32_t ReadAddr, uint8_t* pReadBuffer, uint16_t len)
{
//检测flash是否处于忙碌状态
SPI_Read_Flash_Status();
//片选拉低使能
Myspi_Flash_CS_Enable;
//发送命令读取数据
SPI_Write_One_Byte(W25_Read_Data);
//发送要读取的数据起始地址
//地址高8位
SPI_Write_One_Byte((ReadAddr & 0xFF0000) >> 16);
//地址中8位
SPI_Write_One_Byte((ReadAddr & 0xFF00) >> 8);
//地址低8位
SPI_Write_One_Byte(ReadAddr & 0xFF);
// 读数据
while(len--)
{
*pReadBuffer = SPI_Read_One_Byte();
pReadBuffer++;
}
//片选拉高释放
Myspi_Flash_CS_Disable;
}
(6)写不固定长度数据函数:SPI_WriteDataUnfixed
注:根据手册可知,写flash一次性只能写256字节,对于不固定长度的数据,想要写入flash就要注意数据大小,否则会造成flash页覆盖的现象。
实现思路
flash写不固定长度数据函数思路流程图
具体实现函数
static void SPI_WriteDataUnfixed(uint32_t WriteAddr, uint8_t* pWriteBuffer, uint16_t len)
{
uint32_t PageNum = len / SPI_FLASH_PageSize; //写入页数
uint8_t NotEnoughNum = len % SPI_FLASH_PageSize; //不足一页的数量
uint8_t WriteAddrPageAlignment = WriteAddr % SPI_FLASH_PageSize; //如果取余为0,则地址页对齐,可以写连续写入256字节
uint8_t NotAlignmentNumofPage = SPI_FLASH_PageSize - WriteAddrPageAlignment; //地址不对齐部分,最多可以写入的字节数
// 判断是否地址对齐
if( WriteAddrPageAlignment == 0)
{
// 判断写入数据是否超出256Byte
if( PageNum == 0 )
{
// 没超出256Byte,直接页写入
SPI_Page_Program(WriteAddr, pWriteBuffer, len);
}
else
{
// 超出256Byte,先写入一页
while(PageNum--)
{
SPI_Page_Program(WriteAddr, pWriteBuffer, SPI_FLASH_PageSize);
// 准备下一页要写入的地址和数据
WriteAddr = WriteAddr + SPI_FLASH_PageSize;
pWriteBuffer = pWriteBuffer + SPI_FLASH_PageSize;
}
// 判断是否存在不足一页的数据
if(NotEnoughNum > 0)
{
SPI_Page_Program(WriteAddr, pWriteBuffer, NotEnoughNum);
}
}
}
else
{
// 判断写入数据是否超出256Byte
if( PageNum == 0 )
{
// 判断不足256字节的数据是否超出地址不对齐部分
if( NotEnoughNum <= NotAlignmentNumofPage )
{
SPI_Page_Program(WriteAddr, pWriteBuffer, len);
}
else
{
SPI_Page_Program(WriteAddr, pWriteBuffer, NotAlignmentNumofPage);
pWriteBuffer = pWriteBuffer + NotAlignmentNumofPage;
WriteAddr = WriteAddr + NotAlignmentNumofPage;
//写入超出部分数据
SPI_Page_Program(WriteAddr, pWriteBuffer, NotEnoughNum-NotAlignmentNumofPage);
}
}
else
{
//先写地址不对齐部分允许写入的最大长度,地址此时对齐了
SPI_Page_Program(WriteAddr,pWriteBuffer,NotAlignmentNumofPage);
pWriteBuffer += NotAlignmentNumofPage;
WriteAddr += NotAlignmentNumofPage;
//地址对其后,重新计算写入页数与不足一页的数量
len -= NotAlignmentNumofPage;
PageNum = len / SPI_FLASH_PageSize; //待写入页数
NotEnoughNum = len % SPI_FLASH_PageSize;
//先写入整页
while(PageNum--)
{
SPI_Page_Program(WriteAddr,pWriteBuffer,SPI_FLASH_PageSize);
pWriteBuffer += SPI_FLASH_PageSize;
WriteAddr += SPI_FLASH_PageSize;
}
//再写入不足一页的数据
if(NotEnoughNum > 0)
{
SPI_Page_Program(WriteAddr,pWriteBuffer,NotEnoughNum);
}
}
}
}
5、主函数中循环写入flash字符串,再读出来。测试时候能否正常实现flash的读写。
uint8_t i;
uint8_t Flash_Flag = TRUE;
uint8_t Tx_Buffer[] = "BWD laboratory - SPI Flash Test";
const uint8_t BufferSize = sizeof(Tx_Buffer)/sizeof(Tx_Buffer[0]);
uint8_t Rx_Buffer[BufferSize];
//扇区擦除
Myspi.SPI_EraseSector(0x00001000);
//写入不定长度数据
Myspi.SPI_WriteDataUnfixed(0x00001024, Tx_Buffer, BufferSize);
printf("写入的数据为:%s\r\n", Tx_Buffer);
//读出不定长数据
Myspi.SPI_ReadDataUnfixed(0x0001024, Rx_Buffer, BufferSize);
printf("读出的数据为:%s\r\n", Rx_Buffer);
//比较缓存数据
for(i=0;i<BufferSize;i++)
{
if(Tx_Buffer[i] != Rx_Buffer[i])
{
Flash_Flag = FALSE;
break;
}
}
//打印比较结果
if(Flash_Flag == TRUE)
printf("BWD ---- Falsh芯片读写测试成功! \r\n");
else
printf("BWD ---- Falsh芯片读写测试失败! \r\n");
//延时1s
HAL_Delay(1000);