[GD32VW553-IOT_V2] 边缘 AI 推理与物联网状态监测MQTT终端简单实现

分享作者:EricWang
评测品牌:萤火工场
评测型号:GD32VW553-IOT-V2
发布时间:2026-07-23 09:39:47
1
概要
本项目基于兆易创新 GD32VW553 无线微控制器,构建了一套集本地神经网络推理、Wi-Fi 局域网通信、HTTP 可视化交互、MQTT 状态上报和芯片温度监测于一体的轻量级边缘智能终端。
开源口碑分享内容

1. 前言

GD32VW553是一款集成RISC-V处理器、2.4 GHz Wi-Fi、BLE、片上Flash、SRAM以及多种外设接口的无线微控制器。该芯片最高运行频率为160 MHz,并支持浮点和DSP扩展指令,为轻量级神经网络、无线通信与实时控制功能的融合提供了较好的硬件基础。基于这一特点,本项目以GD32VW553为核心主控,尝试在单芯片资源条件下完成从底层驱动、FreeRTOS任务调度、Wi-Fi网络接入,到HTTP交互、NMSIS-NN模型推理、ADC温度采集及MQTT状态发布的完整部署流程。

本评测报告将重点评估GD32VW553在资源受限环境下同时承载无线网络、Web服务、物联网通信和轻量级神经网络推理任务的可行性。

2. 环境配置

2.1 工具链

从文档上看,GD32VW553使用了nuclei N307内核,核心配置为:

CPU 架构:RISC-V RV32
主频:160 MHz
ABI:ilp32f
FPU:单精度浮点
DSP:Packed-SIMD / Xxldsp
指令扩展:
rv32imafc
zba / zbb / zbc / zbs
xxldsp
Debug: JTAG/cJTAG

因此可以直接下载uclei工具链

编译参数为:

-march=rv32imafc_zba_zbb_zbc_zbs_xxldsp
-mabi=ilp32f

其中,NMSIS-NN 主要利用定点运算、SIMD 指令和优化卷积内核提升神经网络推理效率。

2.2  OpenOCD调试器

GDBuilder使用OpenOCD,实现代码的烧录以及调试。仿照其cfg,我们可以使用OpenOCD,直接将其移植到VSCode中

#
# GigaDevice GD32VW55x target
#
adapter driver cmsis-dap

adapter speed     4000
reset_config trst_only
adapter srst pulse_width 100

transport select jtag

set _CHIPNAME riscv
jtag newtap $_CHIPNAME cpu -irlen 5 -expected-id 0x10307a6d

set _TARGETNAME $_CHIPNAME.cpu
target create $_TARGETNAME riscv -chain-position $_TARGETNAME
$_TARGETNAME configure -work-area-phys 0x20000000 -work-area-size 20480 -work-area-backup 0


# Work-area is a space in RAM used for flash programming
if { [info exists WORKAREASIZE] } {
   set _WORKAREASIZE $WORKAREASIZE
} else {
   set _WORKAREASIZE 0x5000
}

# Allow overriding the Flash bank size
if { [info exists FLASH_SIZE] } {
    set _FLASH_SIZE $FLASH_SIZE
} else {
    # autodetect size
    set _FLASH_SIZE 0
}

# flash size will be probed
set _FLASHNAME $_CHIPNAME.flash

flash bank $_FLASHNAME gd32vw55x 0x08000000 0x400000 0 0 $_TARGETNAME
riscv set_reset_timeout_sec 1
init

