南京制作網站公司網站seo1視頻發(fā)布會
1 引言
????????STM32 微控制器在嵌入式領域應用廣泛,因為它性能不錯、功耗低,還有豐富的外設,像工業(yè)控制、智能家居、物聯(lián)網這些場景都能看到它的身影。與此同時,人工智能技術發(fā)展迅速,也逐漸融入各個行業(yè)。
????????把 AI 部署到 STM32 上,能讓嵌入式系統(tǒng)更智能,不過這并不容易。AI 模型運行需要大量計算資源和內存,而 STM32 硬件資源有限。所以,找到合適工具很重要。
????????今天介紹兩款能在 STM32 上部署 AI 的實用軟件,Nanoedge AI Studio 和 STM32Cube AI。它們能幫開發(fā)者解決難題,順利在 STM32 上部署 AI 算法,下面就詳細講講這兩款軟件。
2?Nanoedge AI Studio
2.1 軟件獲取
Nanoedge AI Studio是用于STM32部署邊緣AI的軟件,Studio可生成四種類型的庫:異常檢測、單分類、多分類、預測。它支持所有類型的傳感器,所生成的庫不需要任何云連接,可以直接在本地學習與部署,支持STM32所有MCU系列。需要搭配STM32CubeMX、Keil或STM32CubeIDE等編譯器使用。
下面這個鏈接是ST的下載網址:
?https://www.st.com/en/development-tools/nanoedgeaistudio.html
下載過程中會收到郵件,里面有l(wèi)icense,第一次使用軟件需要用到
到此,軟件就獲取完畢
2.2 軟件介紹
前面已提到,Studio可生成四種類型的庫:異常檢測、單分類、多分類、預測。左上角四個模型分別對應這四種庫(AD:異常檢測,nC:多分類,1C:單分類,E:預測),然后下面是以前做過的一些歷史工程。
下面對這四種庫講一下使用流程:
2.2.1 異常檢測
異常檢測:異常檢測是指模型對正常數據與異常數據進行訓練、學習后,生成的模型可以識別生成的邊緣AI可以判斷數據是正常還是錯誤。
這里可能會有疑問,為什么不使用閾值判斷等方法,甚至加個濾波可能更加準確,這么做不是更簡單嗎,在這里使用AI的作用在哪?
答案是,第一,得到的數據難免會出現(xiàn)上下浮動的問題,如何選取閾值是一個值得考慮的問題,對于界限沒那么清晰的情況,經常需要經過多次實驗確定閾值。而如果使用AI,我們只需要告訴模型,一種情況下的現(xiàn)象是如何,另一種情況下的現(xiàn)象是如何,直接由算法幫我們分析。
第二,也是比較關鍵的一點,如果我們需要根據多維參數確定一個整體的綜合情況,比如評價水質,我們可能會從多角度去考慮(溫度、濁度、PH值、含氧量等),那么這個時候,設定閾值就會非常難。
綜上,使用AI來做異常檢測,會使得問題考慮起來更容易。
在學習這里的時候,在幾個平臺看到了幾個比較經典的例子,可以引入異常檢測:
1.風扇、電機等異常檢測:通過電流傳感器、ADC等檢測電流、電壓等物理量,我試過通過MPU6050加速度傳感器監(jiān)測電機是否在正常工作
2.聲音檢測:通過聲音傳感器反饋出電壓模擬量
3.無人機飛行異常檢測:沒有仔細去研究參考的物理量,個人認為需要通過上述提到的物理量綜合考慮
當然還有一些例子,這里就不一一列舉了,大家可以嘗試做一下
那么,下面就開始講解軟件基本用法:
點開左上角的AD,進入模型構建界面:
第一個界面是Project Setting:
第一步確定模型在ROM和RAM上的最大占據量,這里我直接用默認
第二步,在右邊確定芯片型號。這里比較好的一點是,我可以直接選擇一個系列,這個系列里的所有型號都能夠通用
第三步,選擇參考量的個數。如果想用三軸加速度傳感器,那么顯然這時需要參考三個量,點擊第一個圖標即可選擇。當然后面還有電流量等等的檢測,這里就不再贅述了。
當然,上面也提到了,使用AI來做異常檢測的好處是能綜合多個量判斷狀態(tài),這時點擊Cross sectional選項可以選擇Multi-sensor,注意參考量個數的選擇,幾個量就是幾個Axes。
這里,我選擇三軸加速度傳感器作為演示實例。
第二個界面是Regular signals。前面提到,需要引入正常數據和異常數據進行檢測,這里就是采集正常數據。
點擊Add Signal,然后看到有兩個方法:
一個是導入本地csv格式的數據表格,另一種是用串口向電腦發(fā)送數據。
對于導入csv表格,軟件內的提示我感覺寫的并不是很清晰,我的理解是:進行的一次實驗測得的數據寫在一行內。測得的每個數據,都要按照x,y,z的順序來,然后不要空格。比如我得到三個方向加速度,分別為x,y,z。第一次實驗,我在一段時間內測得200次,也就是得到200組加速度數據,第一組為x1,y1,z1,第二組為x2,y2,z2。那么,放在表格里的順序是:x1,y1,z1,x2,y2,z2,......第一次實驗完成后進行第二次實驗,把實驗數據寫在第二行,每行最多有256組數據,然后實驗次數不要低于20次,也就是行數不低于20行。
這里注意的一點是,不要寫任何題頭,直接頂格寫數據。
對于用串口發(fā)送數據,可能會更加方便一點,但前提是不對數據再做人工處理。但這里需要寫程序把數據按順序通過串口發(fā)送電腦上。下面的是我用MPU6050發(fā)送數據的代碼,其他的思路都是一樣的,供大家參考。
char buf[30];int cnt;MPU6050_Init();while (1){sprintf(buf,"%ld,%ld,%ld,",AccelData[0],AccelData[1],AccelData[2]);MPU6050ReadAcc(Accel);for( int i=0;i<3;i++){if(Accel[i]>=0){AccelData[i]=Accel[i]*2000/32768;}else{AccelData[i]=-(-Accel[i]+1)*2000/32768;} }HAL_UART_Transmit(&huart1,(uint8_t*)buf,strlen(buf),0xFFFF);cnt++;if(cnt==256){cnt=0;HAL_UART_Transmit(&huart1,(uint8_t*)"\r\n",2,0xFFFF);}
}
每一次實驗的數據要用回車隔開,每個數要用特定字符隔開,這里我用的是逗號,當然其他一些符號也行,大家可以自己研究,最后導入之前再把提示的異常數據刪除即可。
這時就能看到導入的數據的直觀圖了,至于RUN OPTIONAL CHECKS那里,我覺得點不點都行,這個的目的是檢查數據的質量。
第三個部分是對異常情況下數據的采集,一樣的,就不贅述了
第四個部分Benchmark就是模型訓練了,這里會自動計算若干個算法對于導入的這些數據的適配性
訓練完如此,可以選擇最好的幾個進行仿真測試。
最后一步,就是自動生成代碼了。
左邊這幾個選項,第三個和第四個勾選即可,
第二個如果導入的數據里沒有浮點數,就不要勾選,
如果想同時使用多個模型,勾選第一個,并命名。
最后點擊COMPILE LIBRARY,生成文件。
這里需要導入編譯器的文件是libenai.a和NanoEdgeAI.h,找到自己工程的位置
打開Core文件夾,里面有Src和Inc兩個文件夾,把libenai.a放到Src文件夾里,NanoEdgeAI.h放到Inc文件夾里。然后打開編譯器,我這里使用Keil
?導入到工程里,然后雙擊libneai.h文件,點第一個
文件類型選Library file?
?至此文件就導入完畢了
在Studio,最后一步右邊還有自動生成的代碼
/* Includes --------------------------------------------------------------------*/
#include "NanoEdgeAI.h"//直接粘貼
/* Number of samples for learning: set by user ---------------------------------*/
#define LEARNING_ITERATIONS 30//直接粘貼
float input_user_buffer[DATA_INPUT_USER * AXIS_NUMBER]; //直接粘貼
void fill_buffer(float input_buffer[])//直接粘貼,后續(xù)需要在這里加代碼填充input_buffer[]
{/* USER BEGIN *//* USER END */
}/* -----------------------------------------------------------------------------*/
int main(void)
{/* Initialization ------------------------------------------------------------*/enum neai_state error_code = neai_anomalydetection_init();//直接粘貼uint8_t similarity = 0;//直接粘貼if (error_code != NEAI_OK) {/* This happens if the library works into a not supported board. */}//直接粘貼/* Learning process ----------------------------------------------------------*/for (uint16_t iteration = 0 ; iteration < LEARNING_ITERATIONS ; iteration++) {fill_buffer(input_user_buffer);neai_anomalydetection_learn(input_user_buffer);}//直接粘貼/* Detection process ---------------------------------------------------------*/while (1) {fill_buffer(input_user_buffer);neai_anomalydetection_detect(input_user_buffer, &similarity);//直接粘貼/* USER BEGIN *//** e.g.: Trigger functions depending on similarity* (blink LED, ring alarm, etc.).*//* USER END */}
}
similarity代表相似度,相似度越高代表越正常,最高為100,最低為0,低于一定值就代表異常,一般認為90以下異常
最后,別忘勾選Short enums/wchar(之前在Studio里勾選,這里照應上了)
?到這里就應該可以正常編譯了
2.2.2 單分類/多分類
單分類的是指,將數據進行導入,進行機器學習后,可以判斷出設備的正?;蛘弋惓!_@里與異常判斷不同的是,這里會將所有非正常的數據判斷為異常,而異常判斷卻可以分析出當前數據與正常數據的相似度。
單分類跟異常檢測還是很像的,這里就不多說了,唯一需要說的是,導入數據的時候要把兩類都導入,同樣多分類也是,也不多說了
2.2.3 預測
模型可以對過往的數據進行分析,訓練,生成的模型可以根據近期的數據近似分析出下一個時刻可能的數據,大家也可以自行研究。
至此,Nanoedge AI Studio講解完畢
3 STM32Cube AI
3.1 軟件獲取
X-CUBE-AI是STM32Cube.AI生態(tài)系統(tǒng)的STM32Cube擴展軟件包的一部分,通過自動轉換預訓練的神經網絡并將生成的優(yōu)化庫集成到用戶的項目中,擴展了STM32CubeMX功能。
簡而言之就是通過X-Cube-AI擴展將當前比較熱門的AI框架進行C代碼的轉化,以支持在嵌入式設備上使用,目前使用X-Cube-AI需要在STM32CubeMX版本5.0以上,目前支持轉化的模型有Keras、TF lite、ONNX等,還算比較牛的,Cube-AI把模型轉化為一堆數組,而后將這些數組內容解析成模型,和Tensorflow里的模型轉數組后使用原理是一樣的。
在工程內,或Manage Embedded Software Packages里安裝
3.2 軟件介紹
我這里使用8.1版本,導入什么類型的模型就選什么格式,選擇STM32Cube.AI runtime,Compression代表壓縮模型,如果模型過于復雜,那么根據需求選擇壓縮程度,Optimization是選擇時間和空間的協(xié)調程度,即時間優(yōu)先還是空間優(yōu)先
?然后點擊Analyze分析模型,分析結束后可以直接生成代碼,打開編譯器,這里著重講一下代碼,需要對自動生成的代碼有一定了解。
寫程序基本的思路是:輸入數據(預處理)、模型推理、輸出數據(后處理)
先看main.c文件,自動生成的代碼如下:
MX_X_CUBE_AI_Init();
while (1)
{MX_X_CUBE_AI_Process();
}
顯然,第一句是AI初始化,while內的是AI的數據處理
AI的初始化我沒有改動,主要看AI的數據處理過程,右鍵MX_X_CUBE_AI_Process()看這個函數的定義
void MX_X_CUBE_AI_Process(void)
{/* USER CODE BEGIN 6 */int res = -1;printf("TEMPLATE - run - main loop\r\n");if (network) {do {/* 1 - acquire and pre-process input data */res = acquire_and_process_data(data_ins);/* 2 - process the data - call inference engine */if (res == 0)res = ai_run();/* 3- post-process the predictions */if (res == 0)res = post_process(data_outs);} while (res==0);}if (res) {ai_error err = {AI_ERROR_INVALID_STATE, AI_ERROR_CODE_NETWORK};ai_log_err(err, "Process has FAILED");}/* USER CODE END 6 */
}
其實這里就可以看出前面提到的三個步驟,但這里的邏輯我感覺沒有說很好,于是決定自己封裝函數。
這里就不過多講解了,其實這里需要對app_x_cube_ai.c文件里的代碼做一定的了解,
比如代碼里涉及到的數據類型,這里換了個名,要能看懂
typedef uint8_t ai_custom_type_signature;typedef void* ai_handle;
typedef const void* ai_handle_const;typedef float ai_float;
typedef double ai_double;typedef bool ai_bool;typedef char ai_char;typedef uint32_t ai_size;
typedef int16_t ai_short_size;typedef uintptr_t ai_uptr;typedef unsigned int ai_uint;
typedef uint8_t ai_u8;
typedef uint16_t ai_u16;
typedef uint32_t ai_u32;
typedef uint64_t ai_u64;typedef int ai_int;
typedef int8_t ai_i8;
typedef int16_t ai_i16;
typedef int32_t ai_i32;
typedef int64_t ai_i64;typedef uint64_t ai_macc;typedef int32_t ai_pbits;typedef uint32_t ai_signature;typedef void (*ai_handle_func)(ai_handle);
CubeMX也提供了一些示例代碼,也有一個大概的了解
/* USER CODE BEGIN 2 */
int acquire_and_process_data(ai_i8* data[])
{/* fill the inputs of the c-modelfor (int idx=0; idx < AI_NETWORK_1_IN_NUM; idx++ ){data[idx] = ....}*/return 0;
}int post_process(ai_i8* data[])
{/* process the predictionsfor (int idx=0; idx < AI_NETWORK_1_OUT_NUM; idx++ ){data[idx] = ....}*/return 0;
}
/* USER CODE END 2 */
最后附上我自己封裝的代碼,供大家參考。
我訓練的模型是輸入5個float數據,輸出1個ai_i32數據
app_x_cube_ai.c文件
ai_buffer* Get_Inputs(void)
{ai_u16 *in_buffer=(short unsigned int*)0;return ai_network_inputs_get(network,in_buffer);
}
ai_buffer* Get_Outputs(void)
{ai_u16 *out_buffer=(short unsigned int*)0;return ai_network_outputs_get(network,out_buffer);
}
void PrepareInput(ai_float* InputData)
{ai_input=Get_Inputs();ai_input->data=InputData;
}
ai_i32* Predict()
{ai_output=Get_Outputs();ai_run();return (ai_i32*)ai_output->data;
}
void NetPrint()
{printf("Input shape:");for(int i=0;i<Get_Inputs()->shape.size;i++){printf("%d ",Get_Inputs()->shape.data[i]);}printf("\r\n");printf("Output shape:");for(int i=0;i<Get_Outputs()->shape.size;i++){printf("%d ",Get_Outputs()->shape.data[i]);}printf("\r\n");
}
.h文件
void PrepareInput(ai_float* InputData);
void NetPrint(void);
ai_i32* Predict(void);
main.c文件
float Data[6][5]={{1,450,23,7,60},{0,640,25,8,10},{2,420,25,8,0},{3,680,27,8.5,20},{4,400,24,7,80},{5,530,22,7.5,40}};
NetPrint();while (1){ for(int k=0;k<6;k++){float InputData[5]={Data[k][0],Data[k][1],Data[k][2],Data[k][3],Data[k][4]};PrepareInput(InputData);printf("InputData:%f %f %f %f %f\r\n",InputData[0],InputData[1],InputData[2],InputData[3],InputData[4]);printf("outData:%d\r\n",*Predict());printf("\r\n");}}
測試結果與上位機一致。至此,STM32Cube AI的基本使用講解結束。