2025年6月17日火曜日

実例で学ぶRaspberry Pi電子工作 補足情報トップ

はじめに

本ページは、金丸隆志著「実例で学ぶRaspberry Pi電子工作」(講談社ブルーバックス)の補足情報をまとめるためのページです。本書の内容が最新のRaspberry Pi OSで動作するようメンテナンスを続けています。

補足情報

Raspberry PiのOSの更新に伴う内容の変更などを以下のページに記します。このページを書籍と合わせてごらんください。

追加コンテンツ

本書の演習をさらに膨らませる追加コンテンツを以下に記します。音声合成、音声認識、エアコン操作など、より実用的な内容を含んでいます。本書の演習とともに是非チャレンジしてみてください。

前著の記事へのリンク

入門編である「Raspberry Piで学ぶ電子工作」のサポートページ中で、本書の読者の方にも役立ちそうな記事へのリンクを貼ります。

感想など




本書の内容を Pi Zero ~ Pi 5 で実行する方法

サンプルプログラムとカラー版回路配線図について

Pi 5 対応のサンプルプログラムおよび回路配線図、応用PDFは、以下のリンクからダウンロードしてください。サンプルプログラムを圧縮したファイル「 raspi2-sample-pi5.zip 」の「-pi5」の部分が「Pi 5 対応」を意味しています。 なお、サンプルファイルである「raspi2-sample-pi5.zip」をWindowsでダウンロードすると、Windows Defender によりウィルスと誤検出されることが多いようです(2024.3月現在)。
検出されるファイルに問題がないことは確認済みです。そのため、Windows ではなくRaspberry Piで直接ダウンロードすることを推奨します。

これらの PDF は Raspberry Pi 上のブラウザでも見られますが、ブラウザ上の「↓」(ダウンロード)ボタンでダウンロードし、ファイルマネージャーで PDF ファイルを右クリックし「アプリケーションで開く」→「アクセサリ」→「ドキュメントビューア」などで開いてもよいでしょう。
「選択したアプリケーションをこのファイルタイプのデフォルトのアクションとする」にチェックを入れればそのアプリケーションで開くのがデフォルト動作となります。

はじめに

本ページでは、金丸隆志著「実例で学ぶRaspberry Pi電子工作」(講談社ブルーバックス)の内容を Raspberry Pi Zero ~ Raspberry Pi 5 で動作させるための補足情報を本ページに記します。

2023年9月に海外で発表された Raspberry Pi 5 (以下 Pi 5) では、GPIO を用いる仕組みがそれまでのバージョンの Raspberry Pi とは大きく異なります。
その影響で、本書で解説したサンプルプログラムは Raspberry Pi 5 では動作しなくなってしまいました。

Raspberry Pi 5 で本書の演習を実行するためには、本書のプログラムをほぼ全て書き換えねばなりません。
しかし書き換えを実行すると、そのプログラムは Raspberry Pi 5 だけではなく、ほぼ全ての Raspberry Pi のバージョンで動作するというメリットがあります。

本書は2015年の発売から9年がたち、書籍のままでは動かない内容も増えてきました。書籍としてはややくたびれた状態と言えるかもしれません。
そこで、この機会にすべてのプログラムを一新し、このページでその動作方法を解説することにしました。
書籍とこのページだけを見れば、全ての演習を Pi Zero ~ Pi 5 で実行できるようになります。

対象とする OS は、以下のように 2023年5月にリリースされた Rsapberry Pi OS (Bullseye) の最終版と、2023年10月にリリースされた Bookworm 以降の OS です。

日付バージョンカーネルバージョンOSバージョン
2023/5/3Raspberry Pi OS 2023-05-036.1.21+ / 6.1.21-v7+ / 6.1.21-v7l+ / 6.1.21-v8+Bullseye (最終版)
2023/10/10Raspberry Pi OS 2023-10-106.1.0-rpi4-rpi-{v6,v7,v7l,v8,2712}Bookworm
2023/12/5Raspberry Pi OS 2023-10-106.1.0-rpi7-rpi-{v6,v7,v7l,v8,2712}

なお、それ以前の古いOSに対する情報(Pi Zero~Pi 4までのみ対応)は、下記のページに残してありますが、恐らく今後需要は減っていくでしょう。

第1章

p.12: OSインストール法の最新版について

最新の Raspberry Pi OS のインストールおよび設定方法は、 「Raspberry Piではじめる機械学習 補足情報」内にあるRaspberry PiへのOSのインストール方法をご覧ください。本書1章と同等の内容をアップデートされた状態で見ることができます。

p.30, コマンドプロンプトの表記

p.30ではコマンドプロンプトの表記として以下を紹介しました。
pi@raspbberrypi:~ $
ここに見える「pi」はユーザー名を表しており、ユーザー名「pi」は2022年4月以前の古いOSで用いられていたデフォルトのユーザー名です。最新のOSを用いている方ならば、「pi」の部分に自分で決めたユーザー名が表示されているでしょう。私の場合、ユーザー名はいつも「kanamaru」としています。

第2章

p.52:開発環境の代替

開発環境は、デフォルトでインストール済のインストール済の「Thonny Python IDE」を用いてください。


次図のように、「LOAD」がファイルの読み込み、「RUN」がプログラム実行、「STOP」がプログラム停止であることを理解すれば問題なく利用できるでしょう。

ただし、「STOP」ボタンでプログラムを停止すると、サンプルプログラムの「except KeybordInterrupt」部が実行されません。「Shell」と書かれた領域の上でキーボードの「Ctrl-C」によりプログラムを終了するのが良いでしょう。なお、Thonnyには「時々Ctrl-cが効かなくなる」という不具合があるようなので、そういうときのみ「STOP」ボタンでプログラムを停止すると良いでしょう。


Thonny Python IDEを用いると、7章で取り扱うOpenCVを用いたプログラムもIDEから実行できるというメリットがあります(IDLEでは画像処理プログラムのみコンソールから起動させたのでした)。

p.54:サンプルプログラム

2章のプログラムのファイル名は本書のものと変わらず「bb2-02-01-led.py」です。
ただし、GPIO を取り扱うライブラリは、本書で用いた RPi.GPIO から gpiozero に変更されています。 ターミナルで実行する場合は下記のコマンドを用いてください。
python3 bb2-02-01-led.py


第3章

3章のプログラムも、使用ライブラリを RPi.GPIO から gpiozero に変更しています。
プログラムのファイル名は本書のものと変わりません。ターミナルで実行する場合は下記のコマンドを用いてください。
python3 bb2-03-01-3led.py

python3 bb2-03-02-dice.py

python3 bb2-03-03-dice-switch.py

python3 bb2-03-04-dice-switch-delay.py


第4章

4章全般:お天気Webサービスについて

第4章で紹介しているお天気Webサービスですが、2020年7月でサービスを終了したようです。本ページでダウンロードしたサンプルプログラムでは、 その代替として OpenWeather というサービスを利用しました。OpenWeather の利用には準備が必要ですのでそれも合わせ、以下で解説していきます。

p.94:I2C の準備

まず、本書 p.94 で解説した I2C の準備を行いましょう。まず、p.95 で解説されているように、設定アプリケーションにより I2C を有効にしておく必要があります。 具体的には、下記の手順に従います。
  1. デスクトップ左上のメニューから、「設定」→「Raspberry Piの設定」を起動
  2. 「インターフェイス」タブをクリック
  3. 「I2C」のスイッチをONにして有効化する
本書のそれ以降の作業、すなわち、「/etc/modulesへのi2c-devの追記(p.96)」、「必要なパッケージのインストール (p.97)」は実行不要です。

なお、Pi 5で kernel 6.6.20 をお使いの場合、本書で紹介する LCD を用いるときにエラーが頻発する問題があります。
お使いのカーネルのバージョンは、ターミナルで「uname -r」を実行することで調べられます。
2024/4/26 に apt でインストール可能になった kernel 6.6.28 ではこの問題が解決していますので、「sudo apt update ; sudo apt dist-upgrade」などで kernel を更新してください。

p. 97:事前準備

p.97~p.100 で解説されている、お天気Webサービスのサイトの確認は不要です。
p.101 で解説されている、pip と requests のインストールも不要です(デフォルトでインストール済のため)。

その代わり、ここでは OpenWeather のアカウント作成を行いましょう。

OpenWeather のアカウント作成

OpenWeather の利用には、「サイトにアカウントを作成し、APIキーを取得する」というひと手間が必要です。まずその実行から始めましょう。

OpenWeather の料金体系には様々なものがありますが、 本ページでは、無料 (Free) の「3-hour Forecast 5 days (3時間ごとの予報を5日分)」を利用します。一分間に 60 呼び出し、一か月1,000,000呼び出しが可能であることが記されています。

なお、この API は 2024年に更新が行われたようで、その変更への対応を行ったサンプルファイルは2025年6月4日よりダウンロード可能になっています。
それ以前にダウンロードしたサンプルファイルは、現在の OpenWeather では動作しませんのでご注意ください。

以下の手順に従い OpenWeather のアカウントを作成しましょう。
  1. OpenWeatherのアカウント作成ページで必要事項を記入ます。
    • Username: 英語サイトなので、アルファベットからなるユーザー名を入力するのが良いでしょう。アカウント作成後に変更できます。
    • email: アカウント作成時に確認メールが届きますので、正確に入力してください。
    • Password / Repeat Password: パスワードを二回入力します。
    • I am 16 years old and over (16歳以上) の条件を満たしていればチェック
    • I agree with Privacy Policy ... 使用条件などをを理解したらチェック
    • System news, Product news, Corperate news はサイトからのメールを受け取りたければチェック
    • 「私はロボットではありません」にチェック
    • 以上を確認したら「Create Account」ボタンをクリック
  2. 遷移先のページで、Company と Purposeを入力します。
    • Company は、個人ならば記入しなくて構いません
    • Purpose (使用目的) は、私の場合「Education/Science (教育と科学)」を選択しました
    • 「Save」ボタンをクリック
  3. 以上が終わると、登録したメールにOpenWeather からメールが届きます。メールの中の「Verify your email」ボタンをクリックすると、登録完了です。

OpenWeather の API キー取得

さて、以上が終わったあと、OpenWeather において、下図のように「ユーザー名」→「My API keys」と辿ると、API key を取得できます。 このAPI keyをコピーしてメモ帳などで保存しておきましょう。
なお、この API key は Raspberry Pi 上で用いるので、Raspberry Pi 上のブラウザでコピーし、Raspberry Pi 上のテキストエディタ Mousepad で保存するのが良いかもしれません。
また、この API key は登録したメールアドレスにも届きますので、そちらからコピーしても良いでしょう。その中に「Within the next couple of hours, it will be activated and ready to use」とあるので、利用可能になるまで数時間かかることがあるのかもしれません。 私の場合、すぐに使えたように思うのですが、ちょっと自信がありません。

OpenWeather の API キーをプログラムに反映

さて、以上の準備のもとで、本ページでダウンロードしたファイルの利用方法を解説します。
  • bb2-04-00-checkcity.py
  • bb2-04-01-weather.py
  • bb2-04-02-forcast.py
  • bb2-04-04-lcd-4modes.py
  • bb2-04-05-lcd-3modes.py
最初の「bb2-04-00-checkcity.py」は、本書のサンプルプログラムには存在しなかったファイルです。 bb2-04-01~bb2-04-05 のファイルは本書のサンプルプログラムに同じ番号のものがありますが、その OpenWeathter 版です。

これらのファイルを用いるには、上記5つのファイルに冒頭で入手した API key を記入して上書き保存しなければなりません。まずその解説を行います。

これらの5つのファイルを mousepad のようなテキストエディタや、Thonny のような開発環境で開くと、どのファイルにも下記の行が見つかります(冒頭にスペースが入っている場合もあります)。
key = 'API_KEY'
具体的には、5つのファイルの下記の行番号の位置に上の行があります。
  • bb2-04-00-checkcity.py: 5行目
  • bb2-04-01-weather.py: 8行目
  • bb2-04-02-forcast.py: 8行目
  • bb2-04-04-lcd-4modes.py: 101行目
  • bb2-04-05-lcd-3modes.py: 101行目
この行の「API_KEY」の部分を、上でコピーして保存した皆さんの API key で書き換えます。 本当の API key を書くわけにはいかないので、イメージを記すと下記のようになります。
key = '................................'
書き換えたら、ファイルを上書き保存してください。この作業を、5つのファイル全てで行います。 以上でプログラム実行前の準備は完了です。

次に、OpenWeatherの天気予報データを利用するプログラムの実行方法を解説します。

本書に存在しないファイルである「bb2-04-00-checkcity.py」の利用法の解説のみ後まわしとし、残りの4ファイル の解説を順に行います。

なお、デフォルトでは4ファイルは東京の天気を表示します。

p.101:bb2-04-01-weather.py の利用

bb2-04-01-weather.py を Thonny で開いて実行するか、下記のコマンドで実行してください。
python3 bb2-04-01-weather.py
本書の図4-7のように、入手した天気予報データを全て表示するコマンドです。表示された中身を理解する必要はなく、 「天気予報データを入手できた」ということを確認できればそれで十分です。
(省略)
      "weather": [
        {
          "id": 804,
          "main": "Clouds",
          "description": "厚い雲",
          "icon": "04d"
        }
      ],
      "clouds": {
        "all": 100
      },
      "wind": {
        "speed": 2.42,
        "deg": 118,
        "gust": 2.49
      },
      "visibility": 10000,
      "pop": 0,
      "sys": {
        "pod": "d"
      },
      "dt_txt": "2025-06-10 03:00:00"
    }
  ],
  "city": {
    "id": 1850144,
    "name": "東京都",
    "coord": {
      "lat": 35.6895,
      "lon": 139.6917
    },
    "country": "JP",
    "population": 12445327,
    "timezone": 32400,
    "sunrise": 1749065152,
    "sunset": 1749117228
  }
}
なお、取得されるデータは本書の図4-7の形式とは全く異なります。予報のデータは上の「"main": "Clouds",」の部分を使っており、「"description": "厚い雲",」の部分は使っていません。 最終的に LCD に表示するうえで、"description" の部分はバリエーションが多すぎると思えたためです。

p.105, bb2-04-02-forcast.py の利用

bb2-04-02-forcast.py を Thonny で開いて実行するか、下記のコマンドで実行してください。
python3 bb2-04-02-forcast.py
本書の図4-8のように、3日間の予報のみをデータを整理して表示します。
今日, 曇り, 26/28, 2025-06-05 06:00:00
明日, 曇り, 26/26, 2025-06-06 06:00:00
明後日, 曇り, 27/27, 2025-06-07 06:00:00
なお、「お天気Webサービス」と異なり、「時々」や「のち」を含む結果は現れません。 「Clear(晴れ)」、「Clouds(曇り)」、「Rain(雨)」、「Snow(雪)」、「Thunderstorm(雷)」、「Drizzle(霧)」 の6種類の天気しか表示されません。その点はこのサイトの特性としてご了承ください。

p.108:bb2-04-04-lcd-4modes.py および bb2-04-05-lcd-3modes.py の利用

