以下の内容はhttps://nomolk.hatenablog.com/entry/2025/01/18/234221より取得しました。


静電容量式タッチセンサの仕組みでカニをスイッチにする/ArduinoをRaspberry PiのGPIOに接続する【動画解説】

ちょっと前にYoutubeをはじめたのですが、その技術解説記事です。
まずは1本目。

飲んだら即死するお酒を作る
www.youtube.com

この動画で特筆すべきことは下記の3点ですかね。

  • Raspberry Piを心電図にするプログラムをChatGPTに書いてもらう
  • カニやトマトや水を静電容量式タッチセンサにする
  • ArduinoをRaspberry PiのGPIOに接続する

順に解説していきます。

Raspberry Piを心電図にするプログラムをChatGPTに書いてもらう

動画で使っているのは家に転がっていたRaspberry Pi 3なので、ちょっと古い機種です。取り付けているモニタはこの記事で紹介したもの。

Raspberry Pi で使えるポータブルモニタの決定版が出てた(タッチスクリーン付き、GPIOを占有しない、ケース付き、約3000円) - nomolkのブログ

GPIO端子を占有しない(横に延長してくれる)のですごく便利だったのですが、これはもう終売っぽく、他に似たような製品も出ていないみたいです。
(同じものが手に入ったとしてもRaspberry Pi 4以降はHDMI端子がmicroHDMIになってしまったので使えないのですが(ケーブル接続すれば使えるか))

いまだと同じようなものがないので、こういう

  • Freenove
リボンケーブルでつなぐタイプを買うと、GPIOを使わずに接続できて良さそうです。

ChatGPTが普通と違いますが

AIお姉ちゃん化しているからです。こちらを見てください。
nomolk.hatenablog.com

心電図は(動画では明言してないけど多分伝わってると思いますが)心電位をはかっているのではなくそれっぽいグラフを書いているだけです。
プロンプトはこんな感じ。

raspberry piを使って、ホルス心電図を模したシステムを作成します。実際に心電位を測るのではなく、疑似的なものです。Pythonのコードを書いて!
・心電図のようなパターンをフルスクリーンで画面に表示。緑色の折れ線グラフ。
・画面の左からグラフをゆっくり描写し、右端に達したらスクロール
・グラフが大きく振れたピークの位置でピッと音が鳴る。
・GPIO17への入力がHIGHになると、グラフの振幅が徐々に小さくなり、2秒後に0固定になる
・0固定後は1オクターブ高くピーという持続音を鳴らす。
・GPIO17への入力がLOWになると、グラフは元に戻る
・グラフのパターンのサイクルは1分間に90回のスピード

一発では全然うまくいかなくて、このあと動かないところを指摘して直してもらったり、自分でデバッグしたりして5時間ほどかかったので参考程度に。

最終的なコードはこちら。

import pygame
import RPi.GPIO as GPIO
import sys
import time

# 初期設定
WIDTH, HEIGHT = 800, 480
LINE_COLOR = (0, 255, 0)
BG_COLOR = (0, 0, 0)

TRIGGER_PIN = 17  # GPIOピン番号
BPM = 90  # 1分間に90回のサイクル
HEARTBEAT_INTERVAL = int(WIDTH * BPM / 60)  # BPM90の正確な間隔

X_SCALE = 12

# pygame初期化
pygame.init()
screen = pygame.display.set_mode((WIDTH, HEIGHT), pygame.FULLSCREEN)
clock = pygame.time.Clock()
pygame.mixer.init()

# 音の読み込み
sound_heartbeat = pygame.mixer.Sound('sine493.wav')  # 平時の音
sound_heartbeat.set_volume(0.7)
sound_flatline = pygame.mixer.Sound('sine987.wav')   # 心停止時の音
sound_flatline.set_volume(0.7)

# GPIO設定
GPIO.setmode(GPIO.BCM)
GPIO.setup(TRIGGER_PIN, GPIO.IN, pull_up_down=GPIO.PUD_DOWN)

