找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

4539

积分

0

好友

587

主题
发表于 昨天 18:27 | 查看: 13| 回复: 0

在嵌入式开发中,一套高效、直观的数据监控系统能极大提升调试与运维效率。本文分享的是基于恩智浦 FRDM-MCXN947 开发板的 USB Monitor 项目实践:通过 RT-Thread 操作系统、LVGL 图形库和 CherryUSB 协议栈,把 PC 端的 CPU、GPU 等实时信息采集后,显示在 240×320 的并口屏幕上。

NXP标识二维码

环境搭建

环境变量配置

为了提高编译速度,这里选择在 Linux 系统下开发。在 Linux 上开发 MCXN947 需要先安装 env 工具:https://github.com/RT-Thread/env ,按照说明文档安装即可,然后配置环境变量如下。

其中 /opt/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi/bin 是编译工具链路径,/home/book/rt-thread 是 RT-Thread 根目录路径。

source ~/.env/env.sh
export RTT_CC=gcc
export RTT_ROOT=/home/book/rt-thread
export RTT_DIR=/home/book/rt-thread
export RTT_EXEC_PATH=/opt/arm-gnu-toolchain-13.2.Rel1-x86_64-arm-none-eabi/bin
export PATH=$PATH:$RTT_EXEC_PATH

如果需要将 N947 的例程从 RT-Thread 根目录中独立出来,需要删除工程中 Kconfig 文件的这行代码:

# RTT_DIR := .../.../.../...

Kconfig配置项定义界面

代码高亮

这里使用 VSCode 中的 Clang 插件。代码高亮和补全可以通过编译时生成的 compile_commands.json 实现。RT-Thread 工程采用 scons 工具,所以可以使用 scons_compiledb 这个 Python 包生成 compile_commands.json,从而实现代码高亮。修改过的 SConstruct 文件如下:

import os
import sys
import rtconfig
import scons_compiledb

if os.getenv('RTT_ROOT'):
    RTT_ROOT = os.getenv('RTT_ROOT')
else:
    RTT_ROOT = os.path.normpath(os.getcwd() + '/../../../../..')

sys.path = sys.path + [os.path.join(RTT_ROOT, 'tools')]

try:
    from building import *
except:
    print('Cannot found RT-Thread root directory, please check RTT_ROOT')
    print(RTT_ROOT)
    exit(-1)

TARGET = 'rtthread.' + rtconfig.TARGET_EXT

if rtconfig.PLATFORM == 'armcc':
    env = Environment(tools = ['mingw'],
        AS = rtconfig.AS, ASFLAGS = rtconfig.AFLAGS,
        CC = rtconfig.CC, CFLAGS = rtconfig.CFLAGS,
        CXX = rtconfig.CXX, CXXFLAGS = rtconfig.CXXFLAGS,
        AR = rtconfig.AR, ARFLAGS = '-rc',
        LINK = rtconfig.LINK, LINKFLAGS = rtconfig.LFLAGS,
        # overwrite cflags, because cflags has '--C99'
        CXXCOM = '$CXX -o $TARGET --cpp -c $CXXFLAGS $_CCCOMCOM $SOURCES')
else:
    env = Environment(tools = ['mingw'],
        AS = rtconfig.AS, ASFLAGS = rtconfig.AFLAGS,
        CC = rtconfig.CC, CFLAGS = rtconfig.CFLAGS,
        CXX = rtconfig.CXX, CXXFLAGS = rtconfig.CXXFLAGS,
        AR = rtconfig.AR, ARFLAGS = '-rc',
        LINK = rtconfig.LINK, LINKFLAGS = rtconfig.LFLAGS,
        CXXCOM = '$CXX -o $TARGET -c $CXXFLAGS $_CCCOMCOM $SOURCES')

env.PrependENVPath('PATH', rtconfig.EXEC_PATH)
scons_compiledb.enable(env)
env.CompileDb()

