2025年8月28日木曜日

ラズパイ5対応 カラー図解 最新 Raspberry Piで学ぶ電子工作 補足情報トップ

本ページは、2024年10月発売の金丸隆志著「ラズパイ5対応 カラー図解 最新 Raspberry Piで学ぶ電子工作」(技術評論社)の補足情報をまとめるためのページです。

以下のページに、書籍で利用したコマンド一覧や補足情報などを掲載します。

インストールや設定に関する補足情報はこちらをご覧下さい。
Raspberry Pi Zero W 系を利用したい方はこちらをご覧ください。
マイクロコントローラーである Raspberry Pi Pico シリーズで電子工作してみたい方は
以下のページをご覧ください。 さらに、本書の続編である「実例で学ぶRaspberry Pi電子工作」の演習の Pico 版の解説も公開を開始しました。是非お楽しみください!
やや上級者向けの、Raspberry Pi をより便利に使う方法はこちらをご覧下さい。
本ページを活用し、Raspberry Pi を楽しみましょう!

書籍で用いたコマンドおよび補足情報

本ページで表示するコマンドの利用方法

本ページには、本書の演習を実行するために必要なコマンドを全て記し、さらに、本書への補足情報を記していきます。コマンドを本ページからコピーして実行することにより、書き写すことによるミスの恐れがなく確実に実行できます。

本ページのコマンドをコピーにより活用するためには、まずRaspberry Pi上のブラウザ(Chromium)で本ページを開きます。

そして、コピーしたいコマンドをマウスでなぞり色を反転してください。その状態からそのコマンドをコピーするには下記の二つの方法のうちどちらかを実行してください。
  • 色を反転した領域をマウスで右クリックして「コピー」を選択する
  • キーボードの「Ctrl」キーを押しながら「C」キーを押す(Ctrl-C)
そして、コピーされたコマンドをターミナルソフトウェアに貼り付けるには、下記の三つの方法のうちどれかを実行してください。
  • LXTerminalのメニューから「編集」→「貼り付け」を選択する
  • LXTerminal上でキーボードの「Ctrl」キーと「Shift」キーを押しながら「V」キーを押す(Ctrl+Shift+V)
  • LXTerminal上でマウスのホイールを押し込む(ただし、この方法は正確には「コピーされた文字を貼り付ける」のではなく「マウスで色が反転された文字を貼り付ける」という動作になります)
いずれかの方法でLXTerminalにコマンドが貼り付けられたら、そのままキーボードの「Enter」キーを押せばコマンドが実行されます。

なお、複数のコマンドが複数行にわたって連続して書かれている場合、コピー、貼り付け、実行はコマンド一つごとに行ってください。

それでは、本ページを活用して、Raspberry Pi での電子工作を楽しんでください!

2章

p.16, Raspberry Pi を購入できるショップへのリンク

p.16 に記した、Raspberry Pi を購入できるショップへのリンクは以下の通りです。 また、入手しやすい Raspberry Pi の購入リンクを、 著者による別書籍「Raspberry Piではじめる機械学習 補足情報」のサポートサイトRaspberry PiへのOSのインストール方法の冒頭にまとめましたのでご利用ください。

p.19, Pi 5 対応のACアダプタへのリンク

p.19 に記した、Pi 5 に対応したACアダプタへのリンクを以下に記します。2025年8月現在、公式版、スイッチサイエンス版、KSY版があります。お好みのものをお求めください。
公式版 Raspberry Pi 公式ACアダプター(27W USB PD Type-C)
秋月電子通商(黒) / 秋月電子通商(白) / 千石電商(黒)
スイッチサイエンス版 ラズパイ5に最適なACアダプター 5.1V/5.0A USB-PD Type-Cコネクタ出力
スイッチサイエンス
KSY版 USB電源アダプター 5V/5A Type C 1.2m
KSY / 千石電商

p.25, Raspberry Pi Imager のダウンロードページへのリンク

p.25 に記した、Raspberry Pi Imager のダウンロードページへのリンクは以下の通りです。

p.28, Pi Zero 系の機種にインストールすべき OS

書籍において、『Pi Zero 2 W では「Raspberry Pi OS (Legacy, 32-bit)」を選択したほうが安定して動作する可能性』があると記しました。執筆当時の Legacy OS は Bullseye でした。
これは、執筆当時の最新 OS である Bookworm では、2023-10-10 から 2024-03-15 のバージョンまで、OS の更新時に処理が進まなくなる、などの問題があったためです。
この問題は、2024-07-04 のバージョンで改善されましたので、Pi Zero 系の機種で Bookworm を選んでいただいても問題ありません。
さらに、2025年10月に OS が更新され、最新版 = Trixie、Legacy = Bookworm へと変更されました。どの OS を用いても利用可能となるよう、メンテナンスを続けています。

いずれにせよ、ブラウザの動作が非常に重いなど、Pi Zero 系が上級者向けの機種であることには変わりがありません。

p.37, Raspberry Pi Connect の設定項目

設定ウィザードにて、Raspberry Pi Connect の設定を行う画面が現れると述べました。この設定は、2024-10-22 以降の Raspberry Pi OS では現れません。本書の演習には影響がありませんので、そのまま設定を続けてください。

p.39, Trixie での設定アプリケーションについて

設定ウィザード終了後の設定について、本書では設定アプリケーションを紹介しました。2025年10月にリリースされた最新 OS Trixie では、この設定アプリケーションの起動法や見た目が変更されておりますので、ここで解説します。

まず、起動は下図のように「設定」→「Control Centre (コントロールセンター)」を選択することで行います。
起動したアプリケーションの左側の項目で「システム」を選択すると、本書図2-26に相当する画面が現れます。左側の項目をスクロールしないと「システム」が見つからないことがありますのでご注意ください。
同様に、左側の項目で「ディスプレイ」や「ローカライゼーション」を選択すると、本書で解説した設定を行うことができます。

3章

p.44, 秋月電子通商のパーツセットへのリンク

p.44 に記した、秋月電子通商のパーツセットは下記ページで販売されております。 2025年春頃より、このパーツセットの在庫が切れた状況が長らく続いていましたが、
2025年7月3日に在庫が復活していることを確認しました。ぜひ本書と合わせてお楽しみください!

なお、パーツセットが一時的に品切れの場合、あるいはパーツセット内のパーツを単品で欲しい場合、下記のリストをご活用ください。
おおむねパーツセットと同等のパーツを入手できますが、「抵抗100本、赤色LED10個、コンデンサ10個」のように、多めに購入せざるを得ないパーツがある点はご了承ください。

