当前位置: 首页 > news >正文

stm32基于HAL库驱动外部SPI flash制作虚拟U盘

stm32基于HAL库驱动外部SPI flash制作虚拟U盘


  • 📌参考文章:https://xiaozhuanlan.com/topic/6058234791
  • 🎞实现效果演示:
    在这里插入图片描述
  • 🔖上图中的读到的FLASH_ID所指的是针对不同容量,所对应的ID。
//W25X/Q不同容量对应不同ID关系
W25Q80  ID  0XEF13
W25Q16  ID  0XEF14
W25Q32  ID  0XEF15
W25Q64  ID  0XEF16
W25Q128 ID  0XEF17
W25Q256 ID  0XEF18

在这里插入图片描述

  • 🔖在电脑端,支持对虚拟出来的存储器进行读写操作。
    在这里插入图片描述

在这里插入图片描述

  • ✨如果设计成一块PCB,可以制作成一个微小容量的移动U盘。
  • 🌿基于STM32F103,HAL库生成的代码,可以移植到任意支持USB接口的STM32单片机上使用。
  • 🌿程序烧录后,通过PA11/PA12 USB初次连接电脑,会弹出提示格式化窗口。之后就可以使用,包括拷贝和创建文件到盘符内,保存数据后,拔插设备,数据不丢失。

stm32cumx配置

  • 🌿stm32cumx使能对应的SPI接口
    在这里插入图片描述

  • ✨串口非必须,只是方便调试时查看读取是否支持。

  • 🌿使能SPI接口:(尽量将SPI速度配置低一点,防止访问和写入出错)
    在这里插入图片描述

  • 🌿配置SPI CS(片选)引脚:
    在这里插入图片描述

  • 🌿使能USB外设:
    在这里插入图片描述

  • 🌿时钟配置为48MHz:
    在这里插入图片描述

在这里插入图片描述

  • 🌿使能调试接口
    在这里插入图片描述
  • 🌿勾选中间件:
    在这里插入图片描述
  • 🌿调整堆栈区大小:
    在这里插入图片描述

🛠Keil工程修改

  • 🌿导入驱动文件:bsp_spi_flash.cbsp_spi_flash.h到工程对应文件夹下:
    在这里插入图片描述
  • 🌿修改usbd_storage_if.c文件内容:
/* USER CODE BEGIN INCLUDE */
#include "bsp_spi_flash.h"	//将驱动头文件包含进来
/* USER CODE END INCLUDE */
//注释掉自动生成的以下3个宏定义
//#define STORAGE_LUN_NBR                  1
//#define STORAGE_BLK_NBR                  0x10000
//#define STORAGE_BLK_SIZ                  0x200/* USER CODE BEGIN PRIVATE_DEFINES */
//重新定义以下3个宏
#define STORAGE_LUN_NBR                  1
#define STORAGE_BLK_NBR                  2048 //块数量:256*8扇区=8MByte
#define STORAGE_BLK_SIZ                  4096  //每个扇区4096Byte
/* USER CODE END PRIVATE_DEFINES */
  • ✨这里的STORAGE_BLK_NBR宏代表,外部SPI FLASH 容量大小,2048 代表为8MB,如果spi flash容量为16MB,那么这里就是4096.以此类推,4MB spi flash就是1024
  • 📑补充以下函数内容:(usbd_storage_if.c文件内)
int8_t STORAGE_Init_FS(uint8_t lun)
{/* USER CODE BEGIN 2 */W25QXX_Init();return (USBD_OK);/* USER CODE END 2 */
}int8_t STORAGE_GetCapacity_FS(uint8_t lun, uint32_t *block_num, uint16_t *block_size)
{/* USER CODE BEGIN 3 */*block_num  = STORAGE_BLK_NBR;*block_size = STORAGE_BLK_SIZ;return (USBD_OK);/* USER CODE END 3 */
}int8_t STORAGE_IsReady_FS(uint8_t lun)
{/* USER CODE BEGIN 4 */u16 flash_ID;flash_ID =W25QXX_ReadID();printf("flash_ID:%d \r\n",flash_ID);//非必须,如需要调试,包含stdio.h头文件,启用串口return (USBD_OK);/* USER CODE END 4 */
}int8_t STORAGE_Read_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 6 */blk_addr += SPI_FLASH_START_SECTOR;SPI_FLASH_BufferRead(buf, blk_addr * SPI_FLASH_SECTOR_SIZE, blk_len * SPI_FLASH_SECTOR_SIZE);return (USBD_OK);/* USER CODE END 6 */
}
int8_t STORAGE_Write_FS(uint8_t lun, uint8_t *buf, uint32_t blk_addr, uint16_t blk_len)
{/* USER CODE BEGIN 7 */uint32_t write_addr;blk_addr +=SPI_FLASH_START_SECTOR;write_addr = blk_addr * SPI_FLASH_SECTOR_SIZE;SPI_FLASH_SectorErase(write_addr);SPI_FLASH_BufferWrite((uint8_t*)buf, write_addr, blk_len * SPI_FLASH_SECTOR_SIZE);return (USBD_OK);/* USER CODE END 7 */
}int8_t STORAGE_GetMaxLun_FS(void)
{/* USER CODE BEGIN 8 */return (STORAGE_LUN_NBR - 1);/* USER CODE END 8 */
}
  • main.c文件
/* USER CODE BEGIN Includes */
#include "bsp_spi_flash.h"
#include "stdio.h"//用于调试串口输出
/* USER CODE END Includes */
int main(void)
{/* USER CODE BEGIN 1 *//* USER CODE END 1 *//* MCU Configuration--------------------------------------------------------*//* Reset of all peripherals, Initializes the Flash interface and the Systick. */HAL_Init();/* USER CODE BEGIN Init *//* USER CODE END Init *//* Configure the system clock */SystemClock_Config();/* USER CODE BEGIN SysInit *//* USER CODE END SysInit *//* Initialize all configured peripherals */MX_GPIO_Init();MX_SPI1_Init();MX_USB_DEVICE_Init();MX_USART1_UART_Init();/* USER CODE BEGIN 2 */W25QXX_Init();//初始化/* USER CODE END 2 *//* Infinite loop *//* USER CODE BEGIN WHILE */while (1){/* USER CODE END WHILE *//* USER CODE BEGIN 3 */}/* USER CODE END 3 */
}

