admin管理员组

文章数量:1530016

基于 Arm 虚拟硬件平台实现微信支付二维码识别的智能闸机系统

 

本实验过程中所显示的优惠价格及费用报销等相关信息仅在【Arm AI 开发体验创造营】体验活动过程中有效,逾期无效,请根据实时价格自行购买和体验。同时,感谢本次体验活动 Arm 导师 Liliya 对于本博客的指导。 详见活动地址:https://marketing.csdn/p/a11ba7c4ee98e52253c8608a085424be

文章目录

  • 1 写在前面
  • 2 Arm虚拟硬件简介
  • 3 RT-Thread 简介
  • 4 案例项目简介
    • 4.1 项目简介
    • 4.2 工作原理简介
    • 4.3 核心流程图
  • 5 案例项目实现过程
    • 5.1 RT-Thread 的移植
    • 5.2 Zbar 的移植
    • 5.3 认证授权的逻辑实现
    • 5.4 遇到的难题
  • 6 核心代码展示
    • 6.1 ZBar的移植代码
    • 6.2 授权认证的逻辑代码
    • 6.3 远程联机的核心代码
  • 7 功能测试
    • 7.1 工程的编译构建
    • 7.2 验证基于 ZBar 的二维码识别
    • 7.3 运行示例图展示
    • 7.4 相关验证的输入图片
  • 8 更多思考
    • 8.1 关于 Arm 虚拟硬件的几点优势及本项目的前景
    • 8.2 移植过程的心得体会分享
    • 8.3 项目的后续迭代与展望
  • 9 参考资料

1 写在前面

在常规的嵌入式软件开发中,通常需要在用于开发的电脑主机上提前把应用程序编译好,生成可在嵌入式芯片上运行的文件代码,再通过相应的烧录调试工具,把该代码烧录至开发板中,才能查看验证所编写应用程序的正确性。

传统的嵌入式开发流程中,往往需要用到物理开发板才能进行相应的软件开发。但是,没有拿到物理开发板或对于一些新推出的处理器产品(例如:Arm® Cortex®-M55,Cortex-M85, Ethos™-U 系列 NPU 等)市场上硬件的资源较为稀缺且需要较长的时间才能获取到物理开发板的情况下,是否有办法在相应的平台上进行软件开发呢?

答案自然是有的,这就是我们本期实验手册要给大家介绍的一个非常强大的开发工具:**Arm 虚拟硬件(Arm Virtual Hardware)。**借助 Arm 虚拟硬件平台,我们可以做一些非常实用有趣的工具和案例,达到辅助我们日常开发的目的。

另一方面,在当下越来越复杂的物联网应用,比如一些对实时性要求比较高,且应用逻辑复杂的智能家居应用场景下,往往都需要一个优秀的端侧实时操作系统来做底层支撑。而选择一款操作系统,一是看它的稳定性,二是看的软件生态;恰好 RT-Thread 作为一款国内拥有自主知识产权的优秀国产物联网操作系统,因其高效、代码风格良好、开源生态很强,越来越多的物联网人开始使用它开发一些商业级别的产品。它正是我们的不二选择。

本期文章给大家介绍的 基于 Arm 虚拟硬件平台实现微信支付二维码识别的智能闸机系统,下文会该项目做详细介绍。

2 Arm虚拟硬件简介

Arm 虚拟硬件(Arm Virtual Hardware)提供了一个 Ubuntu Linux 镜像,包括用于 物联网、机器学习和嵌入式应用程序的 Arm 开发工具:例如,Arm 编译器、 FVP 模型和其他针对 Cortex-M 系列处理器的开发工具帮助开发者快速入门。Arm 虚拟硬件限时免费提供用于评估用途,例如,评估 CI/CD、MLOps 和 DevOps 工作流中的自动化测试工作流等。订阅访问和使用此版本的 Arm 虚拟硬件,您需同意产品最终用户许可协议中与免费测试版许可相关的条款和协议。

Arm 虚拟硬件产品的技术概览示意图如下所示。开发者也可访问 Arm 虚拟硬件产品介绍页和产品技术文档了解更多关于 Arm 虚拟硬件产品知识。

Arm 虚拟硬件产品概览

3 RT-Thread 简介

RT-Thread 诞生于2006年,是国内以开源中立、 社区化发展起来的一款高可靠实时操作系统 ,由睿赛德科技负责开发维护和运营 。因其十五年的沉淀积累, 专业化的运营推广,其高可靠性、安全、高可伸缩性和中间组件丰富易用等特性极大地满足了市场需求。目前已经成为市面上装机量最大(超20亿台)、开发者数量最多(超20万)软硬件生态最好的操作系统之一,被广泛应用于航空、电力、轨道交通、车载、工业自动化、消费电子等众多行业领域。