パーツ 個数
ブレッドボード BB-801:105294 1
ブレッドボード・ジャンパーワイヤ(オス-メス) 15cm(赤):108933 2パックで20本
ブレッドボード・ジャンパーワイヤ(オス-オス) :105159 1パックで60本以上
カーボン抵抗(炭素皮膜抵抗) 1/4W330Ω125331 1パックで100本入り
カーボン抵抗(炭素皮膜抵抗) 1/4W10kΩ125103 1パックで100本入り
3mm赤色LED 660nm 250mcd LT3U31P:102320 1パックで10個入り
RGBフルカラーLED 5mm OSTA5131A カソードコモン:102476 1
LED光拡散キャップ 5mm 白:100641 1
タクトスイッチ(黒色):103647 3
半固定ボリューム 10kΩ[103]:108012 3
CdSセル 5mmタイプ:100110 1
積層セラミックコンデンサー 0.01μ50V X7R 2.54mm:104063 1パックで10個入り
12bit 8ch ADコンバーター MCP3208-CI/P:100238 1
DCモーター FA-130RA-2270L:109169 1
マイクロサーボ9g SG-90:108761 1
DRV8835使用ステッピング&DCモータードライバーモジュール:109848 1
電池ボックス 単3×3本 リード線・スイッチ付:112240
1
ADT7410使用 高精度・高分解能 I2C・16Bit 温度センサーモジュール:106675 1
Raspberry Pi キャラクター液晶ディスプレイモジュール完成品:111753 1


回路の配線図について

サンプルファイルの中に、本書で作成する回路の配線図が gihyo-RaspberryPi-Circuit.pdf というPDFファイルとして含まれていますのでご活用ください。
このファイルを Windows や macOS で閲覧したい場合、以下のリンクからダウンロードすることもできます。 このファイルを Windows や macOS などの Adobe Reader で閲覧する際、「編集」→「環境設定」を選択し、下図のように「細い線を拡張」のチェックを外すと、より綺麗な配線図を見ることができます。


チェックを外す前後の配線図の状態を示したのが下図です。「細い線を拡張」のチェックを外した方が図がきれいに表示されているのがわかるでしょう。

なお、この設定はPDFの印刷の際には影響しないはずです。


5章

p.102, OpenCV のインストールコマンド

p.102に記した、OpenCV のインストールコマンドを以下に記します。
まず、次のコマンドでインストール可能なパッケージのリストを更新するのでした。
sudo apt update
それが終わったら、次のコマンドで OpenCV をインストールできます。
sudo apt -y install python3-opencv

p.104, mplayer のインスト―ルコマンド

p.104に記した、 mplayerのインストールコマンドを以下に記します。
次の二つのコマンドを、一つずつ順番に実行してください。
sudo apt update

sudo apt -y install mplayer

p.105, mplayer の実行コマンド

ターミナルを起動した後、プログラムの存在する gihyo フォルダ(ディレクトリ)に下記のコマンドで移動します。
cd gihyo
そのターミナルで以下のコマンドを実行すると、05-07-test.mp3 というサンプル音声を再生することができます。デフォルトでは HDMI 接続したディスプレイから音声が鳴るでしょう。
mplayer 05-07-test.mp3
これらのコマンドは、p.122の6.5章でも用いられます。

p.105, 音声の出力先を変更する方法

Pi 4 B までの機種で、音声を HDMI ケーブル経由ではなく、Raspberry Pi の基板上のイヤフォンジャックから聞きたい場合、デフォルトでは音が鳴らないことがあります。 イヤフォンジャックから鳴らすための方法は、OSのバージョンにより異なります。

<Bookworm 以降の場合>

もし、デフォルトでピンジャックから音が出ていない場合、以下の指示に従ってください。なお、raspi-config コマンドを実行中は、キーボードの Esc キーが「戻る」に対応しますので、困ったら Esc キーを何度か押してみると良いでしょう。
  1. ターミナルで「 sudo raspi-config 」コマンドを実行し、設定画面を開く
  2. キーボードの「↓」キーを五回押し、「6 Advanced Options」にフォーカスを合わせる
  3. キーボードの「Enter」キーを押し、「6 Advanced Options」に入る
  4. キーボードの「↓」キーを複数回押し、「A7 Audio Config」にフォーカスを合わせる(冒頭が A8 のこともある)
  5. キーボードの「Enter」キーを押し、「A7 Audio Config」に入る
  6. キーボードの「上」キーを一回押し、「1 PulseAudio」にフォーカスを合わせる
  7. キーボードの「Enter」キーを押し、「1 PulseAudio」を選択する
  8. キーボードの「Enter」キーを押し、「了解」を選択する
  9. キーボードの「TAB」キー二回を押し、「Finish」にフォーカスを合わせる
  10. キーボードの「Enter」キーを押すと、再起動され設定が有効になる

<Bullseye の場合>

2020-12-02版およびそれ以降の Raspberry Pi OS をご利用で、なおかつイヤフォンジャックから音が鳴らない場合、raspi-configというコマンドで音声の出力先をイヤフォンジャックに切り替えます。 以下の手順に従ってください。
  1. ターミナルで「 sudo raspi-config 」コマンドを実行し、設定画面を開く
  2. キーボードの「Enter」キーを押し、「1 System Options」に入る
  3. キーボードの「↓」キーを一回押し、「S2 Audio」にフォーカスを合わせる
  4. キーボードの「Enter」キーを押し、「S2 Audio」の設定画面に入る
  5. キーボードの「↓」キーを一回押し、「1 Headphones」にフォーカスを合わせる
  6. キーボードの「Enter」キーを押し、「1 Headphones」を選択する
  7. キーボードの「TAB」キー二回を押し、「Finish」にフォーカスを合わせる
  8. キーボードの「Enter」キーを押し、raspi-config の設定画面を終了する

以上で、イヤフォンジャックから音声が出るようになります。

6章

p.116, 図6-7、Raspberry Pi Connect の設定項目

p.116 の図6-7では、Raspberry Pi Connect の設定項目が上から2番目に表示されています。この設定項目は、2024-10-22 以降の Raspberry Pi OS では現れません。本書の演習には影響がありませんので、気にせず演習を続けてください。

p.116, 図6-7、Trixie における設定アプリケーションについて

SPI を有効にするため、本書では設定アプリケーションを起動しました。2025年10月にリリースされた最新 OS Trixie では、この設定アプリケーションの起動法や見た目が変更されておりますので、ここで解説します。

まず、起動は下図のように「設定」→「Control Centre (コントロールセンター)」を選択することで行います。
起動したアプリケーションの左側の項目で「インターフェイス」を選択すると、本書図6-7に相当する画面が現れます。左側の項目をスクロールしないと「インターフェイス」が見つからないことがありますのでご注意ください。下図では、6 章で用いる SPI と 7 章で用いる I2C の項目が有効にされています。


7章

p.126, 図7-3、Raspberry Pi Connect の設定項目

p.126 の図7-3では、Raspberry Pi Connect の設定項目が上から2番目に表示されています。この設定項目は、2024-10-22 以降の Raspberry Pi OS では現れません。本書の演習には影響がありませんので、気にせず演習を続けてください。

p.126, 図7-3、Trixie における設定アプリケーションについて

I2C を有効にするため、本書では設定アプリケーションを起動しました。2025年10月にリリースされた最新 OS Trixie では、この設定アプリケーションの起動法や見た目が変更されておりますので、ここで解説します。

まず、起動は下図のように「設定」→「Control Centre (コントロールセンター)」を選択することで行います。
起動したアプリケーションの左側の項目で「インターフェイス」を選択すると、本書図6-7に相当する画面が現れます。左側の項目をスクロールしないと「インターフェイス」が見つからないことがありますのでご注意ください。下図では、6 章で用いる SPI と 7 章で用いる I2C の項目が有効にされています。


