STM32CubeIDE(SPI读写Flash)

news2024/11/26 9:51:46

参考:①正点原子MINI教程②STM32F103配合STM32CubeMX实现SPI读写flash_stm32f103 cube spi_zerfew的博客-CSDN博客

目录

一、理论知识

1、SPI特征

2、SPI框图

3、SPI的工作模式

4、W25Q64

4.1 NOR FLASH 的特性

4.2 W25Q64芯片引脚图

 4.3 NOR FLASH 工作时序

5、SPI主要结构体

二、STM32Cube配置

1、片选脚PA2

 2、SPI配置

三、代码编写

1、函数声明

2、函数调用

3、函数定义


一、理论知识

1、SPI特征

SPI 是英语 Serial Peripheral interface 缩写,顾名思义就是串行外围设备接口。 SPI 通信协
议是 Motorola 公司首先在其 MC68HCXX 系列处理器上定义的。 SPI 接口是一种高速的全双工
同步的通信总线,已经广泛应用在众多 MCU、存储芯片、 AD 转换器和 LCD 之间。 大部分
STM32 是 有 3 个 SPI 接口。

● 3线全双工同步传输
● 带或不带第三根双向数据线的双线单工同步传输
● 8或16位传输帧格式选择
● 主或从操作
● 支持多主模式
● 8个主模式波特率预分频系数(最大为fPCLK/2)
● 从模式频率 (最大为fPCLK/2)
● 主模式和从模式的快速通信
● 主模式和从模式下均可以由软件或硬件进行NSS管理:主/从操作模式的动态改变
● 可编程的时钟极性和相位
● 可编程的数据顺序, MSB在前或LSB在前
● 可触发中断的专用发送和接收标志
● SPI总线忙状态标志
● 支持可靠通信的硬件CRC
─ 在发送模式下, CRC值可以被作为最后一个字节发送
─ 在全双工模式中对接收到的最后一个字节自动进行CRC校验
● 可触发中断的主模式故障、过载以及CRC错误标志
● 支持DMA功能的1字节发送和接收缓冲器:产生发送和接受请求

2、SPI框图

①SPI 的引脚信息:

MISO(Master In / Slave Out)主设备数据输入,从设备数据输出。
MOSI(Master Out / Slave In)主设备数据输出,从设备数据输入。
SCLK(Serial Clock)时钟信号,由主设备产生。
CS(Chip Select)从设备片选信号,由主设备产生。

②SPI 的工作原理:

在主机和从机都有一个串行移位寄存器,主机通过向它的 SPI 串行寄存
器写入一个字节来发起一次传输。串行移位寄存器通过 MOSI 信号线将字节传送给从机,从机
也将自己的串行移位寄存器中的内容通过 MISO 信号线返回给主机。这样,两个移位寄存器中
的内容就被交换。外设的写操作和读操作是同步完成的。如果只是进行写操作,主机只需忽略
接收到的字节。反之,若主机要读取从机的一个字节,就必须发送一个空字节引发从机传输。
 

③SPI 的传输方式:

SPI 总线具有三种传输方式:全双工、单工以及半双工传输方式。
全双工通信,就是在任何时刻,主机与从机之间都可以同时进行数据的发送和接收。
单工通信,就是在同一时刻,只有一个传输的方向,发送或者是接收。
半双工通信,就是在同一时刻,只能为一个方向传输数据。

3、SPI的工作模式

