RTL8772GWP仪表盘

本文档旨在指导用户快速配置开发环境,包括编译SDK,烧录固件,升级固件及抓取Log。用户可以下载SDK中的测试文件,确保EVB可以正常运作,并与开发环境兼容且具备相应功能。

RealTek RTL8772GWP仪表盘SDK提供常用的基本功能,包括行驶速度监控、GUI框架、消息分发等。此外,SDK还提供了易于扩展的API接口,方便仪表盘厂商进行二次开发。

备注

  • 本文所描述的仪表盘样例,采用RealTek RTL8772GWP芯片,搭配EK9716 RGB(800 * 480)显示屏,开发者若选用本文默认的硬件环境,可直接运行样例工程。

  • 如果需要适配其他型号的显示屏,请参考 Port适配LCD,重新适配LCD屏幕驱动。

  • 此外,RealTek RTL8772GWP芯片适配显示屏接口:支持i8080、qspi/spi、RGB888/RGB565,不支持MIPI接口。

概述

根据EVB工作缩略图,用户可以通过Keil/GCC来编译SDK中的工程,然后通过MPTool将文件烧录到EVB中,烧录完成后可以通过DebugAnalyzer来抓取SoC端的日志。

../../../../_images/enviroment_setup.png

EVB工作缩略图

实用案例

下图显示了dashboard应用包含的模块概览,由一块母板、一块子板和一块LCD屏幕组成。

../../../../_images/hal_env.png

仪表盘应用示例

支持功能

Realtek dashboard应用程序提供完整功能支持。

  • 支持BLE/2.4G/USB三种通信模式

  • 支持BLE/2.4G/USB三种模式的最高上报率为125Hz/4KHz/8KHz

  • 支持USB 3.0/2.0

  • 支持电量检测

  • 支持CAN通信

  • 支持findmy功能

  • 支持OTA软件升级

  • 支持来电提醒、消息弹窗功能

RTL8762x系列支持BLE 5.1规范,供电范围广,从1.8V到3.3V,以便开发人员能更灵活地选择外围设备来适应他们的应用。

环境需求

  1. Dashboard demo评估板(RTL8772GWP EVB )×1,带屏(EK9716)

  2. MicroUSB2.0数据线 x 2

  3. PC(需要USB 2.0或更高连接)

  4. 解压BEE4-Bcut-SDK-DASHBOARD-vx.x.x文件

  5. 在PC上安装FT232设备驱动程序

  6. CAN BUS

  1. ARM Keil MDK: https://developer.arm.com

  2. BeeMPTool_kits: RealMCU

  3. DebugAnalyzer: RealMCU

  4. Image Convert Tool: https://docs.realmcu.com/HoneyGUI/cn/latest/tool/index.html

备注

不同的平台支持不同的面板接口,开发者必须依赖IC平台的功能来选择合适的面板。LCD Hardware 平台 显示了每个平台的支持接口。

LCD Hardware 平台

Hardware Platforms

Display interface

RTL8762DW HDK

I8080, QSPI/SPI

RTL8772GWP HDK

I8080, QSPI/SPI, RGB888/RGB565

RTL8772F HDK

I8080, QSPI/SPI, RGB888/RGB565, MIPI

硬件连线

EVB评估板提供了用户开发和应用调试的硬件环境。EVB由主板和子板组成。它有下载模式和工作模式,用户需要使用不同的接线方法来设置EVB进入所需的模式。

RTL8772GWP EVB接口

根据EVB供电接线 ,用户CON1和CON2的USB接口均需要与电脑连接给EVB供电。

../../../../_images/board_power.png

EVB供电接线

下载模式

../../../../_images/log.png

UART-USB接线

将P3_1和TX短接,P3_0和RX短接,LOW和LOG短接,接着按下 reset 键,进入下载烧录模式。

备注

芯片在上电后会读取Log Pin的电平信号,如果电平为低,则Bypass Flash,进入烧录模式,否则运行应用层程序。

工作模式

EVB正常工作需要将LOW和LOG短接断开。

Log接线

RX和LOG用跳线帽短接。

软件配置及环境

需要配置的软件环境主要有:

  • Keil(用于生成应用程序映像)

  • Log工具(用于分析代码执行情况)

  • MP工具(用于生成系统配置和OTA Header镜像文件)

  • Dashboard APK(用于测试的第三方工具)

  • 图像转换工具(用于将图像资源转换为符合RTK GUI识别的bin文件)

具体使用请参考 软件工具

Image Convert Tool

../../../../_images/image_tool.png

Image Convert Tool

  1. 导入待转换目录。

  2. 设置转换格式:选择是否压缩、输出图片color space格式、输出图片属性(png、bmp、jpg)、输出图片保存位置等参数。

  3. 转换图片。

Dashboard apk

演示板的蓝牙设备名称是 BLE_DASHBOARD

  1. 点击 Connect Device

  2. 选择 BLE_DASHBOARD

../../../../_images/Dashboard_APK.png

Dashboard APK设备连接

备注

测试消息弹窗功能,在确保蓝牙连接的情况下,若出现消息弹出未响应,查看log未出现消息相关log,可重新设置主界面“打开通知权限”里面重新关闭-开启“仪表盘”。

GUI模拟器

开启模拟器步骤:

  1. 配置模拟器环境,请参考: https://docs.realmcu.com/Honeygui/cn/latest/get-started/index.html#id3

  2. 进入模拟器 SDK\subsys\HoneyGUI.code-workspace,进入软件界面。

  3. 打开 SDK\subsys\gui\win32_sim\menu_config.h 中宏 CONFIG_REALTEK_BUILD_GUI_800_480,取消注释,同时关闭默认demo宏 CONFIG_REALTEK_BUILD_GUI_454_454_DEMO

  4. 根据下图进行GUI 模拟器调试运行。

../../../../_images/GUI_monitor.png

GUI 模拟器调试运行

  1. 查看GUI 模拟器运行结果

../../../../_images/gui_dashboard.png

GUI 模拟器运行结果

LCD

TL8772GWP芯片支持RGB模式LCD。用户不需要从头开始编写屏幕驱动程序,只需要几个简单的步骤就可以驱动LCD屏幕。RTL8772GWP的RGB信号是固定引脚,不能任意映射,只有一套,如下所示。在演示项目中,我们使用RGB888接口中的EK9716显示驱动程序,尺寸为 800 x 480 的面板作为目标显示,输入格式将使用RGB565,输出格式为RGB888。

#define LCDC_DATA23         P5_0
#define LCDC_DATA22         P5_1
#define LCDC_DATA21         P5_2
#define LCDC_DATA20         P5_3
#define LCDC_DATA19         P5_4
#define LCDC_DATA18         P5_5
#define LCDC_DATA17         P3_5
#define LCDC_DATA16         P3_4
#define LCDC_DATA15         P3_3
#define LCDC_DATA14         P3_2
#define LCDC_DATA13         P9_2
#define LCDC_DATA11         P4_6
#define LCDC_DATA10         P4_5
#define LCDC_DATA9          P4_4
#define LCDC_DATA8          P1_2
#define LCDC_DATA12         P4_7
#define LCDC_DATA6          P4_2
#define LCDC_DATA5          P4_1
#define LCDC_DATA7          P4_3
#define LCDC_DATA4          P4_0
#define LCDC_DATA3          P0_7
#define LCDC_DATA2          P0_6
#define LCDC_DATA1          P0_5
#define LCDC_DATA0          P0_4

#define LCDC_RGB_WRCLK      P0_2
#define LCDC_HSYNC          P1_5
#define LCDC_VSYNC          P0_1
#define LCDC_CSN_DE         P0_0

#define LCDC_RESET          P2_2

配置选项

SDK中dashboard功能的默认配置如下:

  1. CONFIG_REALTEK_BUILD_GUI(开)
    用于HoneyGUI开启GUI demo。
  2. CONFIG_REALTEK_BUILD_GUI_800_480_DEMO(开)
    使能构建上层事例demo相关文件。
  3. CONFIG_REALTEK_BUILD_LVGL_GUI(关)
    默认情况下执行RTK GUI demo,若需要切换至LVGL demo,需要将 menu_config.hCONFIG_REALTEK_BUILD_LVGL_GUI 打开(置“1”),同时,在keil预编译中添加 ENABLE_LVGL_GUI 宏定义。
  4. CONFIG_REALTEK_BUILD_SCRIPT_AS_A_APP(关)
    若需要使用RVD tool进行UI布局,需要将 CONFIG_REALTEK_BUILD_SCRIPT_AS_A_APP 宏定义打开(取消注释)。
  5. CONFIG_REALTEK_BUILD_PPE(开)
    使能硬件加速Pixel Process Engine(PPE1.0),图像特效处理(缩放、透明度、pixel convert处理等)。
  6. CONFIG_REALTEK_CAN(关)
    构建CAN相关工程文件。

