目录

一、功能描述 

1、BootLoader部分:

2、APP部分:

二、BootLoader程序制作

1、分区定义

2、 主函数

3、配置USB

4、配置fatfs文件系统

5、程序跳转

三、APP程序制作

四、工程配置(默认KEIL5)

五、运行测试 

结束语


概述

        IAP(In Application Programming)即在应用中编程,允许在应用程序运行时更新或切换固件。STM32通过修改MSP(主堆栈指针)和PC(程序计数器)实现从不同地址启动,包括Flash或RAM地址。默认情况下,嵌入式程序以连续二进制形式烧录到STM32的可寻址Flash区域。若Flash容量足够存储多个完整程序,每个程序独立且完整,上电后可通过修改MSP值选择不同程序入口,从而实现多固件切换或升级。

         BootLoader(引导加载程序)是嵌入式系统或计算机启动时运行的一段小型程序,负责初始化硬件、加载操作系统内核并将其控制权移交。它是系统从关机状态到操作系统完全运行之间的桥梁。

所以,固件升级的基本思路是将stm32的flash划分为若干个区域,其中包括BootLoader区域和APP区等,将各自的程序写到对应的flash区域里。

一、功能描述 

        使用STM32的USB总线和内部FLASH实现USB模拟U盘升级程序。将FLASH分为4个部分,最后的DOWNLOAD区域用作模拟U盘存储固件。
分区介绍:
        本文使用stm32f103vet6,flash是512k,sector是1k,BootLoader整个代码编译下来有23K左右,所以使用0x08000000~0x00007FFF,SETTING主要存放升级标志位,使用0x08008000~0x00008FFF,剩下的FLASH将分为两个部分都用来存放代码,APP使用0x08009000~0x080447FF,DOWNLOAD使用0x08044800~0x0807FFFF。(如果想更大化地利用flash,可以不要setting区域,具体看自己如何写了)

区域起始地址区域大小功能
BOOT0x080000000x00008000(32k)存放BootLoader程序
SETTING0x080080000x00001000(4k)存放升级标志位/其它掉电不丢失标志位
APP0x080090000x0003B800(238k)存放产品主程序
DOWNLOAD0x080448000x0003B800(238k)存放待升级的程序(bin文件)

1、BootLoader部分:

        运行程序时首先从SETTING区域读取升级标志位,如果需要升级就进入识别U盘程序,否则就直接跳转到APP。上电长按KEY1并复位,电脑上即可模拟出U盘,识别到U盘后复制固件bin文件到U盘,然后将内置FLASH里的fatfs文件系统的升级文件拷贝到APP起始地址,即可实现升级程序,具体请查看本文源码。

tips:触发模拟U盘功能不一定要按键才能触发,例如上电前插入USB线也可以触发,工程已预留代码,只要注释掉按键代码即可。

2、APP部分:

        该部分只需要设置中断向量跳转指针就行,如果想通过串口等下发升级标志位,也可以设置SETTING区域后复位进入BootLoader升级。

二、BootLoader程序制作

        需要包含USB Device中的Mass_Strorage和内部FLASH以及fatfs文件系统的驱动代码。(这部分是需要仔细研究做好的,我是根据正点原子和野火的教程移植的,具体流程不做了)

1、分区定义
#define FLASH_SECTOR_SIZE           1024   //MCU sector size
#define FLASH_SECTOR_NUM            512    // 512K
#define FLASH_START_ADDR            ((uint32_t)0x8000000)
#define FLASH_END_ADDR              ((uint32_t)(0x8000000 + FLASH_SECTOR_NUM * FLASH_SECTOR_SIZE))#define BOOT_SECTOR_ADDR            0x08000000      // BOOT sector start address
#define BOOT_SECTOR_SIZE            0x8000
#define SETTING_SECTOR_ADDR         0x08008000      // APP设置的boot升级标志位
#define SETTING_SECTOR_SIZE         0x1000
#define APP_SECTOR_ADDR             0x08009000      // APP sector start address 
#define APP_SECTOR_SIZE             0x3B800
#define DOWNLOAD_SECTOR_ADDR        0x08044800      // Download sector start address
#define DOWNLOAD_SECTOR_SIZE        0x3B800         // Download sector size #define APP_ERASE_SECTORS           (APP_SECTOR_SIZE / FLASH_SECTOR_SIZE) typedef enum {NONE = 0,START_PROGRAM,    //进入APP主程序或者有更新就执行更新UPDATE_PROGRAM,   //进入更新UPDATE_SUCCESS    //更新成功写标志位
}Update_Process;      //更新状态
2、 主函数

        这部分包含了升级的所有状态,主要思路是判断firmware.bin文件是否存在,存在就执行升级。该框架可以说对比上一篇文章是完全不变的,具体看代码。

