本文将介绍如何为瑞萨 RA6E2 MCU移植轻量级AI推理框架——TinyMaix,并在开发板上运行TinyMaix的手写数字识别示例。本文将首先介绍TinyMaix是什么,以及RA6E2 MCU的主要参数。然后,介绍如何为RA6E2 MCU创建一个支持printf打印和SysTick计时的Keil项目。接着,介绍如何将TinyMaix项目核心框架和手写数字识别示例源码添加到Keil项目中。最后,介绍如何将编译好的二进制程序下载到RA-Eco-RA6E2-64PIN开发板上,并运行手写数字识别示例程序。
开始之前,首先介绍一下轻量级AI推理框架——TinyMaix、本次使用活动的RA-Eco-RA6E2-64PIN开发板,以及它的主控芯片RA6E2BB3C。
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核心框架代码、示例程序、常用模型、测试图片、文档等内容。
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格式的模型;
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 RA-Eco-RA6E2-64PIN开发板及主控芯片
本次试用的RA-Eco-RA6E2-64PIN开发板,主控芯片是RA6E2BB3C,它的主要参数为:
- CPU: Cortex-M33内核,200 MHz,支持TrustZone
- Flash: 256 KB
- RAM: 40 KB
TinyMaix的手写数字识别示例,在32KB Flash 2KB RAM的Arduino UNO R3上都可以运行。
在我们这次试用的主角RA-Eco-RA6E2-64PIN开发板上运行是完全没有任何压力的。
开发板正面特写,可以看到主控芯片型号为RA6E2BB3C:
二、开发环境搭建
2.1 下载RA6E2资料
瑞萨官网RA6E2产品页:RA6E2 - RA6E2 | Renesas
建议下载的文件包括:
- RA6E2数据手册: RA6E2 Group Datasheet
- RA6E2硬件参考手册: RA6E2 Group User's Manual: Hardware
开发板是国内的瑞萨生态工作室做的,资料下载连接: https://www.ramcu.cn/public/uploads/files/20230517/8285e1cbbcac5ca41b43cf4b8c285d18.zip
2.2 安装Keil MDK
ARM官网Keil MDK安装包下载页面(需要填写问卷):
填写完问卷之后,可以看到,最新版本5.38a安装包文件下载页面:
这里的安装包文件的链接可以直接复制:
MD5: 6792e5e0c0b5207b4db8339e043d7461
PS: 使用下载工具下载完成后,记得验证一下MD5值。
Keil本身的安装没啥难度,一路下一步就好了,这里不再详细介绍。
2.3 安装瑞萨配置工具RASC瑞萨FSP(Flexible Software Package,灵活软件包)是一套嵌入式软件框架,不仅包含了寄存器级别的底层驱动,还包括了一些常用的三方库、RTOS等中间件,以及图形化的配置工具。瑞萨的配置工具即为RASC(RA Smart Configurator)。
最新 4.5.0 Windows版下载链接: https://github.com/renesas/fsp/releases/download/v4.5.0/setup_fsp_v4_5_0_rasc_v2023-04.exe
下载下来之后,安装没啥难度,一路下一步就好了,不再赘述。
需要注意的是:RA6E2在4.4之后版本的FSP和RASC才有支持。
详细的开发环境搭建流程可以参考我此前发的两篇文章:
现在,我们需要为APM32F407IGT6芯片创建一个同时支持SysTick计时和printf输出的Keil项目。
3.1 创建RASC项目打开RASC,开始创建名为RA6E2_TinyMaix的项目:
第二步,选择MCU和项目类型:
接下来选择是否需要TrustZone支持:
保持默认的,不需要TrustZone支持。
选择RTOS支持:
保持默认选项,No RTOS,即裸机项目。
最后确认裸机项目:
点击Finish之后,开始生成RASC项目,生成完成后,界面如下:
3.2 修改配置生成Keil项目按照如下步骤,修改RASC项目中的配置:
- RASC界面中,继续在Pins标签页,选择“Connectivity:SCI->SCI9”,Pin Configuration中修改:
- Operation Mode修改为Asynchronous UART;
- TXD9修改为P109;
- RXD9修改为P110;
- 按Ctrl+S保存;
如下图所示: - RASC界面中,切换到Stacks标签页,点击“New Stack->Connectivity->UART”添加一个UART组件,添加后鼠标选中,然后在Properties标签页中,在Settings->Module中的:
- General中Channel修改为9;
- General中Name修改为g_uart9;
- Interrupts中Callback修改为uart9_callback;
- 按Ctrl+S保存;
如下图所示: - 点击右上角的Generate Project Content生成Keil项目;
打开RASC刚刚生成项目的Keil项目,可以看到项目结构:
打开 hal_entry.c 文件,将开始部分修改为:
#include "hal_data.h"#include "r_sci_uart.h"
#include <stdio.h>
#include <stdbool.h>
volatile bool uart_tx_done = false;
void hal_uart9_init()
{
R_SCI_UART_Open(&g_uart9_ctrl, &g_uart9_cfg);
}
int fputc(int ch, FILE* f)
{
(void) f;
uart_tx_done = false;
R_SCI_UART_Write(&g_uart9_ctrl, (uint8_t *)&ch, 1);
while (uart_tx_done == false);
return ch;
}
void uart9_callback(uart_callback_args_t* p_args)
{
switch (p_args->event)
{
case UART_EVENT_RX_CHAR:
break;
case UART_EVENT_TX_COMPLETE:
uart_tx_done = true;
break;
default:
break;
}
}
PS:因为前面的配置中设置了uart9_callback函数,所以这里必须先添加,才能编译通过。
继续在 hal_entry.c 中添加代码:
图中的58到65行为添加的部分,这里先初始化了UART9,然后循环调用printf打印输出。
完成以上修改后,就可以成功编译项目了:
3.3 修改调试器设置
成功编译项目后,就可以准备下载程序到开发板上了。开始下载之前,我们需要先将调试器和USB线接到开发板上,如下图所示:
其中,需要注意的是:
- USB线接到标有UART的口上;
- 调试器接到标有DEBUG的SWD口上,GND、SWDIO、SWDCLK三根线要连接正确;
以上连接完成后,将调试器和USB线的另外一端连接到PC。
接着,修改调试器设置。
按照如下步骤修改调试器设置:
- 在Project视图中,鼠标右键Target1,弹出悬浮菜单,如下图所示:
- 单击”Options for Target ‘Target1’“,将会弹出,如下图所示:
- 点击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标签页,修改为如下配置:这里需要注意的是:
- Programming Algorithm区域的存储段只需要点击Add,弹窗之后选择RA6E2的即可;
- RAM for Algortihm的Start需要设置为0x20000000,Size设置为0x2000;
- Reset and Run勾选之后,下载完程序会自动复位,便于调试;
完成上述调试设置之后,就可以按F8或Download按钮开始下载成了。Download按钮,位置如下图所示:
开始下载后,Build Output窗口将会显示下载进度,下载成功后可以看到:
其中,Application running … 是勾选了Reset and Run才会有的。
连接串口设备:
如果没有意外,链接之后将会看到不断有字符串打印输出:
四、支持SysTick计时
SysTick是ARM Cortex内核自带的计时器外设,CMSIS相关文件里面已经对相关寄存器操作进行了封装。因此SysTick使用起来非常方便,基本上只需要做两件事即可:
- 调用函数 SysTick_Config ,设置两次SysTick中断之间的tick数
- 实现SysTick中断处理函数 SysTick_Handler;
在 hal_entry.c 的比较考前的位置,添加如下代码:
#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;
}
这样,就可以实现毫秒级别的计时了。
4.2 测试计时支持代码接着将 hal_entry 函数略加修改,对以上计时代码进行测试:
重新编译、下载、运行,将会看到:
五、移植TinyMaix核心库和手写数字识别示例
接下来,我们逐步将TinyMaix核心库和手写数字识别示例代码添加到项目中。
5.1 下载TinyMaix源码到本地首先,使用 git 命令下载TinyMaix最新源码:
git clone https://github.com/sipeed/tinymaix.git5.2 拷贝TinyMaix源码到Keil项目目录接着,在RA6E2_TinyMaix目录下,创建TinyMaix目录:
将TinyMaix源码的examples、include、src目录,拷贝到刚刚创建的TinyMaix目录中:
另外,创建tools目录,并将tools目录下的tmdl目录拷贝过去;拷贝的exmaples中包含了多个示例,可以把不需要的删除掉,只保留mnist目录,即手写数字识别示例;并将 minst 目录下的 main.c 重命名为 minist_main.c 。
5.3 将TinyMaix源文件添加到Keil项目中首先,在Keil的Project视图右击APM32F407,选择Manage Project Items:
接着,在弹出的Manage Project Items窗口中,进行如下两步骤操作:
然后,点击Add Files,在弹出的文件添加界面中,依次添加刚刚拷贝过来的TinyMaix目录src目录和examples/mnist目录下的.c文件到项目中:
添加完成后,点击OK确认。
5.4 解决Keil项目的编译问题完成上述修改后,直接编译,将会报错:
首先,按照如下步骤,添加必要的include搜索路径:
- 在Project视图右键Target1,选择”Options for Target ‘Target1’“,如下图所示:
- 在弹出的Options for Target ‘Target1’窗口中,点击C/C++标签页,如图下图所示:
- 在C/C++标签页,点击Include Paths右侧的”…”按钮,会弹出Folder Setup界面,如下图所示:
此时,再次编译项目,报错已经不一样了:
这里报错说,找不到”sys/time.h“文件。
直接双击error信息行,主编辑器区可以看到tm_port.h文件对应位置:
这里的几个宏,正是TinyMaix依赖的计时宏。
将其修改为:
接下来编译,遇到如下错误:
这两个连接错误说有两个main函数定义。
找到手写数字识别的mnist_main.c,将其中的main函数重命名为mnist_main:
再次编译,已经没有报错了:
5.5 调用mnist示例程序接下来,修改hal_entry函数,向其中添加调用mnist_main的代码:
记得保存Ctrl+S。
然后重新编译、下载,下载完成后,自动开始运行,可以看到串口输出:
仅输出了模型信息,并没有输出识别结果和耗时。调试分析可以知道,是因为内存申请失败了。
解决办法——修改内存堆配置。
5.6 修改堆内存配置回到RASC界面,点击BSP标签,底部Properties下拉找到Heap size,默认是0:
前面看到模型是参数1.9KB,bufffer 1.4KB,这里设置为4KB足够用了。
修改完成后,Ctrl+S保存,然后重新生成Keil项目。
5.7 成功运行手写数字再次重新编译、下载、运行,串口看到输出:
可以看到,耗时3毫秒,成功识别到了手写数字2。
原始灰度图数据为:
识别为2是正确的。
六、参考链接
- RA6E2数据手册:RA6E2 Group Datasheet
- RA6E2用户手册:RA6E2 Group User's Manual: Hardware
- FSP在线文档:https://renesas.github.io/fsp/