ページ

2023年7月18日火曜日

抵抗膜式タッチ検知IC XPT2046をpythonで扱うライブラリXPT2046-asukiaaa-pythonの紹介


背景

この1ヶ月ほど抵抗膜式のタッチ機能が付いたLCDをRaspberry Piで扱っています。

Raspberry PiからpythonでSPI接続のLCDを利用
Raspberry Piでpythonを通して抵抗膜式のタッチパネルを動かしてみた(Luca8991/XPT2046-Python利用)

画面描画はadafruitのライブラリを利用し、タッチ機能はgithubでコードが公開されているライブラリを利用していましたが、後者の使い勝手に不満が出てきたので自分好みに変更し、ついでにpipでインストール可能にしました。

XPT2046-asukiaaa-python

使い方を共有します。

使ったもの

「Raspberry Piでpythonを通して抵抗膜式のタッチパネルを動かしてみた(Luca8991/XPT2046-Python利用)」ときと同じです。

  • Raspberry Pi
    3B+とRaspberry Pi OSを使いました
  • LCD 320x240 3.2インチ
    描画にILI9341が使われているものを利用しました。
    spi tft液晶画面
    この記事では3.2インチ版を利用しました。
    3.2インチ版は抵抗膜方式のタッチ検知でXPT2046というICが使われています。
  • ブレッドボード
    SPIの信号線をタッチと画面表示でそれぞれ繋ぐ必要があるので、ジャンパワイヤ直つなぎではなくブレッドボードを介します。
  • ジャンパワイヤ オスオスとオスメス
    LCDの画面表示だけをした時はメスメスを利用しましたが、今回はブレッドボードを使うのでオスメスとオスオスを使いました。

接続

Raspberry PiとLCDをこのように配線します。
「Raspberry Piでpythonを通して抵抗膜式のタッチパネルを動かしてみた(Luca8991/XPT2046-Python利用)」ときと同じです。


タッチと画面表示のSPIは通信口(MOSI MISO SCLK)が分かれていいるので、それぞれ繋ぎます。(青い線がそれです)
SPIの標準のCSであるG8は通信時に問答無用でCSが通信状態に切り替わり複数の装置を1つのSPIのバスに繋いでいるときに意図しない値のやりとりが発生しまうため、G8の利用は避けます。
タッチの割り込みピン IRQはこの記事では利用しません。

ジャンパワイヤとブレッドボードで繋ぐとこうなりました。


環境とライブラリ準備

Raspberry PiのSPI有効化

下記のコマンドを実行します。
再起動は不要です。
sudo raspi-config nonint do_spi 0

aptやpipで関連プログラムやライブラリをインストール

Raspberry Pi OS(Debian系)で呼び出せるプログラムインストールコマンドaptでpythonやgitをインストールし、それでインストールしたpythonのライブラリインストールコマンドpipでLCDの画面表示ライブラリをインストールします。
sudo apt install -y git python3-rpi.gpio python3-spidev python3-pip python3-pil python3-numpy fonts-noto-cjk
pip3 install adafruit-circuitpython-rgb-display XPT2046-asukiaaa-python
説明したいライブラリXPT2046-asukiaaa-pythonも上記のpipコマンドでインストールしています。
画像を描画せずタッチ機能だけ使いたい場合はインストールが必要なライブラリが減りますが、そのような場合は少ないと思うので描画関係も一緒にインストールするコマンドを紹介しています。

サンプルプログラムの動作と関数を解説

githubにも上げているサンプルプログラムdraw_touch_points_ili9341.pyの記事を書いている時点での全体を共有し、関数の解説と動作の様子を紹介します。

draw_touch_points_ili9341.py
from adafruit_rgb_display.ili9341 import ILI9341
from busio import SPI
from digitalio import DigitalInOut, Direction
import board
from PIL import Image, ImageDraw, ImageFont
import time
from XPT2046_asukiaaa_python.xpt2046 import XPT2046

# Pin Configuration
pin_cs_lcd = DigitalInOut(board.D1)
pin_dc = DigitalInOut(board.D25)
pin_rst = DigitalInOut(board.D24)
pin_led = DigitalInOut(board.D23)
pin_cs_touch = DigitalInOut(board.D7)

# Trun on LED of back panel
pin_led.direction = Direction.OUTPUT
pin_led.value = True

# Set up SPI bus
spi = SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO)

# Create the ILI9341 display:
display = ILI9341(
spi,
cs=pin_cs_lcd, dc=pin_dc, rst=pin_rst,
width=240, height=320,
rotation=90,
baudrate=24000000
)

# Define colors and fonts
COLOR_WHITE = (255, 255, 255)
COLOR_BLACK = (0, 0, 0)
COLOR_RED = (255, 0, 0)
COLOR_GREEN = (0, 255, 0)
# COLOR_GREEN = (0, 206, 69)
COLOR_BLUE = (0, 0, 255)
COLOR_GRAY = (50, 50, 50)
COLOR_WHITE_GRAY = (200, 200, 200)
FONT_DEFAULT = ImageFont.load_default()
# FONT_NOTO_18 = ImageFont.truetype("NotoSansCJK-Regular.ttc", 18)

