STM32——“SPI Flash”

news2024/12/24 6:06:18

       引入

        在给单片机写程序的时候,有时会用到显示屏,就拿市面上的0.96寸单色显示器来说,一张全屏的图片就占用8x128=1024个字节,即1kb的空间,这对于单片机来说确实有点奢侈,于是我买了一个8Mb的SPI Flash,型号为华邦的W25Q64。

        在手册里很容易看到他的介绍:

        它支持四线的SPI,在很大程度上增加了读写速度,同时在H7系列中还可以用作扩展的Flash,自带的QSPI功能强大,但是在F1系列中没有QSPI的功能,因此这里只介绍用STM32的普通硬件SPI来驱动这块Flash。

        想要驱动这块Flash,首先要配置STM32的硬件SPI:

        一、配置SPI

SPI_HandleTypeDef	g_W25Qxx_Handle;

void SPI_Init()
{
	g_W25Qxx_Handle.Instance = SPIx;
	g_W25Qxx_Handle.Init.BaudRatePrescaler = SPI_BAUDRATEPRESCALER_2;

	g_W25Qxx_Handle.Init.CLKPhase = SPI_PHASE_1EDGE;
	g_W25Qxx_Handle.Init.CLKPolarity = SPI_POLARITY_LOW;
	g_W25Qxx_Handle.Init.CRCCalculation = SPI_CRCCALCULATION_DISABLE;
	g_W25Qxx_Handle.Init.CRCPolynomial = 1;
	g_W25Qxx_Handle.Init.DataSize = SPI_DATASIZE_8BIT;
	g_W25Qxx_Handle.Init.Direction = SPI_DIRECTION_2LINES;
	g_W25Qxx_Handle.Init.FirstBit = SPI_FIRSTBIT_MSB;
	g_W25Qxx_Handle.Init.Mode = SPI_MODE_MASTER;
	g_W25Qxx_Handle.Init.NSS = SPI_NSS_SOFT;
	g_W25Qxx_Handle.Init.TIMode = SPI_TIMODE_DISABLE;

	HAL_SPI_Init(&g_W25Qxx_Handle);
	
}

void SPI_GPIO_Init()
{
	SPI_FLASH_SCLK_RCC();
	SPI_FLASH_CS_RCC();
	SPI_FLASH_MISO_RCC();
	SPI_FLASH_MOSI_RCC();
	
	SPI_FLASH_SPI_RCC();

	GPIO_InitTypeDef GPIO_InitStruct;
	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pin = FLASH_SCLK_PIN;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(SPI_FLASH_SCLK_PORT,&GPIO_InitStruct);


	GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
	GPIO_InitStruct.Pin = FLASH_MISO_PIN;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	HAL_GPIO_Init(SPI_FLASH_MISO_PORT, &GPIO_InitStruct);


	GPIO_InitStruct.Mode = GPIO_MODE_AF_PP;
	GPIO_InitStruct.Pin = FLASH_MOSI_PIN;
	GPIO_InitStruct.Pull = GPIO_PULLUP;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(SPI_FLASH_MOSI_PORT,&GPIO_InitStruct);
	
	GPIO_InitStruct.Mode = GPIO_MODE_OUTPUT_PP;  // 普通输出
	GPIO_InitStruct.Pin = FLASH_CCS_PIN;
	GPIO_InitStruct.Pull = GPIO_NOPULL;
	GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_HIGH;
	HAL_GPIO_Init(SPI_FLASH_CCS_PORT, &GPIO_InitStruct);
}

void SelectChip()
{
	HAL_GPIO_WritePin(SPI_FLASH_CCS_PORT,FLASH_CCS_PIN,GPIO_PIN_RESET);
}

void UnselectChip()
{
	HAL_GPIO_WritePin(SPI_FLASH_CCS_PORT,FLASH_CCS_PIN,GPIO_PIN_SET);
}


       引脚宏为:

#define SPIx				SPI1

#define	FLASH_CCS_PIN		GPIO_PIN_4
#define FLASH_SCLK_PIN		GPIO_PIN_5
#define FLASH_MISO_PIN		GPIO_PIN_6
#define FLASH_MOSI_PIN		GPIO_PIN_7

