LoginSignup
7
2

More than 1 year has passed since last update.

reTerminalでmicropython + LVGL のGUIアプリを作ろう

Last updated at Posted at 2021-12-01

#はじめに
Raspberry Pi Compute Module 4が入っているSeeed reTerminalにはGUIアプリケーションを作るのに
Qt forPython
Flutter
Electron
LVGL
がSeeed公式wikiで紹介されています
その中で私はLVGLがサンプルデモが豊富で良さそうなので使って見たいと思いましたがC/C++が理解できない・・・
そこでpython(Cpython)に文法的によく似たmicropythonでLVGLが使えるというのでやってみました

インストール

まず必要なパッケージをインストールします

sudo apt update
sudo apt-get install build-essential libreadline-dev libffi-dev git pkg-config libsdl2-2.0-0 libsdl2-dev  parallel git

lv_micropythonリポジトリをクローンします。

git clone --recurse-submodules https://github.com/lvgl/lv_micropython.git

デフォルトフォントを大きくします

/home/pi/lv_micropython/lib/lv_bindings/lv_conf.h
331 #define LV_FONT_MONTSERRAT_48 1
348 /*Always set a default font*/
349 #define LV_FONT_DEFAULT &lv_font_montserrat_48

画面サイズにあわせます

/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/lv_drv_conf.h

86  #define MONITOR_HOR_RES     1280
87  #define MONITOR_VER_RES     720

フルスクリーン表示にします

/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/SDL_monitor.c
129 window = SDL_CreateWindow("TFT Simulator",
130                              SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED,
131                              MONITOR_HOR_RES * MONITOR_ZOOM, MONITOR_VER_RES * MONITOR_ZOOM,SDL_WINDOW_FULLSCREEN);       /*last param. SDL_WINDOW_BORDERLESS to hide borders*/

そのままだとエラーが出るのでLong型でキャストします

/home/pi/lv_micropython/lib/lv_bindings/lvgl/src/extra/libs/qrcode/qrcodegen.c
856 if (result > (long)INT16_MAX)
cd ~/lv_micropython
make -C mpy-cross -j4
make -C ports/unix/ -j4

正常に終わると実行ファイルが出来上がりmicropythonのREPL対話モードが立ち上がります

./ports/unix/micropython

/usr/local/bin/ 辺りにリンクを作っておけばmicropythonのコマンドで動きます

sudo ln -s /home/pi/lv_micropython/ports/unix/micropython /usr/local/bin/micropython
pi@raspberrypi:~ $ micropython
MicroPython v1.17-587-g009190923-dirty on 2021-11-29; linux version
Use Ctrl-D to exit, Ctrl-E for paste mode
>>> help("modules")
SDL               gc                ucryptolib        ure
__main__          imagetools        uctypes           uselect
_thread           lodepng           uerrno            usocket
btree             lv_timer          uhashlib          ussl
builtins          lv_utils          uheapq            ustruct
cmath             lvgl              uio               usys
display_driver    math              ujson             utime
display_driver_utils                micropython       umachine          utimeq
evdev             termios           uos               uwebsocket
fb                uarray            upip              uzlib
ffi               ubinascii         upip_utarfile
fs_driver         ucollections      urandom
Plus any modules on the filesystem
>>>

サンプル

最低限必要なコード

import lvgl as lv
import SDL
# Register SDL display driver.
SDL.init()

# LVGL init
lv.init()
draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

# Regsiter SDL mouse driver

indev_drv = lv.indev_drv_t()
indev_drv.init()
indev_drv.type = lv.INDEV_TYPE.POINTER
indev_drv.read_cb = SDL.mouse_read
indev_drv.register()



これに/home/pi/lv_micropython/lib/lv_bindings/lvgl/examples フォルダに有るサンプルを参考にデモを作ってみました。

lv_exsample_meter_1_test.py
#!/usr/local/bin/micropython
import utime as time
import lvgl as lv
import SDL
# Register SDL display driver.
SDL.init()
# LVGL init
lv.init()
draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

#
# A simple meter
#
meter = lv.meter(lv.scr_act())
meter.center()
meter.set_size(600, 600)

# Add a scale first
scale = meter.add_scale()
meter.set_scale_ticks(scale, 51, 2, 10, lv.palette_main(lv.PALETTE.GREY))
meter.set_scale_major_ticks(scale, 10, 4, 15, lv.color_black(), 10)

indic = lv.meter_indicator_t()

# Add a blue arc to the start
indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.BLUE), 0)
meter.set_indicator_start_value(indic, 0)
meter.set_indicator_end_value(indic, 20)

# Make the tick lines blue at the start of the scale
indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.BLUE), lv.palette_main(lv.PALETTE.BLUE), False, 0)
meter.set_indicator_start_value(indic, 0)
meter.set_indicator_end_value(indic, 20)

