5、HAL库驱动W25Qxx

news2024/9/20 16:48:32

一、 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收发轮询函数进行读写开启外设

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/352114.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

10大主流压力测试工具各有所长,怎么选适合自己的?

市面上流行的压力/负载/性能测试工具多是来自国外&#xff0c;近年来国内的性能测试工具也如雨后春笋崛起。同时由于开发的目的和侧重点不同&#xff0c;其功能也有很大差异&#xff0c;下面就为您简单介绍10款目前最常见的测试产品。 1、kylinTOP测试与监控平台&#xff08;商…

实现一个比ant功能更丰富的Modal组件

普通的modal组件如下&#xff1a; 我们写的modal额外支持&#xff0c;后面没有蒙版&#xff0c;并且Modal框能够拖拽 还支持渲染在文档流里&#xff0c;上面的都是fixed布局&#xff0c;我们这个正常渲染到文档下面&#xff1a; render部分 <RenderDialog{...restState}visi…

Lesson5.2---Python 之 NumPy 切片索引和广播机制

一、切片和索引 ndarray 对象的内容可以通过索引或切片来访问和修改&#xff08;&#xff09;&#xff0c;与 Python 中 list 的切片操作一样。ndarray 数组可以基于 0 - n 的下标进行索引&#xff08;先行后列&#xff0c;都是从 0 开始&#xff09;。 区别在于&#xff1a;数…

代码随想录算法训练营第三十二天 | 122.买卖股票的最佳时机II,55. 跳跃游戏,45.跳跃游戏II

一、参考资料买卖股票的最佳时机IIhttps://programmercarl.com/0122.%E4%B9%B0%E5%8D%96%E8%82%A1%E7%A5%A8%E7%9A%84%E6%9C%80%E4%BD%B3%E6%97%B6%E6%9C%BAII.html 跳跃游戏https://programmercarl.com/0055.%E8%B7%B3%E8%B7%83%E6%B8%B8%E6%88%8F.html 跳跃游戏 IIhttps://pr…

金三银四必备软件测试必问面试题

初级软件测试必问面试题1、你的测试职业发展是什么&#xff1f;测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前 3 年积累测试经验&#xff0c;按如何做好测试工程…

【数据结构期末例题】

前言 本文是博主自己在准备学校数据结构考试时的总结&#xff0c;各个知识点都贴有对应的详细讲解文章以供大家参考&#xff1b;当然文中还有许许多多的截图&#xff0c;这些是博主对主要内容的摘取&#xff0c;对于那些基础较好的同学可以直接看截图&#xff0c;减少跳转对应文…

声呐学习笔记之波束成形

目录什么是波束什么是波束成形线阵数学推导(均匀排布)什么是波束 和光束一样&#xff0c;当所有波的传播方向都一致时&#xff0c;即形成了波束。工程师利用波束已经有相当久的历史。在二战中&#xff0c;工程师已经将波束利用在雷达中&#xff0c;雷达通过扫描波束方向来探测…

力扣-分数排名

大家好&#xff0c;我是空空star&#xff0c;本篇带你了解一道简单的力扣sql练习题。 文章目录前言一、题目&#xff1a;178. 分数排名二、解题1.错误示范①提交SQL运行结果2.错误示范②提交SQL运行结果3.正确示范①提交SQL运行结果4.正确示范②提交SQL运行结果5.正确示范③提交…

全流程GMS地下水数值模拟技能培养及溶质运移反应问题深度解析实践技术

本次综合前期多次学习的效果及重点关注环节&#xff0c;系统性呈现地下水数值模拟软件GMS建模方法同时&#xff0c;建立与实践项目过程中的重点问题相融合&#xff0c;在教学中不仅强调学习三维地质结构建模、水文地质模型概化、边界条件设定、参数反演和模型校核等关键环节&am…

套娃式工具!用 AI 识别 AI ?#AI classifier

2022年以来&#xff0c;市面上就出现了不少 AI 生成文本的工具&#xff0c;尤其是 OpenAI 推出的 ChatGPT &#xff0c;不仅能够协助完成撰写邮件、视频脚本、文案、翻译、代码等任务&#xff0c;还能通过学习和理解人类的语言来进行对话&#xff0c;并根据聊天的上下文进行互动…

