5、HAL库驱动W25Qxx
一、 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 0typedef 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_SPI1SPI_HandleTypeDef hspi1;
/****************************************************
@function:SPI1初始化
@param:void
@return:void
@date:2023.2.16
@note:时序说明:全双工SPI 线束:SCK|MOSI|MISO CSSPI模式 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_14extern 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.c2、spi_Driver.h文件修改外设SPI1相应挂载的外设片选引脚3、spi_Driver.c文件修改SPI1相应通信线(CLK、MOSI、MISO)的引脚宏4、W25Qxx_Driver.h文件修改W25Qxx通信时使用的SPI句柄和外设宏:SPIx、SPIxHandle修改W25Qxx型号对应的宏:sFLASH_ID5、W25Qxx_Driver.h文件开启调试宏(芯片是否正常,会进行擦除数据写入读出对比数据),调试完毕后屏蔽:_W25Qxx_Debug6、调用SPI初始化函数MX_SPI1_Init()(此时SPI进入空闲,CS引脚空闲)7、调用W25Qxx_InitCheck()函数检查芯片(执行完后芯片进入省电模式)8、完毕时序说明:全双工SPI 线束:SCK|MOSI|MISO CSSPI模式 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 theHalf 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 IDW25Qxx_EmptyDelay(200);FlashID = W25Qxx_ReadID();//获取 SPI Flash IDprintf("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收发轮询函数进行读写开启外设
相关文章:
5、HAL库驱动W25Qxx
一、 SPI通信驱动W25Qxx 1、使用驱动文件快速配置工程代码驱动W25Qxx (此驱动文件只适合W25Qxx 16M及以下型号,因为访问地址位数不同) 注:本次使用SPI的方式进行访问W25Qxx Flash进行数据读写,关于W25Qxx芯片不会做…...
git rebase 洐合(变基)
洐合 把一个分支整合到另一个分支的办法有两种:merge(合并) 和 rebase(衍合) 为什么使用? 使提交记录更简洁 三种情况 第一种: 合并多条commit记录 git rebase -i HEAD~合并数量 HEAD~3&a…...
Kubernetes 1.18学习笔记
文章目录一、Kubernetes 概述和架构1、kubernetes 基本介绍2、Kubernetes 功能3、Kubernetes 架构组件4、Kubernetes 核心概念5、Kubernetes 工作原理二、Kubernetes 集群搭建1、系统环境准备1.1 安装要求1.2 系统初始化2、客户端工具kubeadm搭建2.1 安装步骤2.2 安装组件2.3 集…...
AJAX技术
AJAX技术 浏览器是多进程的,简单的说就是,浏览器每打开一个标签页,就相当于创建了一个独立的浏览器进程。但是js是基于单线程的,而这个线程就是浏览器的js引擎,浏览器无论在什么时候都只且只有一个线程在运行JavaScri…...
华为OD机试 - 最大排列(JS)
最大排列 题目 给定一组整数,重排序后输出一个最大的整数 输入 数字组合 输出 最大的整数 示例一 输入 10 9输出 910解题思路 我们可以读入一个字符串,将字符串中的单词按照每个单词的字典序长度,字典序从大到小的顺序排序&#x…...
Prometheus Docker安装及监控自身
前提环境: Docker环境 涉及参考文档: 安装Prometheus开始 Prometheusnode_exporter Agent组件 一、部署Prometheus 1、启动容器将文件拷贝出来 docker run -d prom/prometheus2、容器将文件拷贝出来 docker cp 容器ID:/usr/share/prometheus/conso…...
点云处理PCL常用函数与工具
点云处理PCL常用函数与工具 文章目录点云处理PCL常用函数与工具前言一、点云读取与保存数据读取数据保存自定义的点云保存格式二、点云显示点云显示-根据颜色点云显示-根据指定轴数值点云显示-根据指定信息显示多组点云显示三、点云滤波直通滤波统计滤波均匀下采样滤波VoxelGri…...
FyListen 在 MVP 架构中的内存优化表现
FyListen 在 MVP 中的内存优化表现 本文只是分享个人开源框架的内存优化测试,你可以直接跳到最后,参考内存泄漏的分析过程! 项目地址: https://github.com/StudyNoteOfTu/fylisten2-alpha1 由于使用到 AOP,所以直接…...
Qt代码单元测试以及报告生成
简介 单元测试是所有测试中最底层的一类测试,是第一个环节,也是最重要的一个环节,是唯一一次有保证能够代码覆盖率达到100%的测试,是整个软件测试过程的基础和前提,单元测试防止了开发的后期因bug过多而失控࿰…...
vscode构建Vue3.0项目(vite,vue-cli)
构建Vue3.0项目构建Vue3.0项目1.使用Vite构建vue项目的方法以及步骤1. 安装vite2. 运行vite vue 项目3.说明2.使用vue-cli构建vue项目的方法以及步骤1.安装全局vue cli —— 脚手架2、VSCode3.报错4.运行构建Vue3.0项目 1.使用Vite构建vue项目的方法以及步骤 1. 安装vite n…...
【2023】华为OD机试真题Java-题目0215-优雅数组
优雅数组 题目描述 如果一个数组中出现次数最多的元素出现大于等于 k k k 次,被称为k-优雅数组, k k k 也可以被称为优雅阈值。 例如,数组[1, 2, 3, 1, 2, 3, 1],它是一个3-优雅数组,因为元素1出现次数大于等于3次...
通过Prowork每日自动提醒待处理工作任务
对于中小团队来说,由于不需要繁琐的流程和高频的异地沟通,需要一款更适合中小团队的日程和项目管理工具。而Prowork就是这样一款敏捷高效的协同平台。Prowork与以往各种项目管理⼯具最⼤的不同在于,其弱化流程和弱化权限的特性,不…...
Linux自定义系统服务
文章目录一. Linux系统服务二. 自定义系统服务一. Linux系统服务 Linux 系统服务有时也称为守护程序,是在Linux启动时自动加载并在Linux退出时自动停止的系统任务,CentOS 7.x开始,CentOS开始使用 systemd服务来代替 daemon ,原来…...
mongodb lambda 查询插件
需求背景需要一个像mybatis plus 一样的基于lambda, 且面向对象的查询mongo数据的插件。在网上找了很久,没有发现有类似功能的插件。于是自己手写了一个,借助mongoTemplate屏蔽了底层查询语句的实现细节。在此基础上,实现了查询的统一封装。技…...
C++设计模式(16)——责任链模式
亦称: 职责链模式、命令链、CoR、Chain of Command、Chain of Responsibility 意图 责任链模式是一种行为设计模式, 允许你将请求沿着处理者链进行发送。 收到请求后, 每个处理者均可对请求进行处理, 或将其传递给链上的下个处理…...
springmvc+jsp电影院购票售票选座推荐网站java ssm
本电影购票推荐网站以SSM作为框架,B/S模式以及MySql作为后台运行的数据库。本系统主要包括以下功能模块:个人中心、用户管理、电影信息管理、电影类型管理、影院信息管理、系统管理、订单管理等模块,通过这些模块的实现能够基本满足日常电影购…...
ASEMI高压MOS管4N65SE,4N65SE参数,4N65SE特征
编辑-Z ASEMI高压MOS管4N65SE参数: 型号:4N65SE 漏极-源极电压(VDS):650V 栅源电压(VGS):30V 漏极电流(ID):4A 功耗(PD…...
第46章 自定义静态与数据库动态授权依赖注入的定义实现
1 数据库动态授权表授权原理 2 准备工作 2.1 重构Program.cs using Framework.Infrastructure.Extensions; var builder WebApplication.CreateBuilder(args); //如果启动项中不存在“appsettings.json”文件,则通过.Net(Core)的内置方法自动新建“appsettings.…...
Go语言面试题
请解释 Go 语言中的 goroutine 是什么。请解释 Go 语言中的 channel 是什么,并举例说明它的用途。请解释 Go 语言中的 interface 是什么,并举例说明它的用途。请解释 Go 语言中的 map 和 slice 是什么,并举例说明它们的用途。请解释 Go 语言中…...
Kubernetes入门级教程
Kubernetes入门级教程1. Introduction1.1 概述1.2 关键字介绍2. Cluster Install2.1 Big Data -- Postgres3. 基础知识3.1 Pod3.2 控制器3.3 通讯模式3.4 服务发现4. Command4.0 编辑文件4.1 在宿主机执行命令4.2 创建资源对象4.3 查询资源对象4.4 查询资源描述4.5 修改资源4.6…...
Matlab | matlab常用命令总结
常用命令 一、 基础操作与环境二、 矩阵与数组操作(核心)三、 绘图与可视化四、 编程与控制流五、 符号计算 (Symbolic Math Toolbox)六、 文件与数据 I/O七、 常用函数类别重要提示这是一份 MATLAB 常用命令和功能的总结,涵盖了基础操作、矩阵运算、绘图、编程和文件处理等…...
Java面试专项一-准备篇
一、企业简历筛选规则 一般企业的简历筛选流程:首先由HR先筛选一部分简历后,在将简历给到对应的项目负责人后再进行下一步的操作。 HR如何筛选简历 例如:Boss直聘(招聘方平台) 直接按照条件进行筛选 例如:…...
android13 app的触摸问题定位分析流程
一、知识点 一般来说,触摸问题都是app层面出问题,我们可以在ViewRootImpl.java添加log的方式定位;如果是touchableRegion的计算问题,就会相对比较麻烦了,需要通过adb shell dumpsys input > input.log指令,且通过打印堆栈的方式,逐步定位问题,并找到修改方案。 问题…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
【学习笔记】erase 删除顺序迭代器后迭代器失效的解决方案
目录 使用 erase 返回值继续迭代使用索引进行遍历 我们知道类似 vector 的顺序迭代器被删除后,迭代器会失效,因为顺序迭代器在内存中是连续存储的,元素删除后,后续元素会前移。 但一些场景中,我们又需要在执行删除操作…...
关于easyexcel动态下拉选问题处理
前些日子突然碰到一个问题,说是客户的导入文件模版想支持部分导入内容的下拉选,于是我就找了easyexcel官网寻找解决方案,并没有找到合适的方案,没办法只能自己动手并分享出来,针对Java生成Excel下拉菜单时因选项过多导…...
【前端异常】JavaScript错误处理:分析 Uncaught (in promise) error
在前端开发中,JavaScript 异常是不可避免的。随着现代前端应用越来越多地使用异步操作(如 Promise、async/await 等),开发者常常会遇到 Uncaught (in promise) error 错误。这个错误是由于未正确处理 Promise 的拒绝(r…...
Python 训练营打卡 Day 47
注意力热力图可视化 在day 46代码的基础上,对比不同卷积层热力图可视化的结果 import torch import torch.nn as nn import torch.optim as optim from torchvision import datasets, transforms from torch.utils.data import DataLoader import matplotlib.pypl…...
十九、【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建
【用户管理与权限 - 篇一】后端基础:用户列表与角色模型的初步构建 前言准备工作第一部分:回顾 Django 内置的 `User` 模型第二部分:设计并创建 `Role` 和 `UserProfile` 模型第三部分:创建 Serializers第四部分:创建 ViewSets第五部分:注册 API 路由第六部分:后端初步测…...
Windows电脑能装鸿蒙吗_Windows电脑体验鸿蒙电脑操作系统教程
鸿蒙电脑版操作系统来了,很多小伙伴想体验鸿蒙电脑版操作系统,可惜,鸿蒙系统并不支持你正在使用的传统的电脑来安装。不过可以通过可以使用华为官方提供的虚拟机,来体验大家心心念念的鸿蒙系统啦!注意:虚拟…...