# Add a red arc to the end
indic = meter.add_arc(scale, 3, lv.palette_main(lv.PALETTE.RED), 0)
meter.set_indicator_start_value(indic, 80)
meter.set_indicator_end_value(indic, 100)

# Make the tick lines red at the end of the scale
indic = meter.add_scale_lines(scale, lv.palette_main(lv.PALETTE.RED), lv.palette_main(lv.PALETTE.RED), False, 0)
meter.set_indicator_start_value(indic, 80)
meter.set_indicator_end_value(indic, 100)

# Add a needle line indicator
indic = meter.add_needle_line(scale, 4, lv.palette_main(lv.PALETTE.GREY), -10)

val = 0
while True:
    meter.set_indicator_value(indic, int(val%100))
    val += 1
    time.sleep(0.05)

chmod +x  ./lv_exsample_meter_1_test.py
DISPLAY=:0 ./lv_exsample_meter_1_test.py

モジュールの追加

足りないモジュールは随時 MicroPython のパッケージマネージャーupipでインストールします

micropython
>>> import upip
>>> upip.install("machine")
>>> upip.install("glob")
>>> upip.install("requests")
>>> upip.install("socket")

serialモジュールはupipでは見つからなかったので別に入れます

git clone https://github.com/iRobotEducation/micropython-serial.git
cd micropython-serial/
./install.sh 

入出力

GPIO

from machine import Pin
led=Pin(7,Pin.OUT)
led.value(1)

serial(USB/UART通信)

import serial
ser=serial.Serial("/dev/ttyACM0", 9600, timeout=1)
ser.open()
s_read = ser.read() #byte型
ser.close()

request(インターネット)

requestモジュールを使ってAmbientに送受信するライブラリはmicropython互換なのでそのまま使用できます

https://github.com/AmbientDataInc/ambient-python-lib
からambient.pyファイルを /home/pi/.micropython/lib などインポートできる場所に置きます

import request
import ambient
am = ambient.Ambient(チャネルID,"リードキー","ライトキー")
a_read = am.read(n=1)

socket(socket通信)

import socket
addr = socket.getaddrinfo('0.0.0.0', 50001)[0][-1] #socker Server 
s = socket.socket()
s.bind(addr)
s.listen(1)
cl, addr = s.accept()
c_read = cl.recv()
cl.close()

** 参考 **
各送受信はbyte単位なのでint型やstr型はstruct.pack() struct.unpack() で変換します
https://micropython-docs-ja.readthedocs.io/ja/latest/library/struct.html
https://docs.python.org/ja/3/library/struct.html

int.from_bytes() int.to_bytes() bytes() などを使うときは引数の渡し方に注意

int.from_bytes(b'\x00\x10', byteorder='little', signed=True) -> error
int.from_bytes(b'\x00\x10', 'little')            -> ok signed引数はない

bytes('abc', encoding='utf8') -> error
bytes('abc', 'utf-8')     -> ok

できてないこと

タッチスクリーン対応

マウス入力には反応していますがなぜかタッチスクリーンには反応しません。どこか設定で動くのか知っている方がいたら教えて下さい。
・試してみたこと -> 動きません

/home/pi/lv_micropython/lib/lv_bindings/driver/SDL/lv_drv_conf.h
202 /*-------------------------------------------------
203  * Mouse or touchpad as evdev interface (for Linux based systems)
204  *------------------------------------------------*/
205 #define USE_EVDEV    1
206 #if USE_EVDEV
207 #define EVDEV_NAME   "/dev/input/event1"    

reTerminalハードウェア対応

ユーザーボタンやLED、各センサーについてpython(Cpython)で扱うサンプルは公式 にありますが、そのままではmicropythonでは動きません

やや強引ですがCpythonで取得した値をsocket通信でmicropythonに送り表示することはできました

C/C++への移植

micropython版はオリジナルC版から作られていて関数名もそれを踏襲していているので
プロトタイプとしてmicropythonで作ってそれをC/C++版に移植するのは簡単にできるそうです。(但し、個人の能力による)
また計算能力の高いラズパイのようなプロセッサで試作を作って、ESP32やラズパイPICOのようなマイコンで量産という流れがおすすめとSeeedの中の人も話していました。そういった使い方にも移植性の高いLVGLは向いていそう。

リンク

LVGL(Light and Versatile Graphics Library)HOME

Micropython + LittlevGL ブログ

Github

** LVGL ** : GUIライブラリ

** lv_micropython ** : Micropython + LVGL

** lv_binding_micropython ** : lv_micropythonのサブモジュール

ユーザーボタン入力(2021/12/4追記)

こちらの記事を参考にしたところmicropythonでもユーザーボタン・タッチパネル・加速度センサー値を取得できました。

key_input.py
#!/usr/local/bin/micropython

import struct

