2025年8月29日金曜日

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 シリーズでできることは意外と多いことがわかるページになっていると思います。
ぜひお楽しみください。

本書の電子工作パーツを Raspberry Pi Pico シリーズで利用する

はじめに

2021年1月にRaspberry Pi Picoが発表されました。本書1章でも紹介しましたが、Raspberry Pi Pico シリーズは従来の Raspberry Pi シリーズのように「コンピューター」として使うものではなく、「マイクロコントローラー」に分類される商品です。マイクロコントローラーの代表例として、本書1章では他に Arduino UNO を紹介したのでした。

コンピューターRaspberry Pi Zero / 1 / 2 / 3 / 4 / 5 など
マイクロコントローラーArduino UNO、Raspberry Pi Pico など

これは、Raspberry Pi Pico シリーズは Linux 系 OS をインストールして利用するものではないということを意味します。また従来の Raspberry Pi ではできたことで、Raspberry Pi Pico シリーズにはできないことがたくさんあります。本書の範囲で言えば、カメラを利用する演習などです。

しかし、電子工作は Linux 系 OS を用いる手法よりもマイクロコントローラーを用いて行う手法の方が歴史がありますし、Raspberry Pi Pico シリーズの方が優れている面もまたたくさんあります。例えば、出力できるハードウェアPWMが16個であることや、電源投入後すぐに回路が動き出すことなどです。

本書を購入された方は、秋月電子通商さんによるパーツセットなどの電子工作パーツを合わせて購入された方も多いでしょう。その電子工作パーツを Raspberry Pi Pico シリーズで利用してみると、電子工作の世界をより深く知ることができます。その方法を本ページで紹介します。

従来の Raspberry Pi と Raspberry Pi Pico シリーズの利点を知ることで、それぞれを適材適所で使い分けることもできるでしょう。

下図は、本書4章で行ったLチカを Raspberry Pi Pico で実行している様子です。
また、Raspberry Pi 財団は、公式のキーボードやマウスを販売するなど、ユーザーが愛着を持てるようなブランド作りにこだわっているように筆者には思えます。従来のシリーズとは異なる Raspberry Pi Pico シリーズにも接することで、皆さんの Raspberry Pi シリーズへの愛着はさらに増すかもしれません。

さて、現在 Raspberry Pi Pico の様々なバージョンが発売されており、下記のようにショップで購入することができます。 ピンヘッダを実装したバージョンには名称に「H」が、Wifi機能を持つバージョンには名称に「W」が、そして性能向上したバージョンには名称に「2」がつけられています。

名称 ピンヘッダ Wifi ショップ
Raspberry Pi Pico × × 秋月電子通商 / スイッチサイエンス / KSY / 千石電商
Raspberry Pi Pico H 〇 × 秋月電子通商 / スイッチサイエンス / KSY (独自実装) / KSY / 千石電商
Raspberry Pi Pico W × 〇 秋月電子通商 / スイッチサイエンス / KSY / 千石電商
Raspberry Pi Pico WH 〇 〇 秋月電子通商 / スイッチサイエンス / KSY / 千石電商
Raspberry Pi Pico 2 × × 秋月電子通商 / スイッチサイエンス / KSY / 千石電商
Raspberry Pi Pico 2 H 〇 × 秋月電子通商 / スイッチサイエンス / KSY / 千石電商 (独自実装)
Raspberry Pi Pico 2 W × 〇 秋月電子通商 / スイッチサイエンス / KSY / 千石電商
Raspberry Pi Pico 2 WH 〇 〇 秋月電子通商 / スイッチサイエンス / KSY / 千石電商

種類が多くて選ぶのが難しいですね。

私がまずおすすめしたいのは、ピンヘッダが実装されたもの、すなわち名称に「H」がついたものです。ピンヘッダがどういうものかは、こちらのリンク先を見るとわかるでしょうか。 「H」がついているものを選ばないと、ピンヘッダを取り付けるために40箇所のはんだづけが必要になるのです。

次に、Wifi機能の有無、すなわち名称に「W」がついたものを選ぶべきかどうかについて解説します。通常の Raspberry Pi の場合、Wifi機能はほぼ必須と言えます。しかし、Pico の場合はWifi機能は必ずしも必要ではありません。自分が作る電子工作の作品にWifi機能が必要ならば「W」つきのものを、そうでない場合は「W」なしのものを選ぶようにすれば良いでしょう。本ページで紹介する例は全て「W」なしのPicoで動作します。

