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 <charlesdias.cd@outlook.com>
This commit is contained in:
Charles Dias 2024-04-13 08:06:43 -03:00 committed by Anas Nashif
parent dd15aff5fe
commit 57a8a7c350
10 changed files with 317 additions and 1 deletions

View File

@ -1,4 +1,4 @@
.. mini_stm32h743:
.. _mini_stm32h743:
WeAct Studio MiniSTM32H743 Core Board
#####################################

View File

@ -15,4 +15,5 @@ supported:
- backup_sram
- watchdog
- qspi
- video
vendor: weact

View File

@ -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})

View File

@ -0,0 +1,22 @@
# VIDEO resolution settings
# Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com>
# SPDX-License-Identifier: Apache-2.0
source "Kconfig.zephyr"
config VIDEO_WIDTH
int "Define the width of the video"
default 320
config VIDEO_HEIGHT
int "Define the height of the video"
default 240
config VIDEO_HFLIP
bool "Horizontal flip"
default n
config VIDEO_VFLIP
bool "Vertical flip"
default n

View File

@ -0,0 +1,73 @@
.. zephyr:code-sample:: video-capture-to-lvgl
:name: Video capture to LVGL
:relevant-api: video_interface
Capture video frames and display them on an LCD using LVGL.
Description
***********
The application uses the :ref:`Video API <video_api>` to retrieve video frames from
a video capture device, write a frame count message to the console, and then send
the frame to an LCD display.
Requirements
************
This sample requires a supported :ref:`video capture device <video_api>` (e.g., a camera)
and a :ref:`display <display_api>`.
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] <inf> main: - Device name: dcmi@48020000
[00:00:02.779,000] <inf> main: - Capabilities:
[00:00:02.779,000] <inf> main: RGBP width [160; 160; 0] height [120; 120; 0]
[00:00:02.779,000] <inf> main: RGBP width [176; 176; 0] height [144; 144; 0]
[00:00:02.780,000] <inf> main: RGBP width [240; 240; 0] height [160; 160; 0]
[00:00:02.780,000] <inf> main: RGBP width [320; 320; 0] height [240; 240; 0]
[00:00:02.780,000] <inf> main: RGBP width [352; 352; 0] height [288; 288; 0]
[00:00:02.780,000] <inf> main: RGBP width [640; 640; 0] height [480; 480; 0]
[00:00:02.780,000] <inf> main: RGBP width [800; 800; 0] height [600; 600; 0]
[00:00:02.780,000] <inf> main: RGBP width [1024; 1024; 0] height [768; 768; 0]
[00:00:02.780,000] <inf> main: RGBP width [1280; 1280; 0] height [1024; 1024; 0]
[00:00:02.780,000] <inf> main: RGBP width [1600; 1600; 0] height [1200; 1200; 0]
[00:00:02.780,000] <inf> main: JPEG width [160; 160; 0] height [120; 120; 0]
[00:00:02.780,000] <inf> main: JPEG width [176; 176; 0] height [144; 144; 0]
[00:00:02.780,000] <inf> main: JPEG width [240; 240; 0] height [160; 160; 0]
[00:00:02.780,000] <inf> main: JPEG width [320; 320; 0] height [240; 240; 0]
[00:00:02.780,000] <inf> main: JPEG width [352; 352; 0] height [288; 288; 0]
[00:00:02.780,000] <inf> main: JPEG width [640; 640; 0] height [480; 480; 0]
[00:00:02.780,000] <inf> main: JPEG width [800; 800; 0] height [600; 600; 0]
[00:00:02.780,000] <inf> main: JPEG width [1024; 1024; 0] height [768; 768; 0]
[00:00:02.780,000] <inf> main: JPEG width [1280; 1280; 0] height [1024; 1024; 0]
[00:00:02.780,000] <inf> main: JPEG width [1600; 1600; 0] height [1200; 1200; 0]
[00:00:02.852,000] <inf> main: - Format: RGBP 160x120 320
[00:00:02.854,000] <inf> main: - Capture started
References
**********
.. _WeAct Studio STM32H743: https://github.com/WeActStudio/MiniSTM32H7xx

View File

@ -0,0 +1,11 @@
#
# Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com>
#
# SPDX-License-Identifier: Apache-2.0
#
CONFIG_LOG_BUFFER_SIZE=2048
CONFIG_VIDEO_BUFFER_POOL_SZ_MAX=102400
CONFIG_VIDEO_WIDTH=160
CONFIG_VIDEO_HEIGHT=120

View File

@ -0,0 +1,10 @@
/*
* Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com>
*
* SPDX-License-Identifier: Apache-2.0
*
*/
&st7735r_160x80 {
madctl = <184>; /* Rotate the image 180 degrees. */
};

View File

@ -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

View File

@ -0,0 +1,12 @@
sample:
name: Video capture to LVGL
tests:
sample.video.capture_to_lvgl:
tags:
- video
- samples
platform_allow:
- mini_stm32h743
depends_on: video
integration_platforms:
- mini_stm32h743

View File

@ -0,0 +1,162 @@
/*
* Copyright (c) 2024 Charles Dias <charlesdias.cd@outlook.com>
*
* SPDX-License-Identifier: Apache-2.0
*/
#include <zephyr/kernel.h>
#include <zephyr/device.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/video.h>
#include <lvgl.h>
#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
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;
}
}
}