scons 构建工程

  1. menu_config.h文件开启相关宏,例如构建CAN工程需要开启 CONFIG_REALTEK_CAN,置“1”

  2. proj目录下运行cmd命令:scons --target=MDK5,运行成功标志,结尾出现:scons: done building targets.

../../../../_images/SCONS_success.png

SCONS Success

编译和下载

请参阅 编译和下载,值得注意的是:

  1. 生成Flash Map 步骤中,开发人员需要根据 SDK\applications\dashboard\proj\flash_map.h 生成 flash_map.ini

  2. 生成System Config File 步骤中,dashboard需要至少2条slave links,需要支持LE AE ADV功能。其他功能可根据需要进行配置。

    ../../../../_images/config_file_stack.png

    Dashboard config file

    备注

    该章节介绍sample的编译方法(如keil、gcc等)以及如何download到硬件板上(Jlink、mptool等)。

  3. 编译APP Image 步骤中,dashboard SDK keil工程的路径为: SDK\applications\dashboard\proj\mdk 。APP image由该工程编译得到。

SWD接线

RTL87x2G(BEE4)-SWD对应接口

RTL8772GWP

SWD

GND(黑)

GND

P1_0(绿)

SWIO

P1_1(黄)

SWCK

VDEX(红)

Vterf/3.3V

../../../../_images/SWD_wiring.png

SWD接线

KEIL界面设置

请参阅 Keil MDK 进行keil界面设置。

测试验证

将示例程序烧写到您的EVB板后,用户可以通过使用应用程序的开发套件 仪表盘 ,测试导航信息传输、消息弹窗等是否正常。

EVB调试

为确保EVB可以正常使用,用户可以按照以下步骤进行操作:

  1. 准备一块EVB,确认EVB接口接线正确(UART-USB接线),让EVB进入下载模式,给EVB上电。

  2. 双击运行 MPTool.exe,具体操作步骤和文件路径参考 启动EVB

    1. Load Layout 处,点击 .. 加载 flash_map.ini 文件。

    2. 点击 Browse 加载所有 烧录文件

    3. 点击 User Data 添加用户资源root.bin。

    4. 点击 Detcet 成功识别EVB的COM口。

    5. 点击 Open 成功打开COM口。

    6. 点击 Download 进行烧录,进度条显示当前烧录进度。

    7. 进度条显示OK表示文件烧录成功。

../../../../_images/MPTool_download.png

MPTool烧录界面

  1. 程序烧录完成后,参考工作模式,使EVB进入工作模式。

    1. 观察EVB行为(手机搜索BLE广播),或者查看SoC端Log是否符合预期。

    2. EVB的硬件外围接线请参考硬件连线,程序烧录请参考编译和下载,完成上述步骤后,将EVB反面的LOG和RX短接断开,按下EVB板上的Reset按键或给EVB板重新上电。

用户可以通过以下两种方式来检测SoC端程序是否有正常运行:

方法一:

EVB会直接发出BLE广播,可以使用手机查看。手机设备通过安装Dashboard_V1.2.8.38.apk,搜索并连接BLE信号,其中开机屏幕显示状态 GUI 模拟器运行结果 (其中一帧画面)。

备注

广播名称:BLE_DASHBOARD,蓝牙地址:[25:63:87:4C:E0:01]

../../../../_images/dashboard_display.png

GUI屏幕显示

方法二:

打开Dashboard APK,点击 Connect Device ,进入BLE设备搜索界面,Dashboard APP搜索界面选择 BLE_DASHBOARD 进行连接,主界面蓝牙图标亮起,同时切换进等待导航模式,在APK中输入起点、终点位置,界面切入模拟导航状态。

../../../../_images/dashboard_apk1.jpg

Dashboard APP主界面

../../../../_images/dashboard_apk2.jpg

Dashboard APP搜索界面

日志抓取和分析

Dashboard导航界面EVB板接线请参考Log接线 。EVB板上电后,一旦出现 finish gui_mian 的Log,则代表设备界面初始化完成,屏幕正常刷图。点击手机dashboard apk界面的 Connect Device ,Log打印 GAP adv stoped:because connection created,则代表手机与设备连接成功。

用户有修改flash map的需求,可以查阅Flash指南

软件设计介绍

本章主要介绍RTL8772GWP dashboard解决方案的软件相关技术参数和行为规范,为dashboard的所有功能提供软件概述,用于指导dashboard的开发和追踪软件测试中遇到的问题。

源代码目录

  • Project directory: sdk\application\dashboard\proj

  • Source code directory: sdk\application\dashboard\src

dashboard SDK中的源文件目前分为以下几类:

└── Project: dashboard
    └── app                             includes app main
        ├── menu_config.h
        ├── main.c
        ├── dashboard_init.c
        ├── flash_map.h
        ├── board.h
        └── mem_config.h
    └── Device                          includes startup code
        ├── startup_rtl.c
        └── system_rtl.c
    ├── Lib                             includes all binary symbol files that user application is built on
    ├── Peripheral                      includes all peripheral drivers and module code used by the application
    └── Profile                         includes BLE profiles or services used by the find my application
        ├── ancs_client.c
        ├── gatt_client.c
        ├── bas.c
        ├── dis.c
        ├── dfu_service.c
        ├── ota_service.c
        └── tps.c                       tx power service
    └── dfu                             includes the dashboard dfu application implementation
        ├── dfu_common.c
        └── ble_dfu_transport.c
    └── dfu_task                        includes the  dashboard dfu task application implementation
        ├── dfu_main.c
        ├── dfu_task.c
        └── dfu_app.c
    ├── rtk_gui                         includes all gui port
    ├── realgui/3rd                     includes Third-party library
    ├── realgui/app                     includes Realtek gui app
    ├── realgui/dc                      includes Realtek gui app
    ├── realgui/engine                  includes all gui engine
    ├── realgui/demo                    includes all gui demo
    ├── realgui/input                   includes all gui input
    ├── realgui/misc                    includes all gui misc
    ├── realgui/widget                  includes all gui widget
    ├── database                        includes all database
    └── fs                              includes the  file system
        └──  romfs.c
    └── hal_drivers                     includes the  dashboard hardware layer driver
        ├── drv_flash.c
        ├── drv_dlps.c
        ├── drv_lcd.c
        └── drv_gpio.c
    └── lcd_low_driver                  includes the  dashboard hardware layer lcd driver
        └── EK9716_800480_rgb.c
    └── Compiler                        includes the  dashboard compiler
        ├── syscall.c
        └── syscall_mem.c
    └── bt_app                          includes the  dashboard bluetooth app
        ├── app_le_link_util.c
        ├── app_ble_bas.c
        ├── ancs.c
        ├── app_gap.c
        ├── app_task.c
        ├── his_ms.c
        ├── writband_private_service.c
        ├── app_ble_ota.c
        ├── app_ble_bwps.c
        └── app_ble_dfu.c
    ├── communication                   includes all communication drivers and module code used by the application
    └── findmy

Flash布局

应用程序默认的flash布局头文件: sdk\application\dashboard\proj\flash_map.h

Flash布局

Example layout with a total flash size of 1MB

Size(byte)

Start Address

Reserved

4K

0x04000000

OEM Header

4K

0x04001000

Bank0 Boot Patch

32K

0x04002000

Bank1 Boot Patch

32K

0x0400A000

OTA Bank0

1332K

0x04012000

  • OTA Header

4K

0x04012000

  • System Patch code

32K

0x04013000

  • BT Lowerstack Patch code

60K

0x0401B000

  • BT Host code

212K

0x0402A000

  • APP code

1008K

0x0405F000

  • APP Config File

0K

0x0415B000

  • APP data1

0K

0x0415B000

  • APP data2

16K

0x0415B000

  • APP data3

0K

0x0415F000

  • APP data4

0K

0x0415F000

  • APP data5

0K

0x0415F000

  • APP data6

0K

0x0415F000

OTA Bank1

0K

0x040AC000

Bank0 Secure APP code

16K

0x0415F000

Bank0 Secure APP Data

0K

0x04163000

Bank1 Secure APP code

0K

0x04163000

Bank1 Secure APP Data

0K

0x04163000

OTA Temp

1008K

0x04163000

