• 正文
  • 相关推荐
申请入驻 产业图谱

STM32N6的开发日记(3):如何利用CubeMX快速部署我们的神经网络模型

4小时前
130
加入交流群
扫码加入
获取工程师必备礼包
参与热点资讯讨论

STM32N6作为意法半导体推出的首款集成自研神经处理单元的STM32产品以“MCU+NPU”的异构架构重新定义了边缘AI算力边界,是意法半导体的MCU最前沿技术栈,不过由于其高难度技术应用以及需要的极其深厚的STM32使用经验以及神经网络基础概念,因此上手难度非常的高。

自从STM32N6发布以来,博主有幸获得一块STM32N6570-DK开发板,闲暇之余陆陆续续折腾如何开发。因此将会陆陆续续发表一些使用STM32N6的使用笔记,以供将来的使用者参考。

回顾学习历程,踩了很多很多的坑,在后续使用STM32N6的文章中也会向大家陆续介绍这些点。

使用这块芯片最大的亮点就是在其上运行神经网络单元,本期我们将介绍如何在导入并初步运行和验证我们的神经网络模型。

1准备工作

在开始之前,先介绍一下我们需要准备哪些东西:首先是一个神经网络模型,这里我选择使用ST提供的目标检测模型(单目标:人)

明确其网络架构,例如选用yolov8n_320_quant_pc_uf_od_coco-person.tflite模型,则代表着模型输入为320*320*3的RGB888类型图片,输出为单目标模型,F=2100,因此维度信息为:(1,5,2100),模型总大小为2.96MB。

开发工具为STM32CubeIDE用于代码编程,STM32CubeMX用于初始化配置和STM32CubeProgram用于程序烧录。

同时意法半导体还提供了ST Edge AI工具用来帮助神经网络模型量化和辅助部署在STM32N6中,但是操作起来有点麻烦还要去找NPU的驱动库,官方在某一次更新的时候将其功能整合在了STM32CubeMX中,因此可以直接在CubeMX中进行模型处理和部署。

2、STM32CubeMX配置

在上一期的基础上,我们完成了如何在STM32N6中实现FSBL_XIP模式,配置了外部Flash和SPRAM来存放我们的代码。

外部Flash和SPRAM配置如上图所示,具体的配置信息可以参考上一期文章。

神经网络部署   

原本在N6中使用NPU和神经网络模型的处理需要依靠ST EdgeAI-Core工具:

操作起来比较繁琐,并且没有没有特别多的社区资料用于参考。但是在某一次CubeMX的更新中,STM32CubeMX中的CubeAI插件也继承了ST Edge AI Core的功能,专用于STM32N6系列模型分析和部署。

首先点击X-CUBE-AI将软件包切换到Application工程,因为我们的模型大小为2.96MB而FSBL空间仅为511K,不足以存放模型,因此要放在更大的Application空间。

Profile用于规定RAM和ROM的使用大小,例如在n6-allmems-O3这个方案中,点击右边的齿轮可以查看当前方案的分配情况:

例如规定外部RAM使用地址从0x90000000开始,外部Flash从0x7100000开始,注意这个信息很重要。外部RAM从0x90000000开始的话,我们在使用的时候就注意,不要造成地址复用,比方说用户在采集摄像头图像的时候,不要存在这个地方,方式和神经网络运算过程中造成内存污染。

外部Flash从0x71000000开始,则代表着神经网络模型权重的下载地址为0x71000000,这个信息后面要用!

根据情况修改内存分配情况,我们暂时不改。

点击分析,STM32CubeMX会调用STEdgeAI的命令来帮助我们部署模型并生成处理报告。

处理报告信息   

报告中这部分信息交代了我们的模型使用内存情况,例如该模型使用了npuRAM3,RAM4部分还有2.905MB大小的权重文件位于外部FLASH,首地址为0x71000000。

该部分交代了这个模型的各个部分分别是利用什么来运算,HW为硬件NPU运算,SW为软件CPU运算。

内存管理 

在内存配置中开启AXISRAM3和AXISRAM4(我们使用的RAM)

串口调试 

开启系统串口1用于调试,注意STM32N6570-DK的USART1分别是PE5和PE6。

3代码测试

接下来生成我们的工程,在正式进行测试之前,我们要先进行一些准备工作:

串口重定向

#define PUTCHAR_PROTOTYPE int __io_putchar(int ch)
  PUTCHAR_PROTOTYPE
  {
  HAL_UART_Transmit(&huart1, (uint8_t *)ch, 1, 0xFFFF);
return ch;
  }