bb2-04-04-lcd-4modes.py および bb2-04-05-lcd-3modes.py を実行する際は、あらかじめ I2C を有効にしておき、さらに p.108 のように回路も作成しておかねばなりません。
また、タクトスイッチの利用にはこれまでの章と同様、RPi.GPIO ではなく gpiozero を用いています。

これらのファイルを Thonny で開いて実行するか、下記のようにターミナルでコマンドを実行します。
python3 bb2-04-04-lcd-4modes.py

python3 bb2-04-05-lcd-3modes.py
実行結果は本書のプログラムと同じです。

p.108:I2Cデバイスの認識について

ここで用いる温度センサとLCDの組み合わせは「ラズパイ4対応 カラー図解 最新 Raspberry Piで学ぶ電子工作」でも用いていますが、LCD を自作した場合、プログラムの動作に失敗するという方が多いようです。LCDを用いたプログラムを実行するためには、Raspberry PiからLCDが認識されていることが必要です。LCDが認識されているかどうかのチェック方法は、書籍に記さなかったのですが、ここで紹介します。なお、現在は LCD の完成版が販売されており、失敗のリスクは減っています(完成版では ~RESET ピンは省略されています)。

温度センサとLCDを接続した回路(p.108の図4-9)を作成した状態で下記のコマンドを実行しましょう。
i2cdetect -y 1
このコマンドは、Raspberry Piに接続されたI2Cデバイスのアドレスを出力するものです。
正常な出力結果は下図のようになります。48が温度センサのアドレス0x48を表し、3eがLCDのアドレス0x3eを表します。48が表示されなければ温度センサを用いるプログラムは正常動作しませんし、3eが表示されなければLCDを用いるプログラムは動作しませんので、まずは下図の出力が得られることを目指しましょう。


bb2-04-00-checkcity.py の利用

さて、上で説明したプログラムは、全て東京での天気予報を表示するものでした。これを別の都市の天気予報に変更したいときに用いるプログラムが bb2-04-00-checkcity.py です。 その使い方を解説します。

ここまでのプログラムで天気予報の対象位置を指定するためには、実は緯度と経度を用いなければなりませんでした。5つのプログラム全てで、API key を指定した行の近くに下記の2行が存在します。
lat = '35.6895'
lon = '139.6917'
これは緯度35.6895、経度139.6917を表しており、実はこれが東京を意味していた、というわけです。「東京」という地名で位置を指定したいところですが、それは無料枠の OpenWeather ではできない、ということです。

ですから、天気予報の対象地を変えたいときは、下記の流れで行う必要があります。
  1. 対象地を決める
  2. 対象地の緯度と経度を調べる
  3. 調べた緯度と経度を5つのプログラムに書き込み、上書き保存する
このとき、「対象地の緯度と経度を調べる」ときに用いるのが bb2-04-00-checkcity.py であるというわけです。早速試してみましょう。

まず、こちらのサイトから対象地を決め、そのスペルを確認しておきましょう。ブラウザで位置情報の取得が許可されている必要があるようです。
マウスドラッグで位置変更ができ、マウスホイールで拡大率の変更ができます。
例えば、「Sapporo」を選ぶことにします。

次に、bb2-04-00-checkcity.py を Thonny や mousepad で開き、下記の行を見つけます。
city = 'Tokyo'
この行のTokyoの部分をSapporoに変更し、上書き保存します。
city = 'Sapporo'
そして、bb2-04-00-checkcity.py を Thonny または下記のコマンドで実行してください。
python3 bb2-04-00-checkcity.py
すると、下記のように Sapporo の緯度と経度が得られます。
Sapporo
lat = '43.0642'
lon = '141.3469'
このうち、下記の2行で bb2-04-01~bb2-04-05 のファイルの該当行を置き換えて保存すれば、それらのファイルは Sapporo の天気予報を出力するようになります。
lat = '43.0642'
lon = '141.3469'

第5章

p.141:LIRCの利用

リモコンの信号を学習し、利用するには LIRC というソフトウェアを用います。 Raspberry Pi の OS と LIRC のバージョンが更新されるに伴い、LIRC の利用法は大きく変化してきました。 本書の LIRC の利用方法の解説(本書 p.141~p.163 まで)は、以下の補足ページとともにご覧ください。 ここから先は、p.163 までが実現した状況、すなわち irsend コマンドでテレビを操作できるようになった状況を前提として解説を続けます。

p.163:bb2-05-01-TV.py の利用

タクトスイッチでテレビを操作するプログラム bb2-05-01-TV.py の利用方法はこれまでと同じです。すなわち、Thonny で実行するか、ターミナルで下記のコマンドを実行します。
python3 bb2-05-01-TV.py 
これまで同様、タクトスイッチの利用に RPi.GPIO ではなく gpiozero を用いています。

p.168:ブラウザでのテレビの操作

ブラウザでのテレビの操作には、本書では WebIOPi というライブラリを用いました。WebIOPi は、Webサーバーとなる機能と GPIO を操作する機能を組み合わせたライブラリなのでした。
WebIOPi は Raspberry Pi 5 では動作しませんので、本ページではその代替として FastAPI というライブラリを用います。FastAPI は python3 で簡易的な Webサーバーを作成できるライブラリです。GPIO を操作する機能はありませんので、gpiozero などと組み合わせて用います。

以上の理由から、WebIOPi についての解説がほとんどである本書 p.168~p.176 の解説は現時点ではあまり参考になりません。本ページの解説をご覧ください。

まず、下記コマンドで FastAPI をインストールします。
sudo apt update
sudo apt install python3-fastapi
p.160 の図5-12 の回路が準備できている状態で、FastAPI を用いて書かれた Web サーバーを下記の手順で実行します。
まず、ターミナルを新規に開き、下記のコマンドを実行することで Webサーバーのプログラムが存在するディレクトリ「bb2-05-02-TV」に移動します。
cd bb2-05-02-TV
サンプルプログラムを bluebacks ディレクトリに展開した場合の移動コマンドは以下です。
cd bluebacks/bb2-05-02-TV
移動したら、下記のコマンドで Web サーバーを実行します。
python3 app.py
このコマンドが実行されたままの状態で、ブラウザで下記のアドレスにアクセスします。ブラウザは Raspberry Pi のものでも、同一ネットワーク内にある PC やスマートフォンのものでも構いません。
http://(Raspberry PiのIPアドレス):8000/5-2
例えば、Raspberry PiのIPアドレスが 192.168.1.3 ならば、ブラウザのアドレス欄に「 http://192.168.1.3:8000/5-2 」を記入して Enter キーを押す、ということです。 Raspberry PiのIPアドレスの調べ方は、本書 p.336 に載っているように ifconfig コマンドを用いても良いですし、Raspberry Pi のデスクトップ右上の Wifi アイコンにマウスポインタを重ねることでも調べられると思います。

現れるページの見た目と機能は p.172 の図 5-16 と同じです。なお、「{"detail":"Not Found"}」というエラーが表示される場合、もう一度 raspi2-sample-pi5.zip をダウンロードして頂けますでしょうか?2024.02.25 にサンプルファイルを更新したためです。

なお、ここで実行したプログラムで重要なファイルは下記のものです。必要に応じて参照してください。
app.py : Pythonプログラム。Webサーバーの構築と、GPIO の利用など(ここではGPIOは用いていない)

static/javascript.js:JavaScript プログラム。ブラウザからの指令などを app.py に送る

templates/index.html:HTML ファイル。ページの見た目を決めている

p.176:本章を終えた後の後始末

以上で第5章の解説は終わりですが、Raspberry Pi をこのままの状態で使い続けると、以下の問題が起こります。
  • GPIO 25 と 24 を、LIRC で用いるように設定されているので(/boot/firmware/config.txt)、別の Python プログラムでこれらのピンを使おうとするとエラーがでる
  • lirc のプログラムが動作しっぱなしになる(特に害があるわけではありませんが)
これらの問題の解消方法を以下に記します。

まず、 GPIO 25 と 24 の利用設定の解除です。まず、下記コマンドで /boot/firmware/config.txt を管理者権限の mousepad で開きます。
sudo mousepad /boot/firmware/config.txt
そして、末尾に追加されている設定を、下記のように先頭に「#」をつけることでコメントアウトし無効化します。
#dtoverlay=gpio-ir,gpio_pin=24
#dtoverlay=gpio-ir-tx,gpio_pin=25
上書き保存して mousepad を終了し、Raspberrry Pi を再起動すれば GPIO 25 と 24 の利用設定は解除されています。

また、lirc の無効化は、以下のコマンドで lirc 自体を削除してしまうのが確実です。
sudo apt remove lirc liblirc0 liblirc-client0


第6章

カメラ台を作成する第6章ですが、本書と本ページとでは下記の違いがあります。
  • GPIOの利用:RPi.GPIO から gpiozero への変更。さらに、ADコンバータのために SPI を有効にする必要がある。
  • ハードウェアPWMの利用:WiringPi-Python から独自プログラムへの変更。/boot/firmware/config.txt へ設定を追加する必要がある。
  • ブラウザの利用:WebIOPi から FastAPI への変更。
  • カメラの利用:libcamera 対応とし、カメラのライブラリを PiCamera から PiCamera2 へ変更。
本書のページの順番に従って解説していきます。

p.184:カメラモジュールについて

Raspberry Pi 用のカメラモジュールは、現在では下図のように バージョン 1 から バージョン 3 までの 3 バージョンがあります。
左から、バージョン1(基板が四角)、バージョン2(基板の角が丸く、レンズ周辺が黒)、バージョン3(基板の角が丸く、レンズ周辺が銀)です。基板上にもバージョンの記載がありますね。どのバージョンでも動作させることができます。

Raspberry Pi 1 から Pi 4 でしたら、カメラモジュールの Raspberry Pi 本体への取り付けは、本書記載の通り「(ケーブルの)端子面がRaspberry PiのmicroSDカードの方を向くように」取り付けます。

一方、Raspberry Pi Zero および Pi 5 の場合、カメラモジュールの取り付けにはいくつかの注意があります。

まず、カメラモジュールに取り付けられているケーブルを、Pi Zero および Pi 5 に対応したものに交換する必要があります。例えば下記のものは Pi Zero でも Pi 5 でも利用可能です。 Pi Zero 用ケーブルでRaspberry Pi Zero WH にカメラモジュールを取りつけた様子が下図です。Raspberry Pi 本体に向かってケーブルが細くなっているのがわかるでしょうか。


専用ケーブルをカメラに取り付ける際、金属が露出した端子面を緑色の基板の方を向くようにします。基板上のカバーを引き出し、ケーブルを差し込んだ後でカバーを押し込むことでケーブルが固定されます。
専用ケーブルを Raspberry Pi 本体に取り付ける際、Pi Zero の場合はやはり金属が露出した端子面を緑色の基板の方を向くようにします。
Pi 5 の場合は、下図のように「金属が露出した端子面が USB 端子側を向くように」取り付けます。これは Pi 1~Pi 5とは逆向きですので注意してください。
また、Pi 5 にはカメラを取り付けられる場所が 2 箇所ありますが、「CAM/DISP 0」と書かれた方に接続しましょう。

なお、どの Raspberry Pi を用いるにせよ、カメラのコネクタは壊れやすいので、両手で慎重に開け閉めしましょう。私は片手で雑に扱っていて壊したことがあります。


p.192:ハードウェアPWMを用いるための準備

本書p.192では、ハードウェアPWMを用いるために WiringPi2-Python のインストールを行っていますが、この作業は不要です。 WiringPi2-Python は Pi 5 で動作しないためです。 その代替として、以下のように /boot/firmware/config.txt に一行追加する設定を行ってください。

まず、管理者権限の mousepad で /boot/firmware/config.txt を編集用に開きましょう。下記のコマンドで行うのでした。
sudo mousepad /boot/firmware/config.txt
そして、そのファイルの末尾に下記の一行を追加し、上書き保存してから mousepad を閉じてください。
dtoverlay=pwm-2chan
その後、Raspberry Pi を再起動することで、GPIO 18 と 19 をハードウェアPWMを出力するために利用できるようになります。

なお、Pi 5 をお使いで、アップデートした OS や 2024年3月リリースの OS で kernel 6.6.20 をお使いの方は、ハードウェアPWMを使うプログラムが動作しなくなっています。
お使いのカーネルのバージョンは、ターミナルで「uname -r」を実行することで調べられます。
2024/4/26 に apt でインストール可能になった kernel 6.6.28 ではこの問題が解決していますので、「sudo apt update ; sudo apt dist-upgrade」などで kernel を更新してください。

p.195:サーボモーターを 0 度の位置に動かすためのプログラム

サーボモーターを 0 度の位置に動かすためのプログラムのファイル名は、本書と同じく bb2-06-01-zero.py です。 Thonny で読み込んで実行するか、ターミナルで下記のコマンドで実行してください。管理者権限は不要です。
python3 bb2-06-01-zero.py

p.199~201:タミヤの工作キットで作ったカメラ台のカラー画像

タミヤの工作キットで作るカメラ台の作成方法を示す図ですが、書籍では画像がやや小さく見にくいかもしれません。下記に、カラーで解像度の高い画像を貼り付けますので、ご活用ください。

画像をクリックすると画像が大きくなります。必要に応じて、大きくした画像上で右クリックして「名前を付けて保存」を選択して保存すると良いでしょう。

図6-7

図6-8

図6-9

p.205:カメラマウントA838を用いた場合のカメラ台のカラー画像

同様に、図6-12、図6-13のカラー画像も載せます。

図6-12

図6-13




p.206:カメラモジュールの有効化は不要

本書では、カメラモジュールの有効化の方法が解説されていますが、現在はこの作業は不要です。

p.207:AD変換器の利用のためのSPIの有効化

p.207 よりAD変換器の利用方法が解説されています。現在では、ここで「SPIの有効化」の作業が必要です。以下の手順で実行してください。
  1. デスクトップ左上のメニューから、「設定」→「Raspberry Piの設定」を起動
  2. 「インターフェイス」タブをクリック
  3. 「SPI」のスイッチをONにして有効化する

p.210:bb2-06-02-pantilt.py の実行

サーボモーター2つを半固定抵抗で制御するプログラムのファイル名は、本書と同じく bb2-06-02-pantilt.py です。 Thonny で読み込んで実行するか、ターミナルで下記のコマンドで実行してください。管理者権限は不要です。
python3 bb2-06-02-pantilt.py
サーボの回転の向きを変えたい場合、プログラム中の下記の部分の2行目先頭の「#」を削除するか、
    # 一般的なサーボモーターはこちらを有効に
    #duty = (servo_max-servo_min)*(val-val_min)/(val_max-val_min) + servo_min
あるいは下図のように半固定抵抗の3.3VとGNDへの接続を入れ替えるかのどちらかの方法をとってください。



p.211:カメラモジュールのテスト用コマンド

211ページのカメラモジュールのテスト用コマンドは、現在では下記に変更してください。
libcamera-hello -t 0
libcamera-hello が存在しないと言われた場合、下記のコマンドでインストールできます。
sudo apt update
sudo apt install libcamera-apps
なお、libcamera-hello ではなく、第7章で使用する bb2-07-01-preview.py を実行しても良いです(ただし、OpenCVをインストールする必要があります)。

