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

news2024/9/20 18:35:32

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动,更多开发板试用活动请关注极术社区网站。作者:xusiwei1236

一、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功能。

选定引脚后,即可编写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上运行手写数字识别示例了,具体输出如下图所示:

可以看到,成功识别了数字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…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/343747.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

django+mysql实现一个简单的web登录页面

目录 一、使用pyacharm创建一个django项目 二、启动django项目验证 三、配置mysql数据库 1、本地安装mysql数据库 1&#xff09;安装mysql数据库 2&#xff09;自己创建一个数据库 2、安装 pymysql 3、配置mysql数据库 1&#xff09;在项目同名包下的_init_.py里面添加…

OpenGL渲染管线介绍

一、概述 OpenGL是跨平台计算机图形应用程序的应用规范&#xff0c;广泛应用于仿真、游戏、GIS系统等领域&#xff0c;实现二三维图形的渲染。OpenGL渲染过程需要经历CPU、GPU两个阶段&#xff0c;CPU中进行图形计算&#xff0c;完成之后调用OpenGL开发接口在GPU中创建缓存区缓…

前后端一些下载与配置(第二篇 第10天过后)nuxt banner redis

NUXT 应该是不用怎么装&#xff1f; 有现成的 axios 还需要在npm吗 好像已经有现成的了 banner banner 笔记汇总P396 Redis Linux安装redis tar -xzvf redis-6.2.6.tar.gz cd redis-6.2.6 照着他做 然后 cd /usr/local/redis/bin ./redis-server /usr/local/redis…

图数据建模基础

Neo4j 图的组件 节点&#xff08;Nodes&#xff09;标签&#xff08;Labels&#xff09;关系&#xff08;Relationships&#xff09;属性&#xff08;Properties&#xff09;建模过程 了解领域并为应用程序定义特定用例&#xff08;问题&#xff09;。开发初始图形数据模型。 对…

ansible的剧本(playbook)

一、playbooks 概述以及实例操作 1、playbooks 的组成 playbooks 本身由以下各部分组成 &#xff08;1&#xff09;Tasks&#xff1a;任务&#xff0c;即通过 task 调用 ansible 的模板将多个操作组织在一个 playbook 中运行 &#xff08;2&#xff09;Variables&#xff1…

多模式支持无线监控技术:主动式定位、被动式定位

物联网空间信息与数字技术发展至今&#xff0c;已经催生了一大批优秀的践行者。在日常与商业应用中&#xff0c;室内外定位领域依托于这一技术的发展&#xff0c;更是在近几年风光无限。但是并不是说室内定位与室外定位都已经相当成熟&#xff0c;相对来说&#xff0c;室内定位…

简单实用的内网穿透实现教程

内网穿透&#xff0c;字面理解就是网络地址穿透&#xff0c;是一种比较常用的将内网地址转换成公网地址的方式。通过内网穿透&#xff0c;可以将本地内网局域网提供给外网公网上访问&#xff0c;在外网也能连接访问内网主机和应用&#xff0c;当用户有日常远程和异地外网访问的…

Zabbix的自定义监控

Zabbix的自定义监控 zabbix自动可以提供很多监控项&#xff1b;但是往往不能满足需求&#xff1b;尝尝需要我们自己创建一系列的监控项&#xff0c;这就是自定义监控&#xff1b; 监控项&#xff1a;zabbix进行监控的一个指标&#xff0c;zabbix成为item&#xff1b; 它的值…

C++7:STL-模拟实现vector

目录 vector的成员变量 构造函数 reserve size() capacity() push_back 一些小BUG 赋值操作符重载 析构函数 【】操作符重载 resize pop_back Insert 迭代器失效 erase 二维数组问题 总结一下 vector&#xff0c;翻译软件会告诉你它的意思是向量&#xff0c;但其…

面试腾讯测开岗,结束后被面试官吐槽“什么阿猫阿狗都敢来面试大厂了吗?”.....

前一阵子有个小徒弟向我诉苦&#xff0c;说自己在参加某大厂测试面试的时候被面试官怼得哑口无言&#xff0c;场面让他一度十分尴尬 印象最深的就是下面几个问题&#xff1a; 根据你以前的工作经验和学习到的测试技术&#xff0c;说说你对质量保证的理解&#xff1f; 非关系型…

