0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

Zephyr(その2:ディスプレイ)

0
Posted at

ディスプレイにテキスト表示

深い理由はないが、RTOSとしてのZephyr確認する前の準備として、何かしらのテキストをディスプレイに表示したくなった。本稿はZephyrでディスプレイにテキスト表示する時のメモである。ZephyrでのBuild環境については、その1参照。

テキスト表示環境

まずは、所持するディスプレイ付きマイコンボードおよびサンプルコードをトライ&エラー。結果としては、下記を利用。

サンプルコード実行

上記サンプルコードを実行すると、

となり、文字が裏返しとなってしまった。これについて、調べる(AIさんに問い合わせ)と、用いているディスプレイドライバili9341を修正する必要があるとのこと。

zephyr/drivers/display/display_ili9xxx.c
<>

        } else if (config->quirks->cmd_set == CMD_SET_2) {
                if (orientation == DISPLAY_ORIENTATION_NORMAL) {
                        /* Do nothing */
                } else if (orientation == DISPLAY_ORIENTATION_ROTATED_90) {
                        //この行がオリジナル data->madctl |= ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MY;
                        data->madctl |= ILI9XXX_MADCTL_MV;
                } else if (orientation == DISPLAY_ORIENTATION_ROTATED_180) {
                        data->madctl |= ILI9XXX_MADCTL_MY | ILI9XXX_MADCTL_MX |
                                   ILI9XXX_MADCTL_ML;
                } else if (orientation == DISPLAY_ORIENTATION_ROTATED_270) {
                        data->madctl |= ILI9XXX_MADCTL_MV | ILI9XXX_MADCTL_MX;
                }
        }
<>

上記のように、ローテーション90度のところを修正。後述するDevice Treeファイルの記述内容とセットで、正しく表示されるようだ。

本題(サンプルプロジェクトの修正)

Zephyrプロジェクト構成

これについては、下記サイトの説明がわかりやすい。

で、今回のプロジェクト構成は下記となっている。

% tree                   
.
├── CMakeLists.txt
├── prj.conf
├── src
│   └── main.c
└── wio_terminal.overlay

Zephyrの最小構成としては、CMakeLists.txt、src/main.cとなる。ここでは、ディスプレイにテキストを表示するため、prj.confおよびwio_terminal.overlayが存在している。

ファイル

CMakeLists.txt

CMakeLists.txt
cmake_minimum_required(VERSION 3.20.0)

find_package(Zephyr REQUIRED HINTS $ENV{ZEPHYR_BASE})
project(DisplayText)