最後に、高性能なバージョン、すなわち名称に「2」がついたものを選ぶべきかどうかについて解説します。通常の Raspberry Pi の場合ならば高性能なバージョンの方がデスクトップなどの動作が快適です。しかし、Picoのようなマイクロコントローラの場合、「自分が作る電子工作の作品に十分な性能があればよい」というのが自然な考え方です。最初から「Pico 2」の性能を必要とする方はなかなかいないでしょうから、「2」ではないバージョンで良いのでは、と個人的には考えます。

以上から、私がお勧めするのは「Pico H」または「Pico WH」となります。「Pico WH」を選ぶと、本ページの次のステップである「Raspberry Pi Pico W / Pico 2 W を使って Wifi 経由で回路を制御してみよう」の演習を行うこともできます。本ページで Raspberry Pi Pico シリーズの基本的な取り扱いに慣れてからこのページに進むとよいでしょう。

もちろん、将来のステップアップを見越して「Pico 2 H」または「Pico 2 WH」を購入するのもアリですよ。

以下、本ページでは本書 1 章から 8 章の演習のうち、Raspberry Pi Pico シリーズでも実行可能なものを紹介します。

初回のみ実行すればよい準備

さてここからは、Raspberry Pi Pico シリーズで電子工作するために、最初に一回だけ実行すればよい準備を行っていきます。

ピンヘッダが取り付けられていないバージョンの Raspberry Pi Pico を購入された方は、それをはんだ付けする必要があります。
はんだ付けをしたくない方には、あらかじめページ上部のリンクでピンヘッダ実装済のもの(名称に「H」が含まれるもの)を購入するのがお勧めです。

さて、Raspberry Pi Pico シリーズに自分でピンヘッダをはんだ付けする場合、ピンヘッダとよばれるパーツを入手する必要があります。例えば秋月電子通商さんの下記のものがあります。 これは40ピンのピンヘッダですが、これをニッパで半分にカットして20ピンのものを2つ用意します。なお、ピンヘッダを2つにカットする際、ピンヘッダのプラスチック部が破損することがありますので、 ピンヘッダは複数購入するのが安全です。

ピンヘッダをはんだ付けする際、ピンヘッダと Raspberry Pi Pico のみでは固定するのに苦労します。下図のようにブレッドボードに差した状態ではんだ付けするのが簡単です。 はんだ付けのコツはp.162の図7-2をご覧ください。ピンとランドの両方を熱することが重要なのでした。

なお、手早く行わないとブレッドボードが溶けてきますので注意しましょう。ブレッドボードは安価なので、もし溶けてしまったとしても新しいものを購入することは比較的容易です。

このはんだ付けが、Raspberry Pi Pico を用いる上で最も難易度が高い作業だと個人的には思います。
ピンヘッダが装着されたら、Raspberry Pi Pico を PC に接続します。PCは Windows や macOS を搭載したものでもよいですし、従来の Raspberry Pi Zero / 1 / 2 / 3 / 4 / 5 でも構いません。以下では、Raspberry Pi Pico を Windows PC と接続した場合を例に解説を進めます。

PCとの接続には microUSB ケーブルを用います。microUSB ケーブルは、充電専用のものではなくPCと通信可能なものを用意してください。充電専用のケーブルでは以下の作業を実行できません。

そして、Raspberry Pi Pico の基板上の白い BOOTSEL ボタンを押しながら microUSB ケーブルをPCのUSBポートに接続します。BOOTSEL ボタンを押しながらPCに接続すると、Raspberry Pi Pico は PC からは USB メモリのようなストレージとして見えます。

私の Windows 10 では、下図のようなストレージとして表示されました。
このストレージの内部を見ると、下図のように INDEX.HTM と INFO_UF2.TXT というファイルが存在します。図中に先回りで記述されているように、ここに UF2 と呼ばれるファイルをダウンロード(あるいはコピー)します。このファイルは、Raspberry Pi Pico を MicroPython という言語で用いるために必要なものです。MicroPythonとは、マイクロコントローラーで動作させるための Python です。
UF2ファイルはRaspberry Pi の公式ページのこちらよりダウンロードできます。ページ中の「Download the correct MicroPython UF2 file for your board」という箇所にある UF2 ファイルをダウンロードします。 Pico 用 / Pico W 用 / Pico 2 用 / Pico 2 W 用、の4種類のファイルがありますので、ご自分のお使いの Pico にあったファイルを選びましょう。 Pico H など、名称に「H」がついているバージョンを用いている方は、その「H」を省いた名称のファイルを選んでください。例えば、「Pico WH」を用いている方は、「Pico W 用」のファイルを選ぶ、ということです。