📝驱动文件

  • 📗bsp_spi_flash.h内容
#ifndef __SPI_FLASH_H
#define __SPI_FLASH_H#include "stm32f1xx.h"
#include "stm32f1xx_hal_spi.h"/* ???????? ----------------------------------------------------------------*/
#include "stm32f1xx_hal.h"typedef  uint32_t  u32;
typedef  uint8_t  u8;// 
typedef  uint16_t  u16;#define SPI_FLASH_REBUILD           0    //1:???????????Flash??0??????????????Flash
#define SPI_FLASH_SECTOR_SIZE    4096    // ????Flash????????
#define SPI_FLASH_START_SECTOR   256*4    // ????Flash?????FatFS?????
#define SPI_FLASH_SECTOR_COUNT   256   // ????Flash?????FatFS???????????
//W25X/Q不同容量对应不同ID关系
//W25Q80  ID  0XEF13
//W25Q16  ID  0XEF14
//W25Q32  ID  0XEF15
//W25Q64  ID  0XEF16
//W25Q128 ID  0XEF17
//W25Q256 ID  0XEF18
//#define W25Q80 	0XEF13
//#define W25Q16 	0XEF14
//#define W25Q32 	0XEF15
#define W25Q64 	0XEF16
//#define W25Q128	0XEF17
//#define W25Q256 0XEF18
//#define  sFLASH_ID     0XEF4017     //W25Q64
#define  sFLASH_ID 0XEF16
extern SPI_HandleTypeDef hspi1;
extern u16 W25QXX_TYPE;					//????W25QXX??????//#define	W25QXX_CS 		PBout(12)  		//W25QXX片选引脚
#define W25QXX_CS_1		  HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_SET)
#define W25QXX_CS_0	      HAL_GPIO_WritePin(GPIOA,GPIO_PIN_4,GPIO_PIN_RESET)#define W25X_WriteEnable		0x06
#define W25X_WriteDisable		0x04
#define W25X_ReadStatusReg1		0x05
#define W25X_ReadStatusReg2		0x35
#define W25X_ReadStatusReg3		0x15
#define W25X_WriteStatusReg1    0x01
#define W25X_WriteStatusReg2    0x31
#define W25X_WriteStatusReg3    0x11
#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 W25X_Enable4ByteAddr    0xB7
#define W25X_Exit4ByteAddr      0xE9void W25QXX_Init(void);
u16  W25QXX_ReadID(void);  	    		//???FLASH ID
void W25QXX_WAKEUP(void);				//????
void SPI_FLASH_SectorErase(u32 SectorAddr);
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead);
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite);
#endif /* __SPI_FLASH_H */
  • 📓bsp_spi_flash.c内容
