Zenn
🎎

【Wio Terminal/LvGL】スイッチ操作でスライダーを動かす

2025/02/24に公開

タッチスクリーンでなくとも画面操作できる

以前、Seeed_Arduino_LvGLの基本的な使い方を調べるも様々な情報に踊らされ、瞋恚の末竟に癲狂したのだが、それはさて置き使えるようにはなったのだった。

https://zenn.dev/amenaruya/articles/b3e7a992eec368

しかしWioWio TerminalTerminalLvGLLvGLとの相性は全しというものではなく、タッチスクリーンではないために操作には工夫を要する。その工夫ができたのでここに記す。

プログラム

https://docs.lvgl.io/7.11/widgets/slider.html#set-value-with-slider

主としてこの例を参考に、プログラムを作成した。GitHubGitHubにも掲載する。

https://github.com/amenaruya/wio_terminal_with_lvgl_5switch_slider/tree/main

WioWio TerminalTerminalには青いスイッチがあり、四方へ倒せる他、押し込むこともできる。

Wio Terminal外観
引用:https://www.switch-science.com/products/6360

そしてこれらを入力として扱うことができる。プログラムの概要はコメントを見よ。

value-slider.ino
#include <lvgl.h>
#include <TFT_eSPI.h>

/* tick period */
constexpr uint16_t      TICK_PERIOD = 5;

/* TFT LCD */
static TFT_eSPI         TFT;
/* display buffer */
static lv_disp_buf_t    DISP_BUF;
/* color buffer */
static lv_color_t       COLOR_BUF[LV_HOR_RES_MAX * 10];

/* input device */
static lv_indev_t*      INPUT_DEVICE;
/* input deviceの影響を被るグループ */
static lv_group_t*      INPUT_GROUP;
/* slider */
static lv_obj_t*        SLIDER;
/* input deviceの値を示すラベル */
static lv_obj_t*        LABEL_SLIDERS_VALUE;

/* 画面初期化 */
void display_flush(
    lv_disp_drv_t*      disp,
    const lv_area_t*    area,
    lv_color_t*         color_p
);

/* Reading input device (嘘) */
bool read_encoder(
    lv_indev_drv_t*     indev,
    lv_indev_data_t*    data
);

/* system tick */
static void tick_handler();

/* 設定 */
void backend_setup();

/* slider event callback */
static void slider_event_cb(
    lv_obj_t*           SLIDER,
    lv_event_t          event
);

/* 画面要素 */
void set_contents();

/* 5way switch */
void wio_joy_handler();

