※ 最低限のメモのようなもので、資料としてちゃんと整えておりません。あくまで参考までに。。
参考資料
準備物
- M5StickC Plus2
- 抵抗1MΩ x1
- ジャンパワイヤ
- ワニ口クリップ
システム全体のイメージ

植物に触れるとニャーと鳴くシステム
回路(最低限)
- 送信(チャージ): GPIO25
- 受信(センシング): GPIO26
- LED(デバッグ): 内蔵 LED がないので LCD 表示に変更
※ M5StickC Plus2 は 5 V じゃなく 3.3 V、直接指を触るなら 1 MΩ 以上の抵抗を送受信間に噛ませるのが安全。
G25 —-(1MΩ)—- G26
│
指
また「GND パッド」を用意しておくこと もう片方の指でGNDから伸びたピンを掴んでおく
原理
「RC delay」という物理現象を利用しています。
R (Resistor/抵抗): 1MΩの抵抗
C (Capacitor/コンデンサ): 人間の体(指)

参考:https://www.instructables.com/How-To-Use-Touch-Sensors-With-Arduino/
何が起きているのか?
- 送信 (Tx): ピンをHIGHにして電気を流します(水門を開けるイメージ)。
- 抵抗 (1MΩ): 抵抗があるため、電気はチョロチョロとしか流れません。
- 受信 (Rx): ここに「指(コンデンサ)」という「空のバケツ」が繋がっています。
- 遅延: バケツ(指)が大きいほど、Rxピンの電圧が「HIGH(満タン)」になるまでに時間がかかります。
// 立ち上がりまでカウント(タイム計測)
while (digitalRead(PIN_RX) != HIGH) {
t++;
}
これは、「バケツがいっぱいになるまでの時間をストップウォッチで計っている」という処理そのものです。つまり、基本的には、Arduinoはコンデンサ(つまりタッチセンサ)が充電されるのにどれくらいの時間がかかるかを計測しています
✔️ M5StickC Plus2版(動作する最小コード)
#include <M5StickCPlus2.h>
int t = 0;
int threshold = 300; // ← 必ず自分の環境で調整
// ピンアサイン
const int PIN_TX = 25; // パルス送信
const int PIN_RX = 26; // センサ値読む
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
pinMode(PIN_TX, OUTPUT);
pinMode(PIN_RX, INPUT);
M5.Display.setRotation(1);
M5.Display.setTextSize(2);
M5.Display.clear();
M5.Display.println("Touch Test");
}
void loop() {
t = 0;
// チャージ開始
digitalWrite(PIN_TX, HIGH);
// 立ち上がりまでカウント(タイム計測)
while (digitalRead(PIN_RX) != HIGH) {
t++;
if(t > 20000) break; // ← 無限ループ防止(重要)
}
// 放電
digitalWrite(PIN_TX, LOW);
delayMicroseconds(200);
// 表示
M5.Display.setCursor(0,30);
M5.Display.printf("t = %d \n", t);
// 「タッチ判定」
bool touched = (t > threshold);
M5.Display.setCursor(0,60);
if(touched){
M5.Display.println("TOUCH ");
} else {
M5.Display.println("--------");
}
delay(10);
}