#include "bsp_spi_flash.h"
//需要重新添加代码
#include "stm32f1xx_hal_gpio.h"
#include "stdio.h"u16 W25QXX_TYPE = W25Q64;	//设置SPI存储器型号extern SPI_HandleTypeDef hspi1;u8 W25QXX_ReadSR(u8 regno);             //??????????
void W25QXX_4ByteAddr_Enable(void);     //???4???????
void W25QXX_Write_SR(u8 regno,u8 sr);   //?????????
void W25QXX_Write_Enable(void);  		//?????
void W25QXX_Write_Disable(void);		//??????
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead);   //???flash
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite);//????flash
void W25QXX_Erase_Chip(void);    	  	//???????
void W25QXX_Erase_Sector(u32 Dst_Addr);	//????????
void W25QXX_Wait_Busy(void);           	//???????
void W25QXX_PowerDown(void);        	//?????????//SPI1 读写一个字节
//TxData:要写入的字节
//返回值:读取到的字节
u8 SPI2_ReadWriteByte(u8 TxData)
{u8 Rxdata;HAL_SPI_TransmitReceive(&hspi1,&TxData,&Rxdata,1, 1000);return Rxdata;          		    //返回收到的数据
}void SPI2_SetSpeed(u8 SPI_BaudRatePrescaler)
{assert_param(IS_SPI_BAUDRATE_PRESCALER(SPI_BaudRatePrescaler));//判断有效性__HAL_SPI_DISABLE(&hspi1);            //关闭SPIhspi1.Instance->CR1&=0XFFC7;          //位3-5清零,用来设置波特率hspi1.Instance->CR1|=SPI_BaudRatePrescaler;//设置SPI速度__HAL_SPI_ENABLE(&hspi1);             //使能SPI}//初始化SPI FLASH的IO口
void W25QXX_Init(void)
{u8 temp;GPIO_InitTypeDef GPIO_Initure;//    __HAL_RCC_GPIOB_CLK_ENABLE();           //使能GPIOB时钟__HAL_RCC_GPIOA_CLK_ENABLE();           //使能GPIOB时钟//PA4GPIO_Initure.Pin=GPIO_PIN_4;          	//PA4GPIO_Initure.Mode=GPIO_MODE_OUTPUT_PP;  //推挽输出GPIO_Initure.Pull=GPIO_PULLUP;          //上拉GPIO_Initure.Speed=GPIO_SPEED_FREQ_HIGH;//高速       HAL_GPIO_Init(GPIOA,&GPIO_Initure);     //初始化W25QXX_CS_1;			                //SPI FLASH不选中
//    SPI2_Init();		   			        //初始化SPISPI2_SetSpeed(SPI_BAUDRATEPRESCALER_2); //设置为42M时钟,高速模式W25QXX_TYPE = W25QXX_ReadID();	        //读取FLASH ID.printf("flash_ID:%d \r\n",W25QXX_TYPE);
//    if(W25QXX_TYPE==W25Q64)                //SPI FLASH为W25Q32
//    {temp=W25QXX_ReadSR(3);              //读取状态寄存器3,判断地址模式if((temp&0X01)==0)			        //如果不是4字节地址模式,则进入4字节地址模式{W25QXX_CS_0; 			        //选中SPI2_ReadWriteByte(W25X_Enable4ByteAddr);//发送进入4字节地址模式指令   W25QXX_CS_1;       		        //取消片选   }
//    }
}
//读取W25QXX的状态寄存器,W25QXX一共有3个状态寄存器
//状态寄存器1:
//BIT7  6   5   4   3   2   1   0
//SPR   RV  TB BP2 BP1 BP0 WEL BUSY
//SPR:默认0,状态寄存器保护位,配合WP使用
//TB,BP2,BP1,BP0:FLASH区域写保护设置
//WEL:写使能锁定
//BUSY:忙标记位(1,忙;0,空闲)
//默认:0x00
//状态寄存器2:
//BIT7  6   5   4   3   2   1   0
//SUS   CMP LB3 LB2 LB1 (R) QE  SRP1
//状态寄存器3:
//BIT7      6    5    4   3   2   1   0
//HOLD/RST  DRV1 DRV0 (R) (R) WPS ADP ADS
//regno:状态寄存器号,范:1~3
//返回值:状态寄存器值
u8 W25QXX_ReadSR(u8 regno)
{u8 byte=0,command=0;switch(regno){case 1:command=W25X_ReadStatusReg1;    //读状态寄存器1指令break;case 2:command=W25X_ReadStatusReg2;    //读状态寄存器2指令break;case 3:command=W25X_ReadStatusReg3;    //读状态寄存器3指令break;default:command=W25X_ReadStatusReg1;break;}W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(command);            //发送读取状态寄存器命令byte=SPI2_ReadWriteByte(0Xff);          //读取一个字节W25QXX_CS_1;                            //取消片选return byte;
}
//写W25QXX状态寄存器
void W25QXX_Write_SR(u8 regno,u8 sr)
{u8 command=0;switch(regno){case 1:command=W25X_WriteStatusReg1;    //写状态寄存器1指令break;case 2:command=W25X_WriteStatusReg2;    //写状态寄存器2指令break;case 3:command=W25X_WriteStatusReg3;    //写状态寄存器3指令break;default:command=W25X_WriteStatusReg1;break;}W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(command);            //发送写取状态寄存器命令SPI2_ReadWriteByte(sr);                 //写入一个字节W25QXX_CS_1;                            //取消片选
}
//W25QXX写使能
//将WEL置位
void W25QXX_Write_Enable(void)
{W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_WriteEnable);   //发送写使能W25QXX_CS_1;                            //取消片选
}
//W25QXX写禁止
//将WEL清零
void W25QXX_Write_Disable(void)
{W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_WriteDisable);  //发送写禁止指令W25QXX_CS_1;                            //取消片选
}//读取芯片ID
//返回值如下:
//0XEF13,表示芯片型号为W25Q80
//0XEF14,表示芯片型号为W25Q16
//0XEF15,表示芯片型号为W25Q32
//0XEF16,表示芯片型号为W25Q64
//0XEF17,表示芯片型号为W25Q128
//0XEF18,表示芯片型号为W25Q256
u16 W25QXX_ReadID(void)
{u16 Temp = 0;W25QXX_CS_0;SPI2_ReadWriteByte(0x90);//发送读取ID命令SPI2_ReadWriteByte(0x00);SPI2_ReadWriteByte(0x00);SPI2_ReadWriteByte(0x00);Temp|=SPI2_ReadWriteByte(0xFF)<<8;Temp|=SPI2_ReadWriteByte(0xFF);W25QXX_CS_1;return Temp;
}
//读取SPI FLASH
//在指定地址开始读取指定长度的数据
//pBuffer:数据存储区
//ReadAddr:开始读取的地址(24bit)
//NumByteToRead:要读取的字节数(最大65535)
void W25QXX_Read(u8* pBuffer,u32 ReadAddr,u16 NumByteToRead)
{u16 i;W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_ReadData);      //发送读取命令
//    if(W25QXX_TYPE == W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
//    {
//        SPI2_ReadWriteByte((u8)((ReadAddr)>>24));
//    }SPI2_ReadWriteByte((u8)((ReadAddr)>>16));   //发送24bit地址SPI2_ReadWriteByte((u8)((ReadAddr)>>8));SPI2_ReadWriteByte((u8)ReadAddr);for(i=0;i<NumByteToRead;i++){pBuffer[i]=SPI2_ReadWriteByte(0XFF);    //循环读数}W25QXX_CS_1;
}
/**
* @brief  读取FLASH数据
* @param 	pBuffer,存储读出数据的指针
* @param   ReadAddr,读取地址
* @param   NumByteToRead,读取数据长度
* @retval 无
*/
void SPI_FLASH_BufferRead(u8* pBuffer, u32 ReadAddr, u16 NumByteToRead)
{/* 选择FLASH: CS低电平 */W25QXX_CS_0;/* 发送 读 指令 */SPI2_ReadWriteByte(W25X_ReadData);/* 发送 读 地址高位 */SPI2_ReadWriteByte((ReadAddr & 0xFF0000) >> 16);/* 发送 读 地址中位 */SPI2_ReadWriteByte((ReadAddr& 0xFF00) >> 8);/* 发送 读 地址低位 */SPI2_ReadWriteByte(ReadAddr & 0xFF);/* 读取数据 */while (NumByteToRead--) /* while there is data to be read */{/* 读取一个字节*/*pBuffer = SPI2_ReadWriteByte(0xFF);/* 指向下一个字节缓冲区 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */W25QXX_CS_1;
}
#define WIP_Flag                  0x01/*** @brief  等待WIP(BUSY)标志被置0,即等待到FLASH内部数据写入完毕* @param  none* @retval none*/
void SPI_FLASH_WaitForWriteEnd(void)
{u8 FLASH_Status = 0;/* 选择 FLASH: CS 低 */W25QXX_CS_0;/* 发送 读状态寄存器 命令 */SPI2_ReadWriteByte(W25X_ReadStatusReg1);/* 若FLASH忙碌,则等待 */do{/* 读取FLASH芯片的状态寄存器 */FLASH_Status = SPI2_ReadWriteByte(0xFF);}while ((FLASH_Status & WIP_Flag) == SET);  /* 正在写入标志 *//* 停止信号  FLASH: CS 高 */W25QXX_CS_1;
}/*** @brief  擦除FLASH扇区* @param  SectorAddr:要擦除的扇区地址* @retval 无*/
void SPI_FLASH_SectorErase(u32 SectorAddr)
{/* 发送FLASH写使能命令 */W25QXX_Write_Enable();SPI_FLASH_WaitForWriteEnd();/* 擦除扇区 *//* 选择FLASH: CS低电平 */W25QXX_CS_0;/* 发送扇区擦除指令*/SPI2_ReadWriteByte(W25X_SectorErase);/*发送擦除扇区地址的高位*/SPI2_ReadWriteByte((u8)((SectorAddr)>>16));   //发送24bit地址SPI2_ReadWriteByte((u8)((SectorAddr)>>8));/* 发送擦除扇区地址的低位 */SPI2_ReadWriteByte(SectorAddr & 0xFF);/* 停止信号 FLASH: CS 高电平 */W25QXX_CS_1;/* 等待擦除完毕*/SPI_FLASH_WaitForWriteEnd();
}
/* WIP(busy)标志,FLASH内部正在写入 */#define SPI_FLASH_PageSize              256
#define SPI_FLASH_PerWritePageSize      256
/*** @brief  对FLASH按页写入数据,调用本函数写入数据前需要先擦除扇区* @param	pBuffer,要写入数据的指针* @param WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度,必须小于等于SPI_FLASH_PerWritePageSize* @retval 无*/
void SPI_FLASH_PageWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{/* 发送FLASH写使能命令 */W25QXX_Write_Enable();/* 选择FLASH: CS低电平 */W25QXX_CS_0;/* 写页写指令*/SPI2_ReadWriteByte(W25X_PageProgram);/*发送写地址的高位*/SPI2_ReadWriteByte((WriteAddr & 0xFF0000) >> 16);/*发送写地址的中位*/SPI2_ReadWriteByte((WriteAddr & 0xFF00) >> 8);/*发送写地址的低位*/SPI2_ReadWriteByte(WriteAddr & 0xFF);if(NumByteToWrite > SPI_FLASH_PageSize){NumByteToWrite = SPI_FLASH_PerWritePageSize;//FLASH_ERROR("SPI_FLASH_PageWrite too large!");}/* 写入数据*/while (NumByteToWrite--){/* 发送当前要写入的字节数据 */SPI2_ReadWriteByte(*pBuffer);/* 指向下一字节数据 */pBuffer++;}/* 停止信号 FLASH: CS 高电平 */W25QXX_CS_1;/* 等待写入完毕*/SPI_FLASH_WaitForWriteEnd();
}//SPI在一页(0~65535)内写入少于256个字节的数据
//在指定地址开始写入最大256字节的数据
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大256),该数不应该超过该页的剩余字节数!!!
void W25QXX_Write_Page(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u16 i;W25QXX_Write_Enable();                  //SET WELW25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_PageProgram);   //发送写页命令
//    if(W25QXX_TYPE==W25Q256)                //如果是W25Q256的话地址为4字节的,要发送最高8位
//    {
//        SPI2_ReadWriteByte((u8)((WriteAddr)>>24));
//    }SPI2_ReadWriteByte((u8)((WriteAddr)>>16)); //发送24bit地址SPI2_ReadWriteByte((u8)((WriteAddr)>>8));SPI2_ReadWriteByte((u8)WriteAddr);for(i=0;i<NumByteToWrite;i++)SPI2_ReadWriteByte(pBuffer[i]);//循环写数W25QXX_CS_1;                            //取消片选SPI_FLASH_WaitForWriteEnd();					   //等待写入结束
}/*** @brief  对FLASH写入数据,调用本函数写入数据前需要先擦除扇区* @param	pBuffer,要写入数据的指针* @param  WriteAddr,写入地址* @param  NumByteToWrite,写入数据长度* @retval 无*/
void SPI_FLASH_BufferWrite(u8* pBuffer, u32 WriteAddr, u16 NumByteToWrite)
{u8 NumOfPage = 0, NumOfSingle = 0, Addr = 0, count = 0, temp = 0;/*mod运算求余,若writeAddr是SPI_FLASH_PageSize整数倍,运算结果Addr值为0*/Addr = WriteAddr % SPI_FLASH_PageSize;/*差count个数据值,刚好可以对齐到页地址*/count = SPI_FLASH_PageSize - Addr;/*计算出要写多少整数页*/NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;/*mod运算求余,计算出剩余不满一页的字节数*/NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* Addr=0,则WriteAddr 刚好按页对齐 aligned  */if (Addr == 0){/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*先把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}/* 若地址与 SPI_FLASH_PageSize 不对齐  */else{/* NumByteToWrite < SPI_FLASH_PageSize */if (NumOfPage == 0){/*当前页剩余的count个位置比NumOfSingle小,一页写不完*/if (NumOfSingle > count){temp = NumOfSingle - count;/*先写满当前页*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);WriteAddr +=  count;pBuffer += count;/*再写剩余的数据*/SPI_FLASH_PageWrite(pBuffer, WriteAddr, temp);}else /*当前页剩余的count个位置能写完NumOfSingle个数据*/{SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumByteToWrite);}}else /* NumByteToWrite > SPI_FLASH_PageSize */{/*地址不对齐多出的count分开处理,不加入这个运算*/NumByteToWrite -= count;NumOfPage =  NumByteToWrite / SPI_FLASH_PageSize;NumOfSingle = NumByteToWrite % SPI_FLASH_PageSize;/* 先写完count个数据,为的是让下一次要写的地址对齐 */SPI_FLASH_PageWrite(pBuffer, WriteAddr, count);/* 接下来就重复地址对齐的情况 */WriteAddr +=  count;pBuffer += count;/*把整数页都写了*/while (NumOfPage--){SPI_FLASH_PageWrite(pBuffer, WriteAddr, SPI_FLASH_PageSize);WriteAddr +=  SPI_FLASH_PageSize;pBuffer += SPI_FLASH_PageSize;}/*若有多余的不满一页的数据,把它写完*/if (NumOfSingle != 0){SPI_FLASH_PageWrite(pBuffer, WriteAddr, NumOfSingle);}}}
}//无检验写SPI FLASH
//必须确保所写的地址范围内的数据全部为0XFF,否则在非0XFF处写入的数据将失败!
//具有自动换页功能
//在指定地址开始写入指定长度的数据,但是要确保地址不越界!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
//CHECK OK
void W25QXX_Write_NoCheck(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u16 pageremain;pageremain=256-WriteAddr%256; //单页剩余的字节数if(NumByteToWrite<=pageremain)pageremain=NumByteToWrite;//不大于256个字节while(1){W25QXX_Write_Page(pBuffer,WriteAddr,pageremain);if(NumByteToWrite==pageremain)break;//写入结束了else //NumByteToWrite>pageremain{pBuffer+=pageremain;WriteAddr+=pageremain;NumByteToWrite-=pageremain;			  //减去已经写入了的字节数if(NumByteToWrite>256)pageremain=256; //一次可以写入256个字节else pageremain=NumByteToWrite; 	  //不够256个字节了}};
}
//写SPI FLASH
//在指定地址开始写入指定长度的数据
//该函数带擦除操作!
//pBuffer:数据存储区
//WriteAddr:开始写入的地址(24bit)
//NumByteToWrite:要写入的字节数(最大65535)
u8 W25QXX_BUFFER[4096];
void W25QXX_Write(u8* pBuffer,u32 WriteAddr,u16 NumByteToWrite)
{u32 secpos;u16 secoff;u16 secremain;u16 i;u8 * W25QXX_BUF;W25QXX_BUF=W25QXX_BUFFER;secpos=WriteAddr/4096;//扇区地址secoff=WriteAddr%4096;//在扇区内的偏移secremain=4096-secoff;//扇区剩余空间大小//printf("ad:%X,nb:%X\r\n",WriteAddr,NumByteToWrite);//测试用if(NumByteToWrite<=secremain)secremain=NumByteToWrite;//不大于4096个字节while(1){W25QXX_Read(W25QXX_BUF,secpos*4096,4096);//读出整个扇区的内容for(i=0;i<secremain;i++)//校验数据{if(W25QXX_BUF[secoff+i]!=0XFF)break;//需要擦除}if(i<secremain)//需要擦除{W25QXX_Erase_Sector(secpos);//擦除这个扇区for(i=0;i<secremain;i++)	   //复制{W25QXX_BUF[i+secoff]=pBuffer[i];}W25QXX_Write_NoCheck(W25QXX_BUF,secpos*4096,4096);//写入整个扇区}else W25QXX_Write_NoCheck(pBuffer,WriteAddr,secremain);//写已经擦除了的,直接写入扇区剩余区间.if(NumByteToWrite==secremain)break;//写入结束了else//写入未结束{secpos++;//扇区地址增1secoff=0;//偏移位置为0pBuffer+=secremain;  //指针偏移WriteAddr+=secremain;//写地址偏移NumByteToWrite-=secremain;				//字节数递减if(NumByteToWrite>4096)secremain=4096;	//下一个扇区还是写不完else secremain=NumByteToWrite;			//下一个扇区可以写完了}};
}
//擦除整个芯片
//等待时间超长...
void W25QXX_Erase_Chip(void)
{W25QXX_Write_Enable();                  //SET WELW25QXX_Wait_Busy();W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_ChipErase);        //发送片擦除命令W25QXX_CS_1;                            //取消片选W25QXX_Wait_Busy();   				   //等待芯片擦除结束
}
//擦除一个扇区
//Dst_Addr:扇区地址 根据实际容量设置
//擦除一个扇区的最少时间:150ms
void W25QXX_Erase_Sector(u32 Dst_Addr)
{//监视falsh擦除情况,测试用//printf("fe:%x\r\n",Dst_Addr);Dst_Addr*=4096;W25QXX_Write_Enable();                  //SET WELW25QXX_Wait_Busy();W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_SectorErase);   //发送扇区擦除指令if(W25QXX_TYPE==W25Q64)                //如果是W25Q256的话地址为4字节的,要发送最高8位{SPI2_ReadWriteByte((u8)((Dst_Addr)>>24));}SPI2_ReadWriteByte((u8)((Dst_Addr)>>16));  //发送24bit地址SPI2_ReadWriteByte((u8)((Dst_Addr)>>8));SPI2_ReadWriteByte((u8)Dst_Addr);W25QXX_CS_1;                            //取消片选W25QXX_Wait_Busy();   				    //等待擦除完成
}
//等待空闲
void W25QXX_Wait_Busy(void)
{while((W25QXX_ReadSR(1)&0x01)==0x01);   // 等待BUSY位清空
}
//进入掉电模式
void W25QXX_PowerDown(void)
{W25QXX_CS_0;                            //使能器件SPI2_ReadWriteByte(W25X_PowerDown);     //发送掉电命令W25QXX_CS_1;                            //取消片选//delay_us(3);                            //等待TPD
}
//唤醒
void W25QXX_WAKEUP(void)
{W25QXX_CS_0;                                //使能器件SPI2_ReadWriteByte(W25X_ReleasePowerDown);  //  send W25X_PowerDown command 0xABW25QXX_CS_1;                                //取消片选//delay_us(3);                                //等待TRES1
}