# 心電図波形パターン
def ecg_wave_pattern(x, amplitude=1.2):
    t = x % 150
    if 0 <= t < 25:  # P波
        return int(HEIGHT // 2 - 10 * amplitude * (t / 25))
    elif 25 <= t < 35:  # PRセグメント
        return HEIGHT // 2
    elif 35 <= t < 38:  # Q波(少し急下降)
        return HEIGHT // 2 + 20 * amplitude * (t - 35)
    elif 38 <= t < 43:  # R波(やや緩やかな上昇)
        return HEIGHT // 2 - 80 + 40 * amplitude * (t - 38)
    elif 43 <= t < 50:  # S波(短い下降)
        return HEIGHT // 2 + 30 * amplitude * (t - 43)
    elif 50 <= t < 63:  # T波
        return int(HEIGHT // 2 - 85 * amplitude * (1 - ((t - 50) / 40)))
    elif 63 <= t < 95:
        return int(HEIGHT // 2 - 5 * amplitude * (1 - ((t - 60) / 40)))
    else:
        return HEIGHT // 2

def draw_ecg(step, amplitude=1.0):
    points = [(x * X_SCALE, graph_data[x]) for x in range(len(graph_data))]
    pygame.draw.lines(screen, LINE_COLOR, False, points, 2)
    pygame.display.flip()


def update_ecg_data(step, amplitude):
    """グラフのデータを更新"""
    # 新しい点を計算して追加
    new_y = HEIGHT // 2 if amplitude == 0.0 else ecg_wave_pattern(step * X_SCALE, amplitude)
    graph_data.append(new_y)
    
    # 古いデータを削除し、スクロールを実現
    if len(graph_data) > WIDTH // X_SCALE:
        graph_data.pop(0)

def play_heartbeat_at_peak(step):
    current_value = ecg_wave_pattern(step * X_SCALE)
    if current_value > 350:
        sound_heartbeat.play()

def main():
    step = 0
    last_value = HEIGHT // 2  # 最初は平常値
    amplitude = 1.2
    running = True
    triggered = False
    trigger_time = None
    last_heartbeat_time = 0  # 最後に音を鳴らした時間を追跡
    flatline_played = False  # 心停止音の再生管理
    high_playing = False  # 高い音が再生中かどうか
    is_flatline = False

    global graph_data
    graph_data = [HEIGHT // 2] * (WIDTH // X_SCALE)  # 初期データを中央の線で埋める

    while running:
        if GPIO.input(TRIGGER_PIN) and not triggered:
            trigger_time = time.time()
            triggered = True
            print("心停止トリガー受信")
            amplitude = 0  # 振幅を0にする
            
        if triggered and time.time() - trigger_time < 2:
            # 時間経過に合わせてamplitudeを減らす
            amplitude = max(0, 1.2 - (time.time() - trigger_time) * (0.4))  # 2秒間で0に近づける

        if triggered and time.time() - trigger_time > 3:
            if not flatline_played:
                print("心停止")
                sound_flatline.play(loops=-1)  # 心停止音を鳴らしっぱなしにする
                flatline_played = True
            is_flatline = True
            amplitude = 0  # 心停止後、振幅を完全に0にする

        
        screen.fill(BG_COLOR)  # 毎フレーム最初に背景をリセット
        update_ecg_data(step, amplitude)
        draw_ecg(step, amplitude)
        
        # 心拍の音を鳴らす
        if amplitude > 1:
            play_heartbeat_at_peak(step)

        
        step += 1
        # clock.tick(60)  # 60fpsで更新

        # イベント処理
        for event in pygame.event.get():
            if event.type == pygame.QUIT or (event.type == pygame.KEYDOWN and event.key == pygame.K_ESCAPE):
                running = False

        if GPIO.input(TRIGGER_PIN) == GPIO.LOW:
            if is_flatline:
                sound_flatline.stop()
                flatline_played = False
                is_flatline = False
                amplitude = 1.2
                triggered = False
                print("蘇生")

    pygame.quit()
    GPIO.cleanup()
    sys.exit()

if __name__ == "__main__":
    main()

GPIO17はプルアップ抵抗が有効になっているので、3.3VにつなぐとHIGHになって死にます。死にますというのはRaspberry Piが壊れるということではなくて心電図が止まるということです。

あ、あと音は普通に音声出力端子にスピーカーをつないでやれば鳴るのですが、なぜか音がめっちゃ割れます。原因は不明です。

カニやトマトや水を静電容量式タッチセンサにする

カニスイッチです。動画中でも説明していますが、スマホのタッチパネルと同じ静電容量式タッチセンサという仕組みで、電気を通すものや水分が多いものをスイッチにできます。

画面右下にギリギリ写っていますが、Arduino Uno R3を使っています。Uno R3を使うとカニスイッチはとても簡単に作れます。
回路的にはこんな感じ

クリップの先にカニやスイッチにしたいものをつないでください。その根元の抵抗は100kΩ(キロですキロ)。LEDの方の抵抗は100Ωとか150Ωとか適当にどうぞ。

プログラムの方ですが、自分で書くとかなり複雑なんですけどArduinoならライブラリが用意されています。
「ツール」→「ライブラリの管理...」→検索欄に「CapacitiveSensor」で検索→「Capacitive Sensor by Paul Badger…」をインストール、で入れられます。あとは下記のスケッチを書き込むだけ。

#include <CapacitiveSensor.h>
CapacitiveSensor cs = CapacitiveSensor(4,2); //センサーの定義
const int offLedPin = 8;
const int onLedPin = 9;
long value = 0; //センサー値の格納用変数

void setup(){
  Serial.begin(9600);
  pinMode(onLedPin, OUTPUT);
  pinMode(offLedPin, OUTPUT);
}

void loop(){
  value = cs.capacitiveSensor(30); //センサー値の読み込み
  Serial.println(value);
  delay(100);
  if (value > 2) {
    digitalWrite(offLedPin, LOW);
    digitalWrite(onLedPin, HIGH);
  } else {
    digitalWrite(offLedPin, HIGH);
    digitalWrite(onLedPin, LOW);
  }
}

……なのですが、一つ注意点があります。2025/01時点でライブラリがArduino Uno R4には対応していないみたいで、Uno R3でやる必要があります。
頼む、対応してくれ~~

ArduinoをRaspberry PiのGPIOに接続する

最初Raspberry Pi用にPythonで静電容量センサのプログラムを書こうと思ったのですが、心電図で思いのほか時間を食ってしまったので、やめてArduinoをそのままつないでしまうことにしました。
ArduinoとRaspberry Piをつなぐ方法はいろいろあって、検索してみるとシリアル通信させたりしてることが多いんだけど、今回はON/OFFだけ送信できればいいので単純にGPIOにHIGH/LOWで送ります。

注意点が2つあって、

  • ArduinoとRaspberry PiのGND同士をつなぐこと
  • Arduinoのアナログピンは5V、Raspberry PiのGPIOは3.3Vなので、直結しないで抵抗を使って分圧すること。

ということで、さっきのカニスイッチの回路をRaspberryPiに接続するとこんな感じになります。

Arduino側はさっきのスケッチだとスイッチの状態の出力をしていないので、10番ピンから送るようにしたのがこちら。

#include <CapacitiveSensor.h>
CapacitiveSensor cs = CapacitiveSensor(4,2); //センサーの定義
const int offLedPin = 8;
const int onLedPin = 9;
const int rpiSendPin = 10;
long value = 0; //センサー値の格納用変数

void setup(){
  Serial.begin(9600);
  pinMode(onLedPin, OUTPUT);
  pinMode(offLedPin, OUTPUT);
  pinMode(rpiSendPin, OUTPUT);
}

void loop(){
  value = cs.capacitiveSensor(30); //センサー値の読み込み
  Serial.println(value);
  delay(100);
  if (value > 2) {
    digitalWrite(offLedPin, LOW);
    digitalWrite(onLedPin, HIGH);
    digitalWrite(rpiSendPin, HIGH);
  } else {
    digitalWrite(offLedPin, HIGH);
    digitalWrite(onLedPin, LOW);
    digitalWrite(rpiSendPin, LOW);
  }
}

以上、ざっくり解説でした。
参考になったらチャンネル登録と高評価お願いします。またね。

www.youtube.com




以上の内容はhttps://nomolk.hatenablog.com/entry/2025/01/18/234221より取得しました。
このページはhttp://font.textar.tv/のウェブフォントを使用してます

不具合報告/要望等はこちらへお願いします。
モバイルやる夫Viewer Ver0.14