ボタンで調整できるようにしておく
ボタンで threshold を ±50 ずつ調整できる版
- BtnA:+50
- BtnB:−50
- 画面上に現在の
thresholdも表示
という仕様
#include <M5StickCPlus2.h>
int t = 0;
int threshold = 300; // ← あなたの環境で良さそうな初期値
const int THRESH_STEP = 50; // ボタン1回で増減する量
// ピンアサイン
const int PIN_TX = 25; // パルス送信
const int PIN_RX = 26; // センサ値読む
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
pinMode(PIN_TX, OUTPUT);
pinMode(PIN_RX, INPUT);
M5.Display.setRotation(1);
M5.Display.setTextSize(2);
M5.Display.clear();
M5.Display.println("Touch Test");
}
void loop() {
M5.update(); // ★ボタン状態の更新を最初に
// --- threshold 調整 ---
if (M5.BtnA.wasPressed()) { // Aボタンで +50
threshold += THRESH_STEP;
}
if (M5.BtnB.wasPressed()) { // Bボタンで -50
threshold -= THRESH_STEP;
}
// 負の値やバカみたいな値にならないようにクランプ
if (threshold < 0) threshold = 0;
if (threshold > 20000) threshold = 20000;
// --- タッチ計測 ---
t = 0;
// チャージ開始
digitalWrite(PIN_TX, HIGH);
// 立ち上がりまでカウント(タイム計測)
while (digitalRead(PIN_RX) != HIGH) {
t++;
if (t > 20000) break; // 無限ループ防止
}
// 放電
digitalWrite(PIN_TX, LOW);
delayMicroseconds(200);
// --- 表示 ---
// 先頭行:threshold
M5.Display.setCursor(0, 0);
M5.Display.printf("thr=%5d \n", threshold);
// 次の行:t の値
M5.Display.setCursor(0, 30);
M5.Display.printf("t = %5d \n", t);
// タッチ判定
bool touched = (t > threshold);
M5.Display.setCursor(0, 60);
if (touched) {
M5.Display.println("TOUCH ");
} else {
M5.Display.println("--------- ");
}
delay(10);
}
これでその場で環境変わっても ボタン連打で threshold 調整→即挙動確認できるはず。
PCとの連携
M5側はtをひたすらシリアルに流す、PC側はそれを読んで「一定値超えたらニャー」の構成にしちゃいましょう。
1. M5StickC Plus2側(tをシリアル送信)
元コードに Serial.begin と Serial.println(t); を足しただけの版です。
#include <M5StickCPlus2.h>
int t = 0;
int threshold = 300; // ← あなたの環境で良さそうな初期値
const int THRESH_STEP = 50; // ボタン1回で増減する量
// ピンアサイン
const int PIN_TX = 25; // パルス送信
const int PIN_RX = 26; // センサ値読む
void setup() {
auto cfg = M5.config();
M5.begin(cfg);
// ★ シリアル開始(PC側と合わせておく)
Serial.begin(115200);
pinMode(PIN_TX, OUTPUT);
pinMode(PIN_RX, INPUT);
M5.Display.setRotation(1);
M5.Display.setTextSize(2);
M5.Display.clear();
M5.Display.println("Touch Test");
}
void loop() {
M5.update(); // ★ボタン状態の更新を最初に
// --- threshold 調整 ---
if (M5.BtnA.wasPressed()) { // Aボタンで +50
threshold += THRESH_STEP;
}
if (M5.BtnB.wasPressed()) { // Bボタンで -50
threshold -= THRESH_STEP;
}
// 負の値やバカみたいな値にならないようにクランプ
if (threshold < 0) threshold = 0;
if (threshold > 20000) threshold = 20000;
// --- タッチ計測 ---
t = 0;
// チャージ開始
digitalWrite(PIN_TX, HIGH);
// 立ち上がりまでカウント(タイム計測)
while (digitalRead(PIN_RX) != HIGH) {
t++;
if (t > 20000) break; // 無限ループ防止
}
// 放電
digitalWrite(PIN_TX, LOW);
delayMicroseconds(200);
// --- 表示 ---
// 先頭行:threshold
M5.Display.setCursor(0, 0);
M5.Display.printf("thr=%5d \n", threshold);
// 次の行:t の値
M5.Display.setCursor(0, 30);
M5.Display.printf("t = %5d \n", t);
// タッチ判定(表示用)
bool touched = (t > threshold);
M5.Display.setCursor(0, 60);
if (touched) {
M5.Display.println("TOUCH ");
} else {
M5.Display.println("--------- ");
}
// ★ tの値をPCへ送る(1行1値)
Serial.println(t);
delay(10);
}
これで、PCのシリアルモニタを開くとtがズラッと流れてくるはず。
2. PC側 Python(tを読んで「ニャー」再生)
ls /dev/tty.*
でポート確認
4. PC側の実装 (Python)
M5StickC Plus2から送られてくるシリアルデータをPythonで受信し、しきい値を超えたら効果音を再生します。
環境構築 (uvを使用する場合)
Pythonのパッケージ管理ツール uv を使用して環境を構築する例です。
0. uvのインストール(未導入の場合)
Bash
curl -LsSf https://astral.sh/uv/install.sh | sh
1. プロジェクトフォルダの作成と移動
Bash
mkdir m5_meow
cd m5_meow
2. 仮想環境の作成と有効化
Bash
uv venv .venv
source .venv/bin/activate
# Windowsの場合は .venv\Scripts\activate
3. ライブラリのインストール 今回はシリアル通信用の pyserial のみ使用します。
Bash
uv pip install pyserial
Pythonスクリプト
同じフォルダに音声ファイル(例: meow.wav)を用意してください。 以下のコードを meow_serial.py として保存します。
注意: このスクリプトは macOS 標準の afplay コマンドを使用しています。Windowsの場合は winsound など、環境に合わせた再生方法に変更する必要があります。
Python
import serial
import time
import subprocess
# ===== 設定 =====
# ls /dev/tty.* 等で確認した自身のポート名に書き換えてください
SERIAL_PORT = '/dev/tty.usbserial-XXXXXXXX'
BAUDRATE = 115200
THRESHOLD = 220 # t がこれを超えたら再生(M5側の設定と合わせる)
REFRACTORY_SEC = 0.5 # 連続再生防止(不応期)
WAV_FILE = "meow.wav" # 同じフォルダに置く音声ファイル
# =================
def play_meow():
# macOS 標準の CLI プレイヤーを利用
# Popen にして非同期再生(処理をブロックしない)
try:
subprocess.Popen(["afplay", WAV_FILE])
except FileNotFoundError:
print("ERROR: afplay が見つかりません。macOS以外ですか?")
except Exception as e:
print("ERROR: 再生失敗:", e)
def main():
last_meow_time = 0.0
# シリアルポートを開く
try:
with serial.Serial(SERIAL_PORT, BAUDRATE, timeout=1) as ser:
print(f"Listening on {SERIAL_PORT} ...")
while True:
# 1行読み込み、デコード
line = ser.readline().decode("utf-8", errors="ignore").strip()
if not line:
continue
try:
t = int(line)
except ValueError:
# 数字以外が来たらスキップ
continue
print(f"t = {t}")
now = time.time()
# しきい値判定 & 不応期チェック
if t > THRESHOLD and (now - last_meow_time) > REFRACTORY_SEC:
print("MEOW!")
play_meow()
last_meow_time = now
except serial.SerialException as e:
print(f"Port Error: {e}")
print("ポート名が正しいか、M5が接続されているか確認してください。")
if __name__ == "__main__":
main()
ざっくり動作イメージ
- M5 が
Serial.println(t);で 0〜数千くらいの数字を1行ずつ送る。 - Python がその数字を
intにして受け取る。 t > THRESHOLDなら猫のwavを再生。REFRACTORY_SECで連続「ニャー」暴発を防止。
実行方法
- M5StickC Plus2をPCに接続します。
- スクリプト内の
SERIAL_PORTを正しいポート名に書き換えます。 - ターミナルで以下を実行します。
Bash
python meow_serial.py
植物に触れて、ターミナルに数値が表示され、PCから音が鳴れば成功です。
完成
触るとニャーと鳴きます