if rtconfig.PLATFORM in ['iccarm']:
    env.Replace(CCCOM = ['$CC $CFLAGS $CPPFLAGS $_CPPDEFFLAGS $_CPPINCFLAGS -o $TARGET $SOURCES'])
    env.Replace(ARFLAGS = [''])
    env.Replace(LINKCOM = env["LINKCOM"] + ' --map rtthread.map')

Export('RTT_ROOT')
Export('rtconfig')

SDK_ROOT = os.path.abspath('./')

if os.path.exists(SDK_ROOT + '/Libraries'):
    libraries_path_prefix = SDK_ROOT + '/Libraries'
else:
    libraries_path_prefix = os.path.dirname(SDK_ROOT) + '/Libraries'

SDK_LIB = libraries_path_prefix
Export('SDK_LIB')

# prepare building environment
objs = PrepareBuilding(env, RTT_ROOT, has_libcpu=False)

objs.extend(SConscript(os.path.join(libraries_path_prefix, 'drivers', 'SConscript')))

# include cmsis
objs.extend(SConscript(os.path.join(libraries_path_prefix, rtconfig.BSP_LIBRARY_TYPE, 'SConscript')))

# make a building
DoBuilding(TARGET, objs)

最终搭建完成的效果如下,代码高亮后查看代码会方便很多。

main.c中LVGL演示与SmartDMA初始化代码

LVGL 适配

屏幕扩展板

FRDM-MCXN947 预留了一个 FlexIO 接口,可以适配 8080 并口屏。于是做了一块屏幕扩展板,把手里闲置的屏幕利用起来。

屏幕扩展板电路原理图

实物如下,触摸排线座的位置有点偏下,不过不影响功能。

MCXN947屏幕扩展板背面特写

屏幕手册说明分辨率为 240×320,驱动芯片是 ST7789V,触摸芯片是 FT6336G。官方 SDK 中有 ST7796 和 FT5406 的驱动代码,后续还需要稍作修改。

240×320液晶屏规格参数表

驱动适配

官方 SDK 中有 ST7796 和 FT5406 的驱动程序,直接移植过来即可。同时也把 EDMA 和 SMARTDMA 的驱动复制过来,修改一下屏幕初始化序列,就可以驱动屏幕。

ST7796驱动初始化参数数组

LVGL 适配

将 SDK 中的 lvgl_support 复制到工程中,修改屏幕宽高为 240×320。

LVGL支持文件中LCD宽高宏定义

然后在 board 中新建一个 lv_conf.h 文件,填入 LVGL 的相关配置。因为许多配置已经在 menuconfig 中设置,所以这里的配置项并不多。

#ifndef LV_CONF_H
#define LV_CONF_H

#include <rtconfig.h>

#define LV_USE_SYSMON           1
#define LV_USE_PERF_MONITOR     0

#define LV_COLOR_DEPTH          16
#define LV_HOR_RES_MAX          240
#define LV_VER_RES_MAX          320
#define LV_COLOR_16_SWAP        0

#define BSP_USING_LVGL_BENCHMARK_DEMO
#define BSP_USING_LVGL_WIDGETS_DEMO

#ifdef BSP_USING_LVGL_DAVE2D
#define LV_USE_DRAW_DAVE2D      1
#endif

#ifdef BSP_USING_LVGL_WIDGETS_DEMO
#define LV_USE_DEMO_WIDGETS 1
#define LV_DEMO_WIDGETS_SLIDESHOW   0
#endif  /* BSP_USING_LVGL_WIDGETS_DEMO */

/*Benchmark your system*/
#ifdef BSP_USING_LVGL_BENCHMARK_DEMO
#define LV_USE_DEMO_BENCHMARK 1
/*Use RGB565A8 images with 16 bit color depth instead of ARGB8565*/
#define LV_DEMO_BENCHMARK_RGB565A8  1
#define LV_FONT_MONTSERRAT_14       1
#define LV_FONT_MONTSERRAT_24       1
#endif  /* BSP_USING_LVGL_BENCHMARK_DEMO */

