はじめに

今回は視覚を使わない電子工作について解説します。 年明けに購入したマイコン「Raspberry Pi Pico」で簡単な制御ができるようになったので、その備忘録をまとめました。 ものづくりの面白さが少しでも伝われば幸いです。

方針

今回ははんだ付けをせず、ブレッドボードとジャンパーワイヤーを使って試作を進めます。 ブレッドボードは表面にたくさんの穴が開いた長方形の板です。中央の溝を挟んで左右の穴が内部でつながっており、両端には電源用のラインが並んでいます。ここにジャンパーワイヤーという細い電線を差し込むだけでマイコンとセンサーを電気的につなぐことができます。 接着が必要ないため納得がいくまで何度でもやり直せます。 ちなみに全盲ではんだ付けを実践されている方の記事を拝見したことがあります。僕はまだ未経験ですがいずれチャレンジしてみたいと思っています。

マイコンと部品の購入

今回は通販の秋月電子通商を使いました。スクリーンリーダー環境でも問題なく検索も購入もできます。 商品ページにはデータシートやマニュアルの PDF が掲載されているので購入前に詳しくチェックできます。 マイコンは「Raspberry Pi Pico 2 WH」を選びました。Wi-Fi/Bluetooth 対応モデルです。ピンヘッダーがはんだ付けされているのでブレッドボードに刺してすぐに使えます。

その他、以下の商品もあわせて購入しました。

  • マイクロ USB ケーブル: データ転送対応のもの。本体に付属していないため必須です
  • チルトセンサー: 3.3V で動作するもの(Pico の出力電圧に合わせるため)。
  • アクティブブザー(圧電ブザー): 3.3V で動作するもの(Pico の出力電圧に合わせるため)。
  • ブレッドボード: 400 ~ 800 穴のサイズ
  • ジャンパーワイヤー: オス-オスとオス-メスの 2 種類

静電気対策について

視覚を使わずに作業を行う場合指先で部品の形状や配置を確認するため、どうしても電子部品に触れる機会が多くなります。 しかし電子部品はとてもデリケートです。微細な電気信号で動くため静電気による瞬間的な高電圧で内部回路が破壊される恐れがあります。 そのため事前の静電気対策をすることをおすすめします。

静電気は髪の毛や衣服がこすれることで体に蓄積(帯電)します。特に冬場は空気が乾燥し電気が逃げにくくなるため注意が必要です。 その状態で電位差のあるものに触れると電気が一気に流れ出す「放電」が起きます。ドアノブで「バチッ」とする現象がまさにこれです。

手軽な対策としては作業前に大きな金属に触れて電気を逃がす方法があります。机のメッキされていない金属部分や窓のアルミサッシなどは電気が逃げやすいため有効です。 また、より確実な方法として「静電防止リストストラップ」の使用も有効です。これは手首に巻きコードの先をアースに接続して使用します。 内部に 1MΩ 程度の抵抗が入っているため人体の静電気をゆっくりと安全に逃がし電子部品を守ってくれます。

Raspberry Pi Pico 2 WH の外見

  • 電子部品はチャックつきの袋に入っています。静電気対策になるので袋は捨てずに取っておくことをおすすめします。
  • pico 本体は基板含めても親指くらいの長方形です。最初は導電性のスポンジに刺さっており合計 40 本のピンが保護されています。
  • 長方形の短い辺の一方にマイクロ USB ポートがあります。この記事ではこのポートがある方を「上」として説明します。
  • 裏面には左右に 20 本ずつ、合計 40 本のピンが並んでいます(詳細なピン配置は後述します)
  • 表面上部、マイクロ USB ポートのすぐそばに BOOTSEL というカチッと押せるボタンがあります。

Raspberry Pi Pico 2 WH のピン配置

USB ポートがある方を「上」とし、ボタンがある面を「表」にして、左上の 1 番ピンから反時計回りに順に説明します。

左列(1 番〜20 番:上から下へ)

左列は一番上から順に、以下のようになっています

  1. GP0(左列の一番上)
  2. GP1
  3. GND
  4. GP2
  5. GP3
  6. GP4
  7. GP5
  8. GND
  9. GP6
  10. GP7
  11. GP8
  12. GP9
  13. GND
  14. GP10
  15. GP11
  16. GP12
  17. GP13
  18. GND
  19. GP14
  20. GP15(左列の一番下)

右列(21 番〜40 番:下から上へ)

右下の角から順に、上へ向かって数えます

  1. GP16(右列の一番下)
  2. GP17 23.GND
  3. GND
  4. GP18
  5. GP19
  6. GP20
  7. GP21
  8. GND
  9. GP22
  10. RUN
  11. GP26 / ADC0
  12. GP27 / ADC1
  13. AGND
  14. GP28 / ADC2
  15. ADC_VREF
  16. 3V3
  17. 3V3_EN
  18. GND
  19. VSYS
  20. VBUS(右列の一番上)