📚工程源码

链接:https://pan.baidu.com/s/164MW7mOR8fnaHZUC0dt7SQ 
提取码:0nei

相关文章:

stm32基于HAL库驱动外部SPI flash制作虚拟U盘

stm32基于HAL库驱动外部SPI flash制作虚拟U盘 &#x1f4cc;参考文章&#xff1a;https://xiaozhuanlan.com/topic/6058234791&#x1f39e;实现效果演示&#xff1a; &#x1f516;上图中的读到的FLASH_ID所指的是针对不同容量&#xff0c;所对应的ID。 //W25X/Q不同容量对应…...

vue3-ts- element-plus新增组件-过滤

新增组件-所有值为空时过滤 <el-form-item label"家庭成员"><divclass"username-box"v-for"(item, index) in form.namelist":key"index"><div>姓名&#xff1a;<el-input v-model"item.name" placeho…...

PostgreSQL SQL优化

Oracle SQL优化 一、在字段里面写的子查询放到from后面&#xff0c;用left join&#xff0c;会大幅提高SQL查询速度。 一、在字段里面写的子查询放到from后面&#xff0c;用left join&#xff0c;会大幅提高SQL查询速度。...

debian12网络静态ip配置-OSSIM 安全漏洞扫描系统平台

本配置适合于服务器上的静态ip配置&#xff0c;该方法简单可靠。 1 临时配置 ifconfig eth0 192.168.1.97 netmask 255.255.255.0 broadcast 192.168.1.255 ip route add default via 192.168.1.1 2 主要的网络配置文件 /etc/network/interfaces /etc/resolv.conf 3 配置…...