p.222:mjpg-streamerのインストール用のコマンド

p.222からp.223で紹介したmjpg-streamerのインストール手順は、以下に変更してください。
(1) sudo apt update
(2) sudo apt install libjpeg-dev cmake libcamera-dev
(3) git clone https://github.com/neuralpi/mjpg-streamer.git
(4) cd mjpg-streamer/mjpg-streamer-experimental
(5) make
(6) cd
(7) sudo mv mjpg-streamer/mjpg-streamer-experimental /opt/mjpg-streamer
なお、OS として Bullseye やリリース直後の Bookworm をお使いの方は、上記の (5) で LibCamera.cpp のビルド中にエラーが起こると思います。その場合、エラーが出た状態から以下の 8 コマンドを一つずつ順に実行してください。この 8 コマンドが上記 (5)~(7) の代替、というイメージです。
rm -rf _build
mkdir _build
cd _build
cmake -DLIBCAMERA_USES_TRANSFORM=ON ..
cd ..
make
cd
sudo mv mjpg-streamer/mjpg-streamer-experimental /opt/mjpg-streamer


p.224:mjpg-streamerの実行用コマンド

mjpg-streamerの実行用ファイルは、書籍から変わっており bb2-06-03-stream.py です。以前まではこのファイルは拡張子が sh のシェルスクリプトでしたが、Python によるスクリプトに変更しました。
理由は、libcamera 対応の mjpg-streamer が時折異常終了するため(典型的には 30 分おきくらい)、2秒おきに実行状況をチェックして必要に応じて mjpg-streamer を再起動させるためです。
お手元のファイルが Python ファイル (拡張子 py) ではなくシェルスクリプトのまま(拡張子 sh)の場合は、raspi2-sample-pi5.zip をダウンロードしなおしてください(2024.2.28更新)。

このファイルを実行する前に、使用しているカメラモジュールの設定が必要な場合があります。 まず、mousepad で bb2-06-03-stream.py を開いてみましょう。7行目に下記の内容が見えるはずです。
opt_in = 'input_libcamera.so -camver 1 -fps 15 -r 640x480 -s 640x480'
このうち
-camver 1
の部分が、カメラモジュールのバージョンの数字を指定している部分です。お使いのカメラモジュールがバージョン 2 か 3 なら、この数字を 2 または 3 に変更し、それからファイルを上書き保存してください。

その後、下記コマンドで mjpg-streamer を実行できます。
python3 bb2-06-03-stream.py &
ブラウザでの動作確認は、本書 p.224 と同じです。

p.225:ブラウザでのカメラ台の制御

p.225 から始まるブラウザでのカメラ台の制御は、本書では WebIOPi を用いていました。本ページではそれを FastAPI で行うよう変更します。これは、第5章 p.168 と同じです。 そのため、図6-18の回路図のみは共通ですが、それ以外の p.225~p.235 の WebIOPi の解説はあまり参考にならないと思ってください。以下で FastAPI を用いたプログラムの実行方法を解説します。

まず、まだ FastAPI をインストールしていない場合は下記コマンドでインストールします。
sudo apt update
sudo apt install python3-fastapi
それ以外には、ハードウェアPWMを利用するために本ページの「p.192:ハードウェアPWMを用いるための準備」に基づき、 /boot/firmware/config.txt の末尾に「dtoverlay=pwm-2chan」を追記する必要があります。

そして、p.226 の図6-18 の回路が準備できている状態で、FastAPI を用いて書かれた Web サーバーを下記の手順で実行します。
まず、ターミナルを新規に開き、下記のコマンドを実行することで Webサーバーのプログラムが存在するディレクトリ「bb2-06-05-pantilt」に移動します。
cd bb2-06-05-pantilt
サンプルプログラムを bluebacks ディレクトリに展開した場合の移動コマンドは以下です。
cd bluebacks/bb2-06-05-pantilt
移動したら、下記のコマンドで Web サーバーを実行します。
python3 app.py
このコマンドが実行されたままの状態で、ブラウザで下記のアドレスにアクセスします。ブラウザは Raspberry Pi のものでも、同一ネットワーク内にある PC やスマートフォンのものでも構いません。
http://(Raspberry PiのIPアドレス):8000/6-5
例えば、Raspberry PiのIPアドレスが 192.168.1.3 ならば、ブラウザのアドレス欄に「 http://192.168.1.3:8000/6-5 」を記入して Enter キーを押す、ということです。 Raspberry PiのIPアドレスの調べ方は、本書 p.336 に載っているように ifconfig コマンドを用いても良いですし、Raspberry Pi のデスクトップ右上の Wifi アイコンにマウスポインタを重ねることでも調べられると思います。

現れるページの見た目と機能は p.230 の図 6-19 と同じです。なお、「{"detail":"Not Found"}」というエラーが表示される場合、もう一度 raspi2-sample-pi5.zip をダウンロードして頂けますでしょうか?2024.02.25 にサンプルファイルを更新したためです。

なお、ここで実行したプログラムで重要なファイルは下記のものです。必要に応じて参照してください。
app.py : Pythonプログラム。Webサーバーの構築と、GPIO の利用など

static/javascript.js:JavaScript プログラム。ブラウザからの指令などを app.py に送る

templates/index.html:HTML ファイル。ページの見た目を決めている
なお、ブラウザ上のカメラの映像ですが、上述した mjpg-streamer が異常終了の問題が起こった場合、映像がそこで停止しているでしょう。その場合、ブラウザで再読み込みを実行すると、映像が再び流れ始めると思います。


PDF15:PCA9685 + 半固定抵抗でのカメラ台の操作

追加PDFで解説した、サーボドライバ PC9685 と半固定抵抗でカメラ台を操作するプログラムは bb2-06-04-pantilt-pca9685.py です。 Thonny で読み込んで実行するか、ターミナルで下記のコマンドで実行してください。管理者権限は不要です。I2C が有効化されている必要があります。
python3 bb2-06-04-pantilt-pca9685.py

PDF16:PCA9685 + ブラウザでのカメラ台の操作

追加PDFで解説した、サーボドライバ PC9685 とブラウザでカメラ台を操作するプログラムは bb2-06-06-pantilt-pca9685/ ディレクトリに格納されています。 FastAPI をインストールし、さらに mjpg-streamer をインストール&実行し、I2C を有効にした状態で下記の手順で実行します。
まず、ターミナルを新規に開き、下記のコマンドを実行することで Webサーバーのプログラムが存在するディレクトリ「bb2-06-06-pantilt-pca9685」に移動します。
cd bb2-06-06-pantilt-pca9685
サンプルプログラムを bluebacks ディレクトリに展開した場合の移動コマンドは以下です。
cd bluebacks/bb2-06-06-pantilt-pca9685
移動したら、下記のコマンドで Web サーバーを実行します。
python3 app.py
このコマンドが実行されたままの状態で、ブラウザで下記のアドレスにアクセスします。ブラウザは Raspberry Pi のものでも、同一ネットワーク内にある PC やスマートフォンのものでも構いません。
http://(Raspberry PiのIPアドレス):8000/6-6
例えば、Raspberry PiのIPアドレスが 192.168.1.3 ならば、ブラウザのアドレス欄に「 http://192.168.1.3:8000/6-6 」を記入して Enter キーを押す、ということです。 Raspberry PiのIPアドレスの調べ方は、本書 p.336 に載っているように ifconfig コマンドを用いても良いですし、Raspberry Pi のデスクトップ右上の Wifi アイコンにマウスポインタを重ねることでも調べられると思います。 現れるページの見た目と機能は p.230 の図 6-19 と同じです。なお、「{"detail":"Not Found"}」というエラーが表示される場合、もう一度 raspi2-sample-pi5.zip をダウンロードして頂けますでしょうか?2024.02.25 にサンプルファイルを更新したためです。

第7章

p.238:OpenCVのインストールコマンド

OpenCV は下記のコマンドで実行してください。
sudo apt update
sudo apt install libopencv-dev python3-opencv

p.239~p.260:画像処理プログラムの実行用コマンド

p.239~p.260 で紹介したプログラム(bb2-07-01 から bb2-07-05 まで)の実行コマンドは下記です。書籍との違いは、カメラモジュールの利用に libcamera + picamera2 を用いていることです。
python3 bb2-07-01-preview.py

python3 bb2-07-02-binary.py

python3 bb2-07-03-cannyedge.py

python3 bb2-07-04-circle.py

python3 bb2-07-05-face.py

p.261~p.268:画像追跡プログラムの実行用コマンド

p.261~p.268 で紹介したプログラム(bb2-07-06 から bb2-07-07 まで)の実行するためには、ハードウェアPWMを有効にする必要があります。本ページの「p.192:ハードウェアPWMを用いるための準備」に基づき、 /boot/firmware/config.txt の末尾に「dtoverlay=pwm-2chan」を追記するのでした。以上の準備のもと、下記コマンドを実行します。
python3 bb2-07-06-tracking-circle.py

python3 bb2-07-07-tracking-face.py

PDF17:PCA9685 を用いた画像追跡プログラムの実行用コマンド

PDF17~PDF19 で紹介したプログラム(bb2-07-08 から bb2-07-09 まで)の実行するためには、I2C を有効にする必要があります。その上で、下記コマンドを実行します。
python3 bb2-07-08-tracking-circle-pca9685.py

python3 bb2-07-09-tracking-face-pca9685.py

第8章

p.277:6脚ロボットのカラー画像について

6脚ロボットの作成方法を示す図ですが、書籍では画像がやや小さく見にくいかもしれません。下記に、カラーで解像度の高い画像を貼り付けますので、ご活用ください。

画像をクリックすると画像が大きくなります。必要に応じて、大きくした画像上で右クリックして「名前を付けて保存」を選択して保存すると良いでしょう。

図8-3

図8-12

図8-16

p.280~p.292: PCA9685 でサーボモーターを動かすプログラム

PCA9685 でのサーボモーターを動かすプログラム bb2-08-01-zero-pca9685.py および bb2-08-02-6legs-pca9685.py の内容は、書籍からほとんど変更を受けていません。 これは、外部回路 PCA9685 を用いたことの効果です。ですので、書籍の指示通りにご利用ください。I2C の有効化が必要なことに注意してください。

p.292: ソフトウェアPWMでサーボモーターを動かすプログラム

ソフトウェアPWMを用いるプログラム bb2-08-03-6legs-sw.py を用いる場合は、いくつか注意が必要です。 これまでの章で、/boot/firmware/config.txt を編集した場合、それらを無効にする必要があるのです。

具体的述べましょう。 下記コマンドで /boot/firmware/config.txt を管理者権限の mousepad で開きます。
sudo mousepad /boot/firmware/config.txt
そして、第5章でテレビのリモコン操作をするための設定が末尾にあった場合、下記のように先頭に「#」をつけてコメントアウトして無効化します。
#dtoverlay=gpio-ir,gpio_pin=24
#dtoverlay=gpio-ir-tx,gpio_pin=25
これで GPIO24 と GPIO25 をソフトウェアPWMの出力として利用できるようになります。

さらに、同じく /boot/firmware/config.txt の末尾にハードウェア PWM の設定があった場合、下記のように先頭に「#」をつけてコメントアウトして無効化します。
#dtoverlay=pwm-2chan
これで GPIO18 をソフトウェアPWMの出力として利用できるようになります。

以上の編集を行ったら、 /boot/firmware/config.txt を上書き保存して mousepad を閉じ、Raspberry Pi を再起動しましょう。以上でソフトウェアPWMを用いたプログラムが動作するようになります。

p.296:6脚ロボットの安定性について

ハンチング対策の解説において、サーボモーターSG90に与える電圧(乾電池の本数)によって6脚ロボットの挙動が異なることに触れました。サーボモーターの個体差などにもよりますが、6脚ロボットのモーターに与える電圧が大きく、その結果流れる電流が大きくなるとSG90の挙動が不安定になることがありました。
具体的には「モーターの角度が指定していない角度に動き、そこでロックしてしまう」という現象が起きました。もし、同様の現象に悩まされている方は、以下に記す解消方法を試してみて下さい。なお、書籍の中でサーボドライバの類似品は「Adafruitのものと挙動が異なる場合がある」と記しましたが、類似品の中にはこの「指定していない角度でモーターがロック」という現象が起こりやすいものもあり、そのため、類似品はサポート対象外としました。

対策1:モーターに与える電圧を小さくする
モーターに与える電圧を小さくすると、6脚ロボットが安定する傾向があります。簡単にこれを実現するは、乾電池1本の電圧(約1.5V)とエネループなどのニッケル水素充電池の電圧(約1.2V)が異なることを利用するのが簡単です。以下の様な電圧を簡単に作ることができます。
  • 約4.5V:乾電池3本
  • 約3.6V:エネループなどのニッケル水素充電池3本
もしエネループなどのニッケル水素充電池をお持ちの場合、試してみると良いかもしれません。ただし、乾電池や充電池2本まで電圧を下げると、6脚ロボットは動作しませんので注意してください。なお、乾電池とニッケル水素充電池を混ぜて用いるのはやめましょう。
また、乾電池を用いるにせよ、充電池を用いるにせよ、新品の乾電池あるいは充電直後の充電池は、使っているうちに徐々に電圧が小さくなっていきます(参考:Panasonic | エネループシリーズについて)。そのため、使っているうちに6脚ロボットの安定性が徐々に変化することはあり得ます。

対策2:ロボットの脚の振り幅を小さくする
6脚ロボットを安定させるもう一つの方法は、脚の振り幅を小さくすることです。脚の振り幅が小さいということは、モーターの回転する角度が小さいということなので、モーターに流れる電流が小さくなり、その結果6脚ロボットが安定して動作する傾向があります。ただし、ロボットの動きはややゆっくりになります。下記のファイルを変更することで実現できます。
カメラなし、ハードウェアPWMの場合:/usr/share/webiopi/htdocs/bb2/03/script.py
カメラなし、ソフトウェアPWMの場合:/usr/share/webiopi/htdocs/bb2/04/script.py
カメラあり、ハードウェアPWMの場合:/usr/share/webiopi/htdocs/bb2/05/script.py
これらのファイルの中で、下記の部分を見つけます。
LEG_F_R = NEUTRAL + 100
LEG_F_L = NEUTRAL - 100
LEG_B_R = NEUTRAL - 100
LEG_B_L = NEUTRAL + 100
4つある数字の100が、脚の振り幅(モーターの動く角度)に対応する数値です。これを小さめの値、例えば下記のように60に変更します。
LEG_F_R = NEUTRAL + 60
LEG_F_L = NEUTRAL - 60
LEG_B_R = NEUTRAL - 60
LEG_B_L = NEUTRAL + 60
変更して保存した後はWebIOPiを再起動する必要があります。WebIOPiが自動起動する設定になっている場合、Raspberry Piを再起動してしまうのが簡単でしょう。