執筆時には、Pico 用の場合「rp2-pico-20221011-unstable-v1.19.1-543-gab317a0d6.uf2」という名称のファイルがダウンロードされました。 このファイルを、先ほどの Raspberry Pi Pico を表すストレージに直接ダウンロードするか、あるいはダウンロードされたファイルをストレージにコピーしましょう。 ストレージへのダウンロードまたはコピーが完了すると、Raspberry Pi Pico が自動的に再起動されます。

それにより、基板上の白い BOOTSEL ボタンを押さずに Raspberry Pi Pico に電源を入れたときと同じ状態になり、Raspberry Pi Pico で MicroPython が利用できるようになります。 Raspberry Pi Pico の次回以降の起動の際は、何も押さずに Raspberry Pi Pico と PC を microUSB ケーブルで接続するだけで OK です。

次に、Raspberry Pi Pico 上で MicroPython プログラムを実行するために、開発環境である Thonny をインストールします。 PC として Windows や macOS をお使いの場合、多くの方は Thonny をインストールしていないでしょうから、こちらから各 OS 用の Thonny をダウンロードし、インストールしてください。

Windows の場合、「thonny-4.1.7.exe」のような名前のファイルを保存して実行するのが良いでしょう。 最初に「自分のみ用にインストールする (Install for me only)」か「PC の全利用者用にインストールする (Install for all users)」かを聞かれますが、「Install for me only」が推奨されていますので(recommended)、どちらにすべきか分からない場合はそちらで良いと思います。インストールは英語で行われますが、基本的には Next ボタンなどを押していくだけで構いません。途中で、デスクトップにアイコンを作るかを聞くチェックボックスが現れますが(Create desktop icon)、お好みでチェックして構いません。

インストール後は、スタートメニューから Thonny を探して起動します。初回の起動時に言語(Language)を選択できますが、「日本語」を選ぶことを推奨します。もう一つの選択肢である初期設定(Initial settings) は「Standard」のままで良いです。

PC として Raspberry Pi を用いる場合はインストール済の Thonny を用いて構いません。ただし、Raspberry Pi OS が古い場合、下記コマンドで Thonny を最新にした方が良いでしょう。
sudo apt update
sudo apt install thonny
さて、インストールした Thonny を起動すると下図のようになります。PC として Raspberry Pi を利用している方は、右上の「Switch to regular mode」というリンクをクリックしてから Thonny を再起動しないと、メニューが現れませんのでご注意ください。
さて、Thonny のウインドウの右下に「ローカルPython 3 (Local Python 3)」などと書かれていることに着目してください。これは、皆さんがお使いの PC で Python プログラムが実行されることを示しています。
なお、「ローカルPython 3」より右側に書かれている内容は環境によって異なりますので気にする必要はありません。
それに対し、ここで行いたいのは、「Raspberry Pi Pico 上で Python プログラムを実行すること」です。そのために、「ローカルPython 3」の部分をクリックします。
下図のように、「ローカルPython 3」の他に、「MicroPython (Raspberry Pi Pico)」という選択肢がありますので、そちらをクリックします。
なお、この選択肢が現れるには、MicroPython を利用する設定をした(上で UF2 ファイルをコピーした) Raspberry Pi Pico がお使いの PC に USB 接続されている必要があります。
するとウインドウの右下の表示が以下のようになります。「MicroPython (Raspberry Pi Pico)」より右側に書かれている内容は環境によって異なりますので気にする必要はありません。 この状態になったら、Raspberry Pi Pico で MicroPython プログラムを実行するための開発環境として Thonny を利用することができます。
今回初めて Thonny をインストールした方の場合、 このまま Thonny を Raspberry Pi Pico 専用の開発環境にしてしまってもよいでしょう。そうではない場合、すなわち、これまで Thonny を通常の Python の開発環境としても利用していた場合、 Raspberry Pi Pico 用プログラムの開発が終わったら「ローカルPython 3」を選択しなおし、Thonny をデフォルトの状態に戻す必要がありますのでご注意ください。

4章のLチカを実行してみよう

さて、以上で準備は整いました。ここからは、本書4章以降で用いた電子工作パーツを Raspberry Pi Pico シリーズで利用する方法を解説していきます。