微软 Visual Studio 现已内置 Markdown 编辑器,可直接修改预览 .md 文件

Visual Studio Code V1.66.0 中文版 大小&#xff1a;75.30 MB类别&#xff1a;文字处理 本地下载 Markdown 是一种轻量级标记语言&#xff0c;当开发者想要格式化代码但又不想牺牲易读性时&#xff0c;Markdown 是一个很好的解决方案&#xff0c;比如 GitHub 就使用 Markdo…...

阿里云通义千问开源第二波!大规模视觉语言模型Qwen-VL上线魔搭社区

通义千问开源第二波&#xff01;8月25日消息&#xff0c;阿里云推出大规模视觉语言模型Qwen-VL&#xff0c;一步到位、直接开源。Qwen-VL以通义千问70亿参数模型Qwen-7B为基座语言模型研发&#xff0c;支持图文输入&#xff0c;具备多模态信息理解能力。在主流的多模态任务评测…...

在腾讯云服务器OpenCLoudOS系统中安装Jenkins(有图详解)

Jenkins介绍 Jenkins是一个开源软件项目&#xff0c;是基于java开发的一种持续集成工具&#xff0c;用于监控持续重复的工作&#xff0c;旨在提供一个开放易用的软件平台&#xff0c;使软件的持续集成变成可能。 将项目代码的svn地址配置在Jenkins&#xff0c;就可以直接在Je…...

