Touch LCD Shield¶
Touch LCD Shield は OpenMV Cam に 2.3 インチ 320x240 の静電容量式マルチタッチディスプレイを追加し、ホストコンピュータなしでカメラ出力をプレビュー(および入力を受け付け)できるようにします。2 つの Qwiic ヘッダーにより、追加の I2C デバイスを簡単に連結できます。
完全なデータシート、写真、注文については Touch LCD Shield 製品ページ を参照してください。
主な特長¶
2.3 インチ TFT LCD、320x240、16 ビット RGB565
静電容量式マルチタッチ入力
PWM 制御可能なバックライト
I2C デバイスを簡単に連結できる 2 つの Qwiic コネクタ
ピン配置¶
ピンリファレンス¶
ピン |
機能 |
|---|---|
P0 |
LCD MOSI(ディスプレイへの SPI データ) |
P1 |
LCD TE(ティアリングエフェクト出力) |
P2 |
LCD SCLK(SPI クロック) |
P3 |
LCD SSEL(SPI チップセレクト) |
P4 |
Touch / Qwiic SCL(I²C クロック — Qwiic ヘッダーと共有) |
P5 |
Touch / Qwiic SDA(I²C データ — Qwiic ヘッダーと共有) |
P6 |
LCD バックライト |
P7 |
Touch / LCD RESET_N |
P8 |
LCD RS(データ / コマンド選択) |
P9 |
Touch INT_N |
3.3V レール |
LCD とタッチコントローラに電源を供給します |
GND レール |
共通グランド |
使い方¶
シールドは display.SPIDisplay クラスを通じて駆動します。カメラのフレームを 320×240 の LCD にストリーミングします:
import csi
import time
import display
import image
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
lcd = display.SPIDisplay(width=320,
height=240,
bgr=True,
vflip=False,
hmirror=False)
clock = time.clock()
while True:
clock.tick()
lcd.write(csi0.snapshot(), hint=image.CENTER | image.SCALE_ASPECT_KEEP)
print(clock.fps())
PWM を使ってバックライトを駆動すると明るさを調整できます。machine.PWM を小さなバックライトコントローラクラスでラップし、その backlight 引数を通じて SPIDisplay に渡します。SPIDisplay はレベルを更新する必要があるたびに、そのオブジェクトに対して backlight(value) を呼び出します:
import csi
import time
import display
import image
from machine import Pin, PWM
class PWMBacklight:
"""Drives a backlight pin with machine.PWM (0–100 %)."""
def __init__(self, pin, frequency=200):
self._pwm = PWM(Pin(pin), freq=frequency, duty_u16=0)
def backlight(self, value):
self._pwm.duty_u16(int(value * 65535 / 100))
def deinit(self):
self._pwm.deinit()
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
lcd = display.SPIDisplay(width=320,
height=240,
bgr=True,
vflip=False,
hmirror=False,
backlight=PWMBacklight("P6"))
lcd.backlight(50) # 0–100
clock = time.clock()
while True:
clock.tick()
lcd.write(csi0.snapshot(), hint=image.CENTER | image.SCALE_ASPECT_KEEP)
print(clock.fps())
オンボードの FT6x36 静電容量式コントローラからマルチタッチ入力を読み取ります。これはカメラの I²C バス上の P4/P5 に接続され、リセットは P7、IRQ は P9 に配線されています。以下の例では、タッチをライブカメラストリーミングと組み合わせ、指が押された場所に LCD 上で赤い円を描画します:
from time import sleep_ms
from array import array
from machine import Pin, SoftI2C
import csi
import display
import image
import time
_DEFAULT_ADDR = const(0x38)
_DEV_MODE = const(0x00)
_TD_STATUS = const(0x02)
class FT6X36:
FLAG_PRESSED = 0
FLAG_RELEASED = 1
FLAG_MOVED = 2
def __init__(
self,
bus,
reset_pin,
irq_pin,
address=_DEFAULT_ADDR,
width=320,
height=240,
reverse_x=False,
reverse_y=False,
touch_callback=None,
):
self.bus = bus
self.address = address
self.width = width
self.height = height
self.reverse_x = reverse_x
self.reverse_y = reverse_y
self.touch_callback = touch_callback
# reset_pin=None skips the reset pulse — useful when another
# peripheral on the same line (e.g. the LCD) has already done it.
if reset_pin is not None:
self.rst_pin = Pin(reset_pin, Pin.OUT_PP, value=0)
else:
self.rst_pin = None
self.irq_pin = None
self.irq_pin_label = irq_pin
# Reset the touch panel controller.
self.reset()
# Put the controller into normal operating mode.
self._write_reg(_DEV_MODE, 0x00)
# Scratch buffer for points (x, y, flag, id) — chip max 2.
self.points_data = [array("H", [0, 0, 0, 0]) for _ in range(2)]
self._touch_points_old = 0
self._touch_points = 0
def _read_reg(self, reg, size=1, buf=None):
# FT6X36 expects two separate START/STOP transactions
# (no repeated start), so don't use readfrom_mem here.
self.bus.writeto(self.address, bytes([reg]))
if buf is not None:
self.bus.readfrom_into(self.address, buf)
else:
return self.bus.readfrom(self.address, size)
def _write_reg(self, reg, val, size=1):
if size == 1:
buf = bytes([reg, val & 0xFF])
else:
buf = bytes([reg, val & 0xFF, val >> 8])
self.bus.writeto(self.address, buf)
def reset(self):
if self.irq_pin is not None:
self.irq_pin.irq(handler=None)
if self.rst_pin is not None:
self.rst_pin(0)
sleep_ms(1)
self.rst_pin(1)
sleep_ms(39)
self.irq_pin = Pin(self.irq_pin_label, Pin.IN, Pin.PULL_UP)
if self.touch_callback is not None:
self.irq_pin.irq(
handler=self.touch_callback,
trigger=Pin.IRQ_FALLING,
hard=False,
)
def read_points(self):
regs = self._read_reg(_TD_STATUS, 13)
n_points = min(regs[0] & 0x0F, 2)
for i in range(0, n_points):
base = 1 + i * 6
x = ((regs[base] & 0xF) << 8) | regs[base + 1]
y = ((regs[base + 2] & 0xF) << 8) | regs[base + 3]
if self.reverse_x:
x = self.width - 1 - x
if self.reverse_y:
y = self.height - 1 - y
self.points_data[i][0] = x
self.points_data[i][1] = y
self.points_data[i][2] = regs[base] >> 6
self.points_data[i][3] = regs[base + 2] >> 4
# Mark previously-active slots as released so the caller
# sees a release event after a finger lifts.
for i in range(n_points, 2):
self.points_data[i][2] = self.FLAG_RELEASED
# Latch touch count: rising immediate, falling debounced one read.
if n_points >= self._touch_points:
self._touch_points = n_points
elif n_points <= self._touch_points_old:
self._touch_points = self._touch_points_old
self._touch_points_old = n_points
return self._touch_points, self.points_data
csi0 = csi.CSI()
csi0.reset()
csi0.pixformat(csi.RGB565)
csi0.framesize(csi.QVGA)
lcd = display.SPIDisplay(width=320,
height=240,
bgr=True,
vflip=False,
hmirror=False)
# The LCD and touch controllers share P7 as a reset line. The LCD
# has already pulsed it during its own init, so init the touch
# controller after with reset_pin=None to skip a redundant pulse.
bus = SoftI2C(scl=Pin("P4"), sda=Pin("P5"), freq=100_000)
touch = FT6X36(bus, reset_pin=None, irq_pin="P9", reverse_y=True)
clock = time.clock()
# Some sensors return less than 240 lines at QVGA (e.g. 320x200 on
# the N6). The display centers the frame, so map touch Y to image Y.
y_offset = (touch.height - csi0.height()) // 2
while True:
clock.tick()
img = csi0.snapshot()
n, points = touch.read_points()
for i in range(n):
x, y, flag, tid = points[i]
if flag != FT6X36.FLAG_RELEASED:
iy = y - y_offset
if 0 <= iy < csi0.height():
img.draw_circle(
(x, iy, 18), color=(255, 0, 0), thickness=2
)
lcd.write(img, hint=image.CENTER | image.SCALE_ASPECT_KEEP)
print(clock.fps())