Raspberry Pi PicoのPIOとデュアルコア:_thread、setup1、WS2812B
Raspberry Pi PicoのPIOガイドです。ステートマシン、矩形波とWS2812Bの例、MicroPythonの_threadとArduinoのsetup1/loop1による2つのコアの利用、Pico 2の変更点を解説します。
Raspberry Pi Pico PIOブロックと2つのコアは、この小さなボードを同じ価格帯の多くのマイコンと差別化する2つの特徴です。Picoの中心となるRP2040チップには、独立した2つのプロセッサーコアと8基のPIOステートマシンが搭載されています。2つのコアのおかげで、一方でセンサーを読み取りながら、もう一方でディスプレイやLEDのアニメーションを滑らかに動かし続けられます。一方、PIOはプロセッサーから独立して動作する小さなタイミング制御エンジンの集まりです。CPUに負荷をかけずに、WS2812B LEDに必要なナノ秒精度の信号や、追加のUART、矩形波を生成します。
このガイドでは、まずMicroPythonの_threadモジュールで、次にArduino IDEのsetup1()とloop1()で2つのコアを動かします。その後、PIOの仕組み、ステートマシンの数、使用できる命令を学び、実際に2つのプログラムを書きます。ブザーで音階を鳴らす矩形波発生器と、WS2812Bの虹色表示です。最後に、Pico 2(RP2350)での変更点と、これらの機能が実際に必要になる場面を紹介します。Picoが初めての場合は、先にRaspberry Pi Picoとは?とMicroPythonの基本を読むことをおすすめします。

