はじめに
本書は、前著「Raspberry Piで学ぶ電子工作」の応用編という位置づけです。前著では、書籍の演習を Raspberry Pi Pico シリーズで実行する、という趣旨のコンテンツを作成し、以下のページで公開しました。 Raspberry Pi Pico とは、Raspberry Pi ブランドで開発されているマイクロコントローラー(マイコン)ボードです。
これらのコンテンツは、「半導体不足でRaspberry Pi 本体が入手しにくいときでも演習を実行しやすくする」という目的で作成されましたが、
「シンプルな回路で電子工作により親しめる」、「Pico では実現できない内容を知ることで、Raspberry Pi の特性をより深く知ることができる」などというメリットもあります。
本ページは、上記のページと同様、本書「実例で学ぶRaspberry Pi電子工作」の演習を Raspberry Pi Pico シリーズでも実行可能にすることを目指したページです。
とは言え、全ての演習を実行できるわけではなく、カメラを用いない下記の章のみを Raspberry Pi Pico シリーズで体験できます。
- 3章 たくさんのLEDを点灯させてみよう
- 4章 インターネット上の天気予報データを利用しよう
- 5章 Raspberry Pi Picoを家電製品のリモコンにしてみよう
- 8章 6脚ロボットを操作してみよう
必要な Raspberry Pi Pico ですが、Wifi 接続を多用しますので、Pico W または Pico 2 W を用意してください。Pico シリーズの選び方および設定や利用の方法は 前著のページの1ページ目「本書の電子工作パーツを Raspberry Pi Pico シリーズで利用する」を参考にしてください。
L チカまでを体験して頂くと、本ページの演習もスムーズに実行できるでしょう。
本ページは、読者の方のご要望で作成に着手したのですが、思いのほか作業量が多く、時間がかかってしまいました。
その分、充実した内容になっていると思いますので、お楽しみ頂ければ幸いです。
3章 たくさんのLEDを点灯させてみよう
本書3章では7個のLEDで電子サイコロを作成し、タクトスイッチを一度押すことで「1~6の目が順番に現れ、最終的にランダムな目が点灯する」という動作を実現しました。たくさんのLEDを点灯させるためにシフトレジスタ SN74HC595N を活用する、という趣旨の演習でした。
それを Raspberry Pi Pico で実現するためにブレッドボードに構成する回路が下記です。本演習は Wifi を用いませんので、すべての機種の Pico で実行可能です。
見ての通り、この演習は大きめのブレッドボードがないと実現できません。
また、本書とは異なり、タクトスイッチは GND に接続し、マイコン側のピンをプルアップ接続するよう設定します。これは、Pico 2 ではプルダウン指定では動作しなかったためです。 この回路を Pico で動作させるためのプログラムが下記です。本書のプログラム bb2-03-04-dice-switch-delay.py の Pico 版という位置づけです。 Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行しましょう。本書と同じ動作が実現されます。
本書では、「タクトスイッチが押された時の動作」をスレッドという仕組みで実現しました。最終的にサイコロの目が確定するまでに長い時間(0.6秒程度)かかるためです。
Pico 用のプログラムでもその動作に合わせています。Pico でスレッドを用いる場合、Pico のプロセッサの2つ目のコアで実行されます(参考)。
from machine import Pin from time import sleep, ticks_ms import random import _thread def switch_pressed(p): global prev_time pressed_time = ticks_ms() if pressed_time < prev_time + 200 : return prev_time = pressed_time _thread.start_new_thread(randomizeDice, ()) def randomizeDice(): delay = 0.1 sendLEDdata(LEDdata1, ser, rclk, srclk) sleep(delay) sendLEDdata(LEDdata2, ser, rclk, srclk) sleep(delay) sendLEDdata(LEDdata3, ser, rclk, srclk) sleep(delay) sendLEDdata(LEDdata4, ser, rclk, srclk) sleep(delay) sendLEDdata(LEDdata5, ser, rclk, srclk) sleep(delay) sendLEDdata(LEDdata6, ser, rclk, srclk) sleep(delay) dice = random.randint(1,6) if dice == 1: sendLEDdata(LEDdata1, ser, rclk, srclk) elif dice == 2: sendLEDdata(LEDdata2, ser, rclk, srclk) elif dice == 3: sendLEDdata(LEDdata3, ser, rclk, srclk) elif dice == 4: sendLEDdata(LEDdata4, ser, rclk, srclk) elif dice == 5: sendLEDdata(LEDdata5, ser, rclk, srclk) elif dice == 6: sendLEDdata(LEDdata6, ser, rclk, srclk) def sendLEDdata(data, ser, rclk, srclk): n = len(data) rclk.value(0) srclk.value(0) for i in range(n): if data[i] == 1: ser.value(1) else: ser.value(0) srclk.value(1) srclk.value(0) rclk.value(1) rclk.value(0) prev_time = ticks_ms() ser = Pin(16, Pin.OUT) rclk = Pin(17, Pin.OUT) srclk = Pin(18, Pin.OUT) switch = Pin(19, Pin.IN, Pin.PULL_UP) switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) LEDdata0 = [0, 0, 0,0,0, 0, 0] LEDdata1 = [0, 0, 0,1,0, 0, 0] LEDdata2 = [0, 1, 0,0,0, 1, 0] LEDdata3 = [0, 1, 0,1,0, 1, 0] LEDdata4 = [1, 1, 0,0,0, 1, 1] LEDdata5 = [1, 1, 0,1,0, 1, 1] LEDdata6 = [1, 1, 1,0,1, 1, 1] sendLEDdata(LEDdata0, ser, rclk, srclk)
4章 インターネット上の天気予報データを利用しよう
4章ではインターネット上の天気予報データを取得して LCD に表示する演習を行いました。書籍で紹介した天気予報サービスは既にサービスの提供を終了しておりますので、本サポートサイトでは、既に OpenWeather というサービスの利用に移行しております。
その内容を Pico で実現します。インターネット接続を必要としますので、Wifi 接続可能な Pico W または Pico 2 W が必須となります。
まず、下記ページの「第4章」の内容を参考に、OpenWeather のアカウントの作成、および API キーの取得までを行ってください。 まずはブレッドボードを用いず、OpenWeather による天気予報データの取得など、動作確認をいくつか行いましょう。
まずは、都市名から緯度と経度を取得するプログラム bb2-04-00-checkcity.py の Pico 版を実行してみましょう。下記のプログラムがそうなのですが、皆さんが各自で変更しなければならない所が3行分あります。
from time import sleep import network import requests import json ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' city = 'Tokyo' key = 'API_KEY' address = 'http://api.openweathermap.org/data/2.5/weather?units=metric&q={city}&APPID={key}'.format(city=city, key=key) wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') sleep(1) # Handle connection error if wlan.status() != 3: write_string('conn. failed') # showing on LCD raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) weather_json = requests.get(address).json() print(city) try: print('lat = \'{}\''.format(weather_json['coord']['lat'])) print('lon = \'{}\''.format(weather_json['coord']['lon'])) except KeyError: print('Not found.')まずはプログラム 6~7 行目の以下の部分です。これらの行には、それぞれ「皆さんの Wifi のネットワーク名 (SSID)」および「そのパスワード」を記入する必要があります。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'次にプログラム 10 行目の以下の部分です。この部分には皆さんが OpenWeather のサイトで取得した API キーを記入します。
key = 'API_KEY'イメージとしては下記のように編集する、ということです。
key = '................................'これらの3行は、4章のプログラム全てに存在しますので、全て皆さんの情報に置き換えてください。
以上の変更を適用したプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行しましょう。「Wifi 接続」→「OpenWeather への接続」の順で処理が進み、最終的に Thonny のシェルの部分に下記の出力が現れます。
Tokyo lat = '35.6895' lon = '139.6917'これは、Tokyo の緯度と経度を表示するプログラムなのでした。以下のプログラムでは、この緯度と経度の数値を利用して天気予報の取得を行っています。
もし Tokyo 以外の天気予報を取得したい場合、上記プログラムの下記の部分を別の都市名に置き換え、その都市の緯度と経度を調べる必要があります。
city = 'Tokyo'利用可能な都市名は、こちらのサイトで調べることができます。ブラウザで位置情報の取得が許可されている必要があるようです。
さて、ここまでのチェックが終わったら、次に天気予報データの取得チェックを行いましょう。書籍の bb2-04-01-weather.py の Pico 版が下記のプログラムです。
from time import sleep import network import requests import json ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' lat = '35.6895' lon = '139.6917' units = 'metric' lang = 'ja' key = 'API_KEY' address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang) wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') sleep(1) # Handle connection error if wlan.status() != 3: raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) weather_json = requests.get(address).json() print(json.dumps(weather_json, indent=2, ensure_ascii=False))先ほどと同様、
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'および
key = 'API_KEY'の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895' lon = '139.6917'そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「天気予報データを json という形式で取得して表示」の順で処理が進み、最終的に Thonny のシェルの部分に表示が現れます。
json による天気予報データはそこそこに大きなデータなのですが、 Thonny のシェルの部分では省略表示されるので、詳細はわかりません。
ここではエラーなく実行できれば先に進みましょう。
最後の動作チェックとして、取得した json データから、3日分の天気予報データを抽出して表示してみましょう。書籍のプログラムでは bb2-04-02-forcast.py が該当します。
from time import sleep import network import requests import json ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' lat = '35.6895' lon = '139.6917' units = 'metric' lang = 'ja' key = 'API_KEY' address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang) wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') sleep(1) # Handle connection error if wlan.status() != 3: raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) weather_json = requests.get(address).json() dataLabel = ['今日', '明日', '明後日'] weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'} for i in range(3): forecast = weather_json['list'][8*i] temp_min = round(forecast['main']['temp_min']) temp_max = round(forecast['main']['temp_max']) when = forecast['dt_txt'] try: weather = weather_dict[forecast['weather'][0]['main']] except KeyError: weather = '未定義' print('{}, {}, {}/{}, {}'.format(dataLabel[i], weather, temp_min, temp_max, when))先ほどと同様、
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'および
key = 'API_KEY'の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895' lon = '139.6917'そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「天気予報データを json という形式で取得し、3日分の天気予報を抽出して表示」の順で処理が進み、最終的に Thonny のシェルの部分に下記のような表示が現れます。
この3日分の天気予報のうち、今日と明日の2日分が、LCD に表示する内容になります。なお、各行の末尾に記されている日付は参考データとして表示されており、LCD には表示されません。
その参考データの時刻は恐らく UTC で表示されており、それに 9 時間足した時間が日本時間です。
今日, 曇り, 22/22, 2025-06-12 15:00:00 明日, 曇り, 21/21, 2025-06-13 15:00:00 明後日, 雨, 21/21, 2025-06-14 15:00:00以上の動作チェックが終わったら、ブレッドボード上で回路を組みましょう。
I2C 接続の温度計と LCD を接続した回路です。 この回路において、取得した温度データと天気予報データを LCD に表示します。まず、書籍の bb2-04-04-lcd-4modes.py に該当するのが下記のプログラムです。
from machine import Pin, I2C from time import sleep, ticks_ms import network import requests import json import sys ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' lat = '35.6895' lon = '139.6917' units = 'metric' lang = 'ja' key = 'API_KEY' def setup_st7032(): c_lower = (contrast & 0xf) c_upper = (contrast & 0x30)>>4 i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c])) sleep(0.2) i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01])) sleep(0.001) def clear(): global position global line position = 0 line = 0 i2c.writeto_mem(address_st7032, register_setting, bytes([0x01])) sleep(0.001) def newline(): global position global line if line == display_lines-1: clear() else: line += 1 position = chars_per_line*line i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0])) sleep(0.001) def write_string(s): for c in list(s): write_char(ord(c)) def write_char(c): global position byte_data = check_writable(c) if position == display_chars: clear() elif position == chars_per_line*(line+1): newline() i2c.writeto_mem(address_st7032, register_display, bytes([byte_data])) position += 1 def check_writable(c): if c >= 0x06 and c <= 0xff : return c else: return 0x20 # 空白文字 def read_adt7410(): word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 2), 'little', False) data = (word_data & 0xff00)>>8 | (word_data & 0xff)<<8 data = data>>3 # 13ビットデータ if data & 0x1000 == 0: # 温度が正または0の場合 temperature = data*0.0625 else: # 温度が負の場合、 絶対値を取ってからマイナスをかける temperature = ( (~data&0x1fff) + 1)*-0.0625 return temperature def switch_pressed(p): global prev_time pressed_time = ticks_ms() if pressed_time < prev_time + 200 : return prev_time = pressed_time global mode mode = (mode+1)%4 displayWeather() # 各天気をカタカナに def replaceWeather(weather): if weather.find('晴') != -1: return chr(0xca)+chr(0xda) # ハレ elif weather.find('曇') != -1: return chr(0xb8)+chr(0xd3)+chr(0xd8) # クモリ elif weather.find('雨') != -1: return chr(0xb1)+chr(0xd2) # アメ elif weather.find('雪') != -1: return chr(0xd5)+chr(0xb7) # ユキ else: return '--' # Webサービスより天気予報を取得 def getWeather(): global minmax, weather, minmax2, weather2 try: address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang) weather_json = requests.get(address).json() print('インターネットから天気予報データを入手しました') except requests.exceptions.RequestException: print('ネットワークエラー') if second==0: sys.exit() weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'} for i in range(2): forecast = weather_json['list'][8*i] temp_min = round(forecast['main']['temp_min']) temp_max = round(forecast['main']['temp_max']) try: weather_tmp = weather_dict[forecast['weather'][0]['main']] except KeyError: weather_tmp = '未定義' if i==0: minmax = '{}/{}'.format(temp_min, temp_max) weather = replaceWeather(weather_tmp) else: minmax2 = '{}/{}'.format(temp_min, temp_max) weather2 = replaceWeather(weather_tmp) # 天気情報をLCDに表示 def displayWeather(): clear() if temperature != 999: s = '{0:.1f}'.format(temperature) # 小数点以下1桁 else: s = '' if mode==0: day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+weather) elif mode==1: day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+minmax) elif mode==2: day = chr(0xb1)+chr(0xbd) # アス for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+weather2) elif mode==3: day = chr(0xb1)+chr(0xbd) # アス for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+minmax2) i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) i2c.scan() address_st7032 = 0x3e register_setting = 0x00 register_display = 0x40 contrast = 32 # 0から63のコントラスト。通常は32、文字が薄いときは40を推奨 chars_per_line = 8 # LCDの横方向の文字数 display_lines = 2 # LCDの行数 display_chars = chars_per_line*display_lines position = 0 line = 0 setup_st7032() prev_time = ticks_ms() switch = Pin(17, Pin.IN, Pin.PULL_UP) switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) address_adt7410 = 0x48 register_adt7410 = 0x00 mode = 0 temperature = 999 minmax = '' weather = '' minmax2 = '' weather2 = '' second = 0 # プログラム起動時から経過した秒数(目安) hour = 0 # プログラム起動時から経過した時間(目安) prevhour = -1 # 前回天気予報を問い合わせた時間 wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') write_string('waiting for conn') #showing on LCD sleep(1) # Handle connection error if wlan.status() != 3: write_string('conn. failed') # showing on LCD raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) write_string(status[0]) #showing on LCD try: while True: if hour != prevhour: # 前回問い合わせより約1時間後 getWeather() # 天気予報取得 prevhour = hour try: temperature = read_adt7410() # 温度取得 except IOError: temperature = 999 # 温度取得失敗 displayWeather() # LCDに情報表示 second = second + 1 # 1秒進める hour = int(second/3600) # 秒を時間に変換(0,1,2,…のように整数のみ) sleep(1) except KeyboardInterrupt: passそして、書籍の bb2-04-05-lcd-3modes.py に該当するのが下記のプログラムです。
from machine import Pin, I2C from time import sleep, ticks_ms import network import requests import json import sys ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' lat = '35.6895' lon = '139.6917' units = 'metric' lang = 'ja' key = 'API_KEY' def setup_st7032(): c_lower = (contrast & 0xf) c_upper = (contrast & 0x30)>>4 i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c])) sleep(0.2) i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01])) sleep(0.001) def clear(): global position global line position = 0 line = 0 i2c.writeto_mem(address_st7032, register_setting, bytes([0x01])) sleep(0.001) def newline(): global position global line if line == display_lines-1: clear() else: line += 1 position = chars_per_line*line i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0])) sleep(0.001) def write_string(s): for c in list(s): write_char(ord(c)) def write_char(c): global position byte_data = check_writable(c) if position == display_chars: clear() elif position == chars_per_line*(line+1): newline() i2c.writeto_mem(address_st7032, register_display, bytes([byte_data])) position += 1 def check_writable(c): if c >= 0x06 and c <= 0xff : return c else: return 0x20 # 空白文字 def read_adt7410(): word_data = int.from_bytes(i2c.readfrom_mem(address_adt7410, register_adt7410, 2), 'little', False) data = (word_data & 0xff00)>>8 | (word_data & 0xff)<<8 data = data>>3 # 13ビットデータ if data & 0x1000 == 0: # 温度が正または0の場合 temperature = data*0.0625 else: # 温度が負の場合、 絶対値を取ってからマイナスをかける temperature = ( (~data&0x1fff) + 1)*-0.0625 return temperature def switch_pressed(p): global prev_time pressed_time = ticks_ms() if pressed_time < prev_time + 200 : return prev_time = pressed_time global mode mode = (mode+1)%3 displayWeather() # 各天気をカタカナに def replaceWeather(weather): if weather.find('晴') != -1: return chr(0xca)+chr(0xda) # ハレ elif weather.find('曇') != -1: return chr(0xb8)+chr(0xd3)+chr(0xd8) # クモリ elif weather.find('雨') != -1: return chr(0xb1)+chr(0xd2) # アメ elif weather.find('雪') != -1: return chr(0xd5)+chr(0xb7) # ユキ else: return '--' # Webサービスより天気予報を取得 def getWeather(): global minmax, weather, minmax2, weather2 try: address = 'http://api.openweathermap.org/data/2.5/forecast?lat={lat}&lon={lon}&appid={key}&units={units}&lang={lang}'.format(lat=lat, lon=lon, key=key, units=units, lang=lang) weather_json = requests.get(address).json() print('インターネットから天気予報データを入手しました') except requests.exceptions.RequestException: print('ネットワークエラー') if second==0: sys.exit() weather_dict = {'Clear':'晴れ', 'Clouds':'曇り', 'Rain':'雨','Snow':'雪', 'Thunderstorm':'雷', 'Drizzle':'霧'} for i in range(2): forecast = weather_json['list'][8*i] temp_min = round(forecast['main']['temp_min']) temp_max = round(forecast['main']['temp_max']) try: weather_tmp = weather_dict[forecast['weather'][0]['main']] except KeyError: weather_tmp = '未定義' if i==0: minmax = '{}/{}'.format(temp_min, temp_max) weather = replaceWeather(weather_tmp) else: minmax2 = '{}/{}'.format(temp_min, temp_max) weather2 = replaceWeather(weather_tmp) # 天気情報をLCDに表示 def displayWeather(): clear() if temperature != 999: s = '{0:.1f}'.format(temperature) # 小数点以下1桁 else: s = '' if mode==0: day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+weather) elif mode==1: day = chr(0xb7)+chr(0xae)+chr(0xb3) # キョウ for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+minmax) elif mode==2: day = chr(0xb1)+chr(0xbd) # アス s = minmax2 for i in range(chars_per_line-len(day)-len(s)): s = ' '+s write_string(day+s+weather2) i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) i2c.scan() address_st7032 = 0x3e register_setting = 0x00 register_display = 0x40 contrast = 32 # 0から63のコントラスト。通常は32、文字が薄いときは40を推奨 chars_per_line = 8 # LCDの横方向の文字数 display_lines = 2 # LCDの行数 display_chars = chars_per_line*display_lines position = 0 line = 0 setup_st7032() prev_time = ticks_ms() switch = Pin(17, Pin.IN, Pin.PULL_UP) switch.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) address_adt7410 = 0x48 register_adt7410 = 0x00 mode = 0 temperature = 999 minmax = '' weather = '' minmax2 = '' weather2 = '' second = 0 # プログラム起動時から経過した秒数(目安) hour = 0 # プログラム起動時から経過した時間(目安) prevhour = -1 # 前回天気予報を問い合わせた時間 wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') write_string('waiting for conn') #showing on LCD sleep(1) # Handle connection error if wlan.status() != 3: write_string('conn. failed') # showing on LCD raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) write_string(status[0]) #showing on LCD try: while True: if hour != prevhour: # 前回問い合わせより約1時間後 getWeather() # 天気予報取得 prevhour = hour try: temperature = read_adt7410() # 温度取得 except IOError: temperature = 999 # 温度取得失敗 displayWeather() # LCDに情報表示 second = second + 1 # 1秒進める hour = int(second/3600) # 秒を時間に変換(0,1,2,…のように整数のみ) sleep(1) except KeyboardInterrupt: passどちらのプログラムもこれまでと同様、
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'および
key = 'API_KEY'の部分は皆さんの情報で置き換える必要があります。2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。また、必要に応じて下記の部分も皆さんの好みの緯度と経度に置き換えて構いません。
lat = '35.6895' lon = '139.6917'そのプログラムを Raspberry Pi Pico を指定した Thonny で記述し、main.py という名前で保存して実行します。
「Wifi 接続」→「温度データと天気予報データを LCD に定期的に表示」の順で処理が進みます。
5章 Raspberry Pi Picoを家電製品のリモコンにしてみよう
5章で解説した、Raspberry Pi を家電製品のリモコンにする演習も、Pico で実行できます。その際、meloncookie さんが開発した RemotePy を利用します。 これは、MicroPython で赤外線リモコンの信号を送受信するためのライブラリです。meloncookie さんに感謝いたします。
ここからは、以下の流れで解説を進めます。
- 5.1 Pico 上に RemotePy をセットアップする
- 5.2 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を記録する
- 5.3 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を送信する
- 5.4 RemotePy と自作プログラムを組み合わせ、タクトスイッチでテレビのリモコンの信号を送信する
- 5.5 RemotePy と自作プログラムを組み合わせ、ブラウザでテレビのリモコンの信号を送信する
5.4 と 5.5 は書籍の5.4章に相当する内容です。
5.1 Pico 上に RemotePy をセットアップする
ここでは、RemotePy によって提供される 3 つのファイルを Pico 上に保存します。ここでつまづくと以下の演習も実行できませんので、着実に進めていきましょう。まず、RemotePy のプログラムを圧縮ファイルとして保存しましょう。下記のリンク先で緑色の「Code」ボタン→Download ZIPを選択しましょう。 RemotePy が提供されたファイル群がまとめられた RemotePy-main.zip という圧縮ファイルがダウンロードされますので、お好みの場所に保存します。
次に、ダウンロードされた RemotePy-main.zip を展開します。Windows 11 でしたら、RemotePy-main.zip を右クリックし、「すべて展開」を選択し、現れた「圧縮 (ZIP 形式) フォルダーの展開」という画面で「展開」ボタンをクリックすれば、展開が進みます。もし右クリックで「すべて展開」という選択肢がない場合、「展開」や「解凍」という語句を含む選択肢を探してください。 「展開」や「解凍」は、一つにまとめられたファイルをバラバラに戻すことを指します。
この展開作業を行わないと、先には進めませんので確実に実行してください。
さて、次にこれまで通り Pico を Thonny がインストールされた PC に USB ケーブルで接続します。最終的にブラウザ経由でリモコン信号を送信しますので、Wifi 機能がある Pico W や Pico 2 W が良いでしょう。
そして Thonny を起動し、右下の選択肢で下図のように「MicroPython (Raspberry Pi Pico)」を選択します。さらに、Thonny のメニューから「表示」→「ファイル」を選択し、チェックを入れます。 そうすると、Thonny の左側に、ファイルの一覧を表示する領域があらわれます。上側が PC のファイルを表示する領域で、下側が Pico 上のファイルを表示する領域です。
ここで重要なのは Pico 上のファイルを表示する領域で、下図ではこれまで用いてきた main.py が保存されていることがわかります。
Pico 上に RemotePy が提供する 3 つのファイルを保存し、この領域に表示されることを確認するのがここで目指すことです。 次に、Thonny のメニューから「ファイル」→「ファイルを開く」を選択します。すると、どこにあるファイルを開くのかを決めるために、下記の画面が現れます。ここでは、先ほどダウンロードして展開した RemotePy のファイルを開きたいので、「このコンピュータ」をクリックします。 すると、どのファイルを開くか選択する画面が現れますので、先ほど保存し展開したファイル群から下記のファイルを選択して開きましょう。
- RemotePy/demo/RP2040/micropython/main.py
そのことは、Thonny のタブ上に表示されるファイル名が、[] (角かっこ) で囲われていないことでわかります。すぐ下で確認できるように、Pico 上のファイルのファイル名は [] で囲われるのです。
次に、この PC 状の main.py を Pico 上に保存しましょう。Pico には既に main.py が存在するでしょうから、多くの場合、上書き保存になるでしょう。
Thonny のメニューから「ファイル」→「名前を付けて保存」を選択しましょう。どこに保存するかを決めるため下記の画面が現れますので、「Raspberry Pi Pico」の方をクリックしましょう。 そこで、下記の画面が現れますので、main.py というファイル名を記入してOKボタンをクリックしましょう。上書きの確認を求められたらOKしてください。 すると、下記の状態になります。ほとんど変化がないように見えますが、タブ部のファイル名が [] (角かっこ)で囲われており、そのファイルが Pico 上のファイルであることがわかります。 以上が終わったら、下記の2ファイルについても同じことを行います。
- RemotePy/micropython/RP2040/FromV1_17/UpyIrRx.py
- RemotePy/micropython/RP2040/FromV1_17/UpyIrTx.py
「UpyIrRx.py」、「UpyIrTx.py」というファイル名は複雑ですので、このページからコピーして Thonny 上に貼り付けることを推奨します。
最終的に下図の状態になれば Pico の準備完了です。 main.py のタブをクリックしてから実行すればそのプログラムが動作しますし、これまで通り、Pico に電源を入れると即座にその main.py が動作し始めます。
ただし、main.py が実行されても何も起こりません。このプログラムは「PC 上のデモアプリケーションからの指令を待っている」状態なのです。PC 上のデモアプリケーションから指令が来たときに、「赤外線リモコンの信号の読み取り」や「信号の送信」が行われる、というわけです。
そのため、次にその「PC 上のデモアプリケーション」を実行することにしましょう。
5.2 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を記録する
それでは、PC上のデモアプリケーションを実行してテレビのリモコンの信号を記録してみましょう。この演習を実行するには、すぐ上で行った「5.1 Pico 上に RemotePy をセットアップする」でセットアップした Pico がその PC に USB 接続されており、
さらにその上の main.py が実行されていることが必要です。
セットアップを行った Pico の USB ケーブルを一度抜き差しすれば自動的にその状態が実現します。
main.py は電源投入時に自動的に実行されるからです。
さて、Thonny を起動し、右下の選択肢を下図のように「ローカルPython3」へ変更します。
そして、ローカルPython3 の Thonny に、numpy というライブラリをインストールします。デモアプリケーションの実行に必要になるためです。
そのため、下図のようにメニューから「ツール」→「パッケージを管理」を選択します。
なお、Thonny の実行環境として Windows ではなく Rasberry Pi を用いている方はここでのインストール作業は不要ですので、「パッケージを管理」は実行しないでください。 「パッケージを管理」の選択により現れた画面で、下図のように「numpy」を入力して「PyPIを検索」をクリックします。
すると、numpy のリンクが現れますのでクリックしてください。 すると、下図のように numpy のインストールボタンが現れますので、インストールしてください。以上でパッケージの追加が終わります。 なお、Thonny の実行環境として Windows ではなく Rasberry Pi を用いている方は、ここから作業を再開します。
さて、引き続き「ローカルPython3」が選択された Thonny で、「ファイル」→「ファイルを開く」から
- RemotePy/demo/RP2040/GUI/main.py
これは、meloncookie 氏が作成したアプリケーションであり、これを用いてテレビのリモコンの信号の記録や信号の送信の動作確認を行うことになります。
その動作確認が済んでから本書のプログラムを実行する、という流れです。 さて、ここではテレビのリモコンの信号を読み取って記録しますので、下図のように赤外線リモコン受信モジュールを用いた回路をブレッドボード上に作成しておきましょう。 そうしたら、デモアプリケーションでSelectボタンをクリックし、リモコンの信号を記録したいファイルの位置とファイル名を決めましょう。下図ではデスクトップ上に TV.txt を保存するよう設定を行いました。
場所はお好みの場所で構いませんが、ファイル名は半角英字で TV.txt としてください。
次にOpenボタンをクリックすると、Key list に __system__ という項目が現れます。 ここから先は、リモコンの信号を一つずつ記録していくことになります。
書籍で行ったように、power, input, ch1, ch2, …, ch12, vup, vdown, cup, cdown の名前で信号を記録していきましょう。
それぞれ、電源、入力切替、チャンネル1~チャンネル12、ボリュームアップ、ボリュームダウン、チャンネルアップ、チャンネルダウンの意味でしたね。
まずは、電源ボタンを power という名称で記録しましょう。Edit key の領域に power を記入し、Append ボタンをクリックします。
すると、Key list に power という項目が現れます。 次に、Key list の power が選択された状態(背景色が青の状態)で Record ボタンをクリックします。
それから3秒以内に、ブレッドボード上の赤外線リモコン受信モジュールにテレビのリモコンを向けて、電源(power)ボタンを一度押します。
すると、Signal の領域に、読み取られた信号が記入されます。 その状態で Commit ボタンをクリックすると、読み取った信号が確定され、背景色が緑に変化します。 以上の流れを、記録したい全ての信号に対して行います。すなわち、電源(power)の次に記録する入力切替(input)ボタンについて書けばこうです。
- Edit key の領域に input と記入し、Append ボタンをクリック
- Key list の input が選択された状態で Record ボタンをクリック
- 3秒以内に赤外線リモコン受信モジュールに向けてリモコンの入力切替(input)ボタンを一度押す
- Commitボタンをクリックして信号を確定
全ての信号を記録すると、下図の状態になります。そうしたら、Save ボタンをクリックします。最初に設定した場所に、記録された信号を全てまとめたファイル TV.txt が保存されます。 実際に PC 上に保存された TV.txt を見つけ、ダブルクリックして開いてみましょう。Windows のメモ帳で開いた場合、下図のような見た目になります。数字の羅列が表示されるのを確認できれば閉じてOKです。
なお、このデータは改行のない長い1行として保存されています。画面右端でデータが折り返されて表示されているので、下図のような見た目になっているのです。
画面右端での折り返しが行われないテキストエディタで TV.txt を開いた場合、データの先頭しか表示されないことがありますが、気にする必要はありません。 さて、この TV.txt に記録されたリモコン信号のデータを利用し、今度はリモコンの信号の送信をテストしてみましょう。
ここで用いたデモアプリケーションを引き続き用います。
5.3 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を送信する
ここからは、先ほど記録したテレビのリモコンの信号を、赤外線LEDを通してテレビに送信してみます。「5.1 Pico 上に RemotePy をセットアップする」でセットアップした Pico がその PC に USB 接続されており、その上の main.py が実行されていることが必要です。
さらに、「5.2 RemotePy とPC 上のデモアプリケーションを用いてテレビのリモコンの信号を記録する」で実行したように、
「PC 上のデモアプリケーション」上にすべての信号が記録されていることが必要です。
5.2 からそのまま実行を続ける方は既にその状態が実現されています。
一方、「PC 上のデモアプリケーション」を一度終了してしまっている方は、記録した信号を TV.txt から読み込む必要があります。その手順は下図に示されています。
Select ボタンで保存済みの TV.txt を読み込んで Open ボタンをクリックすると、保存した信号のリストが(アルファベット順にソートされた状態で)Key list に現れる、というわけです。 さて、Key list にすべての信号のリストが存在する状態を実現したら、次にブレッドボート上に下図の信号送信用の回路を構成する必要があります。小さなブレッドボードでも実現可能なよう、ちょっと窮屈な配置となっておりますのでご注意ください。
回路上で「VBUS(5V)」に対する接続は、間違うことのないよう慎重に行ってください。回路上で 5V で動作するのはこの部分のみで、他は 3.3V で動作しているためです。 さて、以上の準備ができたら、赤外線LEDの先端部をテレビのリモコン受光面に向け、信号を送信してみましょう。
PC上のでもアプリケーションの Key list にある信号から、送信したい信号の名称をクリックし、Send ボタンをクリックするのです。
まず最初に試すべきは電源ボタンに相当する power でしょうか。 power の動作確認ができたら、他の信号も試してみましょう。それぞれの信号の送信でテレビの状態が変化することを確認できたら、ここでの動作確認は終わりです。
5.4 RemotePy と自作プログラムを組み合わせ、タクトスイッチでテレビのリモコンの信号を送信する
さて、ここまでは、Pico上のプログラムとPC上のデモアプリケーションは全て RemotePy が提供するファイルを用いてきました。それらを用いて、リモコンの信号の記録と送信を行えるということを確認しました。
ここからは、下記の方針で演習を続けます。書籍でいうと 5.4 章の内容の Pico 版を実現することになります。
- PC 上のアプリケーションは不要
- Pico 上の main.py のみ、自作プログラムに変更し、「タクトスイッチでのリモコン操作」および「ブラウザ経由でのリモコン操作」を実現する
そのためには、先ほど保存した TV.txt を Pico 上に保存する必要があります。すなわち、MicroPython (Raspberry Pi Pico) を指定した Thonny で下図の状態を実現するということです。 TV.txt を Pico 上に保存するには、まず MicroPython (Raspberry Pi Pico) を指定した Thonny のメニューから「ファイル」→「ファイルを開く」を選択し、「このコンピュータ」から TV.txt を開く必要があります。
その際、ファイル選択画面の下図の部分を「all files (*.*)」に変更しないと、TV.txt がファイルの選択肢に現れませんのでご注意ください。
さらに、Thonny では画面右端での折り返しが行われませんから、開いた TV.txt は 1 行目の先頭部分しか表示されませんが、気にする必要はありません。 TV.txt を開いたら、Thonny のメニューから「ファイル」→「名前を付けて保存」を選択し、Raspberry Pi Pico 上に TV.txt という名称で保存するのでした。
また、Pico 上の main.py は以下のプログラムで置き換えます。このプログラムは書籍の bb2-05-01-TV.py に相当し、タクトスイッチで power、cup、cdown、vup、vdown の 5 つの信号の送信を実現します。
from machine import Pin from time import sleep, ticks_ms import json from UpyIrTx import UpyIrTx tx_pin = Pin(19, Pin.OUT) tx = UpyIrTx(0, tx_pin, 38000, 30, 1) with open('TV.txt') as f: commands = json.load(f) def switch_pressed(p): global prev_time pressed_time = ticks_ms() if pressed_time < prev_time + 200 : return prev_time = pressed_time pin_id = int(str(p)[8:11].rstrip(",")) # expecting "Pin(GPIOXX, mode=IN, pull=PULL_UP)" if pin_id == 15: tx.send(commands['power']['signal']) elif pin_id == 14: tx.send(commands['cup']['signal']) elif pin_id == 13: tx.send(commands['cdown']['signal']) elif pin_id == 12: tx.send(commands['vup']['signal']) elif pin_id == 11: tx.send(commands['vdown']['signal']) prev_time = ticks_ms() pin_power = Pin(15, Pin.IN, Pin.PULL_UP) pin_power.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) pin_cup = Pin(14, Pin.IN, Pin.PULL_UP) pin_cup.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) pin_cdown = Pin(13, Pin.IN, Pin.PULL_UP) pin_cdown.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) pin_vup = Pin(12, Pin.IN, Pin.PULL_UP) pin_vup.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed) pin_vdown = Pin(11, Pin.IN, Pin.PULL_UP) pin_vdown.irq(trigger=Pin.IRQ_FALLING, handler=switch_pressed)このプログラムを動かすための回路は下図になります。この回路は大きなブレッドボードでしか実現できませんのでご注意ください。赤外線 LED 部はデモアプリケーションで用いた回路と同じです。 回路が組めたら、Pico 上の main.py を実行し、ブレッドボート上の赤外線 LED をテレビの受光面に向け、タクトスイッチを押してみましょう。
タクトスイッチは、左から電源(power)、チャンネルアップ(cup)、チャンネルダウン(cdown)、ボリュームアップ(vup)、ボリュームダウン(vdown)に相当します。
テレビの状態が変化したら、この演習は成功です。
5.5 RemotePy と自作プログラムを組み合わせ、ブラウザでテレビのリモコンの信号を送信する
5章の演習の最後に、ブラウザでテレビのリモコンの信号を送信する演習を行ってみましょう。書籍でいえば 5.4.3 章の内容です。先ほどのタクトスイッチの例と同様、Pico 上には UpyIrRx.py、UpyIrTx.py、TV.txt、main.py の 4 ファイルが存在する必要があります。そして、main.py の内容は下記のプログラムで置き換えます。
import sys import network import socket from time import sleep from machine import Pin, I2C import json from UpyIrTx import UpyIrTx i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) i2c.scan() tx_pin = Pin(19, Pin.OUT) tx = UpyIrTx(0, tx_pin, 38000, 30, 1) with open('TV.txt') as f: commands = json.load(f) ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' ##### for LCD def setup_st7032(): c_lower = (contrast & 0xf) c_upper = (contrast & 0x30)>>4 i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c])) sleep(0.2) i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01])) sleep(0.001) def clear(): global position global line position = 0 line = 0 i2c.writeto_mem(address_st7032, register_setting, bytes([0x01])) sleep(0.001) def newline(): global position global line if line == display_lines-1: clear() else: line += 1 position = chars_per_line*line i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0])) sleep(0.001) def write_string(s): for c in list(s): write_char(ord(c)) def write_char(c): global position byte_data = check_writable(c) if position == display_chars: clear() elif position == chars_per_line*(line+1): newline() i2c.writeto_mem(address_st7032, register_display, bytes([byte_data])) position += 1 def check_writable(c): if c >= 0x06 and c <= 0xff : return c else: return 0x20 # 空白文字 address_st7032 = 0x3e register_setting = 0x00 register_display = 0x40 contrast = 32 # 0から63のコントラスト。30から40程度を推奨 chars_per_line = 8 # LCDの横方向の文字数 display_lines = 2 # LCDの行数 display_chars = chars_per_line*display_lines position = 0 line = 0 setup_st7032() ##### end of LCD wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) html = """<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no"> <title>テレビのリモコン</title> <style>button { width: 7em; height: auto; background: #003366; font-weight: normal; font-size: 14pt; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #ffffff; padding: 10px 20px; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; border: 1px solid #003366; -moz-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); -webkit-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); text-shadow: 0px -1px 0px rgba(000,000,000,0.7), 0px 1px 0px rgba(255,255,255,0.3); } button:active { position:relative; top:1px; } </style></head> <body> <div align="center"> <table border="0"> <tr> <td> <form><button name="input" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">入力切替</button></form> </td> <td> </td> <td> <form><button name="power" value="pressed" type="submit" style="background: rgb(249 25 25); border: rgb(249 25 25);">電源</button></form> </td> </tr> <tr> <td> <form><button name="ch1" value="pressed" type="submit">1</button></form> </td> <td> <form><button name="ch2" value="pressed" type="submit">2</button></form> </td> <td> <form><button name="ch3" value="pressed" type="submit">3</button></form> </td> </tr> <tr> <td> <form><button name="ch4" value="pressed" type="submit">4</button></form> </td> <td> <form><button name="ch5" value="pressed" type="submit">5</button></form> </td> <td> <form><button name="ch6" value="pressed" type="submit">6</button></form> </td> </tr> <tr> <td> <form><button name="ch7" value="pressed" type="submit">7</button></form> </td> <td> <form><button name="ch8" value="pressed" type="submit">8</button></form> </td> <td> <form><button name="ch9" value="pressed" type="submit">9</button></form> </td> </tr> <tr> <td> <form><button name="ch10" value="pressed" type="submit">10</button></form> </td> <td> <form><button name="ch11" value="pressed" type="submit">11</button></form> </td> <td> <form><button name="ch12" value="pressed" type="submit">12</button></form> </td> </tr> <tr> <td> <div align="center"> 音量 </div> </td> <td> </td> <td> <div align="center"> チャンネル </div> </td> </tr> <tr> <td> <form><button name="vup" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↑</button></form> </td> <td> </td> <td> <form><button name="cup" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↑</button></form> </td> </tr> <tr> <td> <form><button name="vdown" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↓</button></form> </td> <td> </td> <td> <form><button name="cdown" value="pressed" type="submit" style="background: rgb(64 64 64); border: rgb(64 64 64);">↓</button></form> </td> </tr> </table> </div> </body> </html> """ # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') write_string('waiting for conn') #showing on LCD sleep(1) # Handle connection error if wlan.status() != 3: write_string('conn. failed') # showing on LCD raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) write_string(status[0]) #showing on LCD # Open socket addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind(addr) except OSError as e: print(e) s.close() s = None write_string('socket error') #showing on LCD sys.exit() s.listen(1) print('listening on', addr) # Listen for connections, serve client while True: try: cl, addr = s.accept() #print('client connected from', addr) request = cl.recv(1024) #print("request:") #print(request) request = str(request) power_pressed = request.find('power=pressed') input_pressed = request.find('input=pressed') ch1_pressed = request.find('ch1=pressed') ch2_pressed = request.find('ch2=pressed') ch3_pressed = request.find('ch3=pressed') ch4_pressed = request.find('ch4=pressed') ch5_pressed = request.find('ch5=pressed') ch6_pressed = request.find('ch6=pressed') ch7_pressed = request.find('ch7=pressed') ch8_pressed = request.find('ch8=pressed') ch9_pressed = request.find('ch9=pressed') ch10_pressed = request.find('ch10=pressed') ch11_pressed = request.find('ch11=pressed') ch12_pressed = request.find('ch12=pressed') vup_pressed = request.find('vup=pressed') cup_pressed = request.find('cup=pressed') vdown_pressed = request.find('vdown=pressed') cdown_pressed = request.find('cdown=pressed') if power_pressed == 8: tx.send(commands['power']['signal']) elif input_pressed == 8: tx.send(commands['input']['signal']) elif ch1_pressed == 8: tx.send(commands['ch1']['signal']) elif ch2_pressed == 8: tx.send(commands['ch2']['signal']) elif ch3_pressed == 8: tx.send(commands['ch3']['signal']) elif ch4_pressed == 8: tx.send(commands['ch4']['signal']) elif ch5_pressed == 8: tx.send(commands['ch5']['signal']) elif ch6_pressed == 8: tx.send(commands['ch6']['signal']) elif ch7_pressed == 8: tx.send(commands['ch7']['signal']) elif ch8_pressed == 8: tx.send(commands['ch8']['signal']) elif ch9_pressed == 8: tx.send(commands['ch9']['signal']) elif ch10_pressed == 8: tx.send(commands['ch10']['signal']) elif ch11_pressed == 8: tx.send(commands['ch11']['signal']) elif ch12_pressed == 8: tx.send(commands['ch12']['signal']) elif vup_pressed == 8: tx.send(commands['vup']['signal']) elif cup_pressed == 8: tx.send(commands['cup']['signal']) elif vdown_pressed == 8: tx.send(commands['vdown']['signal']) elif cdown_pressed == 8: tx.send(commands['cdown']['signal']) response = html cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') cl.send(response) cl.close() except OSError as e: print('connection closed') except KeyboardInterrupt as e: break s.close() s = Noneこのプログラムを動かすための回路は次の通りです。 見てわかる通り、赤外線 LED 以外に I2C 接続の小型 LCD も接続されています。これは、Pico W / Pico 2 W に割り当てられた IP アドレスやエラーメッセージを表示するためのものです。この LCD を接続しなくても演習は実行可能ですが、その場合、Pico W / Pico 2 W からのメッセージを Windows などの PC 上で見るしかなくなります。LCD を接続すること、すなわち Windows などの PC なしでも Pico W / Pico 2 W を動作させられるようにすることを強く推奨します。
上記のプログラムを実行するための注意をいくつか記します。下記のように、用いている Wifi の SSID (アクセスポイント名) とそのパスワードを記す項目があります。 ここを皆さんの環境における SSID とパスワードに変更しないとこのプログラムは動作しませんのでご注意ください。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'また、回路を LCD なしで実行する場合、プログラム中の下記の部分を消し、
##### for LCD (省略) ##### End of LCD下記の内容で差し替える必要があります。
def write_string(s): passさらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。
記述できたら Pico W / Pico 2 W でプログラムを実行してみましょう。LCD 上に(または Thonny のコンソール上に)Pico W に割り当てられた IP アドレス(例えば、 192.168.1.40 のようなもの)が表示されれば、実行が成功した可能性が高いでしょう。下図のような状態です。 そうしたら、Pico W / Pico 2 W と同じ Wifi ネットワークに接続した PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のような画面が現れます。 これは書籍図5-16とほぼ同じ見た目ですね。ブレッドボード上の赤外線をテレビの受光面に向けた状態で、ブラウザ上の電源ボタンなどを押して、リモコンの信号を送信してみましょう。
送信した信号通りにテレビが動作したらこの演習は成功です。
8章 6脚ロボットを操作してみよう
本ページの最後に、本書 8 章の 6 脚ロボットを Pico で制御してみましょう。Pico と同じ Wifi ネットワークに接続した PC またはスマートフォンのブラウザで操作しますので、Pico W または Pico 2 W が必須です。
また、Pico ではカメラの動画を取り扱えませんので、書籍の 8.4 までの内容が対象となります。
通常の Raspberry Pi と比べると、サーボドライバが不要なのでがシンプルになること、システムの自動起動が容易であること、電源投入から動き出すまでの時間が短いことなど、メリットが大きいです。
デメリットとしては、複雑な操作ページを作りにくいことが挙げられますが、6 脚ロボットでは前進、後退、右回転、左回転、停止の 5 つの動作しかありませんので、このデメリットはあまり感じません。
それではやっていきましょう。
まずは、作成すべき回路です。この回路は、6 脚ロボットが完成した後のもので、書籍でいえば、図8-13 や図8-14 に対応します。 作成した回路を搭載した6脚ロボットが下図に示されています。 この6脚ロボットを制御するために必要なプログラムを以下に示していきましょう。
まず、6 個のサーボモーターを角度 0 に固定するプログラムは下記です。このプログラムは書籍では bb2-08-01-zero-pca9685.py に対応します。
このプログラムではまだ Wifi を利用しません。6 個のサーボモーターがニュートラルである 0 度に移動して固定されるだけです。
from machine import Pin, PWM NEUTRAL = 0.0675 # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均 NEUTRAL = int(65535*NEUTRAL) pwm0 = PWM(Pin(16)) pwm1 = PWM(Pin(17)) pwm2 = PWM(Pin(18)) pwm3 = PWM(Pin(19)) pwm4 = PWM(Pin(20)) pwm5 = PWM(Pin(21)) pwm0.freq(50) pwm1.freq(50) pwm2.freq(50) pwm3.freq(50) pwm4.freq(50) pwm5.freq(50) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL)次に、6 脚ロボットが前進、後退、右回転、左回転の動作をそれぞれ一度ずつ実行し、最後に停止するプログラムが下記のものです。書籍では bb2-08-02-6legs-pca9685.py に対応します。
ただし、書籍では While ループにより4つの動作を繰り返し実行し続けましたが、このプログラムでは一度ずつの実行後に停止するようにしています。動作確認なので、一度ずつで十分だろうと考えたためです。
このプログラムも Wifi 接続は行いません。
from machine import Pin, PWM from time import sleep def forwardPhase1(delay): pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def forwardPhase2(delay): pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def forwardMove(delay): forwardPhase1(delay) forwardPhase2(delay) def backwardPhase1(delay): pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def backwardPhase2(delay): pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def backwardMove(delay): backwardPhase1(delay) backwardPhase2(delay) def rotateRightPhase1(delay): pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def rotateRightPhase2(delay): pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def rotateRightMove(delay): rotateRightPhase1(delay) rotateRightPhase2(delay) def rotateLeftPhase1(delay): pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def rotateLeftPhase2(delay): pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def rotateLeftMove(delay): rotateLeftPhase1(delay) rotateLeftPhase2(delay) def stopMove(delay): pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) NEUTRAL = 0.0675 # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均 LEG_F_R = NEUTRAL + 0.024 LEG_F_L = NEUTRAL - 0.024 LEG_B_R = NEUTRAL - 0.024 LEG_B_L = NEUTRAL + 0.024 NEUTRAL = int(65535*NEUTRAL) LEG_F_R = int(65535*LEG_F_R) LEG_F_L = int(65535*LEG_F_L) LEG_B_R = int(65535*LEG_B_R) LEG_B_L = int(65535*LEG_B_L) DELAY = 0.15 pwm0 = PWM(Pin(16)) pwm1 = PWM(Pin(17)) pwm2 = PWM(Pin(18)) pwm3 = PWM(Pin(19)) pwm4 = PWM(Pin(20)) pwm5 = PWM(Pin(21)) pwm0.freq(50) pwm1.freq(50) pwm2.freq(50) pwm3.freq(50) pwm4.freq(50) pwm5.freq(50) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) forwardMove(DELAY) backwardMove(DELAY) rotateRightMove(DELAY) rotateLeftMove(DELAY) stopMove(DELAY)そして、最後に、ブラウザで 6 脚ロボットを操作するためのプログラムが下記です。Pico 上に main.py として保存して実行するためのものです。
from machine import Pin, PWM, I2C from time import sleep import network import socket import sys import _thread ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD' ##### for LCD def setup_st7032(): c_lower = (contrast & 0xf) c_upper = (contrast & 0x30)>>4 i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x39, 0x14, 0x70|c_lower, 0x54|c_upper, 0x6c])) sleep(0.2) i2c.writeto_mem(address_st7032, register_setting, bytes([0x38, 0x0d, 0x01])) sleep(0.001) def clear(): global position global line position = 0 line = 0 i2c.writeto_mem(address_st7032, register_setting, bytes([0x01])) sleep(0.001) def newline(): global position global line if line == display_lines-1: clear() else: line += 1 position = chars_per_line*line i2c.writeto_mem(address_st7032, register_setting, bytes([0xc0])) sleep(0.001) def write_string(s): for c in list(s): write_char(ord(c)) def write_char(c): global position byte_data = check_writable(c) if position == display_chars: clear() elif position == chars_per_line*(line+1): newline() i2c.writeto_mem(address_st7032, register_display, bytes([byte_data])) position += 1 def check_writable(c): if c >= 0x06 and c <= 0xff : return c else: return 0x20 # 空白文字 i2c = I2C(0, scl=Pin(5), sda=Pin(4), freq=400000) i2c.scan() address_st7032 = 0x3e register_setting = 0x00 register_display = 0x40 contrast = 32 # 0から63のコントラスト。30から40程度を推奨 chars_per_line = 8 # LCDの横方向の文字数 display_lines = 2 # LCDの行数 display_chars = chars_per_line*display_lines position = 0 line = 0 setup_st7032() ##### end of LCD def forwardPhase1(delay): pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def forwardPhase2(delay): pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def forwardMove(delay): forwardPhase1(delay) forwardPhase2(delay) def backwardPhase1(delay): pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def backwardPhase2(delay): pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def backwardMove(delay): backwardPhase1(delay) backwardPhase2(delay) def rotateRightPhase1(delay): pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def rotateRightPhase2(delay): pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def rotateRightMove(delay): rotateRightPhase1(delay) rotateRightPhase2(delay) def rotateLeftPhase1(delay): pwm0.duty_u16(LEG_F_R) pwm1.duty_u16(LEG_F_R) pwm2.duty_u16(LEG_B_L) sleep(delay) pwm3.duty_u16(LEG_B_R) pwm4.duty_u16(LEG_F_L) pwm5.duty_u16(LEG_F_L) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def rotateLeftPhase2(delay): pwm3.duty_u16(LEG_F_R) pwm4.duty_u16(LEG_B_L) pwm5.duty_u16(LEG_B_L) sleep(delay) pwm0.duty_u16(LEG_B_R) pwm1.duty_u16(LEG_B_R) pwm2.duty_u16(LEG_F_L) sleep(delay) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) sleep(delay) def rotateLeftMove(delay): rotateLeftPhase1(delay) rotateLeftPhase2(delay) def stopMove(delay): pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) sleep(delay) def movingThread(): while True: if movingStatus == MOVE_FORWARD: forwardMove(DELAY) elif movingStatus == MOVE_BACKWARD: backwardMove(DELAY) elif movingStatus == ROTATE_RIGHT: rotateRightMove(DELAY) elif movingStatus == ROTATE_LEFT: rotateLeftMove(DELAY) elif movingStatus == MOVE_STOP: stopMove(DELAY) return wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) html = """<!DOCTYPE html><html> <head><meta charset="utf-8"> <meta name="viewport" content="width=device-width, user-scalable=no"> <title>六脚ロボットコントローラー</title> <style>button { width: auto; height: auto; background: #003366; font-weight: normal; font-size: 14pt; font-family: Arial, Helvetica, sans-serif; font-size: 14px; color: #ffffff; padding: 10px 20px; -moz-border-radius: 10px; -webkit-border-radius: 10px; border-radius: 10px; border: 1px solid #003366; -moz-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); -webkit-box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); box-shadow: 0px 1px 3px rgba(000,000,000,0.5), inset 0px 0px 1px rgba(255,255,255,0.5); text-shadow: 0px -1px 0px rgba(000,000,000,0.7), 0px 1px 0px rgba(255,255,255,0.3); } </style></head> <p>押したボタンにより、六脚ロボットが動作します。中央のボタンをクリックしないと六脚ロボットは止まりません。</p> <div align="center"> <table border="0"> <tr align="center"> <td></td> <td><form><button name="forward" value="on" type="submit" style="background: %s;"> ↑ </button></form></td> <td></td> </tr> <tr align="center"> <td><form><button name="rot_l" value="on" type="submit" style="background: %s;"> ← </button></form></td> <td><form><button name="stop" value="on" type="submit" style="background: %s;"> 〇 </button></form></td> <td><form><button name="rot_r" value="on" type="submit" style="background: %s;"> → </button></form></td> </tr> <tr align="center"> <td></td> <td><form><button name="backward" value="on" type="submit" style="background: %s;"> ↓ </button></form></td> <td></td> </tr> </table> <br /><br /> </div> </body></html> """ # Wait for connect or fail max_wait = 20 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') write_string('waiting for conn') #showing on LCD sleep(1) # Handle connection error if wlan.status() != 3: write_string('conn. failed') # showing on LCD raise RuntimeError('network connection failed') else: print('Connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) write_string(status[0]) #showing on LCD # Open socket addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) try: s.bind(addr) except OSError as e: print(e) s.close() s = None write_string('socket error') #showing on LCD sys.exit() s.listen(1) print('listening on', addr) NEUTRAL = 0.0675 # (0.035 + 0.1)/2 # 最小デューティ比3.5%と最大デューティ比10%の平均 LEG_F_R = NEUTRAL + 0.024 LEG_F_L = NEUTRAL - 0.024 LEG_B_R = NEUTRAL - 0.024 LEG_B_L = NEUTRAL + 0.024 NEUTRAL = int(65535*NEUTRAL) LEG_F_R = int(65535*LEG_F_R) LEG_F_L = int(65535*LEG_F_L) LEG_B_R = int(65535*LEG_B_R) LEG_B_L = int(65535*LEG_B_L) DELAY = 0.15 pwm0 = PWM(Pin(16)) pwm1 = PWM(Pin(17)) pwm2 = PWM(Pin(18)) pwm3 = PWM(Pin(19)) pwm4 = PWM(Pin(20)) pwm5 = PWM(Pin(21)) pwm0.freq(50) pwm1.freq(50) pwm2.freq(50) pwm3.freq(50) pwm4.freq(50) pwm5.freq(50) pwm0.duty_u16(NEUTRAL) pwm1.duty_u16(NEUTRAL) pwm2.duty_u16(NEUTRAL) pwm3.duty_u16(NEUTRAL) pwm4.duty_u16(NEUTRAL) pwm5.duty_u16(NEUTRAL) MOVE_FORWARD = 0 MOVE_BACKWARD = 1 ROTATE_RIGHT = 2 ROTATE_LEFT = 3 MOVE_STOP = 4 onCol = '#26a1ff' offCol = '#003366' move_strs = (offCol, offCol, onCol, offCol, offCol) isMoving = False movingStatus = MOVE_STOP # Listen for connections, serve client while True: try: cl, addr = s.accept() #print('client connected from', addr) request = cl.recv(1024) #print("request:") #print(request) request = str(request) forward_on = request.find('forward=on') rot_l_on = request.find('rot_l=on') stop_on = request.find('stop=on') rot_r_on = request.find('rot_r=on') backward_on = request.find('backward=on') if forward_on == 8: move_strs = (onCol, offCol, offCol, offCol, offCol) movingStatus = MOVE_FORWARD if rot_l_on == 8: move_strs = (offCol, onCol, offCol, offCol, offCol) movingStatus = ROTATE_LEFT if stop_on == 8: move_strs = (offCol, offCol, onCol, offCol, offCol) movingStatus = MOVE_STOP isMoving = False if rot_r_on == 8: move_strs = (offCol, offCol, offCol, onCol, offCol) movingStatus = ROTATE_RIGHT if backward_on == 8: move_strs = (offCol, offCol, offCol, offCol, onCol) movingStatus = MOVE_BACKWARD if ((movingStatus == MOVE_FORWARD or movingStatus == MOVE_BACKWARD or movingStatus == ROTATE_LEFT or movingStatus == ROTATE_RIGHT) and isMoving == False): isMoving = True _thread.start_new_thread(movingThread, ()) response = html % move_strs cl.send('HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n') cl.send(response) cl.close() except OSError as e: print('connection closed') except KeyboardInterrupt as e: break s.close() s = None上記のプログラムを実行するための注意をいくつか記します。下記のように、用いている Wifi の SSID (アクセスポイント名) とそのパスワードを記す項目があります。 ここを皆さんの環境における SSID とパスワードに変更しないとこのプログラムは動作しませんのでご注意ください。なお、2.4 GHz の周波数帯の Wifi アクセスポイントにしか接続できませんのでご注意ください。
ssid = 'YOUR_WIFI_SSID' password = 'YOUR_WIFI_PASSWORD'また、回路を LCD なしで実行する場合、プログラム中の下記の部分を消し、
##### for LCD (省略) ##### End of LCD下記の内容で差し替える必要があります。
def write_string(s): passさらに、プログラムの書き換え時は、Pico W / Pico 2 W 上で動作しているプログラムを Thonny の「STOP (停止)」ボタンから一旦停止する必要があることもご注意ください。
記述できたら Pico W / Pico 2 W でプログラムを実行してみましょう。成功すれば、LCD 上に(または Thonny のコンソール上に)Pico W に割り当てられた IP アドレス(例えば、 192.168.1.40 のようなもの)が表示されます。
そうしたら、Pico W / Pico 2 W と同じ Wifi ネットワークに接続した PC やスマートフォンのブラウザでアドレス(例えば 192.168.1.40 の場合、 http://192.168.1.40/ )にアクセスしてみましょう。下図のような画面が現れます。 前進、後退、右回転、左回転に相当するボタンがあり、その中央に静止ボタンがあります。ボタンを押して、6脚ロボットが動作するか、確認してみましょう。
細かな話ですが、ブラウザからの指令の待ち受けを一つ目の CPU コアで、6脚ロボットの制御を二つ目の CPU コアで実現しています。
おわりに
いかがでしたか?作業を開始したときはまさかここまで巨大なページになるとは思いませんでした。
大変な作業ではありましたが、Pico を使う機会が増えるほど、Pico シリーズに対する愛着が増していくのを実感します。
これまでマイコンを使わなければいけない機会があると、ただ単に習慣から Arduino を選ぶケースが多かったのですが、これからは Pico に乗り換えても良いかな?という気持ちになりつつあります。
みなさんも是非 Pico を体験してみてください!
0 件のコメント:
コメントを投稿