STM32固件升級設計——內部FLASH模擬U盤升級固件

目錄

一、功能描述?

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 開發…