注意点

VBUS(40 番ピン)には USB ポートからの 5V が直接流れています。 Pico の多くのピンは 3.3V 対応であるため誤って他のピンに接触させると故障の原因になります。配線の際は十分にご注意ください。

ファームウェアの書き込み

今回はプログラミング言語に MicroPython を使用するためファームウェアの書き込みが必要です。 MicroPython の公式サイトから最新のファームウェアをダウンロードします。 サイトにアクセスし「Release」のところにある「RPI_PICO2_W-20251209-v1.27.0.uf2」のような uf2 ファイルをダウンロードします。(※バージョン番号は最新のものを選んでください)

次に pico 本体の表面上部にある BOOTSEL ボタンを押しながら PC とマイクロ USB ケーブルで接続します。接続したらボタンから指を離しても大丈夫です。 正しく認識されると PC 側で「RP2350」という名前の USB ドライブ(外付けストレージ)として開けるようになります。エクスプローラでそのフォルダ「RP2350」を開き先程ダウンロードした uf2 ファイルをコピーします。 ファイルのコピーが終わると、Pico は自動的に再起動します。これで MicroPython を使う準備は完了です。

VS Code に拡張機能をインストールする

今回はエディタに VS Code(Visual Studio Code) を使用します。スクリーンリーダーに対応しているためキーボードのみで操作できます。 以下の手順で pico + MicroPython のプログラミングに必要な拡張機能をインストールします。

  1. まず Ctrl+Shift+X を押して拡張機能ビューへ移動します。
  2. フォーカスが検索欄に当たっている状態で「micro pico」と入力します。
  3. Tab キーを何度か押すと「拡張リスト」と読まれるので上下矢印キーで選択します。おそらく一番上に「MicroPico, 4.3.4」のように最新バージョンが表示されます。
  4. Tab キーをもう一度押すとインストールボタンがあるので Enter キーで決定します。
  5. すると拡張機能がインストールされます。

動作確認

  1. Ctrl+Shift+P または F1 キーを押します。コマンドパレットが開きます。
  2. 「pico」と入力して結果を絞り込みます。
  3. 上下矢印キーを使って「MicroPico: Connect」というコマンドを選んで Enter キーで決定します。
  4. すると pico 本体との通信が開始されます。
  5. F6 キーを数回押し、フォーカスを「ステータスバー」に合わせます。
  6. 「ステータスバー」と読み上げられたら、左右矢印キーで項目を移動します。
  7. 「check Pico Connected, Toggle board connection」のように読まれる項目があれば接続成功です。

さらに F6 キーを何度か押すと「ターミナル REPL」のように読まれます。これはチャットのように対話式にプログラムを実行できるターミナルです。 試しに「1+1」と入力すると 2 と返ってきます。これは pico 本体で計算した結果がマイクロ USB ケーブルを通って PC のこのターミナルに出力されているということです。 次にimport machine; print(machine.ADC(4).read_u16())と入力すると pico 本体の内蔵温度センサーから電圧を取得して表示できます。 このように REPL を使えばファイルを作成しなくても手軽に動作確認ができるのでセンサーのテストなどに非常に便利です。

アクティブブザーを鳴らす

アクティブブザー(圧電ブザー)は電圧をかけるだけでピーッと音が鳴るシンプルな電子部品です。 本体から 2 本のピン(足)が伸びており、長い方がプラス、短い方がマイナスです。

接続方法は以下の通りです。

  1. Pico 本体をブレッドボード中央の溝をまたぐように差し込みます。マイクロ USB ポートが上端(自分から遠い側)に来るように配置してください。
  2. 左列の上から 3 番目のピン(GND)からブレッドボード左端の電源ラインへジャンパーワイヤーでつなぎます。これで左端のラインがすべて GND(マイナス)になります。
  3. 左列の一番下にあるピン(20 番ピン:GP15)と同じ行の穴にブザーの長い足(プラス)を刺します。
  4. ブザーの短い足(マイナス)は中央の溝を飛び越えて手順 2 で作成した左端の GND ラインに刺します。
  5. ブザー自体が溝をまたぐような形になり足が左右に大きく開く配置になります。

次に PC 側でプログラムを書き込む準備をします。 PC にプロジェクト用の新しいフォルダを作成し VS Code で開きます。 VS Code のコマンドパレット(Ctrl+Shift+P または F1 キー)から「MicroPico: Initialize MicroPico project」を実行します。 これで開発に必要な設定ファイルが自動作成されます。

次に「main.py」という名前で新しいファイルを作成し以下のコードを記述します。

