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 使用的鸟图片如下:

运行结果如下:

下面附件是工程源码,上传给大家以供参考。

 APM32F411_TinyMaix.zip (1.94 MB)。
---------------------
作者:luobeihai
链接:https://bbs.21ic.com/icview-3344148-1-1.html
来源:21ic.com
此文章已获得原创/原创奖标签,著作权归21ic所有,任何人未经允许禁止转载。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