2023年7月3日月曜日

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


ライブラリを作ったので良かったらこちらもご覧ください

本記事で利用しているライブラリを改良して公開しました。

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

上記記事のライブラリはpipでインストールできる上に通信対象によって通信速度を変える機能(adafruit busio spi_device)を利用しているので本記事より画像描画が速いです。
良かったらご覧ください。

背景

以前試したSPI通信で使うLCDに抵抗膜方式のタッチパネルが付いていたので、描画処理と同時に使ってみました。
いくつか詰まりどころがあったのでそれも共有しつつ、全体の流れを記事にまとめます。

使ったもの

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

接続

Raspberry PiとLCDをこのように配線します。


タッチと画面表示の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

プログラムを配置するフォルダにgitでタッチ検知用のライブラリを配置

LCDのタッチIC XPT2046を扱うめの使い勝手の良いライブラリがpip方式では公開されていなかったので、バージョン管理コマンドであるgitでコードを配置して使います。

配置するのは下記のライブラリです。
Luca8991/XPT2046-Python

この記事では下記のフォルダにプログラムを配置します。
~/gitprojects/pi-lcd-touch-draw

下記のコマンドでフォルダ作成とライブラリ配置を行います。
mkdir -p ~/gitprojects/pi-lcd-touch-draw
cd ~/gitprojects/pi-lcd-touch-draw
git clone https://github.com/Luca8991/XPT2046-Python.git

プログラム作成

画像描画タッチ検知を同時に行いつつ、タッチされた場所に丸を描くプログラムです。
タッチ検知用のライブラリを配置したフォルダに作成します。
全体を共有して要所を解説します。
基本的に画面表示ライブラリのサンプル接触検知ライブラリのサンプルを合体したものです。
~/gitprojects/pi-lcd-touch-draw/touch-draw-ili9341.py
from adafruit_rgb_display.rgb import color565
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 gpiozero import Button, DigitalOutputDevice
from importlib import import_module
xpt2046 = import_module('XPT2046-Python.xpt2046')
Touch = xpt2046.Touch

# Pin Configuration
# cs_pin = DigitalInOut(board.D8) #original spi cs
cs_pin = DigitalInOut(board.D1)
dc_pin = DigitalInOut(board.D25)
rst_pin = DigitalInOut(board.D24)
led_pin = DigitalInOut(board.D23)
touch_cs = DigitalOutputDevice(7,active_high=False,initial_value=None)

# Trun on background LED
led_pin.direction = Direction.OUTPUT
led_pin.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=cs_pin, dc=dc_pin, rst=rst_pin,
width=240, height=320,
rotation=90,
baudrate=1000000 # OK
# baudrate=4000000 # can communicate but too fast
# baudrate=8000000 # can communicate but too fast
# baudrate=10000000 # can communicate but too fast
# baudrate=16000000 # bad
# baudrate=24000000 # bad
)

# Define color
COLOR_WHITE = (255, 255, 255)
COLOR_BLACK = (0, 0, 0)

# Fill display with one color
image = Image.new("RGB", (display.height, display.width), COLOR_BLACK)
draw = ImageDraw.Draw(image)
display.image(image)

# Create touch driver
time.sleep(.002)
xpt = Touch(spi, cs=touch_cs)

while True:
pos = xpt.raw_touch()
if pos is not None:
x, y = xpt.normalize(pos[0], pos[1])
print("%3d, %3d" % (x, y))
r = 2
draw.ellipse((y - r, x - r, y + r, x + r), fill=COLOR_WHITE)
display.image(image)
time.sleep(.01)

gitでダウンロードした接触検知ライブラリのフォルダ名には「-」が含まれていて通常のimport文では構文エラーになるためimport_moduleを利用してimportしています。
from importlib import import_module
xpt2046 = import_module('XPT2046-Python.xpt2046')
Touch = xpt2046.Touch