Raspberry Pi Pico のピン配置は以下のようになっています。このうちそれぞれの電子工作パーツに必要なピンを用いて回路を作成していきます。
Raspberry Pi Pico Datasheet の p.4 より引用


まず、4章で用いたLEDでLチカを行うための回路を以下に示します。
そして、実行すべきプログラムは以下のものです。
from machine import Pin
from time import sleep

led = Pin(16, Pin.OUT)

while True:
    led.toggle()
    sleep(0.5)
このプログラムをコピーして下図のように Thonny 上に貼り付けます。そして、貼り付けが終わったら緑色の実行ボタンをクリックしましょう。すぐにプログラムが実行され、 ブレッドボード上の LED が点滅すると思います。これをLチカと呼ぶのでした。
これで「Raspberry Pi Pico で回路用のプログラムを実行する」という目的は達成されているのですが、もう一つ重要な手法をここで学びましょう。
それは、「Raspberry Pi Pico に電源が入った時に(USB 接続されたときに)、回路用プログラムが自動的に実行されるようにする」ことです。
電源を入れるとすぐにプログラムが動き出す、というのがマイコンの基本的な動作だからです。

そのための方法は簡単で、「プログラムを特別な名前 main.py をつけて Raspberry Pi Pico 上に保存する」だけです。やってみましょう。
メニューから「ファイル」→「名前を付けて保存」を選択します。すると、以下のウインドウが現れますので、「Raspberry Pi Pico」をクリックしましょう。
すると、下図のように Raspberry Pi Pico 上に保存する際のファイル名を聞かれます。以下のすべてのプログラムで、プログラム名を main.py としてOKをクリックしてください。 main.py は特別なファイル名となっており、Raspberry Pi Pico に電源が投入されたときに自動的に実行されるプログラムとなるのです。
なお、このとき、「Device is busy... (以下省略)」というエラーメッセージが表示された場合、Raspberry Pi Pico 上でプログラム実行中の可能性があります。その場合、Thonny の「STOP(停止)」ボタンをクリックし、実行中のプログラムを停止してから保存するようにしてください。これは、本ページの以下のプログラムでも共通のことですので、覚えておきましょう。

さて、OKボタンをクリックすると、main.py が保存されます。そうしてからプログラムを実行することで、Pico 上の main.py が実行される、というわけです。

main.py を保存した後の Lチカを確認できたら、Raspberry Pi Pico の電源を一旦切ってから入れ直し、Lチカプログラムが自動実行されることも確認してみましょう。USBケーブルをいったん抜き差しすればよいのです。

Raspberry Pi Pico は従来の Linux 系 OS をインストールする Raspberry Pi と異なり、いきなり USB ケーブルを抜いて電源を切っても構いません。
USBケーブルをRaspberry Pi Pico に接続しなおすと、 即座にLチカが始まったのではないでしょうか? このように「プログラムの自動実行が容易であること」、「電源投入後にすぐに回路が動作すること」が Raspberry Pi Pico の特長です。

Linux 系 OS をインストールする従来の Raspberry Pi ではプログラムの自動実行がそれなりに大変であり(本書での紹介はp.286~291)、回路が動作するまでには OS の起動を待たねばなりませんから 1 分程度の時間がかかるのでした。

さて、以上で Raspberry Pi Pico で電子工作パーツを動かす基本の解説は終わりです。以下では、残りの章で用いた電子工作パーツ用の回路図とプログラムを列挙していきます。

5章のタクトスイッチを使ってみよう

タクトスイッチを利用するための回路が下図です。タクトスイッチの状況に応じて点灯させるための LED も取り付けられています(※2025/4/1 Raspberry Pi Pico 2 で動作するよう、タクトスイッチに PULL_UP 抵抗を指定するように変更しました。少なくともこの時点では Pico 2 では PULL_DOWN 指定にすると動作しませんでした)。
上の回路に対し、「タクトスイッチを押している間だけ LED が点灯する」という動作のプログラムが以下です。本書のサンプルでは 05-02-sw-pd.py に相当します。 このプログラムをコピーして Thonny 上に貼り付け、緑色の実行ボタンをクリックして Raspberry Pi Pico 上に main.py という名前で保存することで実行するのでした(※2025/4/1 Raspberry Pi Pico 2 で動作するよう、タクトスイッチに PULL_UP 抵抗を指定するように変更しました)。
from machine import Pin

led = Pin(16, Pin.OUT)
switch = Pin(17, Pin.IN, Pin.PULL_UP)

