首页 > 评测 > 【APM32F407IG Tiny Board测评】移植轻量级AI推理框架TinyMaix实现手写数字识别

【APM32F407IG Tiny Board测评】移植轻量级AI推理框架TinyMaix实现手写数字识别

  
  • 作者:
  • 来源:
  • [导读]
  • 本文将介绍如何为APM32F407IG芯片移植轻量级AI推理框架TinyMaix,并在开发板上运行TinyMaix的手写数字识别示例。本文将首先介绍TinyMaix是什么,以及APM32F407IG芯片的主要参数。然后,介绍如何为APM32F407IG芯片创建
本文将介绍如何为APM32F407IG芯片移植轻量级AI推理框架TinyMaix,并在开发板上运行TinyMaix的手写数字识别示例。本文将首先介绍TinyMaix是什么,以及APM32F407IG芯片的主要参数。然后,介绍如何为APM32F407IG芯片创建一个支持基于SysTick计时,以及printf输出到UART的Keil项目。接着,介绍如何将TinyMaix项目核心框架和手写数字识别示例源码添加到支持SysTick计时和printf输出的Keil项目中。最后,介绍如何将编译好的二进制程序下载到APM32F407IG Tiny开发板上,并运行手写数字识别示例程序。

一、背景介绍

开始之前,首先介绍一下轻量级AI推理框架TinyMaix,以及本次使用活动的APM32F407IG Tiny Board的主控芯片APM32F407IGT6

1.1 TinyMaix框架简介

TinyMaix是国内sipeed团队开发一个轻量级AI推理框架,官方介绍如下:
TinyMaix 是面向单片机的超轻量级的神经网络推理库,即 TinyML 推理库,可以让你在任意单片机上运行轻量级深度学习模型。
根据官方介绍,在仅有2K RAM的 Arduino UNO(ATmega328, 32KB Flash, 2KB RAM) 上,都可以基于 TinyMaix 进行手写数字识别。对,你没有看错,2K RAM + 32K Flash的设备上都可以使用TinyMaix进行手写数字识别!TinyMaix官网提供了详细介绍,可以在本文末尾的参考链接中找到。
TinyMaix项目源码时以 Apache-2.0协议开源的,
GitHub代码仓:https://github.com/sipeed/tinymaix
TinyMaix项目源代码仓中,包含了TinyMaix核心框架代码、示例程序、常用模型、测试图片、文档等内容。

1.2 TinyMaix上层API

TinyMaix框架对上层应用程序提供的核心API主要位于代码仓的tinymaix.h文件中,核心API如下:

/******************************* MODEL FUNCTION ************************************/
tm_err_t tm_load  (tm_mdl_t* mdl, const uint8_t* bin, uint8_t*buf, tm_cb_t cb, tm_mat_t* in);   //load model
void     tm_unload(tm_mdl_t* mdl);                                      //remove model
tm_err_t tm_preprocess(tm_mdl_t* mdl, tm_pp_t pp_type, tm_mat_t* in, tm_mat_t* out);            //preprocess input data
tm_err_t tm_run   (tm_mdl_t* mdl, tm_mat_t* in, tm_mat_t* out);         //run model

/******************************* UTILS FUNCTION ************************************/
uint8_t TM_WEAK tm_fp32to8(float fp32);
float TM_WEAK tm_fp8to32(uint8_t fp8);

/******************************* STAT FUNCTION ************************************/
#if TM_ENABLE_STAT
tm_err_t tm_stat(tm_mdlbin_t* mdl);                    //stat model
#endif

主要分为三类:
  • 模型函数,包括模型加载、卸载、预处理、推理;
  • 工具函数,包含FP32和uint8的互转;
  • 统计函数,用于输出模型中间层信息;
这里的模型,通常是预训练模型经过脚本转换生成的TinyMaix格式的模型;

1.3 TinyMaix底层依赖

TinyMaix可以简单理解为一个矩阵和向量计算库,目前已支持如下几种计算硬件:

#define TM_ARCH_CPU         (0) //default, pure cpu compute
#define TM_ARCH_ARM_SIMD    (1) //ARM Cortex M4/M7, etc.
#define TM_ARCH_ARM_NEON    (2) //ARM Cortex A7, etc.
#define TM_ARCH_ARM_MVEI    (3) //ARMv8.1: M55, etc.
#define TM_ARCH_RV32P       (4) //T-head E907, etc.
#define TM_ARCH_RV64V       (5) //T-head C906,C910, etc.
#define TM_ARCH_CSKYV2      (6) //cskyv2 with dsp core
#define TM_ARCH_X86_SSE2    (7) //x86 sse2

