一、TinyMaix简介
TinyMaix是国内sipeed团队开发一个轻量级AI推理框架,官方介绍如下:
TinyMaix 是面向单片机的超轻量级的神经网络推理库,即 TinyML 推理库,可以让你在任意单片机上运行轻量级深度学习模型。
根据官方介绍,在仅有2K RAM的 **Arduino UNO(ATmega328, 32KB Flash, 2KB RAM) **上,都可以基于 TinyMaix 进行手写数字识别。
对,你没有看错,2KB RAM 32KB Flash的设备上,都可以使用TinyMaix进行手写数字识别!TinyMaix官网提供了详细介绍,可以在本文末尾的参考链接中找到。
所以,在我们这次试用的主角GD32F427上运行TinyMaix完全是没有任何压力的。接下来,我将介绍如何在GD32F427上运行TinyMaix进行手写数字识别。
1.1 TinyMaix开源项目
二、TinyMaix移植
TinyMaix是一个轻量级AI推理框架,他的核心功能就是支持AI模型的各种算子,可以简单理解为一个矩阵和向量计算库。对于计算库的移植,我们通常只需要解决编译问题即可,不涉及外设和周边元件。
2.1 创建TinyMaix移植项目
以上一篇文章提到的VSCode模板项目为蓝本,在GD32F4xx_Demo_Suites_V2.6.1\GD32427V_START_Demo_Suites\Projects
子目录下克隆项目到GD32F427V_TinyMaix
目录:
git clone https://gitee.com/swxu/GD32F427V_START_Template_VSCode.git GD32F427V_TinyMaix
2.2 添加TinyMaix源码
接下来,克隆TinyMaix源码到到当前项目中:
git clone https://github.com/sipeed/TinyMaix.git
2.3 手写数字识别示例
使用上一篇文章提到的VSCode菜单“终端”->“运行任务”->“create_Makefile”,生成Makefile文件。该任务会调用toMakefile.py
脚本,递归遍历当前目录的源文件(.c),再根据Makefile.template生成Makefile文件。
具体会更新Makefile.template中的源码文件列表,由于TinyMaix的examples目录内有多个main.c文件,并且每个main.c中都有一个main函数,这会导致编译错误,需要修改:
删除examples
目录下除mnist
之外的其他所有目录;
将mnist
目录内的main.c
文件中的main
函数重命名为mnist_main
;
将mnist
目录内的main.c
重命名为mnist_main.c
;
完成以上修改之后,再次生成项目,才可以编译通过。
三、TinyMaix测试
TinyMaix编译后,还需要添加测试代码才能看到效果。TinyMaix已经项目本身已有一些测试可同时用了,无需我们手动编写,例如手写数字识别。
TinyMaix本身纯CPU计算不依赖于任何外设功能,但TinyMaix基准测试依赖于:
日志打印,具体是printf输出
精准计时,精确到毫秒即可
下面分别介绍如何在GD32F427V-START开发板上实现这两个基础功能。
3.1 基于SysTick的计时
SysTick是ARM-Cortex内核自带的外设,CMSIS软件包对它进行了封装,使用起来非常方便。一般来说,我们在项目代码中使用SysTick只需要在代码中:
1、调用SysTick_Config
函数设置SysTick中断频率;
2、编写SysTick_Handler
函数实现SysTick中断处理;
基础项目模板——点灯项目,已经支持了基于SysTick的毫秒级延时,相关代码位于如下几个文件中:
gd32f4xx_it.c
gd32f4xx_it.h
systick.c
systick.h
本次移植TinyMaix,需要实现计时功能,可以在点灯项目代码的基础上进行一些修改,具体修如下:
[url=home.php?mod=space&uid=1999721]@@[/url] -35,7 +35,7 @@ OF SUCH DAMAGE.
#include "gd32f4xx.h"
#include "systick.h"
-static volatile uint32_t delay;
+static volatile uint32_t ticks = 0;
/*!
\brief configure systick
@@ -63,9 +63,9 @@ void systick_config(void)
*/
void delay_1ms(uint32_t count)
{
- delay = count;
+ uint32_t end = ticks + count;
- while(0U != delay) {
+ while (ticks != end) {
}
}
@@ -77,7 +77,10 @@ void delay_1ms(uint32_t count)
*/
void delay_decrement(void)
{
- if(0U != delay) {
- delay--;
- }
+ ticks++;
}
+
+uint32_t systick_get_ms()
+{
+ return ticks;
+}
3.2 选择UART引脚
开始试用UART进行输出之前,需要选择两个空闲引脚作为UART的TX和RX,以及相应的UART外设。需要查阅如下资料:
GD32F427V-START开发板原理图
GD32F4xx DataSheet
这里我选择的是PB6
和PB7
引脚,分别作为USART0_TX
和USART0_RX
功能。
选定引脚后,即可编写USART0初始化代码:
void uart0_init()
{
rcu_periph_clock_enable(RCU_GPIOB);
rcu_periph_clock_enable(RCU_USART0);
gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_6);
gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_7);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_6);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_6);
gpio_mode_set(GPIOB, GPIO_MODE_AF, GPIO_PUPD_PULLUP, GPIO_PIN_7);
gpio_output_options_set(GPIOB, GPIO_OTYPE_PP, GPIO_OSPEED_50MHZ, GPIO_PIN_7);
usart_deinit(USART0);
usart_baudrate_set(USART0, 115200U);
usart_word_length_set(USART0, USART_WL_8BIT);
usart_parity_config(USART0, USART_PM_NONE);
usart_stop_bit_set(USART0, USART_STB_1BIT);
usart_hardware_flow_cts_config(USART0, USART_CTS_DISABLE);
usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE);
usart_receive_config(USART0, USART_RECEIVE_ENABLE);
usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);
usart_enable(USART0);
}
3.3 支持printf输出
GCC环境下,重写_write
即可实现printf
打印输出到UART,如下所示:
int _write (int fd, char *buffer, int size)
{
for (int i = 0; i < size; i++)
{
usart_data_transmit(USART0, buffer[i]);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
}
return size;
}
Keil环境下,实现printf
打印输出到UART,需要重写fputc
:
int fputc(int ch, FILE *f)
{
usart_data_transmit(USART0, (uint8_t)ch);
while(RESET == usart_flag_get(USART0, USART_FLAG_TBE));
return ch;
}
3.4 修改tm_port.h
文件
接下来修改tm_port.h
文件中的几个宏:
#include "systick.h"
#define TM_DBGT_INIT() uint32_t _start,_finish; uint32_t _time; _start = systick_get_ms();
#define TM_DBGT_START() _start = systick_get_ms();
#define TM_DBGT(x) {_finish = systick_get_ms(); \
_time = _finish - _start; \
TM_PRINTF("===%s use %lu ms\n", (x), _time); \
_start = systick_get_ms();}
3.5 修改mnist_main.c
文件
接下来修改mnist_main.c
文件,具体修改为:
注释掉tm_stat((tm_mdlbin_t*)mdl_data);
调用行;
parse_output
函数内,打印浮点型置信度的地方修改为:
float conf = data[i];
printf("%d: %d.%03d\n", i, (int) conf, (int) ((conf - (int) conf) * 1000));
以及最后打印结果行,修改为:
TM_PRINTF("### Predict output is: Number %d, prob %d.%03d\n", maxi, (int) maxp, (int) ((maxp - (int) maxp) * 1000));
PS:这两处打印修改,都是因为暂时没有找到在GCC开发环境下能够在GD32F427上打印浮点数的方法。
3.6 解决编译问题
完成以上修改后,直接编译,会发现如下报错:
c:/program files (x86)/gnu arm embedded toolchain/10 2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/bin/ld.exe: c:/program files (x86)/gnu arm embedded toolchain/10 2021.10/bin/../lib/gcc/arm-none-eabi/10.3.1/../../../../arm-none-eabi/lib/thumb/v7e-m+fp/hard\libnosys.a(sbrk.o): in function `_sbrk':
sbrk.c:(.text._sbrk+0x18): undefined reference to `end'
collect2.exe: error: ld returned 1 exit status
Makefile:203: recipe for target 'build/GD32F427V_START.elf' failed
make: *** [build/GD32F427V_START.elf] Error 1
解决方法——链接脚本中添加一段:
.heap :
{
. = ALIGN(4);
__HEAP_START = .;
. += 0x2000; /* 8K */
__HEAP_END = .;
end = __HEAP_END;
PROVIDE(end = .);
} > DATA
四、运行手写数字识别
完成以上修改后,就可以在GD32F427上运行手写数字识别示例了,具体输出如下图所示:
可以看到,成功识别了数字2。
原作者:xusiwei1236