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:
parent
dd15aff5fe
commit
57a8a7c350
@ -1,4 +1,4 @@
|
||||
.. mini_stm32h743:
|
||||
.. _mini_stm32h743:
|
||||
|
||||
WeAct Studio MiniSTM32H743 Core Board
|
||||
#####################################
|
||||
|
||||
@ -15,4 +15,5 @@ supported:
|
||||
- backup_sram
|
||||
- watchdog
|
||||
- qspi
|
||||
- video
|
||||
vendor: weact
|
||||
|
||||
8
samples/drivers/video/capture_to_lvgl/CMakeLists.txt
Normal file
8
samples/drivers/video/capture_to_lvgl/CMakeLists.txt
Normal 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})
|
||||
22
samples/drivers/video/capture_to_lvgl/Kconfig
Normal file
22
samples/drivers/video/capture_to_lvgl/Kconfig
Normal 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
|
||||
73
samples/drivers/video/capture_to_lvgl/README.rst
Normal file
73
samples/drivers/video/capture_to_lvgl/README.rst
Normal 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
|
||||
@ -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
|
||||
@ -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. */
|
||||
};
|
||||
17
samples/drivers/video/capture_to_lvgl/prj.conf
Normal file
17
samples/drivers/video/capture_to_lvgl/prj.conf
Normal 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
|
||||
12
samples/drivers/video/capture_to_lvgl/sample.yaml
Normal file
12
samples/drivers/video/capture_to_lvgl/sample.yaml
Normal 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
|
||||
162
samples/drivers/video/capture_to_lvgl/src/main.c
Normal file
162
samples/drivers/video/capture_to_lvgl/src/main.c
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user