前言
- 【STM32H7S78-DK】开箱与rtthread工程初体验
- 【STM32H7S78-DK】rtthread 增加 psram 内存管理
- 【STM32H7S78-DK】基于 rtthread 适配 sdcard 文件系统
概述
原先 Kconfig 已经有相应的配置项,但是存在很多问题,现在我们修改下,增加 lcd 驱动,并开启 lvgl
配置工程
打开 bsp/stm32/stm32h7s7-st-disco 目录
- 打开
board/kconfig 文件,在 chip driver 目录增加 lcd 配置,修改 onboard driver 的宏依赖 config BSP_USING_LCD
bool "Enable LCD"
select BSP_USING_LTDC
select BSP_USING_I2C
select BSP_USING_I2C1
select PKG_USING_GT911
default n
- 修改 i2c 的引脚号,SCL 对应 PB6 引脚号为 22,SDA 对应 PB9 引脚号为 25
- 打开
bsp\stm32\libraries\STM32H7RSxx_HAL\SConscript 文件,增加开启 lcd 时的 stm32 hal 库文件 if GetDepend(['BSP_USING_LTDC']):
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_hal_ltdc.c']
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_hal_ltdc_ex.c']
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_hal_dma2d.c']
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_ll_dma2d.c']
if GetDepend(['RT_USING_I2C']):
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_hal_i2c.c']
src += ['STM32H7RSxx_HAL_Driver/Src/stm32h7rsxx_hal_i2c_ex.c']
- 引脚配置在上一章已经进行了对应的初始化,这边就不用处理了
- 在
bsp/stm32/stm32h7s7-st-disco/board/port/ 增加 lcd_port.h 文件,添加如下内容 /*
* Copyright (c) 2006-2024, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-08-28 morph first version
*/
#ifndef __LCD_PORT_H__
#define __LCD_PORT_H__
#define LCD_WIDTH 800
#define LCD_HEIGHT 480
#define LCD_BITS_PER_PIXEL 16
#define LCD_BUF_SIZE (LCD_WIDTH * LCD_HEIGHT * LCD_BITS_PER_PIXEL / 8)
#define LCD_PIXEL_FORMAT RTGRAPHIC_PIXEL_FORMAT_RGB565
#define LCD_HSYNC_WIDTH 5
#define LCD_VSYNC_HEIGHT 5
#define LCD_HBP 8
#define LCD_VBP 8
#define LCD_HFP 8
#define LCD_VFP 8
#define LCD_BACKLIGHT_USING_GPIO
#define LCD_BL_GPIO_NUM GET_PIN(G, 15)
#define LCD_DISP_GPIO_NUM GET_PIN(E, 15)
void drv_lcd_flush(unsigned int *addr);
#endif /* __LCD_PORT_H__ */
- 使用 ****menuconfig** 查看 onboard 驱动,看到已经增加 lcd 配置,保存退出**
- 使用 ****scons --target=mdk5** 工具同步工程**
- 编译下载,查看 lcd 驱动,并运行测试指令
lcd_test
添加 LVGL
- 打开
board/kconfig 文件,在 chip driver 目录增加 lvgl 配置,修改 onboard driver 的宏依赖 menuconfig BSP_USING_LVGL
bool "Enable LVGL for LCD"
select BSP_USING_LCD
select PKG_USING_LVGL
default n
if BSP_USING_LVGL
config BSP_USING_LVGL_DEMO
bool "Enable LVGL demo"
select PKG_USING_LV_MUSIC_DEMO
default y
endif
- 使用 ****pkgs --update** 拉取依赖软件包,然后使用 **scons --target=mdk5** 工具同步工程**
- 软件包同步进来之后,还需要修改
application/lvgl 目录的文件
- 修改
application/lvgl/lv_conf.h /*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-01-28 Rudy Lo The first version
*/
#ifndef LV_CONF_H
#define LV_CONF_H
#include <rtconfig.h>
#define LV_USE_PERF_MONITOR 1
#define LV_COLOR_DEPTH 16
#define LV_HOR_RES_MAX 800
#define LV_VER_RES_MAX 480
#ifdef PKG_USING_LV_MUSIC_DEMO
/* music player demo */
#define LV_USE_DEMO_RTT_MUSIC 1
#define LV_DEMO_RTT_MUSIC_AUTO_PLAY 0
#define LV_FONT_MONTSERRAT_12 1
#define LV_FONT_MONTSERRAT_16 1
#define LV_COLOR_SCREEN_TRANSP 1
#endif /* PKG_USING_LV_MUSIC_DEMO */
#endif
- 修改
application/lvgl/lv_demo.c /*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-08-28 Morph The first version
*/
#include <rtthread.h>
#include <lvgl.h>
void lv_user_gui_init(void)
{
/* display demo; you may replace with your LVGL application at here */
extern void lv_demo_music(void);
lv_demo_music();
}
- 修改
application/lvgl/lv_port_disp.c ,将显示方式修改为双缓存模式,并更新屏幕刷新函数 /*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2022-02-01 Rudy Lo The first version
*/
#include <lvgl.h>
#include <rtthread.h>
#include <rtdevice.h>
#include "lcd_port.h"
#define DBG_TAG "lv_port_disp"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#define LV_DISP_USING_ONE_BUFFER 1
#define LV_DISP_USING_TWO_BUFFER 2
#define LV_DISP_USING_DOUBLE_BUFFERING 3
#define LV_DISP_BUFFER_MODE LV_DISP_USING_DOUBLE_BUFFERING
#define LV_DISP_BUF_SIZE LCD_BUF_SIZE
#ifndef MY_DISP_HOR_RES
// #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen width, default value 320 is used for now.
#define MY_DISP_HOR_RES LCD_WIDTH
#endif
#ifndef MY_DISP_VER_RES
// #warning Please define or replace the macro MY_DISP_HOR_RES with the actual screen height, default value 240 is used for now.
#define MY_DISP_VER_RES LCD_HEIGHT
#endif
static rt_device_t lcd_device = 0;
static struct rt_device_graphic_info info = {0};
/*A static or global variable to store the buffers*/
static lv_disp_draw_buf_t disp_buf;
/*Descriptor of a display driver*/
static lv_disp_drv_t disp_drv;
/*Static or global buffer(s). The second buffer is optional*/
//static lv_color_t buf_1[MY_DISP_HOR_RES * DISP_BUFFER_LINES];
//static lv_color_t buf_2[MY_DISP_HOR_RES * DISP_BUFFER_LINES];
/*Flush the content of the internal buffer the specific area on the display
*You can use DMA or any hardware acceleration to do this operation in the background but
*'lv_disp_flush_ready()' has to be called when finished.*/
static void disp_flush(lv_disp_drv_t * disp_drv, const lv_area_t * area, lv_color_t * color_p)
{
#if LV_DISP_BUFFER_MODE == LV_DISP_USING_DOUBLE_BUFFERING
drv_lcd_flush((uint32_t *)color_p);
#else
/* color_p is a buffer pointer; the buffer is provided by LVGL */
uint32_t x, y;
uint32_t location = 0;
// lv_color_t *fbp = (lv_color_t *)info.framebuffer;
/* 16 bit per pixel */
lv_color16_t *fbp = (lv_color16_t *)info.framebuffer;
for (y = area->y1; y < area->y2 + 1; y++)
{
for (x = area->x1; x < area->x2 + 1; x++)
{
location = x + y * info.width;
fbp[location].full = color_p->full;
color_p++;
}
}
// rt_device_control(lcd_device, RTGRAPHIC_CTRL_RECT_UPDATE, &info);
drv_lcd_flush((uint32_t *)info.framebuffer);
#endif /* LV_DISP_BUFFER_MODE */
/*IMPORTANT!!!
*Inform the graphics library that you are ready with the flushing*/
lv_disp_flush_ready(disp_drv);
}
void lv_port_disp_init(void)
{
rt_err_t result;
lv_color_t *buf_1, *buf_2;
lcd_device = rt_device_find("lcd");
if (lcd_device == RT_NULL)
{
LOG_E("find lcd error!");
return;
}
result = rt_device_open(lcd_device, 0);
if (result != RT_EOK)
{
LOG_E("open lcd error!");
return;
}
/* get framebuffer address */
result = rt_device_control(lcd_device, RTGRAPHIC_CTRL_GET_INFO, &info);
if (result != RT_EOK)
{
/* get device information failed */
LOG_E("lcd get info error!");
return;
}
RT_ASSERT(info.bits_per_pixel == 8 || info.bits_per_pixel == 16 ||
info.bits_per_pixel == 24 || info.bits_per_pixel == 32);
#if LV_DISP_BUFFER_MODE == LV_DISP_USING_ONE_BUFFER
buf_1 = rt_malloc_align(MY_DISP_HOR_RES * 200, 4);
if (buf_1 == RT_NULL) {
LOG_E("alloc disp buf 1 failed");
return;
}
/*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
lv_disp_draw_buf_init(&disp_buf, buf_1, NULL, MY_DISP_HOR_RES * 200);
#elif LV_DISP_BUFFER_MODE == LV_DISP_USING_TWO_BUFFER
buf_1 = rt_malloc_align(MY_DISP_HOR_RES * 100, 4);
if (buf_1 == RT_NULL) {
LOG_E("alloc disp buf 1 failed");
return;
}
buf_2 = rt_malloc_align(MY_DISP_HOR_RES * 100, 4);
if (buf_2 == RT_NULL) {
LOG_E("alloc disp buf 2 failed");
rt_free(buf_1);
return;
}
/*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, MY_DISP_HOR_RES * 100);
#elif LV_DISP_BUFFER_MODE == LV_DISP_USING_DOUBLE_BUFFERING
buf_1 = rt_malloc_align(LV_DISP_BUF_SIZE, 4);
if (buf_1 == RT_NULL) {
LOG_E("alloc disp buf 1 failed");
return;
}
buf_2 = rt_malloc_align(LV_DISP_BUF_SIZE, 4);
if (buf_2 == RT_NULL) {
LOG_E("alloc disp buf 2 failed");
rt_free(buf_1);
return;
}
/*Initialize `disp_buf` with the buffer(s). With only one buffer use NULL instead buf_2 */
lv_disp_draw_buf_init(&disp_buf, buf_1, buf_2, LV_DISP_BUF_SIZE);
#endif /* LV_DISP_BUFFER_MODE */
lv_disp_drv_init(&disp_drv); /*Basic initialization*/
/*Set the resolution of the display*/
disp_drv.hor_res = info.width;
disp_drv.ver_res = info.height;
/*Set a display buffer*/
disp_drv.draw_buf = &disp_buf;
/*Used to copy the buffer's content to the display*/
disp_drv.flush_cb = disp_flush;
#if LV_DISP_BUFFER_MODE == LV_DISP_USING_DOUBLE_BUFFERING
disp_drv.full_refresh = 1;
#endif /* LV_DISP_BUFFER_MODE */
/*Finally register the driver*/
lv_disp_drv_register(&disp_drv);
}
- 在驱动
bsp/stm32/libraries/HAL_Drivers/drivers/drv_lcd.c 增加新的屏幕刷新函数,原先的拷贝方式太慢了 void drv_lcd_flush(unsigned int *addr)
{
if (SCB->CCR & SCB_CCR_DC_Msk) {
SCB_CleanInvalidateDCache();
}
/* Configure the color frame buffer start address */
// LTDC_LAYER(&LtdcHandle, 0)->CFBAR &= ~(LTDC_LxCFBAR_CFBADD);
LTDC_LAYER(&LtdcHandle, 0)->CFBAR = (uint32_t)(addr);
rt_sem_take(&_lcd.lcd_lock, RT_TICK_PER_SECOND / 20);
HAL_LTDC_Relaod(&LtdcHandle, LTDC_SRCR_VBR);
}
- 修改
application/lvgl/lv_port_indev.c ,修改触摸驱动为 gt911,并更新读取函数 /*
* Copyright (c) 2006-2022, RT-Thread Development Team
*
* SPDX-License-Identifier: Apache-2.0
*
* Change Logs:
* Date Author Notes
* 2024-08-28 Morph The first version
*/
#include <lvgl.h>
#include <stdbool.h>
#include <rtdevice.h>
#include <drv_gpio.h>
#include "gt911.h"
#define DBG_TAG "lv_port_indev"
#define DBG_LVL DBG_INFO
#include <rtdbg.h>
#define TOUCH_DEVICE_NAME "touch_gt" /* Touch device name */
#define TOUCH_DEVICE_I2C_BUS "i2c1" /* SCL -> PB6(22), SDA -> PB9(25) */
//#define REST_PIN GET_PIN(A, 3) /* reset pin */
//#define USER_BUTTON_PIN GET_PIN(C, 13) /* Reserve for LV_INDEV_TYPE_BUTTON */
lv_indev_t *touch_indev;
static rt_device_t ts; /* Touch device handle, Touchscreen */
static struct rt_touch_data *read_data;
static void touchpad_read(lv_indev_drv_t *indev, lv_indev_data_t *data)
{
if (rt_device_read(ts, 0, read_data, 1) != 1)
return;
if (read_data->event == RT_TOUCH_EVENT_NONE)
return;
data->point.x = read_data->x_coordinate;
data->point.y = read_data->y_coordinate;
if (read_data->event == RT_TOUCH_EVENT_DOWN)
data->state = LV_INDEV_STATE_PR;
if (read_data->event == RT_TOUCH_EVENT_MOVE)
data->state = LV_INDEV_STATE_PR;
if (read_data->event == RT_TOUCH_EVENT_UP)
data->state = LV_INDEV_STATE_REL;
LOG_D("touch state %d x = %d, y = %d", data->state, data->point.x, data->point.y);
}
rt_err_t rt_hw_gt911_register(void)
{
struct rt_touch_config config;
config.dev_name = TOUCH_DEVICE_I2C_BUS;
rt_hw_gt911_init(TOUCH_DEVICE_NAME, &config);
ts = rt_device_find(TOUCH_DEVICE_NAME);
if (!ts) {
return -RT_ERROR;
}
read_data = (struct rt_touch_data *)rt_calloc(1, sizeof(struct rt_touch_data));
if (!read_data) {
return -RT_ENOMEM;
}
if (!rt_device_open(ts, RT_DEVICE_FLAG_RDONLY)) {
struct rt_touch_info info;
rt_device_control(ts, RT_TOUCH_CTRL_GET_INFO, &info);
rt_kprintf("type :%d\n", info.type);
rt_kprintf("vendor :%s\n", info.vendor);
rt_kprintf("point_num :%d\n", info.point_num);
rt_kprintf("range_x :%d\n", info.range_x);
rt_kprintf("range_y :%d\n", info.range_y);
return RT_EOK;
} else {
rt_kprintf("open touch device failed.\n");
return -RT_ERROR;
}
}
void lv_port_indev_init(void)
{
static lv_indev_drv_t indev_drv; /* Descriptor of a input device driver */
lv_indev_drv_init(&indev_drv); /* Basic initialization */
indev_drv.type = LV_INDEV_TYPE_POINTER; /* Touch pad is a pointer-like device */
indev_drv.read_cb = touchpad_read; /* Set your driver function */
/* Register the driver in LVGL and save the created input device object */
touch_indev = lv_indev_drv_register(&indev_drv);
/* Register touch device */
rt_hw_gt911_register();
}
- 修改
packages/gt911-latest/src/gt911.c ,中断控制脚使用宏约束下,修改触摸检测逻辑
- 编译运行,可以看到 demo 正常运行
总结
得益于 st 的通用 hal 库,在 rtthread 的驱动设计里基本都可以直接使用,只需要添加小部分的芯片寄存器差异,主要是 kconfig 和 SConscript 的修改,需要匹配对应的宏。
*按现在的配置去刷新 800480 的 lcd 整体还是偏慢,而且占用比较大的 cpu 资源,后续开启 dma2d 和 gpu2d 在对比一下。** |