作为一款优秀的国产操作系统,它的核心软件架构图设计得还是非常精妙,参考如下:

它具体包括以下部分:

  • 内核层:RT-Thread 内核,是 RT-Thread 的核心部分,包括了内核系统中对象的实现,例如多线程及其调度、信号量、邮箱、消息队列、内存管理、定时器等;libcpu/BSP(芯片移植相关文件 / 板级支持包)与硬件密切相关,由外设驱动和 CPU 移植构成。
  • 组件与服务层:组件是基于 RT-Thread 内核之上的上层软件,例如虚拟文件系统、FinSH 命令行界面、网络框架、设备框架等。采用模块化设计,做到组件内部高内聚,组件之间低耦合。
  • RT-Thread 软件包:运行于 RT-Thread 物联网操作系统平台上,面向不同应用领域的通用软件组件,由描述信息、源代码或库文件组成。RT-Thread 提供了开放的软件包平台,这里存放了官方提供或开发者提供的软件包,该平台为开发者提供了众多可重用软件包的选择,这也是 RT-Thread 生态的重要组成部分。软件包生态对于一个操作系统的选择至关重要,因为这些软件包具有很强的可重用性,模块化程度很高,极大的方便应用开发者在最短时间内,打造出自己想要的系统。RT-Thread 已经支持的软件包数量已经达到 400+,如下举例:
  • 物联网相关的软件包:Paho MQTT、WebClient、mongoose、WebTerminal 等等。
  • 脚本语言相关的软件包:目前支持 Lua、JerryScript、MicroPython、PikaScript。
  • 多媒体相关的软件包:Openmv、mupdf。
  • 工具类软件包:CmBacktrace、EasyFlash、EasyLogger、SystemView。
  • 系统相关的软件包:RTGUI、Persimmon UI、lwext4、partition、SQLite 等等。
  • 外设库与驱动类软件包:RealTek RTL8710BN SDK。
  • 其他。

有更多需要了解 RT-Thread 的可以到它的文档中心做进一步深入学习。

4 案例项目简介

4.1 项目简介

本次实验案例是需要基于 Arm 虚拟硬件平台实现微信支付二维码识别的智能闸机系统,主要适用于一些智能楼宇的秩序管控领域。

它的核心功能有以下几点:

  • 可以实现对微信支付二维码的快速识别;
  • 支持本地脱机模式(端侧认证)运行;
  • 支持远程联机模式(云侧认证)运行;
  • 支持自主登记可被授权的二维码信息。

4.2 工作原理简介

本案例应用的最核心技术是基于图像输入的二维码识别,在一个微信支付展示支付码界面,其本质就是一个一维码和二维码展示,如下图所示:

我们可以看到下方的二维码就是一串可做身份识别的ID数字,我们只要基于一定的管控逻辑对这串数字做认证授权即可。

4.3 核心流程图

录入微信支付二维码授权信息的流程图:

端侧脱机认证的流程图:

云测联机认证的流程图:

注意,本次实验因远程调试,仅仅核心功能实现,在实现过程中,动态图片均以提前预制好的图片为主,无法从摄像图或其他外设驱动获得真实的二维码图片,但核心逻辑是一致的

5 案例项目实现过程

5.1 RT-Thread 的移植

整个 RT-Thread 的移植可以参考我之前的博文,也可以尝试跟随 RT-Thread 的官方文档中心,自行完成,本文不再赘述。

5.2 Zbar 的移植

移植 ZBar 将本案例项目最核心的一部分内容,它将完成对图像(图片)的识别,从中提取二维码信息。

这是 ZBar 的 Github开源网址,https://github/ZBar/ZBar/ 但是比较遗憾的是,ZBar项目源码在 2012 年就停止维护运营了,不过其核心功能还是很出色的,值得信赖。

ZBar 条形码阅读器是一个开源软件,适用于读取不同来源的条形码,如视频流、图像文件和原始强度传感器。它支持 EAN-13/UPC-A, UPC-E, EAN-8, Code 128,Code 39, Codabar, Interleaved 2/5 和二维码。ZBar库支持8位灰度图片/图像数据做为输入进行对条形码和二维码的解码,本身它是不带图片/图像采集功能的,所以在本项目作用时可以考虑使用视频设备(如网络摄像头)作为条形码/二维码做为采集端,本文将不再详细介绍。对于开发者,支持语言绑定包括 C、C++、Python 和 Perl,以及用于 Qt、GTK 和 PyGTK 的 GUI 小部件。该项目旨在创建一个高性能、稳定、健壮的库组件,并提供支持基础设施,使其易于在各种应用程序中使用。

