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

细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV

目录

一、工程配置

1、时钟、DEBUG

2、GPIO

3、SPI2

4、USART6

5、NVIC 

二、软件设计

1、FALSH

(1)w25flash.h

(2) w25flash.c

        1)W25Q16基本操作指令

        2)计算地址的辅助功能函数

        3)器件、块、扇区擦除函数

        4)存储区读写函数

2、KEY_LED

3、spi.h

4、spi.c

5、main.c

三、下载与运行


        本文旨在说明STM32F407单片机通过SPI2扩展FLASH W25Q16BV,对FLASH进行读ID、写操作、读操作、擦除操作。使用的是旺宝红龙开发板STM32F407ZGT6 KIT V1.0。使用开发板上的按键S2、S3、S4、S5、S6依此执行擦除芯片、擦除BLOCK0、写、读操作、MCU复位。使用开发板上的D1、D2、D3、D4作为动作指示灯。通过串口USART6把操作状态显示到串口助手上。资源利用详见工程配置。

      工程里创建FLASH目录,包含w25flash.c、w25flash.h。

      工程依旧创建KEY_LED目录,包含keyled.c、keyled.h。关于如何在工程中创建目录并添加文件以及keyled.c、keyled.h详见作者的其他文章。

        其它资料可以参考本文作者的其他文章:细说STM32F407单片机SPI基础知识_stm32f407 spi2-CSDN博客  https://wenchm.blog.csdn.net/article/details/144376346

        细说Flash存储芯片W25Q128FW和W25Q16BV_flash拓展地址寄存器0xc5-CSDN博客  https://wenchm.blog.csdn.net/article/details/144398492

一、工程配置

1、时钟、DEBUG

        外部时钟,25MHz,设置到HCLK=168MHz,PCLK1=42MHz,PCLK2=84MHz,其它,都设置成168MHz。

        DEBUG,选择serial wire。

2、GPIO

        项目工程使用了开发板的按键S2、S3、S4、S5、S6,和开发板上的LED灯D1、D2、D3、D4。详细配置如下表:

用户标签

开发板

引脚名称

引脚功能

GPIO模式

默认电平

上拉

或下拉

LED1

D1

PA6

GPIO_Output

推挽输出

HighMCU输出低电平时灯亮

上拉

LED2

D2

PA4

GPIO_Output

推挽输出

HighMCU输出低电平时灯亮

上拉

LED3

D3

PB6

GPIO_Output

推挽输出

HighMCU输出低电平时灯亮

上拉

LED4

D4

PC6

GPIO_Output

推挽输出

HighMCU输出低电平时灯亮

上拉

KeyUp

S2

PA0

GPIO_Input

输入

按键输入低电平

上拉

KeyDown

S3

PD3

GPIO_Input

输入

按键输入低电平

上拉

KeyLeft

S4

PF7

GPIO_Input

输入

按键输入低电平

上拉

KeyRight

S5

PF6

GPIO_Input

输入

按键输入低电平

上拉

S6

NRST

GPIO_Input

输入

按键复位

上拉

3、SPI2

        MCU的SPI2与FLASH的连接原理图,详见参考文章。管脚配置见下表:

用户标签

开发板

标识

引脚名称

引脚功能

GPIO模式

默认电平

上拉

或下拉

FLASH_CS

FLASH_CS

PC13

GPIO_Output片选

推挽输出

High

上拉

SPI2_SCK

SPI_SCK

PB10

时钟

Alternate Function

High

No

SPI2_MOSI

SPI_MOSI

PB15

MOSI数据线

Alternate Function

High

No

SPI2_MISO

SPI_MISO

PB14

MISO数据线

Alternate Function

High

No

4、USART6

        115200、8、None、1,其它参数默认。占用管脚PG14、PG9。

5、NVIC 

         不设置中断,但把Time Base的优先级修改为0。

二、软件设计

1、FALSH

        创建一个名为FLASH的子目录,创建文件w25flash.h和w25flash.c,并将其存放在这个子目录里。 将W25Q16常用的一些功能编写为函数,也就是实现W25Q16常用操作指令,例如擦除芯片、擦除扇区、读取数据、写入数据等。

        注意W25Q16驱动程序与SPI接口的HAL驱动程序的区别。SPI的HAL驱动程序实现了SPI接口数据传输的基本功能,是SPI硬件层的驱动;而W25Q16驱动程序则是根据W25Q16的指令定义,实现器件具体功能操作的一系列函数。W25Q16驱动程序要用到SPI硬件层的HAL驱动程序,要通过SPI的HAL驱动程序实现数据帧的收发。

        W25Q16驱动程序涉及的硬件接口包括SPI接口和CS信号,驱动程序应该能很容易地移植到其他电路板上,所以在文件开头定义了硬件接口相关的宏。

  • 为CS信号连接的GPIO引脚定义了表示GPIO端口和引脚号的宏CS_PORT和CS_PIN,定义了两个宏函数__Select_Flash()__Deselect_Flash()用于对CS置位和复位。
  • W25Q16器件连接的SPI接口定义为宏SPI_HANDLE,这里指向文件spi.h中的变量hspi2,也就是表示SPI2接口的外设对象变量。在驱动程序的所有函数内部都使用宏SPI_HANDLE表示SPI2接口。

        这样定义硬件接口后,如果要将这个驱动程序移植到其他开发板上操作W25Q16,只需修改这3个宏的定义即可。

        文件w25flash.h中的函数分为几组,这些函数就是W25Q16常用指令的实现。文件w25flash.c的全部代码有400多行,这里就不全部显示出来了,只选择其中一些典型的函数进行解释说明。

        文件w25flash.h是W25Q16驱动程序的头文件,定义了一些宏和函数。这个文件的完整代码如下:

(1)w25flash.h

