Skip to content

Commit

Permalink
samples: drivers: video: add capture to lvgl sample
Browse files Browse the repository at this point in the history
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 <[email protected]>
  • Loading branch information
CharlesDias committed Sep 16, 2024
1 parent ca48767 commit b91521a
Show file tree
Hide file tree
Showing 9 changed files with 337 additions and 1 deletion.
2 changes: 1 addition & 1 deletion boards/weact/mini_stm32h743/doc/index.rst
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
.. mini_stm32h743:
.. _mini_stm32h743:

WeAct Studio MiniSTM32H743 Core Board
#####################################
Expand Down
8 changes: 8 additions & 0 deletions samples/drivers/video/capture_to_lvgl/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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 changes: 22 additions & 0 deletions samples/drivers/video/capture_to_lvgl/Kconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# VIDEO resolution settings

# Copyright (c) 2024 Charles Dias <[email protected]>
# 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
74 changes: 74 additions & 0 deletions samples/drivers/video/capture_to_lvgl/README.rst
Original file line number Diff line number Diff line change
@@ -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 <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 <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
12 changes: 12 additions & 0 deletions samples/drivers/video/capture_to_lvgl/boards/mini_stm32h743.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#
# Copyright (c) 2024 Charles Dias <[email protected]>
#
# 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
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright (c) 2024 Charles Dias <[email protected]>
*
* 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 = <DT_FREQ_M(240)>;
d1cpre = <1>;
hpre = <1>;
d1ppre = <2>;
d2ppre1 = <2>;
d2ppre2 = <2>;
d3ppre = <2>;
};

&st7735r_160x80 {
madctl = <184>; /* Rotate the image 180 degrees. */
};
17 changes: 17 additions & 0 deletions samples/drivers/video/capture_to_lvgl/prj.conf
Original file line number Diff line number Diff line change
@@ -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
10 changes: 10 additions & 0 deletions samples/drivers/video/capture_to_lvgl/sample.yaml
Original file line number Diff line number Diff line change
@@ -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
162 changes: 162 additions & 0 deletions samples/drivers/video/capture_to_lvgl/src/main.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
/*
* Copyright (c) 2024 Charles Dias <[email protected]>
*
* 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;
}
}
}

0 comments on commit b91521a

Please sign in to comment.