int _write(int fd, char * ptr, int len){
  HAL_UART_Transmit(&huart1, (uint8_t *) ptr, len, HAL_MAX_DELAY);
return len;
  }

首先是串口重定向,串口重定向让我们可以用printf来输出USART1的内容,方便我们待会打印内容,注意该步骤需要包含头文件stdio.h。

RIF安全配置

  /* USER CODE BEGIN SysInit */
  __HAL_RCC_RIFSC_CLK_ENABLE();
  RIMC_MasterConfig_t RIMC_master = {0};
  RIMC_master.MasterCID = RIF_CID_1;
  RIMC_master.SecPriv = RIF_ATTRIBUTE_SEC | RIF_ATTRIBUTE_PRIV;
  HAL_RIF_RIMC_ConfigMasterAttributes(RIF_MASTER_INDEX_NPU, &RIMC_master);
  HAL_RIF_RISC_SetSlaveSecureAttributes(RIF_RISC_PERIPH_INDEX_NPU, RIF_ATTRIBUTE_PRIV | RIF_ATTRIBUTE_SEC);

  /* USER CODE END SysInit */

  /* Initialize all configured peripherals */
  MX_GPIO_Init();
  MX_CACHEAXI_Init();
  MX_USART1_UART_Init();
  MX_RAMCFG_Init();
  MX_X_CUBE_AI_Init();
  SystemIsolation_Config();
  /* USER CODE BEGIN 2 */

在Application工程中,添加NPU的安全权限,否则系统会直接卡死的。

内存使能

voidMX_X_CUBE_AI_Init(void)
{
    set_clk_sleep_mode();
    __HAL_RCC_NPU_CLK_ENABLE();
    __HAL_RCC_NPU_FORCE_RESET();
    __HAL_RCC_NPU_RELEASE_RESET();
    npu_cache_init();
/* USER CODE BEGIN 5 */
    __HAL_RCC_AXISRAM1_MEM_CLK_ENABLE();
    __HAL_RCC_AXISRAM2_MEM_CLK_ENABLE();
    __HAL_RCC_AXISRAM3_MEM_CLK_ENABLE();
    __HAL_RCC_AXISRAM4_MEM_CLK_ENABLE();
    __HAL_RCC_AXISRAM5_MEM_CLK_ENABLE();
    __HAL_RCC_AXISRAM6_MEM_CLK_ENABLE();

    RAMCFG_SRAM2_AXI->CR &= ~RAMCFG_CR_SRAMSD;
    RAMCFG_SRAM3_AXI->CR &= ~RAMCFG_CR_SRAMSD;
    RAMCFG_SRAM4_AXI->CR &= ~RAMCFG_CR_SRAMSD;
    RAMCFG_SRAM5_AXI->CR &= ~RAMCFG_CR_SRAMSD;
    RAMCFG_SRAM6_AXI->CR &= ~RAMCFG_CR_SRAMSD;
/* USER CODE END 5 */
}

在MX_X_CUBE_AI_Init初始化函数中,为内存管理使能。

4、NPU运行代码

voidMX_X_CUBE_AI_Process(void)
{
/* USER CODE BEGIN 6 */
  LL_ATON_RT_RetValues_t ll_aton_rt_ret = LL_ATON_RT_DONE;
const LL_Buffer_InfoTypeDef * ibuffersInfos = NN_Interface_Default.input_buffers_info();
const LL_Buffer_InfoTypeDef * obuffersInfos = NN_Interface_Default.output_buffers_info();
  buffer_in = (uint8_t *)LL_Buffer_addr_start(&ibuffersInfos[0]);
  buffer_out = (uint8_t *)LL_Buffer_addr_start(&obuffersInfos[0]);

// Printing buffer start and end addresses.
  LL_ATON_RT_RuntimeInit();
// run 10 inferences
for (int inferenceNb = 0; inferenceNb<10; ++inferenceNb) {
/* ------------- */
/* - Inference - */
/* ------------- */
/* Pre-process and fill the input buffer */
// Fill input buffer with constant data.
//_pre_process(buffer_in);
/* Perform the inference */
    LL_ATON_RT_Init_Network(&NN_Instance_Default);  // Initialize passed network instance object
do {
/* Execute first/next step */
      ll_aton_rt_ret = LL_ATON_RT_RunEpochBlock(&NN_Instance_Default);
/* Wait for next event */
if (ll_aton_rt_ret == LL_ATON_RT_WFE) {
        LL_ATON_OSAL_WFE();
      }
    } while (ll_aton_rt_ret != LL_ATON_RT_DONE);
/* Post-process the output buffer */
/* Invalidate the associated CPU cache region if requested */
//_post_process(buffer_out);
    LL_ATON_RT_DeInit_Network(&NN_Instance_Default);
/* -------------------- */
/* - End of Inference - */
/* -------------------- */
  }
  LL_ATON_RT_RuntimeDeInit();
/* USER CODE END 6 */
}