/* 文件: w25flash.h* 功能描述: Flash 存储器W25Q16的驱动程序* 作者:* 移植:* 修改日期:2019-06-05* 移植日期:2024-12-09* W25Q16BV 芯片参数: 2M字节,24位地址线* 分为32个Block,每个Block 64K字节* 一个Block又分为16个Sector,共512个Sector,每个Sector 4K字节* 一个Sector又分为16个Page,共8192个Page,每个Page 256字节* 写数据操作的基本单元是Page,一次连续写入操作不能超过一个Page的范围。写的Page必须是擦除过的。*/#ifndef _W25FLASH_H
#define _W25FLASH_H#include "stm32f4xx_hal.h"
#include "spi.h"			//使用其中的变量 hspi1,表示SPI1接口/* W25Q16硬件接口相关的部分:CS引脚和SPI接口 ,若电路不同,更改这部分配置即可*/
// Flash_CS -->PC13, 片选信号CS操作的宏定义函数
#define CS_PORT GPIOC
#define	CS_PIN GPIO_PIN_13
#define	SPI_HANDLE hspi1	//SPI接口对象,使用spi.h中的变量 hspi1#define	__Select_Flash() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_RESET)	//CS=0
#define	__Deselect_Flash() HAL_GPIO_WritePin(CS_PORT, CS_PIN, GPIO_PIN_SET)	//CS=1//===========Flash存储芯片W25Q16的存储容量参数================
#define FLASH_PAGE_SIZE 256		//一个Page是256字节#define	FLASH_SECTOR_SIZE 4096	//一个Sector是4096字节#define	FLASH_SECTOR_COUNT 512	//总共512个Sector//=======1. SPI 基本发送和接收函数,阻塞式传输============
HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData);					//SPI接口发送一个字节
HAL_StatusTypeDef SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount);	//SPI接口发送多个字节uint8_t	SPI_ReceiveOneByte();												//SPI接口接收一个字节
HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount);	//SPI接口接收多个字节//=========2. W25Qxx 基本控制指令==========
// 0xEF14,表示芯片型号为W25Q16BV, Winbond,0x90指令
// 0xEF16,表示芯片型号为W25Q64JV, Winbond,0x90指令
// 0xEF17,表示芯片型号为W25Q128JV, Winbond,0x90指令
// 根据FLASH数据修改此处,比如:
// 0xC817,表示芯片型号为GD25Q128,ELM,用过
// 0x1C17,表示芯片型号为EN25Q128,台湾EON
// 0xA117,表示芯片型号为FM25Q128,复旦微电子
// 0x2018,表示芯片型号为N25Q128,美光
// 0x2017,表示芯片型号为XM25QH128,武汉新芯,用过uint16_t Flash_ReadID(void); 	// Command=0x90, Manufacturer/Device IDuint64_t Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32);	//Command=0x4B, Read Unique ID, 64-bit
// W25Q16无此指令
// HAL_StatusTypeDef Flash_WriteVolatile_Enable(void);  			//Command=0x50: Write Volatile EnableHAL_StatusTypeDef Flash_Write_Enable(void);  	//Command=0x06: Write Enable, 使WEL=1
HAL_StatusTypeDef Flash_Write_Disable(void);	//Command=0x04, Write Disable,使WEL=0uint8_t	Flash_ReadSR1(void);  		//Command=0x05:  Read Status Register-1,返回寄存器SR1的值
uint8_t	Flash_ReadSR2(void);  		//Command=0x35:  Read Status Register-2,返回寄存器SR2的值void Flash_WriteSR1(uint8_t SR1);	//Command=0x01:  Write Status Register,	只写SR1的值,禁止写状态寄存器uint32_t Flash_Wait_Busy(void);  	//读状态寄存器SR1,等待BUSY变为0,返回值是等待时间
void Flash_PowerDown(void);   		//Command=0xB9: Power Down
void Flash_WakeUp(void);  			//Command=0xAB: Release Power Down//========3. 计算地址的辅助功能函数========
//根据Block  绝对编号获取地址,共32个Block
uint32_t Flash_Addr_byBlock(uint8_t BlockNo);
//根据Sector 绝对编号获取地址,共512个Sector
uint32_t Flash_Addr_bySector(uint16_t  SectorNo);
//根据Page  绝对编号获取地址,共8192个Page
uint32_t Flash_Addr_byPage(uint16_t  PageNo);//根据Block编号,和内部Sector编号计算地址,一个Block有16个Sector,
uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo);
//根据Block编号,内部Sector编号,内部Page编号计算地址
uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t  SubPageNo);
//将24位地址分解为3个字节
void Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow);//=======4. chip、Block,Sector擦除函数============
//Command=0xC7: Chip Erase, 擦除整个器件,大约4秒
void Flash_EraseChip(void);//Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址,耗时大约150ms
void Flash_EraseBlock64K(uint32_t globalAddr);//Command=0x20: Sector Erase(4KB) 扇区擦除, globalAddr是扇区的全局地址,耗时大约30ms
void Flash_EraseSector(uint32_t globalAddr);//=========5. 数据读写函数=============
//Command=0x03,  读取一个字节,任意全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr);//Command=0x03,  连续读取多个字节,任意全局地址
void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);//Command=0x0B,  高速连续读取多个字节,任意全局地址, 速度大约是常规读取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount);//Command=0x02: Page program 对一个Page写入数据(最多256字节), globalAddr是初始位置的全局地址,耗时大约3ms
void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount);//从某个Sector的起始地址开始写数据,数据可能跨越多个Page,甚至跨越Sector,总字节数byteCount不能超过64K,也就是一个Block的大小
void Flash_WriteSector(uint32_t globalAddr,  const uint8_t* pBuffer, uint16_t byteCount);#endif

        定义了4个SPI基本发送和接收函数,用于传输1字节或多字节,接收1字节或多字节。这几个函数实际上就是调用了SPI的HAL驱动程序中阻塞式数据传输函数HAL_SPI_Transmit()和HAL SPI_Receive()。在封装为W25Q16驱动程序的函数时,内部直接使用宏SPI_HANDLE替代了具体的hspil,使用宏定义常量MAX_TIMEOUT作为超时等待时间,这样可以简化函数的调用,因为这几个基本的传输函数在其他函数里被大量调用。