画面表示ライブラリadafruit_rgb_displayと接触検知ライブラリXPT2046-Pythonではピンの定義方法が異り、前者はdigitalioのDigitalInOutで、後者はgpiozeroのDigitalOutputDeviceで利用するピンを定義します。
# Pin Configuration
# cs_pin = DigitalInOut(board.D8) #original spi cs
cs_pin = DigitalInOut(board.D1)
dc_pin = DigitalInOut(board.D25)
rst_pin = DigitalInOut(board.D24)
led_pin = DigitalInOut(board.D23)
touch_cs = DigitalOutputDevice(7,active_high=False,initial_value=None)

接触検知ICは通信速度が早いと情報を読めなくなるため、画面描画ライブラリ生成時に指定する通信速度を1Mbpsにします。
この記事では4~8Mbpsだと通信は出来るものの取得できる値がおかしくなります。
# Create the ILI9341 display:
display = ILI9341(
spi,
cs=cs_pin, dc=dc_pin, rst=rst_pin,
width=240, height=320,
rotation=90,
baudrate=1000000 # OK
# baudrate=4000000 # can communicate but too fast
# baudrate=8000000 # can communicate but too fast
# baudrate=10000000 # can communicate but too fast
# baudrate=16000000 # bad
# baudrate=24000000 # bad
)

接触検知ライブラリのget_pointは複数回読み取り後の平均値を返す関係で応答が遅いので、raw_touchとnormalizeを利用して接点を求めています。
その後、求めた点を中心に半径2の白い点を描いています。
while True:
pos = xpt.raw_touch()
if pos is not None:
x, y = xpt.normalize(pos[0], pos[1])
print("%3d, %3d" % (x, y))
r = 2
draw.ellipse((y - r, x - r, y + r, x + r), fill=COLOR_WHITE)
display.image(image)
time.sleep(.01)

プログラム実行

プログラムを実行するとタッチ検知した箇所に丸が描かれる処理が始まります。
cd ~/gitprojects/pi-lcd-touch-draw
python3 touch-draw-ili9341.py


パネルによっては検知場所が大きくずれるものがあるものの大体良し

手元にタッチ機能付きのLCDが3枚あるので、それぞれプログラムを動かした状態で画面を突っついて検知箇所を表示しました。
3枚の動作結果を載せます。




3番のような異常箇所が存在するものは動作確認して除外が必要ですが、1番と2番の精度が基本であれば細かい操作ができそうな精度でした。

通信速度が早すぎる(4~8Mbps)と接触検知できない箇所が発生する

記事の初版は4Mbpsで通信したところ、精度が悪い結果になっていました。
コメントで指摘されたとおりそれは速すぎたようです。

3枚の動作結果を載せます。




全く反応しない箇所や反応しても意図しない座標として認識される箇所があったり、長押ししていると反応箇所が散らばったりして、利用するには不安な挙動でした。
得られる情報が異常になるため1Mbpsを越える速度での通信はお勧めしません。

おわり

問答無用で通信時に電位が切り替わる標準のCSピンの利用を避けつつ、通信速度を1Mbpsに落とすことで、タッチパネルの描画と接触検知を同時に動かせました。

本記事の内容では画像描画も1Mbpsになって遅いため、通信速度を描画とタッチ認識で切り替えられるライブラリを作成しました。
良かったらこちらの記事をご覧ください。

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

参考

タッチ検知のライブラリです
https://github.com/Luca8991/XPT2046-Python

LCDの描画に取り組んだときの記事です
Raspberry PiからpythonでSPI接続のLCDを利用

変更履歴

