瑞萨单片机论坛
直播中

xusiwei1236

12年用户 182经验值
擅长:嵌入式技术
私信 关注
[经验]

【瑞萨FPB-RA6E1快速原型板】使用TinyMaix识别手写数字

一、TinyMaix简介

TinyMaix是国内sipeed团队开发一个轻量级AI推理框架,官方介绍如下:

TinyMaix 是面向单片机的超轻量级的神经网络推理库,即 TinyML 推理库,可以让你在任意单片机上运行轻量级深度学习模型。

根据官方介绍,在仅有2K RAM的 **Arduino UNO(ATmega328, 32KB Flash, 2KB RAM) **上,都可以基于 TinyMaix 进行手写数字识别。对,你没有看错,2KB RAM 32KB Flash的设备上,都可以使用TinyMaix进行手写数字识别!TinyMaix官网提供了详细介绍,可以在本文末尾的参考链接中找到。

所以,在我们这次试用的主角瑞萨FPB-RA6E1快速原型板上运行TinyMaix完全是没有任何压力的(1MB Flash 256KB SRAM)。接下来,我将介绍如何在瑞萨FPB-RA6E1快速原型板上使用TinyMaix进行手写数字识别。

1.1 TinyMaix开源项目

GitHub代码仓:https://github.com/sipeed/tinymaix

使用如下git命令,可将TinyMaix源码克隆到本地:

git clone https://github.com/sipeed/TinyMaix.git

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();}

二、计时和打印支持

TinyMaix是一个轻量级AI推理框架,他的核心功能就是支持AI模型的各种算子,可以简单理解为一个矩阵和向量计算库。对于计算库的移植,我们通常只需要解决编译问题即可,不涉及外设和周边元件。

2.1 创建RASC项目

和上一篇CoreMark移植文章类似,按照如下步骤创建RASC项目:

  1. 使用RASC创建名为RA6E1_TinyMaix的项目,如下图所示;

    image-20230604185114051

  2. RASC界面中,切换到Pins标签页,选择“System:DEBUG->DEBUG0”,Pin Configuration中修改设置:

    • Operation Mode修改为SWD;

    • SWCLK修改为P300;

    • SWDIO修改为P108;

    • 按Ctrl+S保存,如下图所示:

      image-20230604185201414

  3. RASC界面中,继续在Pins标签页,选择“Connectivity:SCI->SCI9”,Pin Configuration中修改:

    • Operation Mode修改为Asynchronous UART;

    • TXD9修改为P109;

    • RXD9修改为P110;

    • 按Ctrl+S保存,如下图所示:

      image-20230604185301316

  4. RASC界面中,切换到Stacks标签页,点击“New Stack->Connectivity->UART”添加一个UART组件,添加后鼠标选中,然后在Properties标签页中,在Settings->Module中的:

    • General中Channel修改为9;

    • General中Name修改为g_uart9;

    • Interrupts中Callback修改为uart9_callback;

    • 按Ctrl+S保存,如下图所示:

      image-20230604185450121

  5. 点击右上角的Generate Project Content生成Keil项目;

2.3 添加printf打印

和CoreMark移植文章类似,我们在hal_entry.c中添加如下代码:

#include <stdio.h>
#include "hal_data.h"
#include "r_sci_uart.h"

void hal_uart9_init()
{
    R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
}

volatile bool hal_uart_tx_done = false;

void hal_uart9_callback(uart_callback_args_t* p_args)
{
   switch (p_args->event)
   {
   	   case UART_EVENT_RX_CHAR:
           break;
       case UART_EVENT_TX_COMPLETE:
           hal_uart_tx_done = true;
           break;
       default:
           break;
   }
}

int fputc(int ch, FILE* f)
{
    (void) f;

    hal_uart_tx_done = false;
    R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);

    while (hal_uart_tx_done == false);
    return ch;
}

其中,hal_uart9_callback函数名需要和前面RASC配置的函数名相同,否则编译时报连接错误。

2.3 添加SysTick计时

SysTick是ARM-Cortex内核自带的外设,CMSIS软件包对它进行了封装,使用起来非常方便。一般来说,我们在项目代码中使用SysTick只需要在代码中:

  1. 调用SysTick_Config函数设置SysTick中断频率;
  2. 编写SysTick_Handler函数实现SysTick中断处理;

