CircuitPythonの便利なコードサンプル集
todbotさんによるCircuitPythonのトリック集です。大変有用な内容なのでリポジトリをforkさせていただき日本語化しました。
元のリポジトリはこちら
CircuitPythonのトリック集
todbotさんがCircuitPythonのプログラミングの中で見つけられたコツとトリックを集めたものです。
入力
ボタンのデジタル入力を読み取る
import board
from digitalio import DigitalInOut, Pull
button = DigitalInOut(board.D3) # デフォルトでは入力
button.pull = Pull.UP # 内部のプルアップ抵抗を有効化する
print(button.value) # False == ボタンが押された
ポテンショメータを読み取る
import board
import analogio
potknob = analogio.AnalogIn(board.A1)
position = potknob.value # 0-65535の範囲の値
pos = potknob.value // 256 # 0-255の範囲にする
静電容量タッチピンを読み取る
import touchio
import board
touch_pin = touchio.TouchIn(board.GP6)
# Raspberry Pi Pico / RP2040の場合、1MΩのプルダウン抵抗が各入力に必要
if touch_pin.value:
print("touched!")
ロータリーエンコーダを読み取る
import board
import rotaryio
encoder = rotaryio.IncrementalEncoder(board.GP0, board.GP1) # Picoの場合、GP0、GP1のように連続したピンである必要がある
print(encoder.position) # ゼロからスタートして、マイナスまたはプラスの値が表示される
ピンやボタンのデバウンス
import board
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
button_in = DigitalInOut(board.D3) # defaults to input
button_in.pull = Pull.UP # turn on internal pull-up resistor
button = Debouncer(button_in)
while True:
button.update()
if button.fell:
print("press!")
if button.rose:
print("release!")
複数のピンをリスト化してデバウンス
import board
from digitalio import DigitalInOut, Pull
from adafruit_debouncer import Debouncer
pins = (board.GP0, board.GP1, board.GP2, board.GP3, board.GP4)
buttons = [] # デバウンスしたいオブジェクトを格納するためのリスト
for pin in pins:
tmp_pin = DigitalInOut(pin) # defaults to input
tmp_pin.pull = Pull.UP # 内部のプルアップ抵抗を有効化する
buttons.append( Debouncer(tmp_pin) )
while True:
for i in range(len(buttons)):
buttons[i].update()
if buttons[i].fell:
print("button",i,"pressed!")
if buttons[i].rose:
print("button",i,"released!")
出力
ピンにHIGH/LOWを出力 (例: LEDのオン・オフ)
import board
import digitalio
ledpin = digitalio.DigitalInOut(board.D2)
ledpin.direction = digitalio.Direction.OUTPUT
ledpin.value = True
訳注:上記の例はHIGHを出力する例です。ledpin.value = FalseでLOWにすることができます。
DACピンにアナログ値を出力
import board
import analogio
dac = analogio.AnalogOut(board.A0) # Trinket M0とQT Pyの場合A0
dac.value = 32768 # 0-65535の範囲の中央の値
訳注:AnalogOutで指定するピン番号はボードによって異なる場合があるため、ピンアサインを確認ください。
PWMピンにアナログ値を出力
import board
import pwmio
out1 = pwmio.PWMOut(board.MOSI, frequency=25000, duty_cycle=0)
out1.duty_cycle = 32768 # 32768は、0-65535の範囲の中央の値 = 50 % デューティサイクル
Neopixelを制御
import neopixel
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.2)
led[0] = 0xff00ff
led[0] = (255,0,255) # 0xff00ffを指定したのと同じ
訳注:LEDの色の指定は0xff00ffのようにRGBそれぞれ2バイトずつをつなげた16進数で指定することもできますし、(255, 0, 255)のようにRGBのを0-255の範囲の整数で表したタプルを使っても指定することができます。
Neopixel(WS2812B) / Dotstar(APA102)
マイコンに搭載されているNeoPixelを虹色に変化させる
_pixelbuf または adafruit_pypixelbufの一部である組み込みの colorwheel()関数を使用します。
この関数は、0~255の色相を指定して、(R,G,B)のタプルを返します。以下にその使い方を示します。
このコードは、neopixelの代わりにadafruit_dotstarでも動作します
import time
import board
import neopixel
led = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4)
while True:
led.fill( neopixel._pixelbuf.colorwheel((time.monotonic()*50)%255) )
time.sleep(0.05)
訳注:time.monotonic()はボードが起動(またはリセット)してからの経過時間を表します。その値を50倍して255で割った余りを計算することで、0-255の範囲で常に変化する値を作り出しています。
LEDテープに虹のグラデーションを表示
デモは こちら.
import time, random
import board, neopixel
num_leds = 16
leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
delta_hue = 256//num_leds
speed = 10 # 数字が大きい = 色の変化が速い
i=0
while True:
for l in range(len(leds)):
leds[l] = neopixel._pixelbuf.colorwheel( int(i*speed + l * delta_hue) % 255 )
leds.show() # 全部のLEDの値を変更したあとに更新をかける
i = (i+1) % 255
time.sleep(0.05)
LEDテープに流れ星のエフェクトを表示
import time, random
import board, neopixel
num_leds = 16
leds = neopixel.NeoPixel(board.D2, num_leds, brightness=0.4, auto_write=False )
my_color = (55,200,230)
dim_by = 20 # 数字が大きい = 流れ星の尾が短い
pos = 0
while True:
leds[pos] = my_color
leds[0:] = [[max(i-dim_by,0) for i in l] for l in leds] # dim all by (dim_by,dim_by,dim_by)
pos = (pos+1) % num_leds # 次の位置に移動する
leds.show() # 全部のLEDの値を変更したあとに更新をかける
time.sleep(0.05)
USB
USBが接続されているかを検出
def is_usb_connected():
import storage
try:
storage.remount('/', readonly=False) # attempt to mount readwrite
storage.remount('/', readonly=True) # attempt to mount readonly
except RuntimeError as e:
return True
return False
is_usb = "USB" if is_usb_connected() else "NO USB"
print("USB:", is_usb)
CIRCUITPYのディスクサイズと空き容量を取得
import os
fs_stat = os.statvfs('/')
print("Disk size in MB", fs_stat[0] * fs_stat[2] / 1024 / 1024)
print("Free space in MB", fs_stat[0] * fs_stat[3] / 1024 / 1024)
コードからUF2 bootloaderをリセット
import micrcocontroller
microcontroller.on_next_reset(microcontroller.RunMode.BOOTLOADER)
microcontroller.reset()
USBシリアル
USBシリアルに表示
print("hello there") # hello there表示後自動的に改行される
print("waiting...", end='') # end=''とすることで改行しなくなる
USBシリアルから入力を受け付ける(ブロッキング)
while True:
print("Type something: ", end='')
my_str = input() # キー入力をしてENTERを押す
print("You entered: ", my_str)
USBシリアルから入力を受け付ける(ほぼノンブロッキング)
import time
import supervisor
print("Type something when you're ready")
last_time = time.monotonic()
while True:
if supervisor.runtime.serial_bytes_available:
my_str = input()
print("You entered:", my_str)
if time.monotonic() - last_time > 1: # 1秒毎に表示
last_time = time.monotonic()
print(int(last_time),"waiting...")
計算タスク
テキストをフォーマットする
name = "John"
fav_color = 0x003366
body_temp = 98.65
print("name:%s color:%06x thermometer:%2.1f" % (name,fav_color,body_temp))
出力結果
'name:John color:ff3366 thermometer:98.6'
f文字列でテキストをフォーマットする
(QTPy M0のような小さなCircuitPythonでは機能しません)
name = "John"
fav_color = 0x003366
body_temp = 98.65
print(f"name:{name} color:{color:06x} thermometer:{body_temp:2.1f}")
出力結果
'name:John color:ff3366 thermometer:98.6'
configファイルを利用する
# my_config.py
config = {
"username": "Grogu Djarin",
"password": "ig88rules",
"secret_key": "3a3d9bfaf05835df69713c470427fe35"
}
# code.py
from my_config import config
print("secret:", config['secret_key'])
出力結果
'secret: 3a3d9bfaf05835df69713c470427fe35'
訳注:いつもコードを記述するcode.pyとは別ファイル(今回の例はmy_config.py)を作成しておきます。code.pyからmy_configをimportすることで、my_config内に記述した値(今回の例ではconfigという名前の辞書の値)を利用することができます。
より複雑なタスク
値のマッピング
# Arduino map()のようなシンプルなマッピング
def map_range(s, a, b):
(a1, a2), (b1, b2) = a, b
return b1 + ((s - a1) * (b2 - b1) / (a2 - a1))
# 0-0123の範囲の値を0.0-1.0の範囲にマッピングする
out = map_range( in, (0,1023), (0.0,1.0) )
時間の計測
import time
start_time = time.monotonic() # 電源投入時
do_something()
elapsed_time = time.monotonic() - start_time
print("do_something took %f seconds" % elapsed_time)
訳注:do_something()の箇所にコードを追記してください。そのコードを実行するためにかかった時間が表示されます。
Ctrl-Cを押してもコードが停止しないようにする
メインループ内に try / except KeyboardInterrupt を記述してCtrl-Cを押したことを検出します。
while True:
try:
print("Doing something important...")
time.sleep(0.1)
except KeyboardInterrupt:
print("Nice try, human! Not quitting.")
Ctrl-Cを押して、優雅に(LEDを消して、終了メッセージを表示してから)シャットダウンすることもできます。
import time, random
import board, neopixel, adafruit_pypixelbuf
leds = neopixel.NeoPixel(board.NEOPIXEL, 1, brightness=0.4 )
while True:
try:
rgb = adafruit_pypixelbuf.colorwheel(int(time.monotonic()*75) % 255)
leds.fill(rgb)
time.sleep(0.05)
except KeyboardInterrupt:
print("shutting down nicely...")
leds.fill(0)
break # while Trueのループから抜ける
Raspberry Pi Picoをセーフモードで起動できるようにする
他のRP2040搭載ボード(QTPy RP2040等)でも機能します。
# このコードをboot.pyとしてRaspberry Pi PicoのCIRCUITPY driveに保存しておく
# from https://gist.github.com/Neradoc/8056725be1c209475fd09ffc37c9fad4
# Picoがロックアップしてしまったときに便利
import board
import time
from digitalio import DigitalInOut,Pull
import time
led = DigitalInOut(board.LED)
led.switch_to_output()
safe = DigitalInOut(board.GP14)
safe.switch_to_input(Pull.UP)
def reset_on_pin():
if safe.value is False:
import microcontroller
microcontroller.on_next_reset(microcontroller.RunMode.SAFE_MODE)
microcontroller.reset()
led.value = False
for x in range(16):
reset_on_pin()
led.value = not led.value
time.sleep(0.1)
ネットワーク
WiFiをスキャンして信号強度順にSSIDを表示 (ESP32-S2)
networks = []
for network in wifi.radio.start_scanning_networks():
networks.append(network)
wifi.radio.stop_scanning_networks()
networks = sorted(networks, key=lambda net: net.rssi, reverse=True)
for network in networks:
print("ssid:",network.ssid, "rssi:",network.rssi)
IPアドレスにpingを送信 (ESP32-S2)
import time
import wifi
import ipaddress
from secrets import secrets
ip_to_ping = "1.1.1.1"
wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])
print("my IP addr:", wifi.radio.ipv4_address)
print("pinging ",ip_to_ping)
ip1 = ipaddress.ip_address(ip_to_ping)
while True:
print("ping:", wifi.radio.ping(ip1))
time.sleep(1)
JSONファイルを取得 (ESP32-S2)
import time
import wifi
import socketpool
import ssl
import adafruit_requests
from secrets import secrets
wifi.radio.connect(ssid=secrets['ssid'],password=secrets['password'])
print("my IP addr:", wifi.radio.ipv4_address)
pool = socketpool.SocketPool(wifi.radio)
session = adafruit_requests.Session(pool, ssl.create_default_context())
while True:
response = session.get("https://todbot.com/tst/randcolor.php")
data = response.json()
print("data:",data)
time.sleep(5)
secrets.pyってなに?
AdafruitのWiFiライブラリで使用するファイルです。
基本的なWiFi接続を行うような場合に使います。
# secrets.py
secrets = {
"ssid": "Pretty Fly for a WiFi",
"password": "donthackme123"
}
# code.py
from secrets import secrets
print("your WiFi password is:", secrets['password'])
I2C
I2Cバスをスキャンする
参照: https://learn.adafruit.com/circuitpython-essentials/circuitpython-i2c#find-your-sensor-2985153-11
import board
i2c = board.I2C() # or busio.I2C(pin_scl,pin_sda)
while not i2c.try_lock(): pass
print("I2C addresses found:", [hex(device_address)
for device_address in i2c.scan()])
i2c.unlock()
ボードの情報
空きメモリの容量を表示する
参照: https://learn.adafruit.com/welcome-to-circuitpython/frequently-asked-questions
import gc
print(gc.mem_free())
microcontroller.pinとboardのマッピング状況を表示
参照: https://gist.github.com/anecdata/1c345cb2d137776d76b97a5d5678dc97
import microcontroller
import board
for pin in dir(microcontroller.pin):
if isinstance(getattr(microcontroller.pin, pin), microcontroller.Pin):
print("".join(("microcontroller.pin.", pin, "\t")), end=" ")
for alias in dir(board):
if getattr(board, alias) is getattr(microcontroller.pin, pin):
print("".join(("", "board.", alias)), end=" ")
print()
約注:microcontrollerで取得できるピン番号はプロセッサ自体のピン番号で、boardのピン番号とは異なります。
ボード名の表示
import os
print(os.uname().machine)
出力結果
'Adafruit ItsyBitsy M4 Express with samd51g19'
ひとつのcode.pyで複数のボードに対応する
import os
board_type = os.uname().machine
if 'QT Py M0' in board_type:
tft_clk = board.SCK
tft_mosi = board.MOSI
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
elif 'ItsyBitsy M4' in board_type:
tft_clk = board.SCK
tft_mosi = board.MOSI
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
elif 'Pico' in board_type:
tft_clk = board.GP10 # must be a SPI CLK
tft_mosi= board.GP11 # must be a SPI TX
spi = busio.SPI(clock=tft_clk, MOSI=tft_mosi)
else:
print("supported board", board_type)
Hacks(REPIを使う)
コアモジュールとライブラリの一覧を表示
REPLでhelp(“modules”)と入力
Adafruit CircuitPython 6.2.0-beta.2 on 2021-02-11; Adafruit Trinket M0 with samd21e18
>>> help("modules")
__main__ digitalio pulseio supervisor
analogio gc pwmio sys
array math random time
board microcontroller rotaryio touchio
builtins micropython rtc usb_hid
busio neopixel_write storage usb_midi
collections os struct
Plus any modules on the filesystem
ワンライナー
(Pythonではセミコロンで命令をつなぐことができる)
# よく使われるライブラリをまとめてimportする
import time; import board; from digitalio import DigitalInOut,Pull; import analogio; import touchio
# ボードのピンとオブジェクトを表示 (例: 'I2C'、'display'など)
import board; dir(board)
# チップのピンを表示 (上記のimport boardで表示したピンとは違う)
import microcontroller; dir(microcontroller.pin)
# 内蔵ディスプレイのリリース
import displayio; displayio.release_displays()
# ボードに搭載されたNeoPixelを紫色にする
import board; import neopixel; leds = neopixel.NeoPixel(board.D3, 8, brightness=0.2); leds.fill(0xff00ff)
Python情報
コアモジュール以外のどんなライブラリがimportされているかを表示
import sys
print(sys.modules.keys())
# 'dict_keys([])'
import board
import neopixel
import adafruit_dotstar
print(sys.modules.keys())
prints "dict_keys(['neopixel', 'adafruit_dotstar'])"
グローバル変数の一覧を表示
a = 123
b = 'hello there'
my_globals = sorted(dir)
print(my_globals)
# prints "['__name__', 'a', 'b']"
if 'a' in my_globals:
print("you have a variable named 'a'!")
if 'c' in my_globals:
print("you have a variable named 'c'!")
ホスト側のタスク
CircuitPythonのライブラリをインストールする
以下はMacOS / Linuxの例です。 Windowsでも類似のコマンドを使用します。
circupでライブラリをインストールする
circupを使うと簡単にライブラリのインストールとアップデートができます。
$ pip3 install --user circup
$ circup install adafruit_midi
$ circup update # updates all modules
すべてのモジュールを最新版に更新 (例えばCircuitPython 6から7にアップデートする場合など)
(これが必要なのは、circup update が実際には確実に動作していないようだからです)
circup freeze > mymodules.txt
rm -rf /Volumes/CIRCUITPY/lib/*
circup install -r mymodules.txt
また、CircuitPythonの新しいバージョンが出たらcircupを更新します。
$ pip3 install --upgrade circup
cpコマンドでライブラリをコピーする
ライブラリを手動でインストールするには
CircuitPython Library Bundleまたは CircuitPython Community Bundle (circup 非対応)、からバンドルをダウンロードして解凍したあとcp -rXを実行してください。
cp -rX bundle_folder/lib/adafruit_midi /Volumes/CIRCUITPY/lib
注意: Trinkey, Trinket, QTPyのようなストレージ容量の限られたボードでは、MacOSの -x オプションを使用してスペースを節約する必要があります。
また、ライブラリの中で使用しないものを部分的に削除する必要があるかもしれません(例:MIDIノートを送信するだけなら、adafruit_midi/system_exclusiveは必要ありません)。