MX_X_CUBE_AI_Process为神经网络推理部分代码,大致可以分为三个部分:输入数据处理、NPU运行、数据后处理。

  typedefstruct
  {
    constchar *name;             /**< Buffer name. NULL if end of list */
    __LL_address_t addr_base;     /**< Buffer base address */
    uint32_t offset_start;        /**< Offset of the buffer start address from the base address */
    uint32_t offset_end;          /**< Offset of the buffer end address from the base address
                                   *   (first bytes address beyond buffer length) */
    uint32_t offset_limit;        /**< Offset of the limiter address from the base address,
                                   *   (needed for configuring streaming engines) */
    uint8_t is_user_allocated;    /**< */
    uint8_t is_param;             /**< */
    uint16_t epoch;               /**< */
    uint32_t batch;               /**< */
    constuint32_t *mem_shape;    /**< shape as seen by the user in memory (only valid for input/output buffers) */
    uint16_t mem_ndims;           /**< Number of dimensions of mem_shape (Length of mem_shape) */
    Buffer_CHPos_TypeDef chpos;   /**< Position  of channels dimension in mem shape */
    Buffer_DataType_TypeDef type; /**< */
    int8_t Qm;                    /**< */
    int8_t Qn;                    /**< */
    uint8_t Qunsigned;            /**< */
    uint8_t ndims;                /**< */
    uint8_t nbits;                /**< */
    uint8_t per_channel;          /**< */
    constuint32_t *shape;        /**< */
    constfloat *scale;           /**< */
    constint16_t *offset;        /**< This can become int8 or uint8 based on the Qunsigned field.
                                   *   (This field Must have the same format of the quantized value) */
  } LL_Buffer_InfoTypeDef;

LL_Buffer_InfoTypeDef结构体包含了模型的基本信息,包括模型运行过程中各类数据缓冲区的核心属性与量化特征。完整记录了缓冲区的命名、基地址、有效地址偏移范围(起始 / 结束 / 限制偏移量)等内存布局信息,也通过is_user_allocated、is_param等标识区分缓冲区的分配归属(用户分配 / 系统分配)和用途类型(参数缓冲区 / 数据缓冲区);同时,epoch、batch字段适配了批量推理、多轮次执行的场景需求。

结构体通过mem_shape、mem_ndims、chpos定义了数据在内存中的实际存储维度、维度数量及通道维度位置,而type字段则标识了缓冲区的数据类型;对于量化模型,Qm、Qn、Qunsigned、nbits、per_channel等字段精准描述了量化参数(量化位数、符号类型、量化系数),scale(缩放因子)、offset(偏移量)字段,完整还原量化张量的数值映射关系,shape字段则补充了模型逻辑层面的张量维度信息。

代码试试  

  LL_ATON_RT_RetValues_t ll_aton_rt_ret = LL_ATON_RT_DONE;
  const LL_Buffer_InfoTypeDef * ibuffersInfos = NN_Interface_Default.input_buffers_info();
  const LL_Buffer_InfoTypeDef * obuffersInfos = NN_Interface_Default.output_buffers_info();
  buffer_in = (uint8_t *)LL_Buffer_addr_start(&ibuffersInfos[0]);
  buffer_out = (uint8_t *)LL_Buffer_addr_start(&obuffersInfos[0]);
  // Printing buffer start and end addresses.
  uint32_t buff_in_len, buff_out_len;
  printf("输入地址偏移开始 = %lu, \n \r 输入地址偏移结束 = %lu \n \r",ibuffersInfos->offset_start,ibuffersInfos->offset_end);
  printf("输出地址偏移开始 = %lu, \n \r 输出地址偏移结束 = %lu \n \r",obuffersInfos->offset_start,obuffersInfos->offset_end);
  // Getting buffer size and printing it.
  buff_in_len = ibuffersInfos->offset_end - ibuffersInfos->offset_start;
  buff_out_len = obuffersInfos->offset_end - obuffersInfos->offset_start;
  printf("输入大小= %lu \n\r 输出大小 size = %lu \n\r", buff_in_len, buff_out_len);
  LL_ATON_RT_RuntimeInit();