Raspberry Pi Picoの2つのコアとPIOとは?
RP2040には、コア0とコア1という同一仕様のCortex-M0+プロセッサーが2つあります。プログラムの起動時に動くのはコア0だけで、コア1は明示的に起動するまでスリープしています。2つのコアは同じ264 KBのRAMと同じピンを共有するため、データの受け渡しは簡単ですが、同じ変数に同時に書き込んではいけません。そのため、チップには小さな双方向のハードウェアキュー(FIFO、各方向8ワード)と32個のハードウェアロック(スピンロック)が用意されています。PIOはコアとは完全に独立しています。2つのPIOブロックにはそれぞれ4基のステートマシン、共有の32命令分のプログラムメモリがあり、ピンに直接アクセスできます。ステートマシンは一度起動すると、プロセッサーが別の処理に忙しい間も、独自のプログラムを1サイクルずつ実行します。
| リソース | RP2040(Pico、Pico W) | 用途 |
|---|---|---|
| プロセッサーコア | 2 x Cortex-M0+、133 MHz(現行の資料では最大200 MHz) | 2つの別々のプログラムを実際に同時に実行 |
| コア間FIFO | 各方向8 x 32ビット | コア間で数値や命令を送信 |
| ハードウェアロック(スピンロック) | 32 | 共有データに順番にアクセス |
| PIOブロック | 2個(PIO0、PIO1) | 精密なタイミングが必要なピン操作をプロセッサーから切り離す |
| ステートマシン | 各ブロック4基、合計8基 | それぞれが独自のPIOプログラムを実行 |
| PIOプログラムメモリ | 各ブロック32命令分 | 同じブロック内の4基のステートマシンで共有 |
| PIO FIFO | 各ステートマシンにTX 4+RX 4ワード(結合時は8ワード) | プロセッサーとステートマシン間のデータ受け渡し |
MicroPythonで2つ目のコアを使う:_threadモジュール
MicroPythonでコア1を使うには、_thread.start_new_thread(function, args)を呼び出します。Picoでは、この呼び出しで2つ目のコア上に関数が起動し、メインプログラムはコア0で動作を続けます。3つ目のコアはないため、同時に実行できるのは追加のスレッド1つだけです。次の例では、コア0がGP26に接続したポテンショメーターを読み取って結果を表示し、コア1がポテンショメーターで設定された速度で内蔵LEDを点滅させます。コア1でのsleep_ms()による待機はコア0にまったく影響しません。1つのコアで同じことを行うには、タイマーやasyncioが必要です。
1# Pico - iki çekirdek (MicroPython _thread) / two cores (MicroPython _thread)
2import _thread
3import time
4from machine import Pin, ADC
5
6led = Pin("LED", Pin.OUT) # Pico ve Pico W dahili LED / onboard LED on Pico and Pico W
7pot = ADC(26) # GP26 = ADC0, potansiyometre / potentiometer
8
9lock = _thread.allocate_lock() # ortak veri kilidi / shared data lock
10blink_ms = 500 # çekirdek 0 yazar, çekirdek 1 okur / core 0 writes, core 1 reads
11running = True # durdurma bayrağı / stop flag
12
13
14def core1_task():
15 # Çekirdek 1: LED'i kendi hızında yakıp söndürür / Core 1: blinks the LED at its own pace
16 while running:
17 with lock:
18 period = blink_ms
19 led.toggle()
20 time.sleep_ms(period)
21 led.off()
22
23
24_thread.start_new_thread(core1_task, ()) # ikinci çekirdekte başlat / start on the second core
25
26try:
27 while True:
28 # Çekirdek 0: sensörü okur ve hızı ayarlar / Core 0: reads the sensor and sets the speed
29 raw = pot.read_u16() # 0-65535
30 with lock:
31 blink_ms = 50 + raw * 950 // 65535 # 50-1000 ms
32 print("ham / raw:", raw, "ms:", blink_ms)
33 time.sleep_ms(200)
34except KeyboardInterrupt:
35 running = False # çekirdek 1'e dur de / tell core 1 to stop
36 time.sleep_ms(1100)- allocate_lock()で作成したロックにより、with lock:ブロック内では、一度に1つのコアだけが共有変数にアクセスできます。
- runningフラグは、Ctrl+Cでプログラムを停止したときにコア1へループを抜けるよう伝えます。これがないとLEDは点滅し続けます。
- Pin("LED")と書くと、PicoでもPico Wでも内蔵LEDを指定できます。Pico WではLEDが無線チップに接続されているため、Pin(25)は使えません。
- コア1には時間のかかる計算、画面の描画、精密なタイミング制御を任せ、コア0にはWi-Fi、メニュー、ユーザー操作の処理を任せると、うまく役割を分担できます。
Arduino IDEで2つのコアを使う:setup1()とloop1()
Arduino IDEでPicoを使うために最も広く利用されているパッケージは、Earle PhilhowerによるRaspberry Pi Pico/RP2040/RP2350コア(arduino-pico)です。このパッケージで2つのコアを使うのは驚くほど簡単です。おなじみのsetup()とloop()はコア0で動きます。スケッチにsetup1()とloop1()を追加すると、コア1が自動的に起動して、この2つの関数を実行します。コア間のデータ送信にはrp2040.fifoオブジェクトを使います。Arduino公式の「Arduino Mbed OS RP2040」パッケージはsetup1()に対応していないため、ボードマネージャで正しいパッケージを選んでください。
1// Raspberry Pi Pico - iki çekirdek (Arduino-Pico çekirdeği) / two cores (Arduino-Pico core)
2const int POT_PIN = 26; // GP26 = ADC0, potansiyometre / potentiometer
3
4// ---- Çekirdek 0 / Core 0 ----
5void setup() {
6 Serial.begin(115200);
7}
8
9void loop() {
10 int raw = analogRead(POT_PIN); // 0-1023 (varsayılan 10 bit / default 10-bit)
11 rp2040.fifo.push_nb(raw); // FIFO doluysa bekleme, değeri atla / skip the value if the FIFO is full
12 Serial.print("pot: ");
13 Serial.println(raw);
14 delay(100);
15}
16
17// ---- Çekirdek 1 / Core 1 ----
18void setup1() {
19 pinMode(LED_BUILTIN, OUTPUT);
20}
21
22void loop1() {
23 static uint32_t periodMs = 500;
24 uint32_t value;
25 while (rp2040.fifo.pop_nb(&value)) { // FIFO'yu boşalt, sonuncuyu kullan / drain the FIFO, keep the last one
26 periodMs = map(value, 0, 1023, 50, 1000);
27 }
28 digitalWrite(LED_BUILTIN, HIGH);
29 delay(periodMs); // yalnız çekirdek 1'i bekletir / only blocks core 1
30 digitalWrite(LED_BUILTIN, LOW);
31 delay(periodMs);
32}| 関数 | 動作 |
|---|---|
| rp2040.fifo.push(x) | もう一方のコアに32ビットの値を送信し、キューが満杯なら空きを待つ |
| rp2040.fifo.push_nb(x) | 待機せずに送信を試み、キューが満杯ならfalseを返す |
| rp2040.fifo.pop() | もう一方のコアから値を読み取り、キューが空なら待機する |
| rp2040.fifo.pop_nb(&x) | 値があればxに書き込んでtrueを返し、なければ直ちにfalseを返す |
| rp2040.fifo.available() | 読み取り待ちの値の数を返す |
| rp2040.idleOtherCore() / resumeOtherCore() | もう一方のコアを一時停止し、再開する(フラッシュへの書き込み時など) |
PIOとは?プログラマブルI/Oの仕組み
多くのマイコンでは、UART、SPI、I2C、PWMなどの通信・信号生成ブロックはチップ内に固定の回路として実装されています。そこにないプロトコルが必要な場合は、プロセッサーがピンを1つずつオン・オフして処理します(ビットバンギング)。これではプロセッサーが占有され、割り込みが入るたびにタイミングが崩れます。PIO(プログラマブル入出力)は、この問題を根本から解決します。各ステートマシンは、ピン操作専用に設計された9種類の命令を持つ小さなプロセッサーです。各命令は正確に1クロックサイクルで実行され、末尾に0〜31サイクルの遅延を追加できます。つまり、サイクルを数えるだけで、信号の各エッジまでに何ナノ秒かかるかを正確に把握できます。ステートマシンの速度は、システムクロックを1〜65536の分周値で分周して設定します。
| 命令 | 用途 |
|---|---|
| set(pins, v) | 0〜31の定数をピンまたはX/Yレジスタに書き込む |
| out(dest, n) | 出力シフトレジスタ(OSR)からピンまたはX/Yへnビットを移す |
| in_(src, n) | ピンまたはレジスタから入力シフトレジスタ(ISR)へnビットをシフトする |
| pull() / push() | TX FIFOからOSRへ1ワードを取り込み、ISRの1ワードをRX FIFOへ送り出す |
| jmp(cond, label) (条件付きジャンプ命令) | 条件に応じてジャンプする(Xがゼロ、ピンがHIGH、X--など) |
| wait(polarity, src, idx) (待機命令) | ピンまたはIRQフラグが指定したレベルになるまで待機する |
| mov(dest, src) (レジスタ間コピー命令) | レジスタ間でコピーし、必要に応じてビットを反転する |
| irq(idx) | CPUまたは別のステートマシンに割り込みフラグを送る |
| nop()と.side(v) | 何もしない命令。サイドセットは、任意の命令と同じサイクルでピンを変更する |
MicroPythonでは、PIOプログラムは@rp2.asm_pioデコレーターを付けたPython関数として記述します。この関数を呼び出すのではなく、MicroPythonが機械語にアセンブルします。その後、rp2.StateMachine(id, program, freq=..., set_base=...)でステートマシンに読み込みます。Picoでは、ステートマシン番号0〜3がPIO0に、4〜7がPIO1に属します。freqは1秒あたりの命令サイクル数で、システムクロックの1/65536未満にはできません。125 MHzでは約1.9 kHz、150 MHzでは約2.3 kHzが下限です。さらに遅い信号が必要な場合は、遅延やX/Yカウンターを使ったループを利用します。
PIOで矩形波を生成:初めてのステートマシンプログラム
最初の例では、GP15に接続したパッシブブザーで音階を鳴らした後、1 kHzの矩形波を連続して出力します。プログラムはわずか2命令です。ピンをHIGHにして9サイクル待ち、ピンをLOWにして9サイクル待ちます。各命令自体にも1サイクルかかるため、1周期は20サイクルになります。つまり、鳴らしたい音の周波数の20倍をステートマシンに指定するだけです。ブザーのプラス側の端子をGP15に、マイナス側をGNDに接続してください。ブザーの代わりにオシロスコープや、220 Ωの抵抗を接続したLEDも使えます。抵抗はLED抵抗計算ツールで選べます。
1# PIO ile kare dalga ve buzzer notaları / square wave and buzzer notes with PIO
2import time
3import rp2
4from machine import Pin
5
6
7@rp2.asm_pio(set_init=rp2.PIO.OUT_LOW)
8def square():
9 wrap_target()
10 set(pins, 1) [9] # 1 + 9 = 10 döngü HIGH / 10 cycles HIGH
11 set(pins, 0) [9] # 1 + 9 = 10 döngü LOW / 10 cycles LOW
12 wrap() # başa dön, işlemci karışmaz / loop back, no CPU involved
13
14
15def tone(freq_hz):
16 # Bir periyot 20 döngü: durum makinesi frekansı = 20 x ton
17 # One period is 20 cycles: state machine frequency = 20 x tone
18 sm = rp2.StateMachine(0, square, freq=freq_hz * 20, set_base=Pin(15))
19 sm.active(1)
20 return sm
21
22
23notes = [262, 294, 330, 349, 392, 440, 494, 523] # Do-Re-Mi ... / C4 ... C5
24for n in notes:
25 sm = tone(n)
26 time.sleep_ms(300) # işlemci boşta, ses PIO'dan / CPU is idle, PIO makes the sound
27 sm.active(0)
28
29sm = tone(1000) # 1 kHz sürekli kare dalga / continuous 1 kHz square wave
30print("GP15: 1 kHz kare dalga / square wave")ここでの式は簡単です。音の周波数=ステートマシンの周波数/サイクル数です。1000 Hzの場合、ステートマシンは20,000 Hzで動作し、下限を十分に上回ります。遅延をなくして2つのset()命令だけにすると、1周期は2サイクルに短縮されます。この状態でステートマシンを125 MHzで動かすと、ピンから62.5 MHzの矩形波が出力されます。ハードウェアPWMでも同じことができる点を覚えておいてください。単純な音やLEDの明るさの制御なら、PWMで十分です。PIOの特徴は、信号の形を細かく決められることです。数行で幅の変わるパルス、正確な個数のパルス、入力に反応する出力を生成できます。
PIOでWS2812B(NeoPixel)LEDを駆動