while True:
    if switch.value()==0:
        led.value(1)
    else:
        led.value(0)
一方、「タクトスイッチを押すたびに LED の点灯/消灯が切り替わる」という動作のプログラムが以下です。本書のサンプルでは 05-03-sw-pd-event.py に相当します。 「一度イベントを検出したら 200ms の間は次のイベントを検出しない」という機能がライブラリになさそうなので、プログラム上で時間を計測して実現しています(※2025/4/1 Raspberry Pi Pico 2 で動作するよう、タクトスイッチに PULL_UP 抵抗を指定するように変更しました。少なくともこの時点では Pico 2 では PULL_DOWN 指定にすると動作しませんでした)。

また、Raspberry Pi ではプログラムが終了しないように while ループまたは pause 関数の実行が必要でしたが(05-03-sw-pd-event.py)、Pico ではその必要はないようなので、どちらも記述しておりません。
from machine import Pin
import time

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

    led.toggle()

prev_time = time.ticks_ms()
led = Pin(16, Pin.OUT)
switch = Pin(17, Pin.IN, Pin.PULL_UP)
switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)
上記のどちらのプログラムも、Thonny に貼り付けて実行ボタンをクリックし、Raspberry Pi Pico 上に main.py という名称で保存して実行するのでした。

6章の半固定抵抗でアナログ値を使ってみよう

6章のAD変換によるアナログ値の利用をRaspberry Pi Picoでも実行してみましょう。書籍で用いた従来の Raspberry Pi と違うのは、 Raspberry Pi Pico には ADコンバータが内蔵されており、別途用意する必要がないことです。ピン配置図で ADC0~2 と書かれているピンでADコンバータを利用できます。

早速試してみましょう。半固定抵抗を用いる回路図が以下です。これは本書p.144の図6-6に相当します。
この回路を利用するためのプログラムが以下です。 本書のサンプルでは 06-01-print.py に相当します。 このプログラムをコピーして Thonny 上に貼り付け、緑色の実行ボタンをクリックして Raspberry Pi Pico 上に main.py という名前で保存することで実行するのでした。
from machine import ADC
from time import sleep

adc = ADC(26)

# Values in a range [272, 65535] were shown.

while True:
    value = adc.read_u16()
    print(value)
    sleep(0.2)
Raspberry Pi Pico の ADコンバータを read_u16 関数で読み取ると、0~65535 の数値が読み取れます。ただし、私が試したところ、小さな値としては 272 程度までしか下がらず、 0という値は出力されませんでした。そのことがコメントとして書かれています。

次に、フォトレジスタ(CdSセル)により明るさを読み取り、暗ければLEDを点灯するという演習を試してみましょう。回路図は以下で、これは本書p.155の図6-9に相当します。
この回路を利用するためのプログラムが以下です。 本書のサンプルでは 06-02-led.py に相当します。このプログラムでは明るい/暗いの境界の値を 50000 と定めています。必要に応じて変更してください。
from machine import ADC, Pin
from time import sleep

adc = ADC(26)
led = Pin(16, Pin.OUT)

# Values in a range [272, 65535] were shown.

while True:
    value = adc.read_u16()
    if value < 50000:
        led.value(1)
    else:
        led.value(0)
    print(value)
    sleep(0.2)


7章のI2Cデバイスを使ってみよう

次に、7章で用いたI2Cデバイスである温度センサと LCD を利用してみましょう。

温度センサを用いる回路図は以下です。本書p.164の図7-4に相当します。なお、I2C用のプルアップ抵抗ですが、RP2040 内部の 50~80kΩ のプルアップ抵抗が有効にされるようで、本ページの演習は全てI2C用のプルアップ抵抗なしで動作します(参考)。 50~80kΩ では大きすぎる、という場合に数 kΩのプルアップ抵抗を別途追加すると良いでしょう。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 07-01-temp.py に相当します。
from machine import Pin, I2C
from time import sleep

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

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

while True:
    inputValue = read_adt7410()
    print(inputValue)
    sleep(0.5)
次に、温度センサとLCDを同時に用いる回路は以下です。これは、本書p.171の図7-6に相当します。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 07-02-LCD.py に相当します。
from machine import Pin, I2C
from time import sleep

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

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

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

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

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

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

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

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

display_chars = chars_per_line*display_lines

position = 0
line = 0

setup_st7032()

write_string('Hello World')