from machine import Pin
import time

buzzer = Pin(15, Pin.OUT)
buzzer.value(1)
time.sleep(1)
buzzer.value(0)

そしてコマンドパレットから「MicroPico: Run current file on Pico」を実行します。 アクティブブザーが「ピー」と 1 秒間鳴れば成功です!

コードを見て分かる通り pico はただ指定されたピンの電圧を上げ下げしてるだけです。接続している相手が何の部品なのかまでは認識していません。 このようにハードウェアの制御が抽象化されているため同じ書き方でも様々なタイプの電子部品に対応できるわけです。面白いですね。

チルトセンサーとブザーを組み合わせて防犯アラームを作る

チルトセンサーは同じ長さのピンが 2 本伸びていて振るとカチカチと音が鳴ります。内部の金属球が動くとスイッチが入ったり切れたりするので揺れや傾きを検知できます。 これを先ほどのアクティブブザーと組み合わせれば揺れを検知してアラームが鳴る装置を作ることができます。

配線は先ほどのブザーの構成をベースに進めます

  1. ブザーの確認: アクティブブザーが GP15 と GND につながっていることを確認します。
  2. 信号線の接続: 新たに GP13(左下から 4 番目のピン)に、ジャンパーワイヤー(オス-メス)を刺します。
  3. GND の接続: ブレッドボード上の GND(マイナス)ライン にもう 1 本のジャンパーワイヤー(オス-メス)を刺します。
  4. センサーの接続: 用意した 2 本のワイヤーの先(メス側)にチルトセンサーの足をそれぞれ差し込めば完成です。。

チルトセンサーをブレッドボードに直接刺さずワイヤーで延長しています。 これにより、ブレッドボード全体を揺らさなくても、センサーを手で傾けるだけで自由に動かせるようになります

main.py は以下のように変更します。

import machine, time

# ピン設定
tilt_sensor = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
buzzer = machine.Pin(15, machine.Pin.OUT)

print("Waiting...")

try:
    last_time = 0
    # センサーをポーリングする
    while True:
        if tilt_sensor.value() == 0:
            # ブザーを鳴らす
            buzzer.value(1)
            print("Detected")
            # チャタリング防止のために少し待つ
            time.sleep_ms(500)
        else:
            # ブザーを止める
            buzzer.value(0)
        time.sleep_ms(100)
except KeyboardInterrupt:
    # Ctrl+Cが押された時
    print("Stopping...")
finally:
    # 最後にブザーを止める
    buzzer.value(0)
    print("Done")

このプログラムはチルトセンサーを定期的にチェック(ポーリング)して変更があったらブザーを鳴らすシンプルな仕組みを実現しています。チャタリングと呼ばれるセンサーの多重検出を防止するために検出後に少しスリープしています。 コマンドパレットから「MicroPico: Run current file on Pico」を実行するとセンサーの監視が始まります。 チルトセンサーを揺らしたり傾けるとブザーがピーッと鳴ります。 中断したい場合は Ctrl + C キーを押します。

このように電子部品を組み合わせていくことで、実現できるアイデアは一気に広がります。 例えば距離センサーと振動モーターを組み合わせれば、障害物との距離を振動の強さで伝えるデバイスが作れるし、今回使ったチルトセンサーとブザーを応用すればボールが当たると音が鳴りスコアが加算される体験型ゲームも作れます。 何を組み合わせ、どんな仕組みを作るかはアイデア次第です。

Wi-Fi に接続して WEB サーバーを立てる

Raspberry Pi Pico 2 W は Wi-Fi モジュールが搭載されているのでルーターの SSID とパスワードが分かれば自宅の Wi-Fi 経由でインターネットに接続できます。 先ほどの防犯ブザー機能をさらに拡張して、Pico を Web サーバーとして動作させ、PC やスマホのブラウザからセンサーの検知回数を確認できるようにします。

前回の構成をそのまま利用するため配線に変更はありません。アクティブブザーが GP15、チルトセンサーが GP13 に接続されていることを確認してください。 main.py は以下のように変更します。 ※4 行目の SSID と 5 行目のパスワードは自宅の Wi-Fi 設定に合わせて変更してください。

import sys, network, machine, time, asyncio

# Wi-Fi設定(自宅の環境に合わせて変更してください)
SSID = "********"
PASSWORD = "********"

# ピン設定
tilt_sensor  = machine.Pin(13, machine.Pin.IN, machine.Pin.PULL_UP)
buzzer = machine.Pin(15, machine.Pin.OUT)

# センサー検出回数
count = 0

# 検出回数をカウントするタスク
async def counter_task(delay_ms = 500):
    global count
    # センサーをポーリングする
    while True:
        if  tilt_sensor .value() == 0:
            count += 1
            print(f"Count: {count}")
            # チャタリング防止のために少し待つ
            await asyncio.sleep_ms(delay_ms)
        await asyncio.sleep_ms(100)