Update_Process bootupdate_process;void devflash_update(void) 
{u8 file_buffer[1024]={0};   uint32_t flash_addr = APP_SECTOR_ADDR;unsigned long total_bytes_read = 0;UINT bytes_read;fres=f_mount(&fs,"2:",1); 				//挂载FLASH.	if(fres==FR_OK)//FLASH磁盘,FAT文件系统正常{printf("Flash disk OK!\r\n");}// 判断flash根目录下是否有firmware.bin文件fres = f_stat("2:/firmware.bin", &fno);if(fres == FR_OK){printf("2:/firmware.bin”文件信息:\n");printf("》文件大小: %ld(字节)\n", fno.fsize);iap_flash_erase(flash_addr, (fno.fsize/FLASH_SECTOR_SIZE)+1);}else{printf("firmware.bin not found!\r\n");}if(fres == FR_OK) {// 文件存在printf("firmware.bin found, size: %lu bytes\r\n", fno.fsize);printf("开始更新固件...\r\n");// 打开固件文件fres = f_open(&firmware_file, "2:/firmware.bin", FA_READ);if(fres == FR_OK) {// 循环读取文件内容while (total_bytes_read < fno.fsize){// 读取数据块到缓冲区fres = f_read(&firmware_file, file_buffer, 1024, &bytes_read);if (fres != FR_OK || bytes_read == 0){// 读取出错或到达文件末尾printf("读取文件失败或文件已结束,错误码: %d\r\n", fres);break;}// 写入到FLASHprintf("正在写入地址 0x%08X,大小: %u 字节\r\n", flash_addr, bytes_read);iap_write_appbin(flash_addr, file_buffer, bytes_read);// 更新计数器和地址total_bytes_read += bytes_read;flash_addr += bytes_read;// 显示进度printf("更新进度: %lu/%lu bytes\r\n", total_bytes_read, fno.fsize);}// 关闭文件f_close(&firmware_file);if (total_bytes_read == fno.fsize) {printf("固件更新完成! 共写入 %lu 字节\r\n", total_bytes_read);//固件更新完成后删除firmware.bin文件fres = f_unlink("2:/firmware.bin");if (fres == FR_OK) {printf("firmware.bin文件删除成功\r\n");} else {printf("删除firmware.bin文件失败,错误码: %d\r\n", fres);}} else {printf("固件更新未完成! 已写入 %lu/%lu 字节\r\n", total_bytes_read, fno.fsize);}}}	else {// 文件不存在printf("firmware.bin not found!\r\n");}
}
static void iap_process(void)
{uint8_t offline_cnt=0;uint8_t tct=0;uint8_t USB_STA;uint8_t Device_STA; // 定义最大重试次数和每次等待间隔const uint8_t max_retries = 5;const uint8_t wait_interval_ms = 100;uint8_t retry_count = 0;switch (bootupdate_process) {case NONE:break;case START_PROGRAM:spiflash_update();printf("start app...\r\n");delay_ms(50);if ((((*(vu32*)(APP_SECTOR_ADDR+4))&0xFF000000)==0x08000000)&&(!iap_load_app(APP_SECTOR_ADDR))) {printf("no program\r\n");delay_ms(1000);}printf("start app failed\r\n");break;case UPDATE_PROGRAM:MAL_Init(0);Max_Lun=0;	USB_Interrupts_Config();/*设置USB时钟为48M*/Set_USBClock();USB_Init();
//			while (bDeviceState != CONFIGURED);	 //等待配置完成// 等待配置完成,最多重试 5 次while (bDeviceState != CONFIGURED && retry_count < max_retries) {delay_ms(wait_interval_ms);retry_count++;}// 若 5 次后仍未配置完成,进入 START_PROGRAM 状态if (bDeviceState != CONFIGURED) {printf("USB 配置失败,尝试 5 次后退出,进入 START_PROGRAM 状态。\r\n");bootupdate_process = START_PROGRAM;return;}while(1){delay_ms(1);if(USB_STA!=USB_STATUS_REG)//状态改变了 {if(USB_STATUS_REG&0x01)//正在写{printf("USB Writing...\r\n");//提示USB正在写入数据	 }if(USB_STATUS_REG&0x02)//正在读{printf("USB Reading...\r\n");//提示USB正在读出数据  		 }		if(USBD_User_App() == FR_OK)  //检测到有对应的bin文件{bootupdate_process=UPDATE_SUCCESS;delay_ms(500);break;}if(USB_STATUS_REG&0x04)printf("USB Write Err\r\n");//提示写入错误	  if(USB_STATUS_REG&0x08)printf("USB Read Err\r\n");//提示读出错误 USB_STA=USB_STATUS_REG;//记录最后的状态}if(Device_STA!=bDeviceState) {if(bDeviceState==CONFIGURED){LED1_ON;//提示USB连接已经建立}else {LED1_OFF;//提示USB被拔出了}Device_STA=bDeviceState;}tct++;if(tct==200){tct=0;LED1_TOGGLE;//提示系统在运行if(USB_STATUS_REG&0x10){LED1_ON;offline_cnt=0;//USB连接了,则清除offline计数器bDeviceState=CONFIGURED;}else//没有得到轮询 {LED1_OFF;offline_cnt++;  if(offline_cnt>10)bDeviceState=UNCONNECTED;//2s内没收到在线标记,代表USB被拔出了}USB_STATUS_REG=0;}}break;case UPDATE_SUCCESS:bootupdate_process=START_PROGRAM;write_setting_boot_state(bootupdate_process);NVIC_SystemReset();break;default:break;}
}int main(void)
{USART_Config();LED_GPIO_Config();printf("\r\n 使用指南者底板时 左上角排针位置 不要将PC0盖有跳帽 防止影响PC0做SPIFLASH片选脚 \r\n");bootupdate_process=(Update_Process)read_setting_boot_state();if(Key_Scan(KEY1_GPIO_PORT,KEY1_GPIO_PIN) == 1){bootupdate_process=UPDATE_PROGRAM;}while (1){iap_process();}
}
3、配置USB

        这部分根据野火代码移植而来,usb硬件配置自己看代码理解就好,一般没什么问题,根据上面的分区,读写内部FLASH时需要偏移0x08044800。U盘底层读写函数需要特别注意,如果U盘出bug,大概率是以下几个函数有误。