#define SPI_FLASH_SCLK_PORT		GPIOA
#define SPI_FLASH_CCS_PORT		GPIOA
#define SPI_FLASH_MISO_PORT		GPIOA
#define SPI_FLASH_MOSI_PORT		GPIOA


#define SPI_FLASH_SCLK_RCC()		__HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_FLASH_CS_RCC()			__HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_FLASH_MISO_RCC()		__HAL_RCC_GPIOA_CLK_ENABLE()
#define SPI_FLASH_MOSI_RCC()		__HAL_RCC_GPIOA_CLK_ENABLE()

#define SPI_FLASH_SPI_RCC()		__HAL_RCC_SPI1_CLK_ENABLE()

        在SPI的配置上使用模式0或者模式3的高位先发,在这里我使用模式0,即:极性为低,相位是在第一个上升沿。

体现在上边的代码为:

	g_W25Qxx_Handle.Init.CLKPhase = SPI_PHASE_1EDGE;
	g_W25Qxx_Handle.Init.CLKPolarity = SPI_POLARITY_LOW;

        由于在我写硬件SPI代码的过程中,遇到了不能准确识别芯片的问题,研究了很久,才发现是片选代码的问题,即:在写了OLED驱动之后由于思维惯性,我的想法是只要持续拉低片选就一直可以通信,但现实是的确可以通信,只是Flash貌似识别不了我发的命令。最后的解决方法是在每次通信之前拉低片选,通信完之后拉高片选。这里的通信是有命令发出的时候。

        解决完这个问题之后,首先要做的就是和Flash建立通信,即获取Flash的ID。

        二、查询ID

        在手册里,Flash的ID为:EF4017

        对应的查询命令为:0x9F

        第一个是厂商ID,后边的是芯片ID。

/*
**** 函数名 W25Q64GetID
**** 功能 W25Q64 读取设备ID号
**** 参数 无
**** 
*/

uint32_t W25Qxx_GetID()
{
	uint32_t ID = 0;
	uint8_t id[3];
	uint8_t cmd = JEDEC_ID;
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,&cmd,1,1000);
	HAL_SPI_Receive(&g_W25Qxx_Handle,id,3,1000);
	
	ID = (((((ID | id[0]) << 8) | id[1]) << 8) | id[2]);
	UnselectChip();
	return ID;
}

        这里发送命令,然后接收三个字节的ID,最后拼接ID并返回。

int main()
{
	
	HAL_Init();
	SystemClock_Config();
	OLED_Init();
	LED_Init();
	W25Qxx_Init();
	UsartInit(115200);
	
	printf("现在进行硬件SPI实验!\n\n");
	
	uint32_t id = W25Qxx_GetID();
	printf("芯片ID为:%X\n\n",id);

	while(1)
	{
	}
}

        执行后的效果如下:

        可见完全没问题。

        三、读状态寄存器

        接下来是写数据,但是在写数据之前需要写使能和擦除扇区,另外在进行这两个操作之前需要检查Flash是否繁忙,于是接下来是检查Flash的状态,即,检查状态寄存器1

        我们主要检查第一个寄存器的第一个比特位。然而检查第一个寄存器状态的命令是0x05

        返回之后对第一位进行检查:

/*
**** 函数名 W25Q64CheckBusy
**** 功能 W25Q64 读状态寄存器
**** 参数 无
**** 
*/
void W25Qxx_CheckBusy()
{
	uint8_t ret = 0;

	uint8_t cmd = ReadStatusRegister;
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,&cmd,1,1000);
	
	do{
		HAL_SPI_Receive(&g_W25Qxx_Handle,&ret,1,1000);
	}while((ret & 0x01) == 0x01);
	UnselectChip();
}

        倘若Flash繁忙,则第一位为1,反之为0,为1就一直检查,直到芯片空闲。

接下来是写使能。

        四、写使能

        Flash手册里提到:在进行写入,擦除,写状态寄存器等操作之前必须进行写使能。

        它对应的命令是0x06

