不会AI的嵌入式工程师,手把手教你系统跑通第一个STM32 AI模型(多图预警,建议收藏备用)
这里是《贺老师讲嵌入式AI》我《嵌入式AI让单片机学会思考》课程主理人专注AI在MCU上的落地实践。本文手把手记录从新建 STM32CubeMX/STM32CubeIDE 工程到导入 sinemodel.tflite再到在 STM32F407 上运行 CubeAI 推理并通过串口显示正确正弦波的完整过程。1. 实验目标本实验最终要实现的效果是STM32F407 上电后初始化 GPIO、USART1 和 X-CUBE-AI 模型MCU 内部自动生成正弦模型输入x周期性运行模型推理然后通过串口把理论正弦值、模型推理值和误差发送给上位机显示。项目配置MCUSTM32F407VET6工具链STM32CubeIDESTM32CubeMX6.16.1STM32Cube FW_F4V1.28.3X-CUBE-AI10.2.0模型文件sinemodel.tfliteCubeAI 应用模板ApplicationTemplateUSART1PA9 / PA101152008N1LEDPB12 / PB13低电平点亮上位机协议帧头0x7E长度0x08帧尾0x7F2. 新建 STM32CubeIDE 工程打开 STM32CubeIDE选择File - New - STM32 Project。在 MCU/MPU Selector 中搜索STM32F407VET6选择对应芯片后新建工程如下所示Project Name: STM32F407_TinyML Targeted Language: C Targeted Binary Type: Executable Targeted Project Type: STM32Cube Toolchain/IDE: STM32CubeIDE容易踩坑芯片型号要和板子一致STM32F407 系列有不同封装和 Flash/RAM 规格。如果芯片型号选错可能导致下载失败、链接脚本不匹配、引脚不存在甚至工程能编译但烧录后运行异常。模型部署对 Flash 和 RAM 更敏感所以第一步就要选对芯片。3. 基础外设配置3.1 配置调试接口在System Core - SYS中把 Debug 设置为Serial Wire这样会占用PA13和PA14作为 SWD 调试口。3.2 配置时钟本实验模型非常小只有 321 MACC。为了先把部署链路跑通可以使用默认 HSI 16 MHz不必一开始就配置到最高主频。RCC_OscInitStruct.OscillatorType RCC_OSCILLATORTYPE_HSI; RCC_OscInitStruct.HSIState RCC_HSI_ON; RCC_OscInitStruct.PLL.PLLState RCC_PLL_NONE; RCC_ClkInitStruct.SYSCLKSource RCC_SYSCLKSOURCE_HSI;调试建议先用简单时钟把模型部署、串口协议和上位机显示跑通。等链路确认无误后再切换 HSE PLL 提高主频。3.3 配置 LED本实验使用PB12和PB13作为 LED 输出PB12 - LED1 PB13 - LED2由于开发板 LED 是低电平点亮所以 GPIO 默认输出应该设置为GPIO_PIN_SET也就是上电默认熄灭。#define LED1_GPIO_Port GPIOB #define LED1_Pin GPIO_PIN_12 #define LED2_GPIO_Port GPIOB #define LED2_Pin GPIO_PIN_13 #define LED_ON GPIO_PIN_RESET #define LED_OFF GPIO_PIN_SET #define LED_BLINK_HALF_PERIOD_MS 500U容易踩坑低电平点亮不是低电平熄灭很多开发板 LED 一端接 VCCMCU 引脚输出低电平时灌电流点亮。因此GPIO_PIN_RESET是点亮GPIO_PIN_SET是熄灭。3.4 配置 USART1USART1 用于给上位机发送波形数据PA9 - USART1_TX PA10 - USART1_RX Baud Rate: 115200 Word Length: 8 Bits Parity: None Stop Bits: 1 Hardware Flow Control: None容易踩坑文本调试和二进制协议不能混在一起最终上位机要解析二进制帧。如果同一串口里还在输出printf(hello\r\n)就会破坏帧结构。工程中保留文本调试函数但默认关闭#define UART_TEXT_DEBUG_ENABLED 0U4. 添加 X-CUBE-AI 并导入模型在 STM32CubeMX 的 Software Packs 中启用 X-CUBE-AISoftware Packs - Select Components - X-CUBE-AI启用后在左侧会出现 X-CUBE-AI 相关配置页面。导入模型D:\model\sinemodel.tflite模型名称保持为network当前工程.ioc中对应的关键配置如下STMicroelectronics.X-CUBE-AI.10.2.0.ModelStructureFile-...D\:\\model\\sinemodel.tflite STMicroelectronics.X-CUBE-AI.10.2.0.ModelName-...network STMicroelectronics.X-CUBE-AI.10.2.0.ModelKind-...TFLITE STMicroelectronics.X-CUBE-AI.10.2.0.ModelCompression-...None5.模型分析导入模型后点击开始分析分析完成6. Generate Code 后编译成功7. 读取 CubeAI 生成报告确认量化参数模型导入后X-CUBE-AI 会生成报告X-CUBE-AI\App\network_generate_report.txt本模型报告中的关键信息input 1/1 : int8(1x1), QLinear(0.024573976,-128,int8) output 1/1 : int8(1x1), QLinear(0.008472007,4,int8) macc : 321 weights : 420 B activations: 64 B截图如下7.1 QLinear(scale, zero_point) 的含义量化模型中真实浮点值和 int8 值之间的关系是real_value (quantized_value - zero_point) * scale反过来如果有一个浮点输入要送进 int8 模型就要做quantized_value round(real_value / scale zero_point)本模型输入参数为scale 0.024573976 zero_point -128因此输入量化代码是static int8_t sine_quantize_input(float value) { float qf (value / SINE_AI_INPUT_SCALE) (float)SINE_AI_INPUT_ZERO_POINT; int32_t q (int32_t)((qf 0.0f) ? (qf 0.5f) : (qf - 0.5f)); if (q 127) { q 127; } else if (q -128) { q -128; } return (int8_t)q; }本模型输出参数为scale 0.008472007 zero_point 4因此输出反量化代码是static float sine_dequantize_output(int8_t value) { return ((float)value - (float)SINE_AI_OUTPUT_ZERO_POINT) * SINE_AI_OUTPUT_SCALE; }容易踩坑不要把 int8 模型当 float 模型使用如果报告显示输入是int8就必须按量化参数写入 int8。输出也是 int8不能直接当 float 解析必须先反量化。8. 理解 CubeAI 输入输出 buffer报告中还有一句很重要(*) input/output buffers are allocated in the activations buffernetwork.h中也能看到#define AI_NETWORK_INPUTS_IN_ACTIVATIONS (4) #define AI_NETWORK_OUTPUTS_IN_ACTIVATIONS (4)这意味着输入输出 buffer 由 CubeAI 放在 activations 内存池里。ApplicationTemplate初始化后会拿到这些地址ai_input ai_network_inputs_get(network, NULL); ai_output ai_network_outputs_get(network, NULL); for (int idx0; idx AI_NETWORK_IN_NUM; idx) { data_ins[idx] ai_input[idx].data; } for (int idx0; idx AI_NETWORK_OUT_NUM; idx) { data_outs[idx] ai_output[idx].data; }之后采集函数只需要写入data[0][0] (ai_i8)sine_quantize_input(sine_input_x);推理完成后读取输出sine_model_y sine_dequantize_output((int8_t)data[0][0]);容易踩坑不要自己随便换输入输出数组如果 X-CUBE-AI 配置了allocate-inputs和allocate-outputs输入输出已经在 activations buffer 中。手动换成另一个数组可能导致ai_network_run()使用的 buffer 和你写入的 buffer 不是同一个。9. 编写周期推理逻辑在X-CUBE-AI/App/app_x-cube-ai.c的USER CODE区域中定义输入变化参数#define SINE_AI_FRAME_PERIOD_MS 20U #define SINE_AI_INPUT_STEP 0.05f #define SINE_AI_TWO_PI 6.28318530717958647692f static float sine_input_x 0.0f;每次推理后让x增加static void sine_advance_input(void) { sine_input_x SINE_AI_INPUT_STEP; if (sine_input_x SINE_AI_TWO_PI) { sine_input_x - SINE_AI_TWO_PI; } }当前配置下每 20 ms 推理一次每次x增加 0.05 rad一圈约 126 个点一个完整正弦周期大约 2.5 秒适合上位机观察。void MX_X_CUBE_AI_Process(void) { uint32_t now HAL_GetTick(); if ((sine_ai_ready ! 0U) ((now - sine_last_frame_tick) SINE_AI_FRAME_PERIOD_MS)) { sine_last_frame_tick now; if (acquire_and_process_data(data_ins) 0) { if (ai_run() 0) { post_process(data_outs); } } } }调度建议不要在主循环中用长时间HAL_Delay()阻塞。AI 推理、LED 和串口发送都可以用HAL_GetTick()做周期调度。10. 设计上位机串口协议上位机协议如下字段含义帧头0x7E长度0x04表示 8 位0x08表示 16 位0x10表示 32 位或 float曲线1理论正弦值曲线2模型推理值曲线3推理值 - 理论值曲线4绝对误差帧尾0x7F本实验最终选择 16 位模式长度 0x08 四路数据 每路 2 字节 整帧长度 1 1 2 * 4 1 11 字节帧格式0x7E 0x08 C1_L C1_H C2_L C2_H C3_L C3_H C4_L C4_H 0x7F采用小端序发送static void sine_protocol_put_i16(uint8_t *buffer, int16_t value) { uint16_t raw (uint16_t)value; buffer[0] (uint8_t)(raw 0xFFU); buffer[1] (uint8_t)((raw 8) 0xFFU); }最终发送函数static void sine_protocol_send_frame(int16_t curve1, int16_t curve2, int16_t curve3, uint16_t curve4) { uint8_t frame[11]; frame[0] SINE_PROTOCOL_HEADER; frame[1] SINE_PROTOCOL_UINT16_LEN; sine_protocol_put_i16(frame[2], curve1); sine_protocol_put_i16(frame[4], curve2); sine_protocol_put_i16(frame[6], curve3); sine_protocol_put_u16(frame[8], curve4); frame[10] SINE_PROTOCOL_TAIL; HAL_UART_Transmit(huart1, frame, sizeof(frame), 100U); }11. Release 编译并下载因为实际下载使用的是 Release所以要确认 Release 能编译通过而不是只看 Debug。Project Explorer - 右键工程 Build Configurations - Set Active - Release Project - Build Project本工程最终 Release 编译成功大小大致如下text data bss dec hex 23400 1692 4776 29868 74ac12. 上位机显示设置上位机设置串口对应 USB 转串口端口 波特率115200 数据位8 校验位None 停止位1 精度16 位 帧头0x7E 帧尾0x7F曲线含义判断标准曲线1理论正弦值应为平滑正弦波曲线2模型推理值应与曲线1基本重合但允许模型近似误差曲线3推理误差应围绕 0 上下变化曲线4绝对误差应始终为正值欢迎有不同意见的“老板们”和同学们在评论区讨论、交流