# ブザーを鳴らすタスク
async def buzzer_task():
    last_count = -1
    # countをポーリングする
    while True:
        if count  !=  last_count:
            last_count = count

            # ブザーを鳴らす
            for _ in range(3):
                buzzer.value(1)
                await asyncio.sleep_ms(50)
                buzzer.value (0)
                await asyncio.sleep_ms(25)
        await asyncio.sleep_ms(100)

# WEBリクエストの処理
async def server_handler(reader, writer):
    try:
        req = await reader.readline()
        if not req: return

        path = req.decode().split()[1]
        while await reader.readline() != b"\r\n": pass

        if path == "/events":
            # SSEで最新の状態を送信する
            writer.write(
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/event-stream\r\n"
                "Cache-Control: no-cache\r\n"
                "Connection: keep-alive\r\n\r\n"
            )
            await writer.drain()

            # countをポーリングする
            last_count = -1
            while True:
                if count != last_count:
                    last_count = count
                    try:
                        writer.write(f"data: {count}\n\n")
                        await writer.drain()
                    except: break
                await asyncio.sleep(1)
        else:
            # トップページを表示する
            html = f"""<!DOCTYPE html>
<html>
<meta charset="utf-8">
<h2>センサー検知回数:</h2>
<h3 id="stat">{count}</h3>
<script>
const es = new EventSource("/events");
es.onmessage = e => document.getElementById("stat").innerText = e.data;
es.onerror = () => console.log("SSE disconnected");
</script>
</html>
"""
            writer.write(
                "HTTP/1.1 200 OK\r\n"
                "Content-Type: text/html\r\n\r\n" + html
            )
            await writer.drain()
    finally: await writer.wait_closed()

# Wi-Fi接続を待つ処理
async def wait_connection(wlan):
    while not wlan.isconnected():
        await asyncio.sleep_ms(500)


# Wi-Fiに接続する処理
async def connect_wifi(timeout = 30):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    wlan.connect(SSID, PASSWORD)
    print("Connecting to Wi-Fi...")

    try:
        await asyncio.wait_for(wait_connection(wlan), timeout)
        print("Connected:", wlan.ifconfig()[0])
    except asyncio.TimeoutError:
        print("Error: Wi-Fi connection timed out.")
        wlan.active(False)
        return False
    return True

# メインタスク
async def main():
    # Wi-Fiに接続する
    is_connected = await connect_wifi()
    if not is_connected:
        print("failed to connect Wi-Fi")
        sys.exit()

    asyncio.create_task(counter_task())
    asyncio.create_task(buzzer_task())

    try:
        # 80番ポートでWEBサーバーを起動する
        server = await asyncio.start_server(server_handler, "0.0.0.0", 80)
        print("Server started")
        while True: await asyncio.sleep(3600)
    finally:
        server.close()
        await server.wait_closed()
        print("Closed server")

try:
    asyncio.run(main())
    print("Waiting...")
except KeyboardInterrupt:
    # Ctrl+Cが押された時
    print("Stopping...")
finally:
    # 最後にブザーを止める
    buzzer.value(0)
    print("exit")

このプログラムは標準ライブラリの asyncio を使用して並行処理を行っています。それぞれのタスクが他のタスクを止めることなく協調して動作します。 また、Web ブラウザとの通信には SSE(Server-Sent Events) という仕組みを採用しました。ブラウザ側から何度も再読み込みをしなくても Pico 側から検知状況をリアルタイムにプッシュ通知して画面を更新できるのが特徴です。

コマンドパレットから「MicroPico: Run current file on Pico」を実行すると WEB サーバーが起動します。 すると REPL にサーバーの URL(例: http://192.168.11.10)が表示されるので Microsoft Edge などの WEB ブラウザでアクセスしてください。 チルトセンサーを揺らしたり傾けるとブザーがピピピと鳴ります。同時に WEB ブラウザ上の検知回数がリアルタイムに更新されます。 中断したい場合は Ctrl + C キーを押します。

おわりに

今回最後に作成したものはいわゆる「IoT(Internet of Things / モノのインターネット)」と呼ばれる仕組みです。 ネットワーク、ソフトウェア、ハードウェアが交差する領域のため必要な知識は多岐にわたりますが、その分実現できることの幅が広がって面白いです。 応用すればラジコン、ロボット、視覚補助デバイス、体験型ゲームなど様々なものが作れると思います。 既存の製品にはないものもアイデアさえあれば自分好みに作り上げられるのがもの作りの楽しさだと思います。 今回はここまでです。最後まで読んでいただきありがとうございました

参考