UEED035HV-RX40-L001Aメモ
目次
UEED035HV-RX40-L001Aメモ
アリエクで売ってる半透過ディスプレイの仕様について調べたのでメモ。
#基本仕様
- 解像度: 480x360
- Transreflective(半透過)ディスプレイ
- ドライバIC: ST7365
- 静電容量式タッチパネル(I2C)
- 商品ページ: https://viewedisplay.com/product/3-5-inch-320x480-sunlight-readable-transflective-with-super-low-power-tft-lcd-module/
#ディスプレイ接続方式
SPI,MIPI,8080方式に対応しており、方式はIM0-2ピンの値を使って切り替えることができる。自分はMIPIが搭載されていないRaspberry Pi Zeroを使うのでSPI方式を検討する。
#SPI
SPIでも、3-wireと4-wireの両方に対応している。ややこしいが、ここで言う3-wireとは、一般的な4-wireと等しい普通のSPIになっている。 4-wireでは、D/CXもしくはSPI_RSというピンを追加で使うため、実質5-wireとなる。
D/CXとはそのデータがコマンドかデータかを選択するピンで、これを使わない場合にはSPI経由で1ビット余分に送信する必要があるということ。
#ラズパイ
ラズパイがどちらの方式に対応しているのかがわからないので調べた。そもそもST7365はILI9488というもののクローン(?)のようで、そのILI9488を動かすためのドライバが
にある。ILI9341と起動シーケンスが違うだけで似てるからかILI9341にまとめられている。そしてそのフォルダには結構詳しいREADMEファイルがあり、
この中に
-DGPIO_TFT_DATA_CONTROL=number: Specifies/overrides which GPIO pin to use for the Data/Control (DC) line on the 4-wire SPI communication. This pin number is specified in BCM pin numbers. If you have a 3-wire SPI display that does not have a Data/Control line, set this value to -1, i.e.-DGPIO_TFT_DATA_CONTROL=-1to tell fbcp-ili9341 to target 3-wire ("9-bit") SPI communication.
という記述を見つけた。つまりは3-wireと4-wireどちらも使えるようだ。4-wireのほうが高速になりそうなので4-wireにしてみる。
#Pinout
- Pin 9, SPI_CS: チップセレクト
- Pin 10, SPI_SCL: SPIクロック
- Pin 11, SPI_RS: 多分ST7365でD/CXと呼ばれているもの
- Pin 13, SPI_SDA: SDAという名前はST7365に無いのだが、SDOがOutputである以上これがInput、つまりMOSIだと思う。ST7365だとSDIと呼ばれている?
- Pin 14, SPI_SDO: MISO。ST7365でもSDOと呼ばれている。必要ないかも?
#他のピンについて
SPI有効時は、
- IM1-3: 3-wireなら101, 4-wireなら111
- DB0-15: GNDに落とす
- RESET: どっかのGPIOに繋ぐ
- MCU_RD: 多分ST7365のRDXピン。Low-activeなのでHigh?
- TE: フロート のようにする。
↓他のよく分からないピン
#バックライト
バックライトも搭載されており、
Backlight off in Reflective mode @ Surrounding illumination > 1500 cd/m² Backlight on in Transmissive mode @ Surrounding illumination ≤ 1500 cd/m²
と記載されている。のでこれに従いバックライトをオンオフすれば良さそう。また、先程のILI9341ディスプレイドライバでバックライトピンを指定できそうな感じだったのでそれを使うといいかもしれない。
#明るさ制御
バックライトは6個の並列LEDで駆動される。 5.2節によると、
- 順方向電圧: 2.9V
- 順方向電流: 120mA が典型値となっているが、この電流は「モジュール全体」の値とされている。つまり1つのLEDあたり20mAが典型値となる。 そしてこの典型値は寿命を計算するのに用いられており、これを超えて電流を流すと寿命が縮まるということらしい。
ちなみにLED一つあたりの絶対定格電流は120mA
#タッチパネル
Pin 1-4がタッチパネル関係のピンなのだが、CTPとRTPの場合で役割が分かれている。しかしながらこのディスプレイの仕様としてCTPなのでRTPになることが無さそうだが他のバリアントがあるのかもしれない。 今回はCTPなので普通にI2Cを繋げばよさそう
#実際に動かしてみた
↑まではデータシートを読んで考えた話。ここからは実際にこのディスプレイを動かしてみた際のメモ。
#分かったこと
- ST7365は確かにILI9488と互換性がありそう
- このディスプレイに使われているタッチパネルコントローラは「CHSC6540」というもの
- デスクトップを表示するのは難しそう
#デスクトップの表示(今のところできていない)
まず、上に書いたfbcp-ili9341はどうやら32bit環境でないと使用できないので今回はパスすることにした。
次の策として、どうやら最近はmipi-dbi-spiというものが使えるみたい。
これを使うと、各ドライバで微妙に違う初期化コードさえ提供できれば様々なSPIディスプレイを画面として使えるというもののようだ。 つまり、ILI9488用の初期化コードを入手する必要がある。
このリポジトリでは、テキストからこのドライバで使うバイナリを生成するためのツールが提供されており、その中にこんなissueがあった。
このissue内にあるコメントの初期化コードを使用してみたところ、Linuxブートっぽい画面が出たものの白黒で、表示もなんだかおかしかった。
この辺の内容をよく調べればもっとうまくできるかもしれない
#Pythonコードで表示
デスクトップは無理でも、PythonのLuma.lcdというライブラリでILI9488を使えるという情報を見かけた。これはspidevを使ってPythonで直接描画できる。
ということで色々と調べて、公式Exampleにあるbounce.pyを動かすことができた。
import time
import sys
import random
from luma.core.interface.serial import spi
from luma.lcd.device import ili9488
import luma.core.render
from luma.core.sprite_system import framerate_regulator
class Ball(object):
def __init__(self, w, h, radius, color):
self._w = w
self._h = h
self._radius = radius
self._color = color
self._x_speed = (random.random() - 0.5) * 10
self._y_speed = (random.random() - 0.5) * 10
self._x_pos = self._w / 2.0
self._y_pos = self._h / 2.0
def update_pos(self):
if self._x_pos + self._radius > self._w:
self._x_speed = -abs(self._x_speed)
elif self._x_pos - self._radius < 0.0:
self._x_speed = abs(self._x_speed)
if self._y_pos + self._radius > self._h:
self._y_speed = -abs(self._y_speed)
elif self._y_pos - self._radius < 0.0:
self._y_speed = abs(self._y_speed)
self._x_pos += self._x_speed
self._y_pos += self._y_speed
def draw(self, canvas):
canvas.ellipse((self._x_pos - self._radius, self._y_pos - self._radius,
self._x_pos + self._radius, self._y_pos + self._radius), fill=self._color)
def main(num_iterations=sys.maxsize):
serial = spi(port=0, device=0, gpio_DC=22, gpio_RST=24, bus_speed_hz=52000000)
device = ili9488(serial)
device.command(0x21)
colors = ["red", "orange", "yellow", "green", "blue", "magenta"]
balls = [Ball(device.width, device.height, i * 1.5, colors[i % 6]) for i in range(10)]
frame_count = 0
fps = ""
canvas = luma.core.render.canvas(device)
regulator = framerate_regulator(fps=0)
while num_iterations > 0:
with regulator:
num_iterations -= 1
frame_count += 1
with canvas as c:
c.rectangle(device.bounding_box, outline="white", fill="black")
for b in balls:
b.update_pos()
b.draw(c)
c.text((2, 0), fps, fill="white")
if frame_count % 20 == 0:
fps = "FPS: {0:0.3f}".format(regulator.effective_FPS())
if __name__ == '__main__':
try:
main()
except KeyboardInterrupt:
passこれはボールがたくさん跳ねているアニメーションを表示するプログラムになっており、このコードで約35fpsを達成できた。更新量は少ないとは言え、30fps出せることが分かってよかった。
コツは、
- SPIバス作成時に
bus_speed_hz=52000000を指定して速度を速くすること(これぐらい速くても割といけるみたい) device.command(0x21)で色反転を切り替えること(これをしないと色がおかしい)
#タッチパネル
タッチコントローラICについてデータシートにも記載がなかったのでi2cdetectを実行すると0x2eが検出されたが、これだけではICが何か見つけられなかった。 ということで実物のICっぽい部分を見ると普通に「CHSC6540」と書いてあった。
そんなにメジャーなICではないのかドライバは無かったが、Githubサーフィンをしたところそんなに複雑な仕様ではないので自分で読み取れば良さそう。
import time
import RPi.GPIO as GPIO
from smbus2 import SMBus, i2c_msg
# --- 設定 -------------------------------------------
CHSC6540_I2C_ADDRESS = 0x2e
I2C_BUS = 1
IRQ_PIN = 17
# ----------------------------------------------------
def setup_gpio():
"""GPIOピンのセットアップ"""
GPIO.setmode(GPIO.BCM)
GPIO.setup(IRQ_PIN, GPIO.IN, pull_up_down=GPIO.PUD_UP)
print(f"GPIO {IRQ_PIN} をIRQピン(プルアップ入力)として設定しました。")
def read_touch(bus):
"""
IRQピンをチェックし、タッチされていればI2Cで座標を読み取る
"""
if not GPIO.input(IRQ_PIN):
try:
data = bus.read_i2c_block_data(CHSC6540_I2C_ADDRESS, 0x01, 16)
fingers = data[2]
if fingers:
x = data[4]
y = data[6]
return (x, y)
except OSError as e:
print(f"I2C Read Error: {e}")
pass
return None
def main():
"""メイン処理"""
setup_gpio()
# I2Cバスを開く
with SMBus(I2C_BUS) as bus:
print(f"I2Cバス {I2C_BUS} で CHSC6540 (0x{CHSC6540_I2C_ADDRESS:x}) の読み取りを開始します。")
last_touch = None
while True:
touch_coords = read_touch(bus)
if touch_coords:
if touch_coords != last_touch:
print(f"タッチ検出! X: {touch_coords[0]}, Y: {touch_coords[1]}")
last_touch = touch_coords
time.sleep(0.02)
if __name__ == "__main__":
try:
main()
except KeyboardInterrupt:
print("\nプログラムを終了します。")
finally:
GPIO.cleanup()AIに書かせた。