AI技术网关如何用于安全生产监测?有什么优势?

现代工业生产和运营的规模越来越庞大、系统和结构越来越复杂&#xff0c;现场的风险点多面广&#xff0c;给作业一线的安全监管带来极大的挑战。 针对工地、煤矿、危化品、加油站、烟花爆竹、电力等行业的安全生产监管场景&#xff0c;可以借助AI智能与物联网技术&#xff0c;…

4.1 Filter-policy

1. 实验目的 熟悉Filter-policy的应用场景掌握Filter-policy的配置方法2. 实验拓扑 Filter-policy实验拓扑如图4-5所示: 图4-5:Filter-policy 3. 实验步骤 (1) 网络连通性 R1的配置 <Huawei>system-vi…

点成分享|对于粘性液体该如何精准移液?

之前文章介绍移液器原理及分类时有说到&#xff0c;从移液器的使用原理来进行移液器的分类&#xff0c;大致就可分为空气置换式移液器和正向置换移液器&#xff08;即外置活塞式移液器&#xff09;。 对于粘性液体&#xff0c;特别是高粘度液体的移液操作&#xff0c;最好的方…

Vulnhub 渗透练习(四)—— Acid

环境搭建 环境下载 kail 和 靶机网络适配调成 Nat 模式&#xff0c;实在不行直接把网络适配还原默认值&#xff0c;再重试。 信息收集 主机扫描 没扫到&#xff0c;那可能端口很靠后&#xff0c;把所有端口全扫一遍。 发现 33447 端口。 扫描目录&#xff0c;没什么有用的…

代码随想录算法训练营第三十天 | 332.重新安排行程,51. N皇后,37. 解数独,总结

Day29 休息~一、参考资料重点&#xff01;&#xff01; 回溯算法总结篇https://programmercarl.com/%E5%9B%9E%E6%BA%AF%E6%80%BB%E7%BB%93.html 组合问题&#xff1a;N个数里面按一定规则找出k个数的集合排列问题&#xff1a;N个数按一定规则全排列&#xff0c;有几种排列方式…

【数字电路】数字电路的学习核心

文章目录前言一、电子电路知识体系二、数电的学习目标三、数字电路分析例子四、数字电路设计例子总结前言 用数字信号完成对数字量进行算术运算和逻辑运算的电路称为数字电路&#xff0c;或数字系统。由于它具有逻辑运算和逻辑处理功能&#xff0c;所以又称数字逻辑电路。现代…

2023美赛E题思路代码分析

2023美赛数学建模E题思路分析&#xff0c;更多的可以文末 E题&#xff1a;光污染 问题一&#xff1a;制定一个广泛适用的指标来确定一个地点的光污染风险水平。 首先我们要知道光污染以两种形式存在&#xff1a; 天空辉光&#xff08;也称为人造天空辉光、光穹或逃逸光&…

Exchange 2013升级以及域名绑定等若干问题

环境简介Exchange 2013服务器位于ad域中&#xff0c;系统为Windows server 2012 R2&#xff0c;其内部域名为&#xff1a;mail.ad.com一. Exchange客户端无法在浏览器中正常运行在域中部署Exchange服务器后&#xff0c;除了可以通过outlook、foxmail等邮件客户端来使用邮箱功能…

具有非线性动态行为的多车辆列队行驶问题的基于强化学习的方法

论文地址&#xff1a; Reinforcement Learning Based Approach for Multi-Vehicle Platooning Problem with Nonlinear Dynamic Behavior 摘要 协同智能交通系统领域的最新研究方向之一是车辆编队。研究人员专注于通过传统控制策略以及最先进的深度强化学习 (RL) 方法解决自动…

java易错题锦集四

effective java 不要再构造方法中启动任何线程 g new GameServer(); g.start();构造器无返回值&#xff0c;但是不能void修饰 字符串 String是包装类型吗&#xff1f;答案&#xff1a; 不是 对应的基本类型和包装类如下表&#xff1a; 基本数据类型 包装类 byte Byte bool…