/*
**** 函数名 W25Q64WriteEnable
**** 功能 W25Q64 写使能
**** 参数 无
**** 
*/
void W25Qxx_WriteEnable()
{
	uint8_t cmd = WriteEnable;
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,&cmd,1,1000);
	UnselectChip();
}

        准备工作进行完之后就是擦除了。

        五、擦除

        W25Q64的擦除命令有四个:扇区擦除(4kb)-0x20,块擦除(32kb)-0x52,块擦除(64kb)-0xD8,全片擦除-0x60。

        由于前三个的代码大同小异,因此只介绍一下第一个和最后一个。

        1.扇区擦除(4kb)

这里是需要输入地址的,因此在发送命令以后需要发送地址,大小为24位:

/*
**** 函数名 W25Q64SectorErase
**** 功能 W25Q64 扇区擦除(4kb)
**** 参数 address:24位的地址
**** 
*/
void W25Qxx_SectorErase(uint32_t address)
{
	uint8_t cmd[4];
	cmd[0] = SectorErase;
	cmd[1] = (address >> 16) & 0xFF;
	cmd[2] = (address >> 8)  & 0xFF;
	cmd[3] = address & 0xFF;
	
	W25Qxx_CheckBusy();
	W25Qxx_WriteEnable();
	W25Qxx_CheckBusy();
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,cmd,4,1000);
	UnselectChip();
}

        擦除之前记得写使能和检查芯片状态。

        2.全片擦除

        全片擦除不需要输入地址,但是全片擦除等待的时间很长。

/*
**** 函数名 W25Q64ChipErase
**** 功能 W25Q64 全片擦除
**** 参数 无
**** 
*/
void W25Qxx_ChipErase()
{
	uint8_t cmd[1];
	cmd[0] = ChipErase;
	
	W25Qxx_CheckBusy();
	W25Qxx_WriteEnable();

	W25Qxx_CheckBusy();
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,cmd,1,1000);
	UnselectChip();
	W25Qxx_CheckBusy();
}

        这里可以测试一下全片擦除的时间:

int main()
{
	
	HAL_Init();
	SystemClock_Config();
	OLED_Init();
	LED_Init();
	W25Qxx_Init();
	UsartInit(115200);
	
	printf("现在进行硬件SPI实验!\n\n");
	
	uint32_t id = W25Qxx_GetID();
	printf("芯片ID为:%X\n\n",id);
	uint32_t head = 0,tail = 0;
	if(id == 0xEF4017)
	{
		head = HAL_GetTick();
		W25Qxx_ChipErase();
		tail = HAL_GetTick();
		printf("全片擦除所用的时间为%d ms\n\n",tail - head);	
	}
	
	while(1)
	{
	}
}

        时间还是比较长的。另外值得注意的是,根据我的实验结果,擦除时候并不是按照你给的地址开始擦除,而是擦除你地址所在的扇区或者块。

        六、页编程

        页编程的命令是0x02

在此之前付下如图:

        这个图介绍了编程的最小范围是一页,编程超过一页的需要手动变换地址,因为一页写满之后,地址并不会自动跳到下一页继续写,而是回到该页首地址继续写,这样会造成前后数据的覆盖。另外一页是256字节。

/*
**** 函数名 W25Q64PageProgram
**** 功能 W25Q64 页编程(一页256字节)
**** 参数 address:24位的地址
**** 参数 data:   写入的数据
**** 参数 Size:数据的大小,单位:字节
*/
void W25Qxx_PageProgram(uint32_t address,uint8_t* data,uint16_t Size)
{
	uint8_t cmd[4];
	cmd[0] = PageProgram;
	cmd[1] = (address >> 16) & 0xFF;
	cmd[2] = (address >> 8) & 0xFF;
	cmd[3] = address & 0xFF;
	
	W25Qxx_CheckBusy();
	W25Qxx_WriteEnable();
	W25Qxx_CheckBusy();
	
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,cmd,4,1000);
	HAL_SPI_Transmit(&g_W25Qxx_Handle,data,Size,10000);
	UnselectChip();
}

        这个页编程介绍的东西不多,最重要的是下边的随意地址编程,最重要的就是解决写满一页以后需要手动解决地址偏移的问题。

        七、写任意大小数据

        由于页BUFF的限制,一次性只能写入256个字节,因此这个操作就是持续重复写入256及以下字节。