CPOL,详称 Clock Polarity,就是时钟极性,当主从机没有数据传输的时候即空闲状态,
SCL 线的电平状态
,假如空闲状态是高电平, CPOL=1;若空闲状态时低电平,那么 CPOL = 0。
CPHA,详称 Clock Phase,就是时钟相位。同步通信时,数据的变化和采样都是在时钟边沿上进行的,每一个时钟周期都会有上升沿和下降沿两个边沿,那么数据的变化和采样就分别安排在两个不同的边沿,由于数据在产生和到它稳定是需要一定的时间,那么假如我们在第 1 个边沿信号把数据输出了,从机只能从第 2 个边沿信号去采样这个数据。CPHA 实质指的是数据的采样时刻, CPHA = 0 的情况就表示数据的采样是从第 1 个边沿信号上即奇数边沿,具体是上升沿还是下降沿的问题,是由 CPOL 决定的。CPOL=0,有两种情况:一是 CS 使能的边沿,二是上一帧数据的最后一个时钟沿。CPHA=1 的情况就是表示数据采样是从第 2 个边沿即偶数边沿,它的边沿极性要注意一点,不是和上面 CPHA=0 一样的边沿情况。前面的是奇数边沿采样数据,从 SCL 空闲状态的直接跳变,空闲状态是高电平,那么它就是下降沿,反之就是上升沿。由于 CPHA=1 是偶数边沿采样,所以需要根据偶数边沿判断,假如第一个边沿即奇数边沿是下降沿,那么偶数边沿的边
沿极性就是上升沿。 SPI 分成了 4 种模式。使用比较多的是模式 0 和模式 3。

模式0:

 CPOL=0&&CPHA=0 的时序,上图 就是串行时钟的奇数边沿上升沿采样的情况,首先由于配置了 CPOL=0,可以看到当数据未发送或者发送完毕, SCL 的状态是低电平,再者 CPHA=0 即是奇数边沿采集。所以传输的数据会在奇数边沿上升沿被采集,MOSI 和 MISO 数据的有效信号需要在 SCK 奇数边沿保持稳定且被采样,在非采样时刻MOSI 和 MISO 的有效信号才发生变化。

模式3:

 上图是 CPOL=1&&CPHA=1 的情形,可以看到未发送数据和发送数据完毕, SCL
的状态是高电平,奇数边沿的边沿极性是上升沿,偶数边沿的边沿极性是下降沿。因为
CPHA=1, 所以数据在偶数边沿上升沿被采样。在奇数边沿的时候 MOSI 和 MISO 会发生变化,
在偶数边沿时候是稳定的。

SPI 控制寄存器 1(SPI_CR1)
SPI 状态寄存器(SPI_SR)
SPI 数据寄存器(SPI_DR)

4、W25Q64

4.1 NOR FLASH 的特性

W25Q64 是一款大容量 SPI FLASH 产品,其容量为 8M。它将 8M 字节的容量分为 128 个
块(Block),每一个块大小为 64K 字节,每个块又分为 16 个扇区(Sector),每一个扇区 16 页,
每页 256 个字节,即每个扇区 4K 字节。

W25Q64 的最小擦除单位为一个扇区,也就是每次必
须擦除 4K 个字节。 这样我们需要给 W25Q64 开辟一个至少 4K 的缓存区,这样对 SRAM 要求
比较高,要求芯片必须有 4K 以上的 SRAM 才能很好的操作。W25Q64 的擦写周期多达 10W 次,具有 20 年的数据保存期限,支持电压为 2.7~3.6V,W25Q64 支持标准的 SPI,还支持双输出/四输出的 SPI,最大 SPI 时钟可以到 80Mhz(双输出时相当于 160Mhz,四输出时相当于 320Mhz)。

读flash很简单,仅需要执行spi对应的指令即可;
写flash较为复杂,首先要知道,flash不支持覆盖写,仅支持擦除后再写,原因是flash的写操作只能将1变为0,不能将0变为1,若想将0变为1则只能通过擦除操作,所以写flash时,如果对应的位置值不是全1,则需要先执行擦除操作,再执行写操作。

W25Q64的最小擦除单位为一个扇区,也就是每次必须擦除 4K 个字节。所以在程序中定义一个至少4K的数组变量,用于保存待擦除扇区的内容。

4.2 W25Q64芯片引脚图

芯片引脚连接如下: CS 即片选信号输入,低电平有效; DO 是 MISO 引脚, 在 CLK 管脚
的下降沿输出数据; WP 是写保护管脚, 高电平可读可写,低电平仅仅可读; DI 是 MOSI 引脚,
主机发送的数据、地址和命令从 SI 引脚输入到芯片内部,在 CLK 管脚的上升沿捕获捕获数据;
CLK 是串行时钟引脚,为输入输出提供时钟脉冲; HOLD 是保持管脚,低电平有效。
STM32F103 通过 SPI 总线连接到 W25Q64 对应的引脚即可启动数据传输。

 4.3 NOR FLASH 工作时序