rect_detect = [(50, 50), (150, 150)]

# Fill display with one color and show text
image = Image.new("RGB", (display.height, display.width) if display.rotation %
180 == 90 else (display.width, display.height), COLOR_BLACK)
draw = ImageDraw.Draw(image)
draw.text((10, 5), "hello", COLOR_GREEN, FONT_DEFAULT)
draw.rectangle(rect_detect, outline=COLOR_GRAY)
display.image(image)

# Create touch driver
touch = XPT2046(spi, cs=pin_cs_touch, rotation=display.rotation)

while True:
touch.update()
if touch.coordinate is not None or touch.prev_coordinate is not None:
target_coordinate = touch.coordinate
color_point = COLOR_WHITE
if touch.changed_to_press:
color_point = COLOR_RED
elif touch.changed_to_release:
color_point = COLOR_BLUE
target_coordinate = touch.prev_coordinate
(x, y) = target_coordinate
r = 2
draw.ellipse((x - r, y - r, x + r, y + r), fill=color_point)
color_rect = COLOR_GRAY
if touch.is_in_rect(rect_detect):
color_rect = COLOR_WHITE_GRAY
elif touch.changed_to_release and touch.prev_was_in_rect(rect_detect):
color_rect = COLOR_GREEN
draw.rectangle(rect_detect, outline=color_rect)
display.image(image)
time.sleep(.01)

XPT2046の初期化に関する部分はこれらです。
XPT2046では通信速度を通信開始時に切り替えてくれるadafruitのbusioのspi_deviceを内部で使っているため、adafruitの画像描画処理と異る通信速度で通信できます。
これが以前抵抗膜のタッチを動かしたときのライブラリと最も異るところです。
from busio import SPI
from digitalio import DigitalInOut
import board
from XPT2046_asukiaaa_python.xpt2046 import XPT2046

pin_cs_touch = DigitalInOut(board.D7)
spi = SPI(clock=board.SCK, MOSI=board.MOSI, MISO=board.MISO)
touch = XPT2046(spi, cs=pin_cs_touch, rotation=display.rotation)

このライブラリはupdate関数でXPT2046と通信して現時点の情報を更新します。
while True:
touch.update()

最新の位置情報はcoordinate、一つ前の位置情報はprev_coordinateで保持されます。
タッチ始まったときはchanged_to_touchがTrueに、タッチが終わったときはchanged_to_releaseがTrueになります。
このプログラムでは、開始点を赤で、終了点を青で、その間の接触点を白で描画しています。
    if touch.coordinate is not None or touch.prev_coordinate is not None:
target_coordinate = touch.coordinate
color_point = COLOR_WHITE
if touch.changed_to_press:
color_point = COLOR_RED
elif touch.changed_to_release:
color_point = COLOR_BLUE
target_coordinate = touch.prev_coordinate
(x, y) = target_coordinate
r = 2
draw.ellipse((x - r, y - r, x + r, y + r), fill=color_point)

is_in_rect関数でcoordinateがrect(四角)の中にあるか判別できます。
このプログラムには出てきませんが、prev_was_in_rect関数を使うと一つ前の座標がrectの中にあるか判別できます。
changed_to_releaseとprev_was_in_rectを組み合わせると、放したときの座標がrectに含まれていたか判別できます。
このプログラムでは通常はrectの枠を灰色に描画し、rectの内部に接触中なら枠を白目の灰色に描画し、rectの中で離されたら枠を緑色に描画しています。
        color_rect = COLOR_GRAY
if touch.is_in_rect(rect_detect):
color_rect = COLOR_WHITE_GRAY
elif touch.changed_to_release and touch.prev_was_in_rect(rect_detect):
color_rect = COLOR_GREEN
draw.rectangle(rect_detect, outline=color_rect)

ということでプログラムを実行して挙動を見てみます。
python3 draw_touch_points_ili9341.py

タッチ始まりが赤、終わりが青、途中が白で描画されます。


タッチ継続状態でrectに入ると枠が白目の灰色になります。


接触状態のまま枠の外に出ると黒目の灰色に戻ります。


rectの中で接触を終わるとrectが緑色になります。


期待通りに動きました。

おわり

抵抗膜式のタッチパネルの制御IC XPT2046のライブラリを通して、描画速度を維持しつつ画面の接触情報を扱えました。

期待通りに動くライブラリができて嬉しいです。

参考

作成したライブラリです。
XPT2046_asukiaaa_python

以前描画やタッチ検知に取り組んだときの記事です。
Raspberry PiからpythonでSPI接続のLCDを利用
Raspberry Piでpythonを通して抵抗膜式のタッチパネルを動かしてみた(Luca8991/XPT2046-Python利用)

0 件のコメント :

コメントを投稿