👇

電子工作でタッチセンサーを作る

2023/12/19に公開

ラズピコでタッチセンサーを作ってみました。
理屈がわかってしまえば簡単ですが、使い物になるまであれこれ工夫しました

理屈

人間の体は微量ながら電気を蓄えることができます。

理屈としては、人間の体をコンデンサーとしてRC直列回路を形成し、電圧の立ち上がりの遅れを読み取って、人体が回路中に入っているかを読み取ります。

RC直列回路の実験

ラズピコは以下のコードで16番ピンから10msごとにオンとオフを切り替えます
(PWMを使ってもいいのですが、この後がありますので敢えてforとsleepでオンオフを繰り返しています)

pin16 = Pin(16,Pin.OUT)
for i in range(10**10):
    pin16.on()
    sleep_ms(10)
    pin16.off()
    sleep_ms(10)

まずは抵抗だけ繋げた回路で見てみましょう。
オシロスコープで電圧の昇降を見ると、Pinのオンオフに伴って電圧はシャープに昇降します。

次にコンデンサーを繋いでみましょう。
コンデンサーへの充電&放電がありますので、電圧の昇降はシャープではなくなります。

人体で触れる

次にコンデンサーを外して、指で触れてみましょう。

コンデンサーの時と違い、触れている時も触れていない時も電圧の昇降はシャープです。

この理由は、アルミ電解コンデンサーの静電容量が100uFほどあるのに対して人体は100pFと静電容量が非常に小さく、あっという間に人体が満充電されてしまうためです。

これでは触れている時と触れていない時で波形に違いが出ず、タッチセンサーとして困ります。

対策としては、抵抗の値をもっと大きくして(100k〜1MΩ)流れる電流量を少なくすることで、人体が充電される時間を稼ぐと良いです。

抵抗を1MΩにすると、以下のような波形になりました。

アナログ値を読み取る

オシロスコープで値の傾向を掴めましたので、ラズピコで値を読み取ってみましょう。

今回の実験では、ピンをオンした直後と10ms経過した時を読み取りました。
(変数名はpreとpostとしています。もう少し良い変数名があれば良いですが・・・)

a0=ADC(0)

for i in range(10**10):
    pin16.on()
    pre = a0.read_u16()
    sleep_ms(10)
    post = a0.read_u16()
    if i%10=0:
        print(f'{pre}, {post}, {post-pre}')
    pin16.off()
    sleep_ms(10)

触れていない時

2240, 2272, 32
2240, 2256, 16
2272, 2304, 32
2192, 2208, 16
2256, 2224, -32
2224, 2240, 16

触れている時

1792, 1824, 32
3104, 2000, -1104
1792, 2288, 496
1712, 2368, 656
1600, 2320, 720
1744, 1888, 144
2144, 2000, -144
1504, 1680, 176

人体が触れているかの判定

ここからは実装の工夫です。

アナログ値を読み取って、タッチしているかどうかを判定するのですが、postとpreの値だけではタッチしているかどうかの判定を一律に決めるのは難しそうです。
また、postとpreを引き算した値を見てみましたが、これもばらつきが大きく、触れているかどうかの線引きは難しそうです。

もう少し大きな視点で値を見てみましょう。
触れている時の値は、時たま小さい時があるとはいえ、全体的な傾向としては明らかに触れていない時よりも大きいです。
そこで、10回測定したばらつきを見ることで、触れているかどうかの判定ができそうです。
(もちろん1回の判定のために10回測定する必要があるため、少し解像度が悪くなります)

以下の例では分散を見ることで、触れているか判定しています。

from machine import Pin, PWM, ADC
from utime import sleep_ms

led = Pin(25,Pin.OUT)
led.on()

pin16 = Pin(16,Pin.OUT)

a0=ADC(0)

datas = []

for i in range(10**10):
    pin16.on()
    pre = a0.read_u16()
    sleep_ms(10)
    post = a0.read_u16()
    datas.append(post-pre)
    if i%10==0:
        mean = sum(datas) / len(datas)
        variance = sum((xi - mean) ** 2 for xi in datas) / len(datas)
        touch = variance>10000
        led.value(touch)
        datas=[]
    pin16.off()
    sleep_ms(10)

工夫の余地はまだありそうですが、とりあえずきちんと動くことを確認できたのでヨシとします。

Discussion