/*Stress test for LVGL*/
#ifdef BSP_USING_LVGL_STRESS_DEMO
#define LV_USE_DEMO_STRESS 1
#endif  /* BSP_USING_LVGL_STRESS_DEMO */

/*Render test for LVGL*/
#ifdef BSP_USING_LVGL_RENDER_DEMO
#define LV_USE_DEMO_RENDER 1
#endif  /* BSP_USING_LVGL_RENDER_DEMO */

/*Music player demo*/
#ifdef BSP_USING_LVGL_MUSIC_DEMO
#define LV_USE_DEMO_MUSIC 1
#define LV_DEMO_MUSIC_SQUARE    1
#define LV_DEMO_MUSIC_LANDSCAPE 0
#define LV_DEMO_MUSIC_ROUND     0
#define LV_DEMO_MUSIC_LARGE     0
#define LV_DEMO_MUSIC_AUTO_PLAY 0
#define LV_FONT_MONTSERRAT_12   1
#define LV_FONT_MONTSERRAT_16   1
#endif  /* BSP_USING_LVGL_MUSIC_DEMO */

#endif

复制过来的 lvgl_support 中有对 FreeRTOS 的支持,这里将 FreeRTOS 的 API 修改为 RT-Thread 的 API。例如下面这段代码:

static void DEMO_DbIMemoryDoneCallback(status_t status, void *userData)
{
#if defined(SDK_OS_FREE_RTOS)
    rt_sem_release(s_memWriteDone);
    // BaseType_t taskAwake = pdFALSE;
    // xSemaphoreGiveFromISR(s_memWriteDone, &taskAwake);
    // portYIELD_FROM_ISR(taskAwake);
#else
    s_memWriteDone = true;
#endif
}

回调函数中的信号量释放代码

N947 的驱动程序有 EDMA + FlexIO 和 SMARTDMA + FlexIO 两种驱动方式,具体区别不太了解。不过可以运行 LVGL 的 Benchmark 测试看下结果:左边是 SMARTDMA 的结果,右边是 EDMA 的结果。前者的 FPS 更高,所以后续继续采用 SMARTDMA + FlexIO 的驱动方式。

SMARTDMA与EDMA的LVGL性能对比

界面设计

使用 GUI Guider 设计界面,生成绘制界面的代码,然后添加到工程中。

GUI Guider硬件监控仪表盘设计界面

还需要修改工程文件夹中的 rtconfig.py,增加一个 LV_LVGL_H_INCLUDE_SIMPLE 预定义,因为生成的代码默认包含 lvgl.h 时使用的是 #include "lvgl/lvgl.h"

CFLAGS = DEVICE + ' -Wall -D__FPU_PRESENT -DLV_LVGL_H_INCLUDE_SIMPLE'

最终适配完成的 LVGL 代码和 GUI Guider 代码目录如下,LVGL 的 UI 绘制代码段如图右边所示,具体代码可见开源地址。

LVGL工程目录与main.c头文件引用

USB 通讯

适配 CDC

下位机界面初始化完成后,接下来就是把数据采集并发送给下位机更新界面。先完成 USB 通讯。这里使用的是 RT-Thread 官方推荐的 CherryUSB 开源 USB 协议栈。

CherryUSB支持MCX芯片的仓库说明

将下面链接中的适配代码复制到工程中:

https://github.com/CherryUSB/cherryusb_mcx

因为传输数据比较单一,这里采用串口屏思路,直接用 CDC_ACM 通讯方式:在上位机中显示为一个 USB 转串口设备,直接使用串口 API 完成通讯。

将 RT-Thread 根目录中 rt-thread/components/drivers/usb/cherryusb/demo 文件夹里的 CDC_ACM 例程复制到工程中,并把根目录中的这两行代码屏蔽。

CDC ACM模板代码与VOFA+串口数据可视化

修改工程中的 cherryusb_port.c 文件,添加对 CDC_ACM 的支持:

/*
 * Copyright (c) 2006-2024, RT-Thread Development Team
 *
 * SPDX-License-Identifier: Apache-2.0
 *
 * Change Logs:
 * Date           Author       Notes
 * 2024/04/23     sakumisu    first version
 */

#include <rtthread.h>
#include <board.h>

/* low level init here, this has implemented in cherryusb */
/* low level deinit here, this has implemented in cherryusb */

#ifdef RT_CHERRYUSB_DEVICE_TEMPLATE_CDC_ACM
int cherryusb_devinit(void)
{
    // extern void cherryusb_devinit(uint8_t busid, uintptr_t reg_base);
    extern void cdc_acm_init(uint8_t busid, uintptr_t reg_base);
    cdc_acm_init(0, USBHS1__USBC_BASE);
    return 0;
}
INIT_COMPONENT_EXPORT(cherryusb_devinit);
#endif

#ifdef RT_CHERRYUSB_DEVICE_TEMPLATE_MSC
int cherryusb_devinit(void)
{
    extern void msc_ram_init(uint8_t busid, uintptr_t reg_base);
    msc_ram_init(0, USBHS1__USBC_BASE);
    return 0;
}
INIT_COMPONENT_EXPORT(cherryusb_devinit);
#endif

#ifdef RT_CHERRYUSB_HOST
#include "usbh_core.h"
int cherryusb_hostinit(void)
{
    usbh_initialize(0, USBHS1__USBC_BASE);
    return 0;
}
INIT_COMPONENT_EXPORT(cherryusb_hostinit);
#endif

将刚才复制到工程中的 CDC_ACM demo 程序的端点收发逻辑做如下修改,增加输入信息回显:

void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    USB_LOG_RAW("actual out len:%d\r\n", nbytes);
    /* setup next out ep read transfer */
    usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
    for (int i = 0; i < nbytes; i++) {
        printf("%02x ", read_buffer[i]);
    }
    printf("\r\n");
}

验证

插上开发板的 USB HS 接口,用串口工具发一个数据包。

USB转串口设备识别成功

可以看到,设备已经被识别为 USB 串行设备,PID 和 VID 也是自己设置的 0xE6E90x1122。后续上位机与开发板建立通讯、锁定 COM 号,就是依靠 PID/VID 查询实现。使用串口工具给开发板发送的数据也能正常接收。

上位机

时间原因,上位机做得比较简单,实现了以下几个功能:

  • 读取电脑的 CPU、GPU 占用率和温度信息,获取当前时间  
  • 根据 VID、PID 查询 COM,与开发板通讯,下发采集数据与时间  
  • 增加帧头后发送到下位机,固定长度 32+2 个字节  

Windows上位机串口与系统监控数据输出

下位机数据更新

在开发板端增加一个线程,负责把 USB 接收到的数据更新到屏幕上,使用 LVGL 的 API 直接修改数据即可。

数据结构体:

typedef struct
{
    uint16_t cpu_usage;
    uint16_t mem_usage;
    uint16_t gpu_usage;
    uint16_t cpu_freq;
    uint16_t cpu_temperature;
    uint16_t gpu_temperature;
    uint16_t board_temperature;
} monitor_info_u16_t;

typedef struct {
    uint16_t wYear;
    uint16_t wMonth;
    uint16_t wDayOfWeek;
    uint16_t wDay;
    uint16_t wHour;
    uint16_t wMinute;
    uint16_t wSecond;
    uint16_t wMilliseconds;
} mytime_t;

在 USB 端点输出的回调函数中,增加消息队列发送函数:

void usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    USB_LOG_RAW("actual out len:%d\r\n", nbytes);
    /* setup next out ep read transfer */
    usbd_ep_start_read(busid, CDC_OUT_EP, read_buffer, 2048);
    for (int i = 0; i < nbytes; i++) {
        printf("%02x ", read_buffer[i]);
    }
    printf("\r\n");
    if (34 == nbytes)
    {
        rt_mq_send(&usb_mq, read_buffer, 34);
    }
}