对于ARM-Cortex系列MCU,可以支持纯CPU计算和SIMD计算。其中CPU计算部分无特殊依赖(计算代码均使用标准C实现)。SIMD部分,部分计算代码使用了C语言内嵌汇编实现,需要CPU支持相应的汇编指令,才可以正常编译、运行。
TinyMaix的示例代码依赖于精准计时打印输出能力,具体是项目的tm_port.h中的几个宏定义:

#define  TM_GET_US()       ((uint32_t)((uint64_t)clock()*1000000/CLOCKS_PER_SEC))

#define TM_DBGT_INIT()     uint32_t _start,_finish;float _time;_start=TM_GET_US();
#define TM_DBGT_START()    _start=TM_GET_US();
#define TM_DBGT(x)         {_finish=TM_GET_US();\
                            _time = (float)(_finish-_start)/1000.0;\
                            TM_PRINTF("===%s use %.3f ms\n", (x), _time);\
                            _start=TM_GET_US();}1.4 APM32F407IGT6芯片简介

本次试用的APM32F407IG Tiny Board,主控芯片是APM32F407IGT6,它的主要参数为:
  • CPU: Cortex-M4F内核,168 MHz
  • Flash: 1 MB
  • RAM: 192 KB
TinyMaix的手写数字识别示例,在32KB Flash 2KB RAM的Arduino UNO R3上都可以运行。
在我们这次试用的主角APM32F407IG Tiny Board上运行是完全没有任何压力的。
开发板正面特写,可以看到主控芯片型号为APM32F407IGT6:

二、开发环境搭建2.1 下载APM32F407资料

极海官网APM32F07资料下载页: APM32F405/407 (geehy.com)
必须下载的文件包括:
  • APM32F405xG 407xExG数据手册
  • APM32F4xxx用户手册
  • APM32F407IG Tiny 原理图
  • APM32F4xx_SDK
  • APM32F4xx_DFP Pack
具体下载链接,参见本文末尾的参考链接部分。

2.2 安装Keil MDK

ARM官网Keil MDK安装包下载页面(需要填写问卷):
https://www.keil.com/download/product/
填写完问卷之后,可以看到,最新版本5.38a安装包文件下载页面:
这里的安装包文件的链接可以直接复制:
https://www.keil.com/fid/g6daezwlgtwj1wzt54w1lz01v1lvsy2w9vmud1/files/eval/mdk538a.exe
MD5: 6792e5e0c0b5207b4db8339e043d7461
PS: 使用下载工具下载完成后,记得验证一下MD5值。
Keil本身的安装没啥难度,一路下一步就好了,这里不再详细介绍。

2.3 安装ARM Compiler 5

APM32F4xx_SDK需要ARM Compiler 5才可以顺利编译,用Keil MDK较新版本默认附带ARM Compiler 6无法正常编译。因此,需要下载ARM Compiler 5。
ARM Compiler下载页面为: https://developer.arm.com/documentation/ka005198/latest
可以看到ARM Compiler 5最后一个版本是:5.06 update 7 (build 960)
点击中间的ACOMP5蓝色链接,可以跳转到该版本编译器的下载页面(需要注册登录):
这里下载下方的Win32版本即可。
ARM Compiler下载页面的文件链接是动态生成的,无法直接复制出来。
我用某雷下载之后,转存了一个云盘链接,方便国内用户下载:
链接:https://pan.xunlei.com/s/VN_A6-Wskfwt4IYWpJLHgsGkA1?pwd=h23k#MD5: 56a3c52585e7ce4d95fc75ae6ff6b9df

2.4 安装APM32F4xx支持包

Keil MDK APM32F4系列MCU支持包: https://www.geehy.com/uploads/tool/Geehy.APM32F4xx_DFP.1.0.3.pack
下载完成后,双击安装即可(PS:成功安装Keil MDK之后,.pack文件会自动关联到Keil)。
到这里,基础开发环境已经搭建完成了。

三、运行SysTick示例程序

现在,我们需要为APM32F407IGT6芯片创建一个同时支持SysTick计时和printf输出的Keil项目。

3.1 下载APM32F4xx_SDK

首先,从极海官网上下载APM32F4xx_SDK,解压后可以在它的 Examples 子目录下找到 SysTick和USART示例,如下图所示:

3.2 编译SysTick示例程序

接着,打开SysTick目录下,SysTick_TimeBase\Project\MDK 子目录中的 SysTick_TimeBase.uvprojx 文件,可以看到该示例参与编译的源代码文件:
按F7键,进行编译,编译成功可以看到Build Output输出如下:

3.3 修改调试器设置

接下来,将APM32F407 Tiny开发板通过USB Type-C线连接到电脑。注意,开发板注意接在调试的USB口上。
按照如下步骤修改调试器设置:
  • 在Project视图中,鼠标右键APMFF407,弹出悬浮菜单,如下图所示:
  • 单击”Options for Target ‘APM32FF407’“,将会弹出,如下图所示:
  • 点击Debug标签页,界面如下图所示:
  • 下来”ULINK2/ME Cortex Debugger“菜单,选中CMSIS-DAP,如下图所示:
  • 继续点击刚刚的调试器下拉菜单右侧的”Settings“按钮,弹出CMSIS-DAP Cortex-M Target Driver Setup窗口,如下图所示:
PS:这里SW Device中已经成功识别到了ARM CoreSight SW-DP,说明调试和MCU之间通信正常。
  • 点Flash Download标签页,界面显示如下图所示:
查阅数据手册,这里参数都是正确的,不需要修改。
为了方便调试,建议将这里的Rest and Run勾选上,如下图所示:
勾选之后,记得点OK保存。

3.4 下载和运行SysTick示例程序

完成上述调试设置之后,就可以按F8或Download按钮开始下载成了。Download按钮,位置如下图所示:
开始下载后,Build Output窗口将会显示下载进度,下载成功后可以看到:
其中,Application running … 是勾选了Reset and Run才会有的。
如果没有意外,将会看到开发板上的LED灯已经开始闪烁了:

3.5 分析SysTick示例程序主要代码

我们继续看Project视图下的文件:
其中,各源码目录中的对应作用分别为:
  • CMSIS目录,启动(.s)和初始化(.c)
  • StdPeriphDriver目录,外设驱动库
  • Boards目录,APM32F407系列开发板条件编译
  • Application目录,中断处理(apm32f4xx_int.c)和应用代码(main.c)
接下来,看下main.c中的主要代码:
可以看到这里是一个点灯的代码,其中while循环调用了另外两个函数:
  • APM_MINI_LEDToggle,用于实现LED3灯状态翻转;
  • SysTick_Delay_ms,用于实现延时,单位毫秒;
这个while循环,就是闪灯不断LED2和LED,再延时一秒。
另外,还可以看到上面的截图中:
  • 75行有一行对COM1的初始化,查看实现代码可以知道就是USART1,串口参数为8n1;
  • 80行有一行printf打印语句,打印了一行文本,前后各有一个换行;
除此之外,main.c文件中还有如下代码:
通过注释,可以知道,这个函数实现了printf到串口的重定向。

3.6 查看SysTick示例程序的串口输出

前面分析知道,SysTick示例程序的main函数中初始化了USART1,并且有一行字符串打印,另外还实现了printf到串口的重定向。因此,如果没有什么意外的话,我们应该可以通过USART1看到这一行打印的输出。
接下来,查阅原理图的APM32-Link部分:
可以看到,APM32-Link预留了USART1的跳线。
然而不幸的是:
  • 开箱之后发现,APM32F407-Tiny开发板的J3、J5并没有附带跳线帽;
  • 通过USB线将APM32-Link连接到PC之后,并没有在设备管理器中看到串口设备;
没有跳线帽简单,从其他板子上扣下来一对就行了。而APM32-Link连接到PC之后没有识别到串口设备,可能原因有两个:
  • APM32-Link固件不支持USB转UART;
  • APM32-Link的主控芯片APM32F103C8T6不支持USB功能;
通过极海官网查阅APM32F103C8T6参数发现,它是支持USBD功能的,理论上可以实现USB转UART功能。那这里PC没有识别到串口设备的原因就是APM32-Link固件暂时还不支持USB转UART。
查阅极海官网APM32-Link介绍信息,发现确实如此,没有找到对USB转UART功能的说明。
所以,需要外接一个USB转UART用来查看串口输出,连接如下图:
其中,
  • 开发板的U2TX,连接到USB转串口的RX
  • 开发板的U2RX,连接到USB转串口的TX
接着,USB转串口和调试器插上PC,打开MobaXterm,设置如下参数:
打开新的会话之后,按下开发板的RESET按键,将会看到:
成功接收到了printf打印的字符串!
继续查阅原理图的USART1_TX和USART1_RX标号部分:
可以看到,
  • USART1_TX,对应PA9
  • USART1_RX,对应PA10
这也项目代码能够对应起来:
在Keil中一路查看:
  • APM_MINI_COMInit函数定义;
  • COM_TX_PORT、COM_TX_PIN_SOURCE数组定义;
  • MINI_COM1_TX_GPIO_PORT、MINI_COM1_TX_PIN_SOURCE宏定义;
找到的Board_APM32F407_MINI.h文件,在Keil的Project视图也可以看到:
该文件的完整路径为:APM32F407\APM32F4xx_SDK_v1.3\Boards\Board_APM32F407_MINI\inc\Board_APM32F407_MINI.h
我们试用的是APM32F407_Tiny开发板,实际上不应该用这个文件。但因为两个板子的串口配置代码是一样的,所以只使用串口输出的话,不修改也并不会影响串口输出。
两个开发板对应的头文件的主要差异在于,按键相关的宏定义不一样:
如果想要修改为方法是,在Options for Target ‘APM32F407’窗口的C/C++标签页中:
将APM32F407_MINI宏修改为APM32F407_TINY
修改之后,main.c中的也要响应修改:
  • APM_MINI_COMInit 替换为 APM_TINY_COMInit
  • APM_MINI_LEDInit 替换为 APM_TINY_LEDInit
  • APM_MINI_LEDToggle 替换为 APM_TINY_LEDToggle