対策3:モーターに与える電圧を大きくする
さらに、Syun'iti Hondaさんのように、 モーター用の電源を 「約4.5V:乾電池3本」 から 「約4.8V:エネループなどのニッケル水素充電池4本」 に変更すると安定したというご報告もあります。このあたりはサーボモーターの個体差や消耗具合に依存するのかもしれません。 この場合、電池4本用の電池ボックスとしては「電池ボックス 単3×4本 リード線・フタ・スイッチ付」などがあります。

p.296:サーボモーターの消耗について

本書ではSG-90というサーボモーターで6脚ロボットを作成しました。一般的に、サーボモーターは長く使っていると作成当初から挙動が変わり、交換が必要になる場合があります。そういう意味で、サーボモーターは消耗品と考えた方がよいかもしれません。
私の場合、6脚ロボットを初めて作成してから書籍が出版されるまでの約8ヶ月の間に、1つのサーボモーターを交換しました。具体的には、機体を持ったまま脚を動かした際、1つの脚だけ他の脚に比べて動きが遅くなったため交換しました。

p.300: ブラウザからの6脚ロボットの動作

p.300 から始まるブラウザでの6脚ロボットの制御は、本書では WebIOPi を用いていました。本ページではそれを FastAPI で行うよう変更します。これは、第5章 p.168 や 第6章 p.225 と同じです。 そのため、p.301~p.305 の WebIOPi の解説はあまり参考にならないと思ってください。以下で FastAPI を用いたプログラムの実行方法を解説します。

まず、まだ FastAPI をインストールしていない場合は下記コマンドでインストールします。
sudo apt update
sudo apt install python3-fastapi
そして、FastAPI を用いて書かれた Web サーバーを下記の手順で実行します。
まず、ターミナルを新規に開き、下記のコマンドを実行することで Webサーバーのプログラムが存在するディレクトリ「bb2-08-06-pca9685」に移動します(なお、ソフトウェアPWMを用いる場合のディレクトリは bb2-08-07-sw です)。
cd bb2-08-06-pca9685
サンプルプログラムを bluebacks ディレクトリに展開した場合の移動コマンドは以下です。
cd bluebacks/bb2-08-06-pca9685
移動したら、下記のコマンドで Web サーバーを実行します。
python3 app.py
このコマンドが実行されたままの状態で、ブラウザで下記のアドレスにアクセスします。ブラウザは Raspberry Pi のものでも、同一ネットワーク内にある PC やスマートフォンのものでも構いません。
http://(Raspberry PiのIPアドレス):8000/8-6
例えば、Raspberry PiのIPアドレスが 192.168.1.3 ならば、ブラウザのアドレス欄に「 http://192.168.1.3:8000/8-6 」を記入して Enter キーを押す、ということです。 Raspberry PiのIPアドレスの調べ方は、本書 p.336 に載っているように ifconfig コマンドを用いても良いですし、Raspberry Pi のデスクトップ右上の Wifi アイコンにマウスポインタを重ねることでも調べられると思います。

現れるページの見た目と機能は p.305 の図 8-11 と同じです。なお、「{"detail":"Not Found"}」というエラーが表示される場合、もう一度 raspi2-sample-pi5.zip をダウンロードして頂けますでしょうか?2024.02.25 にサンプルファイルを更新したためです。

なお、ここで実行したプログラムで重要なファイルは下記のものです。必要に応じて参照してください。
app.py : Pythonプログラム。Webサーバーの構築と、GPIO の利用など

static/javascript.js:JavaScript プログラム。ブラウザからの指令などを app.py に送る

templates/index.html:HTML ファイル。ページの見た目を決めている
なお、ソフトウェアPWMも用いる場合のプログラムは bb2-08-07-sw ディレクトリに格納されており、実行時に参照すべきアドレスは「http://(Raspberry PiのIPアドレス):8000/8-7」です。

p.307: Webサーバーの自動起動について

p.307 では Web サーバーである WebIOPi の自動起動について記されています。本ページではこれを「python3 app.py コマンドの自動起動」に置き換えて解説します。

まず、「python3 app.py コマンドの自動起動」をするためには、このコマンドをサービスとして登録するのが確実です。 そのために必要なファイルは、サンプルファイルの中の bb2-08-06-pca9685 ディレクトリにある web6legs.service というテキストファイルとして保存されています。 このファイルを活用する前に、その内容を皆さんの環境に合わせてカスタマイズする必要があります。

このファイルは単なるテキストファイルのですので、テキストエディタ(mousepad)で開いてみましょう。

管理者権限は不要ですのでファイルマネージャから開いても良いですし、コマンドで開く場合、「 cd bb2-08-06-pca9685 」または「 cd blubacks/bb2-08-06-pca9685 」でディレクトリを移動した後、下記コマンドで開けます。
mousepad web6legs.service 
すると、以下の内容がみられます。
[Unit]
Description=Web6Legs
After=network.target

[Service]
Type=simple
WorkingDirectory=/home/pi/bb2-08-06-pca9685
#WorkingDirectory=/home/pi/bluebacks/bb2-08-06-pca9685
ExecStart=/usr/bin/python3 -m app
TimeoutStopSec=5
StandardOutput=null

[Install]
WantedBy = multi-user.target
変更しなければならないのは下記の部分、すなわち、プログラムが存在するディレクトリを指定している部分です。
WorkingDirectory=/home/pi/bb2-08-06-pca9685
#WorkingDirectory=/home/pi/bluebacks/bb2-08-06-pca9685
2行ありますが、2行目の先頭に「#」がついていますのでこちらは無効化された状態です。
1行目がサンプルファイルをホームディレクトリに直に展開した場合、2行目がサンプルファイルを bluebacks ディレクトリ内に展開した場合ですので、 自分に合った方を有効にしてください。

次に着目して頂きたいのは、ディレクトリの「/home/pi」の部分です。この「pi」はユーザー名を現していますので、これを皆さんのユーザー名に変更してください。 私の場合でしたら、「pi」の部分を「kanamaru」に変更、ということです。 変更が済んだら上書き保存し、mousepad を閉じてください。

その後、下記のコマンドで web6legs.service ファイルを /etc/systemd/system ディレクトリに管理者権限でコピーします。
sudo cp web6legs.service /etc/systemd/system/
そうすると、ブラウザで6脚ロボットを操作するためのプログラムが「web6legs」という名前のサービスとして利用できるようになります。サービスの起動と終了を行うコマンドは以下の通りです。これらのコマンドは、どのディレクトリで実行しても構いません。

サービス起動(python3 app.py を実行するのと同等)
sudo service web6legs start
サービス終了(python3 app.py の終了と同等)
sudo service web6legs stop
なお、サービスが起動しているかどうかは、下記のように ps ax コマンドの出力から「python3」を含むものを抽出することで確認することができます。
kanamaru@raspberrypi:~ $ ps ax | grep python3
    914 ?        Ssl    0:00 /usr/bin/python3 -m app
   1947 pts/0    S+     0:00 grep --color=auto python3
1行目の「/usr/bin/python3 -m app」が、プログラム app.py が実行されていることを表します。

なお、以上の service コマンドでサービスを起動しても、Raspberry Pi を終了や再起動するとサービスは終了し、自動起動することはありません。 Raspberry Pi の起動時にこのサービスを自動起動するには、下記のコマンドを用います。このコマンドも、どのディレクトリで実行しても構いません。
sudo systemctl enable web6legs
このコマンドを実行後、Raspberry Pi を再起動すると、自動で web6legs が起動されるようになります。ps ax コマンドで確認してみましょう。

自動起動を無効に戻すには、以下のコマンドを用います。
sudo systemctl disable web6legs

p.308: LCDへのIPアドレスの表示について

本書では、Raspberry Piにブラウザからアクセスする際にIPアドレスを用いました。そのため、Raspberry Piにルーターなどから割り振られたIPアドレスを知る目的で、LCDを回路に取り付けました。

しかし、本ページ下部の「p.335:IPアドレスでのURLの指定について」で記しますように、avahiという仕組みを用いると、IPアドレスではなく、下記のようなアドレスでのアクセスが可能になるのでした(ただし、p.335の注釈に示すように制限がありますので注意)。
  • http://raspberrypi.local:8000/5-2
これを用いると、回路からLCDが不要になるなどのメリットがあります。具体的には下記の通りです。
  • p.309の図8-13やp.310の図8-14からLCDを取り外して良い
  • p.310での/etc/rc.localへの「python3 /home/pi/bb2-08-04-lcd.py $_IP &」の記述が不要
さらに、Raspberry PiのIPアドレスを固定する、という方法でも、毎回IPアドレスを調べる必要がなくなりますね。こちらについては前著サポートページ内の「Raspberry PiのIPアドレスを固定する」にまとめましたので、興味のある方は御覧ください

p.311:IPアドレスをLCDに表示するプログラムを自動起動するための記述

ファイル /etc/rc.local を管理者権限の mousepad で開くためのコマンドは以下の通りです。
sudo mousepad /etc/rc.local
さて、このコマンドを実行したとき、既存ファイルである /etc/rc.local がテキストエディタ mousepad で開かれます。そうなった方は先に進んで構いません。
しかし、2024年11月以降の OS では、ファイル /etc/rc.local が削除され存在しないため、mousepad は空白の状態で開きます。そうなった場合、一旦 mousepad を閉じ、以下の3コマンドを順に実行することにより、新たにファイル /etc/rc.local を作成しましょう。
wget https://raw.githubusercontent.com/neuralassembly/raspi/refs/heads/master/rc.local

chmod a+x rc.local

sudo mv rc.local /etc
この3コマンドを実行してファイル /etc/rc.local を作成したら、上記の mousepad で /etc/rc.local を開くコマンドを実行し、以下に進みましょう。

IPアドレスをLCDに表示するプログラムを自動起動するために/etc/rc.localに記述する命令は下記の通りです。コピーなどしてご活用ください。
python3 /home/pi/bb2-08-04-lcd.py $_IP
なお、2022年4月にリリースされた OS よりデフォルトユーザー pi は廃止されています。そのため、上のコマンドの pi の部分は、皆さんが作成したユーザー名で置き換変える必要があります。すなわち、「kanamaru」というユーザーを作成したのなら、「python3 /home/kanamaru/bb2-08-04-lcd.py $_IP」となる、ということです。

また、サンプルをbluebacksフォルダに展開した場合は下記のコマンドに変更されるのでしたね。
python3 /home/pi/bluebacks/bb2-08-04-lcd.py $_IP
上と同様に、「pi」は自分のユーザー名に置き換えてください。

p.314:シャットダウン用プログラムを自動起動するための記述

シャットダウン用プログラムを自動起動するために/etc/rc.localに記述する命令は下記の通りです。コピーなどしてご活用ください。
python3 /home/pi/bb2-08-05-shutdown.py &
なお、2022年4月にリリースされた OS よりデフォルトユーザー pi は廃止されています。そのため、上のコマンドの pi の部分は、皆さんが作成したユーザー名で置き換変える必要があります。すなわち、「kanamaru」というユーザーを作成したのなら、「python3 /home/kanamaru/bb2-08-05-shutdown.py &」となる、ということです。

また、サンプルをbluebacksフォルダに展開した場合は下記のコマンドに変更されるのでしたね。
python3 /home/pi/bluebacks/bb2-08-05-shutdown.py &
上と同様に、「pi」は自分のユーザー名に置き換えてください。

p.318:mjpg-streamerを自動起動するための記述

mjpg-streamerをを自動起動するために/etc/rc.localに記述する命令は下記の通りです。コピーなどしてご活用ください。
python3 /home/pi/bb2-06-03-stream.py &
なお、2022年4月にリリースされた OS よりデフォルトユーザー pi は廃止されています。そのため、上のコマンドの pi の部分は、皆さんが作成したユーザー名で置き換変える必要があります。すなわち、「kanamaru」というユーザーを作成したのなら、「python3 /home/kanamaru/bb2-06-03-stream.py &」となる、ということです。

また、サンプルをbluebacksフォルダに展開した場合は下記のコマンドに変更されるのでしたね。
python3 /home/pi/bluebacks/bb2-06-03-stream.py &
上と同様に、「pi」は自分のユーザー名に置き換えてください。

p.319:ブラウザでのカメラ付き6脚ロボットの操作

ブラウザでカメラ付き6脚ロボットを操作するプログラムは bb2-08-08-pca9685/ ディレクトリに格納されています。 FastAPI をインストールし、さらに mjpg-streamer をインストール&実行し、I2C を有効にした状態で下記の手順で実行します。
まず、ターミナルを新規に開き、下記のコマンドを実行することで Webサーバーのプログラムが存在するディレクトリ「bb2-08-08-pca9685」に移動します。
cd bb2-08-08-pca9685
サンプルプログラムを bluebacks ディレクトリに展開した場合の移動コマンドは以下です。
cd bluebacks/bb2-08-08-pca9685/
移動したら、下記のコマンドで Web サーバーを実行します。
python3 app.py
このコマンドが実行されたままの状態で、ブラウザで下記のアドレスにアクセスします。ブラウザは Raspberry Pi のものでも、同一ネットワーク内にある PC やスマートフォンのものでも構いません。
http://(Raspberry PiのIPアドレス):8000/8-8
例えば、Raspberry PiのIPアドレスが 192.168.1.3 ならば、ブラウザのアドレス欄に「 http://192.168.1.3:8000/8-8 」を記入して Enter キーを押す、ということです。 Raspberry PiのIPアドレスの調べ方は、本書 p.336 に載っているように ifconfig コマンドを用いても良いですし、Raspberry Pi のデスクトップ右上の Wifi アイコンにマウスポインタを重ねることでも調べられると思います。 現れるページの見た目と機能は p.230 の図 6-19 と同じです。なお、「{"detail":"Not Found"}」というエラーが表示される場合、もう一度 raspi2-sample-pi5.zip をダウンロードして頂けますでしょうか?2024.02.25 にサンプルファイルを更新したためです。

なお、カメラ付き6脚ロボットの操作プログラムを自動起動したい場合は、 web6legs.service 内の「WorkingDirectory」の行を書き換えてから /etc/systemd/system ディレクトリにコピーし直せば良いでしょう。

付録A

p.325 圧縮されたサンプルファイルの展開に関する注意

NOOBS 3.1.1 (Raspbian 2019-06-20) 以降では、圧縮ファイルを展開するためのソフトウェアのデフォルトの設定が変化しましたので利用の際は注意が必要です。
下図(左)のように、展開先のデフォルトが「/tmp」とされ、また、展開時に自動的にフォルダが作られるようになっています。
これを、下図(右)のように
  • 展開先に「 /home/pi 」または「 /home/pi/bluebacks 」と記入。ただし、2022年4月にリリースされた OS よりデフォルトユーザー pi は廃止されていますので、この記述の pi の部分は、皆さんが作成したユーザー名で置き換変える必要があります。すなわち、「kanamaru」というユーザーを作成したのなら、「 /home/kanamaru 」や「 /home/kanamaru/bluebacks 」となる、ということです。
  • 「Ensure a containing directory」のチェックを外す
の2点を行ってから「展開」ボタンを押すようにしましょう。展開先に記す文字は、このページからコピー (Ctrl-c) して貼り付ける (Ctrl-v) と安心です。



付録B

p.329:WebIOPi について

