[自作キーボード]QMKファームウェアでカラーLCDに対応する~画像表示~

目次

Abstract

  • 前回の続きでQuantum Painterについて
  • ST7789のLCDに画像を表示した

Introduction

前回に引き続きQuantum Painterを使ったLCDへの描画処理。今回はLCDに画像を表示します。

Method

参考:Quantum Painter

前回のフォント表示と同様に、画像の表示処理も上記のQMKのgithubに詳細の説明があります。基本的にはフォントの描画処理と手順は一緒です。
今回は以下の手順で実装していきます。

    1. 画像ファイル(jpg,png,etc)をQGFファイル形式に変換する
    1. QGFファイルをファームウェアに組み込む
    1. 画像の表示処理を記述する

1. 画像ファイル(jpg,png,etc)をQGFファイル形式に変換する

参考: Quantum Painter CLI Commands

  • ↑のリンクを参考に画像データを変換します
  • 変換はqmk painter-convert-graphicsというコマンドを使用します
  • 最低限必要な引数は以下
    • -f FORMAT
      • 画像の色形式を指定する
      • 指定可能なものは↑のリンクにある
      • ファームウェアサイズにも影響するので、大量に使用するなら色数落とした方が良いかも
      • 今回はrgb565(16bit カラー)を使用します
    • -i INPUT
      • 入力する画像ファイル名を指定
  • 当然ですが、LCDの解像度より大きな画像は使用できないので、入力画像はLCDの解像度以下にしておく
  • -fで指定する色形式によって、どんな見た目、サイズになるかはAppendixを参照

実行例

(venv) user@user:~/qmk$ qmk painter-convert-graphics -f rgb565 -i sample.jpg
Writing /home/user/qmk/sample.qgf.h...
Writing /home/user/qmk/sample.qgf.c...

2. QGFファイルをファームウェアに組み込む

生成したQGFファイルを自分のキーボード用ファームの置き場に置く
自分の例だと以下。config.hとかある場所にqgfディレクトリを作成してその中に配置しました。

  • qmk_firmware/keyboards/cepst/
    • rp2040test // 自分のキーボードファームのソース置き場
    • keymaps/
    • qff/
    • qgf/ ←追加
      • sample.qgf.h ←追加
      • sample.qgf.c ←追加
    • config.h
    • etc.

rules.mkに追記
生成したQGFファイルをコンパイル対象に含めるため、rules.mkに追記します。

↓rules.mk

# This file intentionally left blank
QUANTUM_PAINTER_ENABLE = yes
QUANTUM_PAINTER_DRIVERS += st7789_spi

SRC += qgf/sample.qgf.c

config.hに追記

  • QGFのフォーマットにrgb888, rgb565, pal256, mono256を使用していない場合は飛ばしてよいです
  • 上記のフォーマットを使用する場合は 必ずconfig.hに追加の定義が必要です
    • rgb888, rgb565
      • QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORSの定義追加が必要
    • pal256, mono256
      • QUANTUM_PAINTER_SUPPORTS_256_PALETTEの定義追加が必要

今回はrgb565を使用するので、以下のように記載します

↓config.h

/* Quantum Painter setting */
#define QUANTUM_PAINTER_SUPPORTS_NATIVE_COLORS TRUE // <-追記

注意:上記定義を忘れてもコンパイルエラーにはなりません
rgb888, rgb565, pal256, mono256を使ったとき上記のように定義追加をしなくてもコンパイル自体は通ってしまいますが、ディスプレイには画像が表示されない状況になります。この定義追加忘れで画像が表示されていないってことには気づきにくいので注意です。

keymap.cに生成したQGFのヘッダファイルをインクルードする
QGFのヘッダーファイルをkeymap.cでインクルードします。

#include QMK_KEYBOARD_H
#include <qp.h>
#include "qgf/sample.qgf.h"  // <-追記
// 以下省略

コンパイルを試す
特に問題なくコンパイルできました。この状態でファームウェアにQGF形式の画像データが組み込まれており、ファームウェアサイズなども大きくなっています。


3. 画像の表示処理を記述する

参考: Image Functions

  • 上記リンクに画像表示用の関数の説明があります
  • 表示処理でやらなければ行けないのは以下の3つ
    • 背景描画
    • 画像のロード
    • 画像の表示

実際に描画処理をkeymap.cに記載したコードが以下。

keymap.c

#include QMK_KEYBOARD_H
#include <qp.h>
#include "qgf/sample.qgf.h"  // <-追記

static painter_device_t lcd;

enum HSL
{
    HUE,
    SAT,
    VAL,
};
const uint8_t black[] = {0,   0,   0};
const uint8_t white[] = {0,   0,   255};
const uint8_t red[]   = {0,   255, 127};
const uint8_t blue[]  = {170, 255, 127};

enum Position
{
  X,
  Y,
};

