[原创] 【瑞萨RA8D1 LVGL/LWIP评测】利用LVGL显示边缘AI推理结果

Mr_Fang   2026-2-1 23:15 楼主

瑞萨电子 RA8D1 MCU 系列是业界首款基于 CM85 内核的 32 位图形 MCU,能够在 480 MHz 频率下实现超过 3000 CoreMark 分数的突破性性能,可支持高分辨率显示和视觉 AI 应用的卓越图形功能

 

〇、软硬件

硬件:CPKCOR-RA8D1 核心板、CPKEXP-EKRA8x1 扩展板、Type-C USB 线缆

软件:e² Studio、GUI Guider

 

一、配置项目

使用 e² Studio 创建项目时 Board 选择 CPKCOR-RA8D1B Core Board(PACK 在此处下载),选择使用 FreeRTOS,模板选择 Blinky 或 Minimal 均可,选择 Blinky 将会初始化一个 RTOS 线程 blink 小灯,可作为系统运行的指示

创建好工程后,在 FSP 的 BSP 界面,使能 SDRAM、Data cache、设置 Stack 和 Heap 大小:

image.png  

在 Pins 界面设置外设 SDRAM、MIPI、UART 等,配置比较麻烦,可以从例程中导入

在 Stacks 界面配置外设堆栈,点击右上角的 New Stack 新建 LVGL、FreeRTOS Heap 4,新建 LVGL 后需要添加 MIPI 驱动:

image.png    

因为使用了 Free RTOS,新建一个线程负责 LVGL 显示:

image.png  

主要需要进行配置:

image.png  

image.png  

↑ 配置 LVGL 线程

 

image.png  

↑ 配置 LVGL

 

image.png  

↑ 配置 Graphics LCD

配置完成后就可以保存文件,点击 Generate Project Content 生成代码

 

二、点亮屏幕

需要添加的程序如下:

src/board_init.c

#include "board_init.h"

#include "lvgl.h"
#include "common_data.h"
#include "hal_data.h"


#if LV_USE_DRAW_DAVE2D
#define DIRECT_MODE 1
#else
#define DIRECT_MODE 0
#endif



void board_init(void)
{
    /* Need to initialise the Touch Controller before the LCD, as only a Single Reset line shared between them */
    //touch_init();
    R_SCI_B_UART_Open(&g_uart3_ctrl, &g_uart3_cfg);

    printf("Hello, RA8D1!\r\n");

    R_AGT_Open(&g_timer0_ctrl, &g_timer0_cfg);
    R_AGT_Start(&g_timer0_ctrl);
    R_AGT_Enable(&g_timer0_ctrl);

#if DIRECT_MODE
    fsp_err_t err;
    err = RM_LVGL_PORT_Open(&g_lvgl_port_ctrl, &g_lvgl_port_cfg);
    if (FSP_SUCCESS != err)
    {
        __BKPT(0);
    }
#else
    static uint8_t partial_draw_buf[64*1024] BSP_PLACE_IN_SECTION(".dtcm_bss") BSP_ALIGN_VARIABLE(1024);

    lv_display_t * disp = lv_renesas_glcdc_partial_create(partial_draw_buf, NULL, sizeof(partial_draw_buf));
#endif

#if DIRECT_MODE
    lv_display_set_default(g_lvgl_port_ctrl.p_lv_display);
#else
    lv_display_set_default(disp);
#endif


}

 

src/board_init.h

#pragma once

void board_init(void);

 

src/dsi_configuration_data.c

#include "dsi_layer.h"

/* This table of commands was adapted from sample code provided by FocusLCD
 * Page Link: https://focuslcds.com/product/4-5-tft-display-capacitive-tp-e45ra-mw276-c/
 * File Link: https://focuslcds.com/content/E45RA-MW276-C_init_code.txt
 */
LCD_setting_table lcd_init_focuslcd[] =
{

         {2,     {0x11, 0x00},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_0_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},

         {2,     {0xF0, 0xC3},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xF0, 0x96},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0x36, 0x48},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0x3A, 0x55},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xB4, 0x01},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},

         //0xB5, VFP=2,VBP=6, HBP=48