FTL

16K

0x0425F000

APP Defined Section1

8K

0x04263000

APP Defined Section2

0K

0x04265000

重要

软件架构

系统软件架构如下图所示:

../../../../_images/template_sw_architecture.png

Software architecture

  • Platform: 包括OTA、Flash、FTL 等。

  • IO Drivers: 提供对RTL8772GWP外设接口的应用层访问。

  • OSIF: 实时操作系统的抽象层。

  • GAP: 用户应用程序与BLE协议栈通信的抽象层。

../../../../_images/GUI_SW__architecture.png

仪表盘应用系统框图

/* Non-Secure main. */
int main(void)
{
    DBG_DIRECT("Non-Secure World: main");
    dashboard_system_clock_init();
    dashboard_components_init();
    dashboard_task_init();

    /* Start scheduler. */
    os_sched_start();
}
/*-----------------------------------------------------------*/

任务和优先级

RTL8772GWP默认使用RTOS,并在此基础上封装了一层OS if接口。 RTL8772GWP 内置timer task、idle task、flash task、BT controller stack task、BT host stack task,仪表盘应用程序有自己的app task、GUI task、communication task和CAN task。

../../../../_images/dashboard_task.png

仪表盘应用程序任务组成

仪表盘各任务级描述

Task

描述

优先级

Timer

实现FreeRTOS所需的软件定时器

6

BT controller stack

实现 HCI 以下的BLE协议栈

6

BT Host stack

实现 HCI 以上的BLE协议栈

2

Flash

处理异步flash请求

1

Idle

运行后台任务,包括 DLPS

0

App task

通过消息队列和事件处理函数来处理不同的消息和事件,实现蓝牙功能的管理和控制

6

GUI task

处理图像的生成,屏幕数据的传输

1

Communication task

处理蓝牙交互通信

1

CAN task

处理CAN通信

1

备注

  • 可以创建多个应用任务,并相应地分配内存资源。

  • FreeRTOS提供Idle任务和Timer任务。

  • 已使用 SysTick 中断将任务配置为根据其优先级进行抢占。

  • 中断服务例程 (ISR) 已由供应商实施。

App task初始化相关代码入口:

void dashboard_task_init(void)
{
#if (RTK_BT_TASK == 1) || (RTK_BLE_TASK == 1)
    bt_task_init();
#endif
#ifdef ENABLE_RTK_GUI
#ifdef ENABLE_RTK_GUI_SCRIPT_AS_A_APP
    gui_server_init();
#else
    gui_main();
#endif
#endif
}
void dashboard_system_clock_init(void)
{
    uint32_t actual_mhz;
    int32_t ret0, ret1, ret2, ret3;
    ret0 = pm_cpu_freq_set(125, &actual_mhz);

    ret1 = flash_nor_set_seq_trans_enable(FLASH_NOR_IDX_SPIC0, 1);
    ret2 = fmc_flash_nor_clock_switch(FLASH_NOR_IDX_SPIC0, 160, &actual_mhz);
    ret3 = flash_nor_try_high_speed_mode(0, FLASH_NOR_DTR_4_BIT_MODE);

    APP_PRINT_INFO5("ret0 %d , ret1 %d , ret2 %d , ret3 %d actual_mhz %d", ret0, ret1, ret2, ret3,
                    actual_mhz);
    pm_spic1_freq_set(160, &actual_mhz);
    drv_dlps_exit_cbacks_register("psram", psram_winbond_opi_init);
    pm_display_freq_set(CLK_PLL1_SRC, 100, 100);
    DBG_DIRECT("finish dashboard_system_clock_init");
}
void dashboard_components_init(void)
{
#if (RTK_HAL_GPIO == 1)
    hw_pin_init();
#endif

#if (RTK_HAL_UART == 1)
    hw_uart_init();
#endif

#if (RTK_HAL_I2C == 1)
    hw_i2c_init();
#endif

#if (RTK_HAL_ADC == 1)
    hw_adc_init();
#endif

#if (RTK_HAL_LCD == 1)
    hw_lcd_init();
#endif

#if (RTK_HAL_TOUCH == 1)
    drv_touch_init();
#endif

#ifdef MODULE_VG_LITE
    hw_gpu_init();
#endif

#if (RTK_HAL_RTC == 1)
    hw_rtc_init();
#endif
#if (WS_USING_LETTER_SHELL == 1)
    userShellInit();
#endif
#ifdef RTK_PRIVATE_SERVICE
    communicate_task_init();
#endif // DEBUG

#if (RTK_HAL_DLPS == 1)
    pwr_mgr_init();
#endif
}

APP 任务

../../../../_images/app_task.jpg

app 任务架构

app任务实现的功能包括:

  1. BLE相关状态由gap_handle_msg处理上层发送过来的消息,BLE数据发送和接收回调到app创建服务时注册的读/写接口。

  2. 用户应用层自由实现对BLE行为的控制,包括广播开关、建立连接、更新连接参数等。

  3. 新增app task:app_dashboard_launcher 结构显示了两个基本元素:“thread_entry” 和 “ui_design”。

    1. “ui_design”函数创建UI界面和所需的系统资源。

    2. 开发者可以将周期性执行的程序放在“thread_entry”或消息入口中来接来自其他任务的消息。

    gui_app_t app_dashboard_launcher =
    {
        .screen =
        {
            .name = "app_dashboard_launcher",
            .x    = 0,
            .y    = 0,
        },
        .thread_entry = app_dashboard_launcher_update_thread,
        .ui_design = app_dashboard_launcher_ui_design,
    };
    
    gui_app_t *get_app_dashboard_launcher(void)
    {
        return &app_dashboard_launcher;
    }
    

GUI 任务

GUI 刷新由按键、计时器和其他应用程序行为触发。 在界面刷新之前,用户根据当前行为更新界面中各个控件的信息。 GUI核心部分根据每个控件的类型和位置在帧缓冲区中绘制。帧缓冲区可以是内部 RAM 或 PSRAM为了节省RAM资源,将一整帧FrameBuffer分割成多个部分(当前SDK中将10行作为一块,共分48块绘制),分段绘制并通过LCDC同步发送到屏幕。使用PSRAM时,首先将各部分更新为整帧PSRAM,然后将整个帧通过DPI直接发送到屏幕。

../../../../_images/gui_task_architecture.png

GUI task 框架

演示应用程序中的屏幕分辨率为 800*480,我们将创建两个 Window 小部件作为基础容器,以显示 BLE 连接和非连接状态下的信息。函数 app_dashboard_create_main_display()app_dashboard_create_conn-ected_display() 将创建剩余的小部件并将它们分别附加到这两个窗口小部件。

仪表盘API描述

Dashboard API接口

描述

app_dashboard_launcher_ui_design()

UI界面主入口

app_dashboard_create_main_display()

显示仪表盘主页信息

app_dashboard_data_get_show_main_display()

获取仪表盘主页信息

app_dashboard_create_connected_display()

显示导航页面信息

app_dashboard_initialize_data()

初始化控件内容

app_update_gui_widget()

更新控件内容

app_dashboard_get_data()

获取仪表盘信息

app_dashboard_data_set_battery_level()

设置最新的电池数据

app_dashboard_data_get_battery_level()

获取当前电池数据

app_dashboard_data_set_car_speed()

设置当前小车速度

app_dashboard_data_get_car_speed()

获取当前小车速度

app_dashboard_data_set_car_turn_info()

设置当前小车转向信息

app_dashboard_data_get_car_turn_info()

获取当前小车转向信息

app_dashboard_data_set_bluetooth_status()

设置当前蓝牙状态

app_dashboard_data_get_bluetooth_status()

获取当前蓝牙状态

app_dashboard_data_set_navi_status()

设置当前导航状态

app_dashboard_data_get_navi_status()

获取当前导航状态

app_dashboard_data_update_navi_status()

更新当前导航状态

app_dashboard_data_set_phone_status()

设置当前手机状态

app_dashboard_data_get_phone_status()

获取当前手机状态

app_dashboard_data_update_phone_status()

设置当前手机信息

app_dashboard_data_set_message_data_update()

设置当前message信息

app_dashboard_data_get_message_data_update()

获取当前message信息

app_dashboard_data_update_message_status()

更新当前message信息

app_dashboard_data_set_current_timer()

设置当前时间信息

app_dashboard_data_get_current_timer()

获取当前时间信息

app_dashboard_data_set_tense_timer_info()

设置当前tense信息

app_dashboard_data_get_tense_timer_info()

获取当前tense信息

app_dashboard_data_set_reject_end_call()

