xusiwei1236 · 2022年12月13日 · 江苏

【GD32F427开发板试用】使用TinyMaix进行手写数字识别

【GD32F427开发板试用】使用TinyMaix进行手写数字识别

一、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开源项目

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

二、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,需要实现计时功能,可以在点灯项目代码的基础上进行一些修改,具体修如下:

--- a/systick.c
+++ b/systick.c
@@ -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

这里我选择的是PB6PB7引脚,分别作为USART0_TXUSART0_RX功能。

GD32F417VK_PB6_USART0.png

选定引脚后,即可编写USART0初始化代码:

void uart0_init()
{
    // 使能 模块时钟
    rcu_periph_clock_enable(RCU_GPIOB);  // 使能 GPIOB 时钟
    rcu_periph_clock_enable(RCU_USART0); // 使能 USART0 时钟

    // 设置 USART0_TX USART0_RX 引脚功能
    gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_6); // USART0_TX: PB6 AF7
    gpio_af_set(GPIOB, GPIO_AF_7, GPIO_PIN_7); // USART0_RX: PB7 AF7

    // 设置 USART0_TX 引脚 上下拉模式 和 时钟频率
    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);

    // 设置 USART0_RX 引脚 上下拉模式 和 时钟频率
    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);

    // 复位 USART0
    usart_deinit(USART0);

    // 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); // 禁用 CTS
    usart_hardware_flow_rts_config(USART0, USART_RTS_DISABLE); // 禁用 RTS

    // 使能USART0接收发送
    usart_receive_config(USART0, USART_RECEIVE_ENABLE);
    usart_transmit_config(USART0, USART_TRANSMIT_ENABLE);

    // 打开 USART0
    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上运行手写数字识别示例了,具体输出如下图所示:

GD32F427VK_TinyMaix_mnist.png

可以看到,成功识别了数字2。

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

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

五、参考链接

  1. 【极术社区】GD32F47x/42x系列ARM Cortex-M4高性能MCU资料汇总
  2. 【Keil官网】 MDK v4 Legacy Support (keil.com)
  3. 【GD官网】GD32F427xx数据手册: https://www.gigadevice.com.cn...
  4. 【GD官网】GD32F427xx用户手册: https://www.gigadevice.com.cn...
  5. 【CSDN】STM32 arm-none-eabi-gcc 交叉编译重定向printf: https://blog.csdn.net/weixin_...
  6. 【CSDN】编译报错—undefined reference to \_sbrk: https://blog.csdn.net/jackcsd...
  7. 【腾讯云社区】ARM探索之旅03 | 如何使用 ARM FPU 加速浮点计算:https://cloud.tencent.com/dev...
推荐阅读
关注数
10708
内容数
187
中国高性能通用微控制器领域的领跑者兆易创新GD系列芯片技术专栏。
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息