和上篇CoreMark移植文章类似,在hal_systick.c文件中添加如下代码:

#include <stdint.h>

#define TICKS_PER_SECOND 1000

volatile uint32_t g_tick_count = 0;

void hal_systick_init()
{
    SysTick_Config(SystemCoreClock / TICKS_PER_SECOND);
}

void SysTick_Handler(void)
{
    g_tick_count += 1;
}

uint32_t hal_systick_get()
{
    return g_tick_count;
}

3.4 打印和计时测试

完成上述修改后,可以在hal_entry.c的主体代码中进行测试,在hal_entry函数中添加测试代码:

hal_uart9_init();
    hal_systick_init();
    while (1) {
        printf("ticks: %d\n", hal_systick_get());
        R_BSP_SoftwareDelay(1000, BSP_DELAY_UNITS_MILLISECONDS);
    }

对上SysTick计时功能进行测试。

重新编译、下载后,可以看到串口输出如下:

image-20230604210658516

三、TinyMaix移植

3.1 添加TinyMaix核心库源码

接下来,双击打开RASC生成的Keil项目,Project视图中可以看到项目文件如下:

image-20230604185716612

然后,将TinyMaix的部分源码文件添加到当前项目中,具体为:

  1. 当前项目中,创建TinyMaix目录;
  2. 将TinyMaix项目中的include和src目录,复制到当前项目的TinyMaix目录中;
  3. 在Keil中,鼠标右键“Source Group 1”选中“Add Existing Files to Group 'Soure Group1'”,弹出添加文件对话框;
  4. 在添加文件对话框中,将TinyMaix/src子目录中的源文件全部添加到项目中("tm_layers.c" "tm_layers_fp8.c" "tm_layers_O1.c" "tm_model.c" "tm_stat.c" );
  5. 在Keil中,鼠标右键Target 1,选择“Options for Target 'Target 1'”,点击C/C++标签页;
  6. 在C/C++标签页中,点击Include Path栏右侧的“...”按钮,将TinyMaix/include子目录添加到搜索路径中,点OK保存配置;
  7. 修改 tm_port.h 文件,注释掉其中的 #include <sys/time.h> 行,并按Ctrl+S保存;

完成上述修改后,项目应该可以正常编译通过:

image-20230604202405810

3.2 添加手写数字识别示例源码

按照如下步骤,将TinyMaix的手写数字识别示例源码添加到当前项目:

  1. 当前项目的TinyMaix目录中,创建examples和tools目录;
  2. 找到TinyMaix项目的examples目录中的mnist目录,将其复制到刚刚创建的examples目录中;
  3. 将刚刚复制的examples目录中的main.c文件,重命名为mnist_main.c;
  4. 在Keil中,鼠标右键“Source Group 1”选中“Add Existing Files to Group 'Soure Group1'”,弹出添加文件对话框;
  5. 将刚刚重命名的mnist_main.c添加到当前项目;
  6. 打开mnist_main.c,将其中的main函数重命名为mnist_main,并删除函数参数;
  7. 找到TinyMaix项目的tools目录中的tmdl目录,将其复制到刚刚创建的tools目录中;

3.3 修改tm_port.h文件

接下来修改TinyMaix/include中的tm_port.h文件,具体为其中的几个宏:

#include <stdint.h>
uint32_t hal_systick_get();
#define TM_DBGT_INIT()     uint32_t _start,_finish; uint32_t _time; _start = hal_systick_get();
#define TM_DBGT_START()    _start = hal_systick_get();
#define TM_DBGT(x)         {_finish = hal_systick_get();                  \
                            _time = _finish - _start;                    \
                            TM_PRINTF("===%s use %lu ms\n", (x), _time); \
                            _start = hal_systick_get();}

修改这些宏后,TinyMaix核心框就能够正确计时了。

3.4 增大堆栈内存空间

TinyMaix运行时需要使用malloc申请堆内存,默认创建的RASC堆内存配置为0,会导致运行失败,因此需要增大堆内存空间。