①读操作时序

 读数据指令是 03H,可以读出一个字节或者多个字节。发起读操作时,先把
CS 片选管脚拉低,然后通过 MOSI 引脚把 03H 发送芯片,之后再发送要读取的 24 位地址,这
些数据在 CLK 上升沿时采样。芯片接收完 24 位地址之后,就会把相对应地址的数据在 CLK
引脚下降沿从 MISO 引脚发送出去。从图中可以看出只要 CLK 一直在工作,那么通过一条读
指令就可以把整个芯片存储区的数据读出来。当主机把 CS 引脚拉高,数据传输停止

②页写时序

 在发送页写指令之前,需要先发送“写使能”指令。然后主机拉低 CS 引脚,然后通过
MOSI 引脚把 02H 发送到芯片,接着发送 24 位地址,最后你就可以发送你需要写的字节数据
到芯片。完成数据写入之后,需要拉高 CS 引脚,停止数据传输。

③扇区擦除时序

 扇区擦除指的是将一个扇区擦除, W25Q64 的扇区大小是 4K 字节。擦除扇区后,扇区的位全置 1,即扇区字节为 FFh。同样的,在执行扇区擦除之前,需要先执行写使能指令。这里需要注意的是当前 SPI 总线的状态,假如总线状态是 BUSY,那么这个扇区擦除是无效的,所以在拉低 CS 引脚准备发送数据前,需要先要确定 SPI 总线的状态,这就需要执行读状态寄存器指令,读取状态寄存器的 BUSY 位,需要等待 BUSY 位为 0,才可以执行擦除工作。接着按时序图分析,主机先拉低 CS 引脚,然后通过 MOSI 引脚发送指令代码 20h 到芯片,然后接着把 24 位扇区地址发送到芯片,然后需要拉高 CS 引脚,通过读取寄存器状态等待扇区擦除操作完成。
此外还有对整个芯片进行擦除的操作,时序比扇区擦除更加简单,不用发送 24bit 地址,只需要发送指令代码 C7h 到芯片即可实现芯片的擦除。在 W25Q64 手册中还有许多种方式的读/写/擦除操作,参考 W25Q64 手册

5、SPI主要结构体

typedef struct __SPI_HandleTypeDef
{
SPI_TypeDef *Instance; /* SPI 寄存器基地址 */
SPI_InitTypeDef Init; /* SPI 通信参数 */
uint8_t *pTxBuffPtr; /* SPI 的发送缓存 */
uint16_t TxXferSize; /* SPI 的发送数据大小 */
__IO uint16_t TxXferCount; /* SPI 发送端计数器 */
uint8_t *pRxBuffPtr; /* SPI 的接收缓存 */
uint16_t RxXferSize; /* SPI 的接收数据大小 */
__IO uint16_t RxXferCount; /* SPI 接收端计数器 */
void (*RxISR)(struct __SPI_HandleTypeDef *hspi); /* SPI 的接收端中断服务函数 */
void (*TxISR)(struct __SPI_HandleTypeDef *hspi); /* SPI 的发送端中断服务函数 */
DMA_HandleTypeDef *hdmatx; /* SPI 发送参数设置(DMA) */
DMA_HandleTypeDef *hdmarx; /* SPI 接收参数设置(DMA) */
HAL_LockTypeDef Lock; /* SPI 锁对象 */
__IO HAL_SPI_StateTypeDefState; /* SPI 传输状态 */
__IO uint32_t ErrorCode; /* SPI 操作错误代码 */
} SPI_HandleTypeDef;