设置拒绝和挂断手机

app_dashboard_data_set_accept_call()

设置接听手机

app_dashboard_update_main_display_speed_info()

更新主界面速度信息

app_dashboard_update_main_display_turn_info()

更新主界面转向信息

app_dashboard_update_main_display_bluetooth_info()

更新主界面蓝牙连接信息

app_dashboard_update_main_display_phone_infor()

更新主界面手机信息

app_dashboard_update_main_display_message_infor()

更新主界面message信息

GUI部分控件描述

GUI API接口

描述

gui_win_create()

创建win窗口

gui_set_font_mem_resourse()

查找并设置内存中的字体资源

gui_win_set_animate()

设置窗口动画效果

gui_img_set_attribute()

设置图像对象的属性

gui_img_create_from_mem()

创建image对象

gui_text_create()

创建文本对象

gui_text_mode_set()

设置文本对象的文本模式

GUI控件相关更详细内容,请参考文档: https://docs.realmcu.com/Honeygui/cn/latest/widgets/index.html

void app_dashboard_launcher_ui_design(gui_app_t *app)
{
    win_connected_display = gui_win_create(&(app->screen),
                                "win_connected_display", 0, 0, 800, 480);
    win_main_display = gui_win_create(&(app->screen), "win_main_display", 0, 0, 800, 480);
    gui_font_mem_init(HARMONYOS_SIZE28_BITS1_FONT_BIN);
    gui_font_mem_init(HARMONYOS_SIZE32_BITS1_FONT_BIN);
    gui_font_mem_init(HARMONYOS_SIZE56_BITS1_FONT_BIN);

    app_dashboard_create_main_display(win_main_display);
    app_dashboard_create_connected_display(win_connected_display);
    win_main_display->base.not_show = false;
    win_connected_display->base.not_show = true;
    app_dashboard_initialize_data();
}
  1. 创建BLE非连接状态的控件
    app_dashboard_create_main_display 函数将依赖于 布局文件 将小部件分配给正确的协调。 我们将注册两个事件“obj_animate”来处理面板刷新前后的状态。 在此函数的最后,我们将调用每个小部件的刷新函数以向用户显示最新信息。
    ../../../../_images/BLE_no_connect_display.png

    BLE非连接状态布局

void app_dashboard_create_main_display(gui_win_t *target_main_display)
{
    /* set update callback */
    gui_win_set_animate(target_main_display, 1000, -1, paint_main_display_cb, target_main_display);
    /* set Image data */
    dashboard_background = gui_img_create_from_mem(target_main_display, "dashboard_background",
                                                   BACKGROUND_BIN, 0, 0, 800, 480);
    speed_high_digital = gui_img_create_from_mem(target_main_display, "speed_high_digital", SPED0_BIN,
                                                 360, 202, 40, 60);
    speed_low_digital = gui_img_create_from_mem(target_main_display, "speed_low_digital", SPED0_BIN,
                                                406, 202, 40, 60);
    bluetooth_status = gui_img_create_from_mem(target_main_display, "bluetooth_status", BTOF_BIN, 48,
                                               10, 23, 30);
    left_turn_light_status = gui_img_create_from_mem(target_main_display, "left_turn_light_status",
                                                     TL_OF_BIN, 48, 208, 42, 40);
    right_turn_light_status = gui_img_create_from_mem(target_main_display, "right_turn_light_status",
                                                      TR_OF_BIN, 710, 208, 42, 40);
    hour_high_digital = gui_img_create_from_mem(target_main_display, "hour_high_digital", TIMER0_BIN,
                                                355, 12, 9, 16);
    hour_low_digital = gui_img_create_from_mem(target_main_display, "hour_low_digital", TIMER0_BIN, 366,
                                               12, 9, 16);
    min_high_digital = gui_img_create_from_mem(target_main_display, "min_high_digital", TIMER0_BIN, 387,
                                               12, 9, 18);
    min_low_digital = gui_img_create_from_mem(target_main_display, "min_low_digital", TIMER0_BIN, 399,
                                              12, 9, 18);
    tense_high_digital = gui_img_create_from_mem(target_main_display, "tense_high_digital", APM_A_BIN,
                                                 417,
                                                 12, 9, 18);
    tense_low_digital = gui_img_create_from_mem(target_main_display, "tense_low_digital", APM_M_BIN,
                                                432,
                                                12, 9, 18);
    bat_high_digital = gui_img_create_from_mem(target_main_display, "bat_high_digital", TIMER0_BIN, 394,
                                               360, 9, 18);
    bat_low_digital = gui_img_create_from_mem(target_main_display, "bat_low_digital", TIMER0_BIN, 406,
                                              360, 9, 18);
    tel_box = gui_img_create_from_mem(target_main_display, "tel_box", TELBOX_BIN, 253, 410, 295, 49);
    tel_accept = gui_img_create_from_mem(target_main_display, "tel_accept", TELBOX_BIN, 253, 410, 295,
                                         49);
    tel_reject_end = gui_img_create_from_mem(target_main_display, "tel_reject_end", TELBOX_BIN, 253,
                                             410, 295,
                                             49);
    refuse_button = gui_img_create_from_mem(target_main_display, "refuse_button", REFUS_BIN, 263, 416,
                                            36, 36);
    ans_button = gui_img_create_from_mem(target_main_display, "ans_button", ANS_BIN, 500, 416, 36, 36);
    tel_box_left_button = gui_img_create_from_mem(target_main_display, "tel_box_left_button", SYMB1_BIN,
                                                  315, 416, 36, 36);
    tel_box_right_button = gui_img_create_from_mem(target_main_display, "tel_box_right_button",
                                                   SYMB2_BIN, 479, 416, 36, 36);
    dashboard_pointer = gui_img_create_from_mem(target_main_display, "dashboard_Cpointer",
                                                DASHBOARD_0_BIN, 243, 84,
                                                0, 0);
    short_message = gui_img_create_from_mem(target_main_display, "short_message", MESSAGE_BIN, 221,
                                            0, 359, 80);

    /* set font data */
    app_phone_data current_phone_status;
    app_dashboard_data_get_phone_status(&current_phone_status);
    short_tel_number = gui_text_create(target_main_display,  "short_tel_number",  322, 415, 158, 30);
    memcpy(&show_tel_number[0], &current_phone_status.current_phone_number[0],
           current_phone_status.current_phone_number_len);
    gui_text_set(short_tel_number, (char *)show_tel_number, GUI_FONT_SRC_BMP, gui_rgb(UINT8_MAX,
                 UINT8_MAX, UINT8_MAX), current_phone_status.current_phone_number_len, 28);
    gui_text_mode_set(short_tel_number, CENTER);
    short_tel_accept = gui_text_create(target_main_display, "short_tel_accept", 360, 415, 800, 30);
    gui_text_set(short_tel_accept, "calling", GUI_FONT_SRC_BMP, gui_rgb(UINT8_MAX, UINT8_MAX,
                                                                        UINT8_MAX),
                 7, 32);

    app_message_data current_message_status;
    app_dashboard_data_get_message_data_update(&current_message_status);
    memcpy(&show_message_data[0], &current_message_status.wechat_msg[0],
           current_message_status.wechat_msg_len);
    short_message_data = gui_text_create(target_main_display,  "short_message_data",  300, 10, 240, 50);
    gui_text_set(short_message_data, (char *)show_message_data, GUI_FONT_SRC_BMP, gui_rgb(UINT8_MAX,
                 UINT8_MAX, UINT8_MAX), (current_message_status.wechat_msg_len - 1), 32);
    gui_text_mode_set(short_message_data, MULTI_LEFT);

    /* Prepare the intial data */
    app_dashboard_update_main_display_time_info();
    app_dashboard_update_main_display_tense_apm_info(app_dashboard_data_get_tense_timer_info());
    app_dashboard_update_main_display_battery_info(app_dashboard_data_get_battery_level());
    app_dashboard_update_main_display_speed_info(app_dashboard_data_get_car_speed());
    app_dashboard_update_main_display_turn_info(app_dashboard_data_get_car_turn_info());
    app_dashboard_update_main_display_bluetooth_info(app_dashboard_data_get_bluetooth_status());
    app_dashboard_update_main_display_phone_infor(&current_phone_status);
    app_dashboard_update_main_display_message_infor(&current_message_status);
}
  1. BLE连接状态布局

