1. TinyMaix简介
TinyMaix 是矽速科技(Sipeed)专门为微控制器设计的轻量级开源机器学习库,可以在任意的MCU上运行轻量级深度学习模型。
TinyMaix 所消耗的资源非常小,在只有 2KB RAM,32KB Flash 的 Arduion ATmega328上都可以运行mnist(手写数字识别)。
关于 TinyMaix 详细介绍,可以到 Sipeed 科技的官网 Wiki 查看。
https://wiki.sipeed.com/news/others/tinymaix_cnx/tinymaix_cnx.html
下面是引用自官网 Wiki 对于 TinyMaix 的关键特性介绍:
关键特性
-
核心代码少于 400行(tm_layers.c+tm_model.c+arch_cpu.h), 代码段(.text)少于 3KB
-
低内存消耗,甚至 Arduino ATmega328 (32KB Flash, 2KB Ram) 都能基于TinyMaix跑mnist(手写数字识别)
-
支持 INT8/FP32/FP16模型,实验性地支持 FP8模型,支持keras h5或tflite模型转换
-
支持多种芯片架构的专用指令优化: ARM SIMD/NEON/MVEI,RV32P, RV64V
-
友好的用户接口,只需要load/run模型~
-
支持全静态的内存配置(无需malloc)
-
MaixHub 在线模型训练支持
2. 源码准备
2.1 APM32F411 SDK
我们是要在 APM32F411 上运行 TinyMaix 框架,需要准备的源码自然是APM32F411相关的SDK,与 TinyMaix 源码。
APM32F411的源码,可以到极海的官网获取:
https://www.geehy.com/support/apm32?id=311
下载了他们的SDK之后,在SDK目录下的Example目录,复制一份Template目录下的文件夹,改名为TinyMaix。我们会基于这个模板工程,实现TinyMaix在APM32F411上运行。
2.2 TinyMaix源码
TinyMaix 源码可以到他们官方的 Github 仓库进行下载:
https://github.com/sipeed/TinyMaix
直接 clone 到本地,或者下载压缩包都行。
下载完之后,我们把 TinyMaix 源码放到 APM32F411 SDK 目录下的 Middlewares 子目录里面备用。
TinyMaix 源码目录结构如下图:
各目录结构介绍如下:
-
doc:存放 TinyMaix 各个硬件平台接口的说明文档。
-
examples:TinyMaix官方提供的部分例程实例,比如手写数字识别、分类检测、物体识别等。
-
include:头文件,包括要移植TinyMaix的接口头文件也在该目录
-
src:TinyMaix的源码
-
tools:存放TinyMaix的一些工具。比如可以把图片文件,生成一个C语言数组,这样方便我们代码调用
3. TinyMaix源码简单介绍
下面简单介绍一下与移植有关的 TinyMaix 源码,以及 TinyMaix 提供了哪些 API 给用户使用。
3.1 底层硬件依赖
根据官方的介绍文档,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支持相应的汇编指令,才可以正常编译、运行。
3.2 编译等级选择
TinyMaix 目前支持两种等级:
-
选择最少代码和buf
-
选择速度,需要更多代码和buf
复制
#define TM_OPT0 (0) //default, least code and buf
#define TM_OPT1 (1) //opt for speed, need more code and buf
#define TM_OPT2 (2) //TODO
3.3 计时和调试宏
TinyMaix 有相关的计时和调试的宏定义,这些宏是需要我们移植的时候,针对不同的硬件平台要实现的部分接口代码,如下:
复制
/******************************* DBG TIME CONFIG ************************************/
#include <sys/time.h>
#include <time.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();}
3.4 核心API函数
对于 TinyMaix 框架对上层应用程序提供的核心 API 函数主要位于代码仓的 tinymaix.h 头文件中。
1、与模型相关的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
函数原型如上,包括模型加载、卸载、预处理、运行模型4个函数。
2、用于输出模型中间层信息的统计函数
复制
/******************************* STAT FUNCTION ************************************/
#if TM_ENABLE_STAT
tm_err_t tm_stat(tm_mdlbin_t* mdl); //stat model
#endif
3、FP32 和 uint8 类型的互转工具函数
复制
/******************************* UTILS FUNCTION ************************************/
uint8_t TM_WEAK tm_fp32to8(float fp32);
float TM_WEAK tm_fp8to32(uint8_t fp8);
4. 移植TinyMaix到APM32F411
前面我们已经把准备好的 TinyMaix 源码放到了 APM32F411 SDK 的 Middlewares 目录了,接下来的移植过程就需要用到了该目录下的源码了。下面基于 MDK-Keil 介绍下移植适配的过程。
4.1 Keil工程包含TinyMaix源码
Keil 工程中,主要添加的文件有:
-
src目录下的 C 文件
-
example目录下实例代码,比如 mnist, cifar10, vww 等,我们会在接下来的适配中,移植这3个实例代码,所以这3个相关的 C 文件也一起加进来。
由于 mnist, cifar10, vww 这3个实例代码,他们在 TinyMaix 源码中默认的文件名是 main.c ,由于我们已经有了 main.c 文件了,为了不冲突,我们在加入 Keil 工程之前,先把这些实例代码名称改一下。
接下来,打开 Keil 软件的工程分组管理,然后在工程下新建 TinyMaix 子目录,然后把所需要的文件添加进来。
4.2 添加文件包含路径
前面添加了 TinyMaix 源码之后,它相关的头文件路径要告诉 Keil 才能知道去哪里找到。
打开 Keil 配置界面,找到 C/C++ 配置选项,然后添加路径即可,如下图:
4.3 编译器选择 gun 扩展模式配置
在后面的编译中,我发现 TinyMaix 库的编译需要选择 gun externsions 模式,不热会有很多的报错,所以我们勾选上该模式即可。
4.4 解决编译报错
1、解决头文件找不到错误
添加了头文件路径之后,编译有如下报错,主要就是说找不到 sys/time.h 的头文件。
前面我们介绍过, TinyMaix 需要计时,这个头文件其实就是获取时间相关的函数在该文件声明,但是我们移植到 APM32F411 平台,是没有这个文件的,我们后面需要使用嘀嗒定时器去实现时间获取,所以此处暂时屏蔽该代码。
然后,计时的宏定义我们改为通过嘀嗒定时器获取,如下:
其中,uint32_t systick_get_us(void); 这个函数,是我们后面需要通过嘀嗒定时器去实现的函数,这里先写上。
2、解决 main 函数名重复定义错误
当再次编译报错如下:
只剩下链接报错了,这些报错都是因为 TinyMaix 的实例代码中,都有一个 main.c 函数重复定义造成的,我们找到对应的函数,修改下函数名即可。这里不多介绍。
3、解决 TinyMaix 实例代码中,变量名重复定义错误
再次编译,还剩下变量名的重复定义错误,这个是因为我们添加了 TinyMaix 的 3 个实例代码进Keil工程里面,然后他们使用了相同的变量名,我们找到这些变量,然后添加 static 关键字限制即可。
4、解决 systick_get_us 没有定义的报错
最后编译,只剩下 systick_get_us 这个函数没有定义的错误了,这个函数其实就是我们需要通过嘀嗒定时器实现的 us 获取函数。
这个函数的实现,我们在下面的代码中给出了。
4.5 systick_get_us函数实现
TinyMaix 需要使用计时,来获取时间。对于 APM32F411 我们就使用 Systick 来获取计时即可。实现代码如下:
复制
static volatile uint32_t tick = 0;
/* Millisecond timer */
void systick_init(void)
{
/* SystemFrequency / 1000 = 1ms */
if (SysTick_Config(RCM_ReadSYSCLKFreq() / 1000))
{
/* Capture error */
while (1);
}
}
/* Get millisecond */
uint32_t systick_get_ms(void)
{
return tick;
}
/* Get microsecond */
uint32_t systick_get_us(void)
{
return tick / 1000;
}
/* Syctick handler */
void SysTick_Handler(void)
{
tick++;
}
另外,还需要用到串口进行打印输出调试信息,我们还需要实现串口的打印输出,这里不多介绍了,自己实现就行。
4.6 修改堆空间的大小
TinyMaix 的实例在运行是,需要使用到堆空间的内存。而且,在运行人像识别的实例时,需要至少 54KB 的RAM (手写数字识别不需要这么多),所以我们定义堆空间为 60KB 大小就肯定够用了。
对于 APM32F411 来说,堆空间的大小是在启动文件 startup_apm32f411.s 定义的,我们在该文件定义堆空间的大小为 60KB 。代码如下:
5. TinyMaix运行效果
我们运行 TinyMaix 提供手写数字识别、分类检测、识别人像等实例。
5.1 mnist实例
该实例可以识别图像数字。我们在 main.c 文件的主函数中调用之前修改了名字的 main_mnist 这个函数即可。
代码如下:
复制
/*!
* [url=home.php?mod=space&uid=247401]@brief[/url] Main program
*
* @param None
*
* @retval None
*/
int main(void)
{
USART_Config_T usartConfigStruct;
usartConfigStruct.baudRate = 115200;
usartConfigStruct.hardwareFlow = USART_HARDWARE_FLOW_NONE;
usartConfigStruct.mode = USART_MODE_TX;
usartConfigStruct.parity = USART_PARITY_NONE;
usartConfigStruct.stopBits = USART_STOP_BIT_1;
usartConfigStruct.wordLength = USART_WORD_LEN_8B;
APM_MINI_COMInit(COM1, &usartConfigStruct);
APM_MINI_LEDInit(LED2);
APM_MINI_LEDInit(LED3);
//printf("\r\n========== TinyMaix ==========\r\n");
/* SysTick Initialization */
systick_init();
main_mnist(0, NULL);
//main_cifar10(0, NULL);
//main_vww(0, NULL);
while (1)
{
APM_MINI_LEDToggle(LED2);
APM_MINI_LEDToggle(LED3);
/* Precise Delay 1ms */
//SysTick_Delay_ms(1000);
}
}
然后编译运行效果如下图:
5.2 vww实例
vww实例,是检测图片有没有人,TinyMaix使用的人像图片如下:
然后运行结果是:
可以看出识别到了图片中有人。
5.3 cifar10实例
该实例进行分类检测,然后识别出有鸟的图片,TinyMaix 使用的鸟图片如下:
运行结果如下:
下面附件是工程源码,上传给大家以供参考。
---------------------
作者:luobeihai
链接:https://bbs.21ic.com/icview-3344148-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。