/*
**** 函数名 W25Q64WriteData
**** 功能 W25Q64 写入数据
**** 参数 address:24位的地址
**** 参数 databuffer:写入的数据
**** 参数 Size:读取数据的大小,单位:字节3
*/
void W25Qxx_WriteData(uint32_t address, uint8_t* data, uint32_t Size)
{
    if (address > 0x7FFFFF || data == NULL) // 检查输入参数合法性
    {
        return;
    }

    uint8_t offset = address % 256;         // 当前地址的页内偏移
    uint16_t remainingInPage = 256 - offset; // 当前页剩余空间大小

    // 判断数据是否跨页
    if (Size <= remainingInPage) // 数据小于或等于当前页剩余空间
    {
        W25Qxx_PageProgram(address, data, Size); // 写入当前页
        return;
    }

    // 数据跨页
    // 先填满当前页
    W25Qxx_PageProgram(address, data, remainingInPage);

    // 更新地址和数据指针
    address += remainingInPage;
    data += remainingInPage;
    Size -= remainingInPage;

    // 写入完整页的数据
    while (Size >= 256)
    {
        W25Qxx_PageProgram(address, data, 256);
        address += 256;
        data += 256;
        Size -= 256;
    }

    // 写入最后不足一页的数据
    if (Size > 0)
    {
        W25Qxx_PageProgram(address, data, Size);
    }
}

        在代码中,offset是计算的相对于该写入地址所在的页的首地址的偏移量,remainingInPage 用于计算该页剩余可写入空间大小。举个例子,一个地址是0x100即256,该地址所在的页是[256,511] 一共256个字节,因为前一个页是[0,255]。那么我要在256地址处写数据,那么它的offset = 256%256 = 0,剩余可写入空间为remainingInPage  = 256 - 0 = 256。所以我们可以按照这样的算法,来先把当前页填充满,当前页填充满之后就可以成整数倍的填充256个字节,当剩余的数据小于256字节的时候,在单独填充,这样做的好处就是可以在任意地址写入任意大小的数据,且不用担心数据覆盖。

        八、读任意大小数据

        读数据的命令是0x03。

/*
**** 函数名 W25Q64ReadData
**** 功能 W25Q64 读取数据
**** 参数 address:24位的地址
**** 参数 databuffer:数据接收缓冲区
**** 参数 Size:读取数据的大小,单位:字节
*/
void W25Qxx_ReadData(uint32_t address,uint8_t* databuffer,uint32_t Size)
{
	uint8_t cmd[4];
	cmd[0] = ReadData;
	cmd[1] = (address >> 16) & 0xFF;
	cmd[2] = (address >> 8)  & 0xFF;
	cmd[3] = address & 0xFF;
	
	W25Qxx_CheckBusy();
	
	SelectChip();
	HAL_SPI_Transmit(&g_W25Qxx_Handle,cmd,4,1000);
	HAL_SPI_Receive(&g_W25Qxx_Handle,databuffer,Size,100000);
	UnselectChip();

}

        这个可讲解的地方不多,单纯发送读命令后再接收数据。

        九、测试

        最后测试一下代码效果:

void W25QxxTest()
{
	uint16_t head,tail;

	for(uint16_t i = 0;i < 8192;i++)
	{
		data[i] = '1';
	}
	data[8191] = '\0';
	printf("现在进行硬件SPI实验!\n\n");
	
	uint32_t id = W25Qxx_GetID();
	printf("芯片ID为:%X\n\n",id);
	
	if(id == W25Q64_ID)
	{
		printf("正在擦除...\n");
		W25Qxx_BlockErase(0x000000);
		printf("擦除成功!\n\n");
		
		printf("正在写入...写入数据量为8kb\n");
		head = HAL_GetTick();
		W25Qxx_WriteData(0x00000A,data,8192);
		tail = HAL_GetTick();
		
		printf("写入成功!,写入花费的时间为:%d ms\n\n",tail - head);
	
		printf("正在读取数据,读取数据量为8kb\n");
		head = HAL_GetTick();
		W25Qxx_ReadData(0x00000A,test,8192);
		tail = HAL_GetTick();
		
		
		if(memcmp(test,data,8192) == 0)
		{
			printf("读取成功,读取花费的时间为:%d ms\n\n",tail - head);
		}
		else
		{
			printf("读取失败\n");

		}
			printf("读取到的数据是: %s\n",test);

		
	}



}

