書籍において、『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 (コントロールセンター)」を選択することで行います。
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
本書の演習で 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 系の機種は「デスクトップなど不要」と思えるような上級者向けのものだと考えるのが良いと思います。
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 系の機種は初心者向けとは言い難いところがあります。
一方、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 を動作させられるようにすることを強く推奨します。
さらに、プログラムの書き換え時は、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 版のプログラムと同じ挙動です。ただし、上で既に述べたように、回路のアクションはブラウザ上のページの再読み込みのタイミングでしか起こりませんので、反応が遅いという問題がありますのでご了承ください。これは、本ページの以下のプログラム全てに当てはまります。
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 (停止)」ボタンから一旦停止する必要があることもご注意ください。
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 (停止)」ボタンから一旦停止する必要があることもご注意ください。
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 (停止)」ボタンから一旦停止する必要があることもご注意ください。
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 (停止)」ボタンから一旦停止する必要があることもご注意ください。
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 から一旦停止する必要があることもご注意ください。