USB一线通监控副屏设计方案

描述

在2024年全国大学生嵌入式芯片与系统设计竞赛中,各大高校学子纷纷展现出卓越的创新能力和扎实的技术功底。今天,特别为大家分享获奖作品——USB一线通监控副屏,它以其独特的设计和实用的功能赢得广泛好评与认可。

原文链接:https://club.rt-thread.org/ask/article/fd0a9bdab79b7c65.html

环境搭建

环境变量配置

为了提高一些编译的速度,选择了在Linux系统下进行开发,在Linux上开发N947需要先安装 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文件的这行代码:

usb

代码高亮

这里使用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)

 

最终搭建完成的效果如下所示,代码高亮十分且方便查看代码:

usb

LVGL适配

屏幕拓展板

FRDM-MCXN947这个开发板预留了一个FlexIO接口可以适配8080的并口屏,于是做了一个屏幕拓展板,把手里闲置的屏幕用起来:

usb

实物如下,触摸排线座子有点偏下,不过不影响功能:

usb

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

驱动适配

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

usb

LVGL 适配

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

usb

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

 

#ifndef LV_CONF_H
#define LV_CONF_H
#include 
#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修改为RTT的API,例如如下这段代码:

usb

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

usb

界面设计

使用操作简便的GUI Guider设计一个界面,生成绘制界面的代码,然后添加到工程中:

usb

还需要修改工程文件夹中的 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绘制代码段如图右边所示,具体代码可见开源地址:

usb

USB通讯

适配 CDC

完成了下位机的界面的初始化绘制,后续的任务当然就是怎么把数据采集并发送给下位机来更新界面的数据了,下面先完成USB的通讯,这里使用的是RTT官方推荐的CherryUSB这个开源USB协议栈:

usb

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

https://github.com/CherryUSB/cherryusb_mcx

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

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

usb

修改工程中的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 
#include 
/* 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程序中端点收发的程序做如下修改,增加对于输入信息的回显:

 

oid usbd_cdc_acm_bulk_out(uint8_t busid, uint8_t ep, uint32_t nbytes)
{
    USB_LOG_RAW("actual out len:%d
", 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("
");
}

 

验证

然后插上开发板的USB HS那个USB接口,用串口工具发个数据包:

usb

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

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

读取电脑的CPU、GPU的占用率和温度信息、获取当前时间

根据VID、PID查询COM来与开发板通讯,下发采集数据与时间

增加帧头后发送到下位机,固定长度32+2个字节

usb

下位机数据更新 在开发板端增加一个thread来负责把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
", 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("
");
    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
", p_time_u16->wYear);
        rt_kprintf("wMonth        %u
", p_time_u16->wMonth);
        rt_kprintf("wDayOfWeek    %u
", p_time_u16->wDayOfWeek);
        rt_kprintf("wDay          %u
", p_time_u16->wDay);
        rt_kprintf("wHour         %u
", p_time_u16->wHour);
        rt_kprintf("wMinute       %u
", p_time_u16->wMinute);
        rt_kprintf("wSecond       %u
", p_time_u16->wSecond);
        rt_kprintf("wMilliseconds %u
", p_time_u16->wMilliseconds);
        rt_kprintf("cpu_usage         %u
", p_info_u16->cpu_usage);
        rt_kprintf("mem_usage         %u
", p_info_u16->mem_usage);
        rt_kprintf("gpu_usage         %u
", p_info_u16->gpu_usage);
        rt_kprintf("cpu_freq          %u
", p_info_u16->cpu_freq);
        rt_kprintf("cpu_temperature   %u
", p_info_u16->cpu_temperature);
        rt_kprintf("gpu_temperature   %u
", p_info_u16->gpu_temperature);
        rt_kprintf("board_temperature %u
", 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 的占用率和温度信息,其他的信息还在完善当中。

usb

 

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分