FILE(GLOB app_sources src/*.c)
target_sources(app PRIVATE ${app_sources})

ここはお決まりの記法のようだ。先述したサイトの説明にお任せ。

prj.conf

prj.conf
CONFIG_LV_Z_MEM_POOL_SIZE=16384
CONFIG_MAIN_STACK_SIZE=4096

CONFIG_DISPLAY=y
CONFIG_DISPLAY_LOG_LEVEL_ERR=y

CONFIG_LVGL=y
CONFIG_LV_FONT_MONTSERRAT_20=y

サンプルコードにあったprj.confを、トライ&エラーにより最小化したものである。なお、表示するフォントサイズを大きくするため、「CONFIG_LV_FONT_MONTSERRAT_20=y」を定義している。

wio_terminal.overlay

Device Treeファイル。

wio_terminal.overlay
&ili9341 {
    status = "okay";
    rotation = <90>;
    width = <320>;
    height = <320>;
};

ここはAIさんとともに、トライ&エラーした内容。先ほどのdisplay_ili9xxx.cとともに、rotationに90度を、widthおよびheightに適宜値を指定。なお、ファイル名は、westコマンドのオプション「-b」で指定するボード名(ここでは「wio_terminal」)に拡張子「.overlay」をつけたものと一致させる。

src/main.c

サンプルコードをもとに、AIさんに頼りながら作成。動作内容は下記。

  • 文字列”Bonjour”が表示
  • 1秒ごとにインクリメントするカウンタが表示
    • 左ボタン押下でカウンタリセット
    • 中央ボタン押下でカウンタが100プラス
    • 右ボタン押下でカウンタが1000プラス
main.c
#include <zephyr/device.h>
#include <zephyr/devicetree.h>
#include <zephyr/drivers/display.h>
#include <zephyr/drivers/gpio.h>
#include <lvgl.h>
#include <stdio.h>
#include <string.h>
#include <zephyr/kernel.h>
#include <lvgl_input_device.h>

#define LOG_LEVEL CONFIG_LOG_DEFAULT_LEVEL
#include <zephyr/logging/log.h>
LOG_MODULE_REGISTER(app);

static uint32_t count;

static void call_arg_unused(const struct device *port,
                                struct gpio_callback *cb,
                                uint32_t pins)
{
        ARG_UNUSED(port);
        ARG_UNUSED(cb);
        ARG_UNUSED(pins);
}

static void button0_isr_callback(const struct device *port,
                                struct gpio_callback *cb,
                                uint32_t pins)
{
        call_arg_unused(port, cb, pins);
        count = 0;
        printk("Button0 Pressed\n");
}

static void button1_isr_callback(const struct device *port,
                                struct gpio_callback *cb,
                                uint32_t pins)
{
        call_arg_unused(port, cb, pins);
        count += 100*100;
        printk("Button1 Pressed\n");
}

static void button2_isr_callback(const struct device *port,
                                struct gpio_callback *cb,
                                uint32_t pins)
{
        call_arg_unused(port, cb, pins);
        count += 1000*100;
        printk("Button2 Pressed\n");
}

enum btn {
        BTN0,
        BTN1,
        BTN2,
        BTN_MAX,
};

static struct gpio_callback button0_callback;
static struct gpio_callback button1_callback;
static struct gpio_callback button2_callback;

typedef void (*button_isr_t)(const struct device *port, struct gpio_callback *cb, uint32_t pins);

struct btn_gpio {
        struct gpio_dt_spec dts;
        struct gpio_callback *cb;
        button_isr_t isr_handler;
};

static struct btn_gpio buttons[BTN_MAX] = {
        [BTN0] = { 
                .dts = GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw0), gpios, {0}),
                .cb = &button0_callback, 
                .isr_handler = button0_isr_callback
        },
        [BTN1] = { 
                .dts = GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw1), gpios, {0}), 
                .cb = &button1_callback, 
                .isr_handler = button1_isr_callback
        },
        [BTN2] = { 
                .dts = GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw2), gpios, {0}), 
                .cb = &button2_callback, 
                .isr_handler = button2_isr_callback
        },
};

static void setup_buttons(void)
{
        int err;

        for (int i = 0; i < BTN_MAX; i++) {
                if (!gpio_is_ready_dt(&buttons[i].dts)) {
                        LOG_ERR("Button %d is not ready", i);
                        continue;
                }

                err = gpio_pin_configure_dt(&buttons[i].dts, GPIO_INPUT);
                if (err) {
                        LOG_ERR("Failed to configure Button %d: %d", i, err);
                        continue;
                }

                gpio_init_callback(buttons[i].cb, buttons[i].isr_handler, BIT(buttons[i].dts.pin));

                err = gpio_add_callback(buttons[i].dts.port, buttons[i].cb);
                if (err) {
                        LOG_ERR("Failed to add callback for Button %d: %d", i, err);
                        continue;
                }

                err = gpio_pin_interrupt_configure_dt(&buttons[i].dts, GPIO_INT_EDGE_TO_ACTIVE);
                if (err) {
                        LOG_ERR("Failed to enable interrupt for Button %d: %d", i, err);
                        continue;
                }
        }
}

int main(void)
{
        char count_str[11] = {0};
        const struct device *display_dev;
        lv_obj_t *hello_world_label;
        lv_obj_t *count_label;
        int ret;

        display_dev = DEVICE_DT_GET(DT_CHOSEN(zephyr_display));
        if (!device_is_ready(display_dev)) {
                LOG_ERR("Device not ready, aborting test");
                return 0;
        }

        setup_buttons();

        LV_FONT_DECLARE(lv_font_montserrat_20);

        hello_world_label = lv_label_create(lv_screen_active());
        lv_label_set_text(hello_world_label, "Bonjour");
        lv_obj_set_style_text_font(hello_world_label, &lv_font_montserrat_20, 0);
        lv_obj_align(hello_world_label, LV_ALIGN_CENTER, 0, -30);

        count_label = lv_label_create(lv_screen_active());
        lv_obj_set_style_text_font(count_label, &lv_font_montserrat_20, 0);
        lv_obj_align(count_label, LV_ALIGN_CENTER, 0, 30);

        lv_timer_handler();
        ret = display_blanking_off(display_dev); // Must
        if (ret < 0 && ret != -ENOSYS) {
                LOG_ERR("Failed to turn blanking off (error %d)", ret);
                return 0;
        }

        while (1) {
                if ((count % 100) == 0U) {
                        sprintf(count_str, "%d", count/100U);
                        lv_label_set_text(count_label, count_str);
                        //printk("Uptime: %u seconds\n", count / 100);
                }
                lv_timer_handler();
                ++count;
                k_sleep(K_MSEC(10));
        }
}

内容的にはサンプルコードとほぼ同じ。補足説明。

  • 左ボタン
    • GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw0)
    • button0_isr_callback():押下時の割り込みルーチン
  • 中央ボタン
    • GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw1)
    • button1_isr_callback():押下時の割り込みルーチン
  • 右ボタン
    • GPIO_DT_SPEC_GET_OR(DT_ALIAS(sw2)
    • button2_isr_callback():押下時の割り込みルーチン
  • setup_buttons():ボタン処理初期化
  • LV_FONT_DECLARE(lv_font_montserrat_20):20ポイントフォントの適用(prj.conf参照)
  • テキスト表示
    • lv_label_create():テキストラベル作成
    • lv_label_set_text():テキストセット
    • lv_obj_set_style_text_font():フォント指定
    • lv_obj_align():配置指定

なお、ボタンチャタリングは考慮していない。

Build、書き込み、実行

まずはBuild。

(.venv) name@Ahost display % west build -t menuconfig -b wio_terminal
-- west build: generating a build system
Loading Zephyr default modules (Zephyr base).
<略>

Converted to uf2, output size: 566784, start address: 0x4000
Wrote 566784 bytes to zephyr.uf2

書き込み時には、Wio Terminalの横のスイッチを2回すばやく動かす必要がある。その後、下記コマンドを実行。

(.venv) name@Ahost display %  west flash                     
-- west flash: rebuilding
ninja: no work to do.
-- west flash: using runner bossac
<略>

Done in 7.058 seconds
Set boot flash true
(.venv) name@Ahost display % 

書き込み終了後の実行時の様子。

正常.jpg

文字が見える(裏返しではなく)。

シリアルターミナル

ボタンを押した時のログ。

$ screen /dev/tty.usbmodem1101 115200

*** Booting Zephyr OS build v4.4.0-1647-g29d97bdaf61b ***
Button2 Pressed
Button0 Pressed
Button1 Pressed

おまけ

ボタンの位置変更

Device Treeファイルの定義でボタン操作を変更できる。例えば、

wio_terminal.overlay
/ {
    aliases {
        sw0 = &user_button_1; 
        sw1 = &user_button_0; 
    };
};

&ili9341 {
    status = "okay";
    rotation = <90>;
    width = <320>;
    height = <320>;
};

とすると、左ボタン(user_button_0)と中央ボタン(user_button_1)との操作を入れ替えることができる。

Kconfig

% west build -t menuconfig -b wio_terminal

上記コマンドにより、Build時のオプションを選択できる。

west_build_-t_menuconfig_-b_wio_terminal1.png

今回は未使用であったが、Kconfigに下記内容を記述したとする。

source "Kconfig.zephyr"

mainmenu "Text Sample"

config RESET_COUNTER_SW0
	bool "Reset counter when sw0 is pressed"
	depends on $(dt_alias_enabled,sw0)
	select GPIO
	default y

すると、先ほどのコマンドでは、下記が表示される。

west_build_-t_menuconfig_-b_wio_terminal2.png

Buildオプションとして、上記内容が表示される。また、選択されている場合、ソースコード上で、下記が内容が有効化される(含まれる)。

#ifdef CONFIG_RESET_COUNTER_SW0

終わりに

次は、RTOSとしての内容をトライ。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?