四、移植TinyMaix核心库和手写数字识别示例
简单起见,接下来我们不再新建Keil项目,而是直接将TinyMaix核心库和手写数字识别示例代码拷贝到SysTick示例目录中(虽然这么做不太规范)。
4.1 下载TinyMaix源码到本地
首先,使用 git 命令下载TinyMaix最新源码:
git clone https://github.com/sipeed/tinymaix.git4.2 拷贝TinyMaix源码到Keil项目目录
接着,在SysTick示例的SysTick_TimeBase目录下,创建TinyMaix目录:
将TinyMaix源码的examples、include、src目录,拷贝到刚刚创建的TinyMaix目录中:
另外,创建tools目录,并将tools目录下的tmdl目录拷贝过去;拷贝的exmaples中包含了多个示例,可以把不需要的删除掉,只保留mnist目录,即手写数字识别示例。
4.3 将TinyMaix源文件添加到Keil项目中
首先,在Keil的Project视图右击APM32F407,选择Manage Project Items:
接着,在弹出的Manage Project Items窗口中,进行如下两步骤操作:
然后,点击Add Files,在弹出的文件添加界面中,依次添加刚刚拷贝过来的TinyMaix目录src目录和examples/mnist目录下的.c文件到项目中:
添加完成后,点击OK确认。
4.4 解决Keil项目的编译问题
完成上述修改后,直接编译,将会报错:
首先,按照如下步骤,添加必要的include搜索路径:
  • 在Project视图右键APM32F407,选择”Options for Target ‘APM32F407’“,如下图所示:
  • 在弹出的Options for Target ‘APM32F407’窗口中,点击C/C++标签页,如图下图所示:
  • 在C/C++标签页,点击Include Paths右侧的”…”按钮,会弹出Folder Setup界面,如下图所示:
  • 在Folder Setup界面中,分别点击如下两处,选择目录进行添加:
  • 在弹出的文件夹选择界面中,选择拷贝的TinyMaix的include目录,选择之后,如下图所示:
此时,再次编译项目,报错已经不一样了:
这里报错说,找不到”sys/time.h“文件。
直接双击error信息行,主编辑器区可以看到tm_port.h文件对应位置:
回顾开头1.4节,这里就是TinyMaix依赖的几个计时宏。
将其修改为:
接下来编译,遇到如下错误:
需要打开GNU扩展选项:
再次编译,报链接错误:
这两个连接错误说有两个main函数定义。
找到手写数字识别的main.c,将其中的main函数重命名为mnist_main:
再次编译,报错如下:
这次说,SysTick_Get没有定义。
打开Application下的main.c,Ctrl+F搜索TimingDelay:
找到如下引用:
将其全部替换掉为SysTickCount:
然后,修改main.c中的SysTick_Delay_ms和TimingDelay_Decrement函数,并新增SysTick_Get函数:
再次编译,终于成功了:
4.5 调用mnist示例程序
接下来,在main.c文件的main函数中添加两行代码:
记得保存Ctrl+S。
然后重新编译、下载,下载完成后,自动开始运行,可以看到串口输出:
运行报错了。
这里是因为,TinyMaix需要试用malloc申请内存,APM32F407的SysTick示例程序默认的堆大小配置不够。
需要修改 startup_apm32f40x.s 文件:
将其中的Heap_Size修改为0x1000(4096),默认的是0x200(512)
再次重新编译、下载、运行,串口可以看到:
这次成功运行了,耗时4毫秒,识别了手写数字2。
原始图片数据为:
可以看出来,识别结果是正确的。
PS:看了一下字数统计四千多字了,有点肝。
好了,本文就到这里了,感谢阅读。
五、参考链接
  • APM32F405xG 407xExG数据手册: https://www.geehy.com/uploads/tool/APM32F405xG 407xExG数据手册 V1.5.pdf
  • APM32F4xxx用户手册: https://www.geehy.com/uploads/tool/APM32F4xxx用户手册 V2.0.pdf
  • APM32F407IG Tiny 原理图: https://www.geehy.com/uploads/tool/APM32F407IG TinyBoard.zip
  • APM32F4xx_SDK: https://www.geehy.com/uploads/tool/APM32F4xx_SDK_v1.3.zip
  • APM32F4xx_DFP Pack: https://www.geehy.com/uploads/tool/Geehy.APM32F4xx_DFP.1.0.3.pack





  • 本文系21ic原创,未经许可禁止转载!

网友评论