移植 ZBar 主要分为以下几个重要步骤:

  • 识别出编译配置项,形成 zbar_config.h,比如至少要开启 QRCODE 的支持;
  • 识别出参与编译的工程文件,将他们列入到 cbuild 的 proj 工程文件中;
  • 添加对应的宏定义及头文件检索目录;
  • 执行编译,修复一些编译问题,例如函数接口的转换、内存操作等相关接口的调整;
  • 增加若干测试用例,测试 ZBar 识别二维码的基础能力;
  • 存在一个内存释放的错误,修复相关代码 bug。

5.3 认证授权的逻辑实现

因制作逻辑展示,这部分的逻辑相对做得比较简单,将已授权的二维码信息存放在 RAM 内存中,收到 ZBar 识别的二维码信息,与授权库的二维码信息做匹配即可完成认证过程。

具体的可以结合上文的流程图及下文的示例代码展示。

5.4 遇到的难题

这期间遇到的难度有3个:

  1. ZBar 对识别图片的输入,要求是 8 位深度的灰度图片数据,而不能是原生的,比如 24 位 BMP 图片数据;
  2. 工程实例中,对 PNG 图片尚不支持,只能处理 BMP 图片;
  3. ZBar 对某些情况下的图片识别不够,有时会出错而识别不到;或者有时候识别出错误的信息;这可能跟输入图片的质量有关系。

6 核心代码展示

6.1 ZBar的移植代码

两部分内容:配置文件和工程文件列表:

#ifndef __CONFIG_H__
#define __CONFIG_H__

#include <stdint.h>

#if (CFG_RTT_ZBAR)
//#include "rtthread.h"
#endif

/* include/config.h.  Generated from config.h.in by configure.  */
/* include/config.h.in.  Generated from configure.ac by autoheader.  */

/* whether to build support for Code 128 symbology */
#define ENABLE_CODE128 1

/* whether to build support for Code 39 symbology */
#define ENABLE_CODE39 1

/* whether to build support for EAN symbologies */
#define ENABLE_EAN 1

/* whether to build support for Interleaved 2 of 5 symbology */
#define ENABLE_I25 1

/* whether to build support for PDF417 symbology */
#define ENABLE_PDF417

/* whether to build support for QR Code */
#define ENABLE_QRCODE 1

/* Program major version (before the '.') as a number */
#define ZBAR_VERSION_MAJOR 0

/* Program minor version (after '.') as a number */
#define ZBAR_VERSION_MINOR 10

#define         PRIx32   "lx"
#if 0
/* Define to 1 if you have the `atexit' function. */
#define HAVE_ATEXIT 1

/* Define to 1 if you have the <dlfcn.h> header file. */
#define HAVE_DLFCN_H 1

/* Define to 1 if you have the <fcntl.h> header file. */
#define HAVE_FCNTL_H 1

/* Define to 1 if you have the <features.h> header file. */
#define HAVE_FEATURES_H 1

/* Define to 1 if you have the `getpagesize' function. */
#define HAVE_GETPAGESIZE 1

/* Define if you have the iconv() function and it works. */
#define HAVE_ICONV 1

/* Define to 1 if you have the <inttypes.h> header file. */
#define HAVE_INTTYPES_H 1

/* Define to 1 if you have the <jpeglib.h> header file. */
/* #undef HAVE_JPEGLIB_H */

/* Define to 1 if you have the `jpeg' library (-ljpeg). */
/* #undef HAVE_LIBJPEG */

/* Define to 1 if you have the `pthread' library (-lpthread). */
#define HAVE_LIBPTHREAD 1

/* Define to 1 if you have the <linux/videodev2.h> header file. */
/* #undef HAVE_LINUX_VIDEODEV2_H */

/* Define to 1 if you have the <linux/videodev.h> header file. */
/* #undef HAVE_LINUX_VIDEODEV_H */

/* Define to 1 if you have the <memory.h> header file. */
#define HAVE_MEMORY_H 1

/* Define to 1 if you have the `memset' function. */
#define HAVE_MEMSET 1

/* Define to 1 if you have a working `mmap' system call. */
#define HAVE_MMAP 1

/* Define to 1 if you have the <poll.h> header file. */
#define HAVE_POLL_H 1

/* Define to 1 if you have the <pthread.h> header file. */
#define HAVE_PTHREAD_H 1

/* Define to 1 if you have the `setenv' function. */
#define HAVE_SETENV 1

/* Define to 1 if you have the <stdint.h> header file. */
#define HAVE_STDINT_H 1

/* Define to 1 if you have the <stdlib.h> header file. */
#define HAVE_STDLIB_H 1

/* Define to 1 if you have the <strings.h> header file. */
#define HAVE_STRINGS_H 1