void app_dashboard_create_connected_display(gui_win_t *target_connected_display)
{
    /* set update callback */
    gui_win_set_animate(target_connected_display, 1000, -1, paint_connected_display_cb,
                        target_connected_display);

    /* set Image data */
    dashboard_c_background = gui_img_create_from_mem(target_connected_display, "dashboard_c_background",
                                                     BACKGROUND_C_BIN, 0, 0, 800, 480);
    speed_high_c_digital = gui_img_create_from_mem(target_connected_display, "speed_high_c_digital",
                                                   SPED_C0_BIN, 214, 232, 27, 40);
    speed_low_c_digital = gui_img_create_from_mem(target_connected_display, "speed_low_c_digital",
                                                  SPED_C0_BIN, 251, 232, 27, 40);
    hour_high_c_digital = gui_img_create_from_mem(target_connected_display, "hour_high_digital",
                                                  TIMER0_BIN, 355, 12, 9, 16);
    hour_low_c_digital = gui_img_create_from_mem(target_connected_display, "hour_low_digital",
                                                 TIMER0_BIN, 366, 12, 9, 16);
    min_high_c_digital = gui_img_create_from_mem(target_connected_display, "min_high_digital",
                                                 TIMER0_BIN, 387, 12, 9, 18);
    min_low_c_digital = gui_img_create_from_mem(target_connected_display, "min_low_digital", TIMER0_BIN,
                                                399, 12, 9, 18);
    tense_high_c_digital = gui_img_create_from_mem(target_connected_display, "tense_high_c_digital",
                                                   APM_CA_BIN, 417, 12, 9, 18);
    tense_low_c_digital = gui_img_create_from_mem(target_connected_display, "tense_low_c_digital",
                                                  APM_CM_BIN,
                                                  432, 12, 9, 18);
    bat_high_c_digital = gui_img_create_from_mem(target_connected_display, "bat_high_c_digital",
                                                 TIMER0_BIN, 238, 371, 9, 18);
    bat_low_c_digital = gui_img_create_from_mem(target_connected_display, "bat_low_c_digital",
                                                TIMER0_BIN, 250, 371, 9, 18);
    left_turn_light_c_status = gui_img_create_from_mem(target_connected_display,
                                                       "left_turn_light_c_status", TL_OF_C_BIN, 110, 10, 42, 40);
    right_turn_light_c_status = gui_img_create_from_mem(target_connected_display,
                                                        "right_turn_light_c_status", TR_OF_C_BIN, 665, 10, 42, 40);
    navi_c_status = gui_img_create_from_mem(target_connected_display, "navi_c_status", NAVI_C1_BIN, 505,
                                            170, 268, 218);
    refuse_c_button = gui_img_create_from_mem(target_connected_display, "refuse_c_button", REFUS_BIN,
                                              502, 410, 36, 36);
    ans_c_button = gui_img_create_from_mem(target_connected_display, "ans_c_button", ANS_BIN, 740, 410,
                                           36, 36);
    tel_box_left_c_button = gui_img_create_from_mem(target_connected_display, "tel_box_left_c_button",
                                                    SYMB1_BIN, 554, 414, 36, 36);
    tel_box_right_c_button = gui_img_create_from_mem(target_connected_display, "tel_box_right_c_button",
                                                     SYMB2_BIN, 718, 414, 36, 36);
    dashboard_Cpointer = gui_img_create_from_mem(target_connected_display, "dashboard_Cpointer",
                                                 DASHBOARD_C2_BIN, 110, 133, 9, 18);
    short_c_message = gui_img_create_from_mem(target_connected_display, "short_c_message",
                                              MESSAGE_BIN, 221, 0, 359, 80);

    /* set font data */
    app_navi_data current_navi_data;
    app_dashboard_data_get_navi_data_update(&current_navi_data);

    memcpy(&show_c_navigation_msg[0], &current_navi_data.navigation_msg[0],
           current_navi_data.navigation_num_len);
    short_c_navi_message_1 = gui_text_create(target_connected_display,  "short_c_navi_message_1", 490,
                                             88, 150,
                                             60);
    gui_text_set(short_c_navi_message_1, (char *)show_c_navigation_msg, GUI_FONT_SRC_BMP,
                 gui_rgb(UINT8_MAX, UINT8_MAX, UINT8_MAX), current_navi_data.navigation_num_len, 56);
    gui_text_mode_set(short_c_navi_message_1, RIGHT);

    memcpy(&show_c_navigation_unit[0], &current_navi_data.navigation_unit[0],
           current_navi_data.navigation_unit_len);
    short_c_navi_message_3 = gui_text_create(target_connected_display,  "short_c_navi_message_3", 640,
                                             108, 150,
                                             40);
    gui_text_set(short_c_navi_message_3, (char *)show_c_navigation_unit, GUI_FONT_SRC_BMP,
                 gui_rgb(0xcc,
                         0xcc, 0xcc), current_navi_data.navigation_unit_len, 32);
    gui_text_mode_set(short_c_navi_message_3, LEFT);

    memcpy(&show_c_road_names[0], &current_navi_data.road_names[0],
           current_navi_data.road_num_len);
    short_c_navi_message_2 = gui_text_create(target_connected_display,  "short_c_navi_message_2", 490,
                                             148, 300,
                                             40);
    gui_text_set(short_c_navi_message_2, (char *)show_c_road_names, GUI_FONT_SRC_BMP, gui_rgb(0xcc,
                 0xcc,
                 0xcc), current_navi_data.road_num_len, 32);
    gui_text_mode_set(short_c_navi_message_2, CENTER);

    app_phone_data current_phone_status;
    app_dashboard_data_get_phone_status(&current_phone_status);
    short_c_tel_number = gui_text_create(target_connected_display,  "short_c_tel_number",  560, 410,
                                         158, 30);
    memcpy(&show_c_tel_number[0], &current_phone_status.current_phone_number[0],
           current_phone_status.current_phone_number_len);
    gui_text_set(short_c_tel_number, (char *)show_c_tel_number, GUI_FONT_SRC_BMP, gui_rgb(UINT8_MAX,
                 UINT8_MAX, UINT8_MAX), current_phone_status.current_phone_number_len, 28);
    gui_text_mode_set(short_c_tel_number, CENTER);
    short_c_tel_accept = gui_text_create(target_connected_display,  "short_c_tel_accept",  600, 410,
                                         800, 30);
    gui_text_set(short_c_tel_accept, "calling", GUI_FONT_SRC_BMP, gui_rgb(UINT8_MAX, UINT8_MAX,
                                                                          UINT8_MAX), 7, 28);
    app_message_data current_message_status;
    app_dashboard_data_get_message_data_update(&current_message_status);
    memcpy(&show_c_message_data[0], &current_message_status.wechat_msg[0],
           current_message_status.wechat_msg_len);
    short_c_message_data = gui_text_create(target_connected_display,  "short_c_message_data",  300, 10,
                                           240, 50);
    gui_text_set(short_c_message_data, (char *)show_c_message_data, GUI_FONT_SRC_BMP,
                 gui_rgb(UINT8_MAX,
                         UINT8_MAX, UINT8_MAX), (current_message_status.wechat_msg_len - 1), 32);
    gui_text_mode_set(short_c_message_data, MULTI_LEFT);

    /* Prepare the intial data */
    app_dashboard_update_connected_display_time_info();
    app_dashboard_update_connected_display_tense_apm_info(app_dashboard_data_get_tense_timer_info());
    app_dashboard_update_connected_display_battery_info(app_dashboard_data_get_battery_level());
    app_dashboard_update_connected_display_speed_info(app_dashboard_data_get_car_speed());
    app_dashboard_update_connected_display_turn_info(app_dashboard_data_get_car_turn_info());
    app_dashboard_update_connected_display_phone_infor(&current_phone_status);
    app_dashboard_update_connected_display_message_infor(&current_message_status);
}

BLE通信任务

通信任务负责BLE数据传输的管理,根据当前传输数据命令格式将数据打包并通过蓝牙协议栈发送出去。 客户一般使用自己的蓝牙数据交互格式,任务可以作为发送方法的参考,也可以自己实现。开发者必须参考 Bluetooth,熟悉BLE服务结构以及如何处理消息。在本设计文档中,我们将通过仪表板应用程序的指定 BLE 服务作为演示。开发人员可以根据自己的产品需求选择手机应用程序采用哪些 BLE 服务。

../../../../_images/dashboard_ble_task.png

仪表盘通信框图