《vue3实战》在created生命周期中运用slice()方法结合element plus组件实现电影评价系统的分页

目录 前言 电影评价系统的分页是什么&#xff1f;它具体的作用体现在哪些方面&#xff1f; 一、slice的含义、语法和作用以及created的作用 slice是什么&#xff1f;slice有什么语法&#xff1f;slice的作用体现在哪些方面&#xff1f; created生命周期的作用&#xff1a;…...

NO.04 MyBatis的各种查询功能

目录 1、查询一个实体类对象 2、查询一个List集合 3、查询单个数据 5、查询多条数据并存储在Map集合中 5.1 方法一&#xff1a;将数据存储在map集合中&#xff0c;再将map集合存储在List集合中 5.2 方法二&#xff1a;将数据存储在map集合中 6、MyBatis中为Java中常用的…...

Spring循环依赖

一、Autowired依赖注入的缓存 二、Resource依赖注入过程 三、循环依赖 singletonObjects&#xff1a;缓存经过了完整生命周期的beanearlySingletonObjects&#xff1a;缓存未经过完整生命周期的bean&#xff0c;如果某个bean出现了循环依赖&#xff0c;就会提前把这个暂时未经过…...

docker以distribution和registry管理个人镜像仓库

目录 一.distribution 1.扩展源下载docker-distribution并启动 2.打标签并认证安全仓库 3.推送到私人仓库 4.拉取镜像 二.registry 1.拉取registry的镜像 2.运行容器并打标签 3.认证安全仓库 4.推送到私人仓库 5.拉取镜像 一.distribution 1.扩展源下载docker-dist…...

2023京东酒类市场数据分析(京东数据开放平台)

根据鲸参谋平台的数据统计&#xff0c;今年7月份京东平台酒类环比集体下滑&#xff0c;接下来我们一起来看白酒、啤酒、葡萄酒的详情数据。 首先来看白酒市场。 鲸参谋数据显示&#xff0c;7月份京东平台白酒的销量为210万&#xff0c;环比下滑约49%&#xff1b;销售额将近19…...

Android中的APK打包与安全

aapt2命令行实现apk打包 apk文件结构 classes.dex&#xff1a;Dex&#xff0c;即Android Dalvik执行文件 AndroidManifest.xml&#xff1a;工程中AndroidManifest.xml编译后得到的二进制xml文件 META-INF&#xff1a;主要保存各个资源文件的SHA1 hash值&#xff0c;用于校验…...

HTTPS单向认证与双向认证

HTTPS单向认证与双向认证 HTTPSCA证书单向认证双向认证 HTTPS Https就是HTTPSSL/TSL的简称。 SSL(Secure Socket Layer 安全套接层)是TCP/IP协议中基于HTTP之下TCP之上的一个可选协议层。 起初HTTP在传输数据时使用的是明文&#xff0c;传输过程中并不安全。网景&#xff08;N…...

(七) ElasticSearch 分词器

1.分词器 分词器是 Elasticsearch 用于将文本拆分为单词&#xff08;词项&#xff09;的组件&#xff0c;以便于搜索和索引。以下是一些关于 Elasticsearch 分词器的常见问题和相关操作的介绍&#xff1a; 1&#xff09;什么是分词器&#xff1f; 分词器是 Elasticsearch 中…...

足球- EDA的历史数据分析并可视化

足球- EDA的历史数据分析并可视化 背景数据介绍探索数据时需要遵循的一些方向:数据处理导入库数据探索 数据可视化赛事分析主客场比分相关性分析时间序列分析 总结 背景 该数据集包括从1872年第一场正式比赛到2023年的44&#xff0c;341场国际足球比赛的结果。比赛范围从FIFA世…...

用正则处理Unicode 编码的文本

Unicode&#xff08;中文&#xff1a;万国码、国际码、统一码、单一码&#xff09;是计算机科学领域里的一项业界标准。它对世界上大部分的文字进行了整理、编码。Unicode 使计算机呈现和处理文字变得简单。 现在的 Unicode 字符分为 17 组编排&#xff0c;每组为一个平面&…...

【分布式技术专题】「OSS中间件系列」从0到1的介绍一下开源对象存储MinIO技术架构

MinIO背景介绍 MinIO创始者是Anand Babu Periasamy, Harshavardhana&#xff08;戒日王&#xff09;等人&#xff0c; Anand是GlusterFS的初始开发者、Gluster公司的创始人与CTO&#xff0c;Harshavardhana曾经是GlusterFS的开发人员&#xff0c;直到2011年红帽收购了Gluster公…...

生成式人工智能的潜在有害影响与未来之路(三)