/* Define to 1 if you have the <string.h> header file. */
#define HAVE_STRING_H 1

/* Define to 1 if you have the <sys/ioctl.h> header file. */
#define HAVE_SYS_IOCTL_H 1

/* Define to 1 if you have the <sys/ipc.h> header file. */
#define HAVE_SYS_IPC_H 1

/* Define to 1 if you have the <sys/mman.h> header file. */
#define HAVE_SYS_MMAN_H 1

/* Define to 1 if you have the <sys/shm.h> header file. */
#define HAVE_SYS_SHM_H 1

/* Define to 1 if you have the <sys/stat.h> header file. */
#define HAVE_SYS_STAT_H 1

/* Define to 1 if you have the <sys/times.h> header file. */
#define HAVE_SYS_TIMES_H 1

/* Define to 1 if you have the <sys/time.h> header file. */
#define HAVE_SYS_TIME_H 1

/* Define to 1 if you have the <sys/types.h> header file. */
#define HAVE_SYS_TYPES_H 1

/* Define to 1 if the system has the type `uintptr_t'. */
#define HAVE_UINTPTR_T 1

/* Define to 1 if you have the <unistd.h> header file. */
#define HAVE_UNISTD_H 1

/* Define to 1 if you have the <vfw.h> header file. */
/* #undef HAVE_VFW_H */

/* Define to 1 if you have the <X11/extensions/XShm.h> header file. */
/* #undef HAVE_X11_EXTENSIONS_XSHM_H */

/* Define to 1 if you have the <X11/extensions/Xvlib.h> header file. */
/* #undef HAVE_X11_EXTENSIONS_XVLIB_H */

/* Define as const if the declaration of iconv() needs const. */
#define ICONV_CONST

/* Library major version */
#define LIB_VERSION_MAJOR 0

/* Library minor version */
#define LIB_VERSION_MINOR 2

/* Library revision */
#define LIB_VERSION_REVISION 0

/* Define to the sub-directory in which libtool stores uninstalled libraries.
   */
#define LT_OBJDIR ".libs/"

/* Define to 1 if assertions should be disabled. */
/* #undef NDEBUG */

/* Define to 1 if your C compiler doesn't accept -c and -o together. */
/* #undef NO_MINUS_C_MINUS_O */

/* Name of package */
#define PACKAGE "zbar"

/* Define to the address where bug reports for this package should be sent. */
#define PACKAGE_BUGREPORT "spadix@users.sourceforge"

/* Define to the full name of this package. */
#define PACKAGE_NAME "zbar"

/* Define to the full name and version of this package. */
#define PACKAGE_STRING "zbar 0.10"

/* Define to the one symbol short name of this package. */
#define PACKAGE_TARNAME "zbar"

/* Define to the version of this package. */
#define PACKAGE_VERSION "0.10"

/* Define to 1 if you have the ANSI C header files. */
#define STDC_HEADERS 1

/* Version number of package */
#define VERSION "0.10"

/* Define to 1 if the X Window System is missing or not being used. */
#define X_DISPLAY_MISSING 1

/* Program major version (before the '.') as a number */
#define ZBAR_VERSION_MAJOR 0

/* Program minor version (after '.') as a number */
#define ZBAR_VERSION_MINOR 10

/* Define for Solaris 2.5.1 so the uint32_t typedef from <sys/synch.h>,
   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
   #define below would cause a syntax error. */
/* #undef _UINT32_T */

/* Define for Solaris 2.5.1 so the uint8_t typedef from <sys/synch.h>,
   <pthread.h>, or <semaphore.h> is not used. If the typedef were allowed, the
   #define below would cause a syntax error. */
/* #undef _UINT8_T */

/* Minimum Windows API version */
/* #undef _WIN32_WINNT */

/* used only for pthread debug attributes */
#define __USE_UNIX98 1

/* Define to empty if `const' does not conform to ANSI C. */
/* #undef const */

/* Define to `__inline__' or `__inline' if that's what the C compiler
   calls it, or to nothing if 'inline' is not supported under any name.  */
#ifndef __cplusplus
/* #undef inline */
#endif

/* Define to the type of a signed integer type of width exactly 32 bits if
   such a type exists and the standard includes do not define it. */
/* #undef int32_t */

/* Define to the type of an unsigned integer type of width exactly 32 bits if
   such a type exists and the standard includes do not define it. */
/* #undef uint32_t */

/* Define to the type of an unsigned integer type of width exactly 8 bits if
   such a type exists and the standard includes do not define it. */
/* #undef uint8_t */

/* Define to the type of an unsigned integer type wide enough to hold a
   pointer, if such a type exists, and if the system does not define it. */
/* #undef uintptr_t */