移动应用程序成功连接到仪表板设备后,应用程序可以使用 BLE 写入请求向设备传送必要的信息,所有消息都将通过特征写入。

    /**      * @brief write characteristic data from stack. *
* @param ServiceId ServiceId generated when register to upper stack.
* @param iAttribIndex Attribute index of getting characteristic data.
* @param wLength length of data to be written.
* @param pValue pointer of data to be written.
* @return TProfileResult profile procedure results.         */
T_APP_RESULT bwps_service_attr_write_cb(uint8_t conn_id, T_SERVER_ID service_id,
     uint16_t attrib_index, T_WRITE_TYPE write_type, uint16_t length, uint8_t *p_value,
                                            P_FUN_WRITE_IND_POST_PROC *p_write_ind_post_proc)
    {
        T_APP_RESULT  wCause  = APP_RESULT_SUCCESS;
        uint8_t *buf = NULL;
        APP_PRINT_INFO2("bwps_service_attr_write_cb: attrib_index = %d, data %b", attrib_index,
    CE_BINARY(length, p_value));

        if (p_value == NULL)
        {
            APP_PRINT_ERROR0("bwps_service_attr_write_cb, p_value is NULL");
            wCause = APP_RESULT_INVALID_VALUE_SIZE;
            return wCause;
        }

        switch (attrib_index)
        {
        default:
            wCause = APP_RESULT_ATTR_NOT_FOUND;
            APP_PRINT_ERROR2("bwps_service_attr_write_cb Error: attrib_index 0x%x, length %d", attrib_index,  length);
            break;
        case GATT_SRV_BWPS_TX_INDEX:
            APP_PRINT_INFO0("bwps_service_attr_write_cb, GATT_SRV_BWPS_TX_INDEX");
            /* copy gatt write value in tx_buffer, the first byte is value length*/
    #if 1
            buf = malloc((length + 1) * sizeof(uint8_t));
            buf[0] = length;
            memcpy(buf + 1, p_value, length);
            extern void *raw_data_receive_queue_handle;
            if (os_msg_send(raw_data_receive_queue_handle, &buf, 0) == false)
            {
                APP_PRINT_ERROR0("send_msg_to_l1send_task_fail");
                free(buf);
            }
    #endif
            break;
        case GATT_SRV_BWPS_DEVNAME_INDEX:
            APP_PRINT_INFO0("BWPS: update device name\n");
            break;
        }

        return wCause;
}

所有收到的消息都会发布到“raw_data_receive_queue_handle”队列, 它将在 API“resolve_remote_data”中处理。

void l1receive_task(void *pvParameters)
{
    uint8_t *buf = NULL;
    os_alloc_secure_ctx(1024);
    os_msg_queue_create(&raw_data_receive_queue_handle, "rec_raw_data", 0x10, sizeof(void *));

    while (true)
    {
        if (os_msg_recv(raw_data_receive_queue_handle, &buf, 0xFFFFFFFF) == true)
        {
            //rt_kprintf("buf2 = 0x%x \n", buf);
            resolve_remote_data(buf + 1, buf[0]);
            free(buf);
        }
    }
}

一旦接收到的数据通过了数据完整性检查,API“L2_frame_resolve”将处理该请求。 在仪表盘应用程序中,开发人员只需处理“NAVIGATION_INFORMATION_ID”、“NOTIFY_COMMAND_ID”和“CONTROL_COMMAND_ID”,3种类型数据。

bool L2_frame_resolve(uint8_t *pData, uint16_t length)
{
    if ((pData == NULL) || (length == 0))
    {
        return false;
    }

    WRISTBAND_COMMUNICATE_COMMAND command_id = (WRISTBAND_COMMUNICATE_   COMMAND)pData[0];
    uint8_t first_key = pData[2];
    uint16_t first_value_length = (((pData[3] << 8) | pData[4]) & 0x1FF);
    APP_PRINT_INFO1("L2_frame_resolve length is %d", length);
    switch (command_id)
    {
    case NAVIGATION_INFORMATION_ID:
        {
            APP_PRINT_INFO1("NAVIGATION_INFORMATION_ID, KEY = 0x%x", first_key);
            APP_PRINT_INFO1("NAVIGATION_INFORMATION_ID,length is %d", length);
            APP_PRINT_INFO1("RECEIVE navi info is  %b", TRACE_BINARY(length,
                                                                     (uint8_t *)pData + L2_FIRST_VALUE_POS));
            resolve_Navigation_command(first_key, pData + L2_FIRST_VALUE_POS, first_value_length);
        }
        break;
    case NOTIFY_COMMAND_ID:
        {
            APP_PRINT_INFO2("NOTIFY_COMMAND_ID, KEY = 0x%x, info= 0x%x", first_key, pData[5]);
            resolve_Notify_command(first_key, pData + L2_FIRST_VALUE_POS, first_value_length, pData[5]);
        }
        break;

    case CONTROL_COMMAND_ID:
        {
            APP_PRINT_INFO1("CONTROL_COMMAND, KEY = 0x%x", first_key);
            resolve_Control_command(first_key, pData + L2_FIRST_VALUE_POS, first_value_length);
        }
        break;
    default:
        break;
    }
    return true;
}

每当BLE收到来自移动应用程序的消息时,它会将数据更新到仪表盘数据容器,然后UI更新线程将获取最新信息并刷新显示。

CAN 通信任务

RTL8772GWP CAN 外接 USB CAN FD 分析仪,通过 PC 端收发数据与 CAN controller 进行通信,在 CAN中断中接收 PC(类似外部信号)下发的数据。

canopen单独创建了3个task:

  • send task

  • receive task

  • canopen (canfestival) task

备注

其中canopen task里面都是APP层的行为,基于canopen (canfestival)协议的行为(包括主节点、从节点之间的交互)用户可根据自己的私用协议重新定义该task。

Send task流程

一旦有数据需要发送,立马放到一个loop buffer,send task会实时监控loop buffer是否有数据。

Receive task流程

一旦硬件can模块有数据,会触发can_handler,在handler里用basic rx / dma rx两种方式之一,把数据搬运出来,然后通过canfestival的API,送到通信协议层,针对收到的msg作出对应的行为。

  1. 引脚定义

#define CAN_TX_PIN          P1_4
#define CAN_RX_PIN          P1_6
  1. CAN 发送任务

/**
 * @brief   canSend task to send CANFestival messages.
 * @param   p_param: Parameters sending to the task
 * @return  void
 */
void canSend_Task(void *p_param)
{
    static CAN_TxMsgDef CO_TxMsg;
#if CAN_RX_FIFO_EN
    uint8_t TxMsgBuf_MaxCnt = 12; //when Rx_FIFO enable, only index_0~11 MBs can be used for Tx.
#else
    uint8_t TxMsgBuf_MaxCnt = 16;
#endif

    while (true)
    {
        /*wait for CANOpen tx message*/
        if (!loop_queue_is_empty(p_canSend_queue))
        {
            if (true == loop_queue_read_buf(p_canSend_queue, &CO_TxMsg, sizeof(CAN_TxMsgDef)))
            {
                for (uint8_t TxMsgBufId = 0; TxMsgBufId < CAN_MESSAGE_BUFFER_MAX_CNT; TxMsgBufId++)
                {
                    /* if the MB is idle, then it can send the data. */
                    FlagStatus Tx_req = CAN_GetMBnStatusTxReqFlag(TxMsgBufId);
                    FlagStatus Rx_vld = CAN_GetMBnStatusRxValidFlag(TxMsgBufId);
                    FlagStatus Rx_rdy = CAN_GetMBnStatusRxReadyFlag(TxMsgBufId);
                    if ((Tx_req == RESET) && (Rx_vld == RESET) && (Rx_rdy == RESET))
                    {
                        CO_TxMsg.CO_TxFrame.msg_buf_id = TxMsgBufId;
                        CANError_TypeDef tx_error = CAN_SetMsgBufTxMode(&CO_TxMsg.CO_TxFrame,
                                              CO_TxMsg.data, CO_TxMsg.dlc);
                        CAN_MBTxINTConfig(CO_TxMsg.CO_TxFrame.msg_buf_id, ENABLE);

                        break;
                    }
                }
            }
        }
    }
}
  1. CAN 接收任务