main 函数中接收消息队列:

uint8_t read_buffer[128];
while (1)
{
    /* 从消息队列中接收消息 */
    if (rt_mq_recv(&usb_mq, read_buffer, 34, RT_WAITING_FOREVER) > 0)
    {
        mytime_t* p_time_u16 = (mytime_t*)(read_buffer + 2);
        monitor_info_u16_t* p_info_u16 = (monitor_info_u16_t *)(read_buffer + 2 + sizeof(mytime_t));

        rt_kprintf("wYear         %u\r\n", p_time_u16->wYear);
        rt_kprintf("wMonth        %u\r\n", p_time_u16->wMonth);
        rt_kprintf("wDayOfWeek    %u\r\n", p_time_u16->wDayOfWeek);
        rt_kprintf("wDay          %u\r\n", p_time_u16->wDay);
        rt_kprintf("wHour         %u\r\n", p_time_u16->wHour);
        rt_kprintf("wMinute       %u\r\n", p_time_u16->wMinute);
        rt_kprintf("wSecond       %u\r\n", p_time_u16->wSecond);
        rt_kprintf("wMilliseconds %u\r\n", p_time_u16->wMilliseconds);

        rt_kprintf("cpu_usage         %u\r\n", p_info_u16->cpu_usage);
        rt_kprintf("mem_usage         %u\r\n", p_info_u16->mem_usage);
        rt_kprintf("gpu_usage         %u\r\n", p_info_u16->gpu_usage);
        rt_kprintf("cpu_freq          %u\r\n", p_info_u16->cpu_freq);
        rt_kprintf("cpu_temperature   %u\r\n", p_info_u16->cpu_temperature);
        rt_kprintf("gpu_temperature   %u\r\n", p_info_u16->gpu_temperature);
        rt_kprintf("board_temperature %u\r\n", p_info_u16->board_temperature);

        lv_label_set_text_fmt(guider_ui.screen_label_cpu_temp, "%2d", p_info_u16->cpu_temperature);
        lv_label_set_text_fmt(guider_ui.screen_label_gpu_temp, "%2d", p_info_u16->gpu_temperature);
        lv_label_set_text_fmt(guider_ui.screen_label_cpu_load, "%2d", p_info_u16->cpu_usage);
        lv_label_set_text_fmt(guider_ui.screen_label_gpu_load, "%2d", p_info_u16->gpu_usage);
        lv_arc_set_value(guider_ui.screen_arc_gpu_load, p_info_u16->gpu_usage);
        lv_arc_set_value(guider_ui.screen_arc_gpu_temp, p_info_u16->gpu_temperature);
        lv_label_set_text_fmt(guider_ui.screen_time, "%02d:%02d", p_time_u16->wHour, p_time_u16->wMinute);
        lv_label_set_text_fmt(guider_ui.screen_date, "%02d.%02d.%02d", p_time_u16->wYear, p_time_u16->wMonth, p_time_u16->wDay);
    }
}

成品效果

目前支持时间、日期、CPU/GPU 占用率和温度信息展示,其他信息还在完善中。

FRDM-MCXN947开发板与USB监控屏实物效果

下位机代码可见 GitHub:

https://github.com/POMIN-163/FRDM-MCXN947-USB-Monitor

上位机代码:上位机代码.zip

如果你也对这类 RT-Thread 与 NXP 平台结合的开源实践感兴趣,可以从开源实战里继续挖掘更多源码分析、项目热度和贡献指南;遇到 USB 协议栈、LVGL 适配或 BSP 修改等具体问题,也建议对照技术文档中的架构设计与避坑指南查漏补缺。




上一篇:RAG解析Excel只靠Pandas?合并单元格与多级表头的生产级方案
下一篇:ESP32-C3坦克控制板设计:ESP-NOW低延迟驱动13A履带电机与喷烟特效
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2026-9-10 16:01 , Processed in 0.935123 second(s), 40 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2026 云栈社区.

快速回复 返回顶部 返回列表