#ifndef X_DISPLAY_MISSING
# define HAVE_X
#endif
#endif
#endif

工程文件列表展示,proj 文件的增加:

    <group name="zbar">
      <cflags add="-I ./zbar/decoder -I ./zbar/qrcode -I ./zbar -I ./ -I ./zbar_app" compiler="AC6"/>

      <file category="sourceC" name="zbar/decoder/code39.c"/>
      <file category="sourceC" name="zbar/decoder/code128.c"/>
      <file category="sourceC" name="zbar/decoder/ean.c"/>
      <file category="sourceC" name="zbar/decoder/i25.c"/>
      <file category="sourceC" name="zbar/decoder/pdf417.c"/>
      <file category="sourceC" name="zbar/decoder/qr_finder.c"/>
      <file category="sourceC" name="zbar/qrcode/bch15_5.c"/>
      <file category="sourceC" name="zbar/qrcode/binarize.c"/>
      <file category="sourceC" name="zbar/qrcode/isaac.c"/>
      <file category="sourceC" name="zbar/qrcode/qrdec.c"/>
      <file category="sourceC" name="zbar/qrcode/qrdectxt.c"/>
      <file category="sourceC" name="zbar/qrcode/rs.c"/>
      <file category="sourceC" name="zbar/qrcode/util.c"/>
      <file category="sourceC" name="zbar/config.c"/>
      <file category="sourceC" name="zbar/decoder.c"/>
      <file category="sourceC" name="zbar/error.c"/>
      <file category="sourceC" name="zbar/image.c"/>
      <file category="sourceC" name="zbar/img_scanner.c"/>
      <file category="sourceC" name="zbar/refcnt.c"/>
      <file category="sourceC" name="zbar/scanner.c"/>
      <file category="sourceC" name="zbar/symbol.c"/>
      <file category="sourceC" name="zbar_app/zbar_main.c"/>
      <file category="sourceC" name="zbar_app/zbar_auth.c"/>
    </group>

工程示例图:

6.2 授权认证的逻辑代码

端侧本地脱机认证的逻辑代码:

// register auth ok qrcode
static char *g_auth_ok_qrcoe_list[] = 
{
    "130608135571250081",
};

int zbar_check_auth_offline(char *qrcode)
{
    int i = 0;

    for (i = 0; i < ARRAY_SIZE(g_auth_ok_qrcoe_list); i++) {
        if (!strcmp(g_auth_ok_qrcoe_list[i], qrcode)) {
            rt_kprintf("qrcode: %s auth offline ok\n", qrcode);
            return 0;
        }
    }

    rt_kprintf("qrcode: %s auth offline fail\n", qrcode);
    return 0;
}

云端远程联机认证的逻辑代码:

端侧构建一个格式为: {“qrcode” : “1234567890” } 的JSON字符串发往云端,云端进行授权认证,将结果返回给端侧,参考如下,更为详细的代码见下一小节。

   ret = iotSocketRecv (sock, buffer, sizeof(buffer));
    if (ret < 0) {
            rt_kprintf("recv fail\n");
            goto exit_entry;
    }
    rt_kprintf("<<< %s\n", buffer);

    cJSON * rsp_root = NULL;
    cJSON * result = NULL;

    rsp_root = cJSON_Parse(buffer);
    result = cJSON_GetObjectItem(rsp_root, "result");

    if (!strcmp(result->valuestring, "ok")) {
        rt_kprintf("qrcode: %s auth online ok\n", qrcode);
        ret = 0;
    } else {
            rt_kprintf("qrcode: %s auth online fail\n", qrcode);
    }

6.3 远程联机的核心代码

本部分利用的 Arm 虚拟硬件独有的 VSocket 特性,感兴趣可以从 Arm 虚拟硬件的帮助文档 https://arm-software.github.io/AVH/main/simulation/html/group__arm__vsocket.html 了解 。

具体的代码体现如下,整体采用的经典的 CS 模型,云端做服务器,端侧做客户端。

服务器云端的Python脚本的代码:

import socket
import sys
import json
import socket
import os
import shutil
import binascii

debug_mode = False

tcp_mode = True

# 定义服务器的IP地址和端口号
SERVER_IP_ADDRESS = "127.0.0.1"
if tcp_mode:
    TCP_PORT_NO0 = 18888
    TCP_PORT_NO1 = 18889
else:
    UDP_PORT_NO = 55556

'''
// register auth ok qrcode
static char *g_auth_ok_qrcoe_list[] = 
{
    "130608135571250081",
};
'''
g_auth_ok_qrcoe_list = ["130608135571250081"]

# 创建一个UDP socket
if tcp_mode:
    serverSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
else:
    serverSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