tips:在写函数时不能全部擦除一个扇区,需要把不需要更改的数重新写上,我就是一直卡在这一步,自己每次都是擦除一个扇区,再写一个扇区,然后永远都格式化不了U盘,但是我换到N32的硬件环境上又能格式化U盘,使用正点原子的STMFLASH_Write和STMFLASH_Read就没问题了,这问题就。。。以后有时间再解决了。

uint16_t MAL_Write(uint8_t lun, uint64_t Memory_Offset, uint32_t *Writebuff, uint16_t Transfer_Length)
{switch (lun)		//这里,根据lun的值确定所要操作的磁盘{case 0:		 	//磁盘0为 DEV FLASH盘	STMFLASH_Write(DOWNLOAD_SECTOR_ADDR + Memory_Offset,(u16*)Writebuff,Transfer_Length/2);break; case 1:			//磁盘1为SD卡		  
//			STA=SD_WriteDisk((u8*)Writebuff, Memory_Offset>>9, Transfer_Length>>9);   		  break;default:return MAL_FAIL;}return MAL_OK; 
}uint16_t MAL_Read(uint8_t lun, uint64_t Memory_Offset, uint32_t *Readbuff, uint16_t Transfer_Length)
{switch (lun)		//这里,根据lun的值确定所要操作的磁盘{case 0:			//磁盘0为 DEV FLASH盘 STMFLASH_Read(DOWNLOAD_SECTOR_ADDR + Memory_Offset,(u16*)Readbuff,Transfer_Length/2);break;	  case 1:			//磁盘1为SD卡		    
//			STA=SD_ReadDisk((u8*)Readbuff, Memory_Offset>>9, Transfer_Length>>9);	   break;default:return MAL_FAIL;}return MAL_OK;
}uint16_t MAL_GetStatus (uint8_t lun)
{switch(lun){case 0:Mass_Block_Size[0] =0x400;			//设置FLASH的操作扇区大小为1024Mass_Block_Count[0]=DOWNLOAD_SECTOR_SIZE/0x400;   //238Mass_Memory_Size[0]=DOWNLOAD_SECTOR_SIZE;	//总字节												  return MAL_OK;case 1:											  return MAL_OK;default:return MAL_FAIL;} 
}
4、配置fatfs文件系统

        这部分也是根据野火代码移植而来,新增了一个卷标2,需要特别注意扇区大小和数量需要和USB配置的一样。

#define SD_CARD	 0  //SD卡,卷标为0
#define EX_FLASH 1	//外部flash,卷标为1
#define DEV_FLASH 2 //内部flash,卷标为2//读扇区
DRESULT disk_read (BYTE pdrv,		/* Physical drive nmuber to identify the drive */BYTE *buff,		/* Data buffer to store read data */DWORD sector,	/* Sector address in LBA */UINT count		/* Number of sectors to read */
)
{DRESULT status = RES_PARERR;  	 switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flashbreak;case DEV_FLASH: for(;count>0;count--){			STMFLASH_Read(DOWNLOAD_SECTOR_ADDR + sector*1024,(u16*)buff,1024/2);sector++;buff+=1024;}status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//写扇区
DRESULT disk_write (BYTE pdrv,			/* Physical drive nmuber to identify the drive */const BYTE *buff,	/* Data to be written */DWORD sector,		/* Sector address in LBA */UINT count			/* Number of sectors to write */
)
{uint32_t write_addr;  DRESULT status = RES_PARERR;if (!count) {return RES_PARERR;		/* Check parameter */}switch(pdrv){case SD_CARD://SD卡break;case EX_FLASH://外部flashbreak;case DEV_FLASH: for(;count>0;count--)//写函数不能全部擦除一个扇区,需要把不需要更改的数重新写上{			STMFLASH_Write(DOWNLOAD_SECTOR_ADDR + sector*1024,(u16*)buff,1024/2);								    sector++;buff+=1024;}status = RES_OK;break;default:status = RES_PARERR; }return status;
}
//其他表参数的获得
DRESULT disk_ioctl (BYTE pdrv,		/* Physical drive nmuber (0..) */BYTE cmd,		/* Control code */void *buff		/* Buffer to send/receive control data */
)
{DRESULT status = RES_PARERR;switch (pdrv) {case SD_CARD:	/* SD CARD */status = RES_OK;break;case EX_FLASH:break;case DEV_FLASH: switch(cmd){case CTRL_SYNC:		status = RES_OK; break;	 /* 扇区大小  */case GET_SECTOR_SIZE:*(WORD*)buff = 1024;status = RES_OK;break;	 /* 同时擦除扇区个数 */case GET_BLOCK_SIZE:*(DWORD*)buff = 1;status = RES_OK;break;	 /* 扇区数量:119*2*1024/1024=238(KB) */case GET_SECTOR_COUNT:*(DWORD*)buff =119*2;status = RES_OK;break;default:status = RES_PARERR;break;}break;default:status = RES_PARERR;}	return status;
}
5、程序跳转

        跳转这部分网上也很多,基本没什么区别,关于中断可能要注意一下,可能跳转之前,某些外设中断是开启的,跳转之后,中断产生了,但是APP代码中没有处理对应该中断的中断处理函数,所以就可能会直接死机。

tips:本文的开发环境只有64k ram,所以需要特别注意这里,虽然bootloader的ram不太可能会超。

if (((*(__IO uint32_t*)appxaddr) & 0x2FFF0000 ) == 0x20000000) 

详细参考以下文章:

关于STM32单片机IAP升级中if(((*(__IO uint32_t*)ulAddr_App) & 0x2FFE0000) == 0x20000000)语句的理解-CSDN博客

uint8_t iap_load_app(u32 appxaddr)
{uint8_t i;uint32_t jump_addr;if (((*(__IO uint32_t*)appxaddr) & 0x2FFF0000 ) == 0x20000000) {  jump_addr = *(__IO uint32_t*) (appxaddr + 4);  jump2app = (iapfun)jump_addr;  /* 关闭所有中断,清除所有中断挂起标志 */  for (i = 0; i < 8; i++){NVIC->ICER[i]=0xFFFFFFFF;NVIC->ICPR[i]=0xFFFFFFFF;}	__set_MSP(*(__IO uint32_t*)appxaddr);  jump2app();return 1;}return 0;
}	

三、APP程序制作

        这部分设置一下flash的偏移量就行。

NVIC_SetVectorTable(NVIC_VectTab_FLASH, 0x9000);

四、工程配置(默认KEIL5)

BootLoader部分:0x08000000~0x08007FFF

APP部分:0x08009000~0x080447FF

五、运行测试 

        长按KEY1点击复位(断电重启)可以看到识别到U盘(H:),大小也正常,然后往里面复制一个固件firmware.bin(注意这里的固件名一定要是唯一的,不然程序识别不到),复制进去就会执行升级程序,可以看到打印信息显示固件更新成功,本文和上一篇文章不同的是每次都按照一个扇区1024byte来写和擦除。

结束语

以上内部FLASH模拟U盘升级固件功能已实现,这只是其中的一种升级方式,后面大家看到的也希望可以得到大家的指点。主要的USB库和fatfs库移植教程就不给出来了,网上很多,基本都能实现。

完整代码下载地址:内部FLASH模拟U盘升级固件资源-CSDN下载

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

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

操作系统引导过程

操作系统引导是指计算机利用 CPU 运行特定程序&#xff0c;通过程序识别硬盘&#xff0c;识别硬盘分区&#xff0c;识别硬盘分区上的操作系统&#xff0c;最后通过程序启动操作系统。 引导流程&#xff08;8步核心环节&#xff09; 1. 激活CPU 加电后CPU自动读取 ROM中的Boot…

Safetensors与大模型文件格式全面解析

Safetensors是一种专为存储大型张量数据设计的文件格式&#xff0c;由Hugging Face团队开发&#xff0c;旨在提供安全高效的模型参数存储解决方案。下面将详细介绍Safetensors格式及其特点&#xff0c;并全面梳理当前主流的大模型文件格式。 一、Safetensors格式详解 1. 基本概…

分布式理论:CAP、Base理论

目录 1、CAP理论 1.1、介绍 1.2、CAP的三种选择 1.3、CAP的注意事项 2、BASE理论 2.1、定义介绍 2.2、最终一致性的介绍 2.3、BASE的实现方式 2.4、与ACID的对比 3、CAP与BASE的联系 4、如何选择CAP 前言 在分布式系统中&#xff0c;CAP理论和BASE理论是指导系统设计…

【最新】飞算 JavaAl安装、注册,使用全流程,让ai自己给你写代码,解放双手

目录 飞算 JavaAl 产品介绍 安装飞算 JavaAl 第一步&#xff1a;点击 File->Setting 第二步&#xff1a;点击 Plugins 第三步&#xff1a;搜索 CalEx-JavaAI 第四步&#xff1a;点击 Install 进行安装 第五步&#xff1a;点击 Install &#xff0c;查看安装好的飞算…

无人设备遥控器之姿态控制算法篇

无人设备遥控器的姿态控制算法通过传感器数据融合、控制算法优化和执行机构调节实现动态平衡&#xff0c;核心算法包括PID控制、自适应控制、模型预测控制&#xff08;MPC&#xff09;&#xff0c;以及数据融合中的互补滤波和卡尔曼滤波&#xff0c;同时涉及四元数算法和深度强…

【加解密与C】Base系列(三)Base85

Base85 编码简介 Base85&#xff08;也称为 Ascii85&#xff09;是一种二进制到文本的编码方案&#xff0c;用于将二进制数据转换为可打印的ASCII字符。它的效率高于Base64&#xff0c;但生成的字符串可能包含特殊字符&#xff08;如引号或反斜杠&#xff09;&#xff0c;需在…

Docker企业级应用:从入门到生产环境最佳实践

一、Docker核心概念与架构 1.1 Docker技术栈 #mermaid-svg-CUEiyGo05ZYG524v {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-CUEiyGo05ZYG524v .error-icon{fill:#552222;}#mermaid-svg-CUEiyGo05ZYG524v .error-te…

8、保存应用数据

目录用户首选项的使用用户首选项主要API用户首选项开发流程用户首选项开发实践关系型数据库的使用关系型数据库工作流程关系型数据库开发实践用户首选项的使用 用户首选项主要API 用户首选项开发流程 成功的获取了一个名为myStore的Preferences实例 保存了一个键值对&#x…

(C++)list列表相关基础用法(C++教程)(STL库基础教程)

源代码&#xff1a;#include <iostream> #include <list>using namespace std;int main(){list<int> numbers{10,20,30};numbers.push_front(5);numbers.push_back(40);auto it numbers.begin();advance(it,2);numbers.insert(it,15);cout<<"该列…

Spring CGLIB私有方法访问成员变量为null问题

场景 代码 RestController public class TestJob {Autowiredprivate XxService xxService;XxlJob("testCGLIB")private void doTest(){System.out.println("方法调用");System.out.println("成员变量注入:"(xxService!null));this.doInnerTest()…

Paimon本地表查询引擎LocalTableQuery详解

LocalTableQueryLocalTableQuery 是 Paimon 中实现本地化、带缓存的表查询的核心引擎。它的主要应用场景是 Flink 中的 Lookup Join。当 Flink 作业需要根据一个流中的 Key 去关联一个 Paimon 维表时&#xff0c;LocalTableQuery 可以在 Flink 的 TaskManager 节点上&#xff0…

使用协程简化异步资源获取操作

异步编程的两种场景 在异步编程中&#xff0c;回调函数通常服务于两种不同场景&#xff1a; 一次性资源获取&#xff1a;等待异步操作完成并返回结果。持续事件通知。监听并响应多个状态变更。 Kotlin为这两种场景提供了解决方案&#xff1a;使用挂起函数简化一次性资源获取…

ABP VNext + Cosmos DB Change Feed:搭建实时数据变更流服务

ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680; &#x1f4da; 目录ABP VNext Cosmos DB Change Feed&#xff1a;搭建实时数据变更流服务 &#x1f680;TL;DR ✨&#x1f680;1. 环境与依赖 &#x1f3d7;️2. 服务注册与依赖注入 &…

STM32-定时器

定时器&#xff1a;有4个独立通道&#xff1a;输入捕获&#xff1b;输出比较PWM生成&#xff1b;单脉冲模式输出&#xff1b;可通外部信号控制定时器&#xff08;TIMx-ETR&#xff09;&#xff1b;支持针对定时的增量&#xff08;正交&#xff09;编码器、霍尔传感器电路通用定…

Windows Server 2019--职业技能大赛B模块Windows服务器配置样题

一、赛题说明 &#xff08;一&#xff09;竞赛介绍 请详细阅读网络拓扑图&#xff0c;为所有计算机修改默认防火墙以便允许ICMP和相应的流量&#xff0c;不允许直接关闭主机的防火墙。除了CD-ROM/HDD驱动器&#xff0c;请不要修改虚拟机本身的硬件设置。 &#xff08;二&…

vue3+Echarts实现立体柱状图

Echarts柱状图中文网&#xff1a;https://echarts.apache.org/examples/zh/index.html#chart-type-bar 效果展示&#xff1a; 主要实现过程是三部分的组合&#xff0c;最上面是一个椭圆&#xff0c;中间是正常的柱子&#xff0c;下方再加上一个椭圆&#xff0c;就出来立体的效…

【UE5】虚幻引擎小百科

一、类名前面的大写字母的含义是什么UE5常见前缀分类表前缀含义实例用于AActorACharacter&#xff0c;AWeaponBase可放入世界中的对象&#xff08;有位置、可碰撞等&#xff09;UUObject派生类UUserWidget&#xff0c;UWeaponComponent引擎对象、逻辑模块&#xff0c;不具备Tra…

【Linux系统】vim编辑器 | 编译器gcc/g++ | make/Makefile

1. vim编辑器一、历史发展与Vim vs Vi的区别起源与演进Vi&#xff08;1976年&#xff09; &#xff1a;由Bill Joy开发&#xff0c;嵌入BSD Unix系统&#xff0c;是首个面向屏幕的文本编辑器&#xff0c;但功能有限&#xff08;如无多级撤销&#xff09;。Vim&#xff08;1991年…

国产飞腾主板,赋能网络安全防御硬手段

​ 当前&#xff0c;网络安全形势严峻&#xff0c;网络攻击手段不断翻新&#xff0c;从数据泄露到电脑中毒&#xff0c;企业、机构乃至国家的数字资产都面临着巨大风险。在此背景下&#xff0c;国产硬件技术的突破对筑牢网络安全防线意义重大。 高能计算机基于市场需求&#…

Spring AI 概述与架构设计

目录一、前言二、简介三、核心能力概览四、理解模块架构图五、模型适配能力六、最小应用示例七、与传统 LLM 调用相比八、总结九、参考一、前言 在 AI 正以前所未有的速度“下沉”到各类系统与业务的当下&#xff0c;Spring 官方推出的 Spring AI 项目&#xff0c;为 Java 开发…