WebIOPi は Raspberry Pi 5 以降で動作しませんので、インストールしないでください。本ページでは代わりに FastAPI を用いる方法を紹介しています。

p.335:IPアドレスでのURLの指定について

本書では、ブラウザからRaspberry Piにアクセスする際に、ルーターなどからRaspberry Piに割り振られたIPアドレスを用いました。すなわち、IPアドレスが192.168.1.3の場合にブラウザから例えば下記のようにアクセスできます。
  • http://192.168.1.3:8000/5-2
しかし、この方法はifconfigコマンドなどで事前にIPアドレスを調べておく必要があり、やや面倒でした。

現在の OS では、IPアドレスを用いずにRaspberry Piにアクセスできますのでその方法を紹介します。なお、この方法でRaspberry Piにアクセスできるのは下記のみです。
  • iTunesをインストールしたWindows(iTunesに含まれるBonjourというアプリケーションが必要なためです)
  • macOS
  • iPhoneやiPad
Androidスマートフォンでは現時点ではこの方法ではRasbperry Piにはアクセスできないように思われます。

上記の条件が満たされれば方法は簡単で、ブラウザのアドレス欄に例えば下記のように入力すればOKです。
  • http://raspberrypi.local:8000/5-2
すなわち、IPアドレス「192.168.1.3」などの代わりに「raspberrypi.local」という記法が使える、というわけです。これは、OS 上で動作しているavahiというソフトウェアの働きによります。

なお、avahi がインストールされていない場合、下記のようにavahi-daemonをインストールする必要があります。
sudo apt update
sudo apt install avahi-daemon
インストール後に再起動すると、自動的にavahiが起動し、「raspberrypi.local」でのアクセスが可能になります。

なお、同じネットワーク内でavahiが起動したRaspberry Piが2台以上あると、同じ名前「raspberrypi」のマシンが2つ以上ある状態になり、名前の衝突が起こります。その場合、2台目以降のRaspberry Piには「raspberrypi-2.local」などの名前が自動的につけられますのでご注意ください。名前が衝突しないよう、あらかじめ「raspberrypi」というマシン名を変更しておく方法もありますが、ここでは省略します。

読者の方々の声

本ページでは、読者の方々のレビューなどをご紹介したいと思います。

紹介記事


ご紹介に感謝



以下、twitterでの反応をまとめます。













本書の演習を Raspberry Pi Pico W / Pico 2 W で実行する

はじめに

本書は、前著「Raspberry Piで学ぶ電子工作」の応用編という位置づけです。
前著では、書籍の演習を Raspberry Pi Pico シリーズで実行する、という趣旨のコンテンツを作成し、以下のページで公開しました。 Raspberry Pi Pico とは、Raspberry Pi ブランドで開発されているマイクロコントローラー(マイコン)ボードです。
これらのコンテンツは、「半導体不足でRaspberry Pi 本体が入手しにくいときでも演習を実行しやすくする」という目的で作成されましたが、
「シンプルな回路で電子工作により親しめる」、「Pico では実現できない内容を知ることで、Raspberry Pi の特性をより深く知ることができる」などというメリットもあります。

本ページは、上記のページと同様、本書「実例で学ぶRaspberry Pi電子工作」の演習を Raspberry Pi Pico シリーズでも実行可能にすることを目指したページです。
とは言え、全ての演習を実行できるわけではなく、カメラを用いない下記の章のみを Raspberry Pi Pico シリーズで体験できます。 次の写真は、8章で作成した6脚ロボットを Pico W とスマートフォンで制御している様子です。
そして、その動作の様子を示したのが次の YouTube 動画です。



必要な Raspberry Pi Pico ですが、Wifi 接続を多用しますので、Pico W または Pico 2 W を用意してください。Pico シリーズの選び方および設定や利用の方法は 前著のページの1ページ目「本書の電子工作パーツを Raspberry Pi Pico シリーズで利用する」を参考にしてください。
L チカまでを体験して頂くと、本ページの演習もスムーズに実行できるでしょう。

本ページは、読者の方のご要望で作成に着手したのですが、思いのほか作業量が多く、時間がかかってしまいました。
その分、充実した内容になっていると思いますので、お楽しみ頂ければ幸いです。

3章 たくさんのLEDを点灯させてみよう

本書3章では7個のLEDで電子サイコロを作成し、タクトスイッチを一度押すことで「1~6の目が順番に現れ、最終的にランダムな目が点灯する」という動作を実現しました。
たくさんのLEDを点灯させるためにシフトレジスタ SN74HC595N を活用する、という趣旨の演習でした。

それを Raspberry Pi Pico で実現するためにブレッドボードに構成する回路が下記です。本演習は Wifi を用いませんので、すべての機種の Pico で実行可能です。
見ての通り、この演習は大きめのブレッドボードがないと実現できません。
また、本書とは異なり、タクトスイッチは GND に接続し、マイコン側のピンをプルアップ接続するよう設定します。これは、Pico 2 ではプルダウン指定では動作しなかったためです。
この回路を Pico で動作させるためのプログラムが下記です。本書のプログラム bb2-03-04-dice-switch-delay.py の Pico 版という位置づけです。 Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行しましょう。本書と同じ動作が実現されます。
本書では、「タクトスイッチが押された時の動作」をスレッドという仕組みで実現しました。最終的にサイコロの目が確定するまでに長い時間(0.6秒程度)かかるためです。
Pico 用のプログラムでもその動作に合わせています。Pico でスレッドを用いる場合、Pico のプロセッサの2つ目のコアで実行されます(参考)。
from machine import Pin
from time import sleep, ticks_ms
import random
import _thread

def switch_pressed(p):
    global prev_time
    pressed_time = ticks_ms()
    if pressed_time < prev_time + 200 :
        return
    prev_time = pressed_time

    _thread.start_new_thread(randomizeDice, ())

def randomizeDice():
    delay = 0.1
    sendLEDdata(LEDdata1, ser, rclk, srclk)
    sleep(delay)
    sendLEDdata(LEDdata2, ser, rclk, srclk)
    sleep(delay)
    sendLEDdata(LEDdata3, ser, rclk, srclk)
    sleep(delay)
    sendLEDdata(LEDdata4, ser, rclk, srclk)
    sleep(delay)
    sendLEDdata(LEDdata5, ser, rclk, srclk)
    sleep(delay)
    sendLEDdata(LEDdata6, ser, rclk, srclk)
    sleep(delay)
    
    dice = random.randint(1,6)
    if dice == 1:
        sendLEDdata(LEDdata1, ser, rclk, srclk)
    elif dice == 2:
        sendLEDdata(LEDdata2, ser, rclk, srclk)
    elif dice == 3:
        sendLEDdata(LEDdata3, ser, rclk, srclk)
    elif dice == 4:
        sendLEDdata(LEDdata4, ser, rclk, srclk)
    elif dice == 5:
        sendLEDdata(LEDdata5, ser, rclk, srclk)
    elif dice == 6:
        sendLEDdata(LEDdata6, ser, rclk, srclk)

def sendLEDdata(data, ser, rclk, srclk):
    n = len(data)

    rclk.value(0)
    srclk.value(0)

    for i in range(n):
        if data[i] == 1:
            ser.value(1)
        else:
            ser.value(0)

        srclk.value(1)
        srclk.value(0)

    rclk.value(1)
    rclk.value(0)

prev_time = ticks_ms()
ser  = Pin(16, Pin.OUT)
rclk  = Pin(17, Pin.OUT)
srclk  = Pin(18, Pin.OUT)
switch = Pin(19, Pin.IN, Pin.PULL_UP)
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

LEDdata0 = [0,  0,
            0,0,0,
            0,  0]
LEDdata1 = [0,  0,
            0,1,0,
            0,  0]
LEDdata2 = [0,  1,
            0,0,0,
            1,  0]
LEDdata3 = [0,  1,
            0,1,0,
            1,  0]
LEDdata4 = [1,  1,
            0,0,0,
            1,  1]
LEDdata5 = [1,  1,
            0,1,0,
            1,  1]
LEDdata6 = [1,  1,
            1,0,1,
            1,  1]

sendLEDdata(LEDdata0, ser, rclk, srclk)


4章 インターネット上の天気予報データを利用しよう

4章ではインターネット上の天気予報データを取得して LCD に表示する演習を行いました。
書籍で紹介した天気予報サービスは既にサービスの提供を終了しておりますので、本サポートサイトでは、既に OpenWeather というサービスの利用に移行しております。
その内容を Pico で実現します。インターネット接続を必要としますので、Wifi 接続可能な Pico W または Pico 2 W が必須となります。

まず、下記ページの「第4章」の内容を参考に、OpenWeather のアカウントの作成、および API キーの取得までを行ってください。 まずはブレッドボードを用いず、OpenWeather による天気予報データの取得など、動作確認をいくつか行いましょう。

まずは、都市名から緯度と経度を取得するプログラム bb2-04-00-checkcity.py の Pico 版を実行してみましょう。下記のプログラムがそうなのですが、皆さんが各自で変更しなければならない所が3行分あります。
from time import sleep
import network
import requests
import json

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

city = 'Tokyo'
key = 'API_KEY'

address = 'http://api.openweathermap.org/data/2.5/weather?units=metric&q={city}&APPID={key}'.format(city=city, key=key)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

weather_json = requests.get(address).json()

print(city)
try:
    print('lat = \'{}\''.format(weather_json['coord']['lat']))
    print('lon = \'{}\''.format(weather_json['coord']['lon']))
except KeyError:
    print('Not found.')
まずはプログラム 6~7 行目の以下の部分です。これらの行には、それぞれ「皆さんの Wifi のネットワーク名 (SSID)」および「そのパスワード」を記入する必要があります。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
次にプログラム 10 行目の以下の部分です。この部分には皆さんが OpenWeather のサイトで取得した API キーを記入します。
key = 'API_KEY'
イメージとしては下記のように編集する、ということです。
key = '................................'
これらの3行は、4章のプログラム全てに存在しますので、全て皆さんの情報に置き換えてください。

以上の変更を適用したプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行しましょう。「Wifi 接続」→「OpenWeather への接続」の順で処理が進み、最終的に Thonny のシェルの部分に下記の出力が現れます。
Tokyo
lat = '35.6895'
lon = '139.6917'
これは、Tokyo の緯度と経度を表示するプログラムなのでした。以下のプログラムでは、この緯度と経度の数値を利用して天気予報の取得を行っています。
もし Tokyo 以外の天気予報を取得したい場合、上記プログラムの下記の部分を別の都市名に置き換え、その都市の緯度と経度を調べる必要があります。
city = 'Tokyo'
利用可能な都市名は、こちらのサイトで調べることができます。ブラウザで位置情報の取得が許可されている必要があるようです。

さて、ここまでのチェックが終わったら、次に天気予報データの取得チェックを行いましょう。書籍の bb2-04-01-weather.py の Pico 版が下記のプログラムです。
from time import sleep
import network
import requests
import json

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

lat = '35.6895'
lon = '139.6917'
units = 'metric'
lang = 'ja'
key = 'API_KEY'

address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

weather_json = requests.get(address).json()
print(json.dumps(weather_json, indent=2, ensure_ascii=False))
先ほどと同様、
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
および
key = 'API_KEY'
の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895'
lon = '139.6917'
そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「天気予報データを json という形式で取得して表示」の順で処理が進み、最終的に Thonny のシェルの部分に表示が現れます。
json による天気予報データはそこそこに大きなデータなのですが、 Thonny のシェルの部分では省略表示されるので、詳細はわかりません。
ここではエラーなく実行できれば先に進みましょう。

最後の動作チェックとして、取得した json データから、3日分の天気予報データを抽出して表示してみましょう。書籍のプログラムでは bb2-04-02-forcast.py が該当します。
from time import sleep
import network
import requests
import json

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

lat = '35.6895'
lon = '139.6917'
units = 'metric'
lang = 'ja'
key = 'API_KEY'

address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang)

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )

weather_json = requests.get(address).json()

dataLabel = ['今日', '明日', '明後日']
weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'}

for i in range(3):
    forecast = weather_json['list'][8*i]
    temp_min = round(forecast['main']['temp_min'])
    temp_max = round(forecast['main']['temp_max'])
    when = forecast['dt_txt']
    try:
        weather = weather_dict[forecast['weather'][0]['main']]
    except KeyError:
        weather = '未定義'

    print('{}, {}, {}/{}, {}'.format(dataLabel[i], weather, temp_min, temp_max, when))
先ほどと同様、
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
および
key = 'API_KEY'
の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895'
lon = '139.6917'
そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「天気予報データを json という形式で取得し、3日分の天気予報を抽出して表示」の順で処理が進み、最終的に Thonny のシェルの部分に下記のような表示が現れます。

この3日分の天気予報のうち、今日と明日の2日分が、LCD に表示する内容になります。なお、各行の末尾に記されている日付は参考データとして表示されており、LCD には表示されません。
その参考データの時刻は恐らく UTC で表示されており、それに 9 時間足した時間が日本時間です。
今日, 曇り, 22/22, 2025-06-12 15:00:00
明日, 曇り, 21/21, 2025-06-13 15:00:00
明後日, 雨, 21/21, 2025-06-14 15:00:00
以上の動作チェックが終わったら、ブレッドボード上で回路を組みましょう。
I2C 接続の温度計と LCD を接続した回路です。
この回路において、取得した温度データと天気予報データを LCD に表示します。まず、書籍の bb2-04-04-lcd-4modes.py に該当するのが下記のプログラムです。
from machine import Pin, I2C
from time import sleep, ticks_ms
import network
import requests
import json
import sys

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

lat = '35.6895'
lon = '139.6917'
units = 'metric'
lang = 'ja'
key = 'API_KEY'

def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

def read_adt7410():
    word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 2), 'little', False)
    data = (word_data & 0xff00)>>8 | (word_data & 0xff)<<8
    data = data>>3 # 13ビットデータ
    if data & 0x1000 == 0:  # 温度が正または0の場合
        temperature = data*0.0625
    else: # 温度が負の場合、 絶対値を取ってからマイナスをかける
        temperature = ( (~data&0x1fff) + 1)*-0.0625
    return temperature

def switch_pressed(p):
    global prev_time
    pressed_time = ticks_ms()
    if pressed_time < prev_time + 200 :
        return
    prev_time = pressed_time
    
    global mode
    mode = (mode+1)%4
    displayWeather()

# 各天気をカタカナに
def replaceWeather(weather):
    if weather.find('晴') != -1:
        return chr(0xca)+chr(0xda) # ハレ
    elif weather.find('曇') != -1:
        return chr(0xb8)+chr(0xd3)+chr(0xd8) # クモリ
    elif weather.find('雨') != -1:
        return chr(0xb1)+chr(0xd2) # アメ
    elif weather.find('雪') != -1:
        return chr(0xd5)+chr(0xb7) # ユキ
    else:
        return '--'