(2) w25flash.c

        1)W25Q16基本操作指令

        W25Q16的每个函数对应于W25Q16的一个指令。例如:

        读器件ID的函数Flash ReadID()就是实现了指令码为0x90的指令;FlashWrite_Enable()函数实现了“写使能”(指令码0x06)指令;Flash_Wait_Busy()函数读取状态寄存器SR1,判断BUSY位是否为0,直到BUSY为0时才退出。

        Flash_Write_Enable()和Flash_Wait_Busy()是在其他指令操作函数里经常用到的,例如擦除芯片、擦除扇区、写数据等操作之前必须执行“写使能”指令。一些比较耗时间的操作执行后,必须等待状态寄存器SR1的BUSY位变为0,也就是需要调用函数Flash_Wait_Busy()。

        这些函数的代码解释了通过一个函数实现一个指令操作的方法,它们就是根据指令定义以及相应的指令时序图,通过片选信号CS的控制以及SPI接口的字节数据发送和接收来实现一个指令的操作。例如,函数Flash_ReadID()执行查询芯片的制造商和器件ID的指令,指令码0x90。程序先执行宏函数__Select_Flash()使片选信号CS为低电平,从而开始一次SPI传输。然后,按照指令0x90的定义依次发送4个字节数据0x90、0x00、0x00、0x00,其中0x90是指令码,中间两个0x00是dummy字节,最后一个0x00是特定的。最后,W25Q16会返回2字节数据,依次接收这2字节数据,就能得到制造商和器件ID信息。

        函数Flash_Wait_Busy()是用于判断状态寄存器SR1的BUSY位是否为0的,所以需要调用函数Flash_ReadSR1()读取状态寄存器SR1的内容,直到BUSY位变为0时,函数才退出。

        2)计算地址的辅助功能函数

        W25Q16的一些指令需要使用24位的绝对地址,例如块擦除、读数据等指令都需要提供24位绝对地址。直接记住或推算地址是比较麻烦的,在使用Flash的存储空间时,一般以块、扇区、页为单位进行管理,直接根据块、扇区、页的编号计算地址是比较实用的。所以,我们在驱动程序中定义了几个辅助函数,用于根据块、扇区、页的编号计算24位绝对地址,还可以将24位绝对地址分解为3字节数据,便于在指令中使用。

        3)器件、块、扇区擦除函数

        定义了器件擦除、块擦除和扇区擦除指令的操作函数,其中块擦除和扇区擦除指令需要起始地址。例如,扇区擦除的函数是Flash_EraseSector()。在擦除操作之前,必须执行“写使能”指令,使状态寄存器SR1的WEL位变为1,并且等待BUSY位变为0的时候,才能开始发送擦除操作指令。擦除指令发送结束后,器件执行擦除操作,在此期间BUSY位为1,需等待BUSY位变为0之后,才能退出函数。

        执行擦除操作后的Flash存储区域数据为0xFF。向存储区域写数据时,必须是擦除后的区域才能写入数据,否则写入无效。所以,一个存储区域只能有效写入一次,下次再写入之前必须先擦除。从存储区读出数据的次数是无限制的。

        4)存储区读写函数

        “读数据”指令(指令码0x03)可以从任何一个24位地址开始读取1字节或连续多字节的数据,由此定义了两个函数Flash_ReadOneByte()和Flash_ReadBytes()。

        写数据使用“页编程”指令(指令码0x02),由此定义函数Flash_WriteInPage()用于向一个页内写入数据。

        使用函数Flash_ReadBytes(读取数据时,起始地址可以是任何地址,读取的数据长度也可以超过页的容量,也就是可以超过256字节,最多可连续读取65536字节。使用函数Flash_WriteInPage()写入数据时需要注意以下几点。

  • 一次的写数据操作是限定在一个页范围内的,所以一次写入数据长度最多256字节。
  • 起始地址可以是任何地址,但写数据的偏移地址超过页的边界后,会从该页的开始地址继续写。所以,起始地址为页的开始地址时,最多可写入256字节。
  • 写入数据的存储区域必须是擦除过的,也就是存储内容是0xFF,否则写入数据无效。所以一个页只能写入一次,下次再写之前,需要先擦除页。W25Q16擦除的最小单位是扇区。

        驱动程序中还有一个函数Flash_WriteSector(),它可以从一个扇区的起始地址开始写入不超过64KB的数据。这个函数内部会先擦除需要用到的扇区,然后将数据按页的大小分解,调用Flash_WriteInPage()逐个页写入数据。

/* 文件: w25flash.c* 功能描述: Flash 存储器W25Q16的驱动程序* 作者:* 移植:* 修改日期:2019-06-05* 移植日期:2024-12-10*/#include "w25flash.h"#define MAX_TIMEOUT 200 //SPI轮询操作时的最大等待时间,ms//SPI接口发送一个字节,byteData是需要发送的数据
HAL_StatusTypeDef SPI_TransmitOneByte(uint8_t byteData)
{return HAL_SPI_Transmit(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);
}//SPI接口发送多个字节, pBuffer是发送数据缓存区指针,byteCount是发送数据字节数,byteCount最大256
HAL_StatusTypeDef SPI_TransmitBytes(uint8_t* pBuffer, uint16_t byteCount)
{return HAL_SPI_Transmit(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}//SPI接口接收一个字节,返回接收的一个字节数据
uint8_t	SPI_ReceiveOneByte()
{uint8_t	byteData=0;HAL_SPI_Receive(&SPI_HANDLE, &byteData, 1, MAX_TIMEOUT);return byteData;
}//SPI接口接收多个字节,pBuffer是接收数据缓存区指针,byteCount是需要接收数据的字节数
HAL_StatusTypeDef SPI_ReceiveBytes(uint8_t* pBuffer, uint16_t byteCount)
{return HAL_SPI_Receive(&SPI_HANDLE, pBuffer, byteCount, MAX_TIMEOUT);
}//Command=0x05:  Read Status Register-1,返回寄存器SR1的值
uint8_t Flash_ReadSR1(void)
{  uint8_t byte=0;__Select_Flash();	//CS=0SPI_TransmitOneByte(0x05); //Command=0x05: Read Status Register-1byte=SPI_ReceiveOneByte();__Deselect_Flash();	//CS=1return byte;   
} //Command=0x35: Read Status Register-2,返回寄存器SR2的值
uint8_t Flash_ReadSR2(void)
{uint8_t byte=0;__Select_Flash();	//CS=0SPI_TransmitOneByte(0x35);	//Command=0x35: Read Status Register-2byte=SPI_ReceiveOneByte();	//读取一个字节__Deselect_Flash();	//CS=1return byte;
}//Command=0x01: Write Status Register,只写SR1的值
//耗时大约10-15ms
void Flash_WriteSR1(uint8_t SR1)
{   Flash_Write_Enable();		//必须使 WEL=1__Select_Flash();			//CS=0SPI_TransmitOneByte(0x01);	//Command=0x01:Write Status Register,只写SR1的值SPI_TransmitOneByte(0x00);	//SR1的值
//	SPI_WriteOneByte(0x00);		//SR2的值, 只发送SR1的值,而不发送SR2的值,QE和CMP将自动被清零__Deselect_Flash();			//CS=1Flash_Wait_Busy();			//耗时大约10-15ms
}  /*
HAL_StatusTypeDef Flash_WriteVolatile_Enable(void)  	//Command=0x50: Write Volatile Enable
{__Select_Flash();	//CS=0HAL_StatusTypeDef result=SPI_TransmitOneByte(0x50);__Deselect_Flash();	//CS=1return result;
}
*///Command=0x06: Write Enable,使WEL=1
HAL_StatusTypeDef Flash_Write_Enable(void)
{__Select_Flash();	//CS=0HAL_StatusTypeDef result=SPI_TransmitOneByte(0x06);  //Command=0x06: Write Enable,使WEL=1__Deselect_Flash();	//CS=1Flash_Wait_Busy(); 	//等待操作完成return result;
} //Command=0x04, Write Disable,使WEL=0
HAL_StatusTypeDef Flash_Write_Disable(void)
{  __Select_Flash();	//CS=0HAL_StatusTypeDef result=SPI_TransmitOneByte(0x04); //Command=0x04, Write Disable,使WEL=0__Deselect_Flash();	//CS=1Flash_Wait_Busy();return result;
} //根据Block绝对编号获取地址, 共32个Block,BlockNo 取值范围0-31
//每个块64K字节,16位地址,块内地址范围0x0000-0xFFFF。
uint32_t Flash_Addr_byBlock(uint8_t	BlockNo)
{
//	uint32_t addr=BlockNo*0x10000;uint32_t addr = BlockNo;addr = addr<<16; //左移16位,等于乘以0x10000return addr;
}//根据Sector绝对编号获取地址, 共512个Sector, SectorNo取值范围0-511
//每个扇区4K字节,12位地址,扇区内地址范围0x000-0xFFF
uint32_t Flash_Addr_bySector(uint16_t SectorNo)
{if (SectorNo>511)	//不能超过511SectorNo=0;
//	uint32_t addr=SectorNo*0x1000;uint32_t addr = SectorNo;addr = addr<<12;	//左移12位,等于乘以0x1000return addr;
}//根据Page绝对编号获取地址,共8192个Page,  PageNo取值范围0-8191
//每个页256字节,8位地址,页内地址范围0x00—0xFF
uint32_t Flash_Addr_byPage(uint16_t PageNo)
{
//	uint32_t addr=PageNo*0x100;uint32_t addr = PageNo;addr = addr<<8;		//左移8位,等于乘以0x100return addr;
}//根据Block编号和内部Sector编号计算地址,一个Block有16个Sector
//BlockNo取值范围0-31,内部SubSectorNo取值范围0-15
uint32_t Flash_Addr_byBlockSector(uint8_t BlockNo, uint8_t SubSectorNo)
{if (SubSectorNo>15)	 //不能超过15SubSectorNo = 0;//	uint32_t addr=BlockNo*0x10000;		//先计算Block的起始地址uint32_t addr = BlockNo;addr = addr<<16;	//先计算Block的起始地址//	uint32_t offset=SubSectorNo*0x1000;	//计算Sector的偏移地址uint32_t offset = SubSectorNo;		//计算Sector的偏移地址offset = offset<<12;//计算Sector的偏移地址addr += offset;return addr;
}// 根据Block编号,内部Sector编号,内部Page编号获取地址
// BlockNo取值范围0-31
// 一个Block有16个Sector, 内部SubSectorNo取值范围0-15
// 一个Sector有16个Page , 内部SubPageNo取值范围0-15
uint32_t Flash_Addr_byBlockSectorPage(uint8_t BlockNo, uint8_t SubSectorNo, uint8_t SubPageNo)
{if (SubSectorNo>15)	//不能超过15SubSectorNo = 0;if (SubPageNo>15)	//不能超过15SubPageNo = 0;//	uint32_t addr=BlockNo*0x10000;		//先计算Block的起始地址uint32_t addr = BlockNo;addr = addr<<16;	//先计算Block的起始地址//	uint32_t offset=SubSectorNo*0x1000;	//计算Sector的偏移地址uint32_t offset = SubSectorNo;		//计算Sector的偏移地址offset = offset<<12;//计算Sector的偏移地址addr += offset;//	offset=SubPageNo*0x100;	//计算Page的偏移地址offset = SubPageNo;offset = offset<<8;//计算Page的偏移地址addr += offset;	   //Page的起始地址return addr;
}// 将24位地址分解为3个字节
// globalAddr是全局24位地址, 返回 addrHigh高字节,addrMid中间字节,addrLow低字节
void Flash_SpliteAddr(uint32_t globalAddr, uint8_t* addrHigh, uint8_t* addrMid,uint8_t* addrLow)
{*addrHigh = (globalAddr>>16);		//addrHigh=高字节globalAddr = globalAddr & 0x0000FFFF;//屏蔽高字节*addrMid = (globalAddr>>8);			//addrMid=中间字节*addrLow = globalAddr & 0x000000FF;	//屏蔽中间字节,只剩低字节,addrLow=低字节
}//读取芯片ID
//返回值如下:				   
// 0xEF17,表示芯片型号为W25Q16, Winbond,用过
// 0xC817,表示芯片型号为GD25Q16,ELM,用过
// 0x1C17,表示芯片型号为EN25Q16,台湾EON
// 0xA117,表示芯片型号为FM25Q16,复旦微电子
// 0x2018,表示芯片型号为N25Q16,美光
// 0x2017,表示芯片型号为XM25QH16,武汉新芯,用过//读取芯片的制造商和器件ID,高字节是Manufacturer ID,低字节是Device ID
uint16_t Flash_ReadID(void)
{uint16_t Temp = 0;__Select_Flash();	//CS=0SPI_TransmitOneByte(0x90);		//指令码,0x90=Manufacturer/Device IDSPI_TransmitOneByte(0x00);		//dummySPI_TransmitOneByte(0x00);		//dummySPI_TransmitOneByte(0x00);		//0x00Temp = SPI_ReceiveOneByte()<<8;	//Manufacturer IDTemp|= SPI_ReceiveOneByte();	//Device ID, 与具体器件相关__Deselect_Flash();	//CS=1return Temp;
}// 参数High32和Low32分别返回64位序列号的高32位和低32位的值
// 函数返回值为64位序列号的值
uint64_t Flash_ReadSerialNum(uint32_t* High32,  uint32_t* Low32)//读取64位序列号,
{uint8_t Temp = 0;uint64_t SerialNum = 0;uint32_t High=0,Low = 0;__Select_Flash();			//CS=0SPI_TransmitOneByte(0x4B);	//发送指令码, 4B=read Unique IDSPI_TransmitOneByte(0x00);	//发送4个Dummy字节数据SPI_TransmitOneByte(0x00);SPI_TransmitOneByte(0x00);SPI_TransmitOneByte(0x00);for(uint8_t i=0; i<4; i++)//高32位{Temp = SPI_ReceiveOneByte();High = (High<<8);High = High | Temp;   //按位或}for(uint8_t i=0; i<4; i++)//低32位{Temp = SPI_ReceiveOneByte();Low = (Low<<8);Low = Low | Temp;  	 //按位或}__Deselect_Flash();		 //CS=1*High32 = High;*Low32 = Low;SerialNum = High;SerialNum = SerialNum<<32;//高32位SerialNum = SerialNum | Low;return SerialNum;
}// 在任意地址读取一个字节的数据,返回读取的字节数据
// globalAddr是24位全局地址
uint8_t Flash_ReadOneByte(uint32_t globalAddr)
{uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节__Select_Flash();	//CS=0SPI_TransmitOneByte(0x03);	 //Command=0x03, read dataSPI_TransmitOneByte(byte2);	 //发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);byte2 = SPI_ReceiveOneByte();//接收1个字节 //why byte2?__Deselect_Flash();	//CS=1return byte2;
}//从任何地址开始读取指定长度的数据
//globalAddr:开始读取的地址(24bit), pBuffer:数据存储区指针,byteCount:要读取的字节数
void Flash_ReadBytes(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
{ uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节__Select_Flash();	//CS=0SPI_TransmitOneByte(0x03);  //Command=0x03, read dataSPI_TransmitOneByte(byte2);	//发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);SPI_ReceiveBytes(pBuffer, byteCount);//接收byteCount个字节数据__Deselect_Flash();	//CS=1
}  //Command=0x0B,  高速连续读取flash多个字节,任意全局地址, 速度大约是常规读取的2倍
void Flash_FastReadBytes(uint32_t globalAddr, uint8_t* pBuffer,  uint16_t byteCount)
{
// 	uint16_t i;uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节__Select_Flash();	//CS=0SPI_TransmitOneByte(0x0B);  //Command=0x0B, fast read dataSPI_TransmitOneByte(byte2);	//发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);SPI_TransmitOneByte(0x00);	//Dummy字节SPI_ReceiveBytes(pBuffer, byteCount);//接收byteCount个字节数据__Deselect_Flash();	//CS=1}//Command=0xC7: Chip Erase, 擦除整个器件
// 擦除后,所有存储区内容为0xFF,耗时大约25秒
void Flash_EraseChip(void)
{                                   Flash_Write_Enable();   //使 WEL=1Flash_Wait_Busy();   	//等待空闲__Select_Flash();		//CS=0SPI_TransmitOneByte(0xC7);//Command=0xC7: Chip Erase, 擦除整个器件__Deselect_Flash();		//CS=1Flash_Wait_Busy();		//等待芯片擦除结束,大约25秒
}   // Command=0x02: Page program, 对一个页(256字节)编程, 耗时大约3ms,
// globalAddr是写入初始地址,全局地址
// pBuffer是要写入数据缓冲区指针,byteCount是需要写入的数据字节数
// 写入的Page必须是前面已经擦除过的,如果写入地址超出了page的边界,就从Page的开头重新写
void Flash_WriteInPage(uint32_t globalAddr, uint8_t* pBuffer, uint16_t byteCount)
{uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节Flash_Write_Enable();//SET WELFlash_Wait_Busy();__Select_Flash();	//CS=0SPI_TransmitOneByte(0x02);  //Command=0x02: Page program 对一个扇区编程SPI_TransmitOneByte(byte2);	//发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);SPI_TransmitBytes(pBuffer, byteCount);//发送byteCount个字节的数据
//	for(uint16_t i=0; i<byteCount; i++)
//	{
//		byte2=pBuffer[i];
//		SPI_WriteOneByte(byte2);//要写入的数据
//	}__Deselect_Flash();	//CS=1Flash_Wait_Busy(); 	//耗时大约3ms
}// 从某个Sector的起始位置开始写数据,数据可能跨越多个Page,甚至跨越Sector,不必提前擦除
// globalAddr是写入初始地址,全局地址,是扇区的起始地址,
// pBuffer是要写入数据缓冲区指针
// byteCount是需要写入的数据字节数,byteCount不能超过64K,也就是一个Block(16个扇区)的大小,但是可以超过一个Sector(4K字节)
// 如果数据超过一个Page,自动分成多个Page,调用EN25Q_WriteInPage分别写入
void Flash_WriteSector(uint32_t globalAddr,  const uint8_t* pBuffer, uint16_t byteCount)
{
//需要先擦除扇区,可能是重复写文件uint8_t secCount = (byteCount / FLASH_SECTOR_SIZE);//数据覆盖的扇区个数if ((byteCount % FLASH_SECTOR_SIZE) >0)secCount++;uint32_t startAddr = globalAddr;for (uint8_t k=0; k<secCount; k++){Flash_EraseSector(startAddr);	//擦除扇区startAddr += FLASH_SECTOR_SIZE;	//移到下一个扇区}//分成Page写入数据,写入数据的最小单位是Pageuint16_t leftBytes = byteCount % FLASH_PAGE_SIZE; //非整数个Page剩余的字节数,即最后一个Page写入的数据uint16_t pgCount = byteCount/FLASH_PAGE_SIZE;  	  //前面整数个Pageuint8_t* buff = (uint8_t*)pBuffer;for(uint16_t i=0; i<pgCount; i++)	//写入前面pgCount个Page的数据,{Flash_WriteInPage(globalAddr, buff, FLASH_PAGE_SIZE);//写一整个Page的数据globalAddr += FLASH_PAGE_SIZE;	//地址移动一个Pagebuff += FLASH_PAGE_SIZE;		//数据指针移动一个Page大小}if (leftBytes>0)Flash_WriteInPage(globalAddr, buff, leftBytes);		//最后一个Page,不是一整个Page的数据
}//Command=0xD8: Block Erase(64KB) 擦除整个Block, globalAddr是全局地址
//清除后存储区内容全部为0xFF,  耗时大概150ms
void Flash_EraseBlock64K(uint32_t globalAddr)
{Flash_Write_Enable();//SET WELFlash_Wait_Busy();uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节__Select_Flash();	//CS=0SPI_TransmitOneByte(0xD8);  //Command=0xD8, Block Erase(64KB)SPI_TransmitOneByte(byte2);	//发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);__Deselect_Flash();	//CS=1Flash_Wait_Busy();  //耗时大概150ms
}// 擦除一个扇区(4KB字节),Command=0x20, Sector Erase(4KB)
// globalAddr: 扇区的绝对地址,24位地址0x00XXXXXX
// 擦除后,扇区内全部内容为0xFF, 耗时大约30ms,
void Flash_EraseSector(uint32_t globalAddr)
{  Flash_Write_Enable();//SET WELFlash_Wait_Busy();uint8_t byte2, byte3, byte4;Flash_SpliteAddr(globalAddr, &byte2, &byte3, &byte4);//24位地址分解为3个字节__Select_Flash();	//CS=0SPI_TransmitOneByte(0x20); //Command=0x20, Sector Erase(4KB)SPI_TransmitOneByte(byte2);//发送24位地址SPI_TransmitOneByte(byte3);SPI_TransmitOneByte(byte4);__Deselect_Flash();	//CS=1Flash_Wait_Busy();	//大约30ms
}// 检查寄存器SR1的BUSY位,直到BUSY位为0
uint32_t Flash_Wait_Busy(void)
{   uint8_t	SR1 = 0;uint32_t  delay = 0;SR1=Flash_ReadSR1();	    //读取状态寄存器SR1while((SR1 & 0x01)==0x01){HAL_Delay(1);delay++;SR1 = Flash_ReadSR1();  //读取状态寄存器SR1}return delay;
}// 进入掉电模式
// Command=0xB9: Power Down
void Flash_PowerDown(void)
{ __Select_Flash();	//CS=0SPI_TransmitOneByte(0xB9);	//Command=0xB9: Power Down__Deselect_Flash();	//CS=1HAL_Delay(1);		//等待TPD
}   // 唤醒
// Command=0xAB: Release Power Down
void Flash_WakeUp(void)
{  __Select_Flash();	//CS=0SPI_TransmitOneByte(0xAB);	//Command=0xAB: Release Power Down__Deselect_Flash();	//CS=1HAL_Delay(1);     	//等待TRES1
}

2、KEY_LED

        修改参考文章里keyled.h关于指示灯的定义,其它不变:

#ifdef LED1_Pin	 	//LED1的控制#define LED1_Toggle()	HAL_GPIO_TogglePin(LED1_GPIO_Port,LED1_Pin)					//输出翻转#define LED1_ON()		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_RESET) 	//输出0,亮#define LED1_OFF() 		HAL_GPIO_WritePin(LED1_GPIO_Port,LED1_Pin,GPIO_PIN_SET)		//输出1,灭
#endif#ifdef LED2_Pin 	//LED2的控制#define LED2_Toggle()	HAL_GPIO_TogglePin(LED2_GPIO_Port,LED2_Pin)					//输出翻转#define LED2_ON()		HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_RESET)	//输出0,亮#define LED2_OFF()		HAL_GPIO_WritePin(LED2_GPIO_Port,LED2_Pin,GPIO_PIN_SET)		//输出1,灭
#endif
//新增LED3、LED4
#ifdef LED3_Pin 	//LED3的控制#define LED3_Toggle()	HAL_GPIO_TogglePin(LED3_GPIO_Port,LED3_Pin)					//输出翻转#define LED3_ON()		HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_RESET)	//输出0,亮#define LED3_OFF()		HAL_GPIO_WritePin(LED3_GPIO_Port,LED3_Pin,GPIO_PIN_SET)		//输出1,灭
#endif#ifdef LED4_Pin 	//LED4的控制#define LED4_Toggle()	HAL_GPIO_TogglePin(LED4_GPIO_Port,LED4_Pin)					//输出翻转#define LED4_ON()		HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_RESET)	//输出0,亮#define LED4_OFF()		HAL_GPIO_WritePin(LED4_GPIO_Port,LED4_Pin,GPIO_PIN_SET)		//输出1,灭
#endif

3、spi.h

/* USER CODE BEGIN Private defines */
void Flash_TestReadStatus(void);
void Flash_TestWrite(void);
void Flash_TestRead(void);/* USER CODE END Private defines */

4、spi.c

/* USER CODE BEGIN 0 */
#include "w25flash.h"
#include <string.h>	//用到函数strlen()
#include <stdio.h>
/* USER CODE END 0 */
/* USER CODE BEGIN 1 */
//读取DeviceID,状态寄存器 SR1和SR2
void Flash_TestReadStatus(void)
{
//读DeviceID,SR1,SR2uint16_t devID = Flash_ReadID();	//读取器件IDprintf("Device ID = %X\r\n",devID);	//大写的十六进制printf("The chip is: ");switch (devID){case 0xEF14:printf("W25Q16BV \r\n");break;case 0xEF16:printf("W25Q64JV \r\n");break;case 0xEF17:printf("W25Q128JV \r\n");break;case 0xC817:printf("GD25Q128 \r\n");break;case 0x1C17:printf("EN25Q128 \r\n");break;case 0x2018:printf("N25Q128 \r\n");break;case 0x2017:printf("XM25QH128 \r\n");break;case 0xA117:printf("FM25Q128 \r\n");break;default:printf("Unknown type \r\n");}uint8_t SR1 = Flash_ReadSR1();		//Read SR1=0x00printf("Status Reg1 = %X\r\n",SR1);	//Hex显示uint8_t SR2 = Flash_ReadSR2();		//Read SR2=0x00printf("Status Reg2 = %X\r\n",SR2);	//Hex显示
}//测试写入Page0 和Page1
//注意:一个Page写入之前必须是被擦除过的,写入之后就不能再重复写��??
void Flash_TestWrite(void)
{uint8_t blobkNo = 0;uint16_t sectorNo = 0;uint16_t pageNo = 0;uint32_t memAddress = 0;//写入Page0 两个stringmemAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);	//Page0 addressuint8_t	bufStr1[] = "Hello from beginning";uint16_t len = 1+strlen((char*)bufStr1); 		//Include'\0'Flash_WriteInPage(memAddress,bufStr1,len);   	//Write data at the beginning of Page0.printf("Write in Page0:0 %s\r\n",bufStr1);uint8_t	bufStr2[] = "Hello in page";len = 1+strlen((char*)bufStr2); 				//include '\0'Flash_WriteInPage(memAddress+100,bufStr2,len);	//Offset address 100 within Page0printf("Write in Page0:100 %s\r\n",bufStr2);		//display string//写入Page 1uint8_t	bufPage[FLASH_PAGE_SIZE];	//EN25Q_PAGE_SIZE=256for (uint16_t i=0;i<FLASH_PAGE_SIZE; i++)bufPage[i] = i;					//准备数据pageNo = 1; 						//Page 1memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo, pageNo);//page1 addressFlash_WriteInPage(memAddress, bufPage, FLASH_PAGE_SIZE);//写一个Pageprintf("Write 0-255 in Page1 \r\n");
}//test for reading Page0 and Page1
void Flash_TestRead(void)
{uint8_t	blobkNo = 0;uint16_t sectorNo = 0;uint16_t pageNo = 0;
//读取Page 0uint8_t bufStr[50];							//Data read from Page0uint32_t memAddress = Flash_Addr_byBlockSectorPage(blobkNo,sectorNo,pageNo);Flash_ReadBytes(memAddress,bufStr,50);		//read 50 Bytesprintf("Read from Page0:0 %s\r\n",(char*)bufStr);	//display stringFlash_ReadBytes(memAddress+10,bufStr,50);	//地址偏移100后的50个字节printf("Read from Page0:100 %s\r\n",bufStr);//display string//读取Page 1uint8_t	randData = 0;pageNo = 1;memAddress = Flash_Addr_byBlockSectorPage(blobkNo, sectorNo,pageNo);randData = Flash_ReadOneByte(memAddress+12);//读取1个字节数据,页内地址偏移12printf("Page1[12] = %d\r\n",randData);randData =Flash_ReadOneByte(memAddress+136);//页内地址偏移136printf("Page1[136] = %d\r\n",randData);randData =Flash_ReadOneByte(memAddress+210);//页内地址偏移210printf("Page1[210] = %d\r\n",randData);
}
/* USER CODE END 1 */

5、main.c

/* USER CODE BEGIN Includes */
#include "w25flash.h"	//W25Q16驱动程序.h
#include "keyled.h"
#include <stdio.h>
/* USER CODE END Includes */
  /* USER CODE BEGIN 2 *///Read and display the values ​​of DeviceID, status register SR1 and SR2.Flash_TestReadStatus();// MCU output low level LED light is onLED1_OFF();LED2_OFF();LED3_OFF();LED4_OFF();/* USER CODE END 2 */
/* USER CODE BEGIN 3 */KEYS curKey=ScanPressedKey(KEY_WAIT_ALWAYS);switch(curKey){case KEY_UP:	        //S2printf("Erasing chip, about 30sec...\r\n");Flash_EraseChip();printf("Chip is erased.\r\n");LED1_ON();LED2_OFF();LED3_OFF();LED4_OFF();break;case KEY_DOWN:	    //S3printf("Erasing Block 0(256 pages)...\r\n");uint32_t globalAddr=0x000000;Flash_EraseBlock64K(globalAddr);printf("Block 0 is erased.\r\n");LED1_OFF();LED2_ON();LED3_OFF();LED4_OFF();break;case KEY_LEFT:		//S4Flash_TestWrite();	//测试写入Page0 and Page1LED1_OFF();LED2_OFF();LED3_ON();LED4_OFF();break;case KEY_RIGHT:		//S5Flash_TestRead();	//测试读取Page0 and Page1LED1_OFF();LED2_OFF();LED3_OFF();LED4_ON();break;default:LED1_OFF();LED2_OFF();LED3_OFF();LED4_OFF();break;}printf("** Reselect menu or reset **\r\n");HAL_Delay(500);		    //Delay to eliminate key jitter.}/* USER CODE END 3 */
/* USER CODE BEGIN 4 *///串口打印
int __io_putchar(int ch)
{HAL_UART_Transmit(&huart6, (uint8_t*)&ch, 1, 0xFFFF);return ch;
}
/* USER CODE END 4 */

三、下载与运行

       下载后,自动打印芯片ID为EF14,并打印状态寄存器为0;

        按下S2键,对芯片整体格式化,并打印;

        按下S3键,对BLOCK0格式化,并打印;

        按下S4键, 对芯片进行写操作,并打印;

        按下S5键,对芯片进行读操作,并打印;

        上述操作结果,如下图:

相关文章:

细说STM32F407单片机轮询方式读写SPI FLASH W25Q16BV

目录 一、工程配置 1、时钟、DEBUG 2、GPIO 3、SPI2 4、USART6 5、NVIC 二、软件设计 1、FALSH &#xff08;1&#xff09;w25flash.h &#xff08;2&#xff09; w25flash.c 1&#xff09;W25Q16基本操作指令 2&#xff09;计算地址的辅助功能函数 3&#xff09;器…...

HTMLCSS:惊!3D 折叠按钮

这段代码创建了一个具有 3D 效果和动画的按钮&#xff0c;按钮上有 SVG 图标和文本。按钮在鼠标悬停时会显示一个漂浮点动画&#xff0c;图标会消失并显示一个线条动画。这种效果适用于吸引用户注意并提供视觉反馈。按钮的折叠效果和背景渐变增加了页面的美观性。 演示效果 HT…...

如何更好的进行时间管理

先想一下我们想要做的事情&#xff0c;然后拿出Excel表格将这些事情记录下来&#xff0c;我们把它叫做任务对这些任务按照重要性&#xff0c;紧急程度进行排序&#xff0c;拿出表格中的前六个任务&#xff0c;就是今天要做的任务新建另一张excel表格&#xff0c;表格的一列为时…...

我在华为的安全日常

在华为工作了数年后&#xff0c;我养成了一个习惯&#xff1a;每次离开座位&#xff0c;即便是去卫生间&#xff0c;我也会条件反射地锁屏电脑。晚上回到家&#xff0c;躺在床上&#xff0c;脑海中偶尔会闪过一丝疑虑&#xff1a;办公室的门窗是否关好&#xff1f;虽然这种担忧…...

for媒体打破智能座舱体验同质化,斑马智行荣获“华舆奖”优秀创

打破智能座舱体验同质化&#xff0c;斑马智行荣获“华舆奖”优秀创新生态伙伴 12月12日&#xff0c;消费者洞察与市场研究机构J.D. Power|君迪与同济大学 HVR Lab&#xff08;人车关系实验室&#xff09;共同发布了 2024 中国智能座舱的研究洞察&#xff0c;并公布了华舆奖中国…...

自己搭建专属AI:Llama大模型私有化部署

前言 AI新时代&#xff0c;提高了生产力且能帮助用户快速解答问题&#xff0c;现在用的比较多的是Openai、Claude&#xff0c;为了保证个人隐私数据&#xff0c;所以尝试本地&#xff08;Mac M3&#xff09;搭建Llama模型进行沟通。 Gpt4all 安装比较简单&#xff0c;根据 G…...

芯片Tapeout power signoff 之IR Drop Redhawk Ploc文件格式及其意义

数字IC后端工程师在芯片流程最后阶段都会使用redhawk或voltus进行设计的IR Drop功耗signoff分析。必须确保静态&#xff0c;动态ir drop都符合signoff标准。 在做redhawk ir drop分析前&#xff0c;我们需要提供一个redhawk ploc供电点坐标。 数字IC设计后端实现前期预防IR D…...

[机器学习]sklearn入门指南(1)

简介 scikit-learn&#xff08;简称sklearn&#xff09;是一个开源的Python机器学习库&#xff0c;它提供了简单而高效的工具用于数据挖掘和数据分析&#xff0c;并且拥有一个活跃的开发社区。它建立在NumPy、SciPy和matplotlib这些科学计算库之上&#xff0c;旨在提供一致且可…...

GitCode 光引计划投稿 | GoIoT:开源分布式物联网开发平台

GoIoT 是基于Gin 的开源分布式物联网&#xff08;IoT&#xff09;开发平台&#xff0c;用于快速开发&#xff0c;部署物联设备接入项目&#xff0c;是一套涵盖数据生产、数据使用和数据展示的解决方案。 GoIoT 开发平台&#xff0c;它是一个企业级物联网平台解决方案&#xff…...

【R语言遥感技术】“R+遥感”的水环境综合评价方法

R语言在遥感领域中是一个强大的工具&#xff0c;它提供了一系列的功能和优势&#xff0c;使得遥感数据的分析和应用更加高效和灵活。以下是R语言在遥感中的具体应用&#xff1a; 数据处理&#xff1a;R语言可以处理和清洗遥感数据&#xff0c;包括数据转换、滤波处理、去噪和数…...

QT--信号与槽机制

什么是信号与槽&#xff1f; 在 Qt 中&#xff0c;信号与槽是一种用于对象间通信的机制。它使得一个对象可以通知其他对象某个事件的发生&#xff0c;而不需要直接知道这些对象的具体实现。这种机制非常适合事件驱动的编程模型&#xff0c;如用户界面交互。 1. 信号&#xff…...

Windbg常用命令

禁止垃圾信息 ed nt!Kd_STORMINIPORT_Mask 0 ed nt!Kd_SXS_Mask 0 ed nt!Kd_FUSION_Mask 0 命令大全: 命令 - Windows drivers | Microsoft Learn .reload /f 重新加载符号表 常用命令 继续执行: g单步过/步入: p, t退出: q查看调用堆栈: k, kb列出模块: lm, lml设置断…...

YOLO11改进-模块-引入多分支卷积InceptionDepthwiseConvolution(IDC) 解决多尺度、小目标

YOLOv11 的设计目标是通过高效的网络结构&#xff0c;在保证准确率的前提下&#xff0c;最大化推理速度。传统卷积操作虽然能够捕获局部信息&#xff0c;但在处理大规模场景或复杂背景时&#xff0c;较小的感受野可能导致细节信息不足&#xff0c;影响模型的检测能力。为了解决…...

国标GB28181-2022平台EasyGBS:安防监控中P2P的穿透方法

在安防监控领域&#xff0c;P2P技术因其去中心化的特性而受到关注&#xff0c;尤其是在远程视频监控和数据传输方面。P2P技术允许设备之间直接通信&#xff0c;无需通过中央服务器&#xff0c;这在提高效率和降低成本方面具有明显优势。然而&#xff0c;P2P技术在实际应用中也面…...

C++软件设计模式之外观(Facade)模式

C软件设计模式中的外观&#xff08;Facade&#xff09;模式 1. 外观模式的定义 外观模式&#xff08;Facade Pattern&#xff09;是一种结构型设计模式&#xff0c;它为一个复杂的子系统提供一个简化的接口。外观模式通过一个统一的接口来访问子系统的多个组成部分&#xff0…...

Spring Boot 项目创建

创建一个新项目&#xff1a; 打开 Spring Initializr 网址&#xff1a;https://start.spring.io/ &#xff0c;然后创建一个新项目&#xff1a; springboot3.3.5_jdk17&#xff1a; Project&#xff08;Maven&#xff09;编程语言&#xff08;Java 17&#xff09;Spring Boo…...

SharpDX 从入门到精通:全面学习指南

摘要&#xff1a; 本文旨在为想要深入学习 SharpDX 的开发者提供一份全面的指南。从 SharpDX 的基础概念入手&#xff0c;逐步深入探讨其在不同场景下的应用&#xff0c;包括图形渲染、音频处理等&#xff0c;并结合大量详细的代码案例帮助读者更好地理解和掌握 SharpDX 的使用…...

【Web】2024“国城杯”网络安全挑战大赛决赛题解(全)

最近在忙联通的安全准入测试&#xff0c;很少有时间看CTF了&#xff0c;今晚抽点时间回顾下上周线下的题(期末还没开始复习&#x1f622;) 感觉做渗透测试一半的时间在和甲方掰扯&水垃圾洞&#xff0c;没啥惊喜感&#xff0c;还是CTF有意思 目录 Mountain ez_zhuawa 图…...

操作系统(24)提高磁盘I/O速度的途径

前言 操作系统提高磁盘I/O速度的途径多种多样&#xff0c;这些途径旨在减少磁盘访问的延迟和开销&#xff0c;提高数据传输的效率。 一、磁盘高速缓存&#xff08;Disk Cache&#xff09; 磁盘高速缓存是一种在内存中为磁盘数据设置的缓冲区&#xff0c;用于存储磁盘中某些盘块…...

en3d 部署笔记

目录 依赖项: Nvdiffrast 编译代码和frpc_linux_amd64 下载地址: tiny-cuda-nn 安装 ICON算法库依赖 icon依赖 kaolin infer_normal_fixpose 解决 报错了,推荐的安装方法: kaolin测试: ICON依赖项 requirements.txt 改进 voxelize_cuda 安装ok 运行后: 修改代…...

c++类型判断和获取原始类型

std::traits学习 类型判断和退化&#xff08;获取原始类型&#xff09;的原理就是利用模板的特例化。根据调用模板的特例化&#xff0c;在特例化模板中实现判断的逻辑或者退化的逻辑。 一、类型判断 判断整型数据的模板类 #include <iostream> namespace zk {templa…...

医疗行业 UI 设计系列合集(一):精准定位

在当今数字化时代&#xff0c;医疗行业与信息技术的融合日益紧密&#xff0c;UI 设计在其中扮演着至关重要的角色。精准定位的 UI 设计能够显著提升医疗产品与服务的用户体验&#xff0c;进而对医疗效果和患者满意度产生积极影响。 一、医疗行业 UI 设计的重要性概述 医疗行业…...

EasyExcel停更,FastExcel接力

11月6日消息&#xff0c;阿里巴巴旗下的Java Excel工具库EasyExcel近日宣布&#xff0c;将停止更新&#xff0c;未来将逐步进入维护模式&#xff0c;将继续修复Bug&#xff0c;但不再主动新增功能。 EasyExcel以其快速、简洁和解决大文件内存溢出的能力而著称&#xff0c;官方…...

java agent的使用【通俗易懂版】

一、静态代理Agent 1&#xff0e;生成Agent的jar包 &#xff08;1&#xff09;创建Agent项目&#xff0c;引入javassist.jar包 &#xff08;2&#xff09;编写premain方法 import java.lang.instrument.Instrumentation;public class Agent1 {public static void premain(Stri…...

010 Qt_输入类控件(LineEdit、TextEdit、ComboBox、SpinBox、DateTimeEdit、Dial、Slider)

文章目录 前言一、QLineEdit1.简介2.常见属性及说明3.重要信号及说明4.示例一&#xff1a;用户登录界面5.示例二&#xff1a;验证两次输入的密码是否一致显示密码 二、TextEdit1.简介2.常见属性及说明3.重要信号及说明4.示例一&#xff1a;获取多行输入框的内容5.示例二&#x…...

C++设计模式:享元模式 (附文字处理系统中的字符对象案例)

什么是享元模式&#xff1f; 享元模式是一个非常实用的结构型设计模式&#xff0c;它的主要目的是节省内存&#xff0c;尤其在需要创建大量相似对象时。 通俗解释&#xff1a; 想象我们在写一本书&#xff0c;每个字母都需要表示出来。如果每个字母都单独用对象表示&#xff…...

机器学习之 KNN 算法

一、引言 在机器学习领域中&#xff0c;K 近邻&#xff08;K-Nearest Neighbors&#xff0c;KNN&#xff09;算法是一种简单而有效的分类和回归算法。它的基本思想是根据数据点之间的距离来确定它们的相似性&#xff0c;并根据其最近的邻居的类别或数值来预测新数据点的类别或…...

矩阵:Input-Output Interpretation of Matrices (中英双语)

矩阵的输入-输出解释&#xff1a;深入理解与应用 在线性代数中&#xff0c;矩阵与向量的乘积 ( y A x y Ax yAx ) 是一个极为重要的关系。通过这一公式&#xff0c;我们可以将矩阵 ( A A A ) 看作一个将输入向量 ( x x x ) 映射到输出向量 ( y y y ) 的线性变换。在这种…...

ctfhub技能树——disable_functions

LD_PRELOAD 来到首页发现有一句话直接就可以用蚁剑连接 根目录里有/flag但是不能看;命令也被ban了就需要绕过了 绕过工具在插件市场就可以下载 如果进不去的话 项目地址: #本地仓库;插件存放 antSword\antData\plugins 绕过选择 上传后我们点进去可以看到多了一个绕过的文件;…...

Web3.0安全开发实践:探索比特币DeFi生态中的PSBT

近年来&#xff0c;部分签名比特币交易&#xff08;PSBT&#xff09;在比特币生态系统中获得了显著关注。随着如Ordinal和基于铭文的资产等创新的兴起&#xff0c;安全的多方签名和复杂交易的需求不断增加&#xff0c;这使得PSBT成为应对比特币生态不断发展中不可或缺的工具。 …...

【Yonghong 企业日常问题 06】上传的文件不在白名单,修改allow.jar.digest属性添加允许上传的文件SH256值?

文章目录 前言问题描述问题分析问题解决1.允许所有用户上传驱动文件2.如果是想只上传白名单的驱动 前言 该方法适合永洪BI系列产品&#xff0c;包括不限于vividime desktop&#xff0c;vividime z-suit&#xff0c;vividime x-suit产品。 问题描述 当我们连接数据源的时候&a…...

Lecture 6 Isolation System Call Entry

文章目录 一 重要的函数清单1 write(user/usys.s) 一 usertrap函数(C code) Lecture6 Isolation & System Call Entry视频链接 对应XV6 Book Chapter 4 Traps and device drivers 一 重要的函数清单 1 write(user/usys.s) .global write write:li a7, SYS_writeecallret…...

重温设计模式----装饰模式

文章目录 装饰模式定义UML 图其主要优点包括&#xff1a;装饰模式的主要角色有&#xff1a;C 代码示例总结 装饰模式定义 动态的给一个对象添加一些额外的职责&#xff0c;就增加功能来说&#xff0c;装饰模式必生成子类更加灵活 装饰模式&#xff08;Decorator Pattern&…...

图像处理-Ch2-空间域的图像增强

Ch2 空间域的图像增强 文章目录 Ch2 空间域的图像增强Background灰度变换函数(Gray-level Transformation)对数变换(Logarithmic)幂律变换(Power-Law)分段线性变换函数(Piecewise-Linear)对比度拉伸(Contrast-Stretching)灰度级分层(Gray-level Slicing) 直方图处理(Histogram …...

uniapp Native.js原生arr插件服务发送广播到uniapp页面中

前言 最近搞了个设备&#xff0c;需求是读取m1卡&#xff0c;厂家给了个安卓原生demo&#xff0c;接入arr插件如下&#xff0c;接入后发现还是少了一部分代码&#xff0c;设备服务调起后触发刷卡无法发送到uniapp里。 中间是一些踩坑记录&#xff0c;最后面是解决办法&#xf…...

重温设计模式--1、组合模式

文章目录 1 、组合模式&#xff08;Composite Pattern&#xff09;概述2. 组合模式的结构3. C 代码示例4. C示例代码25 .应用场景 1 、组合模式&#xff08;Composite Pattern&#xff09;概述 定义&#xff1a;组合模式是一种结构型设计模式&#xff0c;它允许你将对象组合成…...

关于鸿蒙架构feature

鸿蒙feature层模块架构 model&#xff1a;定义数据类型&#xff0c;进行接口请求 view&#xff1a;视图层 写UI viewModel&#xff1a;控制层 关于逻辑和请求调用 page页...

CentOS下,离线安装vscode的步骤;

前置条件&#xff1a; 1.CentOS7; 步骤&#xff1a; 1.下载vscode指定版本&#xff0c;例如&#xff1b; 例如 code-1.83.1-1696982959.el7.x86_64.rpm 2.使用下面命令&#xff1a; sudo rpm -ivh code-1.83.1-1696982959.el7.x86_64.rpm 其他&#xff1a; 卸载vscode的命…...

.NET周刊【12月第3期 2024-12-15】

国内文章 重磅推出 Sdcb Chats&#xff1a;一个全新的开源大语言模型前端 https://www.cnblogs.com/sdcb/p/18597030/sdcb-chats-intro Sdcb Chats是一个新推出的开源大语言模型前端&#xff0c;旨在提升用户交互体验&#xff0c;并填补市场上基于.NET的前端空白。它引入树状…...

操作系统(23)外存的存储空间的管理

一、外存的基本概念与特点 定义&#xff1a;外存&#xff0c;也称为辅助存储器&#xff0c;是计算机系统中用于长期存储数据的设备&#xff0c;如硬盘、光盘、U盘等。与内存相比&#xff0c;外存的存储容量大、成本低&#xff0c;但访问速度相对较慢。特点&#xff1a;外存能够…...

vue3中多层级路由缓存失效问题

问题现象&#xff1a; 在项目中路由嵌套了超过两层后&#xff0c;使用keep-alive对路由进行页面的缓存&#xff0c;发现并不能生效。 使用的路由结构&#xff1a; // 一级路由path: menu1,component: () > import(/views/demos/nested/menu1/index), // Parent router-vie…...

Kerberoasting 离线爆破攻击

当域用户请求某个域内服务后&#xff0c;kdc 通常会返回一个加密的 st 服务票据&#xff0c;此 st 服务票据被服务 hash 加密&#xff0c;当我们将使用密码字典派生的多个 hash 值来尝试解密 st 服务票据&#xff0c;如果能够揭秘成功&#xff0c;则说明字典中存在目标服务账号…...

无人机双目视觉鲁棒定位方法!

无人机双目视觉鲁棒定位方法是一种先进的定位技术&#xff0c;它利用两个摄像头&#xff08;即双目相机&#xff09;模拟人的视觉系统&#xff0c;通过视差来确定物体的位置。这种方法在无人机定位领域具有广泛的应用前景&#xff0c;特别是在GPS信号拒止或弱纹理环境中&#x…...

vulnhub靶场——Log4j2

第一步:搭建靶场环境 #开启环境 cd vulhub/log4j/CVE-2021-44228 docker-compose up -d 来到网站首页 第二步:搭建一个dnslog平台上获取我们注入的效果 第三步:发现 /solr/admin/cores?action 这里有个参数可以传 我们可以看到留下了访问记录并且前面的参数被执行后给我们回…...

第十六章 C++ 字符串

C 字符串 C 提供了以下两种类型的字符串表示形式&#xff1a; C 风格字符串C 引入的 string 类类型 C 风格字符串 C 风格的字符串起源于 C 语言&#xff0c;并在 C 中继续得到支持。字符串实际上是使用 null 字符 终止的一维字符数组。因此&#xff0c;一个以 null 结尾的…...

centos权限大集合,覆盖多种权限类型,解惑权限后有“. + t s”问题!

在 CentOS 系统中&#xff0c;权限管理是操作系统的核心功能之一&#xff0c;确保不同用户和进程对文件、目录以及设备的访问被合理控制。 权限系统主要包括传统的 Unix 权限模型、特殊权限&#xff08;SetUID、SetGID、Sticky 位&#xff09;和更精细的访问控制列表&#xff…...

【k8s】访问etcd

1. 配置 export.sh export ETCDCTL_API3 # Kubernetes 1.13 使用 API v3 export ETCDCTL_ENDPOINTShttps://[2023:145:246:270::3]:2379 # etcd API endpoint&#xff0c;通常为集群内的 etcd 服务地址 export ETCDCTL_CACERT/etc/kubernetes/certs/ca.crt # CA 证书文件 …...

【教程宝典】基于“遥感+”蓝碳储量估算、红树林信息提取实践技术应用与科研论文写作

“遥感”助推蓝碳生态系统碳储量调查简介(1)蓝碳生态系统碳储量研究背景 红树林、海草床和盐沼是海岸带最具固碳效率的三大生态系统&#xff0c;统称为“蓝色碳汇”。虽然这三类生态系统的覆盖面积不到海床的0.5%&#xff0c;植物生物量只占陆地植物生物量的0.05%&#xff0c;…...

运动健康中的实体和关系

1. 实体类别 1.1 个人健康相关实体 个人&#xff08;Person&#xff09;&#xff1a;参与体育活动的个体&#xff0c;如运动员、健身爱好者、患者等。健康状况&#xff08;HealthStatus&#xff09;&#xff1a;描述个人的身体状态&#xff0c;如体重、血压、心率、身体质量指…...

【进阶编程】MVC和MVVM实现前后端分离的实现

在 WPF 开发中&#xff0c;通常使用 MVVM&#xff08;Model-View-ViewModel&#xff09;架构来分离视图和业务逻辑&#xff0c;但在某些情况下&#xff0c;你可能希望将 MVC&#xff08;Model-View-Controller&#xff09;模式与 MVVM 结合使用。这种结合有时是为了兼顾不同的架…...