typedef struct
{
uint32_t Mode; /* 模式:主(SPI_MODE_MASTER)从(SPI_MODE_SLAVE) */
uint32_t Direction; /* 方向:只接收模式 单线双向通信数据模式 全双工 */
uint32_t DataSize; /* 数据帧格式: 8 位/16 位 */
uint32_t CLKPolarity; /* 时钟极性 CPOL 高/低电平 */
uint32_t CLKPhase; /* 时钟相位 奇/偶数边沿采集 */
uint32_t NSS; /* SS 信号由硬件(NSS)管脚控制还是软件控制 */
uint32_t BaudRatePrescaler; /* 设置 SPI 波特率预分频值*/
uint32_t FirstBit; /* 起始位是 MSB 还是 LSB */
uint32_t TIMode; /* 帧格式 SPI motorola 模式还是 TI 模式 */
uint32_t CRCCalculation; /* 硬件 CRC 是否使能 */
uint32_t CRCPolynomial; /* 设置 CRC 多项式*/
} SPI_InitTypeDef;

二、STM32Cube配置

1、片选脚PA2

可选为上拉模式,Reset状态即低电平状态CS片选有效

 2、SPI配置

选择全双工主机模式,设置CPOL CPHA,一般常用SPI工作模式0 和 3,CRC校验看需求开启

三、代码编写

1、函数声明

/* USER CODE BEGIN PFP */
void flash_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len);
void flash_write_page(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len);
void flash_erase(uint32_t fan_addr);
void spi_read_flash(uint8_t *pbuffer, uint32_t read_addr, uint16_t read_len);
void spi_write_flash(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len);
/* USER CODE END PFP */

2、函数调用

	/* USER CODE BEGIN 2 */
	uint8_t writeMsg[100] = "1111111111";
	uint8_t readMsg[100] = "";
	uint32_t flash_size = 8*1024*1024;

	spi_write_flash(writeMsg, flash_size - 1000, sizeof(writeMsg));
	spi_read_flash(readMsg, flash_size - 1000, sizeof(readMsg));
	/* USER CODE END 2 */

3、函数定义

/* USER CODE BEGIN 4 */
void flash_write(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len)
{
	uint16_t page_remain;  // 当前页面剩余可写字节数

	// 计算当前页剩余可写字节数
	page_remain = 256 - write_addr % 256;

	// 如果要写入的数据长度小于等于当前页剩余可写字节数,直接写入当前页
	if (write_len <= page_remain)
	{
		page_remain = write_len;
		flash_write_page(pbuffer, write_addr, page_remain);
	} else
	{
		// 如果要写入的数据长度超过当前页剩余可写字节数,需要进行分页写入
		while (1)
		{
			flash_write_page(pbuffer, write_addr, page_remain); // 写入当前页数据
			if (write_len == page_remain)
			{
				break; // 数据已全部写入
			} else
			{
				// 更新缓冲区指针、写入地址和剩余长度
				pbuffer += page_remain;
				write_addr += page_remain;
				write_len -= page_remain;

				// 根据剩余长度判断下一页要写入的字节数
				if (write_len > 256)
				{
					page_remain = 256;
				} else
				{
					page_remain = write_len;
				}
			}
		}
	}
}

void flash_write_page(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len)
{
	uint16_t i = 0;
	uint8_t send_cmd = 0;        // 发送指令
	uint8_t recv_cmd = 0;        // 接收指令

	// 写使能
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
	send_cmd = 0x06;             // 写使能指令
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0);
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);

	// 写页数据
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
	send_cmd = 0x02;             // 写页指令
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0);

	send_cmd = (uint8_t) (write_addr >> 16); // 获取写入地址的高位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址高位并接收响应
	send_cmd = (uint8_t) (write_addr >> 8);  // 获取写入地址的中间位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址中间位并接收响应
	send_cmd = (uint8_t) write_addr;          // 获取写入地址的低位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址低位并接收响应

	for (i = 0; i < write_len; i++)
	{
		send_cmd = pbuffer[i]; // 获取要写入的数据
		HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送数据并接收响应
	}

	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // 禁用片选信号(CS)

	while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY)
		; // 等待 SPI 完成操作
}