if tcp_mode:
    try: 
        # Server address and port
        server_address = (SERVER_IP_ADDRESS, TCP_PORT_NO0)
        # Bind the socket to the port
        if debug_mode:
            print(f"Starting up on {server_address[0]} port {server_address[1]}")
        serverSock.bind(server_address)
    except Exception as e:
        # Server address and port
        server_address = (SERVER_IP_ADDRESS, TCP_PORT_NO1)
        # Bind the socket to the port
        if debug_mode:
            print(f"Starting up on {server_address[0]} port {server_address[1]}")
        serverSock.bind(server_address)
else:
    # 绑定socket到指定的IP地址和端口号
    serverSock.bind((SERVER_IP_ADDRESS, UDP_PORT_NO))

file_fd = None

if tcp_mode:
    # Listen for incoming connections
    serverSock.listen(1)

while True:
    if tcp_mode:
        if debug_mode:
            print("Waiting for a new connection...")
        connection, client_address = serverSock.accept()
        #print(connection)
        #print(client_address)

    #print('-------------1111')
    if tcp_mode:
        data = connection.recv(10240)
    else:
        data, addr = serverSock.recvfrom(10240)

    '''
    {
        "qrcode": "1234567",
        "result": "ok",
    }
    '''

    try:
        qrcode = ""
        data_str = data.decode('utf-8').strip()
        if debug_mode:
            print(data_str)
        parsed_data = json.loads(data_str)
        qrcode = parsed_data.get('qrcode')
        #print(qrcode)
    except Exception as e:
        rsp_content = str(e)

    try:
        if qrcode in g_auth_ok_qrcoe_list:
            rsp_content = "{\"qrcode\":\"" + qrcode + "\", \"result\": \"ok\"}"
        
        else:
            rsp_content = "{\"qrcode\":\"" + qrcode + "\", \"result\": \"fail\"}"
        
    except Exception as e:
        rsp_content = str(e)


    if tcp_mode:
        connection.sendall(rsp_content.encode('utf-8'))
        connection.close()
    else:
        serverSock.sendto(rsp_content.encode('utf-8'), addr)
        serverSock.close()
    
    if debug_mode:
        print(rsp_content)
        print("")

Arm 虚拟硬件(充当客户端)的代码:

int zbar_check_auth_online(char *qrcode)
{
        int sock;
        int ret = -1;        
    char buffer[1024];

    //rt_kprintf("qrcode: %s\n", qrcode);
    if ((sock = iotSocketCreate(IOT_SOCKET_AF_INET, IOT_SOCKET_SOCK_STREAM, IOT_SOCKET_IPPROTO_TCP)) < 0) {
            rt_kprintf("socket fail\n");
            goto exit_entry;
    }

    uint8_t ip_array[] = SERVER_IP_BYTES;
    uint32_t ip_len = sizeof(ip_array);
    ret = iotSocketConnect (sock, (const uint8_t *)ip_array, ip_len, SERVER_PORT0);
    if (ret < 0) {
            //rt_kprintf("connect fail\n");
        ret = iotSocketConnect (sock, (const uint8_t *)ip_array, ip_len, SERVER_PORT1);
        if (ret < 0) {
            rt_kprintf("connect fail\n");
            goto exit_entry;
        }
    }

    //snprintf(buffer, sizeof(buffer), "{\"qrcode\" : \"%s\"}", qrcode);
    cJSON *root = cJSON_CreateObject();
    cJSON_AddItemToObject(root, "qrcode", cJSON_CreateString(qrcode));
    char *json_string = cJSON_PrintUnformatted(root);

    ret = iotSocketSend(sock, json_string, strlen(json_string));
    if (ret < 0) {
            rt_kprintf("send fail\n");
            goto exit_entry;
    }
    rt_kprintf(">>> %s\n", json_string);

    free(json_string);
    cJSON_Delete(root);

    ret = iotSocketRecv (sock, buffer, sizeof(buffer));
    if (ret < 0) {
            rt_kprintf("recv fail\n");
            goto exit_entry;
    }
    rt_kprintf("<<< %s\n", buffer);

    cJSON * rsp_root = NULL;
    cJSON * result = NULL;

    rsp_root = cJSON_Parse(buffer);
    result = cJSON_GetObjectItem(rsp_root, "result");

    if (!strcmp(result->valuestring, "ok")) {
        rt_kprintf("qrcode: %s auth online ok\n", qrcode);
        ret = 0;
    } else {
            rt_kprintf("qrcode: %s auth online fail\n", qrcode);
    }

    cJSON_Delete(rsp_root);

exit_entry:
        if (sock >= 0) {
                iotSocketClose(sock);
        }
        
    return ret;
}

对源码感兴趣的,可以进一步参考文末给出的仓库地址。

7 功能测试