halt
{
    "version": "2.0.0",
    "tasks": [
        {
            "label": "flash",
            "type": "shell",
            "command": "D:/Projects/gd32/workspace/gd32vm553/riscv_toolchains/openocd_1.0.2.202606101542/bin/openocd.exe",
            "args": [
                "-f", "openocd_gdlink.cfg",
                "-c", "halt; flash write_image erase build/gcc-release/edge_ai_web.elf; verify_image build/gcc-release/edge_ai_web.elf; reset run; exit"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "problemMatcher": []
        }
    ]
}

将JTAG连接到调试器,测试一下代码的下载与调试

硬件连线

 PS: > D:/Projects/gd32/workspace/gd32vm553/riscv_toolchains/openocd_1.0.2.202606101542/bin/openocd.exe -f openocd_gdlink.cfg -c 'halt; flash write_image erase build/gcc-release/edge_ai_web.elf; verify_image build/gcc-release/edge_ai_web.elf; reset run; exit'
xPack Open On-Chip Debugger 0.12.0+dev1.14.1-01850-geb6f2745b-dirty (2026-06-09-13:04)
Licensed under GNU GPL v2
For bug reports, read
        http://openocd.org/doc/doxygen/bugs.html
Info : CMSIS-DAP: SWD supported
Info : CMSIS-DAP: JTAG supported
Info : CMSIS-DAP: Atomic commands supported
Info : CMSIS-DAP: FW Version = 0254
Info : CMSIS-DAP: Serial# = 110136024420312043525138313030313932303697969903
Info : CMSIS-DAP: Interface Initialised (JTAG)
Info : SWCLK/TCK = 1 SWDIO/TMS = 1 TDI = 1 TDO = 0 nTRST = 0 nRESET = 1
Info : CMSIS-DAP: Interface ready
Info : clock speed 4000 kHz
Info : cmsis-dap JTAG TLR_RESET
Info : cmsis-dap JTAG TLR_RESET
Info : JTAG tap: riscv.cpu tap/device found: 0x10307a6d (mfg: 0x536 (Nuclei System Technology Co Ltd), part: 0x0307, ver: 0x1)
Info : JTAG tap: auto0.tap tap/device found: 0x790007a3 (mfg: 0x3d1 (GigaDevice Semiconductor (Beijing)), part: 0x9000, ver: 0x7)
Warn : AUTO auto0.tap - use "jtag newtap auto0 tap -irlen 5 -expected-id 0x790007a3"
Info : datacount=4 progbufsize=2
Info : Examined RISC-V core; found 1 harts
Info :  hart 0: XLEN=32, misa=0x4010912f
Info : [riscv.cpu] Examination succeed
Info : [riscv.cpu] starting gdb server on 3333
Info : Listening on port 3333 for gdb connections
Info : device id = 0x23030418
Warn : gd32vw55x flash size failed, probe inaccurate - assuming 4096k flash
Info : ignoring flash probed value, using configured bank size
Info : flash size = 4096kbytes
Info : GD32: Flash erase ... sector erase(0 to 121)
Info : fmc_obstat_reg = 100
Info : erase ok
Info : GD32: Flash write ... not words to write, padding with 0xff
Info : GD32: Flash write ... words to be prgrammed = 0x0001e801
Info : cmsis-dap JTAG TLR_RESET
Info : cmsis-dap JTAG TLR_RESET
Info : JTAG tap: riscv.cpu tap/device found: 0x10307a6d (mfg: 0x536 (Nuclei System Technology Co Ltd), part: 0x0307, ver: 0x1)
Info : JTAG tap: auto0.tap tap/device found: 0x790007a3 (mfg: 0x3d1 (GigaDevice Semiconductor (Beijing)), part: 0x9000, ver: 0x7)

3. 代码实现

3.1 最小系统实现

由于官方 Wi-Fi/BLE SDK 包含大量示例和完整应用框架,对于边缘 AI 场景存在代码冗余。因此项目先对 SDK 进行裁剪,仅保留FreeRTOS,BSP,WiFI/LwIP层,移除BLE协议栈,最终的主应用实现如下:


#include <stdint.h>

#include "gd32vw55x.h"
#include "gd32vw55x_platform.h"
#include "wifi_init.h"
#include "wrapper_os.h"

#include "board.h"
#include "wifi_http_app.h"

#include "FreeRTOS.h"
#include "task.h"

static void led_task(void *argument)
{
    (void)argument;

    for (;;) {
        board_led_toggle();
        vTaskDelay(pdMS_TO_TICKS(500U));
    }
}

static void monitor_task(void *argument)
{
    (void)argument;

    for (;;) {
        board_uart_write("[rtos] uptime_ms=");
        board_uart_write_u32(
            (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));
        board_uart_write(" free_heap=");
        board_uart_write_u32((uint32_t)xPortGetFreeHeapSize());
        board_uart_write(" min_heap=");
        board_uart_write_u32((uint32_t)xPortGetMinimumEverFreeHeapSize());
        board_uart_write("\r\n");

        vTaskDelay(pdMS_TO_TICKS(5000U));
    }
}

static void fail_with_code(const char *stage, int code)
{
    board_uart_write("[FATAL] ");
    board_uart_write(stage);
    board_uart_write(" failed, code=");
    board_uart_write_i32((int32_t)code);
    board_uart_write("\r\n");

    edge_assert_failed(__FILE__, __LINE__, stage);
}

int main(void)
{
    int ret;

    /* Keep the same ECLIC grouping used by the vendor Wi-Fi image. */
    eclic_priority_group_set(ECLIC_PRIGROUP_LEVEL4_PRIO0);

    /* UART1 and the project LED are available for early diagnostics. */
    board_init();
    board_uart_write("\r\nedge_ai_web\r\n");
    board_uart_write("SystemCoreClock=");
    board_uart_write_u32(SystemCoreClock);
    board_uart_write(" Hz\r\n");


    sys_os_init();

    board_uart_write("[platform] init...\r\n");
    platform_init();
    board_uart_write("[platform] ready\r\n");


    board_uart_write("[wifi] init...\r\n");
    ret = wifi_init();
    if (ret != 0) {
        fail_with_code("wifi_init", ret);
    }
    board_uart_write("[wifi] init accepted, free_heap=");
    board_uart_write_u32((uint32_t)xPortGetFreeHeapSize());
    board_uart_write("\r\n");

    if (wifi_http_app_start() != 0) {
        fail_with_code("wifi_http_app_start", -1);
    }

    if (xTaskCreate(led_task,
                    "led",
                    256U,
                    NULL,
                    1U,
                    NULL) != pdPASS) {
        fail_with_code("xTaskCreate(led)", -1);
    }

    if (xTaskCreate(monitor_task,
                    "monitor",
                    384U,
                    NULL,
                    1U,
                    NULL) != pdPASS) {
        fail_with_code("xTaskCreate(monitor)", -1);
    }

    board_uart_write("[rtos] scheduler start\r\n");
    sys_os_start();

    edge_assert_failed(__FILE__, __LINE__, "sys_os_start returned");
    return 0;
}

3.1 Wi-Fi 局域网通信

参考SDK,我们将WiFi设置为STA模式,使其可以连接到无线局域网络

#include <stdint.h>

#include "FreeRTOS.h"
#include "task.h"

#include "board.h"
#include "http_server.h"
#include "mqtt_status.h"
#include "wifi_http_app.h"
#include "wifi_station_config.h"

#include "wifi_management.h"
#include "wifi_net_ip.h"
#include "wifi_init.h"
#include "wrapper_os_config.h"

#define EDGE_HTTP_STACK_WORDS 1536U
#define EDGE_HTTP_TASK_PRIO   OS_TASK_PRIORITY(0)
#define EDGE_WIFI_VIF_DEFAULT 0

static char s_sta_ssid[] = EDGE_WIFI_STA_SSID;
static char s_sta_password[] = EDGE_WIFI_STA_PASSWORD;

static void log_result(const char *operation, int result)
{
    board_uart_write(operation);
    board_uart_write(" result=");
    board_uart_write_i32((int32_t)result);
    board_uart_write("\r\n");
}

static void log_ipv4(uint32_t address)
{
    board_uart_write_u32(address & 0xFFU);
    board_uart_write(".");
    board_uart_write_u32((address >> 8) & 0xFFU);
    board_uart_write(".");
    board_uart_write_u32((address >> 16) & 0xFFU);
    board_uart_write(".");
    board_uart_write_u32((address >> 24) & 0xFFU);
}

static int connect_station(void)
{
    struct wifi_ip_addr_cfg ip_config;
    int result;

    for (;;) {
        board_uart_write("[wifi] connecting STA SSID: ");
        board_uart_write(s_sta_ssid);
        board_uart_write("\r\n");


        result = wifi_management_connect(
            s_sta_ssid,
            (s_sta_password[0] != '\0') ? s_sta_password : NULL,
            1U);

        if (result == 0) {
            result = wifi_get_vif_ip(EDGE_WIFI_VIF_DEFAULT, &ip_config);
            if ((result == 0) && (ip_config.ipv4.addr != 0U)) {
                board_uart_write("[wifi] STA connected\r\n");
                board_uart_write("[wifi] IPv4: ");
                log_ipv4(ip_config.ipv4.addr);
                board_uart_write("\r\n[wifi] gateway: ");
                log_ipv4(ip_config.ipv4.gw);
                board_uart_write("\r\n[http] URL: http://");
                log_ipv4(ip_config.ipv4.addr);
                board_uart_write("/\r\n");
                return 0;
            }

            log_result("[wifi] wifi_get_vif_ip", result);
        } else {
            log_result("[wifi] station connect", result);
        }

        board_uart_write("[wifi] reconnect in ms=");
        board_uart_write_u32(EDGE_WIFI_STA_RECONNECT_PERIOD_MS);
        board_uart_write("\r\n");
        vTaskDelay(pdMS_TO_TICKS(EDGE_WIFI_STA_RECONNECT_PERIOD_MS));
    }
}

static void wifi_http_task(void *argument)
{
    int result;

    (void)argument;

    board_uart_write("[wifi] waiting for core tasks\r\n");

    result = wifi_wait_ready();
    if (result != 0) {
        log_result("[wifi] wifi_wait_ready", result);
        vTaskDelete(NULL);
        return;
    }

    /* wifi_wait_ready() also exposes the 32 KiB shared SRAM heap region. */
    board_uart_write("[wifi] core ready, free_heap=");
    board_uart_write_u32((uint32_t)xPortGetFreeHeapSize());
    board_uart_write("\r\n");

    result = connect_station();
    if (result != 0) {
        log_result("[wifi] connect_station", result);
        vTaskDelete(NULL);
        return;
    }

    /* MQTT and HTTP start only after DHCP has supplied a usable LAN address. */
    result = mqtt_status_start();
    if (result != 0) {
        log_result("[mqtt] mqtt_status_start", result);
    }

    result = http_server_run();
    log_result("[http] server stopped", result);

    (void)wifi_management_disconnect();
    vTaskDelete(NULL);
}

int wifi_http_app_start(void)
{
    BaseType_t result;

    result = xTaskCreate(
        wifi_http_task,
        "wifi-http",
        EDGE_HTTP_STACK_WORDS,
        NULL,
        EDGE_HTTP_TASK_PRIO,
        NULL);

    if (result != pdPASS) {
        board_uart_write("[wifi] failed to create wifi-http task\r\n");
        return -1;
    }

    return 0;
}

3.2 HTTP 可视化交互

设备内部使用静态网页实现一个简单的http server

#include <errno.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "lwip/inet.h"
#include "lwip/sockets.h"

#include "board.h"
#include "cifar10_inference.h"
#include "http_server.h"
#include "mqtt_status.h"

extern const char g_index_html[];
extern const size_t g_index_html_size;

#define HTTP_HEADER_BUFFER_SIZE 1024U
#define HTTP_JSON_BUFFER_SIZE    768U

static uint8_t s_rgb_upload[CIFAR10_INPUT_BYTES];

static const char k_header_html[] =
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: text/html; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "Connection: close\r\n"
    "\r\n";

static const char k_header_json[] =
    "HTTP/1.0 200 OK\r\n"
    "Content-Type: application/json; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "Connection: close\r\n"
    "\r\n";

static const char k_response_not_found[] =
    "HTTP/1.0 404 Not Found\r\n"
    "Content-Type: application/json; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "Connection: close\r\n"
    "\r\n"
    "{\"error\":\"not_found\"}\r\n";

static const char k_response_bad_request[] =
    "HTTP/1.0 400 Bad Request\r\n"
    "Content-Type: application/json; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "Connection: close\r\n"
    "\r\n"
    "{\"error\":\"expected_exactly_3072_bytes_rgb888\"}\r\n";

static const char k_response_server_error[] =
    "HTTP/1.0 500 Internal Server Error\r\n"
    "Content-Type: application/json; charset=utf-8\r\n"
    "Cache-Control: no-store\r\n"
    "Connection: close\r\n"
    "\r\n"
    "{\"error\":\"inference_failed\"}\r\n";

static const char k_model_json[] =
    "{"
    "\"model\":\"nmsis-cifar10-q7\","
    "\"input\":{\"width\":32,\"height\":32,\"channels\":3,"
    "\"format\":\"RGB888\",\"bytes\":3072},"
    "\"classes\":[\"Plane\",\"Car\",\"Bird\",\"Cat\",\"Deer\","
    "\"Dog\",\"Frog\",\"Horse\",\"Ship\",\"Truck\"]"
    "}\r\n";

static void log_socket_error(const char *stage)
{
    board_uart_write(stage);
    board_uart_write(" errno=");
    board_uart_write_i32((int32_t)errno);
    board_uart_write("\r\n");
}

static int send_all(int socket_fd, const void *data, size_t size)
{
    const uint8_t *cursor = (const uint8_t *)data;

    while (size > 0U) {
        int sent = send(socket_fd, cursor, size, 0);

        if (sent <= 0) {
            return -1;
        }

        cursor += (size_t)sent;
        size -= (size_t)sent;
    }

    return 0;
}

static int starts_with(const uint8_t *data,
                       size_t data_size,
                       const char *text)
{
    size_t text_size = strlen(text);

    return (data_size >= text_size) &&
           (memcmp(data, text, text_size) == 0);
}

static size_t find_header_end(const uint8_t *data, size_t size)
{
    size_t i;

    if (size < 4U) {
        return 0U;
    }

    for (i = 0U; i + 3U < size; ++i) {
        if ((data[i] == '\r') && (data[i + 1U] == '\n') &&
            (data[i + 2U] == '\r') && (data[i + 3U] == '\n')) {
            return i + 4U;
        }
    }

    return 0U;
}

static uint8_t ascii_lower(uint8_t value)
{
    if ((value >= (uint8_t)'A') && (value <= (uint8_t)'Z')) {
        return (uint8_t)(value + ((uint8_t)'a' - (uint8_t)'A'));
    }

    return value;
}

static int header_name_matches(const uint8_t *line,
                               size_t line_size,
                               const char *name)
{
    size_t i;
    size_t name_size = strlen(name);

    if (line_size < name_size) {
        return 0;
    }

    for (i = 0U; i < name_size; ++i) {
        if (ascii_lower(line[i]) != ascii_lower((uint8_t)name[i])) {
            return 0;
        }
    }

    return 1;
}

static int parse_content_length(const uint8_t *header,
                                size_t header_size,
                                size_t *content_length)
{
    static const char k_name[] = "content-length:";
    size_t line_start = 0U;

    while (line_start < header_size) {
        size_t line_end = line_start;
        size_t cursor;
        size_t value = 0U;
        int have_digit = 0;

        while ((line_end + 1U < header_size) &&
               !((header[line_end] == '\r') &&
                 (header[line_end + 1U] == '\n'))) {
            ++line_end;
        }

        if (header_name_matches(header + line_start,
                                line_end - line_start,
                                k_name)) {
            cursor = line_start + sizeof(k_name) - 1U;

            while ((cursor < line_end) &&
                   ((header[cursor] == ' ') || (header[cursor] == '\t'))) {
                ++cursor;
            }

            while ((cursor < line_end) &&
                   (header[cursor] >= '0') && (header[cursor] <= '9')) {
                have_digit = 1;
                value = (value * 10U) + (size_t)(header[cursor] - '0');
                if (value > CIFAR10_INPUT_BYTES) {
                    return -1;
                }
                ++cursor;
            }

            if (!have_digit) {
                return -1;
            }

            *content_length = value;
            return 0;
        }

        if (line_end + 2U > header_size) {
            break;
        }
        line_start = line_end + 2U;
    }

    return -1;
}

static int recv_http_header(int client_fd,
                            uint8_t *buffer,
                            size_t capacity,
                            size_t *received_size,
                            size_t *header_end)
{
    size_t total = 0U;

    while (total < capacity) {
        int received = recv(client_fd,
                            buffer + total,
                            capacity - total,
                            0);

        if (received <= 0) {
            return -1;
        }

        total += (size_t)received;
        *header_end = find_header_end(buffer, total);

        if (*header_end != 0U) {
            *received_size = total;
            return 0;
        }
    }

    return -1;
}

static int recv_exact_body(int client_fd,
                           const uint8_t *initial_body,
                           size_t initial_size,
                           uint8_t *destination,
                           size_t expected_size)
{
    size_t copied = initial_size;

    if (copied > expected_size) {
        copied = expected_size;
    }

    if (copied > 0U) {
        memcpy(destination, initial_body, copied);
    }

    while (copied < expected_size) {
        int received = recv(client_fd,
                            destination + copied,
                            expected_size - copied,
                            0);

        if (received <= 0) {
            return -1;
        }

        copied += (size_t)received;
    }

    return 0;
}

static int append_char(char *buffer,
                       size_t capacity,
                       size_t *length,
                       char value)
{
    if (*length + 1U >= capacity) {
        return -1;
    }

    buffer[*length] = value;
    ++(*length);
    buffer[*length] = '\0';
    return 0;
}

static int append_text(char *buffer,
                       size_t capacity,
                       size_t *length,
                       const char *text)
{
    while (*text != '\0') {
        if (append_char(buffer, capacity, length, *text) != 0) {
            return -1;
        }
        ++text;
    }

    return 0;
}

static int append_u32(char *buffer,
                      size_t capacity,
                      size_t *length,
                      uint32_t value)
{
    char digits[10];
    size_t count = 0U;

    do {
        digits[count++] = (char)('0' + (value % 10U));
        value /= 10U;
    } while ((value != 0U) && (count < sizeof(digits)));

    while (count > 0U) {
        if (append_char(buffer,
                        capacity,
                        length,
                        digits[--count]) != 0) {
            return -1;
        }
    }

    return 0;
}

static int append_i32(char *buffer,
                      size_t capacity,
                      size_t *length,
                      int32_t value)
{
    uint32_t magnitude;

    if (value < 0) {
        if (append_char(buffer, capacity, length, '-') != 0) {
            return -1;
        }
        magnitude = (uint32_t)(-(value + 1)) + 1U;
    } else {
        magnitude = (uint32_t)value;
    }

    return append_u32(buffer, capacity, length, magnitude);
}

static int build_result_json(char *buffer,
                             size_t capacity,
                             const cifar10_result_t *result)
{
    size_t length = 0U;
    size_t i;

    buffer[0] = '\0';

    if ((append_text(buffer, capacity, &length,
                     "{\"model\":\"nmsis-cifar10-q7\",\"top1\":{") != 0) ||
        (append_text(buffer, capacity, &length, "\"index\":") != 0) ||
        (append_u32(buffer, capacity, &length, result->top1_index) != 0) ||
        (append_text(buffer, capacity, &length, ",\"label\":\"") != 0) ||
        (append_text(buffer, capacity, &length,
                     cifar10_label(result->top1_index)) != 0) ||
        (append_text(buffer, capacity, &length, "\",\"score_q7\":") != 0) ||
        (append_i32(buffer, capacity, &length, result->top1_score_q7) != 0) ||
        (append_text(buffer, capacity, &length,
                     ",\"probability_1e4\":") != 0) ||
        (append_u32(buffer, capacity, &length,
                    result->top1_probability_1e4) != 0) ||
        (append_text(buffer, capacity, &length,
                     "},\"latency_ms\":") != 0) ||
        (append_u32(buffer, capacity, &length, result->latency_ms) != 0) ||
        (append_text(buffer, capacity, &length, ",\"scores_q7\":[") != 0)) {
        return -1;
    }

    for (i = 0U; i < CIFAR10_CLASS_COUNT; ++i) {
        if ((i != 0U) &&
            (append_char(buffer, capacity, &length, ',') != 0)) {
            return -1;
        }

        if (append_i32(buffer,
                       capacity,
                       &length,
                       result->scores_q7[i]) != 0) {
            return -1;
        }
    }

    if (append_text(buffer, capacity, &length, "]}\r\n") != 0) {
        return -1;
    }

    return (int)length;
}

static void serve_inference(int client_fd,
                            const uint8_t *header_buffer,
                            size_t received_size,
                            size_t header_end)
{
    cifar10_result_t result;
    char json[HTTP_JSON_BUFFER_SIZE];
    size_t content_length = 0U;
    size_t initial_body_size = received_size - header_end;
    int json_size;
    int infer_result;

    if ((parse_content_length(header_buffer,
                              header_end,
                              &content_length) != 0) ||
        (content_length != CIFAR10_INPUT_BYTES)) {
        (void)send_all(client_fd,
                       k_response_bad_request,
                       sizeof(k_response_bad_request) - 1U);
        return;
    }

    if (recv_exact_body(client_fd,
                        header_buffer + header_end,
                        initial_body_size,
                        s_rgb_upload,
                        CIFAR10_INPUT_BYTES) != 0) {
        (void)send_all(client_fd,
                       k_response_bad_request,
                       sizeof(k_response_bad_request) - 1U);
        return;
    }

    board_uart_write("[ai] CIFAR-10 inference start\r\n");
    infer_result = cifar10_infer_rgb888(s_rgb_upload,
                                        CIFAR10_INPUT_BYTES,
                                        &result);

    if (infer_result != 0) {
        board_uart_write("[ai] inference failed code=");
        board_uart_write_i32(infer_result);
        board_uart_write("\r\n");
        (void)send_all(client_fd,
                       k_response_server_error,
                       sizeof(k_response_server_error) - 1U);
        return;
    }

    board_uart_write("[ai] top1=");
    board_uart_write(cifar10_label(result.top1_index));
    board_uart_write(" score_q7=");
    board_uart_write_i32(result.top1_score_q7);
    board_uart_write(" latency_ms=");
    board_uart_write_u32(result.latency_ms);
    board_uart_write("\r\n");

    mqtt_status_record_inference(&result);

    json_size = build_result_json(json, sizeof(json), &result);
    if (json_size < 0) {
        (void)send_all(client_fd,
                       k_response_server_error,
                       sizeof(k_response_server_error) - 1U);
        return;
    }

    if (send_all(client_fd,
                 k_header_json,
                 sizeof(k_header_json) - 1U) == 0) {
        (void)send_all(client_fd, json, (size_t)json_size);
    }
}

static void serve_client(int client_fd)
{
    uint8_t request[HTTP_HEADER_BUFFER_SIZE];
    size_t received_size = 0U;
    size_t header_end = 0U;

    if (recv_http_header(client_fd,
                         request,
                         sizeof(request),
                         &received_size,
                         &header_end) != 0) {
        return;
    }

    if (starts_with(request, header_end, "GET / ") ||
        starts_with(request, header_end, "GET /index.html ")) {
        if (send_all(client_fd,
                     k_header_html,
                     sizeof(k_header_html) - 1U) == 0) {
            (void)send_all(client_fd, g_index_html, g_index_html_size);
        }
        return;
    }

    if (starts_with(request, header_end, "GET /api/model ")) {
        if (send_all(client_fd,
                     k_header_json,
                     sizeof(k_header_json) - 1U) == 0) {
            (void)send_all(client_fd,
                           k_model_json,
                           sizeof(k_model_json) - 1U);
        }
        return;
    }

    if (starts_with(request, header_end, "POST /api/infer ")) {
        serve_inference(client_fd,
                        request,
                        received_size,
                        header_end);
        return;
    }

    (void)send_all(client_fd,
                   k_response_not_found,
                   sizeof(k_response_not_found) - 1U);
}

int http_server_run(void)
{
    int server_fd;
    int reuse = 1;
    struct sockaddr_in address;

    server_fd = socket(AF_INET, SOCK_STREAM, 0);
    if (server_fd < 0) {
        log_socket_error("[http] socket failed");
        return -1;
    }

    if (setsockopt(server_fd,
                   SOL_SOCKET,
                   SO_REUSEADDR,
                   &reuse,
                   sizeof(reuse)) != 0) {
        log_socket_error("[http] SO_REUSEADDR failed");
        close(server_fd);
        return -2;
    }

    memset(&address, 0, sizeof(address));
    address.sin_family = AF_INET;
    address.sin_port = htons(80U);
    address.sin_addr.s_addr = htonl(INADDR_ANY);

    if (bind(server_fd,
             (struct sockaddr *)&address,
             sizeof(address)) != 0) {
        log_socket_error("[http] bind failed");
        close(server_fd);
        return -3;
    }

    if (listen(server_fd, 2) != 0) {
        log_socket_error("[http] listen failed");
        close(server_fd);
        return -4;
    }

    board_uart_write("[http] listening on TCP/80\r\n");
    board_uart_write("[ai] model_bytes=");
    board_uart_write_u32((uint32_t)cifar10_model_bytes());
    board_uart_write(" workspace_bytes=");
    board_uart_write_u32((uint32_t)cifar10_workspace_bytes());
    board_uart_write("\r\n");
    board_uart_write("[ai] POST /api/infer: 3072-byte RGB888\r\n");

    for (;;) {
        int client_fd = accept(server_fd, NULL, NULL);

        if (client_fd < 0) {
            log_socket_error("[http] accept failed");
            continue;
        }

        serve_client(client_fd);
        (void)shutdown(client_fd, SHUT_RDWR);
        close(client_fd);
    }
}

3.3 MQTT 状态上报

MQTT根据sdk参考示例改造,实现:内部温度采集;周期上传;远程状态监控。

#include <stddef.h>
#include <stdint.h>
#include <string.h>

#include "FreeRTOS.h"
#include "task.h"

#include "lwip/apps/mqtt.h"
#include "lwip/ip_addr.h"
#include "lwip/tcpip.h"

#include "board.h"
#include "cifar10_inference.h"
#include "mqtt_status.h"
#include "mqtt_status_config.h"
#include "wrapper_os_config.h"

#include "device_temperature.h"

/*
 * GigaDevice's lwIP MQTT fork declares this symbol in mqtt.h and expects
 * exactly one application-layer definition.
 */
int16_t connect_fail_reason = -1;

#define MQTT_JSON_BUFFER_SIZE  640U
#define MQTT_TOPIC_BUFFER_SIZE 96U

#define MQTT_QOS_TELEMETRY     0U
#define MQTT_RETAIN_EVENT      0U
#define MQTT_RETAIN_STATE      1U

#define MQTT_NOTIFY_INFERENCE  (1UL << 0)
#define MQTT_NOTIFY_CONNECTED  (1UL << 1)

static mqtt_client_t *s_client;
static TaskHandle_t s_task_handle;
static volatile uint8_t s_connected;
static volatile uint8_t s_connecting;
static volatile uint8_t s_started;

static cifar10_result_t s_latest_inference;
static uint32_t s_inference_sequence;
static uint8_t s_inference_valid;

static char s_client_id[] = EDGE_MQTT_CLIENT_ID;
static char s_username[] = EDGE_MQTT_USERNAME;
static char s_password[] = EDGE_MQTT_PASSWORD;
static char s_will_topic[] = EDGE_MQTT_TOPIC_ROOT "/availability";
static char s_will_message[] = "offline";

static struct mqtt_connect_client_info_t s_client_info;

static void append_char(char *buffer, size_t capacity, size_t *length, char value)
{
    if ((*length + 1U) < capacity) {
        buffer[*length] = value;
        ++(*length);
        buffer[*length] = '\0';
    }
}

static void append_text(char *buffer,
                        size_t capacity,
                        size_t *length,
                        const char *text)
{
    if (text == NULL) {
        return;
    }

    while (*text != '\0') {
        append_char(buffer, capacity, length, *text);
        ++text;
    }
}

static void append_u32(char *buffer,
                       size_t capacity,
                       size_t *length,
                       uint32_t value)
{
    char digits[10];
    size_t count = 0U;

    do {
        digits[count++] = (char)('0' + (value % 10U));
        value /= 10U;
    } while ((value != 0U) && (count < sizeof(digits)));

    while (count > 0U) {
        append_char(buffer, capacity, length, digits[--count]);
    }
}

static void append_i32(char *buffer,
                       size_t capacity,
                       size_t *length,
                       int32_t value)
{
    uint32_t magnitude;

    if (value < 0) {
        append_char(buffer, capacity, length, '-');
        magnitude = (uint32_t)(-(int64_t)value);
    } else {
        magnitude = (uint32_t)value;
    }

    append_u32(buffer, capacity, length, magnitude);
}

static int make_topic(char *buffer,
                      size_t capacity,
                      const char *suffix)
{
    size_t root_length = strlen(EDGE_MQTT_TOPIC_ROOT);
    size_t suffix_length = strlen(suffix);

    if ((root_length + 1U + suffix_length + 1U) > capacity) {
        return -1;
    }

    memcpy(buffer, EDGE_MQTT_TOPIC_ROOT, root_length);
    buffer[root_length] = '/';
    memcpy(buffer + root_length + 1U, suffix, suffix_length);
    buffer[root_length + 1U + suffix_length] = '\0';
    return 0;
}

static err_t publish_payload(const char *topic,
                             const char *payload,
                             uint8_t retain)
{
    err_t result;

    if ((s_client == NULL) || (s_connected == 0U)) {
        return ERR_CONN;
    }

    LOCK_TCPIP_CORE();
    result = mqtt_msg_publish(s_client,
                              topic,
                              payload,
                              (u16_t)strlen(payload),
                              MQTT_QOS_TELEMETRY,
                              retain,
                              NULL,
                              NULL);
    UNLOCK_TCPIP_CORE();

    return result;
}

static void log_publish_error(const char *name, err_t result)
{
    if (result != ERR_OK) {
        board_uart_write("[mqtt] publish ");
        board_uart_write(name);
        board_uart_write(" failed=");
        board_uart_write_i32((int32_t)result);
        board_uart_write("\r\n");
    }
}

static void append_scores(char *buffer,
                          size_t capacity,
                          size_t *length,
                          const int8_t *scores)
{
    uint32_t index;

    append_char(buffer, capacity, length, '[');
    for (index = 0U; index < CIFAR10_CLASS_COUNT; ++index) {
        if (index != 0U) {
            append_char(buffer, capacity, length, ',');
        }
        append_i32(buffer, capacity, length, scores[index]);
    }
    append_char(buffer, capacity, length, ']');
}

static size_t build_inference_json(char *buffer,
                                   size_t capacity,
                                   const cifar10_result_t *result,
                                   uint32_t sequence)
{
    size_t length = 0U;

    buffer[0] = '\0';
    append_text(buffer, capacity, &length, "{\"sequence\":");
    append_u32(buffer, capacity, &length, sequence);
    append_text(buffer, capacity, &length, ",\"latency_ms\":");
    append_u32(buffer, capacity, &length, result->latency_ms);
    append_text(buffer, capacity, &length, ",\"index\":");
    append_u32(buffer, capacity, &length, result->top1_index);
    append_text(buffer, capacity, &length, ",\"label\":\"");
    append_text(buffer, capacity, &length, cifar10_label(result->top1_index));
    append_text(buffer, capacity, &length, "\",\"score_q7\":");
    append_i32(buffer, capacity, &length, result->top1_score_q7);
    append_text(buffer, capacity, &length, ",\"probability_1e4\":");
    append_u32(buffer, capacity, &length, result->top1_probability_1e4);
    append_text(buffer, capacity, &length, ",\"scores_q7\":");
    append_scores(buffer, capacity, &length, result->scores_q7);
    append_char(buffer, capacity, &length, '}');

    return length;
}

static size_t build_temperature_json(char *buffer,
                                     size_t capacity,
                                     const device_temperature_sample_t *sample)
{
    size_t length = 0U;

    buffer[0] = '\0';
    append_text(buffer, capacity, &length, "{\"adc_channel\":9,\"adc_raw\":");
    append_u32(buffer, capacity, &length, sample->adc_raw);
    append_text(buffer, capacity, &length, ",\"milli_celsius\":");
    append_i32(buffer, capacity, &length, sample->milli_celsius);
    append_text(buffer, capacity, &length, ",\"vref_channel\":10,\"vref_raw\":");
    append_u32(buffer, capacity, &length, sample->vref_raw);
    append_text(buffer, capacity, &length, ",\"vref_mv\":");
    append_u32(buffer, capacity, &length, sample->vref_mv);
    append_text(buffer, capacity, &length, ",\"vdda_mv\":");
    append_u32(buffer, capacity, &length, EDGE_TEMPERATURE_VDDA_MV);
    append_char(buffer, capacity, &length, '}');

    return length;
}

static size_t build_status_json(char *buffer,
                                size_t capacity,
                                const device_temperature_sample_t *temperature,
                                int temperature_valid,
                                const cifar10_result_t *inference,
                                uint8_t inference_valid,
                                uint32_t sequence)
{
    size_t length = 0U;

    buffer[0] = '\0';
    append_text(buffer, capacity, &length, "{\"uptime_ms\":");
    append_u32(buffer,
               capacity,
               &length,
               (uint32_t)(xTaskGetTickCount() * portTICK_PERIOD_MS));

    append_text(buffer, capacity, &length, ",\"temperature\":");
    if (temperature_valid != 0) {
        append_text(buffer, capacity, &length, "{\"adc_channel\":9,\"adc_raw\":");
        append_u32(buffer, capacity, &length, temperature->adc_raw);
        append_text(buffer, capacity, &length, ",\"milli_celsius\":");
        append_i32(buffer, capacity, &length, temperature->milli_celsius);
        append_text(buffer, capacity, &length, ",\"vref_channel\":10,\"vref_raw\":");
        append_u32(buffer, capacity, &length, temperature->vref_raw);
        append_text(buffer, capacity, &length, ",\"vref_mv\":");
        append_u32(buffer, capacity, &length, temperature->vref_mv);
        append_text(buffer, capacity, &length, ",\"vdda_mv\":");
        append_u32(buffer, capacity, &length, EDGE_TEMPERATURE_VDDA_MV);
        append_char(buffer, capacity, &length, '}');
    } else {
        append_text(buffer, capacity, &length, "null");
    }

    append_text(buffer, capacity, &length, ",\"inference\":");
    if (inference_valid != 0U) {
        append_text(buffer, capacity, &length, "{\"sequence\":");
        append_u32(buffer, capacity, &length, sequence);
        append_text(buffer, capacity, &length, ",\"latency_ms\":");
        append_u32(buffer, capacity, &length, inference->latency_ms);
        append_text(buffer, capacity, &length, ",\"index\":");
        append_u32(buffer, capacity, &length, inference->top1_index);
        append_text(buffer, capacity, &length, ",\"label\":\"");
        append_text(buffer, capacity, &length, cifar10_label(inference->top1_index));
        append_text(buffer, capacity, &length, "\",\"score_q7\":");
        append_i32(buffer, capacity, &length, inference->top1_score_q7);
        append_text(buffer, capacity, &length, ",\"probability_1e4\":");
        append_u32(buffer, capacity, &length, inference->top1_probability_1e4);
        append_text(buffer, capacity, &length, ",\"scores_q7\":");
        append_scores(buffer, capacity, &length, inference->scores_q7);
        append_char(buffer, capacity, &length, '}');
    } else {
        append_text(buffer, capacity, &length, "null");
    }

    append_text(buffer, capacity, &length, ",\"heap_free\":");
    append_u32(buffer, capacity, &length, (uint32_t)xPortGetFreeHeapSize());
    append_text(buffer, capacity, &length, ",\"heap_min\":");
    append_u32(buffer,
               capacity,
               &length,
               (uint32_t)xPortGetMinimumEverFreeHeapSize());
    append_char(buffer, capacity, &length, '}');

    return length;
}

static void copy_latest_inference(cifar10_result_t *result,
                                  uint8_t *valid,
                                  uint32_t *sequence)
{
    taskENTER_CRITICAL();
    *result = s_latest_inference;
    *valid = s_inference_valid;
    *sequence = s_inference_sequence;
    taskEXIT_CRITICAL();
}

static void mqtt_connection_callback(mqtt_client_t *client,
                                     void *argument,
                                     mqtt_connection_status_t status)
{
    (void)client;
    (void)argument;

    s_connecting = 0U;

    if (status == MQTT_CONNECT_ACCEPTED) {
        s_connected = 1U;
    } else {
        s_connected = 0U;
    }

    if (s_task_handle != NULL) {
        (void)xTaskNotify(s_task_handle,
                          MQTT_NOTIFY_CONNECTED,
                          eSetBits);
    }
}

static err_t start_connection(void)
{
    ip_addr_t broker_address;
    err_t result;

    if (ipaddr_aton(EDGE_MQTT_BROKER_IPV4, &broker_address) == 0) {
        return ERR_ARG;
    }

    if (s_client == NULL) {
        LOCK_TCPIP_CORE();
        s_client = mqtt_client_new();
        UNLOCK_TCPIP_CORE();

        if (s_client == NULL) {
            return ERR_MEM;
        }
    }

    memset(&s_client_info, 0, sizeof(s_client_info));
    s_client_info.client_id = s_client_id;
    s_client_info.client_user =
        (s_username[0] != '\0') ? s_username : NULL;
    s_client_info.client_pass =
        (s_password[0] != '\0') ? s_password : NULL;
    s_client_info.keep_alive = EDGE_MQTT_KEEP_ALIVE_SECONDS;
    s_client_info.will_topic = s_will_topic;
    s_client_info.will_msg = s_will_message;
    s_client_info.will_qos = MQTT_QOS_TELEMETRY;
    s_client_info.will_retain = 1U;
    s_client_info.clean_session_disabled = 0U;

    s_connecting = 1U;

    LOCK_TCPIP_CORE();
    result = mqtt_client_connect(s_client,
                                 &broker_address,
                                 EDGE_MQTT_BROKER_PORT,
                                 NULL,
                                 mqtt_connection_callback,
                                 NULL,
                                 &s_client_info);
    UNLOCK_TCPIP_CORE();

    if (result != ERR_OK) {
        s_connecting = 0U;
    }

    return result;
}

static void publish_availability(void)
{
    char topic[MQTT_TOPIC_BUFFER_SIZE];
    err_t result;

    if (make_topic(topic, sizeof(topic), "availability") != 0) {
        return;
    }

    result = publish_payload(topic, "online", MQTT_RETAIN_STATE);
    log_publish_error("availability", result);
}

static void publish_inference(const cifar10_result_t *inference,
                              uint32_t sequence)
{
    char topic[MQTT_TOPIC_BUFFER_SIZE];
    char payload[MQTT_JSON_BUFFER_SIZE];
    err_t result;

    if (make_topic(topic, sizeof(topic), "inference") != 0) {
        return;
    }

    (void)build_inference_json(payload,
                               sizeof(payload),
                               inference,
                               sequence);
    result = publish_payload(topic, payload, MQTT_RETAIN_EVENT);
    log_publish_error("inference", result);
}

static void publish_periodic_status(void)
{
    char topic[MQTT_TOPIC_BUFFER_SIZE];
    char payload[MQTT_JSON_BUFFER_SIZE];
    device_temperature_sample_t temperature = {0};
    cifar10_result_t inference;
    uint32_t sequence;
    uint8_t inference_valid;
    int temperature_result;
    err_t publish_result;

    temperature_result = device_temperature_read(&temperature);
    if (temperature_result != 0) {
        board_uart_write("[temp] ADC9 read failed=");
        board_uart_write_i32(temperature_result);
        board_uart_write("\r\n");
    }

    copy_latest_inference(&inference, &inference_valid, &sequence);

    if (make_topic(topic, sizeof(topic), "temperature") == 0) {
        if (temperature_result == 0) {
            (void)build_temperature_json(payload,
                                         sizeof(payload),
                                         &temperature);
            publish_result = publish_payload(topic,
                                             payload,
                                             MQTT_RETAIN_STATE);
            log_publish_error("temperature", publish_result);
        }
    }

    if (make_topic(topic, sizeof(topic), "status") == 0) {
        (void)build_status_json(payload,
                                sizeof(payload),
                                &temperature,
                                temperature_result == 0,
                                &inference,
                                inference_valid,
                                sequence);
        publish_result = publish_payload(topic,
                                         payload,
                                         MQTT_RETAIN_STATE);
        log_publish_error("status", publish_result);
    }
}

static void mqtt_status_task(void *argument)
{
    TickType_t next_status_tick;
    TickType_t next_connect_tick = 0U;
    uint32_t last_published_sequence = 0U;
    uint32_t notification_value;

    (void)argument;

    s_task_handle = xTaskGetCurrentTaskHandle();
    next_status_tick = xTaskGetTickCount();

    board_uart_write("[mqtt] broker=");
    board_uart_write(EDGE_MQTT_BROKER_IPV4);
    board_uart_write(":");
    board_uart_write_u32(EDGE_MQTT_BROKER_PORT);
    board_uart_write("\r\n");

    for (;;) {
        TickType_t now = xTaskGetTickCount();

        if ((s_connected == 0U) &&
            (s_connecting == 0U) &&
            ((int32_t)(now - next_connect_tick) >= 0)) {
            err_t result = start_connection();

            if (result == ERR_OK) {
                board_uart_write("[mqtt] connecting\r\n");
            } else {
                board_uart_write("[mqtt] connect start failed=");
                board_uart_write_i32((int32_t)result);
                board_uart_write("\r\n");
            }

            next_connect_tick =
                now + pdMS_TO_TICKS(EDGE_MQTT_RECONNECT_PERIOD_MS);
        }

        notification_value = 0U;
        (void)xTaskNotifyWait(0U,
                              UINT32_MAX,
                              ¬ification_value,
                              pdMS_TO_TICKS(250U));

        if ((notification_value & MQTT_NOTIFY_CONNECTED) != 0U) {
            if (s_connected != 0U) {
                board_uart_write("[mqtt] connected\r\n");
                publish_availability();
                next_status_tick = xTaskGetTickCount();
            } else {
                board_uart_write("[mqtt] disconnected\r\n");
                next_connect_tick =
                    xTaskGetTickCount() +
                    pdMS_TO_TICKS(EDGE_MQTT_RECONNECT_PERIOD_MS);
            }
        }

        if (s_connected != 0U) {
            cifar10_result_t inference;
            uint32_t sequence;
            uint8_t valid;

            /* The notification only shortens latency. Sequence polling also
             * covers an inference recorded before the MQTT task was scheduled
             * or while the broker was disconnected. */
            copy_latest_inference(&inference, &valid, &sequence);
            if ((valid != 0U) && (sequence != last_published_sequence)) {
                publish_inference(&inference, sequence);
                last_published_sequence = sequence;
            }
        }

        now = xTaskGetTickCount();
        if ((s_connected != 0U) &&
            ((int32_t)(now - next_status_tick) >= 0)) {
            publish_periodic_status();
            next_status_tick =
                now + pdMS_TO_TICKS(EDGE_MQTT_STATUS_PERIOD_MS);
        }
    }
}

int mqtt_status_start(void)
{
    BaseType_t result;
    int temperature_result;

    taskENTER_CRITICAL();
    if (s_started != 0U) {
        taskEXIT_CRITICAL();
        return 0;
    }
    s_started = 1U;
    taskEXIT_CRITICAL();

    temperature_result = device_temperature_init();
    if (temperature_result != 0) {
        board_uart_write("[temp] ADC9 init failed=");
        board_uart_write_i32(temperature_result);
        board_uart_write("\r\n");
    } else {
        board_uart_write("[temp] ADC9 temperature + ADC10 VREFINT ready\r\n");
    }

    result = xTaskCreate(mqtt_status_task,
                         "mqtt-status",
                         EDGE_MQTT_TASK_STACK_WORDS,
                         NULL,
                         OS_TASK_PRIORITY(EDGE_MQTT_TASK_PRIORITY_OFFSET),
                         NULL);

    if (result != pdPASS) {
        taskENTER_CRITICAL();
        s_started = 0U;
        taskEXIT_CRITICAL();
        board_uart_write("[mqtt] task create failed\r\n");
        return -1;
    }

    return 0;
}

void mqtt_status_record_inference(const cifar10_result_t *result)
{
    TaskHandle_t task_handle;

    if (result == NULL) {
        return;
    }

    taskENTER_CRITICAL();
    s_latest_inference = *result;
    ++s_inference_sequence;
    s_inference_valid = 1U;
    task_handle = s_task_handle;
    taskEXIT_CRITICAL();

    if (task_handle != NULL) {
        (void)xTaskNotify(task_handle,
                          MQTT_NOTIFY_INFERENCE,
                          eSetBits);
    }
}

3.4 NMSIS-NN 神经网络推理

AI 部署采用官方 NMSIS-NN CIFAR10 示例。

#include "cifar10_inference.h"

#include <stddef.h>
#include <stdint.h>

#include "FreeRTOS.h"
#include "task.h"

#include "riscv_math.h"
#include "riscv_nnfunctions.h"

#include "cifar10_parameter.h"
#include "cifar10_weights.h"

/* Keep weights in Flash/rodata.  The original standalone example omitted
 * const, which would copy roughly 32 KiB of weights into RAM at startup. */
static const q7_t s_conv1_wt[
    CONV1_IM_CH * CONV1_KER_DIM * CONV1_KER_DIM * CONV1_OUT_CH] = CONV1_WT;
static const q7_t s_conv1_bias[CONV1_OUT_CH] = CONV1_BIAS;

static const q7_t s_conv2_wt[
    CONV2_IM_CH * CONV2_KER_DIM * CONV2_KER_DIM * CONV2_OUT_CH] = CONV2_WT;
static const q7_t s_conv2_bias[CONV2_OUT_CH] = CONV2_BIAS;

static const q7_t s_conv3_wt[
    CONV3_IM_CH * CONV3_KER_DIM * CONV3_KER_DIM * CONV3_OUT_CH] = CONV3_WT;
static const q7_t s_conv3_bias[CONV3_OUT_CH] = CONV3_BIAS;

static const q7_t s_ip1_wt[IP1_DIM * IP1_OUT] = IP1_WT;
static const q7_t s_ip1_bias[IP1_OUT] = IP1_BIAS;

/* Exact workspaces used by the upstream example:
 *   activation scratch: 40 KiB
 *   im2col/FC scratch:    3.125 KiB
 * Total:                 43.125 KiB
 *
 * They are static so inference does not fragment or consume the Wi-Fi heap. */
static q7_t s_col_buffer[2U * 5U * 5U * 32U * 2U]
    __attribute__((aligned(8)));
static q7_t s_scratch_buffer[32U * 32U * 10U * 4U]
    __attribute__((aligned(8)));
static q7_t s_output_data[IP1_OUT];

static const char *const s_labels[CIFAR10_CLASS_COUNT] = {
    "Plane", "Car", "Bird", "Cat", "Deer",
    "Dog", "Frog", "Horse", "Ship", "Truck"
};

static int check_status(riscv_status status)
{
    return (status == RISCV_MATH_SUCCESS) ? 0 : -1;
}

int cifar10_infer_rgb888(const uint8_t *rgb,
                         size_t rgb_size,
                         cifar10_result_t *result)
{
    static const int32_t mean_data[3] = INPUT_MEAN_SHIFT;
    static const uint32_t scale_data[3] = INPUT_RIGHT_SHIFT;
    q7_t *img_buffer1;
    q7_t *img_buffer2;
    TickType_t tick_start;
    TickType_t tick_end;
    uint8_t top1_index;
    int8_t top1_score;
    size_t i;

    if ((rgb == NULL) || (result == NULL) ||
        (rgb_size != (size_t)CIFAR10_INPUT_BYTES)) {
        return -1;
    }

    img_buffer1 = s_scratch_buffer;
    img_buffer2 = img_buffer1 + (32U * 32U * 32U);

    tick_start = xTaskGetTickCount();

    /* RGB888 -> signed q7 input using the example model's mean/scale. */
    for (i = 0U; i < (size_t)CIFAR10_INPUT_BYTES; i += 3U) {
        uint32_t channel;

        for (channel = 0U; channel < 3U; ++channel) {
            int32_t value;
            uint32_t shift;

            shift = scale_data[channel];
            value = ((int32_t)rgb[i + channel] - mean_data[channel]) << 7;
            value += (int32_t)(1UL << (shift - 1U));
            value >>= shift;
            img_buffer2[i + channel] = (q7_t)__SSAT(value, 8U);
        }
    }

    if (check_status(riscv_convolve_HWC_q7_RGB(
            img_buffer2,
            CONV1_IM_DIM,
            CONV1_IM_CH,
            s_conv1_wt,
            CONV1_OUT_CH,
            CONV1_KER_DIM,
            CONV1_PADDING,
            CONV1_STRIDE,
            s_conv1_bias,
            CONV1_BIAS_LSHIFT,
            CONV1_OUT_RSHIFT,
            img_buffer1,
            CONV1_OUT_DIM,
            (q15_t *)s_col_buffer,
            NULL)) != 0) {
        return -2;
    }

    riscv_relu_q7(img_buffer1,
                  CONV1_OUT_DIM * CONV1_OUT_DIM * CONV1_OUT_CH);

    riscv_maxpool_q7_HWC(img_buffer1,
                         CONV1_OUT_DIM,
                         CONV1_OUT_CH,
                         POOL1_KER_DIM,
                         POOL1_PADDING,
                         POOL1_STRIDE,
                         POOL1_OUT_DIM,
                         NULL,
                         img_buffer2);

    if (check_status(riscv_convolve_HWC_q7_fast(
            img_buffer2,
            CONV2_IM_DIM,
            CONV2_IM_CH,
            s_conv2_wt,
            CONV2_OUT_CH,
            CONV2_KER_DIM,
            CONV2_PADDING,
            CONV2_STRIDE,
            s_conv2_bias,
            CONV2_BIAS_LSHIFT,
            CONV2_OUT_RSHIFT,
            img_buffer1,
            CONV2_OUT_DIM,
            (q15_t *)s_col_buffer,
            NULL)) != 0) {
        return -3;
    }

    riscv_relu_q7(img_buffer1,
                  CONV2_OUT_DIM * CONV2_OUT_DIM * CONV2_OUT_CH);

    riscv_maxpool_q7_HWC(img_buffer1,
                         CONV2_OUT_DIM,
                         CONV2_OUT_CH,
                         POOL2_KER_DIM,
                         POOL2_PADDING,
                         POOL2_STRIDE,
                         POOL2_OUT_DIM,
                         s_col_buffer,
                         img_buffer2);

    if (check_status(riscv_convolve_HWC_q7_fast(
            img_buffer2,
            CONV3_IM_DIM,
            CONV3_IM_CH,
            s_conv3_wt,
            CONV3_OUT_CH,
            CONV3_KER_DIM,
            CONV3_PADDING,
            CONV3_STRIDE,
            s_conv3_bias,
            CONV3_BIAS_LSHIFT,
            CONV3_OUT_RSHIFT,
            img_buffer1,
            CONV3_OUT_DIM,
            (q15_t *)s_col_buffer,
            NULL)) != 0) {
        return -4;
    }

    riscv_relu_q7(img_buffer1,
                  CONV3_OUT_DIM * CONV3_OUT_DIM * CONV3_OUT_CH);

    riscv_maxpool_q7_HWC(img_buffer1,
                         CONV3_OUT_DIM,
                         CONV3_OUT_CH,
                         POOL3_KER_DIM,
                         POOL3_PADDING,
                         POOL3_STRIDE,
                         POOL3_OUT_DIM,
                         s_col_buffer,
                         img_buffer2);

    if (check_status(riscv_fully_connected_q7_opt(
            img_buffer2,
            s_ip1_wt,
            IP1_DIM,
            IP1_OUT,
            IP1_BIAS_LSHIFT,
            IP1_OUT_RSHIFT,
            s_ip1_bias,
            s_output_data,
            (q15_t *)img_buffer1)) != 0) {
        return -5;
    }

    riscv_softmax_q7(s_output_data, IP1_OUT, s_output_data);

    tick_end = xTaskGetTickCount();

    top1_index = 0U;
    top1_score = s_output_data[0];

    for (i = 0U; i < CIFAR10_CLASS_COUNT; ++i) {
        result->scores_q7[i] = s_output_data[i];

        if (s_output_data[i] > top1_score) {
            top1_score = s_output_data[i];
            top1_index = (uint8_t)i;
        }
    }

    result->top1_index = top1_index;
    result->top1_score_q7 = top1_score;
    result->top1_probability_1e4 =
        (uint16_t)(((uint32_t)(uint8_t)top1_score * 10000U + 63U) / 127U);
    result->latency_ms =
        (uint32_t)((tick_end - tick_start) * portTICK_PERIOD_MS);

    return 0;
}

const char *cifar10_label(uint8_t index)
{
    if (index >= CIFAR10_CLASS_COUNT) {
        return "Unknown";
    }

    return s_labels[index];
}

size_t cifar10_workspace_bytes(void)
{
    return sizeof(s_col_buffer) + sizeof(s_scratch_buffer) +
           sizeof(s_output_data);
}

size_t cifar10_model_bytes(void)
{
    return sizeof(s_conv1_wt) + sizeof(s_conv1_bias) +
           sizeof(s_conv2_wt) + sizeof(s_conv2_bias) +
           sizeof(s_conv3_wt) + sizeof(s_conv3_bias) +
           sizeof(s_ip1_wt) + sizeof(s_ip1_bias);
}

4. 效果

系统初始化

MQTT EMQX Broker

MQTT消息

网页推理

单张图片推理约670ms左右


5. 小结

本项目在 GD32VW553 单芯片平台上实现了从底层驱动、实时操作系统、无线通信到神经网络推理和物联网遥测的完整系统。评测表明,GD32VW553 能够在资源受限的 MCU 环境中同时承载无线网络、Web 服务、MQTT 遥测和小型神经网络推理。


全部评论
暂无评论
0/144