void canReceive_Task(void *p_param)
      {
          static CAN_RxMsgDef Rx_msg;
          static Message msg;
          while (true)
          {
              /*wait for CANOpen rx message*/
              if (!loop_queue_is_empty(p_canReceive_queue))
              {
                  if (true == loop_queue_read_buf(p_canReceive_queue, &Rx_msg,
                                                  sizeof(CAN_RxMsgDef)))
                  {
                      msg.cob_id = Rx_msg.mb_info.standard_frame_id;
                      msg.rtr = Rx_msg.mb_info.rtr_bit;
                      msg.len = Rx_msg.mb_info.data_length;
                      memcpy(msg.data, Rx_msg.data, sizeof(Rx_msg.data));
                      for (uint8_t index = 0; index < msg.len; index++)
                      {
                          APP_PRINT_INFO2("[CAN] CO_RxMsg.data[%d] 0x%02X", index, msg.data[index]);
                      }
                      APP_PRINT_INFO1("msg.cob_id = 0x%x", msg.cob_id);
                      /*use CANOpen API*/
                      TIM_INTConfig(CANOPEN_TIMx, DISABLE);
      #if CANOPEN_ROLE_MASTER_SUPPORT
                      canDispatch(&Master_Data, &msg);
      #else
                      canDispatch(&slave_all_Data, &msg);
      #endif
                      TIM_INTConfig(CANOPEN_TIMx, ENABLE);
                  }
              }
          }
      }

常见问题

图形数据加载过程

从外部Flash加载程序和绘制数据到PSRAM,并将绘制数据显示到TFT,数据通路 如下所示。

  1. 用户资源显示文件处理:图形资源使用image convert tool处理,输出图片二进制文件,包换图片属性header(图片尺寸、是否压缩、颜色格式(RGB565、RGB888等)),制作User Data:root.bin。

  2. 下载flash区域:使用MPtool工具烧录进EVB XIP flash区域。

  3. Flash内数据读取:采用QSPI,80MHz带宽,双边沿数据(DTR)读取方式。

  4. 分配内存:在加载图像数据之前,需要根据图像的尺寸和格式来分配足够的内存来存储图像像素数据(SRAM)。

  5. 压缩图形像素处理:GUI image控件通过图片名的方式索引文件在flash中地址,判断是否压缩,压缩需解压处理,从flash经DMA搬运至SRAM,通过PPE像素处理引擎,处理pixel并存储在之前分配的内存中(SRAM)。

  6. 未压缩图形像素处理:GUI image控件通过图片名的方式索引文件在flash中地址,判断是否压缩,未压缩图片直接从flash经DMA搬运至SRAM经过PPE处理,并存储在SRAM。

    备注

    PPE处理未压缩数据,压缩数据需解压后,才能经过PPE处理。

  7. TFT显示:PPE处理后pixel经DMA搬运到PSRAM缓存,最后经LCDC(OPI)显示在屏幕上。

../../../../_images/display_progress.png

数据通路

PPE1.0性能衡量

  1. PPE功能:Blend,单位 ms, Destination 固定格式RGB565,屏幕尺寸 480 by 480, 误差不超过180us。

    1. PPE:RGB565 Bypass模式数据 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      7.178

      7.18

      PSRAM

      5.64

      13.524

      SRAM

      3.426

      5.228

    2. PPE:RGB565 source over模式数据 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      7.474

      13.642

      PSRAM

      5.918

      20.846

      SRAM

      5.04

      13.528

    3. PPE:RGB8565 Bypass模式数据 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      11.518

      11.52

      PSRAM

      8.754

      16.932

      SRAM

      4.488

      5.23

    4. PPE:RGB8565 source over模式数据 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      11.775

      14.546

      PSRAM

      8.99

      24.11

      SRAM

      5.775

      13.528

  2. PPE功能:Scale,单位 ms,Destination 固定格式RGB565, 误差不超过80us。

    1. PPE:480 * 480缩小2倍至240 * 240 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      3.762

      3.758

      PSRAM

      2.891

      4.83

      SRAM

      1.54

      1.493

    2. PPE:240 * 240 放大2倍至480 * 480 (Source to Destination),unit:ms

      SRAM

      PSRAM

      FLASH

      16.119

      16.119

      PSRAM

      7.78

      13.507

      SRAM

      3.214

      5.167

Port适配LCD

LCD屏幕适配需要根据屏幕缓存情况适配,本样例默认条件是PSRAM作为屏幕缓存,选择GRAM作为屏幕缓存请参考 GRAM 小节。

RAMLESS

  1. 屏幕分辨率信息配置(参考屏幕规格书),主要配置LCD宽度、高度、像素深度信息。以示例为例,修改在 EK9716_800480_rgb.h 头文件中的对应参数。

#define EK9716_800480_LCD_WIDTH                   800  //Screen width
#define EK9716_800480_LCD_HEIGHT                  480  //Screen Height
#define EK9716_DRV_PIXEL_BITS                     16  //Pixel Depth
  1. 需要配置section高度,高度选择根据实际情况而定,在示例工程中配置LCD_SECTION_HEIGHT = 10,对应参数可以在 gui_port_dc.c 中修改。

#define LCD_SECTION_HEIGHT                   10
  1. PSRAM作为屏幕缓存需要配置地址信息。在 gui_port_dc.c 中,为framebuffer配置PSRAM缓存的示例如下:

static uint32_t video_buffer1 = SPIC1_ADDR;
static uint32_t video_buffer2 = (SPIC1_ADDR + EK9716_800480_LCD_WIDTH * EK9716_800480_LCD_HEIGHT * 2);
static uint32_t current_buffer = SPIC1_ADDR;
屏幕显示DMA搬运API

API

说明

memcpy_by_dma_start()

DMA开始搬运数据

memcpy_by_dma_done()

等待DMA搬运数完成

  1. LCD驱动层其他接口API,对应 gui_port_dc.c

    LCD屏幕显示驱动接口

    API

    说明

    drv_lcd_get_screen_width()

    获取屏幕宽度

    drv_lcd_get_screen_height()

    获取屏幕高度

    drv_lcd_get_pixel_bits()

    获取屏幕像素深度

    port_gui_lcd_power_on()

    屏幕启动

    port_gui_lcd_power_off()

    屏幕关闭

    gui_port_dc_init()

    初始化LCD

    port_gui_lcd_update()

    更新LCD的每一个区段(segment)

    drv_lcd_update()

    将指定缓存区传输到屏幕

    将帧缓冲区的内容更新的LCD屏幕上,使用 port_gui_lcd_update() 接口,默认情况屏幕搭配PSRAM作为帧缓冲区。

    void port_gui_lcd_update(struct gui_dispdev *dc)
    {
    
        uint32_t i = dc->section_count;
        uint32_t total_section_cnt = (drv_lcd_get_screen_height() / LCD_SECTION_HEIGHT + ((
                drv_lcd_get_screen_height() % LCD_SECTION_HEIGHT) ? 1 : 0));
        uint32_t section = (drv_lcd_get_screen_height() / LCD_SECTION_HEIGHT + ((
                drv_lcd_get_screen_height() % LCD_SECTION_HEIGHT) ? 1 : 0));
    #if 0 //gram
        if (i == 0)
        {
            drv_lcd_set_window(0, dc->fb_height * dc->section_count,   dc->fb_width, dc->fb_height);
            drv_lcd_start_transfer(dc->frame_buf, dc->fb_width * dc->fb_height);
        }
        else if (i == section - 1)
        {
            uint32_t last_height = dc->screen_height - dc->section_count * dc->fb_height;
            drv_lcd_transfer_done();
            drv_lcd_set_window(0, dc->fb_height * dc->section_count, dc->fb_width, last_height);
            drv_lcd_start_transfer(dc->frame_buf, dc->fb_width * last_height);
            drv_lcd_transfer_done();
        }
        else
        {
            drv_lcd_transfer_done();
            drv_lcd_set_window(0, dc->fb_height * dc->section_count, dc->fb_width, dc->fb_height);
            drv_lcd_start_transfer(dc->frame_buf, dc->fb_width * dc->fb_height);
        }
    #else //psram
        void *dst = (void *)(current_buffer + i * dc->fb_width * dc->fb_height * source_bytes_per_pixel);
        if (i == 0)
        {
            memcpy_by_dma_start(dst, dc->frame_buf,
                                dc->fb_width * dc->fb_height * source_bytes_per_pixel);
        }
        else if (i == section - 1)
        {
            uint32_t last_height = dc->screen_height - dc->section_count * dc->fb_height;
            memcpy_by_dma_done();
            memcpy_by_dma_start(dst, dc->frame_buf, dc->fb_width * last_height * source_bytes_per_pixel);
            memcpy_by_dma_done();
    
            drv_lcd_update((uint8_t *)current_buffer, 0, 0, drv_lcd_get_screen_width(),
                           drv_lcd_get_screen_height());
    
            if (current_buffer == video_buffer1)
            {
                current_buffer = video_buffer2;
            }
            else if (current_buffer == video_buffer2)
            {
                current_buffer = video_buffer1;
            }
            else
            {
                current_buffer = video_buffer1;
            }
        }
        else
        {
            memcpy_by_dma_done();
            memcpy_by_dma_start(dst, dc->frame_buf,
                                dc->fb_width * dc->fb_height * source_bytes_per_pixel);
        }
    #endif
    }
    