# カタカナや特殊記号は文字コードを一文字ずつ入力
# 以下は「ラズベリー パイ」と表示する例
#s = chr(0xd7)+chr(0xbd)+chr(0xde)+chr(0xcd)+chr(0xde)+chr(0xd8)+chr(0xb0)+' '+chr(0xca)+chr(0xdf)+chr(0xb2)
#write_string(s)
また、温度センサの値をLCDに表示するためのプログラムは以下です。本書のサンプルでは 07-03-LCD-temp.py に相当します。 なお、古いバージョンの MicroPython では、温度センサで値を読み取り、LCDに結果を表示する際、間に「i2c.scan()」という命令を実行しないとエラーが出ていましたので、そのことがコメント文で記されています。
from machine import Pin, I2C
from time import sleep

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

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

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

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

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

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

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

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

address_adt7410 = 0x48
register_adt7410 = 0x00

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

while True:
    inputValue = read_adt7410()
    #i2c.scan() # 古い MicroPython ではデバイス切り替え時に必要だった
    clear()
    s = str(inputValue)
    write_string(s)

    sleep(1)


8章で学んだPWMを使ってみよう

最後に8章で学んだ PWM を Raspberry Pi Pico でも利用してみましょう。 Raspberry Pi Pico ではハードウェア PWM を 16 個出力できます。これは従来の Raspberry Pi の 2 個に比べると大きな利点です。本ページでは解説しませんが、従来の Raspberry Pi と Raspberry Pi Pico を接続し、お互いの利点を活かす方法もあります(希望があれば接続方法を解説するかもしれません)。

さて、PWMにより LED の明るさを変更するための回路は以下です。これは、本書p.194の図8-6に相当します。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 08-01-led.py に相当します。ADコンバータの読みが 272~65535となっていましたので、デューティ比の計算にその補正が入っています。
from machine import Pin, ADC, PWM
from time import sleep

adc = ADC(26)
pwm = PWM(Pin(16))
pwm.freq(100)

# Values in a range [272, 65535] were shown.
adc_min = 272
adc_max = 65535

while True:
    value = adc.read_u16()
    print(value)
    duty = (value-adc_min)/(adc_max-adc_min)
    if duty < 0:
        duty = 0
    pwm.duty_u16(int(65535*duty))
    sleep(0.2)
次に、3つのPWMにより RGBフルカラーLED の色を変更するための回路は以下です。これは、本書p.200の図8-8に相当します。半固定抵抗を3つ並べる際にスペースが必要となりますので、この回路は大きめのブレッドボードがないと実現できませんのでご注意ください。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 08-02-rgbled.py に相当します。ADコンバータの読みが 272~65535となっていましたので、デューティ比の計算にその補正が入っています。
from machine import Pin, ADC, PWM
from time import sleep

adc1 = ADC(26)
adc2 = ADC(27)
adc3 = ADC(28)
pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm3 = PWM(Pin(18))
pwm1.freq(100)
pwm2.freq(100)
pwm3.freq(100)

# Values in a range [272, 65535] were shown.
adc_min = 272
adc_max = 65535

while True:
    value1 = adc1.read_u16()
    value2 = adc2.read_u16()
    value3 = adc3.read_u16()
    duty1 = (value1-adc_min)/(adc_max-adc_min)
    duty2 = (value2-adc_min)/(adc_max-adc_min)
    duty3 = (value3-adc_min)/(adc_max-adc_min)
    if duty1 < 0:
        duty1 = 0
    if duty2 < 0:
        duty2 = 0
    if duty3 < 0:
        duty3 = 0
    # アノードコモンの場合、下記の3行を有効に   
    #duty1 = 1 - duty1
    #duty2 = 1 - duty2
    #duty3 = 1 - duty3
    pwm1.duty_u16(int(65535*duty1))
    pwm2.duty_u16(int(65535*duty2))
    pwm3.duty_u16(int(65535*duty3))
    
    sleep(0.2)
次に、PWMにより DC モーターを制御するための回路は以下です。これは、本書p.208の図8-11に相当します。モータードライバーと半固定抵抗の配置が窮屈になっていますので、大きめのブレッドボードをお持ちの方は、そちらで余裕をもって配置すると良いでしょう。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 08-03-dcmotor.py に相当します。ADコンバータの読みが 272~65535となっていましたので、デューティ比の計算にその補正が入っています。
from machine import Pin, ADC, PWM
from time import sleep

adc = ADC(26)
pwm1 = PWM(Pin(16))
pwm2 = PWM(Pin(17))
pwm1.freq(100)
pwm2.freq(100)

