发新帖本帖赏金 120.00元(功能说明)我要提问
返回列表
打印
[APM32F4]

TinyMaix赋予APM32F411 AI推理能力

[复制链接]
2600|9
手机看帖
扫描二维码
随时随地手机跟帖
跳转到指定楼层
楼主
luobeihai|  楼主 | 2023-11-28 09:16 | 只看该作者 |只看大图 回帖奖励 |倒序浏览 |阅读模式
本帖最后由 luobeihai 于 2023-11-28 12:35 编辑

#申请原创#   @21小跑堂  
  
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)





使用特权

评论回复

打赏榜单

21小跑堂 打赏了 120.00 元 2023-11-28
理由:恭喜通过原创审核!期待您更多的原创作品~

评论
21小跑堂 2023-11-28 15:33 回复TA
非常有意思的TinyMaix和APM32F411 的结合,通过TinyMaix的开源库,赋予APM32F411 AI推理能力。作者实现很好,并对开发过程中遇到的问题予以解决和记录,整体实现较好。(您已满足升级蓝V达人的条件,升级为蓝v用户可获得更高的打赏,!快来升级吧https://bbs.21ic.com/icview-3279072-1-1.html) 
沙发
susutata| | 2023-11-28 17:17 | 只看该作者
学习,学习

使用特权

评论回复
板凳
地球十强666| | 2023-12-2 23:11 | 只看该作者
楼主人才

使用特权

评论回复
地板
kai迪皮| | 2023-12-4 17:32 | 只看该作者
学习学习

使用特权

评论回复
5
oldbottle| | 2023-12-5 16:19 | 只看该作者
厉害,在其它32位单片机上应该也可以实现吧?

使用特权

评论回复
6
chenjun89| | 2023-12-8 21:10 | 只看该作者
MCU跑机器学习算法还是有点鸡肋

使用特权

评论回复
7
主战坦克| | 2023-12-10 17:27 | 只看该作者
我理解不了,例程中的数字识别和人像识别,是已经把训练结果固化到代码里面了么?另外怎么讲图像信息输给单片机呢。

使用特权

评论回复
评论
luobeihai 2023-12-10 23:26 回复TA
这几个Demo示例,是已经把图像的数据通过python工具,转换为了一个C语言数组编译进代码里面了。然后TinyMaix计算存在代码的图像数据即可。实际的项目应用中,图像的数据肯定不是事先存放在代码中,而是通过摄像头或者其他什么通信接口获得图像数据。 
8
gangong| | 2024-10-25 16:26 | 只看该作者
楼主牛

使用特权

评论回复
发新帖 本帖赏金 120.00元(功能说明)我要提问
您需要登录后才可以回帖 登录 | 注册

本版积分规则

12

主题

56

帖子

2

粉丝