//          {5,     {0xB5, 0x02,0x06,0x00,0x30},                           MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},

         {4,     {0xB6, 0x8A, 0x07, 0x3B},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
//          {9,     {0xB6, 0x8A, 0x8A, 0x00,0x00,0x29,0x19,0xA5,0x33},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xB7, 0xC6},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {3,     {0xB9, 0x02,0xE0},                           MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {3,     {0xC0, 0xC0,0x64},                           MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xC1, 0x1D},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xC2, 0xA7},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xC5, 0x18},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {9,     {0xE8, 0x40, 0x8A, 0x00,0x00,0x29,0x19,0xA5,0x33},/*0x25,0x0A,0x38,0x33},*//*0x29,0x19,0xA5,0x33},*/   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         //0xE0, positive gamma 控制
         {15,    {0xE0, 0xF0, 0x0B, 0x12,0x09,0x0A,0x26,0x39,0x54,0x4E,0x38,0x13,0x13,0x2E,0x34},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         //0xE1, negative gamma 控制
         {15,    {0xE1, 0xF0, 0x10, 0x15,0x0D,0x0C,0x07,0x38,0x43,0x4D,0x3A,0x16,0x15,0x30,0x35},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xF0, 0x3C},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0xF0, 0x69},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0x35, 0x00},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
//          {1,     {0x29},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
//          {1,     {0x21},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_1_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {2,     {0x29, 0x00},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_0_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},

         {2,     {0x21, 0x00},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_0_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},

         {5,     {0x2A, 0x00, 0x31, 0x01,0x0E},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {5,     {0x2B, 0x00, 0x00, 0x01,0xDF},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},
//          {5,     {0x2B, 0x00, 0x02, 0x01,0xE1},   MIPI_DSI_CMD_ID_DCS_LONG_WRITE, MIPI_DSI_CMD_FLAG_LOW_POWER},

         {2,     {0x2C, 0x00},                           MIPI_DSI_CMD_ID_DCS_SHORT_WRITE_0_PARAM, MIPI_DSI_CMD_FLAG_LOW_POWER},
         {0x00,  {0},                                    MIPI_DSI_DISPLAY_CONFIG_DATA_END_OF_TABLE, (mipi_dsi_cmd_flag_t)0},


};


 

src/dsi_layer.c

#include "r_ioport.h"
#include "r_mipi_dsi_api.h"

#include "hal_data.h"
#include "dsi_layer.h"

void mipi_dsi0_callback(mipi_dsi_callback_args_t * p_args);
static fsp_err_t dsi_layer_set_peripheral_max_return_msg_size(void);

static volatile bool g_message_sent = false;
void mipi_dsi_push_table (const LCD_setting_table *table);

fsp_err_t dsi_layer_configure_peripheral()
{
    fsp_err_t err = FSP_SUCCESS;
    LCD_setting_table * init_table = lcd_init_focuslcd;

    err = dsi_layer_set_peripheral_max_return_msg_size(); // This must be performed prior to reading from display
    if (FSP_SUCCESS == err)
    {

        LCD_setting_table * p_entry = init_table;
        uint32_t counter = 0;
        while(p_entry->msg_id != MIPI_DSI_DISPLAY_CONFIG_DATA_END_OF_TABLE)
        {
            mipi_dsi_cmd_t msg = { .channel = 0,
                                   .cmd_id = p_entry->msg_id,
                                   .flags = p_entry->flags,
                                   .tx_len = p_entry->size,
                                   .p_tx_buffer = p_entry->buffer };


            if (p_entry->msg_id == MIPI_DSI_DISPLAY_CONFIG_DATA_DELAY_FLAG)
            {
                R_BSP_SoftwareDelay(p_entry->size, BSP_DELAY_UNITS_MILLISECONDS);
            }
            else
            {

                g_message_sent = false;
                err = R_MIPI_DSI_Command (&g_mipi_dsi0_ctrl, &msg);
                if (FSP_SUCCESS == err)
                {

                    while(!g_message_sent);

                    mipi_dsi_status_t status;
                    R_MIPI_DSI_StatusGet(&g_mipi_dsi0_ctrl, &status);
                    while (MIPI_DSI_LINK_STATUS_CH0_RUNNING & status.link_status)
                    {
                        R_MIPI_DSI_StatusGet(&g_mipi_dsi0_ctrl, &status);
                    }
                }
                else
                {
                    break;
                }

            }
            p_entry++;
            counter++;
        }
    }

    return err;
}