# Webサービスより天気予報を取得
def getWeather():
    global minmax, weather, minmax2, weather2

    try:
        address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang)
        weather_json = requests.get(address).json()
        print('インターネットから天気予報データを入手しました')
    except requests.exceptions.RequestException:
        print('ネットワークエラー')
        if second==0:
            sys.exit()

    weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'}

    for i in range(2):
        forecast = weather_json['list'][8*i]
        temp_min = round(forecast['main']['temp_min'])
        temp_max = round(forecast['main']['temp_max'])
        try:
            weather_tmp = weather_dict[forecast['weather'][0]['main']]
        except KeyError:
            weather_tmp = '未定義'

        if i==0:
            minmax = '{}/{}'.format(temp_min, temp_max)
            weather = replaceWeather(weather_tmp)
        else:
            minmax2 = '{}/{}'.format(temp_min, temp_max)
            weather2 = replaceWeather(weather_tmp)

# 天気情報をLCDに表示
def displayWeather():
    clear()
    if temperature != 999:
        s = '{0:.1f}'.format(temperature) # 小数点以下1桁
    else:
        s = ''
    if mode==0:
        day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+weather)
    elif mode==1:
        day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+minmax)
    elif mode==2:
        day = chr(0xb1)+chr(0xbd) # アス
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+weather2)
    elif mode==3:
        day = chr(0xb1)+chr(0xbd) # アス
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+minmax2)

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()
address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。通常は32、文字が薄いときは40を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()

prev_time = ticks_ms()
switch = Pin(17, Pin.IN, Pin.PULL_UP)
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

address_adt7410 = 0x48
register_adt7410 = 0x00

mode = 0
temperature = 999
minmax = ''
weather = ''
minmax2 = ''
weather2 = ''

second = 0  # プログラム起動時から経過した秒数(目安)
hour = 0    # プログラム起動時から経過した時間(目安)
prevhour = -1  # 前回天気予報を問い合わせた時間

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

try:
    while True:
    
        if hour != prevhour: # 前回問い合わせより約1時間後
            getWeather()     # 天気予報取得
            prevhour = hour

        try:
            temperature = read_adt7410() # 温度取得
        except IOError:
            temperature = 999  # 温度取得失敗

        displayWeather()  # LCDに情報表示

        second = second + 1 # 1秒進める 
        hour = int(second/3600)  # 秒を時間に変換(0,1,2,…のように整数のみ)

        sleep(1)

except KeyboardInterrupt:
    pass
そして、書籍の bb2-04-05-lcd-3modes.py に該当するのが下記のプログラムです。
from machine import Pin, I2C
from time import sleep, ticks_ms
import network
import requests
import json
import sys

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

lat = '35.6895'
lon = '139.6917'
units = 'metric'
lang = 'ja'
key = 'API_KEY'

def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

def read_adt7410():
    word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 2), 'little', False)
    data = (word_data & 0xff00)>>8 | (word_data & 0xff)<<8
    data = data>>3 # 13ビットデータ
    if data & 0x1000 == 0:  # 温度が正または0の場合
        temperature = data*0.0625
    else: # 温度が負の場合、 絶対値を取ってからマイナスをかける
        temperature = ( (~data&0x1fff) + 1)*-0.0625
    return temperature

def switch_pressed(p):
    global prev_time
    pressed_time = ticks_ms()
    if pressed_time < prev_time + 200 :
        return
    prev_time = pressed_time
    
    global mode
    mode = (mode+1)%3
    displayWeather()

# 各天気をカタカナに
def replaceWeather(weather):
    if weather.find('晴') != -1:
        return chr(0xca)+chr(0xda) # ハレ
    elif weather.find('曇') != -1:
        return chr(0xb8)+chr(0xd3)+chr(0xd8) # クモリ
    elif weather.find('雨') != -1:
        return chr(0xb1)+chr(0xd2) # アメ
    elif weather.find('雪') != -1:
        return chr(0xd5)+chr(0xb7) # ユキ
    else:
        return '--'

# Webサービスより天気予報を取得
def getWeather():
    global minmax, weather, minmax2, weather2

    try:
        address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang)
        weather_json = requests.get(address).json()
        print('インターネットから天気予報データを入手しました')
    except requests.exceptions.RequestException:
        print('ネットワークエラー')
        if second==0:
            sys.exit()

    weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'}

    for i in range(2):
        forecast = weather_json['list'][8*i]
        temp_min = round(forecast['main']['temp_min'])
        temp_max = round(forecast['main']['temp_max'])
        try:
            weather_tmp = weather_dict[forecast['weather'][0]['main']]
        except KeyError:
            weather_tmp = '未定義'

        if i==0:
            minmax = '{}/{}'.format(temp_min, temp_max)
            weather = replaceWeather(weather_tmp)
        else:
            minmax2 = '{}/{}'.format(temp_min, temp_max)
            weather2 = replaceWeather(weather_tmp)

# 天気情報をLCDに表示
def displayWeather():
    clear()
    if temperature != 999:
        s = '{0:.1f}'.format(temperature) # 小数点以下1桁
    else:
        s = ''
    if mode==0:
        day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+weather)
    elif mode==1:
        day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+minmax)
    elif mode==2:
        day = chr(0xb1)+chr(0xbd) # アス
        s = minmax2
        for i in range(chars_per_line-len(day)-len(s)):
            s = ' '+s
        write_string(day+s+weather2)

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()
address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。通常は32、文字が薄いときは40を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()

prev_time = ticks_ms()
switch = Pin(17, Pin.IN, Pin.PULL_UP)
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

address_adt7410 = 0x48
register_adt7410 = 0x00

mode = 0
temperature = 999
minmax = ''
weather = ''
minmax2 = ''
weather2 = ''

second = 0  # プログラム起動時から経過した秒数(目安)
hour = 0    # プログラム起動時から経過した時間(目安)
prevhour = -1  # 前回天気予報を問い合わせた時間

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

try:
    while True:
    
        if hour != prevhour: # 前回問い合わせより約1時間後
            getWeather()     # 天気予報取得
            prevhour = hour

        try:
            temperature = read_adt7410() # 温度取得
        except IOError:
            temperature = 999  # 温度取得失敗

        displayWeather()  # LCDに情報表示

        second = second + 1 # 1秒進める 
        hour = int(second/3600)  # 秒を時間に変換(0,1,2,…のように整数のみ)

        sleep(1)

except KeyboardInterrupt:
    pass
どちらのプログラムもこれまでと同様、
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
および
key = 'API_KEY'
の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895'
lon = '139.6917'
そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「温度データと天気予報データを LCD に定期的に表示」の順で処理が進みます。

5章 Raspberry Pi Picoを家電製品のリモコンにしてみよう

5章で解説した、Raspberry Pi を家電製品のリモコンにする演習も、Pico で実行できます。
その際、meloncookie さんが開発した RemotePy を利用します。 これは、MicroPython で赤外線リモコンの信号を送受信するためのライブラリです。meloncookie さんに感謝いたします。

ここからは、以下の流れで解説を進めます。 動作確認に相当する 5.1、5.2、5.3 の演習が重要なのですが、解説が長く複雑で、自分が何をやっているのかわからなくなりがちです。慌てずに一つずつ作業を進めてください。
5.4 と 5.5 は書籍の5.4章に相当する内容です。

5.1 Pico 上に RemotePy をセットアップする

ここでは、RemotePy によって提供される 3 つのファイルを Pico 上に保存します。ここでつまづくと以下の演習も実行できませんので、着実に進めていきましょう。

まず、RemotePy のプログラムを圧縮ファイルとして保存しましょう。下記のリンク先で緑色の「Code」ボタン→Download ZIPを選択しましょう。 RemotePy が提供されたファイル群がまとめられた RemotePy-main.zip という圧縮ファイルがダウンロードされますので、お好みの場所に保存します。
次に、ダウンロードされた RemotePy-main.zip を展開します。Windows 11 でしたら、RemotePy-main.zip を右クリックし、「すべて展開」を選択し、現れた「圧縮 (ZIP 形式) フォルダーの展開」という画面で「展開」ボタンをクリックすれば、展開が進みます。もし右クリックで「すべて展開」という選択肢がない場合、「展開」や「解凍」という語句を含む選択肢を探してください。 「展開」や「解凍」は、一つにまとめられたファイルをバラバラに戻すことを指します。
この展開作業を行わないと、先には進めませんので確実に実行してください。

さて、次にこれまで通り Pico を Thonny がインストールされた PC に USB ケーブルで接続します。最終的にブラウザ経由でリモコン信号を送信しますので、Wifi 機能がある Pico W や Pico 2 W が良いでしょう。

そして Thonny を起動し、右下の選択肢で下図のように「MicroPython (Raspberry Pi Pico)」を選択します。さらに、Thonny のメニューから「表示」→「ファイル」を選択し、チェックを入れます。
そうすると、Thonny の左側に、ファイルの一覧を表示する領域があらわれます。上側が PC のファイルを表示する領域で、下側が Pico 上のファイルを表示する領域です。
ここで重要なのは Pico 上のファイルを表示する領域で、下図ではこれまで用いてきた main.py が保存されていることがわかります。
Pico 上に RemotePy が提供する 3 つのファイルを保存し、この領域に表示されることを確認するのがここで目指すことです。
次に、Thonny のメニューから「ファイル」→「ファイルを開く」を選択します。すると、どこにあるファイルを開くのかを決めるために、下記の画面が現れます。ここでは、先ほどダウンロードして展開した RemotePy のファイルを開きたいので、「このコンピュータ」をクリックします。
すると、どのファイルを開くか選択する画面が現れますので、先ほど保存し展開したファイル群から下記のファイルを選択して開きましょう。
  • RemotePy/demo/RP2040/micropython/main.py
すると、下図のように Thonny 上で main.py が開き、表示されます。
ここで注意しなければならないのは、このファイルは皆さんの PC 上にあるファイルであり、Pico 上にあるファイルではない、ということです。
そのことは、Thonny のタブ上に表示されるファイル名が、[] (角かっこ) で囲われていないことでわかります。すぐ下で確認できるように、Pico 上のファイルのファイル名は [] で囲われるのです。

次に、この PC 状の main.py を Pico 上に保存しましょう。Pico には既に main.py が存在するでしょうから、多くの場合、上書き保存になるでしょう。
Thonny のメニューから「ファイル」→「名前を付けて保存」を選択しましょう。どこに保存するかを決めるため下記の画面が現れますので、「Raspberry Pi Pico」の方をクリックしましょう。
そこで、下記の画面が現れますので、main.py というファイル名を記入してOKボタンをクリックしましょう。上書きの確認を求められたらOKしてください。
すると、下記の状態になります。ほとんど変化がないように見えますが、タブ部のファイル名が [] (角かっこ)で囲われており、そのファイルが Pico 上のファイルであることがわかります。
以上が終わったら、下記の2ファイルについても同じことを行います。
  • RemotePy/micropython/RP2040/FromV1_17/UpyIrRx.py
  • RemotePy/micropython/RP2040/FromV1_17/UpyIrTx.py
すなわち、「PC 上の UpyIrRx.py を Thonny で開き、Pico 上に UpyIrRx.py という名前で保存する」、「PC 上の UpyIrTx.py を Thonny で開き、Pico 上に UpyIrTx.py という名前で保存する」という手順を行うのです。
「UpyIrRx.py」、「UpyIrTx.py」というファイル名は複雑ですので、このページからコピーして Thonny 上に貼り付けることを推奨します。

最終的に下図の状態になれば Pico の準備完了です。
main.py のタブをクリックしてから実行すればそのプログラムが動作しますし、これまで通り、Pico に電源を入れると即座にその main.py が動作し始めます。
ただし、main.py が実行されても何も起こりません。このプログラムは「PC 上のデモアプリケーションからの指令を待っている」状態なのです。PC 上のデモアプリケーションから指令が来たときに、「赤外線リモコンの信号の読み取り」や「信号の送信」が行われる、というわけです。

そのため、次にその「PC 上のデモアプリケーション」を実行することにしましょう。

5.2 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を記録する

それでは、PC上のデモアプリケーションを実行してテレビのリモコンの信号を記録してみましょう。
この演習を実行するには、すぐ上で行った「5.1 Pico 上に RemotePy をセットアップする」でセットアップした Pico がその PC に USB 接続されており、
さらにその上の main.py が実行されていることが必要です。

セットアップを行った Pico の USB ケーブルを一度抜き差しすれば自動的にその状態が実現します。
main.py は電源投入時に自動的に実行されるからです。

さて、Thonny を起動し、右下の選択肢を下図のように「ローカルPython3」へ変更します。
そして、ローカルPython3 の Thonny に、numpy というライブラリをインストールします。デモアプリケーションの実行に必要になるためです。

そのため、下図のようにメニューから「ツール」→「パッケージを管理」を選択します。
なお、Thonny の実行環境として Windows ではなく Rasberry Pi を用いている方はここでのインストール作業は不要ですので、「パッケージを管理」は実行しないでください。
「パッケージを管理」の選択により現れた画面で、下図のように「numpy」を入力して「PyPIを検索」をクリックします。
すると、numpy のリンクが現れますのでクリックしてください。
すると、下図のように numpy のインストールボタンが現れますので、インストールしてください。以上でパッケージの追加が終わります。
なお、Thonny の実行環境として Windows ではなく Rasberry Pi を用いている方は、ここから作業を再開します。

さて、引き続き「ローカルPython3」が選択された Thonny で、「ファイル」→「ファイルを開く」から
  • RemotePy/demo/RP2040/GUI/main.py
を開いてください。下図のようになります。
開いたら、その main.py を実行しましょう。下図のデモアプリケーションが起動します。
これは、meloncookie 氏が作成したアプリケーションであり、これを用いてテレビのリモコンの信号の記録や信号の送信の動作確認を行うことになります。
その動作確認が済んでから本書のプログラムを実行する、という流れです。
さて、ここではテレビのリモコンの信号を読み取って記録しますので、下図のように赤外線リモコン受信モジュールを用いた回路をブレッドボード上に作成しておきましょう。
そうしたら、デモアプリケーションでSelectボタンをクリックし、リモコンの信号を記録したいファイルの位置とファイル名を決めましょう。下図ではデスクトップ上に TV.txt を保存するよう設定を行いました。
場所はお好みの場所で構いませんが、ファイル名は半角英字で TV.txt としてください。
次にOpenボタンをクリックすると、Key list に __system__ という項目が現れます。
ここから先は、リモコンの信号を一つずつ記録していくことになります。
書籍で行ったように、power, input, ch1, ch2, …, ch12, vup, vdown, cup, cdown の名前で信号を記録していきましょう。
それぞれ、電源、入力切替、チャンネル1~チャンネル12、ボリュームアップ、ボリュームダウン、チャンネルアップ、チャンネルダウンの意味でしたね。

まずは、電源ボタンを power という名称で記録しましょう。Edit key の領域に power を記入し、Append ボタンをクリックします。
すると、Key list に power という項目が現れます。
次に、Key list の power が選択された状態(背景色が青の状態)で Record ボタンをクリックします。
それから3秒以内に、ブレッドボード上の赤外線リモコン受信モジュールにテレビのリモコンを向けて、電源(power)ボタンを一度押します。
すると、Signal の領域に、読み取られた信号が記入されます。
その状態で Commit ボタンをクリックすると、読み取った信号が確定され、背景色が緑に変化します。
以上の流れを、記録したい全ての信号に対して行います。すなわち、電源(power)の次に記録する入力切替(input)ボタンについて書けばこうです。
  1. Edit key の領域に input と記入し、Append ボタンをクリック
  2. Key list の input が選択された状態で Record ボタンをクリック
  3. 3秒以内に赤外線リモコン受信モジュールに向けてリモコンの入力切替(input)ボタンを一度押す
  4. Commitボタンをクリックして信号を確定