7.1 工程的编译构建

本工程项目采用 Makefile + cbuild 来进行构建。最外层,我们使用的事Makefile进行操作的封装。

输入make help可以获取帮助信息,一眼可以看出如何进行输入命令:

$ make help
make help       -> Show this help msg..
make build      -> Build thie demo.
make clean      -> Clean object files.
make run        -> Run this demo.
make run_finsh  -> Run this demo, which enable RTT finsh.
make debug      -> Build & run this demo.
make all        -> Source & clean & build & run all together.
make stop       -> Stop to run this demo.

输入make clean 清除编译生成的中间文件,make build 可以进行代码的编译构建,少部分编译警告信息可以忽略:

ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar$ make clean
Clean ...
rm -rf ./Objects/
rm -rf out
ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar$ 
ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar$ make build
Building ...
cbuild --packs ./AVH-FVP_MPS2_Cortex-M85.cprj
info cbuild: Build Invocation 1.2.0 (C) 2022 ARM
(cbuildgen): Build Process Manager 1.3.0 (C) 2022 Arm Ltd. and Contributors
M650: Command completed successfully.
(cbuildgen): Build Process Manager 1.3.0 (C) 2022 Arm Ltd. and Contributors
M652: Generated file for project build: '/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/Objects/CMakeLists.txt'
-- The ASM compiler identification is ARMClang
-- Found assembler: /opt/armcompiler/bin/armclang
-- Configuring done
-- Generating done
-- Build files have been written to: /home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/Objects
[8/80] Building C object CMakeFiles/image.dir/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar/decoder.o
In file included from /home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar/decoder.c:31:
/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar/decoder.h:194:26: warning: implicit declaration of function 'rt_realloc' is invalid in C99 [-Wimplicit-function-declaration]
    unsigned char *buf = rt_realloc(dcode->buf, len);
                         ^
/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar/decoder.h:194:20: warning: incompatible integer to pointer conversion initializing 'unsigned char *' with an expression of type 'int' [-Wint-conversion]
    unsigned char *buf = rt_realloc(dcode->buf, len);
    
    省略部分log
    
 /home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar/symbol.h:70:18: warning: incompatible integer to pointer conversion assigning to 'point_t *' (aka 'struct point_s *') from 'int' [-Wint-conversion]
        sym->pts = rt_realloc(sym->pts, ++sym->pts_alloc * sizeof(point_t));
                 ^ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/zbar_app/zbar_main.c:137:5: warning: implicit declaration of function 'rt_free' is invalid in C99 [-Wimplicit-function-declaration]
    rt_free(pic_data);
    ^
6 warnings generated.
[80/80] Linking C executable image.axf
"/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar/RTE/Device/ARMCM85/ARMCM85_ac6.sct", line 31 (column 7): Warning: L6314W: No section matches pattern *(.bss.noinit).
Program Size: Code=102280 RO-data=123052 RW-data=476 ZI-data=233152  
Finished: 0 information, 1 warning and 0 error messages.
info cbuild: build finished successfully!

至此,我们就完成了面向 Arm 平台的 axf 文件的编译生成。

7.2 验证基于 ZBar 的二维码识别

输入make run_finsh即可把编译生成好的axf文件跑起来:

ubuntu@arm-43ecd4886e664dffa086d3a69133a5e5:~/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar$ make run_finsh
Running ...
make[1]: Entering directory '/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar'
make[1]: Leaving directory '/home/ubuntu/new/AVH-RTTHREAD-DEMO-RECAN/rt-thread/bsp/arm/AVH-FVP_MPS2_Cortex-M85-ZBar'
/opt/VHT/bin/FVP_MPS2_Cortex-M85 --stat --simlimit 80000000 -f ./vht_config.txt out/image.axf &
telnetterminal2: Listening for serial connection on port 5000
telnetterminal1: Listening for serial connection on port 5001
telnetterminal0: Listening for serial connection on port 5002

 \ | /
- RT -     Thread Operating System
 / | \     5.2.0 build Jun 15 2024 07:30:02
 2006 - 2024 Copyright by RT-Thread team
Hello ZBAR ...
Debug server create success, accepting clients @ port 12345 ...
Conncting debug server ... Conncted debug server ok ...
msh > 
msh > help
RT-Thread shell commands:
zbar_qrcode_auth - auth qrcode which decoded by zbar
pin              - pin [option]
clear            - clear the terminal screen
version          - show RT-Thread version information
list             - list objects
help             - RT-Thread shell help
ps               - List threads in the system
free             - Show the memory usage in the system

核心的命令就是 /opt/VHT/bin/FVP_MPS2_Cortex-M85 --stat --simlimit 80000000 -f ./vht_config.txt out/image.axf