void flash_erase(uint32_t fan_addr)
{
	uint8_t send_cmd = 0;   // 发送指令
	uint8_t recv_cmd = 0;   // 接收指令
	fan_addr *= 4096;       // 将扇区地址转换为字节地址(每个扇区大小为 4096 字节)

	// 写使能
	send_cmd = 0x06;        // 写使能指令
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0);
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET);
	while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY){}

	// 扇区擦除
	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET);
	send_cmd = 0x20;        // 扇区擦除指令
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0);

	//8M 23字节?
	send_cmd = (uint8_t) (fan_addr >> 16); // 获取擦除地址的高位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址高位并接收响应
	send_cmd = (uint8_t) (fan_addr >> 8);  // 获取擦除地址的中间位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址中间位并接收响应
	send_cmd = (uint8_t) fan_addr;          // 获取擦除地址的低位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址低位并接收响应

	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // 禁用片选信号(CS)
	while (HAL_SPI_GetState(&hspi1) != HAL_SPI_STATE_READY)
		;
}

void spi_read_flash(uint8_t *pbuffer, uint32_t read_addr, uint16_t read_len)
{
	uint8_t send_cmd;   // 发送指令
	uint8_t recv_cmd;   // 接收指令
	uint16_t i;

	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_RESET); // 选择片选信号(CS)低电平使能
	send_cmd = 0x03;   // 读数据指令

	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送读指令并接收响应

	//8M 23位? (24位)
	send_cmd = (uint8_t) (read_addr >> 16); // 获取读取地址的高位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址高位并接收响应
	send_cmd = (uint8_t) (read_addr >> 8);  // 获取读取地址的中间位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址中间位并接收响应
	send_cmd = (uint8_t) read_addr;          // 获取读取地址的低位字节
	HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送地址低位并接收响应

	send_cmd = 0xff;    // 发送空数据以接收闪存中的数据
	for (i = 0; i < read_len; i++)
	{
		HAL_SPI_TransmitReceive(&hspi1, &send_cmd, &recv_cmd, 1, 0); // 发送空数据并接收闪存数据
		pbuffer[i] = recv_cmd;  // 将接收到的数据存储在缓冲区中
	}

	HAL_GPIO_WritePin(GPIOA, GPIO_PIN_2, GPIO_PIN_SET); // 禁用片选信号(CS)
}

uint8_t flash_buffer[4096];  // 用于缓存闪存数据的缓冲区
void spi_write_flash(uint8_t *pbuffer, uint32_t write_addr, uint16_t write_len)
{
	uint32_t sector_index;      // 扇区索引
	uint16_t sector_offset;     // 扇区内的偏移
	uint16_t sector_remain;     // 扇区内剩余可写字节数
	uint16_t i = 0;             // 循环计数器

	sector_index = write_addr / 4096;    // 计算扇区索引
	sector_offset = write_addr % 4096;   // 计算扇区内偏移
	sector_remain = 4096 - sector_offset; // 计算扇区内剩余可写字节数

	// 如果要写入的数据长度小于等于扇区内剩余可写字节数,直接写入扇区
	if (write_len <= sector_remain)
	{
		sector_remain = write_len;
		flash_write(pbuffer, write_addr, sector_remain);
	} else
	{
		// 如果要写入的数据长度超过扇区内剩余可写字节数,需要分多次写入
		while (1)
		{
			spi_read_flash(flash_buffer, sector_index * 4096, 4096); // 读取整个扇区的数据

			// 检查扇区内是否有非擦除状态的数据
			for (i = 0; i < sector_remain; i++)
			{
				if (flash_buffer[sector_offset + i] != 0xFF)
				{
					break; // 如果存在非擦除状态的数据,跳出循环
				}
			}

			if (i < sector_remain)
			{
				flash_erase(sector_index); // 擦除整个扇区

				// 将新数据写入缓冲区
				for (i = 0; i < sector_remain; i++)
				{
					flash_buffer[sector_offset + i] = pbuffer[i];
				}

				flash_write(flash_buffer, sector_index * 4096, 4096); // 将缓冲区数据写入扇区
			} else
			{
				flash_write(pbuffer, write_addr, sector_remain); // 直接写入扇区
			}

			if (write_len == sector_remain)
			{
				break; // 数据已全部写入
			} else
			{
				sector_index++;
				sector_offset = 0;

				pbuffer += sector_remain;
				write_addr += sector_remain;
				write_len -= sector_remain;

				if (write_len > 4096)
				{
					sector_remain = 4096;
				} else
				{
					sector_remain = write_len;
				}
			}
		}
	}
}
/* USER CODE END 4 */

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

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

