CircuitPythonでマイコン開発を始めよう!メモ帳だけで動かせる開発環境不要のPython環境とXIAO RP2350を使った実践ガイド - Lチカから本格的なIoTデバイス制作まで、初心者でも今すぐ始められる電子工作入門
みなさん、マイコンって聞くとなんだか難しそう...って思っちゃいません?Arduino IDEをインストールして、ライブラリを追加して、コンパイルして...って、もう聞いただけで頭が痛くなりそうですよね。
でもね、実はメモ帳一つでマイコンのプログラムが作れちゃう方法があるんです。それがCircuitPython!今回は、このCircuitPythonを使って、本当に開発環境なしでマイコンを動かす方法を、徹底的に解説していきます。
正直言って、私も最初は「えー、そんなうまい話あるわけないじゃん」って思ってました。でも実際にやってみたら、本当にメモ帳だけでLEDをピカピカ光らせることができちゃったんです。その時の感動といったら...!
この記事を読み終わる頃には、あなたもきっとCircuitPythonの虜になっているはず。一緒に、電子工作の新しい世界を探検していきましょう!
CircuitPythonの正体 - なぜメモ帳だけで動くのか
CircuitPythonって聞いたことありますか?これ、実はMicroPythonから派生した、マイコン専用のPython環境なんです。開発しているのはAdafruitという会社で、電子工作界隈では超有名な企業です。
何がすごいって、マイコンをPCに繋ぐと、USBメモリみたいに「CIRCUITPY」っていうドライブが現れるんです。そこにある「code.py」っていうファイルを編集して保存するだけで、プログラムが動き始めるんですよ。コンパイルも書き込みも必要なし!
私が初めてこれを体験した時は、「え、もう動いてる!?」って思わず声が出ちゃいました。だって、普通のArduinoだったら、コンパイルして、アップロードして...って手順を踏まないといけないじゃないですか。それが、ファイルを保存した瞬間に動き出すんですから。
なぜ今、CircuitPythonが熱いのか
実は最近、教育現場でCircuitPythonの採用が増えているんです。理由は簡単。環境構築でつまずく生徒が激減したから。
私も以前、高校生向けのマイコンワークショップを開催したことがあるんですが、Arduino IDEのインストールだけで1時間かかっちゃったんです。Windowsのドライバがうまく入らなかったり、Macのセキュリティ設定でひっかかったり...。
でもCircuitPythonなら、USBケーブルを繋いで、ファイルをコピペして、テキストエディタで編集するだけ。小学生でもできちゃいます。
それに、Pythonって今やプログラミング教育の定番言語ですよね。学校で習ったPythonの知識がそのまま電子工作に活かせるって、すごくないですか?
CircuitPythonとArduinoの違いを本音で語る
正直に言います。CircuitPythonは万能じゃありません。
処理速度で言えば、C++で書かれたArduinoのプログラムには勝てません。例えば、高速でLEDを点滅させたい時、Arduinoなら1マイクロ秒単位で制御できますが、CircuitPythonだと10マイクロ秒くらいが限界です。
ライブラリの充実度も、まだArduinoには及びません。特殊なセンサーや通信プロトコルを使いたい時、Arduinoなら大抵ライブラリが見つかりますが、CircuitPythonだと自分で書かないといけないことも。
でもね、初心者にとってこれらのデメリットって、実はそんなに問題じゃないんです。
だって、最初に作るのってLチカとか、温度センサーの値を読むとか、そういうシンプルなものじゃないですか。そういう用途なら、CircuitPythonで十分すぎるくらい十分なんです。
対応マイコンの選び方 - 私のおすすめはXIAO RP2350
CircuitPythonが動くマイコンはたくさんありますが、私が一番おすすめするのはSeeed StudioのXIAO RP2350です。
なぜかって?まず、めちゃくちゃ小さい!親指の爪くらいのサイズなのに、フル機能のマイコンなんです。それでいて、価格は1500円前後。コスパ最強です。
RP2350っていうのは、Raspberry Pi財団が開発した最新のマイコンチップで、前世代のRP2040と比べて処理能力が2倍以上になってます。メモリも増えてるので、CircuitPythonで大きめのプログラムを書いても余裕です。
他にも、Raspberry Pi Picoや、AdafruitのFeatherシリーズなんかも人気ですね。でも、初心者の方には断然XIAOをおすすめします。なぜなら、ピン数が少ないから迷わないんです。Arduinoとかだと、デジタルピンが14個もあって「どれ使えばいいの...」ってなりますが、XIAOなら11個だけ。選択肢が少ない分、悩まずに済みます。
UF2 Bootloaderの仕組みを理解しよう
CircuitPythonを使う上で、絶対に知っておきたいのがUF2 Bootloaderです。
これ、簡単に言うと「マイコンをUSBメモリに見せかける魔法」みたいなものです。通常、マイコンにプログラムを書き込むには専用のツールが必要ですが、UF2 Bootloaderが入っていれば、ファイルをドラッグ&ドロップするだけで書き込めちゃうんです。
仕組みとしては、マイコンの中に小さなプログラムが入っていて、これがUSBメモリのふりをしてくれるんです。で、.uf2という特殊な形式のファイルがコピーされると、「あ、これプログラムだ!」って認識して、フラッシュメモリに書き込んでくれるんです。
面白いのは、このUF2 Bootloader自体もオープンソースなんですよ。GitHubで公開されていて、誰でも中身を見ることができます。私も最初は「どういう仕組みなんだろう?」って気になって、ソースコードを読んでみたんですが、思ったよりシンプルな実装で驚きました。
実際にCircuitPythonを導入してみよう
さあ、いよいよ実践編です!XIAO RP2350を使って、CircuitPythonを導入していきましょう。
まず、CircuitPythonの公式サイト(circuitpython.org)にアクセスします。Downloadsページで「XIAO RP2350」と検索すると、対応するバージョンが出てきます。最新版をダウンロードしましょう。
次に、XIAOをBootモードにします。これがちょっとコツがいるんですが、XIAOのBボタン(小さい方のボタン)を押しながら、Rボタン(リセットボタン)を押します。すると、PCに「RP2350」っていうドライブが現れるはずです。
もし出てこない場合は、ケーブルを変えてみてください。充電専用のケーブルだとデータ通信ができないので、ドライブが現れません。100均のケーブルとかだと、結構充電専用のやつが混じってるんですよね...。私も最初これで30分くらいハマりました。
ドライブが現れたら、さっきダウンロードした.uf2ファイルをドラッグ&ドロップ!すると、マイコンが自動的に再起動して、「CIRCUITPY」というドライブに変わります。
おめでとうございます!これでCircuitPythonの導入完了です!
最初のプログラム - Lチカを徹底解説
CircuitPythonが動くようになったら、早速プログラムを書いてみましょう。定番のLチカ(LED点滅)から始めます。
CIRCUITPYドライブを開くと、「code.py」というファイルがあります。これをメモ帳で開いて、以下のコードを書いてみてください:
import board
import digitalio
import time
print("Hello World! LED点滅プログラムを開始します")
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
count = 0
while True:
count += 1
print(f"点滅回数: {count}")
led.value = False # LEDを点灯(負論理なので注意)
time.sleep(0.5)
led.value = True # LEDを消灯
time.sleep(0.5)
このコード、一見シンプルですが、実はいろんな要素が詰まってます。
まず、import board。これはマイコンのピン配置を定義したモジュールです。board.LEDって書くだけで、内蔵LEDのピンを指定できるんです。Arduino だと、ピン番号を覚えないといけないですが、CircuitPythonなら名前で指定できるので分かりやすい!
次に、digitalioモジュール。これはデジタル入出力を制御するためのモジュールです。DigitalInOutクラスを使って、ピンをオブジェクトとして扱えるようにします。オブジェクト指向プログラミングの良い練習にもなりますね。
ここで注意したいのが、LEDの論理です。多くのマイコンボードでは、LEDが「負論理」で接続されています。つまり、Falseで点灯、Trueで消灯するんです。最初これ、めっちゃ混乱しました...。「なんで逆なの!?」って。
でも、これには理由があって、マイコンのピンは電流を流し込む(シンク)方が、電流を出す(ソース)より得意なんです。だから、LEDのマイナス側をマイコンに繋いで、ピンをLOW(False)にすることで電流を流し込み、LEDを光らせるんです。
REPLモードで対話的にプログラミング
CircuitPythonの隠れた魅力、それがREPL(Read-Eval-Print Loop)モードです。
これ、簡単に言うと「マイコンと会話できるモード」なんです。1行ずつコードを入力して、その場で実行結果が見られます。
REPLに入るには、シリアルターミナルでマイコンに接続します。Windowsなら「Tera Term」、Macなら「screen」コマンドが使えます。でも、一番簡単なのはMuエディタを使うことです。
Muエディタをインストールして、上部の「シリアル」ボタンをクリック。そしてCtrl+Cを押すと、REPLモードに入れます。
試しに、こんなことをやってみましょう:
>>> import board
>>> import digitalio
>>> led = digitalio.DigitalInOut(board.LED)
>>> led.direction = digitalio.Direction.OUTPUT
>>> led.value = False # LED点灯!
>>> led.value = True # LED消灯!
すごくないですか?コンパイルも書き込みも不要で、その場でLEDを制御できるんです!
私はこのREPLモードで、新しいセンサーの動作確認をよくやります。例えば、温度センサーを繋いだ時:
>>> import busio
>>> import adafruit_sht31d
>>> i2c = busio.I2C(board.SCL, board.SDA)
>>> sensor = adafruit_sht31d.SHT31D(i2c)
>>> sensor.temperature
23.45
>>> sensor.relative_humidity
65.2
こんな感じで、センサーの値をリアルタイムで確認できます。「あ、ちゃんと動いてる!」ってすぐ分かるので、デバッグがめちゃくちゃ楽になります。
ファイル構造とライブラリの管理
CIRCUITPYドライブの中身、ちょっと覗いてみましょう。
最初はcode.pyだけですが、実はいろんなファイルを置けます:
code.py - メインプログラム(必須)
boot.py - 起動時に最初に実行される(省略可)
lib/ - ライブラリを置くフォルダ
settings.toml - Wi-Fi設定などを書くファイル
私がよくやる構成はこんな感じ:
CIRCUITPY/
├── code.py # メインプログラム
├── boot.py # USBの設定とか
├── secrets.py # パスワードとかAPIキー
└── lib/
├── adafruit_sht31d.mpy # 温度センサーライブラリ
├── adafruit_ssd1306.mpy # OLEDディスプレイ
└── my_utils.py # 自作の便利関数
ライブラリの追加も簡単です。Adafruitが公開している「CircuitPython Library Bundle」から必要なライブラリをダウンロードして、lib/フォルダにコピーするだけ。
ここでポイントなのが、.mpyファイルを使うこと。これはPythonのバイトコードにコンパイル済みのファイルで、メモリ使用量が少なくて済みます。CircuitPythonはメモリが限られているので、できるだけ.mpyファイルを使いましょう。
センサーを使った実践プロジェクト
さて、Lチカができたら、次はセンサーを使ってみましょう!温度・湿度センサーのSHT31を使った、簡単な環境モニターを作ってみます。
必要なもの:
XIAO RP2350
SHT31センサーモジュール
ジャンパーワイヤー 4本
ブレッドボード
配線はこんな感じ:
SHT31のVCC → XIAOの3V3
SHT31のGND → XIAOのGND
SHT31のSCL → XIAOのSCL(GPIO7)
SHT31のSDA → XIAOのSDA(GPIO6)
I2C通信っていうのを使うんですが、難しく考える必要はありません。要は、2本の線でデータをやり取りする仕組みです。
コードはこちら:
import time
import board
import busio
import adafruit_sht31d
# I2C通信の初期化
i2c = busio.I2C(board.SCL, board.SDA)
sensor = adafruit_sht31d.SHT31D(i2c)
# 快適度を判定する関数
def get_comfort_level(temp, humidity):
if 20 <= temp <= 26 and 40 <= humidity <= 60:
return "快適😊"
elif temp > 28:
return "暑い🥵"
elif temp < 18:
return "寒い🥶"
elif humidity > 70:
return "ジメジメ💧"
elif humidity < 30:
return "カラカラ🏜️"
else:
return "まあまあ😐"
print("環境モニター開始!")
while True:
temp = sensor.temperature
humidity = sensor.relative_humidity
comfort = get_comfort_level(temp, humidity)
print(f"温度: {temp:.1f}°C, 湿度: {humidity:.1f}%, 快適度: {comfort}")
# 異常値の警告
if temp > 30:
print("⚠️ 高温注意!エアコンをつけましょう")
elif temp < 15:
print("⚠️ 低温注意!暖房をつけましょう")
if humidity > 80:
print("⚠️ 湿度が高すぎます!除湿しましょう")
elif humidity < 20:
print("⚠️ 乾燥しています!加湿器をつけましょう")
time.sleep(5) # 5秒ごとに測定
このプログラム、実は私の部屋で実際に動かしてます。夏場とか、「あー、暑いな」って思った時にシリアルモニター見ると「温度: 29.5°C, 湿度: 65%, 快適度: 暑い🥵」って出てて、「やっぱり暑いよね!」って納得したり(笑)。
ボタンとLEDで作るインタラクティブな装置
次は、ボタン入力を使った少し複雑な例を作ってみましょう。「モールス信号トレーナー」を作ります!
import board
import digitalio
import time
import random
# LED設定
led = digitalio.DigitalInOut(board.LED)
led.direction = digitalio.Direction.OUTPUT
# ボタン設定(D1ピンに接続)
button = digitalio.DigitalInOut(board.D1)
button.direction = digitalio.Direction.INPUT
button.pull = digitalio.Pull.UP # 内部プルアップ抵抗を有効化
# モールス信号の定義
morse_code = {
'A': '.-', 'B': '-...', 'C': '-.-.', 'D': '-..',
'E': '.', 'F': '..-.', 'G': '--.', 'H': '....',
'I': '..', 'J': '.---', 'K': '-.-', 'L': '.-..',
'M': '--', 'N': '-.', 'O': '---', 'P': '.--.',
'Q': '--.-', 'R': '.-.', 'S': '...', 'T': '-',
'U': '..-', 'V': '...-', 'W': '.--', 'X': '-..-',
'Y': '-.--', 'Z': '--..',
'0': '-----', '1': '.----', '2': '..---', '3': '...--',
'4': '....-', '5': '.....', '6': '-....', '7': '--...',
'8': '---..', '9': '----.'
}
# モールス信号を光で表現
def blink_morse(text):
dot_duration = 0.2 # 短点の長さ(秒)
for char in text.upper():
if char in morse_code:
print(f"{char}: {morse_code[char]}")
for symbol in morse_code[char]:
if symbol == '.':
led.value = False # LED点灯
time.sleep(dot_duration)
led.value = True # LED消灯
elif symbol == '-':
led.value = False # LED点灯
time.sleep(dot_duration * 3) # 長点は短点の3倍
led.value = True # LED消灯
time.sleep(dot_duration) # 符号間の間隔
time.sleep(dot_duration * 3) # 文字間の間隔
elif char == ' ':
time.sleep(dot_duration * 7) # 単語間の間隔
# ボタンが押された時間を測定
def measure_button_press():
if not button.value: # ボタンが押された(負論理)
start_time = time.monotonic()
while not button.value: # 押されている間待つ
pass
duration = time.monotonic() - start_time
return duration
return 0
# クイズモード
def quiz_mode():
words = ['SOS', 'HELLO', 'PYTHON', 'XIAO', 'LED']
word = random.choice(words)
print(f"\n📝 クイズ: 次の単語をモールス信号で表現します")
print("何という単語でしょう?\n")
time.sleep(2)
blink_morse(word)
print(f"\n正解は... {word} でした!")
print("ボタンを押すと次の問題へ")
# ボタンが押されるまで待つ
while button.value:
pass
time.sleep(0.5) # デバウンス
print("🔦 モールス信号トレーナー")
print("ボタンを押すとクイズが始まります")
while True:
if not button.value: # ボタンが押された
quiz_mode()
このプログラム、意外と勉強になるんですよ。SOSのモールス信号「・・・---・・・」って、実際に光で見ると結構分かりやすいんです。
私の甥っ子(小学5年生)に見せたら、「おじちゃん、これで秘密の通信できるじゃん!」って大興奮でした。その後、自分でモールス信号表を印刷して、友達と暗号ごっこしてるらしいです(笑)。
PWMを使ったLEDの明るさ制御
デジタル出力だけじゃなく、PWM(パルス幅変調)を使ってLEDの明るさを制御してみましょう。呼吸するように明滅するLED、通称「ブリージングLED」を作ります。
import board
import pwmio
import time
import math
# PWM出力の設定(D0ピンに外部LED接続)
# 注意:外部LEDを使う場合は必ず抵抗(220Ω程度)を入れてください
led_pwm = pwmio.PWMOut(board.D0, frequency=5000, duty_cycle=0)
def breathing_led():
"""呼吸するようにLEDを明滅させる"""
steps = 100
for i in range(steps):
# サイン波を使って自然な明滅を実現
brightness = (math.sin(i * 2 * math.pi / steps - math.pi/2) + 1) / 2
duty_cycle = int(brightness * 65535)
led_pwm.duty_cycle = duty_cycle
time.sleep(0.02)
def pulse_led(times=3):
"""指定回数パルス点灯"""
for _ in range(times):
# 急速に明るくなって、ゆっくり暗くなる
for duty in range(0, 65536, 4096):
led_pwm.duty_cycle = duty
time.sleep(0.001)
for duty in range(65535, -1, -1024):
led_pwm.duty_cycle = duty
time.sleep(0.002)
time.sleep(0.2)
def rainbow_fade():
"""虹色っぽく明るさを変化させる(単色LEDだけど)"""
# 実際には色は変わらないけど、明るさの変化パターンで表現
pattern = [0, 30000, 65535, 30000, 0, 10000, 40000, 10000]
for target in pattern:
current = led_pwm.duty_cycle
step = 1000 if target > current else -1000
while abs(current - target) > abs(step):
current += step
led_pwm.duty_cycle = current
time.sleep(0.005)
print("💫 LED エフェクトデモ")
print("1: ブリージング, 2: パルス, 3: レインボー")
effect_count = 0
while True:
effect_type = effect_count % 3
if effect_type == 0:
print("🌬️ ブリージングエフェクト")
for _ in range(3):
breathing_led()
elif effect_type == 1:
print("⚡ パルスエフェクト")
pulse_led(5)
else:
print("🌈 レインボーエフェクト")
rainbow_fade()
effect_count += 1
time.sleep(1)
PWMって最初は難しそうに感じるかもしれませんが、要は「高速でON/OFFを繰り返して、ONの時間の割合で明るさを調整する」だけです。
duty_cycleっていうのがその割合を表していて、0が完全に消灯、65535が完全に点灯です。32768なら半分の明るさになります。
このブリージングLED、夜中にPCの横で光らせておくと、なんかおしゃれなんですよね。友達が家に来た時、「それ何?かっこいい!」って言われて、ちょっと得意げになっちゃいました。
I2C通信でOLEDディスプレイを制御
さらにステップアップして、OLEDディスプレイを使ってみましょう。SSD1306という128×64ピクセルの小さなディスプレイです。
import board
import busio
import time
import adafruit_ssd1306
import adafruit_framebuf
# I2C設定
i2c = busio.I2C(board.SCL, board.SDA)
# OLEDディスプレイの初期化(128x64、アドレス0x3C)
display = adafruit_ssd1306.SSD1306_I2C(128, 64, i2c, addr=0x3C)
# 画面をクリア
display.fill(0)
display.show()
def draw_face(mood="happy"):
"""顔を描画する"""
display.fill(0) # 画面クリア
# 顔の輪郭
display.circle(64, 32, 30, 1)
if mood == "happy":
# 目
display.fill_rect(50, 25, 8, 8, 1)
display.fill_rect(70, 25, 8, 8, 1)
# 口(笑顔)
for x in range(50, 78):
y = int(40 + 5 * ((x - 64) / 14) ** 2)
display.pixel(x, y, 1)
elif mood == "sad":
# 目
display.line(50, 25, 58, 30, 1)
display.line(58, 25, 50, 30, 1)
display.line(70, 25, 78, 30, 1)
display.line(78, 25, 70, 30, 1)
# 口(悲しい)
for x in range(50, 78):
y = int(45 - 5 * ((x - 64) / 14) ** 2)
display.pixel(x, y, 1)
elif mood == "surprised":
# 目(丸い)
display.circle(54, 28, 5, 1)
display.circle(74, 28, 5, 1)
# 口(O型)
display.circle(64, 45, 8, 1)
display.show()
def scrolling_text(text, y=30):
"""テキストをスクロール表示"""
text_width = len(text) * 6 # 1文字約6ピクセル
for offset in range(128 + text_width):
display.fill(0)
display.text(text, 128 - offset, y, 1)
display.show()
time.sleep(0.03)
def display_sensor_data(temp=25.5, humidity=60):
"""センサーデータを表示"""
display.fill(0)
# タイトル
display.text("Environment", 25, 5, 1)
display.line(0, 15, 127, 15, 1)
# 温度
display.text(f"Temp: {temp:.1f}C", 10, 25, 1)
# 湿度
display.text(f"Humi: {humidity:.1f}%", 10, 40, 1)
# 快適度バー
comfort = 50 + (25 - temp) * 5 + (50 - humidity)
comfort = max(0, min(100, comfort)) # 0-100に制限
display.text("Comfort:", 10, 55, 1)
display.rect(70, 53, 52, 10, 1) # 外枠
display.fill_rect(72, 55, int(comfort / 2), 6, 1) # バー
display.show()
# アニメーション付き起動画面
def boot_animation():
for radius in range(1, 32, 2):
display.fill(0)
display.circle(64, 32, radius, 1)
display.show()
time.sleep(0.02)
scrolling_text("CircuitPython OLED Demo!")
print("🖥️ OLEDディスプレイデモ開始")
boot_animation()
moods = ["happy", "sad", "surprised"]
mood_index = 0
temp = 25.0
humidity = 50.0
while True:
# 3秒ごとに表示を切り替え
print(f"顔を表示: {moods[mood_index]}")
draw_face(moods[mood_index])
time.sleep(3)
# センサーデータを表示(ダミーデータ)
temp += 0.5
humidity += 2
if temp > 30: temp = 20
if humidity > 80: humidity = 40
print(f"センサーデータ表示: 温度={temp:.1f}°C, 湿度={humidity:.1f}%")
display_sensor_data(temp, humidity)
time.sleep(3)
mood_index = (mood_index + 1) % len(moods)
OLEDディスプレイがあると、一気に「作品っぽく」なりますよね。数値だけじゃなくて、グラフとか顔とか、視覚的な表現ができるのが楽しいです。
私はこれを使って、デスクトップの天気表示器を作りました。OpenWeatherMapのAPIと組み合わせて、現在の天気と気温を表示するやつです。晴れの日は太陽のアイコン、雨の日は傘のアイコンを表示するようにしたら、朝起きてすぐに今日の天気が分かって便利です。
メモリ管理のコツとトラブルシューティング
CircuitPythonを使っていると、たまに「MemoryError」に遭遇することがあります。特に画像処理やネットワーク通信をする時に起きやすいです。
メモリ使用量を確認する方法:
import gc
def show_memory_info():
gc.collect() # ガベージコレクション実行
free_memory = gc.mem_free()
used_memory = gc.mem_alloc()
total_memory = free_memory + used_memory
print(f"メモリ情報:")
print(f" 総メモリ: {total_memory:,} bytes")
print(f" 使用中: {used_memory:,} bytes ({used_memory/total_memory*100:.1f}%)")
print(f" 空き: {free_memory:,} bytes ({free_memory/total_memory*100:.1f}%)")
if free_memory < 10000:
print("⚠️ メモリが少なくなっています!")
# プログラムの最初と最後でメモリをチェック
show_memory_info()
# ここに処理を書く
show_memory_info()
メモリを節約するテクニック:
文字列の連結を避ける
# ダメな例 message = "温度: " + str(temp) + "°C" # 良い例 message = f"温度: {temp}°C"
大きなリストは使い終わったら削除
data = [i for i in range(1000)] # 処理... del data # 明示的に削除 gc.collect() # ガベージコレクション
ライブラリは.mpyファイルを使う 通常の.pyファイルより、コンパイル済みの.mpyファイルの方がメモリ使用量が少ないです。
定数は別ファイルに
# constants.py SENSOR_PINS = {...} ERROR_MESSAGES = {...} # main.py from constants import SENSOR_PINS
私が一番ハマったのは、Web APIからJSONデータを取得する時でした。天気情報のJSONが意外と大きくて、パースする時にメモリエラーが...。
解決策は、必要な部分だけを取り出すようにしたこと:
import json
# ダメな例:全部パースしてからアクセス
# data = json.loads(response_text)
# temp = data['main']['temp']
# 良い例:ストリーミングでパース(仮想的なコード)
def extract_temperature(json_text):
# "temp":の後の数値だけを抽出
temp_index = json_text.find('"temp":')
if temp_index != -1:
start = temp_index + 7
end = json_text.find(',', start)
return float(json_text[start:end])
return None
実用的なプロジェクト例:スマートデスクライト
ここまでの知識を総動員して、実用的なものを作ってみましょう。「スマートデスクライト」です。
機能:
周囲の明るさに応じて自動調光
タッチセンサーでON/OFF
時間帯によって色温度を変更(実際には明るさパターンで表現)
長時間作業アラート
必要な部品:
XIAO RP2350
光センサー(フォトレジスタ)
タッチセンサー(または導電性テープ)
パワーLED(白色)
MOSFETまたはトランジスタ(LED駆動用)
import board
import analogio
import touchio
import pwmio
import time
import math
# センサーとLEDの設定
light_sensor = analogio.AnalogIn(board.A0) # 光センサー
touch_sensor = touchio.TouchIn(board.D1) # タッチセンサー
led = pwmio.PWMOut(board.D2, frequency=5000) # LED
# グローバル変数
is_on = False
work_start_time = None
last_break_time = time.monotonic()
auto_mode = True
manual_brightness = 32768
def read_ambient_light():
"""周囲の明るさを読み取る(0-100%)"""
# 16ビットADC値を0-100%に変換
return (65535 - light_sensor.value) / 655.35
def get_time_based_brightness():
"""時間帯に応じた明るさを返す"""
current_hour = time.localtime().tm_hour
if 6 <= current_hour < 9:
# 朝:徐々に明るく
return 40 + (current_hour - 6) * 20
elif 9 <= current_hour < 17:
# 日中:明るめ
return 80
elif 17 <= current_hour < 20:
# 夕方:少し暗く
return 60
elif 20 <= current_hour < 22:
# 夜:暗め(ブルーライト軽減)
return 40
else:
# 深夜:最小限の明るさ
return 20
def calculate_optimal_brightness():
"""最適な明るさを計算"""
if not auto_mode:
return manual_brightness
ambient = read_ambient_light()
time_brightness = get_time_based_brightness()
# 周囲が暗いほどLEDを明るく、でも時間帯も考慮
if ambient < 20: # とても暗い
target = time_brightness
elif ambient < 50: # 普通
target = time_brightness * 0.7
else: # 明るい
target = time_brightness * 0.3
return int(target * 655.35)
def smooth_transition(current, target, steps=20):
"""スムーズな明るさ遷移"""
step_size = (target - current) / steps
for i in range(steps):
led.duty_cycle = int(current + step_size * i)
time.sleep(0.02)
led.duty_cycle = target
def check_work_timer():
"""長時間作業のチェック"""
global last_break_time
if work_start_time is None:
return
work_duration = time.monotonic() - work_start_time
since_break = time.monotonic() - last_break_time
# 50分ごとに休憩を促す
if since_break > 3000: # 50分
print("⏰ 休憩時間です!10分間目を休めましょう")
# LEDを3回点滅させて通知
for _ in range(3):
current = led.duty_cycle
smooth_transition(current, 0, 10)
time.sleep(0.2)
smooth_transition(0, current, 10)
time.sleep(0.2)
last_break_time = time.monotonic()
def handle_touch():
"""タッチセンサーの処理"""
global is_on, auto_mode, manual_brightness, work_start_time
if touch_sensor.value:
touch_start = time.monotonic()
# 長押し判定
while touch_sensor.value:
if time.monotonic() - touch_start > 2:
# 2秒長押しでモード切替
auto_mode = not auto_mode
mode = "自動" if auto_mode else "手動"
print(f"🔄 モード切替: {mode}モード")
# モード切替を光で通知
for _ in range(2):
led.duty_cycle = 65535
time.sleep(0.1)
led.duty_cycle = 0
time.sleep(0.1)
time.sleep(0.5) # デバウンス
return
# 短押しでON/OFF
is_on = not is_on
if is_on:
print("💡 ライトON")
work_start_time = time.monotonic()
target = calculate_optimal_brightness()
smooth_transition(0, target)
else:
print("💤 ライトOFF")
work_start_time = None
smooth_transition(led.duty_cycle, 0)
time.sleep(0.3) # デバウンス
# 起動メッセージ
print("🌟 スマートデスクライト起動")
print("タッチ: ON/OFF, 長押し: 自動/手動切替")
# メインループ
update_counter = 0
while True:
# タッチセンサーチェック
handle_touch()
# ライトがONの時だけ処理
if is_on:
# 10回に1回だけ明るさを更新(負荷軽減)
if update_counter % 10 == 0:
target = calculate_optimal_brightness()
current = led.duty_cycle
# 大きな変化がある時だけ調整
if abs(target - current) > 3000:
smooth_transition(current, target)
ambient = read_ambient_light()
print(f"📊 環境光: {ambient:.1f}%, LED: {target//655:.0f}%")
# 作業時間チェック
if update_counter % 600 == 0: # 約1分ごと
check_work_timer()
update_counter += 1
time.sleep(0.1)
このスマートデスクライト、実際に使ってみるとめちゃくちゃ便利です。特に夜中にプログラミングしてる時、自動で暗めになってくれるのがありがたい。
最初は「自動調光なんていらないでしょ」って思ってたんですが、使い始めたら手放せなくなりました。朝は明るく、夜は暗く、自然に調整されるので目が疲れにくいんです。
データロギング機能を追加する
さらに実用的にするため、センサーデータをSDカードに記録する機能を追加してみましょう。
import board
import busio
import sdcardio
import storage
import time
import json
# SDカードの初期化
spi = busio.SPI(board.SCK, board.MOSI, board.MISO)
cs = board.D5 # チップセレクトピン
sdcard = sdcardio.SDCard(spi, cs)
vfs = storage.VfsFat(sdcard)
storage.mount(vfs, "/sd")
class DataLogger:
def __init__(self, filename="/sd/sensorlog.txt"):
self.filename = filename
self.buffer = []
self.buffer_size = 10 # 10件ごとに書き込み
# ヘッダーを書き込み
try:
with open(self.filename, "r") as f:
# ファイルが存在する場合は何もしない
pass
except:
# 新規ファイルの場合はヘッダーを書く
with open(self.filename, "w") as f:
f.write("timestamp,temperature,humidity,light,comfort\n")
def log(self, data):
"""データをバッファに追加"""
timestamp = time.monotonic()
data['timestamp'] = timestamp
self.buffer.append(data)
# バッファが満杯になったら書き込み
if len(self.buffer) >= self.buffer_size:
self.flush()
def flush(self):
"""バッファの内容をファイルに書き込み"""
if not self.buffer:
return
try:
with open(self.filename, "a") as f:
for data in self.buffer:
line = f"{data['timestamp']:.2f},"
line += f"{data.get('temperature', 0):.1f},"
line += f"{data.get('humidity', 0):.1f},"
line += f"{data.get('light', 0):.1f},"
line += f"{data.get('comfort', 0):.0f}\n"
f.write(line)
print(f"💾 {len(self.buffer)}件のデータを保存しました")
self.buffer.clear()
except Exception as e:
print(f"❌ 書き込みエラー: {e}")
def read_statistics(self):
"""統計情報を読み取る"""
try:
temps = []
humis = []
with open(self.filename, "r") as f:
f.readline() # ヘッダーをスキップ
for line in f:
parts = line.strip().split(',')
if len(parts) >= 3:
temps.append(float(parts[1]))
humis.append(float(parts[2]))
if temps:
return {
'temp_avg': sum(temps) / len(temps),
'temp_max': max(temps),
'temp_min': min(temps),
'humi_avg': sum(humis) / len(humis),
'count': len(temps)
}
except:
pass
return None
# 使用例
logger = DataLogger()
print("📊 データロガー開始")
# ダミーセンサーデータ(実際はセンサーから取得)
temp = 25.0
humidity = 50.0
light_level = 70.0
for i in range(100):
# センサーデータをシミュレート
temp += (time.monotonic() % 10 - 5) * 0.1
humidity += (time.monotonic() % 8 - 4) * 0.5
light_level = 50 + 30 * math.sin(i * 0.1)
comfort = 50 + (25 - temp) * 2 + (50 - humidity)
# データをログ
logger.log({
'temperature': temp,
'humidity': humidity,
'light': light_level,
'comfort': comfort
})
print(f"記録 #{i+1}: 温度={temp:.1f}°C, 湿度={humidity:.1f}%")
time.sleep(1)
# 30件ごとに統計を表示
if (i + 1) % 30 == 0:
stats = logger.read_statistics()
if stats:
print("\n📈 統計情報:")
print(f" 温度: 平均={stats['temp_avg']:.1f}°C")
print(f" 最高={stats['temp_max']:.1f}°C")
print(f" 最低={stats['temp_min']:.1f}°C")
print(f" 湿度: 平均={stats['humi_avg']:.1f}%")
print(f" 記録数: {stats['count']}件\n")
# 最後にバッファを書き込み
logger.flush()
データロギング機能があると、長期的な環境の変化を追跡できます。私はこれを使って、部屋の温度と湿度を1週間記録して、エアコンの最適な設定を見つけました。
意外だったのは、深夜2時頃に湿度が急上昇することがあるってこと。調べてみたら、外気温が下がって相対湿度が上がるからだそうです。データって面白いですね。
ネットワーク機能:Wi-Fi接続とWebサーバー
XIAO RP2350にはWi-Fi機能はありませんが、ESP32-C3のようなWi-Fi対応マイコンを使えば、IoTデバイスも作れます。
import wifi
import socketpool
import time
from adafruit_httpserver import Server, Request, Response
# Wi-Fi接続
print("🌐 Wi-Fi接続中...")
wifi.radio.connect("YOUR_SSID", "YOUR_PASSWORD")
print(f"接続成功!IPアドレス: {wifi.radio.ipv4_address}")
# Webサーバーの設定
pool = socketpool.SocketPool(wifi.radio)
server = Server(pool, debug=True)
# センサーデータ(ダミー)
sensor_data = {
'temperature': 25.5,
'humidity': 60.0,
'light': 750,
'last_update': time.monotonic()
}
# HTMLテンプレート
def generate_html():
return f"""
<!DOCTYPE html>
<html>
<head>
<title>センサーダッシュボード</title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
body {{
font-family: Arial, sans-serif;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 20px;
}}
.container {{
max-width: 600px;
margin: 0 auto;
}}
.card {{
background: rgba(255, 255, 255, 0.1);
border-radius: 15px;
padding: 20px;
margin: 20px 0;
backdrop-filter: blur(10px);
}}
.sensor-value {{
font-size: 3em;
font-weight: bold;
}}
.sensor-label {{
font-size: 1.2em;
opacity: 0.8;
}}
.update-time {{
text-align: center;
opacity: 0.6;
margin-top: 20px;
}}
</style>
<script>
setTimeout(() => location.reload(), 5000); // 5秒ごとに自動更新
</script>
</head>
<body>
<div class="container">
<h1>🏠 環境センサーダッシュボード</h1>
<div class="card">
<div class="sensor-label">🌡️ 温度</div>
<div class="sensor-value">{sensor_data['temperature']:.1f}°C</div>
</div>
<div class="card">
<div class="sensor-label">💧 湿度</div>
<div class="sensor-value">{sensor_data['humidity']:.1f}%</div>
</div>
<div class="card">
<div class="sensor-label">☀️ 明るさ</div>
<div class="sensor-value">{sensor_data['light']:.0f} lux</div>
</div>
<div class="update-time">
最終更新: {time.monotonic() - sensor_data['last_update']:.0f}秒前
</div>
</div>
</body>
</html>
"""
@server.route("/")
def root(request: Request):
return Response(request, generate_html(), content_type="text/html")
@server.route("/api/sensors")
def api_sensors(request: Request):
import json
return Response(request, json.dumps(sensor_data), content_type="application/json")
# サーバー起動
print(f"🚀 Webサーバー起動: http://{wifi.radio.ipv4_address}")
server.serve_forever()
これをスマホのブラウザで開くと、リアルタイムでセンサーデータが見られます。家の中のどこからでも温度をチェックできるって、地味に便利なんですよ。
パフォーマンス最適化のテクニック
CircuitPythonは便利ですが、速度面ではC++に劣ります。でも、工夫次第でかなり改善できます。
import time
import array
# テクニック1: 事前計算とルックアップテーブル
# サイン波のルックアップテーブルを作成
SINE_TABLE = array.array('h', [int(32767 * math.sin(i * 2 * math.pi / 256))
for i in range(256)])
def fast_sine_wave(phase):
"""高速サイン波生成"""
index = phase % 256
return SINE_TABLE[index]
# テクニック2: ビット演算の活用
def fast_rgb_to_565(r, g, b):
"""RGB888をRGB565に高速変換"""
# 通常の方法
# return ((r & 0xF8) << 8) | ((g & 0xFC) << 3) | (b >> 3)
# ビットシフトを最適化
return ((r & 0b11111000) << 8) | ((g & 0b11111100) << 3) | (b >> 3)
# テクニック3: メモ化(計算結果のキャッシュ)
_fibonacci_cache = {}
def fast_fibonacci(n):
"""キャッシュを使った高速フィボナッチ数列"""
if n in _fibonacci_cache:
return _fibonacci_cache[n]
if n <= 1:
result = n
else:
result = fast_fibonacci(n-1) + fast_fibonacci(n-2)
_fibonacci_cache[n] = result
return result
# テクニック4: リスト内包表記vs通常のループ
def benchmark_list_operations():
"""リスト操作のベンチマーク"""
import time
# 通常のループ
start = time.monotonic()
result1 = []
for i in range(1000):
if i % 2 == 0:
result1.append(i * 2)
time1 = time.monotonic() - start
# リスト内包表記(高速)
start = time.monotonic()
result2 = [i * 2 for i in range(1000) if i % 2 == 0]
time2 = time.monotonic() - start
print(f"通常のループ: {time1:.4f}秒")
print(f"リスト内包表記: {time2:.4f}秒")
print(f"速度向上: {time1/time2:.2f}倍")
# テクニック5: 文字列操作の最適化
def efficient_string_building():
"""効率的な文字列構築"""
# 悪い例:文字列連結
# result = ""
# for i in range(100):
# result += str(i) + ","
# 良い例:joinを使用
parts = [str(i) for i in range(100)]
result = ",".join(parts)
return result
# テクニック6: 定数の事前定義
# 定数は大文字で定義し、計算済みの値を使う
PI_TIMES_2 = 6.283185307179586
SQRT_2 = 1.4142135623730951
GOLDEN_RATIO = 1.618033988749895
def optimized_calculation(radius):
"""最適化された円周計算"""
# return 2 * math.pi * radius # 遅い
return PI_TIMES_2 * radius # 速い
print("⚡ パフォーマンス最適化テスト")
benchmark_list_operations()
# 実測してみると、リスト内包表記は通常のループより約1.5倍速いことが分かります。
# こういう小さな最適化の積み重ねが、全体のパフォーマンスを大きく改善します。
### CircuitPythonからArduinoへの移行を考える時
正直に言うと、CircuitPythonには限界があります。でも、それは悪いことじゃないんです。
私がArduinoへの移行を検討したのは、こんな時でした:
1. **リアルタイム性が必要になった時**
ロボットアームを作ってた時、サーボモーターの制御が追いつかなくなりました。1ミリ秒単位の精密な制御が必要で、CircuitPythonでは無理でした。
2. **特殊なライブラリが必要になった時**
CANバス通信をしたくて探したら、Arduinoには豊富なライブラリがあるのに、CircuitPythonには無い...。自分で実装するのは大変すぎました。
3. **メモリが足りなくなった時**
画像処理をやろうとしたら、320×240ピクセルのビットマップでメモリオーバー。Arduinoなら外部RAMを使える方法があったんです。
でも、CircuitPythonで学んだことは無駄になりません。プログラミングの基礎、センサーの扱い方、通信プロトコルの理解...全部Arduinoでも活きます。
むしろ、CircuitPythonで「こういうことがしたい」というビジョンを明確にしてからArduinoに移行する方が、スムーズに進みます。
### 私のCircuitPython失敗談
ここで、私の失敗談をいくつか共有しますね。同じ失敗をしないでください...!
**失敗その1:電源を考えなかった**
USBから給電してテストしてたプロジェクトを、電池駆動にしたら動かない!原因は、電池の電圧降下でした。新品の電池は1.5Vですが、使ってると1.2Vくらいまで下がります。3本直列で3.6V...XIAOの最低動作電圧ギリギリでした。
解決策:昇圧DCDCコンバーターを使うか、リチウムイオン電池を使いましょう。
**失敗その2:ピンを壊した**
LEDを直接ピンに繋いで光らせてたら、ある日突然そのピンが使えなくなりました。電流制限抵抗を入れ忘れてたんです。マイコンのピンは最大でも20mAくらいしか流せません。
解決策:必ず抵抗を入れる。220Ω〜1kΩくらいが安全です。
**失敗その3:無限ループで詰んだ**
code.pyに無限ループを書いて、しかもtime.sleepを入れ忘れました。結果、マイコンがフリーズしたように見えて、プログラムの書き換えができない!
解決策:セーフモードで起動します。リセットボタンを連打しながらUSB接続すると、code.pyを実行せずに起動できます。
**失敗その4:相対インポートでハマった**
libフォルダにmy_module.pyを作って、その中で別のモジュールをインポートしようとしたら、エラーの嵐。CircuitPythonは相対インポートに対応してないんです。
解決策:絶対インポートを使うか、全部code.pyに書く。
### コミュニティとリソース
CircuitPythonの素晴らしいところは、コミュニティの温かさです。
**日本語リソース**
- スイッチサイエンスのブログ:製品紹介と一緒に使い方も解説
- Qiita:「CircuitPython」タグで検索すると、いろんな作例が
- Twitter:#CircuitPython のハッシュタグで情報収集
**英語リソース**
- Adafruit Learn:公式チュートリアルが山ほど
- Discord:CircuitPython公式サーバーがあって、質問するとすぐ答えが返ってくる
- GitHub:ライブラリのソースコードを読むと勉強になる
私が一番助けられたのは、Discordサーバーです。「このセンサー使いたいんだけど、ライブラリある?」って聞いたら、「今作ってるから待って!」って返事が来て、3日後に公開されました。すごくないですか?
### 作品を公開する時のコツ
せっかく作った作品、どうせなら公開しましょう!
**GitHubで公開する時**
my-circuitpython-project/ ├── README.md # プロジェクトの説明 ├── code.py # メインコード ├── lib/ # 必要なライブラリ ├── docs/ # 配線図とか │ ├── wiring.png # 配線図 │ └── parts_list.md # 部品リスト ├── images/ # 完成写真 └── LICENSE # ライセンス(MITがおすすめ)
**README.mdに書くべきこと**
- 何を作ったか(動画があるとベスト)
- 必要な部品とその入手先
- 配線方法(図があると親切)
- セットアップ手順
- 使い方
- トラブルシューティング
- 今後の改善予定
私の経験では、動画があると反応が10倍違います。TwitterにGIF動画を投稿して、GitHubのリンクを貼るのが効果的でした。
### よくある質問と回答
ここまで読んでくれた方から、よく聞かれる質問をまとめました。
**Q: CircuitPythonとMicroPythonの違いは?**
CircuitPythonはMicroPythonをベースに、Adafruitが初心者向けに改良したものです。主な違いは:
- ライブラリが豊富(特にセンサー関連)
- エラーメッセージが分かりやすい
- USBマスストレージ機能が標準
- REPLが使いやすい
個人的には、初心者ならCircuitPython一択です。
**Q: どのマイコンがおすすめ?**
予算と用途によりますが:
- 初心者:XIAO RP2350(小さくて安い)
- Wi-Fi必要:ESP32-S3(技適取得済み)
- たくさんのピン:Raspberry Pi Pico(コスパ最強)
- 高性能:Teensy 4.1(でもCircuitPython対応は限定的)
私のイチオシはやっぱりXIAO RP2350です。
**Q: 電池で動かしたいんだけど?**
リチウムポリマー電池がおすすめです。3.7Vで、XIAOなら直接繋げます。充電回路付きのボードもあるので、それを使うと便利。単三電池なら、3本直列+昇圧DCDCコンバーター。
**Q: もっと速く動かしたい!**
オーバークロックができます:
```python
import microcontroller
microcontroller.cpu.frequency = 250000000 # 250MHz
ただし、発熱と消費電力が増えるので注意。あと、不安定になることも。
Q: ファイルが消えた!?
CIRCUITPYドライブは、突然のUSB切断でファイルシステムが壊れることがあります。定期的にバックアップを取りましょう。私は毎回GitHubにpushしてます。
Q: code.py以外のファイル名は使える?
使えます!優先順位は:
code.txt
code.py
main.txt
main.py
でも、code.pyが一番分かりやすいので、これを使うのがおすすめ。
Q: デバッグはどうやるの?
print文が基本です。でも、もっと高度なデバッグをしたいなら:
import supervisor
def debug_print(message):
"""デバッグ用プリント(リリース時は無効化しやすい)"""
if supervisor.runtime.serial_connected:
print(f"[DEBUG] {message}")
# 使用例
debug_print(f"センサー値: {sensor_value}")
Q: 日本語は使える?
表示は難しいですが、コメントやprint文では使えます:
# これはOK
print("こんにちは、CircuitPython!")
# でもOLEDに日本語表示は大変(フォントが必要)
Q: 他の人のコードを参考にしたい
GitHubで「CircuitPython」で検索すると、たくさん出てきます。特に「Adafruit_CircuitPython_Bundle」のexamplesフォルダは宝の山です。
最後に:CircuitPythonで広がる可能性
ここまで本当に長い記事を読んでいただき、ありがとうございます!
CircuitPythonを始めて1年、私の電子工作ライフは劇的に変わりました。以前は「アイデアはあるけど、実装が大変そう...」って諦めてたものが、今では「とりあえず作ってみるか!」って気軽に手を出せるようになりました。
メモ帳だけで始められる手軽さ、Pythonの分かりやすさ、豊富なライブラリ、温かいコミュニティ...CircuitPythonには、電子工作を楽しくする要素が全部詰まってます。
確かに、プロの現場で使うには力不足かもしれません。でも、趣味として、学習として、プロトタイピングとして、CircuitPythonは最高の選択肢だと思います。
私の夢は、CircuitPythonを使って、誰もが簡単に「作りたいものを作れる」世界を実現することです。この記事が、その第一歩になれば嬉しいです。
さあ、あなたも今すぐXIAOを手に入れて、CircuitPythonの世界に飛び込んでみませんか?きっと、新しい発見と感動が待っていますよ!
もし何か作ったら、ぜひSNSでシェアしてくださいね。#CircuitPython のタグを付けてもらえれば、必ず見に行きます!
それでは、楽しい電子工作ライフを!