p.129, 接続されている I2C デバイスのアドレスを表示するコマンド

接続されている I2C デバイスのアドレスを表示するコマンドは以下の通りです。
i2cdetect -y 1

p.137, LCD へ文字を表示するプログラムの実行コマンド

ターミナルを起動したあと、プログラムの存在する gihyo フォルダ(ディレクトリ)に下記のコマンドで移動します。
cd gihyo
そのターミナルから 07-02-LCD.py を実行するコマンドは以下の通りです。付録C.3「タブによる補完」を学ぶと、ターミナルでのプログラムの実行が格段に楽になりますので興味のある方は参考にしてください。
python3 07-02-LCD.py
このコマンドに、「'test'」という引数をつけて実行する場合のコマンドは下記の通りです。p.137に記した通り、「↑」キーにより過去に実行したコマンドを再表示すると楽にこのコマンドを記述できます。
python3 07-02-LCD.py 'test'

p.142, /etc/rc.local を管理者権限の mousepad で開くためのコマンド

p.142に記した, /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 を開くコマンドを実行し、以下に進みましょう。

p.142, LCD へ文字を表示するプログラムの実行コマンドを、/etc/rc.local に記述するための表記

p.142 に記した、/etc/rc.local の「exit 0」の行の上に記すべきコマンドは以下の通りです。ただし、「kanamaru」の部分は皆さんのユーザー名で置き換える必要があります。末尾の「&」も忘れずに記述しましょう。
python3 /home/kanamaru/gihyo/07-03-LCD-temp.py &
さらに、/etc/rc.local の「exit 0」の行の上に記すべき、シャットダウンボタン用のコマンドは下記の通りです。やはり、「kanamaru」の部分は皆さんのユーザー名で置き換える必要があります。末尾の「&」も忘れずに記述しましょう。
python3 /home/kanamaru/gihyo/05-06-sw-poweroff.py &

8章

p.169, ハードウェアPWMを利用するための準備

本書p.169 に記した、管理者権限の mousepad で /boot/firmware/config.txt を編集用に開くコマンドは下記の通りです。
sudo mousepad /boot/firmware/config.txt 
なお、執筆時の Legacy OS である Bullseye ではファイルが /boot/config.txt であるため、コマンドは下記に変更されます。
sudo mousepad /boot/config.txt 
そして、そのファイルの末尾に記すべき一行は下記の通りです。
dtoverlay=pwm-2chan
記した後ファイルを上書き保存し、Raspberry Pi を再起動するのでした。

ハードウェアPWMのトラブル

2025年5月にリリースされたOSでは、Linuxのコアである kernel のバージョンが 6.12 となり、Pi 5 でハードウェアPWMを出力するための仕様が変わりました。
そのため、その対応を施したサンプルファイルでしか動作しなくなっております。2025年6月22日にサンプルファイルを更新しましたので、それ以降にダウンロードした方でしたら問題なく動作します。 古いサンプルファイルをご利用の方は、ホームフォルダの gihyo フォルダを一度削除し、最新のファイルをダウンロードし直してください。
変更を受けたのは下記の4ファイルです。
  • 08-04-servo.py
  • 08-06-2servos.py
  • 09-05-servo/app.py
  • 10-03-servo/app.py
なお、やや細かな話ですが、GPIO 18 と 19 にハードウェア PWM 信号を出力するために用いるインターフェースは下記のようになります。

kernel 6.6 までの Pi 5 /sys/class/pwm/pwmchip2/pwm{2,3}
kernel 6.12 以降の Pi 5 /sys/class/pwm/pwmchip0/pwm{2,3}
Pi Zero ~ Pi 4 /sys/class/pwm/pwmchip0/pwm{0,1}

9章

p.180, FastAPI のインストールコマンド

p.180に記した、FastAPI のインストールコマンドは以下の通りです。二つのコマンドを一つずつ実行しましょう。
sudo apt update

sudo apt -y install python3-fastapi

p.181, IP アドレスを調べるためのコマンド

p.181に記した、IP アドレスを調べるためのコマンドは以下の通りです。
ifconfig

p.184, FastAPI用に書かれたプログラムを実行するためのコマンド

p.184に記した、FastAPI用に書かれたプログラムを実行するためのコマンドは以下の通りです。
新規に開いたターミナルで、下記のコマンドを一つずつ順に実行するのでした。
cd gihyo/09-01-led

python3 app.py
同様に、9章のすべての実行コマンドを一気に列挙します。
どれも、新規に開いたターミナルで実行する必要があります。
cd gihyo/09-02-temp

python3 app.py
cd gihyo/09-03-rgbled

python3 app.py
cd gihyo/09-04-dcmotor

python3 app.py
cd gihyo/09-05-servo

python3 app.py
なお、「ターミナルに新規に開いて実行」ではなく、例えば「今 09-01-led フォルダにいるが、次に 09-02-temp フォルダの app.py を実行したい」などという場合、実行すべきコマンドは以下のようになるでしょう。
これは「..」が「一つ上のフォルダ」を意味することを知ればその意味を理解できるでしょう。
cd ../09-02-temp

python3 app.py

ハードウェアPWMのトラブル

2025年5月にリリースされたOSでは、Linuxのコアである kernel のバージョンが 6.12 となり、Pi 5 でハードウェアPWMを出力するための仕様が変わりました。
そのため、その対応を施したサンプルファイルでしか動作しなくなっております。2025年6月22日にサンプルファイルを更新しましたので、それ以降にダウンロードした方でしたら問題なく動作します。 詳細は 8 章の注釈をご覧ください。

10章

p.209, 機体B用のABS樹脂板の購入について

p.209の表10-2、およびp.214において、機体Bの作成のために「スタジオミュウ タッピングプレート」も「TAMIYAユニバーサルプレート」も入手できない場合、「はざいや」で「ABS樹脂板」を購入して加工するのがよい、と述べました。
しかし、2025年9月に確認したところ、大きなサイズからの切り出しでしか購入できなくなっておりました(1000×2000で4000円程度)そのため、現在はこの代替案はお勧めしません。
ただし、2025年9月の時点では「TAMIYAユニバーサルプレート」の在庫が復活しておりますので工作は問題なくできると思います。

ここからは、穴の開いていないABS樹脂板を入手できた場合の加工方法を記します。
入手したABS樹脂板に対し、下記の図の位置にピンバイスで3.2mmの穴をあけます。
上図の通りに穴をあけた様子が下図になります。
そして、これらを用い、アームクローラー工作セット(2chリモコンタイプ)で機体Bを組み立てた様子が下図になります。

p.210, 図10-1の高解像度画像

p.210にある図10-1の高解像度画像は以下となります。クリックすると拡大されます。

p.213, 図10-3の高解像度画像

p.213にある図10-3の高解像度画像は以下となります。クリックすると拡大されます。

p.217, FastAPI用に書かれたプログラムを実行するためのコマンド

p.217に記した、FastAPI用に書かれたプログラムを実行するためのコマンドは以下の通りです。
新規に開いたターミナルで、下記のコマンドを一つずつ順に実行するのでした。
cd gihyo/10-01-tank

python3 app.py

