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は必要ありません)。

Follow me on Twitter