GRAM

相较于PSRAM,GRAM读写速度更慢。因此,对于需要频繁更新显示内容的场景(如动画或视频播放)而言 ,选用GRAM可能不太适合。下面解释屏幕更新所需要的几个关键函数:

  1. 等待前面section搬运结束:中间层API接口 rtk_lcd_hal_transfer_done() ,底层实现 rtk_lcd_hal_transfer_done()

void rtk_lcd_hal_transfer_done(void)
{
    LCDC_HANDLER_DMA_FIFO_CTRL_t handler_reg_0x18;
    do
    {
        handler_reg_0x18.d32 = LCDC_HANDLER->DMA_FIFO_CTRL;
    }
    while (handler_reg_0x18.b.dma_enable != RESET);
    LCDC_HANDLER_OPERATE_CTR_t handler_reg_0x14;
    do
    {
        handler_reg_0x14.d32 = LCDC_HANDLER->OPERATE_CTR;
    }
    while (handler_reg_0x14.b.auto_write_start != RESET);

    LCDC_ClearDmaFifo();  // clear DMA FIFO
    LCDC_ClearTxPixelCnt();  // clear the pixel counter for the transfer
    LCDC_AXIMUXMode(LCDC_FW_MODE);  // switch AXI MUX mode to FW mode
    LCDC_Cmd(DISABLE);  // disable LCD controller
}
  1. 设置绘制区域:中间层API接口 rtk_lcd_hal_set_window() ,对应底层实现 rtk_lcd_hal_set_window()

void rtk_lcd_hal_set_window(uint16_t xStart, uint16_t yStart, uint16_t w, uint16_t h)
{
    uint8_t data[4];
    uint16_t xEnd = xStart + w - 1;
    uint16_t yEnd = yStart + h - 1;

    xStart = xStart + 0x14;
    xEnd = xEnd + 0x14;

    data[0] = xStart >> 8;
    data[1] = xStart & 0xff;
    data[2] = xEnd >> 8;
    data[3] = xEnd & 0xff;
    incna3311_cmd_param4(0x2A, data);


    data[0] = yStart >> 8;
    data[1] = yStart & 0xff;
    data[2] = yEnd >> 8;
    data[3] = yEnd & 0xff;
    incna3311_cmd_param4(0x2B, data);

    uint32_t len_byte = (xEnd - xStart + 1) * (yEnd - yStart + 1) * OUTPUT_PIXEL_BYTES;
    incna3311_enter_data_output_mode(len_byte);
}
  1. 从帧缓存区搬运开始:中间层API接口 rtk_lcd_hal_start_transfer(),对应底层实现 rtk_lcd_hal_start_transfer()

void rtk_lcd_hal_start_transfer(uint8_t *buf, uint32_t len)
{

#if (DMA_LINKLIST == 0)  //无DMA链表
    LCDC_DMA_InitTypeDef LCDC_DMA_InitStruct = {0};
    LCDC_DMA_StructInit(&LCDC_DMA_InitStruct);
    LCDC_DMA_InitStruct.LCDC_DMA_ChannelNum          = LCDC_DMA_CHANNEL_NUM;
    LCDC_DMA_InitStruct.LCDC_DMA_DIR                 = 4;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceInc           = LCDC_DMA_SourceInc_Inc;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationInc      = LCDC_DMA_DestinationInc_Fix;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceDataSize      = LCDC_DMA_DataSize_Word;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationDataSize = LCDC_DMA_DataSize_Word;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceMsize         = LCDC_DMA_Msize_8;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationMsize    = LCDC_DMA_Msize_8;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceAddr          = (uint32_t)buf;
    LCDC_DMA_InitStruct.LCDC_DMA_Multi_Block_En     = 0;
    LCDC_DMA_Init(LCDC_DMA_CHANNEL_INDEX, &LCDC_DMA_InitStruct);
#else
    LCDC_DMA_InitTypeDef LCDC_DMA_InitStruct = {0};
    LCDC_DMA_StructInit(&LCDC_DMA_InitStruct);
    LCDC_DMA_InitStruct.LCDC_DMA_ChannelNum          = LCDC_DMA_CHANNEL_NUM;
    LCDC_DMA_InitStruct.LCDC_DMA_DIR                 = 4;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceInc           = LCDC_DMA_SourceInc_Inc;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationInc      = LCDC_DMA_DestinationInc_Fix;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceDataSize      = LCDC_DMA_DataSize_Word;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationDataSize = LCDC_DMA_DataSize_Word;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceMsize         = LCDC_DMA_Msize_8;
    LCDC_DMA_InitStruct.LCDC_DMA_DestinationMsize    = LCDC_DMA_Msize_8;
    LCDC_DMA_InitStruct.LCDC_DMA_SourceAddr          = 0;

    LCDC_DMA_InitStruct.LCDC_DMA_Multi_Block_Mode   =
    LLI_TRANSFER;//LLI_TRANSFER or LLI_WITH_CONTIGUOUS_SAR
    LCDC_DMA_InitStruct.LCDC_DMA_Multi_Block_En     = 1;
    LCDC_DMA_InitStruct.LCDC_DMA_Multi_Block_Struct  = LCDC_DMA_LINKLIST_REG_BASE + 0x50;
    LCDC_DMA_Init(LCDC_DMA_CHANNEL_INDEX, &LCDC_DMA_InitStruct);

    LCDC_SET_GROUP1_BLOCKSIZE(ICNA3311_LCD_WIDTH * INPUT_PIXEL_BYTES);
    LCDC_SET_GROUP2_BLOCKSIZE(ICNA3311_LCD_WIDTH * INPUT_PIXEL_BYTES);

    /*16 pixel aligned for GPU*/
    LCDC_DMALLI_InitTypeDef LCDC_DMA_LLI_Init = {0};
    LCDC_DMA_LLI_Init.g1_source_addr = (uint32_t)buf;
    LCDC_DMA_LLI_Init.g1_sar_offset = ICNA3311_LCD_WIDTH * INPUT_PIXEL_BYTES * 2;

    LCDC_DMA_LLI_Init.g2_source_addr = (uint32_t)(buf + ICNA3311_LCD_WIDTH * INPUT_PIXEL_BYTES);
    LCDC_DMA_LLI_Init.g2_sar_offset = ICNA3311_LCD_WIDTH * INPUT_PIXEL_BYTES * 2;
    LCDC_DMA_LinkList_Init(&LCDC_DMA_LLI_Init,
                           &LCDC_DMA_InitStruct);//LLI_TRANSFER or LLI_WITH_CONTIGUOUS_SAR
#endif

    LCDC_ClearDmaFifo();  // 清空 DMA FIFO
    LCDC_ClearTxPixelCnt();  // 清零传输的像素计数器

    LCDC_SwitchMode(LCDC_AUTO_MODE);  // 切换 LCD 控制器到自动模式
    LCDC_SwitchDirect(LCDC_TX_MODE);  // 切换 LCD 控制器到发送模式
    LCDC_SetTxPixelLen(len);  // 设置发送的像素数目

    LCDC_Cmd(ENABLE);  // 使能 LCD 控制器
#if DMA_LINKLIST
    LCDC_DMA_MultiBlockCmd(ENABLE);  // 在 DMA 链表模式下,使能多块传输
#endif
    LCDC_DMAChannelCmd(LCDC_DMA_CHANNEL_NUM, ENABLE);  // 使能 DMA 通道
    LCDC_DmaCmd(ENABLE);  // 使能 DMA
#if (TE_VALID == 1)
    LCDC_TeCmd(ENABLE);  // 如果 TE 有效,则使能 TE (Transfer End) 信号
#endif
#if (TE_VALID == 0)
    LCDC_AutoWriteCmd(ENABLE);  // 如果 TE 无效,则使能自动写入命令
#endif

}
  1. Te信号配置,防止屏幕撕裂。
    硬件带TE信号引脚,配置引脚为下降沿触发,同时在te回调函数中,修改te标志位。
  2. 屏幕参数配置,完全参照PSRAM作为屏幕缓存的情况,配置屏幕基础信息,修改section高度。

参考文档

  1. RTL8772GWP_Cluster_SDK_Programming_Guide R2