p.219, webtankサービス用の設定ファイルを編集するためのコマンド

p.219に記した、webtankサービス用の設定ファイルを編集するためのコマンドは以下の通りです。
新規に開いたターミナルで、下記のコマンドを一つずつ順に実行するのでした。
cd gihyo/10-01-tank

mousepad webtank.service

p.220, webtankサービス用の設定ファイルをシステム領域に移動するためのコマンド

p.219に記した、webtankサービス用の設定ファイルを編集するためのコマンドは以下の通りです。
mousepad を起動したフォルダ(すなわち、gihyo/10-01-tankフォルダ)で以下のコマンドを実行します。
sudo mv webtank.service /etc/systemd/system/

p.220, webtankサービスの自動実行を有効にするコマンド

p.219に記した、webtankサービス用の設定ファイルを編集するためのコマンドは以下の通りです。
mousepad を起動したフォルダ(すなわち、gihyo/10-01-tankフォルダ)で以下のコマンドを実行します。
sudo systemctl enable webtank

p.221, LCDへのIPアドレスの表示に関わるコマンド

p.221に記した、自動実行したいプログラムを記述するためのファイル /etc/rc.local を管理者権限で開くためのコマンドは以下の通りです。
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 を開くコマンドを実行し、以下に進みましょう。

p.222, IPアドレスをLCDに表示するために /etc/rc.local に記入するコマンド

p.222に記した、IPアドレスをLCDに表示するために /etc/rc.local の「exit 0」の行の上に記入するコマンドは下記の通りです。
python3 /home/kanamaru/gihyo/07-02-LCD.py $_IP
なお、上のコマンドの「kanamaru」の部分はみなさんのユーザー名に変更する必要があります。

p.223, シャットダウンプログラムの自動実行のために /etc/rc.local に記入するコマンド

p.223に記した、シャットダウンプログラムの自動実行のために /etc/rc.local の「exit 0」の行の上に記入するコマンドは下記の通りです。「&」も忘れずに記述しましょう。
python3 /home/kanamaru/gihyo/10-04-sw-poweroff.py &
なお、上のコマンドの「kanamaru」の部分はみなさんのユーザー名に変更する必要があります。

p.224, mjpg-streamer のインストールコマンド