2023.07.08
「抵抗膜」を「低硬膜」と間違えていたので修正しました。
2023.07.12
配線図のCSのLCDとTouchが逆になっていたので修正しました。
描画半径として定義したrが使われていなかったので、使うプログラムに修正しました。
2023.07.17
コメントで指摘されたので通信速度を1Mbpsに落としたところ精度が上がりました。
精度に難があったのは通信速度がXPT2046にとって早すぎたのが原因だったので、速度を落として期待通りに接点が取れる情報を追加しつつ題名を「Raspberry Piでpythonを通して抵抗膜式のタッチパネルを利用したが、自分が試したパネルは精度に難があった」から「Raspberry Piでpythonを通して抵抗膜式のタッチパネルを動かしてみた(Luca8991/XPT2046-Python利用)」に変更しました。
2023.07.18
この記事で分かった不満を解消するべく作成したライブラリの紹介記事
抵抗膜式タッチ検知IC XPT2046をpythonで扱うライブラリXPT2046-asukiaaa-pythonの紹介
のリンクを追加しました。

6 件のコメント :

辻田 さんのコメント...

低硬膜→抵抗膜(Resistive)

ですね。上の抵抗膜と、下の抵抗膜とを、物理的に接触させることで変化する抵抗値を測定することで座標を検知するので、静電容量(Capasitive)方式のタッチパネルに比べると誤差要因や故障原因は多いですが、結果を見る限り残念ながら粗悪品だと思います。

辻田 さんのコメント...

Aliexpressのサイトを見てみましたが、タッチパネルに関する仕様が全く記載されていないので、良品として出荷できなかったパネルをくっつけて販売してるかも?って、私だったら思ってしまいます。

辻田 さんのコメント...

あと、気になったのは、XPT2046の動作はサンプリングクロック周波数が2MHzでサンプリング周期125kHzのようなので、8MHzのクロックは速過ぎるのでは?

http://aitendo3.sakura.ne.jp/aitendo_data/product_img/ic/touch/XPT2046/XPT2046-EN.pdf

LCDの動作時と、タッチパネルコントローラの動作時でクロックの周波数を変更しなくてはいけないかもです。

Asuki Kono さんのコメント...

コメントありがとうございます。

> 低硬膜→抵抗膜(Resistive)

指摘ありがとうございます。
抵抗としていたつもりが、「ていこうまく」で変換すると何故か「硬」の方が出てきてしまい、合っているだろうと読み飛ばして気づきませんでした。
修正しました。


> 良品として出荷できなかったパネルをくっつけて販売してるかも?

確かに、aliexpress店舗なので不良品を使っている可能性はありそうです。

Waveshareの公式店舗の商品なら試験に通ったものが使われていそうなので、気が向いたら試してみます。
3.2inch Resistive Touch Display (B) for Raspberry Pi, 320×240, SPI
3.5inch Resistive Touch Display (B) for Raspberry Pi, 480×320, IPS Screen, SPI


> XPT2046の動作はサンプリングクロック周波数が2MHzでサンプリング周期125kHzのようなので、8MHzのクロックは速過ぎるのでは?

情報ありがとうございます。
XPT2047は位置情報としてxとyを2バイトずつ受け取っているので、
125Khz * 8bit * 4byte = 4Mbps
となり、8Mbpsではなく4Mbpsで良さそうです。


> LCDの動作時と、タッチパネルコントローラの動作時でクロックの周波数を変更しなくてはいけないかもです。

この実現方法が気になるので調べてみます。

Asuki Kono さんのコメント...

AdafruitのSPIDeviceというクラスを使えば、通信先によって速度を変えれると分かりました。

adafruitのcircuit pythonでSPIの通信速度を通信先によって切り替える

さらに、通信速度を1Mbpsにすると、接触を認識可能な領域が広がるのを確認しました。
(4Mbpsではまだ速いようで8Mbpsと同様におかしな値になりました。)
タッチ用のライブラリを準備できたら記事を更新予定です。

Asuki Kono さんのコメント...

通信速度を1Mbpsにしたら期待通りに接触点を認識できるLCDが多かったという内容に記事を更新しました。
また、通信速度を通信先によって切り替えるadafruitのspi_deviceを内部で利用するXPT2046のライブラリを作成しました。

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

上記記事のライブラリを使うと描画速度は速いままタッチ検知できます。
頂いた情報と試行錯誤の集大成です。