昨天有說到,通常當輸入把所有的模型資料都塞進 MCU 以後,可能會發現一些尷尬的事情,可以用的資源很有可能已經到了極限,如果還要加入其他的應用就變得有困難。因此今天要來分享如何使用外部記憶體來進行應用。
首先我們要先大概知道,大部分 MCU 是使用 Harvard 架構,這個架構大概如下表示:
這邊可以看到 MCU 透過 Instruction Memory(通常也就是 Flash Memory)進行指令讀取(也就是讀取程式碼),接著再透過 Data Memory(通常也就是 SRAM)讀取資料進行運算。
因此通常我們看到的 MCU 使用的 Memory 資源圖(如下圖所示)中的 ROM 和 RAM 分別指的就是上面的 Instruction Memory 和 Data Memory(這邊的 ROM 和 RAM 命名不一定,要看 Linker script 是怎麼寫的):
而這邊的資源通常都是指 CPU 上面原生的 Memory 資源,例如上面的圖就顯示了 U585 可以使用的原生 Internal Flash 和 Internal SRAM 資源,分別為 2MB 和 768KB,如果想要增加這個部分的資源,就需要透過加入外部的記憶特進行運算。
這邊可以先簡單看一下底下的 U5 系列的系統架構圖:
這邊可以看到除了原生擁有的 Internal Flash 和 SRAM 以外,右下角還提供了 OCTOSPI1 和 OCTOSPI2 可以讓你額外連接 External Flash / External SRAM。
而在 B-U585I-IOT2A 這塊開發板上,已經預先幫你先上好了一塊 SRAM 和 FLASH,分別就是掛在 OCTOSPI1 和 OCTOSPI2 上,因此可以直接進行使用。
使用 External Memory 來進行 EdgeAI 的操作分成兩個部分,一個是先要實現“如何透過連接介面(OCTOSPI)對連接的 External memory 進行控制”,另一個則是要怎麼把你想放進去得資料丟到相應的地區進行使用,而使用這塊開發板很好的事情是,幾乎兩個部分都幫你做好非常多的準備了,你只要稍微修改部分資料就能夠快速進行應用。
這邊就直接透過上次用到一半的 CubeMX 專案進行測試(Efficient Net 那個),來看看要怎麼使用。
首先可以到 X-AI Model 的部分進行上次沒操作到的 Memory 設定,首先是 RAM 的部分,把所有的 Activation buffer 都放到 External SRAM 裡面進行操作:
然後是 Flash 的部分,把 Network Data 生成獨立的 binary 檔案以後,放到 External Flash 裡面:
這邊可以看到地址填分別是 SRAM: 0x90000000 和 FLASH: 0x70000000,這個部分可以參考 U5 系列的 Datasheet:
接著可以直接按確認,CubeMX 就會重新計算整個網路的資訊並且設定,然後再重新生成 Code。
這時候可以在生成的 Code 裡面可以看到幾個檔案(應該會在 Driver/BSP 資料夾底下):
這幾個檔案就是 ST 預先寫好給 B-U585I-IOT2A 的 BSP 檔案(Board support package),這幾個檔案就是操作開發板上 External Flash / External SRAM 的 Driver,這個直接省去需要自己看著 Datasheet 刻程式的時間成本。
這時候我們回去看 void MX_X_CUBE_AI_Init(void)
(在app_x-cube-ai.c底下),理應會看到以下的程式碼:
void MX_X_CUBE_AI_Init(void)
{
MX_UARTx_Init();
// 初始化透過 OCTOSPI 連接的 External NOR FLASH
BSP_OSPI_NOR_Init_t ospiInit;
ospiInit.InterfaceMode=BSP_OSPI_NOR_OPI_MODE;
ospiInit.TransferRate=BSP_OSPI_NOR_STR_TRANSFER;
BSP_OSPI_NOR_Init(0, &ospiInit);
// 使用方式是利用 Memory Map,這樣就會把 Memory 映射到 0x70000000
BSP_OSPI_NOR_EnableMemoryMappedMode(0);
// 初始化透過 OCTOSPI 連接的 External SRAM
BSP_OSPI_RAM_Init(0);
// 使用方式是利用 Memory Map,這樣就會把 Memory 映射到 0x90000000
BSP_OSPI_RAM_EnableMemoryMappedMode(0);
aiSystemPerformanceInit();
/* USER CODE BEGIN 5 */
/* USER CODE END 5 */
}
如果沒有,可能需要自己補一下缺失的地方XD 因為這工具還是有一些 bug 要克服,可以看上面我的註解大概了解這些 code 分別是在做什麼。
當加好這些東西以後,可能還需要多做一件事情(不確定,因為感覺也是工具的 bug?),就是透過修改 Linker script 去把 activation buffer 指定到 External SRAM。
首先打開 STM32U585xx_FLASH.ld,修改開頭加入 External Memory:
/* Memories definition */
MEMORY
{
RAM (xrw) : ORIGIN = 0x20000000, LENGTH = 768K
ROM (rx) : ORIGIN = 0x08000000, LENGTH = 2048K
EXTSRAM (xrw) : ORIGIN = 0x90000000, LENGTH = 32M
SRAM4 (xrw) : ORIGIN = 0x28000000, LENGTH = 16K
}
然後再加入 activation buffer 的區段:
.activations :
{
. = ALIGN(8);
__activations_start__ = .;
*(.activations)
. = ALIGN(8);
__activations_end__ = .;
} >EXTSRAM
最後在 app_x-cube-ai.c 中把 pool0 指定到剛剛設定的區段:
AI_ALIGNED(32)
static uint8_t pool0[AI_NETWORK_DATA_ACTIVATION_1_SIZE] __attribute__((section(".activations")));
ai_handle data_activations0[] = {pool0};
完成以上的步驟就可以開始燒錄程式了,但還記得剛剛在 External Flash 上我們有把 Network data 分別成一個單獨的檔案,應該可以在專案根目錄底下看到這個檔案 network_data.bin,這個也需要單獨燒到 external flash 上,還好 ST 的工具也十分齊全,透過 ST32Cube-Programmer 就能輕鬆做到。
首先打開 Programmer,找到 External Loader(左下角第三個選項),然後選擇 B-U585I-IOT2A 這塊板子,並且勾選起來,如下圖:
接著再到 External Loader 中(左上選單第二個選項)選擇剛剛的 network_data.bin 檔案,並且燒錄,如下圖:
這樣 network_data.bin 就燒錄完成了,接著再正常燒錄程式剩餘編譯出來的程式就可以。
另外在編譯完成的時候,其實也可以注意到,相比於一開始的 Memory 使用量,這邊跑出來的結果差非常多:
這就是把資料分開燒錄以及加入External SRAM 的結果。
接著順利都燒錄進去以後,應該可以看到以下的結果:
那到這邊就完成了把 Network data 和 推理使用的 buffer 放到 External memory 的過程。
但...魔鬼藏在細節裡,難道這樣就結束了嗎?如果仔細看可能會發現好像有不少 trade-off 呢!這部分之後再來提~今天就先到這裡,下期再會~