p.224に記した、mjpg-streamerのインストールコマンドは下記の通りです
(1) sudo apt update
(2) sudo apt -y install libjpeg-dev cmake libcamera-dev
(3) git clone https://github.com/neuralpi/mjpg-streamer
(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.225:mjpg-streamerの実行用コマンド

p.225に記した、mjpg-streamerを実行するために /etc/rc.local の「exit 0」の行の上に記入するコマンドは下記の通りです。
python3 /home/kanamaru/gihyo/10-05-stream.py &
なお、上のコマンドの「kanamaru」の部分はみなさんのユーザー名に変更する必要があります。

p.226:webtank.serviceを編集するためのコマンド

p.226に記した、webtank.serviceを編集するためのコマンドは下記の通りです。
sudo mousepad /etc/systemd/system/webtank.service

p.228, 図10-10の高解像度画像

p.228にある図10-10の高解像度画像は以下となります。クリックすると拡大されます。

p.229:webtankサービスの自動実行を停止するコマンド

p.229に記した、webtankサービスの自動起動を停止するためのコマンドは下記の通りです
sudo systemctl disable webtank

p.230, 図10-11の高解像度画像

p.230にある図10-11の高解像度画像は以下となります。クリックすると拡大されます。

p.231, 図10-12の高解像度画像

p.231にある図10-12の高解像度画像は以下となります。クリックすると拡大されます。

10章全般:キャタピラ式模型のメンテナンス

キャタピラ式模型を長く使っていると、だんだん動作が安定しなくなってくることがあります。例えば「右のキャタピラは正常動作するが、左はなかなか動かない」などです。

もちろん、「ジャンパーワイヤーが抜けていないか」など、回路の配線をまずはチェックすべきです。 しかし回路の配線に問題がなかった場合、どこに注意してメンテナンスすべきかを以下に記します。上から順にチェックしていきましょう。
  1. 電池の残量不足ではないかチェック:基本ではありますが、まずはここからチェックしましょう。
  2. モーターの軸が空回りしていないかチェック:モーターの軸にはピニオンギアが取り付けられています。ピニオンギアが劣化してゆるくなり、モーターの軸が空回りすることがしばしば起こります。これはモーターをギアボックスから取り外してみないとわかりません。ピニオンギアがゆるくなっていた場合、「AO-7005 8Tピニオンセット白(10個)」や「AO-1014 8Tピニオン(紫)」などを購入して交換しましょう。ギア部で異音がする場合は大体このケースが当てはまります。個人的に、この問題には何度も遭遇しています。
  3. ジャンパワイヤのチェック:ジャンパワイヤの抜き差しをする際、持ち手ではなくケーブルを引っ張ることを繰り返すと中で断線することがあります。断線したかどうかは外から見ただけではわかりません。断線が疑われるジャンパワイヤは捨ててしまった方がよいでしょう。これも回路のパーツを長く使っていると遭遇することの多い問題ですね。
  4. ブレッドボードのチェック:ブレッドボードを長くつかっていると、なんらかの理由でブレッドボード内部の抵抗が大きくなりモーターがあまり回転しなくなることがあります。頻繁に起こることではないと思いますが、筆者は何度かそのような経験をしています。安価なもので良いのでテスターを購入すると内部の抵抗の大きさをチェックできます。内部の抵抗が大きく計測されたブレッドボードは消耗品と考えて捨ててしまった方がよいでしょう。
  5. ギアボックスで空回りしているシャフトがないかチェック:ギアボックスの作成の際、イモネジを六角レンチで締めてシャフトを固定している部分があります。そこが緩んで空回りしていないかチェックしましょう。
  6. モーターのチェック:上のどの問題にも該当しなかった場合、モーターに問題がある可能性があります。新品の「AO-1001 FA-130タイプノーマルモーター」を購入して交換してみるのも手です。
  7. モータードライバーのチェック:上のどの問題にも該当しなかった場合、モータードライバに問題がある可能性があります。新品のモータードライバーを購入して交換してみるのも手です。

付録

p.238, サンプルファイルのダウンロードコマンド

p.238に記した、本書のサンプルファイルのダウンロードコマンドは以下の通りです。Raspberry Piがネットワークに接続されている必要があります。
git clone https://github.com/neuralpi/gihyo

p.238, git コマンドのインストールコマンド

執筆時の Raspberry Pi OS では実行不要ですが、git のインストールのコマンドを念のために記すと以下のようになります。
sudo apt update

sudo apt -y install git

p.240, zip 圧縮されたサンプルファイルgihyo-master.zipを展開するコマンド

p.240に記した、 zip 圧縮されたサンプルファイルgihyo-master.zipを展開するコマンドは以下の通りです。二つのコマンドを一つずつ順に実行してください。
unzip gihyo-master.zip

mv gihyo-master gihyo

p.241, nano の設定ファイル .nanorc に記す内容

p.241に記した、nano の設定ファイル .nanorc に記す内容は以下の通りです。
set tabsize "4"
set tabstospaces

p.241, vi の設定ファイル .vimrc に記す内容

p.241に記した、vim の設定ファイル .vimrc に記す内容は以下の通りです。
set expandtab
set tabstop=4
set softtabstop=4
set shiftwidth=4

p.242, IPアドレスの代わりに利用可能なアドレス

p.241に記した、IPアドレスの代わりに利用可能なアドレスは以下の通りです。
raspberrypi.local
9章の演習で用いるアドレスの形式で書くと、例えば以下のようになるでしょう。
http://raspberrypi.local:8000/9-1

p.242, Windows 用 iTunes をダウンロードするためのリンク

p.242 で紹介した、 Windows 用 iTunes をダウンロードするためのリンクは以下です。 このリンク先のページを少しスクロールしたところにある 「ほかのバージョンをお探しですか? macOS> Windows> 」の「Windows>」をクリックすることで、iTunes64Setup.exe をダウンロードできるのでした。

p.243, Raspberry Pi の IP アドレスを検索するための Android アプリへのリンク

p.243で紹介した、Raspberry Pi の IP アドレスを検索するための Android アプリへのリンクは以下です。 このアプリで raspberrypi という名称を検索すると、IP アドレスを知ることができます。

p.243, 日本語入力ソフトウェアのインストールの準備

p.243に記した、raspi-configの実行コマンドは以下の通りです。
sudo raspi-config

p.244, 日本語入力ソフトウェアのインストールコマンド

p.244に記した、日本語入力ソフトウェアのインストールコマンドは以下の通りです。
sudo apt update

sudo apt -y install fcitx-mozc
なお、画面描画の仕組みを Wayland から X11 に変更し、fcitx-mozc を正常に使えるようにするには、下記の手順に従います。
  1. ターミナルで「 sudo raspi-config 」コマンドを実行し、設定画面を開く
  2. キーボードの「↓」キーを五回押し、「6 Advanced Options」にフォーカスを合わせる
  3. キーボードの「Enter」キーを押し、「6 Advanced Options」に入る
  4. キーボードの「↓」キーを六回押し、「A7 Wayland」にフォーカスを合わせる
  5. キーボードの「Enter」キーを押し、「A7 Wayland」に入る
  6. 「w1 X11」にフォーカスが合っていることを確認する。会っていなければ「↑」キーで合わせる
  7. キーボードの「Enter」キーを押し、「w1 X11」を選択する
  8. キーボードの「Enter」キーを押し、「了解」を選択する
  9. キーボードの「TAB」キー二回を押し、「Finish」にフォーカスを合わせる
  10. キーボードの「Enter」キーを押すと、再起動され設定が有効になる
なお、fcitx-mozc を使っていると、キーボードの設定が英語キーボードになってしまうことがあるように思います。その解決方法を記します。

まず、右上のキーボードアイコン(または「あ」アイコン)を右クリックし、下図のように「入力メソッド」の項目を確認します。図のように「キーボード - 日本語」、「Mozc」の二つの項目がこの順番で並んでいれば、問題は解決します。この状態を目指します。
そのためには、右上のキーボードアイコン(または「あ」アイコン)を右クリックし、「設定」を選択します。「入力メソッド」のタブで、項目が「キーボード - 日本語」、「Mozc」の二つになるように調整します。
図に記されているように、「+」ボタンで「キーボード - 日本語」の追加、「-」ボタンで「キーボード - 英語 (US)」の削除、「↑」「↓」ボタンで「キーボード - 日本語」を一番上に移動、などをする必要がある場合があります。
以上です。

p.244, 日本語フォントのインストールコマンド

p.244に記した、日本語フォントのインストールコマンドは以下の通りです。
sudo apt -y install fonts-vlgothic

p.250, 参考文献のリンク

p.250の参考文献で記したリンクは以下の通りです。

Raspberry Pi Zero W 系の機種で本書の演習を行う方法

はじめに

本書の演習で Raspberry Pi Zeroシリーズを用いる方法はサポートページで解説すると述べました。本ページにでその解説を行います。

Raspberry Pi Zero W 系 (以下 Pi Zero W 系) の機種では、GPIO ポートにピンヘッダが取り付けられた Raspberry Pi Zero WH (以下 Pi Zero WH) が最も簡単です。ピンヘッダが取り付けられていないと、本書で用いるオス-メスタイプのジャンパーワイヤを用いることができません。
なお、Pi Zero W の後継である Pi Zero 2 W も登場しており、ピンヘッダが取り付けられた Pi Zero 2 WH も販売中です。

また、Pi Zero W 系の機種は、搭載メモリ量が 512MB と少ないためデスクトップでブラウザを使うのにも支障が出るレベルです。ですので、Pi Zero W 系の機種は「デスクトップなど不要」と思えるような上級者向けのものだと考えるのが良いと思います。

Pi Zero W 系を含めた Raspberry Pi の種類と、それを購入できるサイトへのリンクを 「Raspberry Piではじめる機械学習:Raspberry PiへのOSのインストール方法」の冒頭部にまとめてありますので、参考にしてみてください。

周辺機器の接続方法

Pi Zero 2 Wと周辺機器との接続は下図のようになります。
図からわかるように、以下のものが必要となります。
Pi Zero 系の機種をセット販売で購入した場合、「HDMI(メス)-ミニHDMI(オス)変換アダプタ」や「USB OTGケーブル」はセットに含まれる場合もあるようですので、ご確認ください。

なお、microSD カードはなるべく高速で高性能なものを使うことをお勧めします。Zero 系の機種はメモリが512MBしか搭載されておらず、microSDカードをメモリの代替として用いる「スワップ」という技術が多用されるためです。

なお、ピンヘッダが取り付けられていない Pi Zero 系の機種で電子工作の演習を行いたい場合、以下の方法がありますが自己責任でお願いします。
  1. まず、下記のようなテストワイヤをGPIO部の穴に差し込んで使うという方法がまずあります。ただし、これはあくまでテスト用であり、本書のように何度もGPIOを利用する場合、何度も抜き差しすることで接触が悪くなることが考えられるためお勧めできません。
  2. それ以外には、下記のように 40 ピンのピンヘッダをハンマーで打ち込む GPIO Hammer Header という製品があります。日本のサイトでは売り切れの場合もありますが、下記のスイッチサイエンスでは、定期的に入荷するようです。 なお、amazon.co.jp でも買えるようですが、レビューを見る限り装着に必要な治具 (Jig) が付属しない可能性があり、お勧めしにくいです。 海外通販を利用できる方なら、公式から Jig つきのもの Pimoroni: GPIO Hammer Header (Solderless) – Male + Female + Installation Jig を購入するのも良いでしょう。 治具 (Jig) の利用法はこちらで見られます。

  3. 最後に、ピンヘッダ 2×20 (40P)を半田付けする方法です。半田付けが得意な方以外にはお勧めできません。 個人的な感想ですが、一般的なセンサモジュールなどよりも半田ごてで熱すべき時間が長く、かなり難易度が高いと思いました。

本書の演習の実行について

Pi Zero W 系の機種を用いて本書の演習を行う場合、注意が必要なのは下記となるでしょう。
  • 5.6 カメラのシャッターの演習:カメラモジュールを接続するための専用ケーブルが必要
  • 5.7 MP3ファイルの再生:オーディオジャックがないので、音声はHDMI経由のみでの出力となるでしょう
  • 6.5 音声のボリューム:同様に音声はHDMI経由のみとなるでしょう
  • 10.4 キャタピラ式模型へのカメラの搭載:カメラモジュールを接続するための専用ケーブルが必要
なお、カメラモジュールの専用ケーブルとは、例えば下記のものです。Pi Zero 用のものと Pi 5 用のものの両方が使えます。Pi 5 用のものの方がケーブルの長さを選べる点は便利ですが、ケーブル自体がやや硬いので、好みは分かれるでしょう。 Pi Zero 系の機種をセット販売で購入した場合は付属する場合があるようですので確認してください。 Pi Zero 用のケーブルで Pi Zero 2 W にカメラモジュールを取りつけた様子が下図です。
ケーブルを取り付ける際、金属が露出した端子面を、どちらも緑色の基板の方を向くようにします。基板上のカバーを引き出し、ケーブルを差し込んだ後でカバーを押し込むことでケーブルが固定されます。

10.4 節のカメラ付きキャタピラ式模型を Pi Zero 2 W で実現したのが下図の画像です。Pi Zero を用いるメリットは、模型に搭載する回路が若干コンパクトになることと、低消費電力のため Pi Zero に電源を供給するモバイルバッテリーの性能が低くても良いことです。ここでは、5V/1A のモバイルバッテリー(10年以上前に購入した Panasonic QE-PL102)を用いましたが、問題なく動作しました。ただし、Pi Zero 2 W は最大 2A の電流が流れるそうなので、もう少し良いモバイルバッテリーを用いた方が良かったかもしれません。


デスクトップやブラウザの利用に関する注意

本書で学習する場合、「デスクトップでブラウザで補足ページを開きコマンドなどをコピーしてターミナルに貼り付ける」というスタイルで学習するのが最も容易です。

しかし、Pi Zero 系の機種の計算能力では、ブラウザがまともに動作しないことが多いと思います。ページ表示の待ち時間が長く、十分な時間待ったとしてもページが表示されるとは限らない、というのが主な症状です。

さらに、そもそもグラフィックをもったデスクトップを利用すること自体、OS が新しくなるとともに厳しくなっています。
これらの問題は、 Pi Zero W 系の機種のメモリが少ないことが原因と思われます。

以上から、Pi Zero 系の機種ではブラウザの利用をあきらめ、 「ディスプレイ・マウス・キーボードを接続せずにRaspberry Piを利用する~SSH編」の解説に従い、 Windows や macOS から Raspberry Pi へターミナルソフトウェアでログインして利用する、という方法を用いるのが現実的です。 この方法を用いると、電子回路の制御には Pi Zero W 系の機種を用い、補足ページの閲覧は Windows や macOS を用い、コマンドの貼り付けはターミナルソフトウェア経由で行う、ということが可能になります。
さらに、「Raspberry Pi をヘッドレスでセットアップする」に従うと、OS のインストールの段階から ディスプレイ、マウス・キーボードを接続せずに Raspberry Pi を利用することが可能になります。

とはいえ、これらの方法は Linux に慣れている人向けの方法です。ですから、Pi Zero W 系の機種は初心者向けとは言い難いところがあります。

Raspberry Pi Pico W / Pico 2 W を使って Wifi 経由で回路を制御してみよう

0. はじめに

本書の電子工作パーツを Raspberry Pi Pico シリーズで利用する」にて、本書の公式パーツセットを Raspberry Pi Pico シリーズ(以後 Pico)で利用する方法を紹介しました。

Wifi 機能のない Raspberry Pi Pico で実行できる演習は、 本書で Wifi 接続を用いない 1 章から 8 章の内容でした。

一方、2023年3月、Wifi 機能付きの Raspberry Pi Pico W (以下 Pico W)が日本で販売開始されました。 さらに、2025年3月にはその高機能版である Raspberry Pi Pico 2 W (以下 Pico 2 W)が日本で販売開始されました。 Pico W や Pico 2 W を用いると、 Wifi 接続を用いる 9 章および 10 章の演習をある程度再現できるようになります。 具体的には、本ページでは下記の内容を取り扱います。
  • 9章の内容: PC やスマートフォンのブラウザで、Pico W / Pico 2 W に接続された LED / I2C温度センサ / RGB フルカラー LED / DCモータ / サーボモータ を制御
  • 10章の内容: PCやスマートフォンのブラウザで、Pico W / Pico 2 W を搭載したキャタピラ式模型を制御
10章のキャタピラ式模型を Pico W で実現した様子を示したのが下図です。もともと Raspberry Pi が配置されていたスペースが空いていますが、これは 回路と Pico W を全てブレッドボード上に配置できるからです。回路動作用のバッテリーが、大電流を流せないものでも良い、というメリットもあります。
そして、このキャタピラ式模型の動作の様子が下記の YouTube 動画です。



なお、Wifi 経由での Pico W / Pico 2 W の制御には欠点もあり、回路への制御信号の送信が、ブラウザのページ再読み込みのタイミングでしか行えないため、ブラウザに対して行ったアクションへのレスポンスが悪い、という問題があります。

さて、本ページでは以上の内容の実現方法を解説していきます。

その内容に進む前に、「本書の電子工作パーツを Raspberry Pi Pico シリーズで利用する」を参考に、 下記の内容を進めておいてください。Pico W や Pico 2 W でも、Pico 用の演習をすべて実行することができます。Pico W や Pico 2 W を購入できるサイトもリンク先で紹介されています。
  • Pico W / Pico 2 W への MicroPython 環境のインストール (自分が用いているバージョンの Pico の UF2 ファイルを用いることに注意)
  • Windows などへの、開発環境 Thonny のインストール
  • Pico W / Pico 2 W で Python プログラムを実行する方法の習得

1. ブラウザのボタンによるLEDの点灯

まずは、PCやスマートフォンのブラウザから、Pico W / Pico 2 W に接続された LED のオン / オフを切り替える演習を行ってみましょう。9.3 章の内容です。必要な回路は下図の通りです。
見てわかる通り、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 を動作させられるようにすることを強く推奨します。

さて、この回路に対して実行するプログラムは以下の通りです。なお、本ページで紹介するプログラムはすべてこちらのページの解説を参考に作成しました。
import sys
import network
import socket
from time import sleep
from machine import Pin, I2C

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

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>1 - LEDの点灯制御</title>
<style>button {
    width: auto;
    height: auto;
    background: %s;
    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>下のボタンを押すごとに、LEDの点灯状態が変化します。</p>
<div align="center">
<form><button name="led" value="toggle" type="submit">LED</button></form>
</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)

ledState = 0
buttonColor = '#003366'

# 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)
        led_toggle = request.find('led=toggle')

        #print( 'led_toggle = ' + str(led_toggle))

        if led_toggle == 8:
            #print("led toggle")
            ledState = 1 - ledState
            led.value(ledState)
            if ledState == 0:
                buttonColor = '#003366'
            else:
                buttonColor = '#26a1ff'

        response = html % buttonColor
        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.4GHzの周波数帯の 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 と同じネットワーク内に存在する PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のようなページが現れるはずです。
ブラウザ上のボタンを押すことで Pico W / Pico 2 W に接続された LED の点灯/消灯が切り替わります。LED 点灯時の画面の様子は下記の通りで、ボタンの色が変わります。これは、本書の Raspberry Pi 版のプログラムと同じ挙動です。ただし、上で既に述べたように、回路のアクションはブラウザ上のページの再読み込みのタイミングでしか起こりませんので、反応が遅いという問題がありますのでご了承ください。これは、本ページの以下のプログラム全てに当てはまります。
なお、ここでの解説がよくわからないという場合、本書 9 章の記述を読み直してみることを推奨します。本ページは、本書読者の方を対象としているため、本書と重複する内容の記述は最低限にとどめています。

2. ブラウザへの温度センサの値の表示

PCやスマートフォンのブラウザに、Pico W / Pico 2 W に接続された I2C 温度センサの値を表示する演習を行ってみましょう。9.4 章の内容です。必要な回路は下図の通りです。
そして、この回路上で実行すべきプログラムは下記の通りです。
import sys
import network
import socket
from time import sleep
from machine import Pin, I2C

ssid = 'YOUR_WIFI_SSID'
password = 'YOUR_WIFI_PASSWORD'

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

##### for temperature sensor
def read_adt7410():
    word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 4), 'little')
    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

