在2024年全國(guó)大學(xué)生嵌入式芯片與系統(tǒng)設(shè)計(jì)競(jìng)賽中,各大高校學(xué)子紛紛展現(xiàn)出卓越的創(chuàng)新能力和扎實(shí)的技術(shù)功底。今天,特別為大家分享獲獎(jiǎng)作品——USB一線(xiàn)通監(jiān)控副屏,它以其獨(dú)特的設(shè)計(jì)和實(shí)用的功能贏(yíng)得廣泛好評(píng)與認(rèn)可。
原文鏈接:https://club.rt-thread.org/ask/article/fd0a9bdab79b7c65.html
環(huán)境搭建
環(huán)境變量配置
為了提高一些編譯的速度,選擇了在Linux系統(tǒng)下進(jìn)行開(kāi)發(fā),在Linux上開(kāi)發(fā)N947需要先安裝 env 工具h(yuǎn)ttps://github.com/RT-Thread/env,按照說(shuō)明文檔進(jìn)行安裝即可,然后配置一些環(huán)境變量:
其中 /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 exportPATH=$PATH:$RTT_EXEC_PATH
如果需要將N947的例程從rt-thread的根文件夾中獨(dú)立出來(lái)的話(huà),需要?jiǎng)h除工程中Kconfig文件的這行代碼:
代碼高亮
這里使用VSCode中的Clang插件,代碼高亮和補(bǔ)全可以通過(guò)使用編譯時(shí)候生成的 compile_commands.json文件來(lái)實(shí)現(xiàn),而RT-Thread的工程是采用的scons工具,所以可以使用scons_compiledb這個(gè)python包來(lái)生成compile_commands.json 實(shí)現(xiàn)代碼高亮,修改過(guò)的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)
最終搭建完成的效果如下所示,代碼高亮十分且方便查看代碼:
LVGL適配
屏幕拓展板
FRDM-MCXN947這個(gè)開(kāi)發(fā)板預(yù)留了一個(gè)FlexIO接口可以適配8080的并口屏,于是做了一個(gè)屏幕拓展板,把手里閑置的屏幕用起來(lái):
實(shí)物如下,觸摸排線(xiàn)座子有點(diǎn)偏下,不過(guò)不影響功能:
屏幕手冊(cè)說(shuō)明分辨率是240*320驅(qū)動(dòng)芯片是ST7789V、觸摸芯片是FT6336G,而官方的SDK中是有ST7796和FT5406的驅(qū)動(dòng)代碼的,后續(xù)還需要稍作修改:
驅(qū)動(dòng)適配
在官方的SDK中有ST7796和FT5406的驅(qū)動(dòng)程序,直接移植過(guò)來(lái)即可,同時(shí)也把 EDMA和SMARTDMA的驅(qū)動(dòng)復(fù)制了過(guò)來(lái),修改一下屏幕的初始化序列即可驅(qū)動(dòng)屏幕:
LVGL 適配
將SDK中的 lvgl_support復(fù)制到工程中,修改屏幕的寬高為240*320:
然后在board中新建一個(gè)lv_conf.h文件,填入關(guān)于LVGL的一些配置,因?yàn)樵S多配置在menuconfig中有所設(shè)置,所以這里的配置項(xiàng)并不多:
#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
?復(fù)制過(guò)來(lái)的lvgl_support中有對(duì)FreeRTOS的支持,這里將FreeRTOS的API修改為RTT的API,例如如下這段代碼:
并且 N947 的驅(qū)動(dòng)程序有EDMA + FlexIO和SMARTDMA + FlexIO兩種驅(qū)動(dòng)方式,具體區(qū)別不太了解,不過(guò)可以運(yùn)行LVGL的Benchmark測(cè)試來(lái)看下結(jié)果,左邊是SMARTDMA運(yùn)行的結(jié)果,右邊是EDMA的結(jié)果,可以看到前者的FPS更高。后續(xù)也就繼續(xù)采用SMARTDMA + FlexIO的驅(qū)動(dòng)方式:
界面設(shè)計(jì)
使用操作簡(jiǎn)便的GUI Guider設(shè)計(jì)一個(gè)界面,生成繪制界面的代碼,然后添加到工程中:
還需要修改工程文件夾中的 rtconfig.py,增加一個(gè) LV_LVGL_H_INCLUDE_SIMPLE 的預(yù)定義,因?yàn)樯傻拇a默認(rèn)包含lvgl.h是#include "lvgl/lvgl.h",
CFLAGS = DEVICE + ' -Wall -D__FPU_PRESENT -DLV_LVGL_H_INCLUDE_SIMPLE'
最終適配完成的LVGL代碼和GUI Guider的代碼目錄如下,LVGL 的UI繪制代碼段如圖右邊所示,具體代碼可見(jiàn)開(kāi)源地址:
USB通訊
適配 CDC
完成了下位機(jī)的界面的初始化繪制,后續(xù)的任務(wù)當(dāng)然就是怎么把數(shù)據(jù)采集并發(fā)送給下位機(jī)來(lái)更新界面的數(shù)據(jù)了,下面先完成USB的通訊,這里使用的是RTT官方推薦的CherryUSB這個(gè)開(kāi)源USB協(xié)議棧:
將如下鏈接中的適配代碼復(fù)制到工程中:
https://github.com/CherryUSB/cherryusb_mcx
因?yàn)閭鬏數(shù)臄?shù)據(jù)比較單一,這里使用串口屏的思路,直接用CDC_ACM的通訊方式,也就是在上位機(jī)顯示為一個(gè)USB轉(zhuǎn)串口設(shè)備,直接使用串口A(yíng)PI完成通訊。
將RTT根目錄中 rt-thread/components/drivers/usb/cherryusb/demo文件夾中的CDC_ACM例程復(fù)制到工程中,并且把根目錄中的這兩行代碼屏蔽:
修改工程中的cherryusb_port.c文件,添加對(duì)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
將剛才復(fù)制到工程中的CDC_ACM 的 demo程序中端點(diǎn)收發(fā)的程序做如下修改,增加對(duì)于輸入信息的回顯:
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(" "); }
驗(yàn)證
然后插上開(kāi)發(fā)板的USB HS那個(gè)USB接口,用串口工具發(fā)個(gè)數(shù)據(jù)包:
可以看到已經(jīng)識(shí)別成了USB串行設(shè)備,PID 和VID也是我自己設(shè)置的0xE6E9和0x1122,后續(xù)上位機(jī)與開(kāi)發(fā)板建立通訊鎖定COM號(hào)就是依靠PID VID來(lái)查詢(xún)實(shí)現(xiàn),使用串口工具給開(kāi)發(fā)板發(fā)送的數(shù)據(jù)也可以正常接收到。
上位機(jī) 時(shí)間原因上位機(jī)做的比較簡(jiǎn)單,實(shí)現(xiàn)了如下幾個(gè)功能:
讀取電腦的CPU、GPU的占用率和溫度信息、獲取當(dāng)前時(shí)間
根據(jù)VID、PID查詢(xún)COM來(lái)與開(kāi)發(fā)板通訊,下發(fā)采集數(shù)據(jù)與時(shí)間
增加幀頭后發(fā)送到下位機(jī),固定長(zhǎng)度32+2個(gè)字節(jié)
下位機(jī)數(shù)據(jù)更新 在開(kāi)發(fā)板端增加一個(gè)thread來(lái)負(fù)責(zé)把USB接收到的數(shù)據(jù)更新到屏幕上面,使用LVGL的API直接修改數(shù)據(jù)即可,代碼如下:
數(shù)據(jù)結(jié)構(gòu)體:
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端點(diǎn)輸出的回調(diào)函數(shù)中增加消息隊(duì)列發(fā)送函數(shù):
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函數(shù)中的接收消息隊(duì)列:
uint8_t read_buffer[128]; while (1) { /* 從消息隊(duì)列中接收消息 */ 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); } }
成品效果
目前支持了對(duì)于時(shí)間、日期、CPU、GPU 的占用率和溫度信息,其他的信息還在完善當(dāng)中。
-
嵌入式
+關(guān)注
關(guān)注
5082文章
19104瀏覽量
304784 -
usb
+關(guān)注
關(guān)注
60文章
7936瀏覽量
264454 -
Linux系統(tǒng)
+關(guān)注
關(guān)注
4文章
593瀏覽量
27392 -
代碼
+關(guān)注
關(guān)注
30文章
4779瀏覽量
68519 -
RT-Thread
+關(guān)注
關(guān)注
31文章
1285瀏覽量
40077
原文標(biāo)題:【24嵌入式設(shè)計(jì)大賽獲獎(jiǎng)文章】USB 一線(xiàn)通監(jiān)控副屏
文章出處:【微信號(hào):NXP_SMART_HARDWARE,微信公眾號(hào):恩智浦MCU加油站】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論