增大堆内存空间的操作步骤如下:

  1. RASC中,点击BSP标签页,展开RA Common;
  2. 找到Main stack size,将其修改为4096;
  3. 找到Heap size,将其修改为8192(实测8192可以正常运行,修改成更大值也可以,只要最终生成程序占用内存不超过SRAM都可以);

3.5 运行手写数字识别示例

完成上述修改后,编译、烧录、运行,就可以在串口看到如下输出了:

image-20230604213244580

可以看到,成功识别了数字2,耗时2毫秒。

四、原理解读

4.1 模型结构展示

我们取消堆tm_stat调用的注释,重新编译、烧录后,运行时将会看到模型结构输出:

RA6E1_TinyMaix_mnist_model.png

可以看到输入大小为28x28x1,经过6个中间操作之后,得到输出结果。输出是一个1x1x10(对应10个数字的置信度);

原始模型位于TinyMaix代码仓的,tools/tflie子目录下,mnist_valid_q.tflite文件,可以使用Netron查看模型结构:

mnist_valid_q.png

4.2 示例源码解读

mnist_main.c文件中,开始的几行用于根据tm_port.h中定义的数据使用对应的模型:

#if TM_MDL_TYPE == TM_MDL_INT8
#include "../../tools/tmdl/mnist_valid_q.h"
//#include "../../tools/tmdl/mnist_resnet_q.h"
#elif TM_MDL_TYPE == TM_MDL_FP32
#include "../../tools/tmdl/mnist_valid_f.h"
//#include "../../tools/tmdl/mnist_resnet_f.h"
#elif TM_MDL_TYPE == TM_MDL_FP16
#include "../../tools/tmdl/mnist_valid_fp16.h"
#elif TM_MDL_TYPE == TM_MDL_FP8_143
#include "../../tools/tmdl/mnist_fp8_143.h"
#elif TM_MDL_TYPE == TM_MDL_FP8_152
#include "../../tools/tmdl/mnist_fp8_152.h"
#endif

这些.h文件是由tflite2tmdl.py脚本生成的TinyMaix模型,mnist_valid_f模型的转换命令为:

python3 tflite2tmdl.py tflite/mnist_valid_f.tflite tmdl/mbnet_fp8.tmdl fp8_152 1 28,28,1 10

接下来定义了一个数组,uint8_t mnist_pic[28*28],保存一张测试图片,数组每个元素对应一个像素的灰度值:

image-20230313211621713

接下来,mnist_main中使用模型,主要使用了一下几个TinyMaix的API:

  • tm_stat 打印模型结构等信息;
  • tm_load 将模型加载到内存;
  • tm_preprocess 输入数据预处理;
  • tm_run 模型推理,得到输出;
  • tm_unload 模型卸载,释放内存;

使用起来还是非常简单的,跟读细节可以参考TinyMaix介绍页。

本篇内容就到这里了,感谢阅读。

本文完整项目代码仓(感兴趣的同学可以下载下来自行实验):https://gitee.com/swxu/RA6E1-tiny-maix

五、参考链接

  1. **【瑞萨官网】**RA6E1参考手册(英文): https://www.renesas.com/us/en/document/dst/ra6e1-group-datasheet?r=1521986
  2. **【瑞萨官网】**RA6E1硬件手册(英文): https://www.renesas.cn/cn/zh/document/mah/ra6e1-group-users-manual-hardware?r=1521986
  3. **【RASC用户指南】**RA SC User Guide for MDK and IAR https://renesas.github.io/fsp/_s_t_a_r_t__d_e_v.html#RASC-MDK-IAR-user-guide
  4. **【Keil官方文档】**关于重定义库函数使用printf的说明:https://developer.arm.com/documentation/dui0475/c/the-arm-c-and-c---libraries/redefining-low-level-library-functions-to-enable-direct-use-of-high-level-library-functions
  5. 【野火 瑞萨RA系列FSP库开发实战指南】 SCI UART——串口通信:https://doc.embedfire.com/mcu/renesas/fsp_ra/zh/latest/doc/chapter19/chapter19.html
  6. 【野火 瑞萨RA系列FSP库开发实战指南】 SysTick——系统定时器:https://doc.embedfire.com/mcu/renesas/fsp_ra/zh/latest/doc/chapter17/chapter17.html

更多回帖

发帖
×
20
完善资料,
赚取积分