address_adt7410 = 0x48
register_adt7410 = 0x00
##### end of temperature sensor

##### 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)

# refresh per 5 sec.
html = """<!DOCTYPE html><html>
<head><meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no">
<meta http-equiv="refresh" content="5">
<title>2 - I2C温度センサ(ADT7410)による温度の取得</title>
</head>
<div align="center">
<p>温度: %s</p>
</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)
        inputValue = read_adt7410()
        inputStr = '{:.1f}'.format(inputValue)

        response = html % inputStr
        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
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、ブラウザでアクセスすると下記のようなページが開きます。
5秒おきにページが再読み込みされ、センサの値がページ上で更新されるようになっています。 これは、プログラム中の下記の部分で設定されています。あまり頻繁に通信が行われるのも良くないですので、この部分を小さな値に変更することはお勧めしません。
<meta http-equiv="refresh" content="5">


3. ブラウザのボタンによるRGBフルカラーLED の制御

PCやスマートフォンのブラウザで、Pico W / Pico 2 W に接続された RGBフルカラーLED の色を制御する演習を行ってみましょう。9.5 章の内容です。

なお、Rapsberry Pi を対象とした本書では、RGBフルカラーLEDの色の制御に、ブラウザのスライダを用いました。 しかし、Pico W / Pico 2 W ではスライダを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したスライダの値を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
そして、この回路上で実行すべきプログラムは下記の通りです。
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm3 = PWM(Pin(18))
pwm1.freq(100)
pwm2.freq(100)
pwm3.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)
pwm3.duty_u16(0)
# アノードコモンの場合、下記の3行を有効に   
#pwm1.duty_u16(65535)
#pwm2.duty_u16(65535)
#pwm3.duty_u16(65535)

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

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>3 - RGBフルカラーLEDの制御</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>ボタンをクリックすることで、RGBフルカラーLEDの色が変わります。</p>
<p>どの行のボタンが赤、青、緑の何色に対応するかは用いるRGBフルカラーLEDの種類によって異なります。</p>
<div align="center">
<form>色 1 <button name="col1_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col1_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col1_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col1_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col1_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
<form>色 2 <button name="col2_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col2_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col2_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col2_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col2_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<br /><br />
<form>色 3 <button name="col3_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="col3_1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="col3_2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="col3_3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="col3_4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<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)

onCol = '#26a1ff'
offCol = '#003366'
col1_strs = (onCol, offCol, offCol, offCol, offCol)
col2_strs = (onCol, offCol, offCol, offCol, offCol)
col3_strs = (onCol, offCol, offCol, offCol, offCol)
colall_strs = col1_strs + col2_strs + col3_strs
buttonValMax = 4
# 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)
        col1_0_on = request.find('col1_0=on')
        col1_1_on = request.find('col1_1=on')
        col1_2_on = request.find('col1_2=on')
        col1_3_on = request.find('col1_3=on')
        col1_4_on = request.find('col1_4=on')
        col2_0_on = request.find('col2_0=on')
        col2_1_on = request.find('col2_1=on')
        col2_2_on = request.find('col2_2=on')
        col2_3_on = request.find('col2_3=on')
        col2_4_on = request.find('col2_4=on')
        col3_0_on = request.find('col3_0=on')
        col3_1_on = request.find('col3_1=on')
        col3_2_on = request.find('col3_2=on')
        col3_3_on = request.find('col3_3=on')
        col3_4_on = request.find('col3_4=on')

        if col1_0_on == 8:
            col1_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col1_1_on == 8:
            col1_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col1_2_on == 8:
            col1_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col1_3_on == 8:
            col1_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col1_4_on == 8:
            col1_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        if col2_0_on == 8:
            col2_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col2_1_on == 8:
            col2_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col2_2_on == 8:
            col2_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col2_3_on == 8:
            col2_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col2_4_on == 8:
            col2_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        if col3_0_on == 8:
            col3_strs = (onCol, offCol, offCol, offCol, offCol)
            duty = 0
        if col3_1_on == 8:
            col3_strs = (offCol, onCol, offCol, offCol, offCol)
            duty = 1
        if col3_2_on == 8:
            col3_strs = (offCol, offCol, onCol, offCol, offCol)
            duty = 2
        if col3_3_on == 8:
            col3_strs = (offCol, offCol, offCol, onCol, offCol)
            duty = 3
        if col3_4_on == 8:
            col3_strs = (offCol, offCol, offCol, offCol, onCol)
            duty = 4

        # アノードコモンの場合、下記の行を有効に   
        #duty = buttonValMax - duty

        if col1_0_on == 8 or col1_1_on == 8 or col1_2_on == 8 or col1_3_on == 8 or col1_4_on == 8:
            pwm1.duty_u16(int(duty*65535/buttonValMax))
        if col2_0_on == 8 or col2_1_on == 8 or col2_2_on == 8 or col2_3_on == 8 or col2_4_on == 8:
            pwm2.duty_u16(int(duty*65535/buttonValMax))      
        if col3_0_on == 8 or col3_1_on == 8 or col3_2_on == 8 or col3_3_on == 8 or col3_4_on == 8:
            pwm3.duty_u16(int(duty*65535/buttonValMax))

        colall_strs = col1_strs + col2_strs + col3_strs

        response = html % colall_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
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行じ、ブラウザで LCD 上のアドレスにアクセスすると、下記のようなページが開きます。スライダの代わりに、ボタンによってRGBフルカラーLEDの3つの色の明るさを調整する仕組みです。


4. ブラウザのボタンによる DC モーターの速度制御

PCやスマートフォンのブラウザで、Pico W / Pico 2 W に接続された DC モーターの回転速度を制御する演習を行ってみましょう。9.6 章の内容です。

なお、Rapsberry Pi を対象とした本書では、DC モーターの速度制御に、ブラウザのタッチイベントを用いました。 しかし、Pico W / Pico 2 W ではタッチイベントを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したタッチイベントの情報を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm1.freq(100)
pwm2.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)

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

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>4 - ボタンによるDCモーターの速度制御</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">
<form><button name="vel_n2" value="on" type="submit" style="background: %s;"> -2 </button>
<button name="vel_n1" value="on" type="submit" style="background: %s;"> -1 </button>
<button name="vel_0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="vel_p1" value="on" type="submit" style="background: %s;"> +1 </button>
<button name="vel_p2" value="on" type="submit" style="background: %s;"> +2 </button>
</form>
<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)

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

duty_max = 0.7

# 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)
        vel_n2_on = request.find('vel_n2=on')
        vel_n1_on = request.find('vel_n1=on')
        vel_0_on = request.find('vel_0=on')
        vel_p1_on = request.find('vel_p1=on')
        vel_p2_on = request.find('vel_p2=on')

        if vel_n2_on == 8:
            vel_strs = (onCol, offCol, offCol, offCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
        if vel_n1_on == 8:
            vel_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max/2))
        if vel_0_on == 8:
            vel_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(0)
        if vel_p1_on == 8:
            vel_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max/2))
        if vel_p2_on == 8:
            vel_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))

        response = html % vel_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
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。タッチの代わりに、ボタンでモーターの回転の向きと速度を調整する仕組みです。


5. ブラウザのボタンによるサーボモーターの角度制御

PCやスマートフォンのブラウザで、Pico W / Pico 2 W に接続されたサーボモーターの角度を制御する演習を行ってみましょう。9.7 章(付録 PDF)の内容です。

なお、Rapsberry Pi を対象とした本書では、サーボモーターの角度制御に、ブラウザのスライダを用いました。 しかし、Pico W / Pico 2 W ではスライダを用いるのは難しそうに思えましたので(専門的に言えば、JavaScript で取得したスライダの値を回路に反映する方法が思いつかない)、簡易的にボタンによる制御を行うことにしました。

必要な回路は下図の通りです。
import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

def servo_duty_hwpwm_web(val):
    val_min = 0
    val_max = 4
    servo_min = 0.035   # 最小デューティ比3.5%
    servo_max = 0.1  # 最大デューティ比10%
    if val < val_min:
        val = val_min
    duty = (servo_min-servo_max)*(val-val_min)/(val_max-val_min) + servo_max
    # 一般的なサーボモーターはこちらを有効に
    #duty = (servo_max-servo_min)*(val-val_min)/(val_max-val_min) + servo_min
    return duty

pwm1 = PWM(Pin(16))
pwm1.freq(50)
pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(2)))

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

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>7 - サーボモーターの制御</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">
<form><button name="pos0" value="on" type="submit" style="background: %s;"> 0 </button>
<button name="pos1" value="on" type="submit" style="background: %s;"> 1 </button>
<button name="pos2" value="on" type="submit" style="background: %s;"> 2 </button>
<button name="pos3" value="on" type="submit" style="background: %s;"> 3 </button>
<button name="pos4" value="on" type="submit" style="background: %s;"> 4 </button>
</form>
<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)

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

# 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)
        pos0_on = request.find('pos0=on')
        pos1_on = request.find('pos1=on')
        pos2_on = request.find('pos2=on')
        pos3_on = request.find('pos3=on')
        pos4_on = request.find('pos4=on')

        if pos0_on == 8:
            pos_strs = (onCol, offCol, offCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(0)))
        if pos1_on == 8:
            pos_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(1)))
        if pos2_on == 8:
            pos_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(2)))
        if pos3_on == 8:
            pos_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(3)))
        if pos4_on == 8:
            pos_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(int(65535*servo_duty_hwpwm_web(4)))

        response = html % pos_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
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。スライダの代わりに、ボタンでサーボモーターの位置を調整する仕組みです。


6. ブラウザのボタンによるキャタピラ式模型の制御

PCやスマートフォンのブラウザで、Pico W / Pico 2 W に接続されたキャタピラ式模型を制御する演習を行ってみましょう。10 章の内容です。

ここまでの演習同様、「前進」、「後退」、「右旋回」、「左旋回」、「静止」という 5 つのボタンによる簡易的な制御ですのでご了承ください。

import sys
import network
import socket
from time import sleep
from machine import Pin, PWM, I2C

pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm3 = PWM(Pin(18))
pwm4 = PWM(Pin(19))
pwm1.freq(100)
pwm2.freq(100)
pwm3.freq(100)
pwm4.freq(100)
pwm1.duty_u16(0)
pwm2.duty_u16(0)
pwm3.duty_u16(0)
pwm4.duty_u16(0)

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

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>5 - クローラーの操作</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)

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

duty_max = 0.7

# 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)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
            pwm4.duty_u16(0)
            pwm3.duty_u16(int(65535*duty_max))
        if rot_l_on == 8:
            move_strs = (offCol, onCol, offCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))
            pwm4.duty_u16(0)
            pwm3.duty_u16(int(65535*duty_max))
        if stop_on == 8:
            move_strs = (offCol, offCol, onCol, offCol, offCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(0)
            pwm3.duty_u16(0)
            pwm4.duty_u16(0)
        if rot_r_on == 8:
            move_strs = (offCol, offCol, offCol, onCol, offCol)
            pwm2.duty_u16(0)
            pwm1.duty_u16(int(65535*duty_max))
            pwm3.duty_u16(0)
            pwm4.duty_u16(int(65535*duty_max))
        if backward_on == 8:
            move_strs = (offCol, offCol, offCol, offCol, onCol)
            pwm1.duty_u16(0)
            pwm2.duty_u16(int(65535*duty_max))
            pwm3.duty_u16(0)
            pwm4.duty_u16(int(65535*duty_max))

        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
なお、「1. ブラウザのボタンによるLEDの点灯」で述べたように、YOUR_WIFI_SSID と YOUR_WIFI_PASSWORD を自分の環境のものに変更することと、 LCD なしでプログラムを実行したい場合は「# for LCD ~ # End of LCD」の部分の差し替えが必要ですのでご注意ください。

さらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny から一旦停止する必要があることもご注意ください。

以上のプログラムを実行し、LCD上のアドレスにブラウザでアクセスすると、下記のようなページが開きます。タッチの代わりに、ボタンでキャタピラ式模型の移動方法を変更する仕組みです。

おわりに

いかがでしたか?
本書の内容の多くが、工夫次第では Raspberry Pi Pico シリーズでも体験できることがお分かりいただけると思います。
ただし、「工夫次第では」というところがポイントで、Pico シリーズではブラウザのタッチイベントやスライダを利用できない、さらにカメラからのリアルタイムな映像を利用できないなど、物足りないところがあるのも事実です。

そのように「通常の Raspberry Pi」と「Raspberry Pi Picoシリーズ」のそれぞれの得意分野を考えて応用例を考えるというのも参考になると思います。

さらに、本書の続編である「実例で学ぶRaspberry Pi電子工作」の内容の Pico 版も下記のページで公開し始めました。 本書の場合と同様、カメラのリアルタイムの映像や画像処理は利用できないのですが、それでも Pico シリーズでできることは意外と多いことがわかるページになっていると思います。
ぜひお楽しみください。