18個のリモコンのボタンに対して同じ作業を行いますので、少し大変です。

全ての信号を記録すると、下図の状態になります。そうしたら、Save ボタンをクリックします。最初に設定した場所に、記録された信号を全てまとめたファイル TV.txt が保存されます。
実際に PC 上に保存された TV.txt を見つけ、ダブルクリックして開いてみましょう。Windows のメモ帳で開いた場合、下図のような見た目になります。数字の羅列が表示されるのを確認できれば閉じてOKです。
なお、このデータは改行のない長い1行として保存されています。画面右端でデータが折り返されて表示されているので、下図のような見た目になっているのです。
画面右端での折り返しが行われないテキストエディタで TV.txt を開いた場合、データの先頭しか表示されないことがありますが、気にする必要はありません。
さて、この TV.txt に記録されたリモコン信号のデータを利用し、今度はリモコンの信号の送信をテストしてみましょう。
ここで用いたデモアプリケーションを引き続き用います。

5.3 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を送信する

ここからは、先ほど記録したテレビのリモコンの信号を、赤外線LEDを通してテレビに送信してみます。
「5.1 Pico 上に RemotePy をセットアップする」でセットアップした Pico がその PC に USB 接続されており、その上の main.py が実行されていることが必要です。

さらに、「5.2 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を記録する」で実行したように、
「PC 上のデモアプリケーション」上にすべての信号が記録されていることが必要です。
5.2 からそのまま実行を続ける方は既にその状態が実現されています。

一方、「PC 上のデモアプリケーション」を一度終了してしまっている方は、記録した信号を TV.txt から読み込む必要があります。その手順は下図に示されています。
Select ボタンで保存済みの TV.txt を読み込んで Open ボタンをクリックすると、保存した信号のリストが(アルファベット順にソートされた状態で)Key list に現れる、というわけです。
さて、Key list にすべての信号のリストが存在する状態を実現したら、次にブレッドボート上に下図の信号送信用の回路を構成する必要があります。小さなブレッドボードでも実現可能なよう、ちょっと窮屈な配置となっておりますのでご注意ください。
回路上で「VBUS(5V)」に対する接続は、間違うことのないよう慎重に行ってください。回路上で 5V で動作するのはこの部分のみで、他は 3.3V で動作しているためです。
さて、以上の準備ができたら、赤外線LEDの先端部をテレビのリモコン受光面に向け、信号を送信してみましょう。
PC上のでもアプリケーションの Key list にある信号から、送信したい信号の名称をクリックし、Send ボタンをクリックするのです。
まず最初に試すべきは電源ボタンに相当する power でしょうか。
power の動作確認ができたら、他の信号も試してみましょう。それぞれの信号の送信でテレビの状態が変化することを確認できたら、ここでの動作確認は終わりです。

5.4 RemotePy と自作プログラムを組み合わせ、タクトスイッチでテレビのリモコンの信号を送信する

さて、ここまでは、Pico上のプログラムとPC上のデモアプリケーションは全て RemotePy が提供するファイルを用いてきました。
それらを用いて、リモコンの信号の記録と送信を行えるということを確認しました。

ここからは、下記の方針で演習を続けます。書籍でいうと 5.4 章の内容の Pico 版を実現することになります。
  • PC 上のアプリケーションは不要
  • Pico 上の main.py のみ、自作プログラムに変更し、「タクトスイッチでのリモコン操作」および「ブラウザ経由でのリモコン操作」を実現する
まずは、書籍の 5.4.2 章で行った「タクトスイッチでのリモコン操作」を Pico で実現しましょう。
そのためには、先ほど保存した TV.txt を Pico 上に保存する必要があります。すなわち、MicroPython (Raspberry Pi Pico) を指定した Thonny で下図の状態を実現するということです。
TV.txt を Pico 上に保存するには、まず MicroPython (Raspberry Pi Pico) を指定した Thonny のメニューから「ファイル」→「ファイルを開く」を選択し、「このコンピュータ」から TV.txt を開く必要があります。
その際、ファイル選択画面の下図の部分を「all files (*.*)」に変更しないと、TV.txt がファイルの選択肢に現れませんのでご注意ください。
さらに、Thonny では画面右端での折り返しが行われませんから、開いた TV.txt は 1 行目の先頭部分しか表示されませんが、気にする必要はありません。
TV.txt を開いたら、Thonny のメニューから「ファイル」→「名前を付けて保存」を選択し、Raspberry Pi Pico 上に TV.txt という名称で保存するのでした。

また、Pico 上の main.py は以下のプログラムで置き換えます。このプログラムは書籍の bb2-05-01-TV.py に相当し、タクトスイッチで power、cup、cdown、vup、vdown の 5 つの信号の送信を実現します。
from machine import Pin
from time import sleep, ticks_ms
import json
from UpyIrTx import UpyIrTx

tx_pin = Pin(19, Pin.OUT)
tx = UpyIrTx(0, tx_pin, 38000, 30, 1)

with open('TV.txt') as f:
    commands = json.load(f)

def switch_pressed(p):
    global prev_time
    pressed_time = ticks_ms()
    if pressed_time < prev_time + 200 :
        return
    prev_time = pressed_time

    pin_id = int(str(p)[8:11].rstrip(",")) # expecting "Pin(GPIOXX, mode=IN, pull=PULL_UP)"
    if pin_id == 15:
        tx.send(commands['power']['signal'])
    elif pin_id == 14:
        tx.send(commands['cup']['signal'])
    elif pin_id == 13:
        tx.send(commands['cdown']['signal'])
    elif pin_id == 12:
        tx.send(commands['vup']['signal'])
    elif pin_id == 11:
        tx.send(commands['vdown']['signal'])

prev_time = ticks_ms()

pin_power = Pin(15, Pin.IN, Pin.PULL_UP)
pin_power.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

pin_cup = Pin(14, Pin.IN, Pin.PULL_UP)
pin_cup.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

pin_cdown = Pin(13, Pin.IN, Pin.PULL_UP)
pin_cdown.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

pin_vup = Pin(12, Pin.IN, Pin.PULL_UP)
pin_vup.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)

pin_vdown = Pin(11, Pin.IN, Pin.PULL_UP)
pin_vdown.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)
このプログラムを動かすための回路は下図になります。この回路は大きなブレッドボードでしか実現できませんのでご注意ください。赤外線 LED 部はデモアプリケーションで用いた回路と同じです。
回路が組めたら、Pico 上の main.py を実行し、ブレッドボート上の赤外線 LED をテレビの受光面に向け、タクトスイッチを押してみましょう。
タクトスイッチは、左から電源(power)、チャンネルアップ(cup)、チャンネルダウン(cdown)、ボリュームアップ(vup)、ボリュームダウン(vdown)に相当します。
テレビの状態が変化したら、この演習は成功です。

5.5 RemotePy と自作プログラムを組み合わせ、ブラウザでテレビのリモコンの信号を送信する

5章の演習の最後に、ブラウザでテレビのリモコンの信号を送信する演習を行ってみましょう。書籍でいえば 5.4.3 章の内容です。
先ほどのタクトスイッチの例と同様、Pico 上には UpyIrRx.py、UpyIrTx.py、TV.txt、main.py の 4 ファイルが存在する必要があります。そして、main.py の内容は下記のプログラムで置き換えます。
import sys
import network
import socket
from time import sleep
from machine import Pin, I2C
import json
from UpyIrTx import UpyIrTx

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()

tx_pin = Pin(19, Pin.OUT)
tx = UpyIrTx(0, tx_pin, 38000, 30, 1)

with open('TV.txt') as f:
    commands = json.load(f)

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>テレビのリモコン</title>
<style>button {
    width: 7em;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
button:active {
    position:relative;
    top:1px;
}
</style></head>
<body>

<div align="center">
<table border="0">
  <tr>
    <td>
      <form><button name="input" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">入力切替</button></form>
    </td>
    <td>
    </td>
    <td>
      <form><button name="power" value="pressed" type="submit" style="background: rgb(249 25 25); border: rgb(249 25 25);">電源</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="ch1" value="pressed" type="submit">1</button></form>
    </td>
    <td>
      <form><button name="ch2" value="pressed" type="submit">2</button></form>
    </td>
    <td>
      <form><button name="ch3" value="pressed" type="submit">3</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="ch4" value="pressed" type="submit">4</button></form>
    </td>
    <td>
      <form><button name="ch5" value="pressed" type="submit">5</button></form>
    </td>
    <td>
      <form><button name="ch6" value="pressed" type="submit">6</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="ch7" value="pressed" type="submit">7</button></form>
    </td>
    <td>
      <form><button name="ch8" value="pressed" type="submit">8</button></form>
    </td>
    <td>
      <form><button name="ch9" value="pressed" type="submit">9</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="ch10" value="pressed" type="submit">10</button></form>
    </td>
    <td>
      <form><button name="ch11" value="pressed" type="submit">11</button></form>
    </td>
    <td>
      <form><button name="ch12" value="pressed" type="submit">12</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <div align="center">
        音量
      </div>
    </td>
    <td>
    </td>
    <td>
      <div align="center">
        チャンネル
      </div>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="vup" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↑</button></form>
    </td>
    <td>
    </td>
    <td>
      <form><button name="cup" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↑</button></form>
    </td>
  </tr>

  <tr>
    <td>
      <form><button name="vdown" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↓</button></form>
    </td>
    <td>
    </td>
    <td>
      <form><button name="cdown" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↓</button></form>
    </td>
  </tr>

</table>
</div>

</body>
</html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        power_pressed = request.find('power=pressed')
        input_pressed = request.find('input=pressed')
        ch1_pressed = request.find('ch1=pressed')
        ch2_pressed = request.find('ch2=pressed')
        ch3_pressed = request.find('ch3=pressed')
        ch4_pressed = request.find('ch4=pressed')
        ch5_pressed = request.find('ch5=pressed')
        ch6_pressed = request.find('ch6=pressed')
        ch7_pressed = request.find('ch7=pressed')
        ch8_pressed = request.find('ch8=pressed')
        ch9_pressed = request.find('ch9=pressed')
        ch10_pressed = request.find('ch10=pressed')
        ch11_pressed = request.find('ch11=pressed')
        ch12_pressed = request.find('ch12=pressed')
        vup_pressed = request.find('vup=pressed')
        cup_pressed = request.find('cup=pressed')
        vdown_pressed = request.find('vdown=pressed')
        cdown_pressed = request.find('cdown=pressed')

        if power_pressed == 8:
            tx.send(commands['power']['signal'])
        elif input_pressed == 8:
            tx.send(commands['input']['signal'])
        elif ch1_pressed == 8:
            tx.send(commands['ch1']['signal'])
        elif ch2_pressed == 8:
            tx.send(commands['ch2']['signal'])
        elif ch3_pressed == 8:
            tx.send(commands['ch3']['signal'])
        elif ch4_pressed == 8:
            tx.send(commands['ch4']['signal'])
        elif ch5_pressed == 8:
            tx.send(commands['ch5']['signal'])
        elif ch6_pressed == 8:
            tx.send(commands['ch6']['signal'])
        elif ch7_pressed == 8:
            tx.send(commands['ch7']['signal'])
        elif ch8_pressed == 8:
            tx.send(commands['ch8']['signal'])
        elif ch9_pressed == 8:
           tx.send(commands['ch9']['signal'])
        elif ch10_pressed == 8:
            tx.send(commands['ch10']['signal'])
        elif ch11_pressed == 8:
            tx.send(commands['ch11']['signal'])
        elif ch12_pressed == 8:
            tx.send(commands['ch12']['signal'])
        elif vup_pressed == 8:
            tx.send(commands['vup']['signal'])
        elif cup_pressed == 8:
            tx.send(commands['cup']['signal'])
        elif vdown_pressed == 8:
            tx.send(commands['vdown']['signal'])
        elif cdown_pressed == 8:
            tx.send(commands['cdown']['signal'])
               
        response = html
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
このプログラムを動かすための回路は次の通りです。
見てわかる通り、赤外線 LED 以外に I2C 接続の小型 LCD も接続されています。これは、Pico W / Pico 2 W に割り当てられた IP アドレスやエラーメッセージを表示するためのものです。この LCD を接続しなくても演習は実行可能ですが、その場合、Pico W / Pico 2 W からのメッセージを Windows などの PC 上で見るしかなくなります。LCD を接続すること、すなわち Windows などの PC なしでも Pico W / Pico 2 W を動作させられるようにすることを強く推奨します。

上記のプログラムを実行するための注意をいくつか記します。下記のように、用いている Wifi の SSID (アクセスポイント名) とそのパスワードを記す項目があります。 ここを皆さんの環境における SSID とパスワードに変更しないとこのプログラムは動作しませんのでご注意ください。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
また、回路を LCD なしで実行する場合、プログラム中の下記の部分を消し、
##### for LCD
(省略)
##### End of LCD
下記の内容で差し替える必要があります。
def write_string(s):
    pass
さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

記述できたら Pico W / Pico 2 W でプログラムを実行してみましょう。LCD 上に(または Thonny のコンソール上に)Pico W に割り当てられた IP アドレス(例えば、 192.168.1.40 のようなもの)が表示されれば、実行が成功した可能性が高いでしょう。下図のような状態です。
そうしたら、Pico W / Pico 2 W と同じ Wifi ネットワークに接続した PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のような画面が現れます。
これは書籍図5-16とほぼ同じ見た目ですね。ブレッドボード上の赤外線をテレビの受光面に向けた状態で、ブラウザ上の電源ボタンなどを押して、リモコンの信号を送信してみましょう。
送信した信号通りにテレビが動作したらこの演習は成功です。

8章 6脚ロボットを操作してみよう

本ページの最後に、本書 8 章の 6 脚ロボットを Pico で制御してみましょう。
Pico と同じ Wifi ネットワークに接続した PC またはスマートフォンのブラウザで操作しますので、Pico W または Pico 2 W が必須です。
また、Pico ではカメラの動画を取り扱えませんので、書籍の 8.4 までの内容が対象となります。

通常の Raspberry Pi と比べると、サーボドライバが不要なのでがシンプルになること、システムの自動起動が容易であること、電源投入から動き出すまでの時間が短いことなど、メリットが大きいです。
デメリットとしては、複雑な操作ページを作りにくいことが挙げられますが、6 脚ロボットでは前進、後退、右回転、左回転、停止の 5 つの動作しかありませんので、このデメリットはあまり感じません。
それではやっていきましょう。

まずは、作成すべき回路です。この回路は、6 脚ロボットが完成した後のもので、書籍でいえば、図8-13 や図8-14 に対応します。
作成した回路を搭載した6脚ロボットが下図に示されています。
この6脚ロボットを制御するために必要なプログラムを以下に示していきましょう。

まず、6 個のサーボモーターを角度 0 に固定するプログラムは下記です。このプログラムは書籍では bb2-08-01-zero-pca9685.py に対応します。
このプログラムではまだ Wifi を利用しません。6 個のサーボモーターがニュートラルである 0 度に移動して固定されるだけです。
from machine import Pin, PWM

NEUTRAL = 0.0675   # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均
NEUTRAL = int(65535*NEUTRAL)

pwm0 = PWM(Pin(16))
pwm1 = PWM(Pin(17))
pwm2 = PWM(Pin(18))
pwm3 = PWM(Pin(19))
pwm4 = PWM(Pin(20))
pwm5 = PWM(Pin(21))

pwm0.freq(50)
pwm1.freq(50)
pwm2.freq(50)
pwm3.freq(50)
pwm4.freq(50)
pwm5.freq(50)

pwm0.duty_u16(NEUTRAL)
pwm1.duty_u16(NEUTRAL)
pwm2.duty_u16(NEUTRAL)
pwm3.duty_u16(NEUTRAL)
pwm4.duty_u16(NEUTRAL)
pwm5.duty_u16(NEUTRAL)
次に、6 脚ロボットが前進、後退、右回転、左回転の動作をそれぞれ一度ずつ実行し、最後に停止するプログラムが下記のものです。書籍では bb2-08-02-6legs-pca9685.py に対応します。
ただし、書籍では While ループにより4つの動作を繰り返し実行し続けましたが、このプログラムでは一度ずつの実行後に停止するようにしています。動作確認なので、一度ずつで十分だろうと考えたためです。
このプログラムも Wifi 接続は行いません。
from machine import Pin, PWM
from time import sleep

def forwardPhase1(delay):
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def forwardPhase2(delay):
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def forwardMove(delay):
    forwardPhase1(delay)
    forwardPhase2(delay)

def backwardPhase1(delay):
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def backwardPhase2(delay):
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def backwardMove(delay):
    backwardPhase1(delay)
    backwardPhase2(delay)

def rotateRightPhase1(delay):
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def rotateRightPhase2(delay):
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def rotateRightMove(delay):
    rotateRightPhase1(delay)
    rotateRightPhase2(delay)

def rotateLeftPhase1(delay):
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def rotateLeftPhase2(delay):
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def rotateLeftMove(delay):
    rotateLeftPhase1(delay)
    rotateLeftPhase2(delay)

def stopMove(delay):
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

NEUTRAL = 0.0675   # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均
LEG_F_R = NEUTRAL + 0.024
LEG_F_L = NEUTRAL - 0.024
LEG_B_R = NEUTRAL - 0.024
LEG_B_L = NEUTRAL + 0.024

NEUTRAL = int(65535*NEUTRAL)
LEG_F_R = int(65535*LEG_F_R)
LEG_F_L = int(65535*LEG_F_L)
LEG_B_R = int(65535*LEG_B_R)
LEG_B_L = int(65535*LEG_B_L)

DELAY = 0.15

pwm0 = PWM(Pin(16))
pwm1 = PWM(Pin(17))
pwm2 = PWM(Pin(18))
pwm3 = PWM(Pin(19))
pwm4 = PWM(Pin(20))
pwm5 = PWM(Pin(21))

pwm0.freq(50)
pwm1.freq(50)
pwm2.freq(50)
pwm3.freq(50)
pwm4.freq(50)
pwm5.freq(50)

pwm0.duty_u16(NEUTRAL)
pwm1.duty_u16(NEUTRAL)
pwm2.duty_u16(NEUTRAL)
pwm3.duty_u16(NEUTRAL)
pwm4.duty_u16(NEUTRAL)
pwm5.duty_u16(NEUTRAL)

forwardMove(DELAY)
backwardMove(DELAY)
rotateRightMove(DELAY)
rotateLeftMove(DELAY)
stopMove(DELAY)
そして、最後に、ブラウザで 6 脚ロボットを操作するためのプログラムが下記です。Pico 上に main.py として保存して実行するためのものです。
from machine import Pin, PWM, I2C
from time import sleep
import network
import socket
import sys
import _thread

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

##### for LCD
def setup_st7032():
    c_lower = (contrast & 0xf)
    c_upper = (contrast & 0x30)>>4
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c]))
    sleep(0.2)
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01]))
    sleep(0.001)