END............

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

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

相关文章

深入浅出:AWT的基本组件及其应用

目录 前言 1. AWT简介 2. AWT基本组件 2.1 Button&#xff1a;按钮 2.2 Label&#xff1a;标签 ​编辑 2.3 TextField&#xff1a;文本框 2.4 Checkbox&#xff1a;复选框 2.5 Choice&#xff1a;下拉菜单 2.6 List&#xff1a;列表 综合案例 注意 3. AWT事件处理 …

Flutter组件————PageView

PageView 可以创建滑动页面效果的widget&#xff0c;它允许用户通过水平或垂直滑动手势在多个子页面&#xff08;child widgets&#xff09;之间切换。每个子页面通常占据屏幕的全部空间。 参数 参数名类型描述childrenList<Widget>包含在 PageView 中的所有子部件&am…

三种电子画册制作方法

今天教大家三种电子画册的制作方法&#xff0c;很容易上手&#xff0c;需要的赶紧收藏起来 一、 利用在线平台--FLBOOK 1.注册并登录在线平台。 2.选择喜欢的模板&#xff0c;根据需求进行修改 3.批量上传PDF文件一键转换H5翻页电子画册 4.添加图片、文字等元素&#xff0c…

以太坊账户详解

文章目录 一、账户基本概念1.1 外部账户1.2 合约账户1.3 差异对比 二、帐户创建2.1 外部账户创建2.2 合约账户创建 三、账户数据结构3.1 账户状态3.2 账户状态结构 对比比特币的 “UTXO” 余额模型&#xff0c;以太坊使用“账户”余额模型。 以太坊丰富了账户内容&#xff0c;除…

AWS Transfer 系列:简化文件传输与管理的云服务

在数字化转型的今天&#xff0c;企业对文件传输、存储和管理的需求日益增长。尤其是对于需要大量数据交换的行业&#xff0c;如何高效、可靠地传输数据成为了一大挑战。为了解决这一难题&#xff0c;AWS 提供了一系列的文件传输服务&#xff0c;统称为 AWS Transfer 系列。这些…

基础I/O -> 如何谈文件与文件系统?

文件的基础理解 空文件也要在磁盘上占据空间。文件 文件内容文件属性。文件操作 对内容的操作 对属性的操作或者是对内容和属性的操作。标定一个文件&#xff0c;必须使用&#xff1a;文件路径 文件名&#xff08;具有唯一性&#xff09;。如果没有指明对应的文件路径&…

网络安全检测

实验目的与要求 (1) 帮助学生掌握木马和入侵的防护和检测方法、提高学习能力、应用能力和解决实际问题的能力。 (2) 要求学生掌握方法, 学会应用软件的安装和使用方法, 并能将应用结果展示出来。 实验原理与内容 入侵检测是通过对计算机网络或计算机系统中若干关键点收集信…

谷歌浏览器的资源管理功能详解

谷歌浏览器作为一款广受欢迎的网页浏览器&#xff0c;不仅以其快速、简洁和易用著称&#xff0c;还提供了强大的资源管理功能。本文将详细介绍如何在Chrome浏览器中进行资源管理&#xff0c;包括查看网页的渲染性能、禁用标签页的背景更新以及管理正在下载的文件。&#xff08;…

ARM异常处理 M33

1. ARMv8-M异常类型及其详细解释 ARMv8-M Exception分为两类&#xff1a;预定义系统异常(015)和外部中断(1616N)。 各种异常的状态可以通过Status bit查看&#xff0c;获取更信息的异常原因&#xff1a; CFSR是由UFSR、BFSR和MMFSR组成&#xff1a; 下面列举HFSR、MMFSR、…

Unity2021.3.16f1可以正常打开,但是Unity2017.3.0f3却常常打开闪退或者Unity2017编辑器运行起来就闪退掉

遇到问题&#xff1a; 从今年开始&#xff0c;不知道咋回事&#xff0c;电脑上的Unity2017像是变了个人似得&#xff0c;突然特别爱闪退掉&#xff0c;有时候还次次闪退&#xff0c;真是让人无语&#xff0c;一直以来我都怀疑是不是电脑上安装了什么别的软件了&#xff0c;导致…