不连接显示器或者HDMI欺骗器来 使用Moonlight串流游戏

环境&#xff1a;WIN11NVIDIA显卡Moonlight串流 问题&#xff1a;当主机不连接显示器时&#xff0c;Moonlight客户端黑屏 解决办法&#xff1a;使用虚拟显示器来使显卡工资 背景&#xff1a;当SteamDeck 大卖的时候&#xff0c;我开始思考是否也需要购买一台Steam Deck来躺在床…

G1垃圾回收器详解

文章目录前言一、思考问题二、官方文档三、基本介绍四、G1的内存模型五、G1的标记过程六、G1的垃圾回收1、G1过程梳理2、Young GC3、Mixed GC4、Full GC七、参数介绍八、典型问题1、疏散失败&#xff08;Evacuation Failure&#xff09;2、大对象分配&#xff08;Humongous All…

【Linux】动静态库以及动静态链接

环境&#xff1a;centos7.6&#xff0c;腾讯云服务器Linux文章都放在了专栏&#xff1a;【Linux】欢迎支持订阅&#x1f339;链接扩展我们在使用Linux的时候&#xff0c;不禁会有这么一个疑问&#xff1a;为什么我们能够在Linux下进行c/c代码的编写以及编译呢&#xff1f;这是因…

_Linux(网络基础)

文章目录1. 相关基础概念2. 认识 "协议"3. 网络协议初识协议分层OSI七层模型TCP/IP五层(或四层)模型4. 网络传输基本流程网络传输流程图数据包封装和分用小结5. 网络中的地址管理认识IP地址认识MAC地址认识端口号1. 相关基础概念 独立模式: 计算机之间相互独立网络互…

MySQL数据库13——插入数据(INSERT)

下面的语句用于向student表插入数据。 插入语句&#xff1a; INSERT INTO student(ID,name,sex,birthday,origin,contact1,contact2,institute) VALUES (0013,塔赛努,男,1997/9/15,内蒙古自治区,NULL,NULL,计算机学院);INSERT INTO student VALUES (0014,呼和嘎拉,男,1995-02…

字节青训营——秒杀系统设计学习笔记(一)

如何做系统设计 1. 场景分析(Scenario) 什么系统&#xff0c;需要哪些功能&#xff0c;多大的并发量 2. 存储设计(Storage) 数据如何组织&#xff0c;Sq|存储&#xff0c; NoSq|存储 3. 服务设计(Service) 业务功能实现和逻辑整合 4. 可扩展性(Scale) 解决设计缺陷&…

Go的web开发Gin框架1(八)——Gin

一、重点内容&#xff1a; 知识要点有哪些&#xff1f; 1、了解Gin框架 2、导入使用Gin框架 3、尝试配合GORM开发 4、整合html&#xff0c;css&#xff0c;js 二、详细知识点介绍&#xff1a; 1、Gin框架介绍 ​ Gin是一个golang的微框架&#xff0c;封装比较优雅&…

MyBatis无法通过getGenerateKeys获得自增主键的问题

我遇到这个问题的法伤原因比较蠢&#xff0c;查阅了网上相关经验都没有能够解决。看看这个经验能否帮助到你。问题描述&#xff1a;设置了属性的自增后想通过getGenerateKeys获得MySQL对应表单中自增主键id&#xff0c;检查了类、映射器、xml都没有发现问题&#xff0c;但是进行…

如何使用 Python 编程进行多线程

多线程&#xff1a;理论上能在同一个时间段执行多个程序片段&#xff0c;每个程序片段就看作是一个线程。为什么要说理论上&#xff0c;因为实际在操作系统中真正的在同一时间段基本是不存在的&#xff0c;但是在软件编程中我们可以理解为它是在同一时间段执行的。 同步&#…

c/c++开发,无可避免的模板编程实践(篇一)

一、c模板 c开发中&#xff0c;在声明变量、函数、类时&#xff0c;c都会要求使用指定的类型。在实际项目过程中&#xff0c;会发现很多代码除了类型不同之外&#xff0c;其他代码看起来都是相同的&#xff0c;为了实现这些相同功能&#xff0c;我们可能会进行如下设计&#xf…