EVENT_FORMAT = "llHHI"; # long, long, unsigned short, unsigned short, unsigned int
EVENT_SIZE = struct.calcsize(EVENT_FORMAT)
def get_userkeys():
    infile_path = "/dev/input/by-path/platform-gpio_keys-event"
    with open(infile_path, "rb") as file:
        event = file.read(EVENT_SIZE)
        while event:
            (tv_sec, tv_usec, type, code, value) = struct.unpack(EVENT_FORMAT, event)
            #print(struct.unpack(EVENT_FORMAT, event))
            if code != 0:
                #print(f"code={code} value={value}")
                if value != 0:
                    return code
            event = file.read(EVENT_SIZE)

日本語フォントの表示方法 (2021/12/30追記)

micropythonでのサンプルはこちらにあります
https://github.com/lvgl/lv_binding_micropython/blob/620a3de2408d2ccb283c2e710c77c6cf006b42e3/examples/Dynamic_loading_font_example.py

FS driver init. 辺りをSDL display driverに適宜合わせて動かしてみると表示はされますが、
フォントサイズが小さく、ひらがなのみしか表記できない。そこで

公式ドキュメント
https://docs.lvgl.io/latest/en/html/overview/font.html#add-new-font
を見ながら新しくフォントを加えます

まずお好きなフォントを選びます

Windowsに入っているお好きなフォントをC:\Windows\Fontsからコピーする

(例:dfgoteb.ttc DF特太ゴシック体 標準)
ファイル形式を見ると.ttcだったのでこちらの記事を参考に.tffに変換する
TTCフォントをTTFに分解する方法。
https://qiita.com/maboy/items/9f64d406c830aab548b1

(解凍フォルダ)\unitettc >unitettc64 dfgoteb.ttc

dfgoteb001.ttf dfgoteb002.ttf
が出来上がる

次にtffからbinファイルを作る

Converts TTF/WOFF fonts to compact bitmap format(tff-> bin オフラインコンバーター)
https://github.com/lvgl/lv_font_conv
node.jsを使うのでここから**ラズパイ(reTerminal)**上で行う
node.js(v10~) と npmが入っていないときはインストールしておく
(たまたま前に入れてたのでつまずきはなかったが ラズパイ Node.js インストールでググる)

~$ mkdir myfont && cd myfont 
~/myfont$ npm init --yes
~/myfont$ sudo npm i lv_font_conv -g
~/myfont$ sudo npm i lvgl/lv_font_conv -g

このフォルダに先程のtffファイルを移動しておく

コマンドの詳しい説明はREADME.mdにかいてあります
https://github.com/lvgl/lv_font_conv/blob/master/README.md

range オプションについて
https://jrgraphix.net/research/unicode.php

0x21-0x7f  ASCII
0x3040-0x09F ひらがな
0x30A0-0x30FF カタカナ
0x2190-0x21FF 矢印
0x3300-0x33FF CJK互換文字
0xFF00-0xFFE  半角カナ

とりあえず使いそうなこのあたり
漢字については CJK統合漢字(0x4E00-0x9FFF) 全部を入れるとメモリが足りないので使う文字だけ一つずつ追加しました。
(どこかの設定でメモリを増やせば全部入る?)

 lv_font_conv --size 48 --format bin --bpp 1 --font "/home/pi/myfont/dfgoteb001.ttf" --range 0x21-0x7f,0x3042-0x30FF,0x2190-0x21ff,0x3300-0x33ff,0xFF00-0xFFEF,0x4E16,0x754C --no-compress -o font-jp-48.bin

作ったフォントを使う

あとは最初のサンプルを見ながら適宜置き換えていきます

unicodefont.py
#!/usr/local/bin/micropython

import usys as sys
sys.path.append('') # See: https://github.com/micropython/micropython/issues/6419

import lvgl as lv
import SDL,time
SDL.init()
lv.init()

# display driver init...

import fs_driver
fs_drv = lv.fs_drv_t()
fs_driver.fs_register(fs_drv, 'S')

# Register SDL display driver.

draw_buf = lv.disp_draw_buf_t()
buf1_1 = bytearray(1280*10)
draw_buf.init(buf1_1, None, len(buf1_1)//4)
disp_drv = lv.disp_drv_t()
disp_drv.init()
disp_drv.draw_buf = draw_buf
disp_drv.flush_cb = SDL.monitor_flush
disp_drv.hor_res = 1280
disp_drv.ver_res = 720
disp_drv.register()

#font
myfont_jp = lv.font_load("S:/home/pi/myfont/font-jp-48.bin")
scr = lv.scr_act()
scr.clean()
label1 = lv.label(scr)
label1.set_style_text_font(myfont_jp, 0)  # set the font
label1.set_text("Hello! ハロー こんにちは↑ コンニチハ世界㎡㌶")  
label1.align(lv.ALIGN.CENTER, 0, 0)
while 1:
    time.sleep(1)

IMG_20211230_193533.jpg

7
2
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
7
2