相关文章

jsp 图书销售系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 图书销售系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysql5.0&…

安卓移动应用开发实训室建设方案

一 、系统概述 安卓移动应用开发作为新一代信息技术的重点和促进信息消费的核心产业&#xff0c;已成为我国转变信息服务业的发展新热点&#xff1a;成为信息通信领域发展最快、市场潜力最大的业务领域。互联网尤其是移动互联网&#xff0c;以其巨大的信息交换能力和快速渗透能…

论文阅读:DIN-SQL: Decomposed In-Context Learning of Text-to-SQL withSelf-Correction

NL2SQL是将自然语言转化为SQL的任务&#xff0c;该任务隶属于NLP的子任务&#xff0c;NL2SQL在AIGC时代之前&#xff0c;以seq2seq、BERT等系列的模型在NL2SQL的主流数据集上取得了不错的效果&#xff0c;2022年底&#xff0c;ChatGPT爆火&#xff0c;凭借LLM强大的逻辑推理、上…

气传导耳机怎么样?四款值得入手的气传导耳机推荐

​随着科技的进步&#xff0c;蓝牙耳机越来越受欢迎。类型也越来越多&#xff0c;其中气传导耳机因其不入耳设计&#xff0c;佩戴更舒适&#xff0c;音质更自然&#xff0c;能够提供更为清晰、自然的音质。面对还不知如何挑选气传导耳机的用户&#xff0c;在这里&#xff0c;我…

Firefox(火狐),使用技巧汇总,问题处理

本文目的 说明火狐如何安装在C盘之外的盘&#xff0c;即定制安装路径。如何将同步功能切换到本地服务上。默认是国际服务器。安装在C盘之后如何解决&#xff0c;之前安装的扩展无法自动同步的问题。顺带讲解一下&#xff0c;火狐的一些比较好用的扩展。 安装路径定制 火狐目前…

sentinel的基本使用

在一些互联网项目中高并发的场景很多&#xff0c;瞬间流量很大&#xff0c;会导致我们服务不可用。 sentinel则可以保证我们服务的正常运行&#xff0c;提供限流、熔断、降级等方法来实现 一.限流&#xff1a; 1.导入坐标 <dependency><groupId>com.alibaba.c…

行为型(一) - 观察者模式

一、概念 观察者模式&#xff08;Observer Pattern&#xff09;&#xff1a;在对象之间定义一个一对多的依赖&#xff0c;当一个对象状态改变的时候&#xff0c;所有依赖的对象都会自动收到通知。 二、实现 这里举个电视剧订阅的例子&#xff0c;比如琅琊榜&#xff0c;我们…

基于学习交流社区的自动化测试实现

一 项目介绍 项目名称 项目展示链接&#xff1a; 学习交流社区 项目介绍 项目介绍&#xff1a; 学习交流社区是一个基于Spring的前后端分离的在线论坛系统。使用了MySQL数据库来存储相关信息&#xff0c;项目完成后使用Xshell将其部署到云服务器上。 前端页面&#xff1a; 前端…

让 exoplayer2 支持播放 ftp ( 扩展 exoplayer 支持 ftp 协议 ) 的两种方式

exoplayer 是安卓开源播放器组件库&#xff0c;由谷歌开发维护。它提供了一个可高度扩展的音视频播放框架&#xff0c;支持许多媒体格式与高级媒体功能&#xff0c;比如 adaptive streaming&#xff0c;DRM&#xff0c;以及安卓 media session 集成。 但是不支持 ftp &#xf…

罗德与施瓦茨频谱分析仪RSFSUP50

FSUP50 R&S FSUP50 信号源分析仪&#xff0c;20Hz到50GHz 壹捌叁贰零玖壹捌陆伍叁 R&S FSUP 是一款测量功能丰富、高度灵活的相位噪声测试仪&#xff0c;它兼具***信号和频谱分析仪及单纯相位噪声测试仪两者的功能。 主要特点 频率范围高达 8 GHz、26.5 GHz 或 50…

网络原理详解(图文结合)

