一、 SPI通信驱动W25Qxx
1、使用驱动文件快速配置工程代码驱动W25Qxx
(此驱动文件只适合W25Qxx 16M及以下型号,因为访问地址位数不同)
注:本次使用SPI的方式进行访问W25Qxx Flash进行数据读写,关于W25Qxx芯片不会做介绍,只在于如何配置代码使其能使用该芯片
关于SPI想使用CubeMx的方式配置代码解以参考我的文章:–>> 4、HAL库SPI数据收发
编写好的驱动文件下载:
链接:https://pan.baidu.com/s/1r0JCrUNAHt6sGJ6D_tT0lg
提取码:fxzn
W25Qxx驱动文件(SPI通信 SCK MOSI MISO CS)
使用方法:
1、添加文件到工程:Common_Driver.c、spi_Driver.c、W25Qxx_Driver.c
2、spi_Driver.h文件
修改外设SPI1相应挂载的外设片选引脚
3、spi_Driver.c文件
修改SPI1相应通信线(CLK、MOSI、MISO)的引脚宏
4、W25Qxx_Driver.h文件
修改W25Qxx通信时使用的SPI句柄和外设宏:SPIx、SPIxHandle
修改W25Qxx型号对应的宏:sFLASH_ID
5、W25Qxx_Driver.h文件
开启调试宏(芯片是否正常,会进行擦除数据写入读出对比数据),调试完毕后屏蔽:_W25Qxx_Debug
6、调用SPI初始化函数MX_SPI1_Init()(此时SPI进入空闲,CS引脚空闲)
7、调用W25Qxx_InitCheck()函数检查芯片(执行完后芯片进入省电模式)
8、完毕
时序说明:全双工SPI 线束:SCK|MOSI|MISO CS
SPI模式 CPOL CPHA 空闲时 SCK 时钟 采样时刻
0 0 0 低电平 奇数边沿(W25Qxx支持)
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿(W25Qxx支持)当前使用
数据长度8
高位在前
速度配置为PCLK2/2分频 = 42MHz
注意:W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1);
以此使能外设SPI,使用HAL库自带的收发函数不需要此使能
此驱动文件只适合W25Qxx 16M及以下型号,因为访问地址位数不同
W25Qxx_Driver.c文件中的所有函数需要先初始化SPI后才可以调用
本代码W25Qxx驱动时序指令代码参考野火教程
驱动源码:
Common_Driver.c
/**********************************************************************
*file:常用通用函数
*author:残梦
*date:2023.2.13
*note:
**********************************************************************/
#include "Common_Driver.h"
#include "usart_Driver.h"
/****************************************************
@function:串口重定义
@param:void
@return:void
@date:2023.2.14
@note:使用printf时需要此函数,并在Keil魔术棒中勾选User MicroLIB库
****************************************************/
int fputc(int ch,FILE *f)
{
uint8_t data = ch;
HAL_UART_Transmit(&huart1,&data,1,1);
return(ch);
}
/****************************************************
@function:计算数据的拟合系数
@param:*pA,*pB--系数
x[],y[]--数据源
dataSize--数据个数
@return:void
@date:2021.11.6
@note:y=Ax+B
****************************************************/
void LinearFitCoefficient(double *pA,double *pB,double x[],double y[],unsigned short int dataSize)
{
unsigned short int i= 0;
double AverX = 0.0f,AverY = 0.0f,a1 = 0.0f,a2 = 0.0f;
if(dataSize == 0){*pA = *pB = 0.0;return;}
else if(dataSize == 1){*pA = 0.0;*pB = y[0];return;}
while(i < dataSize) {AverX += x[i];AverY += y[i];i++;}
AverX /= (double)(dataSize);AverY /= (double)(dataSize);
a1 = a2 = 0.0f;
for(i=0;i<dataSize;i++)
{
a1 += (x[i] - AverX)*(y[i] - AverY);
a2 += (x[i] - AverX)*(x[i] - AverX);
}
*pA = a1/a2;
*pB = AverY - (*pA)*AverX;
}
/****************************************************
@function:系统错误死循环显示信息
@param:void
@return:void
@date:2023.2.14
@note:使用printf时需要此函数,并在Keil魔术棒中勾选User MicroLIB库
****************************************************/
void FunctionError(char *str)
{
while(1)
{
if(str != NULL){printf("%s",str);}
HAL_Delay(500);
}
}
/****************************************************
@function:比较两个缓冲区中的数据是否相等
@param:stringA、stringB--待比较的字符串指针
Length--字符串待比较的长度,不得大于stringA或stringB的长度
@return:-1--不相等,0--相等
@date:2023.2.17
@note:
****************************************************/
int StringCompare(uint8_t* stringA, uint8_t* stringB, uint16_t Length)
{
while(Length--)
{
if(*stringA != *stringB){return -1;}
stringA++;stringB++;
}
return 0;
}
/****************************************************
@function:计数延时
@param:nCount--待延时的大小
@return:void
@date:2023.2.17
@note:
****************************************************/
void EmptyDelay(uint32_t nCount)
{
while(nCount--);
}
Common_Driver.h
#ifndef _Common_Driver_H_
#define _Common_Driver_H_
#include "main.h"
#include "stdio.h"
#include "string.h"
typedef int32_t s32;
typedef int16_t s16;
typedef int8_t s8;
typedef const int32_t sc32;
typedef const int16_t sc16;
typedef const int8_t sc8;
typedef __IO int32_t vs32;
typedef __IO int16_t vs16;
typedef __IO int8_t vs8;
typedef __I int32_t vsc32;
typedef __I int16_t vsc16;
typedef __I int8_t vsc8;
typedef uint32_t u32;
typedef uint16_t u16;
typedef uint8_t u8;
typedef const uint32_t uc32;
typedef const uint16_t uc16;
typedef const uint8_t uc8;
typedef __IO uint32_t vu32;
typedef __IO uint16_t vu16;
typedef __IO uint8_t vu8;
typedef __I uint32_t vuc32;
typedef __I uint16_t vuc16;
typedef __I uint8_t vuc8;
#define INVALID 0
#define VALID 1
#define RETURN_NORMAL INVALID
#define RETURN_ABNORMAL -1
#define PI 3.14159265358979323846
#define _BOOL(x) (x?VALID:INVALID)
#define _SET_PIN(GPIOx,Pin) GPIOx->BSRR = Pin //pin set 1
#define _RESET_PIN(GPIOx,Pin) GPIOx->BSRR = ((uint32_t)Pin << 16u) //pin set 0
typedef struct
{
unsigned char byte1;
unsigned char byte2;
unsigned char byte3;
unsigned char byte4;
}Byte4_MemoryParameterStructDef;
typedef struct
{
unsigned char byte1;
unsigned char byte2;
}Byte2_MemoryParameterStructDef;
typedef union
{
short int Value;
Byte2_MemoryParameterStructDef Memory;
}Convert_ShortIntParameter_UnionDef;
typedef union
{
unsigned short int Value;
Byte2_MemoryParameterStructDef Memory;
}Convert_UnsignedShortIntParameter_UnionDef;
typedef union
{
unsigned long int Value;
Byte4_MemoryParameterStructDef Memory;
}Convert_UnsignedLongIntParameter_UnionDef;
typedef union
{
float Value;
Byte4_MemoryParameterStructDef Memory;
}Convert_FloatParameter_UnionDef;
extern void LinearFitCoefficient(double *pA,double *pB,double x[],double y[],unsigned short int dataSize);
extern void FunctionError(char *str);
extern int StringCompare(uint8_t* stringA, uint8_t* stringB, uint16_t Length);
extern void EmptyDelay(uint32_t nCount);
#endif
spi_Driver.c
/**********************************************************************
*file:spi驱动文件
*author:残梦
*date:2023.2.16
*note:
**********************************************************************/
#include "spi_Driver.h"
#define SPI1_CLK_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_CLK_GPIO_PORT GPIOB
#define SPI1_CLK_GPIO_PIN GPIO_PIN_3
#define SPI1_CLK_GPIO_AF GPIO_AF5_SPI1
#define SPI1_MOSI_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_MOSI_GPIO_PORT GPIOB
#define SPI1_MOSI_GPIO_PIN GPIO_PIN_5
#define SPI1_MOSI_GPIO_AF GPIO_AF5_SPI1
#define SPI1_MISO_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_MISO_GPIO_PORT GPIOB
#define SPI1_MISO_GPIO_PIN GPIO_PIN_4
#define SPI1_MISO_GPIO_AF GPIO_AF5_SPI1
SPI_HandleTypeDef hspi1;
/****************************************************
@function:SPI1初始化
@param:void
@return:void
@date:2023.2.16
@note:
时序说明:全双工SPI 线束:SCK|MOSI|MISO CS
SPI模式 CPOL CPHA 空闲时 SCK 时钟 采样时刻
0 0 0 低电平 奇数边沿(W25Qxx支持)
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿(W25Qxx支持)当前使用
数据长度8
高位在前
速度配置为PCLK2/2分频 = 42MHz
注意:W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1);
以此使能外设SPI,使用HAL库自带的收发函数不需要此使能
****************************************************/
void MX_SPI1_Init(void)
{
hspi1.Instance = SPI1;
hspi1.Init.Mode = SPI_MODE_MASTER;
hspi1.Init.Direction = SPI_DIRECTION_2LINES;
hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
hspi1.Init.CLKPolarity = SPI_POLARITY_HIGH;
hspi1.Init.CLKPhase = SPI_PHASE_2EDGE;
hspi1.Init.NSS = SPI_NSS_SOFT;
hspi1.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;
hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
hspi1.Init.TIMode = SPI_TIMODE_DISABLE;
hspi1.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
hspi1.Init.CRCPolynomial = 7;
if(HAL_SPI_Init(&hspi1) != HAL_OK){Error_Handler();}
__HAL_SPI_ENABLE(&hspi1);//W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1),以此使能外设SPI,使用HAL库自带的收发函数不需要此使能
}
/****************************************************
@function:SPI1 GPIO、NVIC、CLOCK初始化
@param:void
@return:void
@date:2023.2.16
@note:
****************************************************/
void HAL_SPI_MspInit(SPI_HandleTypeDef* spiHandle)
{
GPIO_InitTypeDef GPIO_InitStruct = {0};
if(spiHandle->Instance == SPI1)
{
__HAL_RCC_SPI1_CLK_ENABLE();
SPI1_CLK_GPIO_CLK_ENABLE();
SPI1_MOSI_GPIO_CLK_ENABLE();
SPI1_MISO_GPIO_CLK_ENABLE();
SPI1_W25Qxx_CS_GPIO_CLK_ENABLE();
GPIO_InitStruct.Pin = SPI1_CLK_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = SPI1_CLK_GPIO_AF;
HAL_GPIO_Init(SPI1_CLK_GPIO_PORT,&GPIO_InitStruct);
GPIO_InitStruct.Pin = SPI1_MOSI_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = SPI1_MOSI_GPIO_AF;
HAL_GPIO_Init(SPI1_MOSI_GPIO_PORT,&GPIO_InitStruct);
GPIO_InitStruct.Pin = SPI1_MISO_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = SPI1_MISO_GPIO_AF;
HAL_GPIO_Init(SPI1_MISO_GPIO_PORT,&GPIO_InitStruct);
GPIO_InitStruct.Pin = SPI1_W25Qxx_CS_GPIO_PIN;
GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;
GPIO_InitStruct.Pull = GPIO_PULLUP;
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_VERY_HIGH;
GPIO_InitStruct.Alternate = 0;
HAL_GPIO_Init(SPI1_W25Qxx_CS_GPIO_PORT,&GPIO_InitStruct);
}
}
spi_Driver.h
#ifndef _spi_Driver_H_
#define _spi_Driver_H_
#include "main.h"
//外设SPI1相应挂载的外设片选引脚
#define SPI1_W25Qxx_CS_GPIO_CLK_ENABLE() __HAL_RCC_GPIOB_CLK_ENABLE()
#define SPI1_W25Qxx_CS_GPIO_PORT GPIOB
#define SPI1_W25Qxx_CS_GPIO_PIN GPIO_PIN_14
extern SPI_HandleTypeDef hspi1;
extern void MX_SPI1_Init(void);
#endif
W25Qxx_Driver.c
/**********************************************************************
*file:W25Qxx驱动文件(SPI通信 SCK MOSI MISO CS)
*author:残梦
*date:2023.2.17
*note:
使用方法:
1、添加文件到工程:Common_Driver.c、spi_Driver.c、W25Qxx_Driver.c
2、spi_Driver.h文件
修改外设SPI1相应挂载的外设片选引脚
3、spi_Driver.c文件
修改SPI1相应通信线(CLK、MOSI、MISO)的引脚宏
4、W25Qxx_Driver.h文件
修改W25Qxx通信时使用的SPI句柄和外设宏:SPIx、SPIxHandle
修改W25Qxx型号对应的宏:sFLASH_ID
5、W25Qxx_Driver.h文件
开启调试宏(芯片是否正常,会进行擦除数据写入读出对比数据),调试完毕后屏蔽:_W25Qxx_Debug
6、调用SPI初始化函数MX_SPI1_Init()(此时SPI进入空闲,CS引脚空闲)
7、调用W25Qxx_InitCheck()函数检查芯片(执行完后芯片进入省电模式)
8、完毕
时序说明:全双工SPI 线束:SCK|MOSI|MISO CS
SPI模式 CPOL CPHA 空闲时 SCK 时钟 采样时刻
0 0 0 低电平 奇数边沿(W25Qxx支持)
1 0 1 低电平 偶数边沿
2 1 0 高电平 奇数边沿
3 1 1 高电平 偶数边沿(W25Qxx支持)当前使用
数据长度8
高位在前
速度配置为PCLK2/2分频 = 42MHz
注意:W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1);
以此使能外设SPI,使用HAL库自带的收发函数不需要此使能
此驱动文件只适合W25Qxx 16M及以下型号,因为访问地址位数不同
W25Qxx_Driver.c文件中的所有函数需要先初始化SPI后才可以调用
**********************************************************************/
#include "W25Qxx_Driver.h"
#include "spi_Driver.h"
#define W25Qxx_CS_LOW() _RESET_PIN(SPI1_W25Qxx_CS_GPIO_PORT,SPI1_W25Qxx_CS_GPIO_PIN)
#define W25Qxx_CS_HIGH() _SET_PIN(SPI1_W25Qxx_CS_GPIO_PORT,SPI1_W25Qxx_CS_GPIO_PIN)
static __IO uint32_t SPITimeout = SPIT_LONG_TIMEOUT;
static uint8_t W25Qxx_ReadByte(void);
static uint8_t W25Qxx_SendByte(uint8_t byte);
static uint16_t W25Qxx_SendHalfWord(uint16_t HalfWord);
static void W25Qxx_WriteEnable(void);
static void W25Qxx_WaitForWriteEnd(void);
static uint16_t W25Qxx_SPI_TIMEOUT_UserCallback(uint8_t errorCode);
/****************************************************
@function:W25Q擦除FLASH扇区
@param:SectorAddr:要擦除的扇区地址
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_SectorErase(uint32_t SectorAddr)
{
/* 发送FLASH写使能命令 */
W25Qxx_WriteEnable();
W25Qxx_WaitForWriteEnd();
/* 擦除扇区 */
/* 选择FLASH: CS低电平 */
W25Qxx_CS_LOW();
/* 发送扇区擦除指令*/
W25Qxx_SendByte(W25X_SectorErase);
/*发送擦除扇区地址的高位*/
W25Qxx_SendByte((SectorAddr & 0xFF0000) >> 16);
/* 发送擦除扇区地址的中位 */
W25Qxx_SendByte((SectorAddr & 0xFF00) >> 8);
/* 发送擦除扇区地址的低位 */
W25Qxx_SendByte(SectorAddr & 0xFF);
/* 停止信号 FLASH: CS 高电平 */
W25Qxx_CS_HIGH();
/* 等待擦除完毕*/
W25Qxx_WaitForWriteEnd();
}
/****************************************************
@function:W25Q擦除FLASH扇区,整片擦除
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_BulkErase(void)
{
/* 发送FLASH写使能命令 */
W25Qxx_WriteEnable();
/* 整块 Erase */
/* 选择FLASH: CS低电平 */
W25Qxx_CS_LOW();
/* 发送整块擦除指令*/
W25Qxx_SendByte(W25X_ChipErase);
/* 停止信号 FLASH: CS 高电平 */
W25Qxx_CS_HIGH();
/* 等待擦除完毕*/
W25Qxx_WaitForWriteEnd();
}
/****************************************************
@function:对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区
@param: pBuffer,要写入数据的指针
WriteAddr,写入地址
NumByteToWrite,写入数据长度,必须小于等于W25Qxx_PerWritePageSize
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
/* 发送FLASH写使能命令 */
W25Qxx_WriteEnable();
/* 选择FLASH: CS低电平 */
W25Qxx_CS_LOW();
/* 写页写指令*/
W25Qxx_SendByte(W25X_PageProgram);
/*发送写地址的高位*/
W25Qxx_SendByte((WriteAddr & 0xFF0000) >> 16);
/*发送写地址的中位*/
W25Qxx_SendByte((WriteAddr & 0xFF00) >> 8);
/*发送写地址的低位*/
W25Qxx_SendByte(WriteAddr & 0xFF);
if(NumByteToWrite > W25Qxx_PerWritePageSize)
{
NumByteToWrite = W25Qxx_PerWritePageSize;
FLASH_ERROR("W25Qxx_PageWrite too large!");
}
/* 写入数据*/
while (NumByteToWrite--)
{
/* 发送当前要写入的字节数据 */
W25Qxx_SendByte(*pBuffer);
/* 指向下一字节数据 */
pBuffer++;
}
/* 停止信号 FLASH: CS 高电平 */
W25Qxx_CS_HIGH();
/* 等待写入完毕*/
W25Qxx_WaitForWriteEnd();
}
/****************************************************
@function:对FLASH写入数据,调用本函数写入数据前需要先擦除扇区
@param: pBuffer,要写入数据的指针
WriteAddr,写入地址
NumByteToWrite,写入数据长度
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite)
{
uint8_t NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;
/*mod运算求余,若writeAddr是W25Qxx_PageSize整数倍,运算结果Addr值为0*/
Addr = WriteAddr % W25Qxx_PageSize;
/*差count个数据值,刚好可以对齐到页地址*/
count = W25Qxx_PageSize - Addr;
/*计算出要写多少整数页*/
NumOfPage = NumByteToWrite / W25Qxx_PageSize;
/*mod运算求余,计算出剩余不满一页的字节数*/
NumOfSingle = NumByteToWrite % W25Qxx_PageSize;
/* Addr=0,则WriteAddr 刚好按页对齐 aligned */
if (Addr == 0)
{
/* NumByteToWrite < W25Qxx_PageSize */
if (NumOfPage == 0)
{
W25Qxx_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
else /* NumByteToWrite > W25Qxx_PageSize */
{
/*先把整数页都写了*/
while (NumOfPage--)
{
W25Qxx_PageWrite(pBuffer, WriteAddr, W25Qxx_PageSize);
WriteAddr += W25Qxx_PageSize;
pBuffer += W25Qxx_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
W25Qxx_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
/* 若地址与 W25Qxx_PageSize 不对齐 */
else
{
/* NumByteToWrite < W25Qxx_PageSize */
if (NumOfPage == 0)
{
/*当前页剩余的count个位置比NumOfSingle小,写不完*/
if (NumOfSingle > count)
{
temp = NumOfSingle - count;
/*先写满当前页*/
W25Qxx_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*再写剩余的数据*/
W25Qxx_PageWrite(pBuffer, WriteAddr, temp);
}
else /*当前页剩余的count个位置能写完NumOfSingle个数据*/
{
W25Qxx_PageWrite(pBuffer, WriteAddr, NumByteToWrite);
}
}
else /* NumByteToWrite > W25Qxx_PageSize */
{
/*地址不对齐多出的count分开处理,不加入这个运算*/
NumByteToWrite -= count;
NumOfPage = NumByteToWrite / W25Qxx_PageSize;
NumOfSingle = NumByteToWrite % W25Qxx_PageSize;
W25Qxx_PageWrite(pBuffer, WriteAddr, count);
WriteAddr += count;
pBuffer += count;
/*把整数页都写了*/
while (NumOfPage--)
{
W25Qxx_PageWrite(pBuffer, WriteAddr, W25Qxx_PageSize);
WriteAddr += W25Qxx_PageSize;
pBuffer += W25Qxx_PageSize;
}
/*若有多余的不满一页的数据,把它写完*/
if (NumOfSingle != 0)
{
W25Qxx_PageWrite(pBuffer, WriteAddr, NumOfSingle);
}
}
}
}
/****************************************************
@function:读取FLASH数据
@param: pBuffer,存储读出数据的指针
ReadAddr,读取地址
NumByteToRead,读取数据长度
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead)
{
/* 选择FLASH: CS低电平 */
W25Qxx_CS_LOW();
/* 发送 读 指令 */
W25Qxx_SendByte(W25X_ReadData);
/* 发送 读 地址高位 */
W25Qxx_SendByte((ReadAddr & 0xFF0000) >> 16);
/* 发送 读 地址中位 */
W25Qxx_SendByte((ReadAddr& 0xFF00) >> 8);
/* 发送 读 地址低位 */
W25Qxx_SendByte(ReadAddr & 0xFF);
/* 读取数据 */
while (NumByteToRead--)
{
/* 读取一个字节*/
*pBuffer = W25Qxx_SendByte(Dummy_Byte);
/* 指向下一个字节缓冲区 */
pBuffer++;
}
/* 停止信号 FLASH: CS 高电平 */
W25Qxx_CS_HIGH();
}
/****************************************************
@function:读取FLASH ID
@param:void
@return:FLASH ID
@date:2023.2.17
@note:
****************************************************/
uint32_t W25Qxx_ReadID(void)
{
uint32_t Temp = 0, Temp0 = 0, Temp1 = 0, Temp2 = 0;
/* 开始通讯:CS低电平 */
W25Qxx_CS_LOW();
/* 发送JEDEC指令,读取ID */
W25Qxx_SendByte(W25X_JedecDeviceID);
/* 读取一个字节数据 */
Temp0 = W25Qxx_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp1 = W25Qxx_SendByte(Dummy_Byte);
/* 读取一个字节数据 */
Temp2 = W25Qxx_SendByte(Dummy_Byte);
/* 停止通讯:CS高电平 */
W25Qxx_CS_HIGH();
/*把数据组合起来,作为函数的返回值*/
Temp = (Temp0 << 16) | (Temp1 << 8) | Temp2;
return Temp;
}
/****************************************************
@function:读取FLASH Device ID
@param:void
@return:FLASH Device ID
@date:2023.2.17
@note:
****************************************************/
uint32_t W25Qxx_ReadDeviceID(void)
{
uint32_t Temp = 0;
/* Select the FLASH: Chip Select low */
W25Qxx_CS_LOW();
/* Send "RDID " instruction */
W25Qxx_SendByte(W25X_DeviceID);
W25Qxx_SendByte(Dummy_Byte);
W25Qxx_SendByte(Dummy_Byte);
W25Qxx_SendByte(Dummy_Byte);
/* Read a byte from the FLASH */
Temp = W25Qxx_SendByte(Dummy_Byte);
/* Deselect the FLASH: Chip Select high */
W25Qxx_CS_HIGH();
return Temp;
}
/****************************************************
@function:W25Qxx_StartReadSequence
@param:ReadAddr : FLASH's internal address to read from.
@return:void
@date:2023.2.17
@note:Initiates a read data byte (READ) sequence from the Flash.
* This is done by driving the /CS line low to select the device,
* then the READ instruction is transmitted followed by 3 bytes
* address. This function exit and keep the /CS line low, so the
* Flash still being selected. With this technique the whole
* content of the Flash is read with a single READ instruction.
****************************************************/
void W25Qxx_StartReadSequence(uint32_t ReadAddr)
{
/* Select the FLASH: Chip Select low */
W25Qxx_CS_LOW();
/* Send "Read from Memory " instruction */
W25Qxx_SendByte(W25X_ReadData);
/* Send the 24-bit address of the address to read from -----------------------*/
/* Send ReadAddr high nibble address byte */
W25Qxx_SendByte((ReadAddr & 0xFF0000) >> 16);
/* Send ReadAddr medium nibble address byte */
W25Qxx_SendByte((ReadAddr& 0xFF00) >> 8);
/* Send ReadAddr low nibble address byte */
W25Qxx_SendByte(ReadAddr & 0xFF);
}
/****************************************************
@function:使用SPI读取一个字节的数据
@param:void
@return:返回接收到的数据
@date:2023.2.17
@note:
****************************************************/
static uint8_t W25Qxx_ReadByte(void)
{
return (W25Qxx_SendByte(Dummy_Byte));
}
/****************************************************
@function:使用SPI发送一个字节的数据
@param:byte:要发送的数据
@return:返回接收到的数据
@date:2023.2.17
@note:
****************************************************/
static uint8_t W25Qxx_SendByte(uint8_t byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待发送缓冲区为空,TXE事件 */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(0);
}
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
WRITE_REG(SPIxHandle.Instance->DR, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收缓冲区非空,RXNE事件 */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(1);
}
/* 读取数据寄存器,获取接收缓冲区数据 */
return READ_REG(SPIxHandle.Instance->DR);
}
/****************************************************
@function:W25Qxx_SendHalfWord
@param:Half Word : Half Word to send.
@return:The value of the received Half Word.
@date:2023.2.17
@note:Sends a Half Word through the SPI interface and return the
Half Word received from the SPI bus.
****************************************************/
static uint16_t W25Qxx_SendHalfWord(uint16_t HalfWord)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* Loop while DR register in not emplty */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(2);
}
/* Send Half Word through the SPIx peripheral */
WRITE_REG(SPIxHandle.Instance->DR, HalfWord);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* Wait to receive a Half Word */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(3);
}
/* Return the Half Word read from the SPI bus */
return READ_REG(SPIxHandle.Instance->DR);
}
/****************************************************
@function:向FLASH发送 写使能 命令
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
static void W25Qxx_WriteEnable(void)
{
/* 通讯开始:CS低 */
W25Qxx_CS_LOW();
/* 发送写使能命令*/
W25Qxx_SendByte(W25X_WriteEnable);
/*通讯结束:CS高 */
W25Qxx_CS_HIGH();
}
/****************************************************
@function:等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
static void W25Qxx_WaitForWriteEnd(void)
{
uint8_t FLASH_Status = 0;
/* 选择 FLASH: CS 低 */
W25Qxx_CS_LOW();
/* 发送 读状态寄存器 命令 */
W25Qxx_SendByte(W25X_ReadStatusReg);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 若FLASH忙碌,则等待 */
do
{
/* 读取FLASH芯片的状态寄存器 */
FLASH_Status = W25Qxx_SendByte(Dummy_Byte);
{
if((SPITimeout--) == 0)
{
W25Qxx_SPI_TIMEOUT_UserCallback(4);
return;
}
}
}
while ((FLASH_Status & WIP_Flag) == SET); /* 正在写入标志 */
/* 停止信号 FLASH: CS 高 */
W25Qxx_CS_HIGH();
}
/****************************************************
@function:进入掉电模式
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_PowerDown(void)
{
/* 选择 FLASH: CS 低 */
W25Qxx_CS_LOW();
/* 发送 掉电 命令 */
W25Qxx_SendByte(W25X_PowerDown);
/* 停止信号 FLASH: CS 高 */
W25Qxx_CS_HIGH();
}
/****************************************************
@function:唤醒
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
void W25Qxx_WAKEUP(void)
{
/*选择 FLASH: CS 低 */
W25Qxx_CS_LOW();
/* 发上 上电 命令 */
W25Qxx_SendByte(W25X_ReleasePowerDown);
/* 停止信号 FLASH: CS 高 */
W25Qxx_CS_HIGH(); //等待TRES1
}
/****************************************************
@function:等待超时回调函数
@param:void
@return:void
@date:2023.2.17
@note:
****************************************************/
static uint16_t W25Qxx_SPI_TIMEOUT_UserCallback(uint8_t errorCode)
{
/* 等待超时后的处理,输出错误信息 */
FLASH_ERROR("SPI 等待超时!errorCode = %d",errorCode);
return 0;
}
/****************************************************
@function:计数延时
@param:nCount--待延时的大小
@return:void
@date:2023.2.17
@note:
****************************************************/
static void W25Qxx_EmptyDelay(uint32_t nCount)
{
while(nCount--);
}
/********************以下为测试代码*********************/
#ifdef _W25Qxx_Debug
#define FLASH_WriteAddress 0x00000
#define FLASH_ReadAddress FLASH_WriteAddress
#define FLASH_SectorToErase FLASH_WriteAddress
/* 获取缓冲区的长度 */
#define TxBufferSize1 (countof(TxBuffer1) - 1)
#define RxBufferSize1 (countof(TxBuffer1) - 1)
#define countof(a) (sizeof(a) / sizeof(*(a)))
#define BufferSize (countof(Tx_Buffer)-1)
/****************************************************
@function:比较两个缓冲区中的数据是否相等
@param:stringA、stringB--待比较的字符串指针
Length--字符串待比较的长度,不得大于stringA或stringB的长度
@return:-1--不相等,0--相等
@date:2023.2.17
@note:
****************************************************/
static int W25Qxx_StringCompare(uint8_t* stringA, uint8_t* stringB, uint16_t Length)
{
while(Length--)
{
if(*stringA != *stringB){return -1;}
stringA++;stringB++;
}
return 0;
}
#endif
/****************************************************
@function:W25Q初始化
@param:void
@return:-1--异常,0--正常
@date:2023.2.17
@note:
****************************************************/
int W25Qxx_InitCheck(void)
{
//读取的ID存储位置
uint32_t DeviceID = 0,FlashID = 0;
DeviceID = W25Qxx_ReadDeviceID();//获取 Flash Device ID
W25Qxx_EmptyDelay(200);
FlashID = W25Qxx_ReadID();//获取 SPI Flash ID
printf("W25Qxx FlashID:0x%0X, Manufacturer Device ID:0x%0X\r\n", FlashID, DeviceID);
if (FlashID == sFLASH_ID)//检验SPI Flash ID
{
printf("检测到SPI FLASH W25Q128 !\r\n");
#ifdef _W25Qxx_Debug
/* 发送缓冲区初始化 */
uint8_t Tx_Buffer[] = "感谢您选用野火stm32开发板\r\nhttp://firestm32.taobao.com";
uint8_t Rx_Buffer[BufferSize];
W25Qxx_SectorErase(FLASH_SectorToErase);//擦除将要写入的 SPI FLASH 扇区,FLASH写入前要先擦除
W25Qxx_BufferWrite(Tx_Buffer, FLASH_WriteAddress, BufferSize);//将发送缓冲区的数据写到flash中
printf("写入的数据为:\r\n%s", Tx_Buffer);
W25Qxx_BufferRead(Rx_Buffer, FLASH_ReadAddress, BufferSize);//将刚刚写入的数据读出来放到接收缓冲区中
printf("读出的数据为:\r\n%s", Rx_Buffer);
if(W25Qxx_StringCompare(Tx_Buffer, Rx_Buffer, BufferSize) < 0)//检查写入的数据与读出的数据是否相等
{printf("16M串行flash(W25Q128)测试失败!\n\r");return -1;}
else{printf("16M串行flash(W25Q128)测试成功!\n\r");}
#endif
}
else{printf("获取不到 W25Q128 ID!\n\r");return -1;}
W25Qxx_PowerDown();//进入省电模式
return 0;
}
W25Qxx_Driver.h
#ifndef _W25Qxx_Driver_H_
#define _W25Qxx_Driver_H_
#include "Common_Driver.h"
#define _W25Qxx_Debug //未定义--平时无需此代码,定义--芯片是否正常,会进行擦除数据写入读出对比数据
#define SPIx SPI1 //使用的SPIx相应外设
#define SPIxHandle hspi1 //使用的spi相应句柄
//#define SPIx_FORCE_RESET() __HAL_RCC_SPI1_FORCE_RESET()
//#define SPIx_RELEASE_RESET() __HAL_RCC_SPI1_RELEASE_RESET()
/* Private typedef ------选择实际使用的型号-------------------------------------*/
//#define sFLASH_ID 0xEF3015 //W25X16
//#define sFLASH_ID 0xEF4015 //W25Q16
//#define sFLASH_ID 0XEF4017 //W25Q64
#define sFLASH_ID 0XEF4018 //W25Q128
//#define W25Qxx_PageSize 4096
#define W25Qxx_PageSize 256
#define W25Qxx_PerWritePageSize 256
/* Private define ------------------------------------------------------------*/
/*命令定义-开头*******************************/
#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
#define WIP_Flag 0x01 /* Write In Progress (WIP) flag */
#define Dummy_Byte 0xFF
/*命令定义-结尾*******************************/
/*等待超时时间*/
#define SPIT_FLAG_TIMEOUT ((uint32_t)0x1000)//使用HAL库SPI收发函数时,超时时间需加大
#define SPIT_LONG_TIMEOUT ((uint32_t)(10 * SPIT_FLAG_TIMEOUT))
/*信息输出*/
#define FLASH_DEBUG_ON 1
#define FLASH_INFO(fmt,arg...) printf("<<-FLASH-INFO->> "fmt"\n",##arg)
#define FLASH_ERROR(fmt,arg...) printf("<<-FLASH-ERROR->> "fmt"\n",##arg)
#define FLASH_DEBUG(fmt,arg...) do{\
if(FLASH_DEBUG_ON)\
printf("<<-FLASH-DEBUG->> [%d]"fmt"\n",__LINE__, ##arg);\
}while(0)
extern void W25Qxx_Init(void);
extern void W25Qxx_SectorErase(uint32_t SectorAddr);
extern void W25Qxx_BulkErase(void);
extern void W25Qxx_PageWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
extern void W25Qxx_BufferWrite(uint8_t* pBuffer, uint32_t WriteAddr, uint16_t NumByteToWrite);
extern void W25Qxx_BufferRead(uint8_t* pBuffer, uint32_t ReadAddr, uint16_t NumByteToRead);
extern uint32_t W25Qxx_ReadID(void);
extern uint32_t W25Qxx_ReadDeviceID(void);
extern void W25Qxx_StartReadSequence(uint32_t ReadAddr);
extern void W25Qxx_PowerDown(void);
extern void W25Qxx_WAKEUP(void);
extern int W25Qxx_InitCheck(void);
#endif
2、驱动代码笔记说明
1)、HAL库和直接使用野火W25Qxx代码时进行SPI读写区别
__HAL_SPI_ENABLE(&hspi1);
这句是什么作用呢?
/** @brief Enable the SPI peripheral.
* @param __HANDLE__ specifies the SPI Handle.
* This parameter can be SPI where x: 1, 2, or 3 to select the SPI peripheral.
* @retval None
*/
#define __HAL_SPI_ENABLE(__HANDLE__) SET_BIT((__HANDLE__)->Instance->CR1, SPI_CR1_SPE)
由此明白了SPE,
注意:CubeMx配置生成的代码MX_SPI1_Init()函数并没有对SPI外设使能,只有在调用读写函数时才进行使能,后面会说到
W25Qxx底层读写采用的直接操作寄存器,所以需要在MX_SPI1_Init()函数中的HAL_SPI_Init()后调用__HAL_SPI_ENABLE(&hspi1),以此使能外设SPI,使用HAL库自带的收发函数不需要此使能语句
比如说这个使用SPI发送一个字节的数据函数
/****************************************************
@function:使用SPI发送一个字节的数据
@param:byte:要发送的数据
@return:返回接收到的数据
@date:2023.2.17
@note:
****************************************************/
static uint8_t W25Qxx_SendByte(uint8_t byte)
{
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待发送缓冲区为空,TXE事件 */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_TXE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(0);
}
/* 写入数据寄存器,把要写入的数据写入发送缓冲区 */
WRITE_REG(SPIxHandle.Instance->DR, byte);
SPITimeout = SPIT_FLAG_TIMEOUT;
/* 等待接收缓冲区非空,RXNE事件 */
while (__HAL_SPI_GET_FLAG( &SPIxHandle, SPI_FLAG_RXNE ) == RESET)
{
if((SPITimeout--) == 0) return W25Qxx_SPI_TIMEOUT_UserCallback(1);
}
/* 读取数据寄存器,获取接收缓冲区数据 */
return READ_REG(SPIxHandle.Instance->DR);
}
由此可以看出里面是寄存器直接读写,但是读写前SPI外设没有进行使能
再来看看HAL自带的读写函数HAL_SPI_TransmitReceive()
HAL_StatusTypeDef HAL_SPI_TransmitReceive(SPI_HandleTypeDef *hspi, uint8_t *pTxData, uint8_t *pRxData, uint16_t Size,
uint32_t Timeout)
{
/* Set the transaction information */
hspi->ErrorCode = HAL_SPI_ERROR_NONE;
hspi->pRxBuffPtr = (uint8_t *)pRxData;
hspi->RxXferCount = Size;
hspi->RxXferSize = Size;
hspi->pTxBuffPtr = (uint8_t *)pTxData;
hspi->TxXferCount = Size;
hspi->TxXferSize = Size;
/*Init field not used in handle to zero */
hspi->RxISR = NULL;
hspi->TxISR = NULL;
/* Check if the SPI is already enabled */
if ((hspi->Instance->CR1 & SPI_CR1_SPE) != SPI_CR1_SPE)
{
/* Enable SPI peripheral */
__HAL_SPI_ENABLE(hspi);
}
}
此处可以可以看出该函数内部会自动使能CR1的SPE位,注意这个函数使能此SPI外设后不会进行关闭,如果不使用__HAL_SPI_ENABLE(&hspi1)也可以直接随意调用HAL库的SPI收发轮询函数进行读写开启外设