void set_contents() {
    /* sliderの設定 */
    SLIDER = lv_slider_create(lv_scr_act(), NULL);
    /* 幅 */
    lv_obj_set_width(SLIDER, LV_DPI * 2);
    /* 画面中央に配置する */
    lv_obj_align(SLIDER, NULL, LV_ALIGN_CENTER, 0, 0);
    /* callbackを設定する */
    lv_obj_set_event_cb(SLIDER, slider_event_cb);
    /* 値の取る範囲 */
    lv_slider_set_range(SLIDER, 0, 100);
    /* アニメーションの時間 */
    lv_slider_set_anim_time(SLIDER, 75);

    /* slider値を示すlabelの設定 */
    LABEL_SLIDERS_VALUE = lv_label_create(lv_scr_act(), NULL);
    /* 初期表示 */
    lv_label_set_text(LABEL_SLIDERS_VALUE, "0");
    /* 自動整理 */
    lv_obj_set_auto_realign(LABEL_SLIDERS_VALUE, true);
    /* sliderを基準とし、下部中央に配置する */
    lv_obj_align(LABEL_SLIDERS_VALUE, SLIDER, LV_ALIGN_OUT_BOTTOM_MID, 0, 10);

    /* 説明labelの設定 */
    /* 1行目 */
    lv_obj_t* info1 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_text(
        info1,
        "This is the joy switch + SLIDER demo."
    );
    lv_obj_align(info1, NULL, LV_ALIGN_IN_TOP_MID, 0, 10);
    /* 2行目 */
    lv_obj_t* info2 = lv_label_create(lv_scr_act(), NULL);
    /* スクロール */
    lv_label_set_long_mode(info2, LV_LABEL_LONG_SROLL_CIRC);
    /* 幅 */
    lv_obj_set_width(info2, LV_HOR_RES_MAX);
    /* スクロール設定後に文章を定める */
    lv_label_set_text(
        info2,
        "Move the switch right/up, SLIDER moves to right."
    );
    lv_obj_align(info2, info1, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
    /* 3行目 */
    lv_obj_t* info3 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_long_mode(info3, LV_LABEL_LONG_SROLL_CIRC);
    lv_obj_set_width(info3, LV_HOR_RES_MAX);
    lv_label_set_text(
        info3,
        "Move the switch left/down, SLIDER moves to left."
    );
    lv_obj_align(info3, info2, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);
    /* 4行目 */
    lv_obj_t* info4 = lv_label_create(lv_scr_act(), NULL);
    lv_label_set_long_mode(info4, LV_LABEL_LONG_SROLL_CIRC);
    lv_obj_set_width(info4, LV_HOR_RES_MAX);
    lv_label_set_text(
        info4,
        "Press the switch, SLIDER moves to the left end."
    );
    lv_obj_align(info4, info3, LV_ALIGN_OUT_BOTTOM_MID, 0, 0);

    /* groupの設定 */
    INPUT_GROUP = lv_group_create();
    /* input deviceを設定する */
    lv_indev_set_group(INPUT_DEVICE, INPUT_GROUP);
    /* sliderを追加する */
    lv_group_add_obj(INPUT_GROUP, SLIDER);
}

void wio_joy_handler() {
    /*
    ↑: LV_KEY_UP
    ↓: LV_KEY_DOWN
    ←: LV_KEY_LEFT
    →: LV_KEY_RIGHT

    UP or RIGHT:  increment
    DOWN or LEFT: decrement

    PRESS: 0
    */
    if (digitalRead(WIO_5S_UP) == LOW) {
        /* groupに入力LV_KEY_UPを送る */
        lv_group_send_data(INPUT_GROUP, LV_KEY_UP);
    }
    else if (digitalRead(WIO_5S_DOWN) == LOW) {
        /* groupに入力LV_KEY_DOWNを送る */
        lv_group_send_data(INPUT_GROUP, LV_KEY_DOWN);
    }
    else if (digitalRead(WIO_5S_LEFT) == LOW) {
        /* groupに入力LV_KEY_LEFTを送る */
        lv_group_send_data(INPUT_GROUP, LV_KEY_LEFT);
    }
    else if (digitalRead(WIO_5S_RIGHT) == LOW) {
        /* groupに入力LV_KEY_RIGHTを送る */
        lv_group_send_data(INPUT_GROUP, LV_KEY_RIGHT);
    }
    else if (digitalRead(WIO_5S_PRESS) == LOW) {
        /* sliderの値を0にする */
        lv_slider_set_value(SLIDER, 0, LV_ANIM_ON);
    }
    else {
        /* 何もない時は何もしない */
        return;
    }
    /* イベントは勝手に起こらないため、ここで起こす */
    lv_event_send(SLIDER, LV_EVENT_VALUE_CHANGED, NULL);
}

void setup() {
    /* 設定 */
    backend_setup();

    /* 画面構成 */
    set_contents();
}

void loop() {
    tick_handler();
    lv_task_handler();
    wio_joy_handler();
    delay(TICK_PERIOD);
}

value-slider-settings.ino
/*
画面初期化
画面いっぱい色で塗り潰している
*/
void display_flush(
    lv_disp_drv_t*      disp,
    const lv_area_t*    area,
    lv_color_t*         color_p
) {
    uint32_t w = (area->x2 - area->x1 + 1);
    uint32_t h = (area->y2 - area->y1 + 1);

    TFT.startWrite();
    TFT.setAddrWindow(area->x1, area->y1, w, h);
    TFT.pushColors(&color_p->full, w * h, true);
    TFT.endWrite();

    /* callbackではこれを召喚しなければならない */
    lv_disp_flush_ready(disp);
}

/* Reading input device (嘘) */
bool read_encoder(
    lv_indev_drv_t*     indev,
    lv_indev_data_t*    data
) {
    /* no input */
    return false;
}

/* system tick */
void tick_handler() {
    lv_tick_inc(TICK_PERIOD);
}

/* slider event callback */
static void slider_event_cb(lv_obj_t* SLIDER, lv_event_t event) {
    if (event == LV_EVENT_VALUE_CHANGED) {
        static char buf[4]; /* max 3 bytes for number plus 1 null terminating byte */
        snprintf(buf, 4, "%u", lv_slider_get_value(SLIDER));
        lv_label_set_text(LABEL_SLIDERS_VALUE, buf);
    }
}

/* 設定 */
void backend_setup() {
    /* lvglの初期化 */
    lv_init();

    /* TFTの初期設定 */
    TFT.begin();
    TFT.setRotation(3);

    /* 画面表示用bufferそのものの初期化 */
    lv_disp_buf_init(&DISP_BUF, COLOR_BUF, NULL, LV_HOR_RES_MAX * 10);

    /* 画面制御設定 */
    lv_disp_drv_t disp_drv;
    lv_disp_drv_init(&disp_drv);
    disp_drv.hor_res    = LV_HOR_RES_MAX;
    disp_drv.ver_res    = LV_VER_RES_MAX;
    disp_drv.flush_cb   = display_flush;
    disp_drv.buffer     = &DISP_BUF;
    lv_disp_t* pDisplay = lv_disp_drv_register(&disp_drv);

    /* 入力の設定 */
    lv_indev_drv_t indev_drv;
    lv_indev_drv_init(&indev_drv);
    indev_drv.type      = LV_INDEV_TYPE_ENCODER;
    indev_drv.read_cb   = read_encoder;
    INPUT_DEVICE = lv_indev_drv_register(&indev_drv);

    /* enable 5way switch */
    pinMode(WIO_5S_UP, INPUT);
    pinMode(WIO_5S_DOWN, INPUT);
    pinMode(WIO_5S_LEFT, INPUT);
    pinMode(WIO_5S_RIGHT, INPUT);
    pinMode(WIO_5S_PRESS, INPUT);
}

LV_KEY_*によるスライダーの増減は我々が決める必要のあることではなく、次のように定義されている。

https://github.com/Seeed-Studio/Seeed_Arduino_LvGL/blob/master/src/src/lv_widgets/lv_slider.c#L412-L421

つまり、スイッチによる物理的な入力を、LV_KEY_*などと謂う仮想的な入力へ対応付けるだけで宜い。

なお、説明文は長すぎて画面に収まらないため、自動スクロールするようになっている。

実行結果

次の様子が見て取れる。

  • 画面上半分の文章が流動している
  • スライダーが移動している
  • スライダー下部のラベル表示が変化している

操作前
スイッチ操作前

操作後
スイッチ操作後

Discussion

ログインするとコメントできます