# Values in a range [272, 65535] were shown.
adc_min = 272
adc_max = 65535
adc_mid = (adc_min + adc_max)/2

duty_max = 0.7

while True:
    inputVal = adc.read_u16()
    
    if inputVal < adc_mid:
        pwm2.duty_u16(0)
        duty = (adc_mid-inputVal)/(adc_mid-adc_min)
        if duty > 1:
            duty = 1
        duty *= duty_max
        pwm1.duty_u16(int(65535*duty))
    else:
        pwm1.duty_u16(0)
        duty = (inputVal-adc_mid)/(adc_max-adc_mid)
        if duty > 1:
            duty = 1
        duty *= duty_max
        pwm2.duty_u16(int(65535*duty))
    sleep(0.5)
最後に、PWMにより RC サーボモーターを制御するための回路は以下です。これは、本書p.215の図8-13に相当します。
この回路を動かすためのプログラムは以下です。本書のサンプルでは 08-04-servo.py に相当します。ADコンバータの読みが 272~65535となっていましたので、デューティ比の計算にその補正が入っています。
from machine import Pin, ADC, PWM
from time import sleep

def servo_duty_hwpwm(val):
    val_min = 272
    val_max = 65535
    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

adc = ADC(26)
pwm = PWM(Pin(16))
pwm.freq(50)

while True:
    value = adc.read_u16()
    duty = servo_duty_hwpwm(value)
    pwm.duty_u16(int(65535*duty))
    sleep(0.2)


おわりに

以上、本書で実行した 1 章から 8 章の演習のうち、Raspberry Pi Pico でも実行可能なものを紹介しました。

9章以降の Wifi 接続を必要とする演習は、Wifi機能付きの Raspberry Pi Pico W や Pico 2 W を用いればおおむね動作させることができます(機能の制限はありますが)。その方法を示したのが下記のページです。 さらに、本書の続編である「実例で学ぶRaspberry Pi電子工作」で紹介した演習のうち Pico W や Pico 2 W で実現できるものを下記のページで紹介しています。 Raspberry Pi Pico シリーズで実現できない演習、典型的には画像処理を行う演習や、「Raspberry Piではじめる機械学習」で紹介した、機械学習やディープラーニングを行う演習が、通常バージョンの Raspberry Pi の得意分野と言えるでしょう。

本ページをきっかけに、Raspberry Pi を用いた電子工作に対する皆さんのアイディアがさらに膨らむと幸いです。

2025年8月18日月曜日

Raspberry Pi OS の Lite 版を利用する

1. はじめに

Raspberry Pi OS には、インストール済のアプリケーションの数によって Full 版、通常版、Lite 版の 3 種類があります。 本書の旧版では、Full 版の利用を推奨していましたが、必要な microSD カードの容量が増大するに伴い、現在は通常版の利用を推奨しています。

Lite 版はインストール済のアプリケーション数が最も少ないバージョンですが、グラフィックを用いたデスクトップがデフォルトで起動しない(インストールもされない)という上級者向けなバージョンであるため、初心者向けの本書では利用を推奨しませんでした。

その一方で、本サポートサイトでは、下記のページで Raspberry Pi にディスプレイ、キーボード、マウスを接続せずに用いるヘッドレス(headless)な運用方法を解説してきました。 実は、上記 2 ページをマスターすると、Raspberry Pi OS の Lite 版を利用する知識が自然と身についています。本ページではRaspberry Pi OS の Lite 版で本書の演習を実行する際の注意点を解説します。

Raspberry Pi OS の Lite 版を用いるメリットは以下のようになるでしょうか。
  • インストールする OS のサイズが小さいので、OS のダウンロードとインストールにかかる時間が短い
  • インストール済のアプリケーション数が少ないので、microSD カードの使用量が少ない
    (ただし、追加でアプリケーションをインストールしていくと、通常版と大差ない使用量になり得る)
  • インストール済のアプリケーション数が少ないので、OS の更新にかかる時間が短い
  • グラフィック描画用のライブラリがデフォルトでインストールされていないので、使用メモリ量が小さい
  • 自分が必要とするアプリケーションのみがインストールされている状態が(人によっては)気持ち良い
現在は Raspberry Pi 本体の性能も向上しており、大容量の microSD カードも入手しやすくなっていますので、上記のメリットに魅力を感じる方は多くはないかもしれません。
その一方で、性能の低い Raspberry Pi Zero シリーズや、旧式の Raspberry Pi を利用する場合、この Lite 版の利用を検討する価値はあるでしょう。