为便于开发者理解,此处简单的展开说明,该命令调用了 Arm 虚拟硬件镜像中的 Cortex-M85 处理器的 FVP 模型 FVP_MPS2_Cortex-M85 模拟一个硬件开发板,我们直接在云服务器上就把本来应该跑在 Arm 开发板上的可执行文件给跑起来了。其中,该命令部分参数解读如下:

  • FVP_MPS2_Cortex-M85即为所调用的 Cortex-M85 的 FVP 模型的名称。
  • --stat 表示停止模拟时,打印相关的运行状态信息。
  • --simlimit 80000000 表示模拟运行的时间上限为 80000000s,即若用户未手动退出,则80000000s 后程序会自动退出运行。
  • out/image.axf 即为具备 ZBar 解析二维码功能的可执行应用文件。
  • -f vht_config.txt 即指定了 FVP 模型运行时的所依据的配置文件。可以通过 FVP_MPS2_Cortex-M85 -l 命令获取 Cortex-M85 的 FVP 模型所有可配置的参数及其默认值(初始值)信息。用户可根据自身需求进行参数调整,获得不同的应用执行效果。

7.3 运行示例图展示

因仅做核心功能展示,我们这里用到了 RT-Thread 的自定义 finsh 命令,构建了一个 zbar_qrcode_auth 命令,我们通过这个命令可以完成授权认证测试,参考如下。

在实验代码中,已提取将 “130608135571250081” 录入到授权库中,而 “130608135571250082”没有录入进去,所以预期是一个会授权认证成功,一个授权认证失败。

本地脱机模式认证成功:

本地脱机模式认证失败:

云端联机模式认证成功:

云端联机模式认证失败:

7.4 相关验证的输入图片

本次案例使用的图片案例位于下面的目录,使用的是红色标记出的两个图片输入:

在做图片转换的过程,需要用到 Image2Lcd 软件工具,它可以从公开的一些工具网站下载到,使用界面参考如下,它可以将对应的图片生成工程代码中需要的 16 位 RGB565 图片。

它还可以将图片直接转成 C 语言的数组,方便编程中嵌入使用,非常方便,感兴趣可以自行了解下这个工具的使用。

8 更多思考

8.1 关于 Arm 虚拟硬件的几点优势及本项目的前景

我想补充几点:

  • Arm 虚拟硬件平台给了非常便利的开发、编译、调试、运行验证的操作体验,无论是在开发阶段还是在生产阶段,都能给开发者及企业带来很大的便利。
  • 相对于其他孤立的芯片开发平台,Arm 虚拟硬件平台在成套的软件包上还是比较完毕的,比如 RTOS 相关的、网络相关的、安全相关的等等软件包,都可以通过快速配置得到比较好的复用,这一点在开发流程上,也得到了很大的改善。
  • 借助 Arm 虚拟硬件的网络通讯平台,其具备公网通讯的能力,这一点可以在适当的功能扩展上做成很多基于网络通讯的应用工程,是一个值得期待的开发亮点。
  • 有了 RT-Thread 和 ZBar 的加持,对于一维码和二维码识别的场景,我们就能够支持了,欢迎广大读者想象出更多的应用场景。

8.2 移植过程的心得体会分享

  • 核心逻辑先跑 PC 环境,这一点非常重要;
  • 移植一个开源库,学习它的官方文档是必不可少的;
  • 遇到问题,需要冷静分析,一步步细心排查,不放松一丁点的蛛丝马迹。

8.3 项目的后续迭代与展望

当然,就当前的实现案例来说,也还存在一些不同需要后续补充改进,比如:

  • ZBar 识别成功率的问题;
  • 图片格式的识别问题;
  • 内存占用过大的优化问题;
  • 接入摄像头外设等问题。

这些问题,都是一些值得深挖的核心问题,需要读者朋友进一步去探索和实践,也非常欢迎有有问题与我讨论。

9 参考资料

  1. 本案例的主代码仓库地址:https://github/recan-li/AVH-RTTHREAD-DEMO-RECAN 在对应的rt-thread/bsp/arm/ 目录下即可找到对应的FVP适配工程及对应的代码。
  2. Arm 虚拟硬件产品简介
  3. Arm 虚拟硬件帮助文档
  4. Arm 虚拟硬件开发者资源
  5. 【中文技术指南】Arm 虚拟硬件实践专题一:产品订阅指南(百度智能云版)
  6. 【中文技术指南】Arm 虚拟硬件实践专题二:Arm 虚拟硬件 FVP 模型入门指南
  7. 【中文视频直播课】加速AI开发,1小时快速入门Arm虚拟硬件
  8. Arm 社区微信公众号

本文标签: 硬件智能行业系统平台