产品责任法的潜在适用 背景和风险 产品责任是整个二十世纪发展起来的一个法律领域&#xff0c;旨在应对大规模生产的产品可能对社会造成的伤害。这一法律领域侧重于三个主要危害&#xff1a;设计缺陷的产品、制造缺陷的产品和营销缺陷的产品。产品责任法的特点有两个要素&…...

【2023钉钉杯复赛】A题 智能手机用户监测数据分析 Python代码分析

【2023钉钉杯复赛】A题 智能手机用户监测数据分析 Python代码分析 1 题目 一、问题背景 近年来&#xff0c;随着智能手机的产生&#xff0c;发展到爆炸式的普及增长&#xff0c;不仅推动了中 国智能手机市场的发展和扩大&#xff0c;还快速的促进手机软件的开发。近年中国智能…...

Django(5)-视图函数和模板渲染

Django 中的视图的概念是「一类具有相同功能和模板的网页的集合」 在我们的投票应用中&#xff0c;我们需要下列几个视图&#xff1a; 问题索引页——展示最近的几个投票问题。 问题详情页——展示某个投票的问题和不带结果的选项列表。 问题结果页——展示某个投票的结果。 投…...

Windows下 MySql通过拷贝data目录迁移数据库的方法

MySQL数据库的文件目录下图所示&#xff0c; 现举例说明通过COPY文件夹data下数据库文件&#xff0c;进行数据拷贝的步骤&#xff1b;源数据库运行在A服务器上&#xff0c;拷贝到B服务器&#xff0c;假定B服务器上MySQL数据库已经安装完成&#xff0c;为空数据库。 首先进入A服…...

RabbitMQ---订阅模型-Fanout

1、 订阅模型-Fanout Fanout&#xff0c;也称为广播。 流程图&#xff1a; 在广播模式下&#xff0c;消息发送流程是这样的&#xff1a; 1&#xff09; 可以有多个消费者 2&#xff09; 每个消费者有自己的queue&#xff08;队列&#xff09; 3&#xff09; 每个队列都要绑定…...

nginx 中新增url请求参数

1、nginx中新增配置&#xff1a; set $args "$args&参数名参数值"; 示例&#xff1a; set $args "$args&demo1cn_yaojin&demo2123123&myip$remote_addr"; location / {add_header Access-Control-Allow-Origin *;add_header Access-Contro…...

[系统] 电脑突然变卡 / 电脑突然** / 各种突发情况解决思路

今天来公司办公&#xff0c;开机之后发现电脑出现各种问题&#xff0c;死机、卡顿、点什么都加载&#xff0c;甚至开一个文件夹要1分钟才能打开&#xff0c;花了2个小时才解决&#xff0c;走了很多弯路&#xff0c;其实早点想通&#xff0c;5分钟就能解决问题&#xff0c;所以打…...

改进YOLO系列:8.添加SimAM注意力机制

添加SimAM注意力机制 1. SimAM注意力机制论文2. SimAM注意力机制原理3. SimAM注意力机制的配置3.1common.py配置3.2yolo.py配置3.3yaml文件配置1. SimAM注意力机制论文 论文题目:SimAM: A Simple, Parameter-Free Attention Module for Convolutional Neural Network…...

Go与Rust的对比与分析

Rust 和 Go 是两种现代语言&#xff0c;近年来获得了巨大的关注&#xff0c;每种语言都有自己独特的优势和权衡。在这篇文章中&#xff0c;我们将深入探讨 Rust 和 Go 之间的差异&#xff0c;重点关注性能、语言功能和其他关键因素&#xff0c;以帮助您针对您的开发需求做出明智…...

SpingMVC拦截器-异常处理的思路,用户体验不好的地方

1、异常处理机制 1.1 原先我们的异常都是手动的try..catch() 2、他存在着一些缺陷&#xff1a;这里创建了一个Demo的controller,内部有一个show方法&#xff1a; 3、访问内部&#xff0c;我要实现demoshow方法&#xff0c;我们来调用show1和show2的方法&#xff1a; 4、有一…...

【C++设计模式】用动画片《少年骇客》(Ben10)来解释策略模式

2023年8月25日&#xff0c;周五上午 今天上午学习设计模式中的策略模式时&#xff0c;发现这个有点像很多卡通片里面的变身器... #include<iostream>//alien hero是外星英雄的意思 //在《少年骇客》中&#xff0c;主角可以通过变身器变成10种外星英雄 class AlienHero{ …...

软件测试及数据分析处理实训室建设方案

一 、系统概述 软件测试及数据分析处理是软件开发过程中的一项重要测试活动&#xff0c;旨在验证不同软件模块或组件之间的集成与交互是否正常。综合测试确保各个模块按照设计要求正确地协同工作&#xff0c;以实现整个软件系统的功能和性能。以下是软件测试及数据分析处理的一…...

切换Debian的crontab的nano编辑器

Debian的crontab默认的编辑器是nano&#xff0c;用起来很不习惯,怎么才能转回vim呢? 用以下命令便可&#xff1a; #update-alternatives --config editor 出现以下所示的界面&#xff1a; 而后选择8使用/usr/bin/vim就能够了。 PS&#xff1a;若是你发现你的定时没有生效&…...

Spring Cloud Alibaba-Sentinel--服务容错

1 高并发带来的问题 在微服务架构中&#xff0c;我们将业务拆分成一个个的服务&#xff0c;服务与服务之间可以相互调用&#xff0c;但是由于网络 原因或者自身的原因&#xff0c;服务并不能保证服务的100%可用&#xff0c;如果单个服务出现问题&#xff0c;调用这个服务就会出…...

Stable Diffusion 系列教程 | 如何获得更高清优质的AI绘画

目录 1 高清修复 1.1 原理 1.2 基本操作 1.3 优缺点 2 UpScale 放大脚本 2.1 原理 2.2 基本操作 2.3 优缺点 3 附加功能放大 3.1 原理 3.2 基本操作 3.3 优缺点 优化出图质量&#xff0c;产出更高清&#xff0c;分辨率更高&#xff0c;更有细节的绘画作品呢&#x…...

食品饮料制造行业如何实现数字化转型和工业4.0

随着科技的不断进步和全球产业的不断发展&#xff0c;食品饮料制造行业也正迎来数字化转型和工业4.0的浪潮。这一转型不仅提升了生产效率和质量&#xff0c;还满足了消费者对更健康、更可持续产品的需求。本文将深入探讨食品饮料制造行业在数字化转型和工业4.0方面的趋势、挑战…...