void mipi_dsi0_callback (mipi_dsi_callback_args_t * p_args)
{
    fsp_err_t err;
    switch (p_args->event)
    {
        case MIPI_DSI_EVENT_SEQUENCE_0:
        {
//            g_message_sent |= (p_args->tx_status == MIPI_DSI_SEQUENCE_STATUS_DESCRIPTORS_FINISHED);
            if (MIPI_DSI_SEQUENCE_STATUS_DESCRIPTORS_FINISHED == p_args->tx_status)
            {
                g_message_sent = 1U;
            }
            break;
        }
        case MIPI_DSI_EVENT_SEQUENCE_1:
        {
            __NOP();
            break;
        }
        case MIPI_DSI_EVENT_VIDEO:
        {
            __NOP();
            break;
        }
        case MIPI_DSI_EVENT_RECEIVE:
        {
            __NOP();
            break;
        }
        case MIPI_DSI_EVENT_FATAL:
        {
            __NOP();
            break;
        }
        case MIPI_DSI_EVENT_PHY:
        {
            __NOP();
            break;
        }
        case MIPI_DSI_EVENT_POST_OPEN:
        {
            /* This case is called from R_DSI_Open(), so not from an interrupt */

            err = dsi_layer_configure_peripheral();
              if (FSP_SUCCESS != err)
              {
                  __BKPT(0);
              }
            break;
        }
        default:
        {
            break;
        }
    }
}

/* See ILI9806E Datasheet, chapter  3.5.39
 *  1. Set max return packet size
 *  2. Read data be sending appropriate request */
static fsp_err_t  dsi_layer_set_peripheral_max_return_msg_size()
{
    fsp_err_t err;
    uint8_t msg_buffer[] = {0x02, 0x00};
    mipi_dsi_cmd_t return_size_msg = { .channel = 0,
                                       .cmd_id = MIPI_CMD_ID_SET_MAXIMUM_RETURN_PACKET_SIZE,
                                       .flags = MIPI_DSI_CMD_FLAG_LOW_POWER,
                                       .tx_len = 2,
                                       .p_tx_buffer = msg_buffer, };
    /* Set Return packet size */
    g_message_sent = false;
    err = R_MIPI_DSI_Command (&g_mipi_dsi0_ctrl, &return_size_msg);
    if (FSP_SUCCESS == err)
    {
    while(!g_message_sent);
    }

    return err;
}

 

src/dsi_layer.h

#pragma once

#include "r_mipi_dsi.h"


typedef struct {
    unsigned char size;
    unsigned char buffer[15];
    mipi_dsi_cmd_id_t  msg_id;
    mipi_dsi_cmd_flag_t flags;
} LCD_setting_table;


extern const mipi_dsi_cfg_t g_mipi_dsi1_cfg;
extern mipi_dsi_instance_ctrl_t  g_mipi_dsi0_ctrl;
extern LCD_setting_table lcd_init_focuslcd[];

fsp_err_t dsi_layer_configure_peripheral(void);


#define MIPI_DSI_DISPLAY_CONFIG_DATA_DELAY_FLAG      ((mipi_dsi_cmd_id_t) 0xFE)
#define MIPI_DSI_DISPLAY_CONFIG_DATA_END_OF_TABLE    ((mipi_dsi_cmd_id_t) 0xFD)

 

src/lv_conf_user.h

#ifndef LV_CONF_USER_H_
#define LV_CONF_USER_H_

#define LV_BUILD_DEMOS 0
#define LV_BUILD_EXAMPLES 0

#endif /* LV_CONF_USER_H_ */

添加好上面的程序后就可以在 lvgl_thread_entry.c 中的 lvgl_thread_entry() 里编写程序了,我们可以编写一个 Hello World 程序测试屏幕:

void lvgl_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    lv_init();
    board_init();

    lv_obj_set_style_bg_color(lv_screen_active(), lv_color_hex(0x0049a5), LV_PART_MAIN);

    lv_obj_t * label = lv_label_create(lv_screen_active());
    lv_label_set_text(label, "Hello, RA8D1~");
    lv_obj_set_style_text_color(lv_screen_active(), lv_color_hex(0xffffff), LV_PART_MAIN);
    lv_obj_align(label, LV_ALIGN_TOP_LEFT, 10, 10);

    lv_timer_handler();

    while (1)
    {
        lv_timer_handler();
        vTaskDelay(1);
    }
}

编译并烧录程序即可看到效果:

image.png  

三、好看的 GUI

恩智浦提供了一个 GUI Guider,可以以可视化的方式配置 LVGL 界面,创建项目时选择 LVGL 9.3.0,设备模板选择 Simulator,应用模板选择 EmptyUI(或者喜欢的),注意调整面板的宽度和高度:

image.png  

我简单设计了一个 GUI 界面显示边缘 AI 的推理结果:

image.png  

需要注意的是,添加图片时格式化选择合适的索引颜色格式可以减少图片体积,不要选择图片压缩:

image.png  

设计完成后可使用快捷键 Ctrl+G 生成 C 代码,将 generated 目录下生成的全部文件放到工程的 src/ui 中

image.png  

编辑 lvgl_thread_entry():

void lvgl_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    lv_init();
    board_init();

    setup_ui(&guider_ui);
    events_init(&guider_ui);

    lv_timer_handler();

    while (1)
    {
        lv_timer_handler();
        vTaskDelay(5);
    }
}

编译并烧录程序即可看到效果:

image.png  

 

四、部署边缘 AI

这里使用 TinyMaix 实现在单片机上使用过去 24h 的天气数据(温度、湿度、大气压强)预测未来 24h 的平均天气,可以参考以下内容完成部署:

【RA】桌面边缘AI气象站

在 MCU 部署边缘 AI —— 以 RA6E2 天气预测为例

可以参考开源平台的参考程序,只需要将其中的 OLED 更换为 LVGL 即可:

void lvgl_thread_entry(void *pvParameters)
{
    FSP_PARAMETER_NOT_USED (pvParameters);

    lv_init();
    board_init();

    setup_ui(&guider_ui);
    events_init(&guider_ui);

    lv_timer_handler();

    // tinyML_run();

    while (1)
    {
        lv_timer_handler();

        if (line_ready)
        {
            line_ready = false;
            store_weather_frame((char *)rx_buf, false);
        }

        if (timer_flag)
        {
            timer_flag = false;
            store_weather_frame((char *)rx_buf, true);
            if (w_cnt >= 24) {
                tinyML_run();
            }
        }

        if (w_maxi >= 0 && w_cnt == 24)
        {
            lv_obj_add_flag(guider_ui.screen_waiting_label, LV_OBJ_FLAG_HIDDEN);
            switch (w_maxi) {
                case 0: lv_image_set_src(guider_ui.screen_result_icon, &_sunny_I4_72x72); break;
                case 1: lv_image_set_src(guider_ui.screen_result_icon, &_cloudy_I4_72x72); break;
                case 2: lv_image_set_src(guider_ui.screen_result_icon, &_cloudy_I4_72x72); break;
                case 3: lv_image_set_src(guider_ui.screen_result_icon, &_rainy_I4_72x72); break;
                case 4: lv_image_set_src(guider_ui.screen_result_icon, &_snowy_I4_72x72); break;
            }
        }
        else
        {
            lv_image_set_src(guider_ui.screen_result_icon, &_waiting_I4_72x72);
            sprintf(print_buf, "%d/24", w_cnt);
            lv_obj_remove_flag(guider_ui.screen_waiting_label, LV_OBJ_FLAG_HIDDEN);
            lv_label_set_text(guider_ui.screen_waiting_label, print_buf);
        }

        sprintf(print_buf, "%.2f", w_temp);
        lv_label_set_text(guider_ui.screen_temp_value, print_buf);

        sprintf(print_buf, "%.2f", w_humi);
        lv_label_set_text(guider_ui.screen_humi_value, print_buf);

        sprintf(print_buf, "%.2f", w_pres);
        lv_label_set_text(guider_ui.screen_pres_value, print_buf);

        lv_timer_handler();
    }
}

image.png  

终、未完待续...

感谢瑞萨提供的本块 RA8D1 开发板,这是我第一次接触 LVGL 开发,e2 Studio + FSP 配合 RA8D1 强大的图形处理能力是 LVGL 开发入门的好选择

最初打算使用 RA8D1 的 LwIP 实现接入 MQTT 获取传感器数据,但由于种种原因迟迟没有调通,后续程序调通后将第一时间发帖分享

本文内使用 TinyMaix 部署了边缘 AI,瑞萨也提供了如 Reality Al 在内的边缘 AI 工具,后续将在【瑞萨AI挑战赛】文章内分享使用 RAI 完成数据采集、储存,模型训练、部署、调试

本帖最后由 Mr_Fang 于 2026-2-1 23:32 编辑

回复评论 (2)

LVGL显示边缘AI推理原来是这么玩 ,谢谢分享

点赞  2026-2-3 18:36

没有卫星云图的天气预测,不知道准不准。

点赞  2026-2-6 07:39
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 京公网安备 11010802033920号
    写回复