それでは、やっていきましょう!

2. Raspberry Pi OS の Lite 版のインストール

下記のページの手順に従い、Raspberry Pi Imager でインストールします。Raspberry Pi Imager は最新版を用いるようにしてください。以下では Raspberry Pi Imager 2.0.0 を用いて解説します。 その際、「OS」項目の選択肢は、下記のように「Raspberry Pi OS (other)」を一度選択します。
そして、切り替わった画面で「Raspberry Pi OS Lite (64-bit)」または「Raspberry Pi OS Lite (32-bit)」を選択してください。選択後は「次へ」ボタンをクリックします。
OS と microSD カードのドライブを選択した後は、「Raspberry Pi をヘッドレスでセットアップする」の解説に従い、ユーザー名、Wifi接続の設定、日本語キーボードの設定、SSH の有効化などを Raspberry Pi Imager で行うのでした。

インストール後に OS を起動した後は、上記のページに従い、TeraTerm などのターミナルソフトウェアで接続し「sudo raspi-config」コマンドで日本語化の設定まで行ってしまいましょう。

インストールの解説は以上です。

3. Raspberry Pi OS の Lite 版で本書の演習を行う際の注意

ここから先は、Raspberry Pi OS の Lite 版で本書の演習を行う際の注意点を述べていきます。Lite 版ではデフォルトでインストール済のアプリケーション数が少ないので、いくつか追加で実行すべきことがあるのです。

インストール後の設定

まず、インストール直後は下記のコマンドで OS を最新にしておきましょう。
sudo apt update

sudo apt -y dist-upgrade
サンプルプログラムをダウンロードするためには、git コマンドを下記でインストールしておく必要があります。
sudo apt -y install git

5章のカメラを用いる演習 05-04-sw-camera.py

このプログラムは、OpenCV をインストールする必要があるのですが、そうすると、グラフィックなどのライブラリ (X11関連など) が大量にインストールされるので、あまりお勧めしません。 Lite 版の良さが失われるためです。それでも実行したいという場合、下記のように picamera2 のライブラリもインストールしておく必要があります。OpenCV のインストール方法は書籍と同じです。
sudo apt -y install python3-picamera2

5章の音声を用いる演習 05-05-sw-mp3.py

このプログラムは、MP3 により音声を鳴らすのですが、Lite 版はデフォルトでは音が鳴りません。音を鳴らすためのライブラリをインストールすると、一緒にグラフィックなどのライブラリ (X11関連など) が大量にインストールされるので、あまりお勧めしません。 Lite 版の良さが失われるためです。それでも実行したいという場合、下記のライブラリもインストールしておく必要があります。mplayer のインストール方法は書籍と同じです。
sudo apt -y install pulseaudio pipewire-pulse

6章 SPIを用いるプログラム

6 章で SPI を用いるプログラムを実行する場合、「sudo raspi-config」を実行し、下記の手順で SPI を有効化する必要があります。
  1. ↓を2回押して「3 Interface Options」にフォーカスがあっていることを確認してEnter
  2. ↓を3回押して「I4 SPI」にフォーカスがあっていることを確認してEnter
  3. 「Would you like the SPI interface to be enabled?」という質問に対して ←を1回押して「はい」にフォーカスがあっていることを確認してEnter
  4. 「了解」にフォーカスがあっていることを確認してEnter
  5. TABキーをニ回押し、「Finish」にフォーカスを合わせEnter

7章 I2Cを用いるプログラム

7 章で I2C を用いるプログラムを実行する場合、まず下記のコマンドで必要なライブラリやツールをインストールする必要があります。
sudo apt -y install python3-smbus i2c-tools
さらに、「sudo raspi-config」を実行し、下記の手順で I2C を有効化する必要があります。
  1. ↓を2回押して「3 Interface Options」にフォーカスがあっていることを確認してEnter
  2. ↓を4回押して「I5 I2C」にフォーカスがあっていることを確認してEnter
  3. 「Would you like the ARM I2C interface to be enabled?」という質問に対して ←を1回押して「はい」にフォーカスがあっていることを確認してEnter
  4. 「了解」にフォーカスがあっていることを確認してEnter
  5. TABキーをニ回押し、「Finish」にフォーカスを合わせEnter
それ以外は本書の指示通りで演習を実行できると思います。

以上、お疲れさまでした!