From b91521a74dec69eba25d2121f179eab682c9b6fc Mon Sep 17 00:00:00 2001 From: Charles Dias Date: Sat, 13 Apr 2024 08:06:43 -0300 Subject: [PATCH] samples: drivers: video: add capture to lvgl sample Add sample application to capture an image frame from a camera and send it for display on LCD via the LVGL library. Signed-off-by: Charles Dias --- boards/weact/mini_stm32h743/doc/index.rst | 2 +- .../video/capture_to_lvgl/CMakeLists.txt | 8 + samples/drivers/video/capture_to_lvgl/Kconfig | 22 +++ .../drivers/video/capture_to_lvgl/README.rst | 74 ++++++++ .../boards/mini_stm32h743.conf | 12 ++ .../boards/mini_stm32h743.overlay | 31 ++++ .../drivers/video/capture_to_lvgl/prj.conf | 17 ++ .../drivers/video/capture_to_lvgl/sample.yaml | 10 ++ .../drivers/video/capture_to_lvgl/src/main.c | 162 ++++++++++++++++++ 9 files changed, 337 insertions(+), 1 deletion(-) create mode 100644 samples/drivers/video/capture_to_lvgl/CMakeLists.txt create mode 100644 samples/drivers/video/capture_to_lvgl/Kconfig create mode 100644 samples/drivers/video/capture_to_lvgl/README.rst create mode 100644 samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.conf create mode 100644 samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.overlay create mode 100644 samples/drivers/video/capture_to_lvgl/prj.conf create mode 100644 samples/drivers/video/capture_to_lvgl/sample.yaml create mode 100644 samples/drivers/video/capture_to_lvgl/src/main.c diff --git a/boards/weact/mini_stm32h743/doc/index.rst b/boards/weact/mini_stm32h743/doc/index.rst index 4a96538ac1b23f0..fb84bbf802675d7 100644 --- a/boards/weact/mini_stm32h743/doc/index.rst +++ b/boards/weact/mini_stm32h743/doc/index.rst @@ -1,4 +1,4 @@ -.. mini_stm32h743: +.. _mini_stm32h743: WeAct Studio MiniSTM32H743 Core Board ##################################### diff --git a/samples/drivers/video/capture_to_lvgl/CMakeLists.txt b/samples/drivers/video/capture_to_lvgl/CMakeLists.txt new file mode 100644 index 000000000000000..8863a2350f46072 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/CMakeLists.txt @@ -0,0 +1,8 @@ +# SPDX-License-Identifier: Apache-2.0 + +cmake_minimum_required(VERSION 3.20.0) +find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE}) +project(video_capture) + +FILE(GLOB app_sources src/*.c) +target_sources(app PRIVATE ${app_sources}) diff --git a/samples/drivers/video/capture_to_lvgl/Kconfig b/samples/drivers/video/capture_to_lvgl/Kconfig new file mode 100644 index 000000000000000..9d4ee75e8cd3ef3 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/Kconfig @@ -0,0 +1,22 @@ +# VIDEO resolution settings + +# Copyright (c) 2024 Charles Dias +# SPDX-License-Identifier: Apache-2.0 + +source "Kconfig.zephyr" + +config VIDEO_WIDTH + int "Define the width of the video" + default 160 + +config VIDEO_HEIGHT + int "Define the height of the video" + default 120 + +config VIDEO_HFLIP + bool "Horizontal flip" + default n + +config VIDEO_VFLIP + bool "Vertical flip" + default n diff --git a/samples/drivers/video/capture_to_lvgl/README.rst b/samples/drivers/video/capture_to_lvgl/README.rst new file mode 100644 index 000000000000000..9659bb0ed75ea85 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/README.rst @@ -0,0 +1,74 @@ +.. zephyr:code-sample:: video-capture-to-lvgl + :name: Video capture to LVGL + :relevant-api: video_interface + + This sample application demonstrates how to use the video API to retrieve video + frames from a capture device and display them on an LCD via the LVGL library. + +Description +*********** + +The application uses the :ref:`Video API ` to retrieve video frames from +a video capture device, writes a frame count message to the console, and then sends +the frame to an LCD display. + +Requirements +************ + +This sample requires a supported :ref:`video capture device ` (e.g., a camera) +and a :ref:`display `. + +Wiring +****** + +On the `WeAct Studio STM32H743`_, connect the OV2640 camera module and the 0.96" ST7735 +TFT LCD display. Connect a USB cable from a host to the micro USB-C connector on the +board to receive console output messages. + +Building and Running +******************** + +For :ref:`mini_stm32h743`, build this sample application with the following commands: + +.. zephyr-app-commands:: + :zephyr-app: samples/drivers/video/capture_to_lvgl/ + :board: mini_stm32h743 + :shield: weact_ministm32h7xx_ov2640 + :goals: build flash + :gen-args: -DCONFIG_BOOT_DELAY=2000 + :compact: + +Sample Output +============= + +.. code-block:: console + + [00:00:02.779,000] main: - Device name: dcmi@48020000 + [00:00:02.779,000] main: - Capabilities: + [00:00:02.779,000] main: RGBP width [160; 160; 0] height [120; 120; 0] + [00:00:02.779,000] main: RGBP width [176; 176; 0] height [144; 144; 0] + [00:00:02.780,000] main: RGBP width [240; 240; 0] height [160; 160; 0] + [00:00:02.780,000] main: RGBP width [320; 320; 0] height [240; 240; 0] + [00:00:02.780,000] main: RGBP width [352; 352; 0] height [288; 288; 0] + [00:00:02.780,000] main: RGBP width [640; 640; 0] height [480; 480; 0] + [00:00:02.780,000] main: RGBP width [800; 800; 0] height [600; 600; 0] + [00:00:02.780,000] main: RGBP width [1024; 1024; 0] height [768; 768; 0] + [00:00:02.780,000] main: RGBP width [1280; 1280; 0] height [1024; 1024; 0] + [00:00:02.780,000] main: RGBP width [1600; 1600; 0] height [1200; 1200; 0] + [00:00:02.780,000] main: JPEG width [160; 160; 0] height [120; 120; 0] + [00:00:02.780,000] main: JPEG width [176; 176; 0] height [144; 144; 0] + [00:00:02.780,000] main: JPEG width [240; 240; 0] height [160; 160; 0] + [00:00:02.780,000] main: JPEG width [320; 320; 0] height [240; 240; 0] + [00:00:02.780,000] main: JPEG width [352; 352; 0] height [288; 288; 0] + [00:00:02.780,000] main: JPEG width [640; 640; 0] height [480; 480; 0] + [00:00:02.780,000] main: JPEG width [800; 800; 0] height [600; 600; 0] + [00:00:02.780,000] main: JPEG width [1024; 1024; 0] height [768; 768; 0] + [00:00:02.780,000] main: JPEG width [1280; 1280; 0] height [1024; 1024; 0] + [00:00:02.780,000] main: JPEG width [1600; 1600; 0] height [1200; 1200; 0] + [00:00:02.852,000] main: - Format: RGBP 160x120 320 + [00:00:02.854,000] main: - Capture started + +References +********** + +.. _WeAct Studio STM32H743: https://github.com/WeActStudio/MiniSTM32H7xx diff --git a/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.conf b/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.conf new file mode 100644 index 000000000000000..68b891411f34f35 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.conf @@ -0,0 +1,12 @@ +# +# Copyright (c) 2024 Charles Dias +# +# SPDX-License-Identifier: Apache-2.0 +# + +CONFIG_CLOCK_STM32_MCO1_SRC_HSI48=y +CONFIG_CLOCK_STM32_MCO1_DIV=4 + +CONFIG_LOG_BUFFER_SIZE=2048 + +CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=102400 diff --git a/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.overlay b/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.overlay new file mode 100644 index 000000000000000..5e009651012d510 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.overlay @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 Charles Dias + * + * SPDX-License-Identifier: Apache-2.0 + * + */ + +&pll { + div-m = <5>; + mul-n = <96>; + div-p = <2>; + div-q = <2>; + div-r = <2>; + clocks = <&clk_hse>; + status = "okay"; +}; + +&rcc { + clocks = <&pll>; + clock-frequency = ; + d1cpre = <1>; + hpre = <1>; + d1ppre = <2>; + d2ppre1 = <2>; + d2ppre2 = <2>; + d3ppre = <2>; +}; + +&st7735r_160x80 { + madctl = <184>; /* Rotate the image 180 degrees. */ +}; diff --git a/samples/drivers/video/capture_to_lvgl/prj.conf b/samples/drivers/video/capture_to_lvgl/prj.conf new file mode 100644 index 000000000000000..bd8407787cfd284 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/prj.conf @@ -0,0 +1,17 @@ +CONFIG_VIDEO=y +CONFIG_VIDEO_SW_GENERATOR=y + +CONFIG_PRINTK=y +CONFIG_LOG=y + +CONFIG_MAIN_STACK_SIZE=4096 + +CONFIG_DISPLAY=y +CONFIG_DISPLAY_LOG_LEVEL_ERR=y + +CONFIG_LVGL=y +CONFIG_LV_CONF_MINIMAL=y +CONFIG_LV_MEM_CUSTOM=y +CONFIG_LV_USE_IMG=y +CONFIG_LV_Z_MEM_POOL_SIZE=16384 +CONFIG_LV_USE_PERF_MONITOR=y diff --git a/samples/drivers/video/capture_to_lvgl/sample.yaml b/samples/drivers/video/capture_to_lvgl/sample.yaml new file mode 100644 index 000000000000000..78b25b64170bbaf --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/sample.yaml @@ -0,0 +1,10 @@ +sample: + name: Video capture to LVGL +tests: + sample.video.capture_to_lvgl: + build_only: true + tags: + - video + platform_allow: + - mini_stm32h743 + depends_on: video diff --git a/samples/drivers/video/capture_to_lvgl/src/main.c b/samples/drivers/video/capture_to_lvgl/src/main.c new file mode 100644 index 000000000000000..11dfd5a39ee5d58 --- /dev/null +++ b/samples/drivers/video/capture_to_lvgl/src/main.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2024 Charles Dias + * + * SPDX-License-Identifier: Apache-2.0 + */ + +#include +#include +#include +#include +#include + +#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL +#include +LOG_MODULE_REGISTER(main); + +#define VIDEO_DEV_SW "VIDEO_SW_GENERATOR" + +int main(void) +{ + struct video_buffer *buffers[2], *vbuf; + const struct device *display_dev; + struct video_format fmt; + struct video_caps caps; + const struct device *video_dev; + size_t bsize; + int i = 0; + + display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display)); + if (!device_is_ready(display_dev)) { + LOG_ERR("Device not ready, aborting test"); + return 0; + } + +#if DT_HAS_CHOSEN(zephyr_camera) + video_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_camera)); + if (!device_is_ready(video_dev)) { + LOG_ERR("%s device is not ready", video_dev->name); + return 0; + } +#else + video_dev = device_get_binding(VIDEO_DEV_SW); + if (video_dev == NULL) { + LOG_ERR("%s device not found", VIDEO_DEV_SW); + return 0; + } +#endif + + LOG_INF("- Device name: %s", video_dev->name); + + /* Get capabilities */ + if (video_get_caps(video_dev, VIDEO_EP_OUT, &caps)) { + LOG_ERR("Unable to retrieve video capabilities"); + return 0; + } + + LOG_INF("- Capabilities:"); + while (caps.format_caps[i].pixelformat) { + const struct video_format_cap *fcap = &caps.format_caps[i]; + /* four %c to string */ + LOG_INF(" %c%c%c%c width [%u; %u; %u] height [%u; %u; %u]", + (char)fcap->pixelformat, (char)(fcap->pixelformat >> 8), + (char)(fcap->pixelformat >> 16), (char)(fcap->pixelformat >> 24), + fcap->width_min, fcap->width_max, fcap->width_step, fcap->height_min, + fcap->height_max, fcap->height_step); + i++; + } + + /* Get default/native format */ + if (video_get_format(video_dev, VIDEO_EP_OUT, &fmt)) { + LOG_ERR("Unable to retrieve video format"); + return 0; + } + + /* Set format */ + fmt.width = CONFIG_VIDEO_WIDTH; + fmt.height = CONFIG_VIDEO_HEIGHT; + fmt.pitch = fmt.width * 2; + fmt.pixelformat = VIDEO_PIX_FMT_RGB565; + + if (video_set_format(video_dev, VIDEO_EP_OUT, &fmt)) { + LOG_ERR("Unable to set up video format"); + return 0; + } + + LOG_INF("- Format: %c%c%c%c %ux%u %u", (char)fmt.pixelformat, (char)(fmt.pixelformat >> 8), + (char)(fmt.pixelformat >> 16), (char)(fmt.pixelformat >> 24), fmt.width, fmt.height, + fmt.pitch); + + /* Size to allocate for each buffer */ + bsize = fmt.pitch * fmt.height; + + /* Alloc video buffers and enqueue for capture */ + for (i = 0; i < ARRAY_SIZE(buffers); i++) { + buffers[i] = video_buffer_alloc(bsize); + if (buffers[i] == NULL) { + LOG_ERR("Unable to alloc video buffer"); + return 0; + } + + video_enqueue(video_dev, VIDEO_EP_OUT, buffers[i]); + } + +#ifdef CONFIG_VIDEO_HFLIP + /* Video flip image horizontally */ + if (video_set_ctrl(video_dev, VIDEO_CID_HFLIP, (void *)1)) { + LOG_ERR("Unable to set video control (HFLIP)"); + return 0; + } +#endif + +#ifdef CONFIG_VIDEO_VFLIP + /* Video flip image vertically */ + if (video_set_ctrl(video_dev, VIDEO_CID_VFLIP, (void *)1)) { + LOG_ERR("Unable to set video control (VFLIP)"); + return 0; + } +#endif + + /* Start video capture */ + if (video_stream_start(video_dev)) { + LOG_ERR("Unable to start capture (interface)"); + return 0; + } + + display_blanking_off(display_dev); + + const lv_img_dsc_t video_img = { + .header.always_zero = 0, + .header.w = CONFIG_VIDEO_WIDTH, + .header.h = CONFIG_VIDEO_HEIGHT, + .data_size = CONFIG_VIDEO_WIDTH * CONFIG_VIDEO_HEIGHT * sizeof(lv_color_t), + .header.cf = LV_IMG_CF_TRUE_COLOR, + .data = (const uint8_t *)buffers[0]->buffer, + }; + + lv_obj_t *screen = lv_img_create(lv_scr_act()); + + LOG_INF("- Capture started"); + + /* Grab video frames */ + while (1) { + int err; + + err = video_dequeue(video_dev, VIDEO_EP_OUT, &vbuf, K_FOREVER); + if (err) { + LOG_ERR("Unable to dequeue video buf"); + return 0; + } + + lv_img_set_src(screen, &video_img); + lv_obj_align(screen, LV_ALIGN_BOTTOM_LEFT, 0, 0); + + lv_task_handler(); + + err = video_enqueue(video_dev, VIDEO_EP_OUT, vbuf); + if (err) { + LOG_ERR("Unable to requeue video buf"); + return 0; + } + } +}