我们打印一下输入缓存和输出缓存的信息内容查看一下结果:

因为我们的模型是320*320*3的图片,总计307200字节,完全对的上。

输出数据是1*2100*5的float类型,所以总大小为1*2100*5*4总计42000大小。

NPU运行测试   

在进行测试之前,我们还需要把权重文件烧录到单片机的指定地址中(前面设置过的0x71000000)

工程目录下随着STM32CubeMX的生成,有着一个raw格式的文件,我们将其后缀修改为.bin文件:

将其烧录到STM32N6的指定地址中。

  for (int inferenceNb = 0; inferenceNb<10; ++inferenceNb) {
    /* ------------- */
    /* - Inference - */
    /* ------------- */
    /* Pre-process and fill the input buffer */
    //_pre_process(buffer_in);
    /* Perform the inference */
    LL_ATON_RT_Init_Network(&NN_Instance_Default);  // Initialize passed network instance object
    do {
      /* Execute first/next step */
      ll_aton_rt_ret = LL_ATON_RT_RunEpochBlock(&NN_Instance_Default);
      /* Wait for next event */
      if (ll_aton_rt_ret == LL_ATON_RT_WFE) {
        LL_ATON_OSAL_WFE();
      }
    } while (ll_aton_rt_ret != LL_ATON_RT_DONE);
    /* Post-process the output buffer */
    /* Invalidate the associated CPU cache region if requested */
    //_post_process(buffer_out);


    float *floatout = (float *)buffer_out;
    for(int i = 0;i<2100;i++)
    {
      printf("index:%d [%.2f %.2f %.2f %.2f %.2f]\r\n",
          i,floatout[i*5],floatout[i*5+1],floatout[i*5+2],floatout[i*5+3],floatout[i*5+4]);
    }

    LL_ATON_RT_DeInit_Network(&NN_Instance_Default);
    /* -------------------- */
    /* - End of Inference - */
    /* -------------------- */
  }
  LL_ATON_RT_RuntimeDeInit();

接着我们在代码中添加:把输出地址,按照1*5*2100的形状,按照float类型打印出来,模型的运行结果如下:

这5个数据分别对应着Yolov8n模型输出的检测框的:中心横坐标x,中心纵坐标y,检测框宽还有检测框高以及各个类别的置信度,由于我们只有一个类别(人),因此输出就是5个一组。

由于我们的输入数据是随机的,因此输出数据也不可信,但是由于输出的所有结果都在0~1之间,符合Yolo格式的输出结果,因此可以证明神经网络的运行是完全正确的。

后面就是如何运行摄像头,摄像头图像到神经网络输入,输出后处理在屏幕中绘制:

意法半导体

意法半导体

意法半导体(ST)集团于1987年6月成立,是由意大利的SGS微电子公司和法国Thomson半导体公司合并而成。1998年5月,SGS-THOMSON Microelectronics将公司名称改为意法半导体有限公司。意法半导体是世界最大的半导体公司之一,公司销售收入在半导体工业五大高速增长市场之间分布均衡(五大市场占2007年销售收入的百分比):通信(35%),消费(17%),计算机(16%),汽车(16%),工业(16%)。 据最新的工业统计数据,意法半导体是全球第五大半导体厂商,在很多市场居世界领先水平。例如,意法半导体是世界第一大专用模拟芯片和电源转换芯片制造商,世界第一大工业半导体和机顶盒芯片供应商,而且在分立器件、手机相机模块和车用集成电路领域居世界前列.

意法半导体(ST)集团于1987年6月成立,是由意大利的SGS微电子公司和法国Thomson半导体公司合并而成。1998年5月,SGS-THOMSON Microelectronics将公司名称改为意法半导体有限公司。意法半导体是世界最大的半导体公司之一,公司销售收入在半导体工业五大高速增长市场之间分布均衡(五大市场占2007年销售收入的百分比):通信(35%),消费(17%),计算机(16%),汽车(16%),工业(16%)。 据最新的工业统计数据,意法半导体是全球第五大半导体厂商,在很多市场居世界领先水平。例如,意法半导体是世界第一大专用模拟芯片和电源转换芯片制造商,世界第一大工业半导体和机顶盒芯片供应商,而且在分立器件、手机相机模块和车用集成电路领域居世界前列.收起

查看更多

相关推荐