// イメージハンドルの宣言(追記)
static painter_image_handle_t img_sample;

const uint16_t PROGMEM keymaps[][MATRIX_ROWS][MATRIX_COLS] = {
    [0] = LAYOUT_ortho_2x3(
        KC_A,    KC_B,    KC_C,
        KC_D,    KC_E,    KC_F
    )
};

void keyboard_post_init_user(void)
{
    setPinOutput(LCD_BACKLIGHT_PIN);
    writePinHigh(LCD_BACKLIGHT_PIN);

    lcd = qp_st7789_make_spi_device(LCD_HEIGHT, LCD_WIDTH, SPI_CS_PIN, SPI_MISO_PIN, LCD_RESET_PIN, SPI_DIVISOR, SPI_MODE);
    qp_init(lcd, QP_ROTATION_0);
    qp_power(lcd, true);
    qp_clear(lcd);

    /* 描画処理 */
    uint16_t startPos[] = {0, 0};
    uint16_t endPos[] = {LCD_WIDTH, LCD_HEIGHT};

    // 背景描画
    qp_rect(lcd, startPos[X], startPos[Y], endPos[X], endPos[Y], black[HUE], black[SAT], black[VAL], true);
    qp_flush(lcd);

    // 画像の表示処理(追記)
    img_sample = qp_load_image_mem(gfx_sample);
    if(img_sample != NULL)
    {
        qp_drawimage(lcd, 0, 0, img_sample);
    }
}

keymap.cに追加した処理の説明

背景描画
フォントのときと同じでLCDの解像度のサイズぴったりの画像を表示するだけなら良いですが、そうじゃない場合は余白部分がモザイク状に表示されてしまうので、背景をqp_rect()を使って塗りつぶしています。

画像(QGF)のロード処理

  • 下記の関数を使ってフォントをロードする
  • 引数のbufferは1で生成したQGFデータを指定する
    • 2で変換した~.qgf.cにフォントデータ配列が下記のように定義されているので、その配列の変数名を指定すれば良い
    • 下記の例だと, “gfx_sample"を指定する

qmk_firmware/keyboards/cepst/rp2040test/qgf/sample.qff.c

#include <qp.h>

const uint32_t gfx_sample_length = 115248;

// clang-format off
const uint8_t gfx_sample[115248] = {
    0x00, 0xFF, 0x12, 0x00, 0x00, 0x51, 0x47, 0x46, 0x01, 0x30, 0xC2, 0x01, 0x00, 0xCF, 0x3D, 0xFE,
// 以下省略

画像の表示処理

  • そのまま画像を表示するqp_drawimage()と白黒画像に着色をするqp_drawimage_recolorの2種類がある
    • 参考: Draw image
      • bool qp_drawimage(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image)
      • bool qp_drawimage_recolor(painter_device_t device, uint16_t x, uint16_t y, painter_image_handle_t image, uint8_t hue_fg, uint8_t sat_fg, uint8_t val_fg, uint8_t hue_bg, uint8_t sat_bg, uint8_t val_bg)
  • 引数の指定は図形描画、フォント表示の関数と同様

Result

上記で変更したrules.mk, keymap.cを使ってコンパイルを実行し、作成したファームウェアを書き込んだ結果が以下。無事に画像が表示できた。

\


Conclusion

今回はQMKで画像表示とGIF動画の再生をしてみました。やはり二色OLEDとかと違ってカラーLCDなので画像が表現できるのが一番嬉しいですね。
次回はLCDに動画を表示させる予定です。



QGFのフォーマット形式の比較

  • 参考までに全フォーマットに変換したときにどんな表示でどんなサイズになるかを確認してみました
    • Array Sizeは~.qfg.cにかかれているデータの配列サイズです
    • Firmware Sizeはqmkでコンパイルした後のファームウェア(.uf2)のサイズです
    • 変換後のImageは、LCDに実際に表示したものを写真で撮影したものです
      • 光の反射とかLCD品質で色味うまく出てないのもあるので参考までに。。
  • カラー画像を使う場合、サイズ節約するならpal16, 画像品質も考えるならpal256, rgb565な感じですかね。。
    • pal2,pal4はかなり色数少ないのでUI画像とか、文字画像に使うのが良いかも
    • rgb565は結構サイズ大きくなっちゃうので、PicoBootとかフラッシュのサイズ大きい奴を使わないと複数枚は難しいですね。
Format Image Array Size Firmware Size[byte]
original - -
mono2 7,055 80,896
mono4 12,284 91,648
mono16 28,848 124,416
mono256 57,648 182,272
pal2 6,860 80,896
pal4 12,287 91,648
pal16 27,515 121,856
pal256 58,421 183,808
rgb565 115,248 297,472
rgb888 172,848 412,672

※今回使用したLCDはrgb888形式に非対応のため、rgb888は何も映っていない