WS2812B LEDは、1ビットあたり約1.25マイクロ秒のパルスを必要とします。ビットが1か0かは、パルスがHIGHを維持する時間で決まります。これはPIOに最適な用途です。次のプログラムはRaspberry Pi公式のMicroPythonの例を基にしています。ステートマシンは8 MHzで動作し、各ビットに正確に10サイクルかかるため、毎秒800,000ビットを送信します。autopullにより、ステートマシンは24ビットごとにFIFOから新しい色情報を取り込みます。プロセッサーはsm.put()で配列をFIFOに渡すだけです。LEDの個数は使用するテープに合わせて変更してください。WS2812Bの仕組みと色の順序は、WS2812B NeoPixelのガイドを参照してください。
| WS2812Bのピン | Picoのピン | 補足 |
|---|---|---|
| DIN(データ入力) | GP16(物理ピン21) | 330 Ωの直列抵抗で信号を保護 |
| 5V / VCC | VBUS(40番ピン) | USBからの5 V。LEDが多い場合は外部の5 V電源を使用 |
| GND | GND(38番ピンなど) | 外部電源のGNDもここに接続する必要がある |
1# PIO ile WS2812B (NeoPixel) gökkuşağı / WS2812B (NeoPixel) rainbow with PIO
2import array
3import time
4import rp2
5from machine import Pin
6
7NUM_LEDS = 24 # halka ya da şeritteki LED sayısı / LEDs in the ring or strip
8LED_PIN = 16 # DIN bağlantısı / DIN connection
9BRIGHTNESS = 0.2 # 0.0-1.0, akımı düşük tutar / keeps current low
10
11
12@rp2.asm_pio(sideset_init=rp2.PIO.OUT_LOW, out_shiftdir=rp2.PIO.SHIFT_LEFT,
13 autopull=True, pull_thresh=24)
14def ws2812():
15 T1 = 2
16 T2 = 5
17 T3 = 3
18 wrap_target()
19 label("bitloop")
20 out(x, 1) .side(0) [T3 - 1] # 1 biti al, hat LOW / take 1 bit, line LOW
21 jmp(not_x, "do_zero") .side(1) [T1 - 1] # hat HIGH / line HIGH
22 jmp("bitloop") .side(1) [T2 - 1] # bit 1: uzun HIGH / long HIGH
23 label("do_zero")
24 nop() .side(0) [T2 - 1] # bit 0: kısa HIGH / short HIGH
25 wrap()
26
27
28# Bit başına 10 döngü x 8 MHz = 800 kHz / 10 cycles per bit x 8 MHz = 800 kHz
29sm = rp2.StateMachine(0, ws2812, freq=8_000_000, sideset_base=Pin(LED_PIN))
30sm.active(1)
31
32pixels = array.array("I", [0] * NUM_LEDS)
33
34
35def set_pixel(i, r, g, b):
36 r = int(r * BRIGHTNESS)
37 g = int(g * BRIGHTNESS)
38 b = int(b * BRIGHTNESS)
39 pixels[i] = (g << 16) | (r << 8) | b # WS2812B sırası GRB / byte order is GRB
40
41
42def show():
43 sm.put(pixels, 8) # 24 biti sola hizala / left-align the 24 bits
44
45
46def wheel(pos):
47 # 0-255 arası değeri renge çevir / turn 0-255 into a colour
48 pos %= 255
49 if pos < 85:
50 return 255 - pos * 3, pos * 3, 0
51 if pos < 170:
52 pos -= 85
53 return 0, 255 - pos * 3, pos * 3
54 pos -= 170
55 return pos * 3, 0, 255 - pos * 3
56
57
58step = 0
59while True:
60 for i in range(NUM_LEDS):
61 set_pixel(i, *wheel(i * 255 // NUM_LEDS + step))
62 show()
63 step = (step + 3) % 255
64 time.sleep_ms(30) # kare arası bekleme, aynı zamanda reset / frame gap, also the reset timeプログラムを1行ずつ追ってみましょう。out(x, 1)は、サイドセットで信号線をLOWに保ちながら、OSRからXレジスタへ1ビットを取り込みます(3サイクル)。次に信号線をHIGHにし、Xを確認します(2サイクル)。ビットが1なら、さらに5サイクルHIGHを維持します。0なら信号線をLOWにして5サイクル待ちます。どちらの経路も合計10サイクル、つまり1.25 µsです。pull_thresh=24は各ワードの24ビットだけを使うよう指定します。そのため、Python側で色情報を左にシフトしています(sm.put(pixels, 8))。色の順序はRGBではなくGRBです。赤と緑が入れ替わる場合は、これが原因です。24個のLEDを白色で最大輝度にすると最大1.4 Aを消費するため、この例では明るさを20%に制限しています。
Pico 2(RP2350)で何が変わった?
Raspberry Pi Pico 2は、サイズとピン配置をそのままに、RP2350チップを搭載した新世代のボードです。コア数は引き続き2つですが、今回は起動時に2つのArm Cortex-M33コアか、2つのHazard3 RISC-Vコアかを選べます。この記事のコードはすべてPico 2でも変更せずに動きます。MicroPythonの_threadは同じで、Arduino-Picoコアのsetup1()/loop1()とrp2040.fifoも同じ名前のままです。PIOには3つ目のブロックが追加され、ステートマシンは12基(MicroPythonでは0〜11)になり、命令のオプションもいくつか追加されました。
| 特徴 | Pico(RP2040) | Pico 2(RP2350) |
|---|---|---|
| コア | 2 x Cortex-M0+ | 2 x Cortex-M33または2 x Hazard3 RISC-V |
| クロック周波数 | 133 MHz | 150 MHz |
| RAM | 264 KB | 520 KB |
| 内蔵フラッシュ | 2 MB | 4 MB |
| PIO | 2ブロック、8基のステートマシン | 3ブロック、12基のステートマシン |
| 浮動小数点演算 | ソフトウェア処理(高速なROMルーチン) | ハードウェアFPU(単精度)+倍精度コプロセッサー |
| セキュリティ | なし | TrustZone、セキュアブート、8 KB OTP |
| MicroPythonファイル | RPI_PICO (.uf2) | RPI_PICO2 (.uf2) |
実際にPico 2で最も実感しやすい違いは速度です。ハードウェアFPUのおかげで、フィルタ、センサーフュージョン、音声処理など、浮動小数点演算の多い処理が数倍速くなります。また、RAMが2倍になり、大きなディスプレイバッファを確保できます。注意すべきなのは、ボードに合ったUF2ファイルを書き込むことです。Pico用のファイルはPico 2では動きません。初期のRP2350には、内部プルダウンを使う入力に既知のハードウェア問題(E9)があり、A4リビジョンで修正されました。どちらの版でも、ボタンの配線には外付け抵抗を使うのが安全です。
2つのコアが必要なときと、PIOが必要なとき
| やりたいこと | 適した方法 | 理由 |
|---|---|---|
| LEDの明るさ、サーボ、単純な音 | ハードウェアPWM | 専用ブロックがあり、コード1行で使える |
| 少数のセンサー+ディスプレイ+ボタン | 1つのコアとmillis()またはasyncio | 余分に複雑にする必要がない |
| 長い計算中も滑らかなアニメーション | 2つのコア | コア1の計算で画面が固まらない |
| Wi-Fiと並行した連続測定 | 2つのコア | ネットワークの遅延がサンプリング間隔を乱さない |
| WS2812B、追加のUART、赤外線送信機、エンコーダー | PIO | ナノ秒精度で、CPUを自由に使える |
| VGA/DVI映像、I2S音声、超高速信号 | PIO + DMA | CPUでは処理が追いつかないデータ転送速度に対応 |
判断の目安は、処理がソフトウェア上で競合するなら2つ目のコア、ピンにマイクロ秒未満の精度が必要ならPIOです。趣味のプロジェクトの大半では、どちらも必要ありません。それでも、Wi-Fiリクエスト中にWS2812Bのアニメーションがカクついたり、UARTが足りなくなったりしたら、解決策はすでにPicoの中にあります。
PicoのPIOと2つのコアを使うプロジェクトアイデア
- 音楽に反応するLEDリング:コア1がマイクのサンプルを処理し、PIOがWS2812Bリングを駆動し、コア0がボタンを監視します。
- 8チャンネルのパルス発生器:各ステートマシンが異なる周波数の矩形波を生成し、ステッピングモータードライバや試験装置に供給します。
- 欠測のないデータロガー:コア1が10 msごとにADCを読み取り、コア0がWi-Fiでデータを送信します。ネットワークが遅くてもサンプルを失いません。
- 追加のシリアルポート:PIOで3つ目と4つ目のUARTを作り、GPSとBluetoothモジュールを同時に接続します。
- ロータリーエンコーダーのカウンター:PIOがエンコーダーのパルスを1つも取りこぼさずに数え、プロセッサーは結果を読み取るだけです。
- 呼吸するように明滅する常夜灯:滑らかなPWMのフェード処理をコア1に、光センサーの処理をコア0に任せます。
よくある間違いと対処法
| 症状 | 原因 | 対処法 |
|---|---|---|
| 停止後にボードが応答しない | コア1のループがまだ動いている | 停止フラグを使い、必要ならUSBを抜き差しする |
| 2回目のstart_new_threadが失敗する | Picoで使える追加スレッドは1つだけ | 前のスレッドが終了するまで新しいものを起動しない |
| 変数がときどき不正な値になる | 両方のコアがロックなしで共有データに書き込んでいる | allocate_lock()またはFIFOを使う |
| StateMachineでValueErrorが発生する | freqがシステムクロック/65536未満になっている | 周波数を上げ、遅延で速度を落とす |
| PIOASMError: delay too large(遅延が長すぎる) | サイドセットのビットによって遅延フィールドが小さくなった | 遅延を分割するか、nop()を追加する |
| WS2812Bで赤と緑が入れ替わる | 色の順序がGRBになっている | (g << 16) | (r << 8) | bの形式を使う |
| setup1()がまったく動かない | Arduino Mbed RP2040パッケージが選択されている | Earle PhilhowerのRaspberry Pi Pico/RP2040パッケージをインストールする |
2つのコアとPIOは、Picoを単なるLED点滅用ボードから本格的な組み込みシステムへと変えます。ただし、その土台はどれも同じです。ピン、タイミング、LEDをよく理解することです。BlueGraysアプリには、LEDの点滅、流れるLED、ナイトライダー風LED、ボタンで動かすステートマシン、PWMによる呼吸のような明滅など、Raspberry Pi Picoのプロジェクトがあります。すべてに回路図とMicroPythonコード、手順ごとの説明があり、この記事の技術を加えて発展させられます。
よくある質問
Raspberry Pi PicoのPIOとは何ですか?
PIO(プログラマブルI/O)は、RP2040とRP2350に搭載された、プロセッサーから独立して動く小さなステートマシンの集まりです。それぞれが9種類の命令からなるシンプルな言語で書かれたプログラムを、サイクル単位の正確なタイミングで実行します。WS2812B LED、追加のUART、赤外線送信、エンコーダーの読み取りなど、精密なタイミングが必要な処理をCPUを占有せずに行えます。
Raspberry Pi Picoのコア数はいくつですか?
PicoとPico WのRP2040には、2つのCortex-M0+コアがあります。Pico 2のRP2350も2コアで、起動時にCortex-M33かHazard3 RISC-Vのコアを選べます。プログラムの起動時に動くのはコア0だけで、コア1は自分で起動します。
PicoにはPIOステートマシンが何基ありますか?
RP2040には2つのPIOブロックがあり、それぞれに4基、合計8基のステートマシンがあります。各ブロックの32命令分のプログラムメモリは、そのブロック内のステートマシンで共有します。Pico 2のRP2350にはPIOブロックが3つあり、ステートマシンは12基です。
MicroPythonでPicoの両方のコアを使えますか?
はい。Picoでは、_thread.start_new_thread()で起動した関数は2つ目のコアで動きます。同時に使える追加スレッドは1つだけで、共有データは_thread.allocate_lock()で作ったロックで保護してください。MicroPythonのマルチコア対応はまだ実験的な機能のため、追加のスレッドでは大量のメモリを扱う処理を避けるのがよいでしょう。
Arduino IDEでPicoの2つ目のコアを使うには?
Earle PhilhowerのRaspberry Pi Pico/RP2040/RP2350パッケージをインストールしたら、スケッチにsetup1()とloop1()を追加するだけで、コア1が自動的に実行します。コア間ではrp2040.fifo.push()とrp2040.fifo.pop()を使って32ビットの値を受け渡します。ArduinoのMbedベースのRP2040パッケージには、この機能はありません。
WS2812B LEDにはPIOを使う必要がありますか?
いいえ。MicroPython内蔵のneopixelモジュールも、ArduinoのAdafruit NeoPixelライブラリも、Picoで問題なく動きます。PIOで自分で実装する利点は、送信中もプロセッサーを自由に使え、同じ手法をほかの精密なタイミングが必要なプロトコルにも応用できることです。
この記事を読んだ後に作れるプロジェクト
全プロジェクトを見る
Raspberry Pi Picoの内蔵LEDを点滅(PicoとPico W)
Raspberry Pi Picoシリーズの最初のプロジェクトです!ボード上の緑色LEDを点滅させて動作を確認する、定番の「Hello World」に相当する実験です。外付け部品は不要で、PicoとUSBケーブルだけで始められます。コードはMicroPython(Raspberry Pi Foundationが公式にサポートする言語)で書きます。Thonny IDEでmachine.Pinクラス、Pin.OUTモード、led.on()/off()メソッド、time.sleep()を学びます。シリアル(REPL)出力で、切り替わる様子をリアルタイムに確認できます。

Raspberry Pi Picoで外付けLEDを点灯(ブレッドボード)
Picoの内蔵LEDの次は、ブレッドボードに外付けLEDを接続し、GP14ピンで制御します。このプロジェクトでは、Picoの汎用GPIOピンを出力として使う方法、LEDの極性(長い足=アノード/+、短い足=カソード/−)、電流制限抵抗(220Ω)が必要な理由を学びます。抵抗を入れないと、LEDに過大な電流が流れて壊れることがあります。回路はとても簡単です。Pico GP14 → 220Ω抵抗 → LED(+)→ LED(−)→ Pico GND。Wokwiでシミュレーションできます。

Raspberry Pi Picoで信号機(3個のLEDを順に点灯)
赤・黄・緑のLEDで本物の信号機を再現する、Picoシリーズ初の3個のLEDを使うプロジェクトです!緑が4秒間「進め」、黄が1.5秒間「準備」、赤が3秒間「止まれ」、赤と黄が1秒間「発進準備」を示します(ヨーロッパの信号機のパターン)。関数の定義(def all_off())、状態機械の仕組み(順序)、複数のGPIOの同期を学びます。回路はとても簡単です。GP11(緑)、GP12(黄)、GP13(赤)に3個のLEDを接続し、それぞれに220Ωの電流制限抵抗を使います。

Raspberry Pi Picoで流れるLED(6個のLED)
Picoで定番の「流れるLED」アニメーションを、6個のLEDで作ります!6個のLEDが左から右へ順に点灯し、光が列に沿って移動しているように見えます。Pythonのリスト、リスト内包表記([Pin(p, Pin.OUT) for p in LED_PINS])、forループによるピン制御、enumerate()を学びます。GP9-GP14に6個のLEDを接続し、それぞれに220Ωの電流制限抵抗を使います。6個のLEDを1個ずつ定義する代わりにリストを使うため、コードがすっきりします。使用可能な空きGPIOピンの数まで、それぞれに抵抗を付けてLEDを追加できます。

Raspberry Pi Picoでナイトライダー風LED(6個が往復)
Picoで定番の「ナイトライダー」風LED点灯パターンを作ります!6個のLEDが左から右へ、次に右から左へと、点灯を繰り返します。1980年代の人気ドラマに登場する車、KITTのフロントグリルのような動きです。このプロジェクトは「流れるLED」プロジェクトの発展版です。6個のLEDを使う回路(GP9-GP14)は同じで、コードのパターンを変えます。reversed()関数でリストを逆順にたどる方法、2つのforループで往復させる方法、同じ回路で複数のアニメーションを作る方法を学びます。同じハードウェア(LED6個、220Ωの抵抗6個)を使います。

Raspberry Pi Picoで信号機の4モードをボタンで切り替え(ステートマシン)
1個のボタンで信号機の4つのモードを切り替えます!押すたびに、通常サイクル(赤→黄→緑の順に点灯)、赤のみ(停止)、黄色点滅(注意・夜間モード)、消灯(メンテナンス)へ切り替わります。ステートマシン(mode変数で実行する動作を決定)、剰余演算によるモードの循環((mode + 1) % 4で0→1→2→3→0を繰り返す)、処理を止めない時間管理(time.ticks_ms() + time.ticks_diff()でLEDの各段階の時間を管理)を学びます。ただし、モード切り替え時には待ち時間があります。

Raspberry Pi Picoのボタンのチャタリング対策:IRQとソフトウェアポーリングの比較
ボタンを1回押しただけで、コードでは3~4回と数えられますか?原因はチャタリングです。金属接点がマイクロ秒単位で振動します!シリアルモニタで、2つのカウント方法を並べて比較します。未処理のポーリングは振動も検出して多く数え、IRQとソフトウェアによるチャタリング対策は正確に数えます。接点が振動する仕組み、ソフトウェアによる対策(ticks_diff() > 50ms)、Pin.irq()による割り込みの登録、IRQ_FALLINGトリガー、ISRハンドラー、globalとISRの安全な扱い方を学びます。IRQでLEDを切り替え、動作を目で確認できます。シリアルモニタには押すたびに両方のカウントが表示されます。

Raspberry Pi PicoのPWMでLEDをフェード(呼吸のような明滅)
LEDを単に点滅させるだけでなく、呼吸するように明滅させます!Raspberry Pi PicoのPWM(パルス幅変調)を使い、スリープ中のMacBookのランプのように、明るさをゆっくり上げてからゆっくり下げます。machine.PWMクラス、duty_u16()(0–65535の16ビットの明るさ)、PWM周波数(freq(1000)で1秒間に1000回オン・オフし、ちらつきを感じさせない)、range()による段階的なループ(増加と減少)、デジタルピンからのアナログのような出力を学びます。PWMはRGBの混色やモーターなどに使われる、組み込み開発の基本技術です。
ほかの記事
BlueGraysアプリ
この回路をスマホでステップごとに作る
回路図、コード、部品リストを1つの画面で確認できます。Arduino、ESP32、Picoのプロジェクトを200件以上用意しています。