def clear():
    global position
    global line
    position = 0
    line = 0
    i2c.writeto_mem(address_st7032, register_setting, bytes([0x01]))
    sleep(0.001)

def newline():
    global position
    global line
    if line == display_lines-1:
        clear()
    else:
        line += 1
        position = chars_per_line*line
        i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0]))
        sleep(0.001)

def write_string(s):
    for c in list(s):
        write_char(ord(c))

def write_char(c):
    global position
    byte_data = check_writable(c)
    if position == display_chars:
        clear()
    elif position == chars_per_line*(line+1):
        newline()
    i2c.writeto_mem(address_st7032, register_display, bytes([byte_data]))
    position += 1

def check_writable(c):
    if c >= 0x06 and c <= 0xff :
        return c
    else:
        return 0x20 # 空白文字

i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000)
i2c.scan()
address_st7032 = 0x3e
register_setting = 0x00
register_display = 0x40

contrast = 32 # 0から63のコントラスト。30から40程度を推奨
chars_per_line = 8  # LCDの横方向の文字数
display_lines = 2   # LCDの行数

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()
##### end of LCD

def forwardPhase1(delay):
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def forwardPhase2(delay):
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def forwardMove(delay):
    forwardPhase1(delay)
    forwardPhase2(delay)

def backwardPhase1(delay):
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def backwardPhase2(delay):
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def backwardMove(delay):
    backwardPhase1(delay)
    backwardPhase2(delay)

def rotateRightPhase1(delay):
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def rotateRightPhase2(delay):
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def rotateRightMove(delay):
    rotateRightPhase1(delay)
    rotateRightPhase2(delay)

def rotateLeftPhase1(delay):
    pwm0.duty_u16(LEG_F_R)
    pwm1.duty_u16(LEG_F_R)
    pwm2.duty_u16(LEG_B_L)
    sleep(delay)
    pwm3.duty_u16(LEG_B_R)
    pwm4.duty_u16(LEG_F_L)
    pwm5.duty_u16(LEG_F_L)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def rotateLeftPhase2(delay):
    pwm3.duty_u16(LEG_F_R)
    pwm4.duty_u16(LEG_B_L)
    pwm5.duty_u16(LEG_B_L)
    sleep(delay)
    pwm0.duty_u16(LEG_B_R)
    pwm1.duty_u16(LEG_B_R)
    pwm2.duty_u16(LEG_F_L)
    sleep(delay)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    sleep(delay)

def rotateLeftMove(delay):
    rotateLeftPhase1(delay)
    rotateLeftPhase2(delay)

def stopMove(delay):
    pwm0.duty_u16(NEUTRAL)
    pwm1.duty_u16(NEUTRAL)
    pwm2.duty_u16(NEUTRAL)
    pwm3.duty_u16(NEUTRAL)
    pwm4.duty_u16(NEUTRAL)
    pwm5.duty_u16(NEUTRAL)
    sleep(delay)

def movingThread():
    while True:
        if movingStatus == MOVE_FORWARD:
            forwardMove(DELAY)
        elif movingStatus == MOVE_BACKWARD:
            backwardMove(DELAY)
        elif movingStatus == ROTATE_RIGHT:
            rotateRightMove(DELAY)
        elif movingStatus == ROTATE_LEFT:
            rotateLeftMove(DELAY)
        elif movingStatus == MOVE_STOP:
            stopMove(DELAY)
            return

wlan = network.WLAN(network.STA_IF)
wlan.active(True)
wlan.connect(ssid, password)

html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<title>六脚ロボットコントローラー</title>
<style>button {
    width: auto;
    height: auto;
    background: #003366;
    font-weight: normal;
    font-size: 14pt;
    font-family: Arial, Helvetica, sans-serif;
    font-size: 14px;
    color: #ffffff;
    padding: 10px 20px;
    -moz-border-radius: 10px;
    -webkit-border-radius: 10px;
    border-radius: 10px;
    border: 1px solid #003366;
    -moz-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    -webkit-box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    box-shadow:
        0px 1px 3px rgba(000,000,000,0.5),
        inset 0px 0px 1px rgba(255,255,255,0.5);
    text-shadow:
        0px -1px 0px rgba(000,000,000,0.7),
        0px 1px 0px rgba(255,255,255,0.3);
}
</style></head>
<p>押したボタンにより、六脚ロボットが動作します。中央のボタンをクリックしないと六脚ロボットは止まりません。</p>
<div align="center">
<table border="0">
<tr align="center">
<td></td>
<td><form><button name="forward" value="on" type="submit" style="background: %s;"> ↑ </button></form></td>
<td></td>
</tr>
<tr align="center">
<td><form><button name="rot_l" value="on" type="submit" style="background: %s;"> ← </button></form></td>
<td><form><button name="stop" value="on" type="submit" style="background: %s;"> 〇 </button></form></td>
<td><form><button name="rot_r" value="on" type="submit" style="background: %s;"> → </button></form></td>
</tr>
<tr align="center">
<td></td>
<td><form><button name="backward" value="on" type="submit" style="background: %s;"> ↓ </button></form></td>
<td></td>
</tr>
</table>
<br /><br />
</div>
</body></html>
"""

# Wait for connect or fail
max_wait = 20
while max_wait > 0:
    if wlan.status() < 0 or wlan.status() >= 3:
        break
    max_wait -= 1
    print('waiting for connection...')
    write_string('waiting for conn') #showing on LCD
    sleep(1)

# Handle connection error
if wlan.status() != 3:
    write_string('conn. failed') # showing on LCD
    raise RuntimeError('network connection failed')
else:
    print('Connected')
    status = wlan.ifconfig()
    print( 'ip = ' + status[0] )
    write_string(status[0]) #showing on LCD

# Open socket
addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1]
s = socket.socket()
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
try:
    s.bind(addr)
except OSError as e:
    print(e)
    s.close()
    s = None
    write_string('socket error') #showing on LCD
    sys.exit()

s.listen(1)
print('listening on', addr)

NEUTRAL = 0.0675   # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均
LEG_F_R = NEUTRAL + 0.024
LEG_F_L = NEUTRAL - 0.024
LEG_B_R = NEUTRAL - 0.024
LEG_B_L = NEUTRAL + 0.024

NEUTRAL = int(65535*NEUTRAL)
LEG_F_R = int(65535*LEG_F_R)
LEG_F_L = int(65535*LEG_F_L)
LEG_B_R = int(65535*LEG_B_R)
LEG_B_L = int(65535*LEG_B_L)

DELAY = 0.15

pwm0 = PWM(Pin(16))
pwm1 = PWM(Pin(17))
pwm2 = PWM(Pin(18))
pwm3 = PWM(Pin(19))
pwm4 = PWM(Pin(20))
pwm5 = PWM(Pin(21))

pwm0.freq(50)
pwm1.freq(50)
pwm2.freq(50)
pwm3.freq(50)
pwm4.freq(50)
pwm5.freq(50)

pwm0.duty_u16(NEUTRAL)
pwm1.duty_u16(NEUTRAL)
pwm2.duty_u16(NEUTRAL)
pwm3.duty_u16(NEUTRAL)
pwm4.duty_u16(NEUTRAL)
pwm5.duty_u16(NEUTRAL)

MOVE_FORWARD = 0
MOVE_BACKWARD = 1
ROTATE_RIGHT = 2
ROTATE_LEFT = 3
MOVE_STOP = 4

onCol = '#26a1ff'
offCol = '#003366'
move_strs = (offCol, offCol, onCol, offCol, offCol)

isMoving = False
movingStatus = MOVE_STOP

# Listen for connections, serve client
while True:
    try:
        cl, addr = s.accept()
        #print('client connected from', addr)
        request = cl.recv(1024)
        #print("request:")
        #print(request)
        request = str(request)
        forward_on = request.find('forward=on')
        rot_l_on = request.find('rot_l=on')
        stop_on = request.find('stop=on')
        rot_r_on = request.find('rot_r=on')
        backward_on = request.find('backward=on')

        if forward_on == 8:
            move_strs = (onCol, offCol, offCol, offCol, offCol)
            movingStatus = MOVE_FORWARD
        if rot_l_on == 8:
            move_strs = (offCol, onCol, offCol, offCol, offCol)
            movingStatus = ROTATE_LEFT
        if stop_on == 8:
            move_strs = (offCol, offCol, onCol, offCol, offCol)
            movingStatus = MOVE_STOP
            isMoving = False
        if rot_r_on == 8:
            move_strs = (offCol, offCol, offCol, onCol, offCol)
            movingStatus = ROTATE_RIGHT
        if backward_on == 8:
            move_strs = (offCol, offCol, offCol, offCol, onCol)
            movingStatus = MOVE_BACKWARD
                
        if ((movingStatus == MOVE_FORWARD or movingStatus == MOVE_BACKWARD 
            or movingStatus == ROTATE_LEFT or movingStatus == ROTATE_RIGHT) 
            and isMoving == False):
            isMoving = True
            _thread.start_new_thread(movingThread, ())

        response = html % move_strs 
        cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n')
        cl.send(response)
        cl.close()

    except OSError as e:
        print('connection closed')

    except KeyboardInterrupt as e:
        break

s.close()
s = None
上記のプログラムを実行するための注意をいくつか記します。下記のように、用いている Wifi の SSID (アクセスポイント名) とそのパスワードを記す項目があります。 ここを皆さんの環境における SSID とパスワードに変更しないとこのプログラムは動作しませんのでご注意ください。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'
また、回路を LCD なしで実行する場合、プログラム中の下記の部分を消し、
##### for LCD
(省略)
##### End of LCD
下記の内容で差し替える必要があります。
def write_string(s):
    pass
さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

記述できたら Pico W / Pico 2 W でプログラムを実行してみましょう。成功すれば、LCD 上に(または Thonny のコンソール上に)Pico W に割り当てられた IP アドレス(例えば、 192.168.1.40 のようなもの)が表示されます。
そうしたら、Pico W / Pico 2 W と同じ Wifi ネットワークに接続した PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のような画面が現れます。
前進、後退、右回転、左回転に相当するボタンがあり、その中央に静止ボタンがあります。ボタンを押して、6脚ロボットが動作するか、確認してみましょう。

細かな話ですが、ブラウザからの指令の待ち受けを一つ目の CPU コアで、6脚ロボットの制御を二つ目の CPU コアで実現しています。

おわりに

いかがでしたか?
作業を開始したときはまさかここまで巨大なページになるとは思いませんでした。

大変な作業ではありましたが、Pico を使う機会が増えるほど、Pico シリーズに対する愛着が増していくのを実感します。
これまでマイコンを使わなければいけない機会があると、ただ単に習慣から Arduino を選ぶケースが多かったのですが、これからは Pico に乗り換えても良いかな?という気持ちになりつつあります。

みなさんも是非 Pico を体験してみてください!