目录 ​编辑一、应用层 1、请求和响应 2、通用的协议格式 &#xff08;1&#xff09;xml &#xff08;2&#xff09;json &#xff08;3&#xff09;protobuffer 二、传输层 1、UDP 2、TCP &#xff08;1&#xff09;TCP 协议段格式&#xff1a; &#xff08;2&am…

js中 0==‘0‘、0==[] 为 true ,‘0‘==[] 为false

文章目录 问题分析 问题 js中 0‘0’、0[] 为 true &#xff0c;为什么 ‘0’[] 为false 分析 是弱类型比较 当两者类型不同时会发生类型转换0 “0”&#xff1a;先把“0”转为number类型再比较&#xff1b;0 []&#xff1a;有对象的话&#xff0c;先获取对象的原始值&#…

WPF中的效果Effect

WPF中的效果Effect 前言 WPF提供了可应用于任何元素的可视化效果。效果的目标是提供一种简便的声明式方法&#xff0c;从而改进文本、图像、按钮以及其他控件的外观。不是编写自己的绘图代码&#xff0c;而是使用某个继承自Effect的类&#xff0c;以立即获得诸如模糊、光辉以…

问道管理:数字经济概念走势强劲,竞业达、久其软件等涨停,观想科技等大涨

信创、智慧政务等数字经济概念22日盘中走势微弱&#xff0c;截至发稿&#xff0c;观想科技、慧博云通涨超15%&#xff0c;竞业达、中远海科、久其软件等涨停&#xff0c;云赛智联、延华智能、汇纳科技涨约9%&#xff0c;天玑科技、安硕信息、思特奇、零点稀有涨逾7%。 音讯面上…

虚幻官方项目《CropOut》技术解析 之 在实战中理解Enhanced Input系统

文章目录 概要Enhanced Input系统基础回顾旧版输入系统定义物理按键和Action/Axis的映射输入事件 Enhanced Input系统统一的ActionInput Mapping Context输入事件 《Crop Out》《Crop Out》中基于Enhanced Input的输入控制系统Input Mapping Context分层管理输入修改器(Input M…

Ubuntu系统全盘备份——TimeShift的安装与使用

Timeshift&#xff0c;它使用完全备份的方式&#xff0c;直接将整个操作系统包括个人配置全部备份打包。也就是全盘备份。 操作过程 依次执行命令&#xff1a; sudo apt-add-repository -y ppa:teejee2008/ppa sudo apt update sudo apt install timeshift打开软件后&#x…

【ES6】—【必备知识】—函数的参数

一、参数的默认值 1. ES5 设置默认值 function foo (x, y) {y y || worldconsole.log(x, y) } foo(hello, xiaoxiao) foo(hello, 0) // hello xiaoxiao // hello worldPS&#xff1a; 使用 || 的方式设置默认值不严谨&#xff0c; 0、undefined、‘’ 这些参数都会被判定为f…

安装VSCA 过程中的报错

安装VSCA 过程中的报错&#xff1a;无法获取目标服务器证书的SSL指纹&#xff1a; 解决方案三点&#xff1a; 防火墙 网络IP&#xff0c;网关 使用ping命令或者telnet检查网络是否可达 网络端口没有开启 有无启用 另外最新版本有漏洞 建议换一个常用版本

NestJS 中的 gRPC 微服务通信

想象一下&#xff0c;你回家过节&#xff0c;你的家人决定聚会。而不是让一个人做所有的烹饪&#xff0c;每个人都同意带上他们擅长制作的特色菜。目标是通过组合所有这些菜肴来制作一顿完整的饭菜。你同意做鸡肉炒饭&#xff0c;你哥哥做甜点蛋糕&#xff0c;妹妹做沙拉。 每…

SV-7101T网络音频广播终端使用手册

1.1、产品简介 感谢你使用我司的SV-7101T网络音频播放终端&#xff0c;SV-7101T能处理tcp/ip网络音频流&#xff0c;提供一路线路输出。主要用于公共数字广播&#xff0c;媒体教学&#xff0c;报警等需要数字音频的领域。 SV-7101T具有10/100M以太网接口&#xff0c;支持最高4…