SpringBoot核心:自动配置

有使用过SSM框架的&#xff0c;还记得曾经在spring-mybatis.xml配置了多少内容吗&#xff1f;数据源、连接池、会话工厂、事务管理&#xff0c;而现在Spring Boot告诉你这些都不需要了&#xff0c;简单的几个注解统统搞定&#xff0c;是不是很方便&#xff01; 前言 SpringBoo…

重温设计模式--享元模式

文章目录 享元模式&#xff08;Flyweight Pattern&#xff09;概述享元模式的结构C 代码示例1应用场景C示例代码2 享元模式&#xff08;Flyweight Pattern&#xff09;概述 定义&#xff1a; 运用共享技术有效地支持大量细粒度的对象。 享元模式是一种结构型设计模式&#xff0…

Taro小程序开发性能优化实践

我们团队在利用Taro进行秒送频道小程序的同时&#xff0c;一直在探索性能优化的最佳实践。随着需求的不断迭代&#xff0c;项目中的性能问题难免日积月累&#xff0c;逐渐暴露出来影响用户体验。适逢双十一大促&#xff0c;我们趁着这个机会统一进行了Taro性能优化实践&#xf…

纯血鸿蒙APP实战开发——textOverflow长文本省略

介绍 本示例实现了回复评论时&#xff0c;当回复人的昵称与被回复人的昵称长度都过长时&#xff0c;使用textOverflow和maxLines()实现昵称的长文本省略展示的功能。 效果图预览 使用说明 点击评论中的"回复"&#xff0c;在输入框中输入回复内容&#xff0c;点击发…

【java面向对象编程】第九弹----抽象类、接口、内部类

笔上得来终觉浅,绝知此事要躬行 &#x1f525; 个人主页&#xff1a;星云爱编程 &#x1f525; 所属专栏&#xff1a;javase &#x1f337;追光的人&#xff0c;终会万丈光芒 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 一、抽象类 1.1基本介绍 &…

Qt笔记:网络编程UDP

一、铺垫 1.Qt框架使用的网络结构的基础就是Linux学习的网络编程基础&#xff1b;所以使用Qt写客户端&#xff0c;使用Linux写服务端&#xff1b;两者是可以实现互联的 二、UDP 网络编程UDP使用套路&#xff1a; 1.首先在.pro文件中加上network&#xff0c;使Qt可以搭载网络…

Redis存在安全漏洞

Redis是美国Redis公司的一套开源的使用ANSI C编写、支持网络、可基于内存亦可持久化的日志型、键值&#xff08;Key-Value&#xff09;存储数据库&#xff0c;并提供多种语言的API。 Redis存在安全漏洞。攻击者利用该漏洞使用特制的Lua脚本触发堆栈缓冲区溢出漏洞&#xff0c;从…

【潜意识Java】蓝桥杯算法有关的动态规划求解背包问题

目录 背包问题简介 问题描述 输入&#xff1a; 输出&#xff1a; 动态规划解法 动态规划状态转移 代码实现 代码解释 动态规划的时间复杂度 例子解析 输出&#xff1a; 总结 作者我蓝桥杯&#xff1a;2023第十四届蓝桥杯国赛C/C大学B组一等奖&#xff0c;所以请听我…

ReactPress 1.6.0:重塑博客体验,引领内容创新

ReactPress 是一个基于Next.js的博客&CMS系统&#xff0c; Github项目地址&#xff1a;https://github.com/fecommunity/reactpress 欢迎Star。 体验地址&#xff1a;http://blog.gaoredu.com/ 今天&#xff0c;我们自豪地宣布ReactPress 1.6.0版本的正式发布&#xff0c;…

单元测试-Unittest框架实践

文章目录 1.Unittest简介1.1 自动化测试用例编写步骤1.2 相关概念1.3 用例编写规则1.4 断言方法 2.示例2.1 业务代码2.2 编写测试用例2.3 生成报告2.3.1 方法12.3.2 方法2 1.Unittest简介 Unittest是Python自带的单元测试框架&#xff0c;适用于&#xff1a;单元测试、Web自动…