UE学习记录03----UE5.2 使用MVVM示例

1.打开ue5.2新建C项目 2.项目中通过类导向新建C类&#xff0c;父类选择为UMVVMViewModelBase&#xff0c;创建完成会自动打开vs 3.在VS中对新建的类进行宏定义 使用 C 类向导 创建的类声明自动通过 UCLASS() 宏进行处理。 UCLASS() 宏使得引擎意识到这个类的存在&#xff0c;并…...

代码审计-审计工具介绍-DAST+SAST+IAST项目

DASTSASTIAST项目介绍 DAST&#xff1a; 动态应用程序安全测试&#xff08;Dynamic Application Security Testing&#xff09;技术在测试或运行阶段分析应用程序的动态运行状态。它模拟黑客行为对应用程序进行动态攻击&#xff0c;分析应用程序的反应&#xff0c;从而确定该We…...

网络安全应急响应预案培训

应急响应预案的培训是为了更好地应对网络突发状况&#xff0c;实施演 练计划所做的每一项工作&#xff0c;其培训过程主要针对应急预案涉及的相 关内容进行培训学习。做好应急预案的培训工作能使各级人员明确 自身职责&#xff0c;是做好应急响应工作的基础与前提。应急响应…...

STM32F4X 定时器中断

STM32F4X 定时器中断 什么是定时器STM32F4X 定时器分类有关定时器的概念预分频(PSC)自动重装载值(ARR) STM32F4X定时器例程定时器相关函数定时器例程 什么是定时器 定时器(Timer)最基本的功能就是定时&#xff0c;比如定时翻转LED灯&#xff0c;定时向串口发送数据等。除此之外…...

MongoDB +Dataframe+excel透视表

读取MongoDB中的表 from pymongo import MongoClient import pandas as pd client MongoClient(IP地址, 27017)db client[AOI] collection db[表名] #替换为实际的名称 document collection.find({time:{$gte:2023-08-15 15:26:06}})#筛选数据 df pd.DataFrame(list(docu…...

PostgreSQL日期相关

PostgreSQL日期相关 PostgreSQL日期相减得到整数 PostgreSQL日期相减得到整数 天数相减得到整数&#xff0c;执行下面的SQL&#xff1a; select to_number(date_trunc(day,2023-08-18 00:00:00::TIMESTAMP - 2023-08-10 00:00:00::TIMESTAMP INTERVAL 1 day)::text, 9999999…...

C++编程法则365天一天一条(8)const_cast去除cv限定

文章目录 1、使用场景2、关于const_cast的陷阱3、总结const_cast主要用于去除cv限定,且在C++提供的4个类型转换运算符里,只有const_cast可以去除或者添加cv属性。 1、使用场景 用法如下: const_cast< target-type >( expression ) 两个可能是多级指针且指向相同类型…...

某网站DES加密逆向分析实战

文章目录 一、抓包分析二、加密分析一、重写加密 一、抓包分析 分析站点&#xff1a; aHR0cDovL2VpcC5jaGFuZmluZS5jb20v 首先我们提交一下登陆信息&#xff1a; 搜索j_password查看加密函数: 把上图搜索到的encryptPassword函数拿出来分析一下&#xff1a; function encryptP…...

面向对象的理解

想要对象了&#xff1f;没问题&#xff0c;new一个就好了。 但是&#xff0c;new太多对象&#xff0c;对象也会生气的哦。 你瞧&#xff0c;她来了 从两段代码发现端倪 我们来计算一个矩形的面积&#xff0c;看看这两段代码有什么区别呢&#xff1f; 第一段&#xff1a; con…...

java ssl加密发送邮件

通过25端口发送邮件不安全&#xff0c;改为ssl加密方式发送邮件&#xff0c;比较常见的2中实现类发送邮件如下所示。 1、JavaMailSenderImpl 类 使用该实现类发送邮件&#xff0c;ssl加密使用端口号为465&#xff0c;借助Properties类设置ssl的各种配置。 SysUserEntity user…...

SpringBoot-yml配置文件的使用与优势

目录 一、什么是yml配置文件&#xff1f; 二、如何在SpringBoot中使用yml配置文件&#xff1f; 三、如何在SpringBoot中注入yml配置文件的属性&#xff1f; Value注解 ConfigurationProperties注解 四、yml配置文件与properties配置文件的区别和优势 区别 优势 五、总结…...

Layer Normalization(层规范化)

详细内容在这篇论文&#xff1a;Layer Normalization 训练深度神经网络需要大量的计算&#xff0c;减少计算时间的一个有效方法是规范化神经元的活动&#xff0c;例如批量规范化BN&#xff08;batch normalization&#xff09;技术&#xff0c;然而&#xff0c;批量规范化对小批…...

redisson参数配置

文章目录 pom配置链接配置建议 pom <!-- 引入spring-data-redis组件 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId><exclusions><!-- 因springboot2.x…...

【基于Arduino的仿生蚂蚁机器人】

【基于Arduino的仿生蚂蚁机器人】 1. 概述2. Arduino六足位移台–蚂蚁机器人3D模型3. 3D 打印零件4. 组装Arduino六足位移台5. Arduino蚂蚁机器人电路图6. 为Arduino Hexapod设计PCB7. 组装电路板8. 系统代码9. Arduino蚂蚁机器人安卓应用程序在本教程中,我将向您展示如何构建…...

angular12里面FormGroup做多个项目的相关check

FromFroup在鼠标失去焦点时做相关check&#xff0c;可以在group方法第二个参数的位置加一个对象参数 { validator: this.checkPasswords } 在Angular 12中&#xff0c;可以使用formGroup来进行两个密码是否一致的检查。以下是一个示例&#xff1a; 首先&#xff0c;在组件的…...

TypeScript 的发展与基本语法

目录 一、为什么什么是TypeScript&#xff1f; 1、发展历史 2、typescript与javascript 3、静态类型的好处 二、基础语法 1、基础数据类型 2、补充类型 3、泛型 4、泛型的高级语法 5、类型别名&类型断言 6、字符串/数字 字面量 三、高级类型 1、联合/交叉类型…...