Руководства · 9 мин чтения

Raspberry Pi Pico: PIO и два ядра — _thread, setup1, WS2812B

Руководство по PIO Raspberry Pi Pico: конечные автоматы, меандр и пример с WS2812B, работа двух ядер через MicroPython _thread и Arduino setup1/loop1, изменения в Pico 2.

Блок PIO Raspberry Pi Pico и два ядра — две особенности, которые отличают эту маленькую плату от большинства микроконтроллеров в её ценовой категории. Микросхема RP2040 в основе Pico содержит два отдельных процессорных ядра и 8 конечных автоматов PIO. Благодаря ядрам можно читать датчики на одном, пока на другом плавно работает дисплей или светодиодная анимация. А PIO — это набор миниатюрных устройств точного формирования временных сигналов, работающих независимо от процессора: они создают необходимый светодиодам WS2812B сигнал с наносекундной точностью, дополнительный UART или меандр, не нагружая CPU.

В этом руководстве ты сначала запустишь оба ядра с помощью модуля _thread в MicroPython, затем с помощью setup1() и loop1() в Arduino IDE. После этого разберёшься, как работает PIO, сколько в нём конечных автоматов и какие команды они понимают, и напишешь две настоящие программы: генератор меандра, играющий ноты на зуммере, и радугу на WS2812B. В последних разделах рассмотрим, что изменилось в Pico 2 (RP2350) и когда эти возможности действительно нужны. Если ты только знакомишься с Pico, рекомендую сначала прочитать «Что такое Raspberry Pi Pico?» и «Что такое MicroPython?».

Отладочная плата Raspberry Pi Pico
Raspberry Pi Pico: микросхема RP2040 в центре содержит два ядра Cortex-M0+ и два блока PIO. Все 26 пинов GPIO можно подключить к PIO.

Что представляют собой два ядра и PIO в Raspberry Pi Pico?

В RP2040 два одинаковых процессора Cortex-M0+, которые называются ядро 0 и ядро 1. При запуске программы работает только ядро 0; ядро 1 спит, пока ты его не запустишь. Оба ядра используют общие 264 КБ ОЗУ и одни и те же пины, поэтому передавать данные между ними легко, но записывать одну переменную одновременно нельзя. Для этого в микросхеме есть небольшая двунаправленная аппаратная очередь (FIFO, по 8 слов в каждом направлении) и 32 аппаратные блокировки (spinlock). PIO — совершенно отдельный мир: каждый из двух блоков PIO содержит 4 конечных автомата, общую память программы на 32 команды и имеет прямой доступ к пинам. После запуска конечный автомат выполняет свою программу такт за тактом, даже когда процессор занят чем-то другим.

РесурсRP2040 (Pico, Pico W)Для чего нужен?
Процессорное ядро2 x Cortex-M0+, 133 MHz (до 200 MHz в актуальной документации)Действительно одновременное выполнение двух отдельных программ
Межъядерная FIFO8 x 32 бита в каждом направленииПередача чисел и команд между ядрами
Аппаратная блокировка (spinlock)32Поочерёдный доступ к общим данным
Блок PIO2 (PIO0, PIO1)Перенос работы с пинами, требующей точного времени, с процессора
Конечный автомат4 на блок, всего 8Каждый выполняет собственную программу PIO
Память программы PIO32 команды на блокОбщая для 4 конечных автоматов этого блока
FIFO PIOTX 4 + RX 4 слова на автомат (8 при объединении)Обмен данными между процессором и конечным автоматом

Второе ядро в MicroPython: модуль _thread

В MicroPython для использования ядра 1 служит вызов _thread.start_new_thread(function, args). На Pico он запускает функцию на втором ядре, а основная программа продолжает работать на ядре 0. Одновременно может работать только один дополнительный поток, ведь третьего ядра нет. В примере ниже ядро 0 считывает потенциометр на GP26 и выводит результат, а ядро 1 мигает встроенным светодиодом со скоростью, заданной потенциометром. Ожидание sleep_ms() на ядре 1 никак не влияет на ядро 0; на одном ядре для этого понадобился бы таймер или asyncio.

MicroPython
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: к общей переменной обращается только одно ядро.
  • Флаг running сообщает ядру 1, что нужно выйти из цикла, когда ты останавливаешь программу сочетанием Ctrl+C; иначе светодиод продолжит мигать.
  • Запись Pin("LED") находит встроенный светодиод и на Pico, и на Pico W; на Pico W светодиод подключён к микросхеме беспроводной связи, поэтому Pin(25) не работает.
  • Удобно поручить ядру 1 долгие вычисления, отрисовку дисплея или точную работу со временем, а ядру 0 — Wi-Fi, меню и взаимодействие с пользователем.
Кнопка «Стоп» (Stop) в Thonny прерывает программу только на ядре 0. Если на ядре 1 продолжает работать бесконечный цикл, плата выглядит занятой и новый код не загружается. В этом случае отключи и снова подключи USB-кабель или сбрось плату, кратковременно соединив пин RUN с GND. Всегда используй флаг остановки, как в примере, и избегай интенсивного выделения памяти (больших списков, объединения длинных строк) в дополнительном потоке; поддержка нескольких ядер в MicroPython всё ещё считается экспериментальной.

Два ядра в Arduino IDE: setup1() и loop1()

Самый распространённый пакет для Pico в Arduino IDE — ядро Raspberry Pi Pico/RP2040/RP2350 от Earle Philhower (arduino-pico). Использовать с ним оба ядра удивительно просто: привычные setup() и loop() работают на ядре 0; если добавить в скетч setup1() и loop1(), ядро 1 автоматически запустится и будет выполнять эти две функции. Данные между ядрами передаются через объект rp2040.fifo. Официальный пакет Arduino «Arduino Mbed OS RP2040» не поддерживает setup1(), поэтому проверь, что в «Менеджере плат» (Boards Manager) выбран нужный пакет.

C++ (Arduino IDE)
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()Ненадолго приостанавливает и возобновляет работу другого ядра (например, при записи во флеш-память)
Не обращайся к одному аппаратному устройству — последовательному порту, дисплею или шине SPI — с обоих ядер одновременно. Как в примере, закрепи каждое периферийное устройство за одним ядром, а другое пусть передаёт ему числа через FIFO. Так ты избежишь перемешанного текста и испорченного изображения без блокировок.

Что такое PIO? Как работает программируемый ввод-вывод

В большинстве микроконтроллеров блоки связи и формирования сигналов, такие как UART, SPI, I2C и ШИМ, жёстко заданы в кремнии; если нужен протокол, которого в списке нет, процессор реализует его, поочерёдно включая и выключая пины (bit-banging). Это занимает процессор, а любое прерывание нарушает временные интервалы. PIO (Programmable Input/Output, программируемый ввод-вывод) решает проблему в корне: каждый конечный автомат — крошечный процессор с девятью командами, предназначенными только для работы с пинами. Каждая команда выполняется ровно за один такт, а в конце к ней можно добавить задержку от 0 до 31 такта. Поэтому, просто считая такты, ты точно знаешь время каждого фронта сигнала в наносекундах. Скорость конечного автомата задаётся делением системной тактовой частоты на коэффициент от 1 до 65 536.

КомандаНазначение
set(pins, v)Записывает константу 0–31 в пины или регистры X/Y
out(dest, n)Переносит n бит из выходного сдвигового регистра (OSR) на пины или в X/Y
in_(src, n)Сдвигает n бит с пинов или из регистров во входной сдвиговый регистр (ISR)
pull() / push()Забирает слово из TX FIFO в OSR, отправляет слово из ISR в RX FIFO
jmp(cond, label) (команда условного перехода)Выполняет переход по условию (X равен нулю, высокий уровень пина, X-- и так далее)
wait(polarity, src, idx) (команда ожидания)Ждёт, пока пин или флаг IRQ примет нужный уровень
mov(dest, src) (команда копирования между регистрами)Копирует данные между регистрами, при необходимости инвертируя биты
irq(idx)Передаёт флаг прерывания CPU или другому конечному автомату
nop() и .side(v)Пустая команда; side-set изменяет состояние пина в том же такте, что и любая команда

В MicroPython программы PIO — это функции Python с декоратором @rp2.asm_pio; функция не вызывается, а ассемблируется MicroPython в машинный код. Затем ты загружаешь её в конечный автомат через rp2.StateMachine(id, program, freq=..., set_base=...). На Pico автоматы с номерами 0–3 относятся к PIO0, а 4–7 — к PIO1. Значение freq — число тактов выполнения команд в секунду, и оно не может быть ниже 1/65536 системной частоты; при 125 MHz это около 1,9 kHz, при 150 MHz — около 2,3 kHz. Если нужен более медленный сигнал, используй задержки или циклы со счётчиками X/Y.

Меандр с PIO: твоя первая программа конечного автомата

В первом примере сыграем звукоряд на пассивном зуммере, подключённом к GP15, а затем оставим непрерывный меандр частотой 1 kHz. В программе всего две команды: установить HIGH на пине и подождать 9 тактов, установить LOW на пине и подождать 9 тактов. Поскольку сама каждая команда занимает 1 такт, один период составляет 20 тактов, поэтому достаточно задать конечному автомату частоту в 20 раз выше нужной частоты звука. Подключи положительный вывод зуммера к GP15, а отрицательный — к GND. Вместо зуммера можно использовать осциллограф или светодиод с резистором на 220 Ω; подбери резистор с помощью калькулятора резистора для светодиода.

MicroPython
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, что намного выше нижнего предела. Если убрать задержки и оставить только две команды set(), один период сократится до 2 тактов; тогда при частоте автомата 125 MHz на пине получится меандр 62,5 MHz. Помни, что аппаратная ШИМ может делать то же самое: для простого звука или яркости светодиода достаточно ШИМ. Отличие PIO в том, что точную форму сигнала определяешь ты; несколькими строками можно создать импульсы переменной ширины, точное число импульсов или выходные сигналы, реагирующие на вход.

Управление светодиодами WS2812B (NeoPixel) через PIO

RGB-кольцо WS2812B из 24 светодиодов
Кольцо WS2812B из 24 светодиодов: внутри каждого светодиода свой контроллер, который получает цвет в виде 24 бит по одной линии данных.

Светодиоды WS2812B ожидают импульс длительностью около 1,25 микросекунды на бит; будет ли бит равен 1 или 0, определяется длительностью уровня HIGH в импульсе. Это классическая задача для PIO. Программа ниже основана на официальном примере MicroPython от Raspberry Pi: конечный автомат работает на 8 MHz, а каждый бит занимает ровно 10 тактов, поэтому в секунду передаётся 800 000 бит. Благодаря autopull автомат забирает новый цвет из FIFO каждые 24 бита; процессор лишь помещает массив в FIFO через sm.put(). Измени число светодиодов под свою ленту; о принципе работы WS2812B и порядке цветов читай в руководстве по WS2812B NeoPixel.

Пин WS2812BПин PicoПримечание
DIN (вход данных)GP16 (физический пин 21)Последовательный резистор на 330 Ω защищает сигнальную линию
5V / VCCVBUS (пин 40)5 V от USB; для большого числа светодиодов используй внешний источник 5 V
GNDGND (например, пин 38)GND внешнего источника тоже нужно подключить сюда
MicroPython
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

Разберём программу построчно. out(x, 1) берёт один бит из OSR в регистр X, а side-set удерживает линию в LOW (3 такта). Затем линия переходит в HIGH и проверяется X (2 такта). Если бит равен 1, линия остаётся в HIGH ещё 5 тактов; если он равен 0, линия переводится в LOW и автомат ждёт 5 тактов. Оба пути в сумме занимают 10 тактов, то есть 1,25 µs. pull_thresh=24 указывает автомату использовать только 24 бита каждого слова, поэтому на стороне Python цвет сдвигается влево (sm.put(pixels, 8)). Порядок цветов — GRB, а не RGB; если красный и зелёный поменялись местами, причина в этом. Поскольку 24 светодиода на полной яркости белого могут потреблять до 1,4 A, в примере яркость ограничена 20%.

Если нужно просто зажечь светодиоды, подойдёт и встроенный модуль MicroPython neopixel: np = neopixel.NeoPixel(Pin(16), 24), np[0] = (255, 0, 0), np.write(). Преимущество собственной версии на PIO в том, что процессор остаётся свободным во время передачи, а тот же приём можно адаптировать к другим протоколам.

Что изменилось в Pico 2 (RP2350)?

Raspberry Pi Pico 2 — новое поколение с теми же размерами и распиновкой, но с микросхемой RP2350 внутри. Ядер по-прежнему два, но теперь при загрузке можно выбрать два ядра Arm Cortex-M33 или два ядра Hazard3 RISC-V. Весь код из этой статьи работает на Pico 2 без изменений: _thread в MicroPython тот же, а в ядре Arduino-Pico setup1()/loop1() и rp2040.fifo сохраняют прежние имена. В PIO добавлен третий блок, поэтому конечных автоматов стало 12 (0–11 в MicroPython), а также появились новые варианты команд.

ХарактеристикаPico (RP2040)Pico 2 (RP2350)
Ядра2 x Cortex-M0+2 x Cortex-M33 или 2 x Hazard3 RISC-V
Тактовая частота133 MHz150 MHz
ОЗУ264 КБ520 КБ
Флеш-память на плате2 МБ4 МБ
PIO2 блока, 8 конечных автоматов3 блока, 12 конечных автоматов
Плавающая точкаПрограммно (быстрые процедуры в ПЗУ)Аппаратный FPU (одинарная точность) + сопроцессор двойной точности
БезопасностьНетTrustZone, безопасная загрузка, 8 КБ OTP
Файл MicroPythonRPI_PICO (.uf2)RPI_PICO2 (.uf2)

На практике самое заметное отличие Pico 2 — скорость: благодаря аппаратному FPU задачи с большим числом вычислений с плавающей точкой (фильтры, объединение данных датчиков, обработка звука) выполняются в несколько раз быстрее, а удвоенный объём ОЗУ оставляет место для больших буферов дисплея. Важно загрузить правильный UF2-файл для платы: файл для Pico не работает на Pico 2. В ранних версиях RP2350 была известная аппаратная проблема (E9) со входами, использующими встроенную подтяжку к земле, которую исправили в ревизии A4; внешний резистор при подключении кнопок — безопасный выбор в любом случае.

Когда нужны два ядра, а когда PIO?

Что нужноЛучший вариантПочему?
Яркость светодиода, сервопривод, простой звукАппаратная ШИМГотовый блок, одна строка кода
Несколько датчиков + дисплей + кнопкаОдно ядро, millis() или asyncioДополнительная сложность не нужна
Плавная анимация во время долгих вычисленийДва ядраВычисления на ядре 1 не замораживают дисплей
Непрерывные измерения одновременно с Wi-FiДва ядраСетевые задержки не нарушают интервал дискретизации
WS2812B, дополнительный UART, ИК-передатчик, энкодерPIOНаносекундная точность, CPU остаётся свободным
Видео VGA/DVI, звук I2S, очень быстрые сигналыPIO + DMAСкорость передачи данных, с которой CPU не справится

Итак, о втором ядре стоит подумать, когда задачи мешают друг другу в программе, а о PIO — когда пинам нужна точность меньше микросекунды. В большинстве любительских проектов не нужно ни то ни другое; но если однажды анимация WS2812B начнёт дёргаться во время Wi-Fi-запроса или на плате закончатся UART, решение уже есть внутри Pico.

Идеи проектов с PIO и двумя ядрами Pico

  • Светодиодное кольцо, реагирующее на музыку: ядро 1 обрабатывает отсчёты микрофона, PIO управляет кольцом WS2812B, а ядро 0 опрашивает кнопки.
  • Восьмиканальный генератор импульсов: каждый конечный автомат создаёт меандр на своей частоте для драйверов шаговых двигателей или испытательных стендов.
  • Регистратор данных без пропусков: ядро 1 считывает АЦП каждые 10 мс, а ядро 0 отправляет данные по Wi-Fi; медленная сеть не приводит к потере ни одного отсчёта.
  • Дополнительные последовательные порты: создай третий и четвёртый UART с помощью PIO и подключи GPS и Bluetooth-модуль одновременно.
  • Счётчик поворотного энкодера: PIO считает импульсы энкодера, не пропуская ни одного; процессор только читает результат.
  • Ночник с эффектом дыхания: поручить плавное изменение яркости через ШИМ можно ядру 1, а датчик освещённости — ядру 0.

Частые ошибки и способы их устранения

СимптомПричинаРешение
Плата не отвечает после остановкиЦикл на ядре 1 продолжает работатьИспользуй флаг остановки; при необходимости отключи и снова подключи USB
start_new_thread не срабатывает во второй разPico допускает только один дополнительный потокНе запускай новый, пока не завершится предыдущий
Переменные иногда получают бессмысленные значенияОба ядра записывают общие данные без блокировкиИспользуй allocate_lock() или FIFO
StateMachine вызывает ValueErrorfreq ниже системной частоты / 65536Повысь частоту и замедли выполнение задержками
PIOASMError: delay too large (сообщение об ошибке: слишком большая задержка)Биты side-set сократили поле задержкиРаздели задержку или добавь nop()
Красный и зелёный на WS2812B поменялись местамиПорядок цветов — GRBИспользуй формат (g << 16) | (r << 8) | b
setup1() никогда не выполняетсяВыбран пакет Arduino Mbed RP2040Установи пакет Raspberry Pi Pico/RP2040 от Earle Philhower
При написании программы PIO записывай число тактов каждой команды и проверяй, что сумма соответствует нужному времени. Если есть осциллограф или недорогой логический анализатор — отлично; иначе временно снизь частоту конечного автомата до 2–3 kHz и наблюдай за пином через светодиод, чтобы увидеть ошибки своими глазами.

Два ядра и PIO превращают Pico из простой мигалки в серьёзную встраиваемую систему, но всё опирается на одну основу: хорошее понимание пинов, временных интервалов и светодиодов. В приложении BlueGrays ты найдёшь проекты для Raspberry Pi Pico: мигание светодиодом, бегущие огни, эффект Knight Rider, конечный автомат с управлением кнопкой и эффект дыхания на ШИМ. Каждый проект содержит схему и код MicroPython, разобран пошагово и готов к добавлению приёмов из этой статьи.

Частые вопросы

Что такое PIO в Raspberry Pi Pico?

PIO (программируемый ввод-вывод) — набор небольших конечных автоматов в микросхемах RP2040 и RP2350, работающих независимо от процессора. Каждый выполняет программу на простом языке из девяти команд с точностью до такта. PIO берёт на себя задачи, требующие точного времени: управление светодиодами WS2812B, дополнительные UART, ИК-передачу или чтение энкодера, не занимая CPU.

Сколько ядер у Raspberry Pi Pico?

Микросхема RP2040 на Pico и Pico W имеет два ядра Cortex-M0+. RP2350 на Pico 2 тоже имеет два ядра, а при загрузке можно выбрать между Cortex-M33 и Hazard3 RISC-V. При запуске программы работает только ядро 0; ядро 1 ты запускаешь самостоятельно.

Сколько конечных автоматов PIO у Pico?

В RP2040 два блока PIO по 4 конечных автомата, всего 8. Память программы каждого блока на 32 команды используется совместно автоматами этого блока. В RP2350 на Pico 2 три блока PIO, то есть 12 конечных автоматов.

Может ли MicroPython использовать оба ядра Pico?

Да. На Pico функция, запущенная через _thread.start_new_thread(), работает на втором ядре. Одновременно может быть запущен только один дополнительный поток, а общие данные нужно защищать блокировкой из _thread.allocate_lock(). Поддержка нескольких ядер в MicroPython всё ещё экспериментальная, поэтому в дополнительном потоке лучше избегать интенсивной работы с памятью.

Как использовать второе ядро Pico в Arduino IDE?

После установки пакета Raspberry Pi Pico/RP2040/RP2350 от Earle Philhower просто добавь в скетч функции setup1() и loop1(); ядро 1 выполнит их автоматически. 32-битные значения передаются между ядрами через rp2040.fifo.push() и rp2040.fifo.pop(). У пакета RP2040 от Arduino на основе Mbed этой возможности нет.

Обязательно ли использовать PIO для светодиодов WS2812B?

Нет. И встроенный модуль neopixel в MicroPython, и библиотека Adafruit NeoPixel в Arduino хорошо работают на Pico. Преимущество собственной реализации на PIO в том, что процессор остаётся свободным во время передачи, а тот же приём можно применить к другим протоколам, требующим точного времени.

Все проекты
Мигание встроенным светодиодом Raspberry Pi Pico (Pico и Pico W)
#147Новичок

Мигание встроенным светодиодом Raspberry Pi Pico (Pico и Pico W)

Первый проект серии Raspberry Pi Pico! Мигаем зелёным встроенным светодиодом, чтобы проверить, что плата работает, — классический первый опыт «Hello World». НИКАКИХ внешних компонентов, только Pico и USB-кабель. Код написан на MicroPython (официальном языке, поддерживаемом Raspberry Pi Foundation). Изучи в Thonny IDE класс machine.Pin, режим Pin.OUT, методы led.on()/off() и time.sleep(). Каждое переключение видно в выводе (REPL).

5.0(1)
10
Raspberry Pi Pico: управление внешним светодиодом (макетная плата)
#148Новичок

Raspberry Pi Pico: управление внешним светодиодом (макетная плата)

Следующий шаг после встроенного светодиода Pico: подключить внешний светодиод на макетной плате и управлять им через пин GP14. В этом проекте ты узнаешь, как использовать любой пин GPIO общего назначения у Pico как выход, какая полярность у светодиода (длинная ножка = анод/+, короткая = катод/–) и зачем нужен токоограничивающий резистор (220Ω). Без резистора через светодиод пойдёт слишком большой ток, и он может сгореть. Схема очень простая: Pico GP14 → резистор 220Ω → светодиод → GND.

4.5(2)
7
Симулятор светофора на Pico (3 светодиода по очереди)
#150Новичок

Симулятор светофора на Pico (3 светодиода по очереди)

Настоящий светофор на красном, жёлтом и зелёном светодиодах — первый проект серии Pico с 3 светодиодами! Зелёный 4 с «едем», жёлтый 1,5 с «внимание», красный 3 с «стоп» и красный + жёлтый 1 с «приготовиться» (европейская схема светофора). Изучи объявление функций (def all_off()), логику конечного автомата (последовательность) и синхронизацию нескольких GPIO. Простая схема: 3 светодиода на GP11 (зелёный), GP12 (жёлтый), GP13 (красный), у каждого токоограничивающий резистор 220Ω.

4.0(1)
12
Бегущие огни на Pico (6 светодиодов)
#153Новичок

Бегущие огни на Pico (6 светодиодов)

Классическая анимация «бегущие огни» на Pico — версия на 6 светодиодов! 6 светодиодов загораются по очереди слева направо — будто свет идёт по ряду. Изучи список Python, генератор списка ([Pin(p, Pin.OUT) for p in LED_PINS]), цикл for с управлением пинами и enumerate(). 6 светодиодов на GP9–GP14, у каждого токоограничивающий резистор 220Ω. Очень чистый код: вместо объявления 6 светодиодов по одному используется список, и можно добавлять светодиоды по числу свободных пинов GPIO.

0.0(0)
8
Сканер Knight Rider на Pico (6 светодиодов туда-обратно)
#154Новичок

Сканер Knight Rider на Pico (6 светодиодов туда-обратно)

Классический эффект светодиодного сканера Knight Rider на Pico! 6 светодиодов бегут слева направо, затем справа налево, и так бесконечно. Как передняя решётка машины KITT из культового сериала 80-х. Этот проект — доработка проекта «Бегущие огни»: та же схема из 6 светодиодов (GP9–GP14), но другой узор в коде. Изучи функцию reversed() для разворота списка, два отдельных цикла for для движения туда-обратно и несколько анимаций на одной схеме.

0.0(0)
13
Светофор на Pico с выбором 4 режимов кнопкой (конечный автомат)
#158Средний

Светофор на Pico с выбором 4 режимов кнопкой (конечный автомат)

4 режима светофора всего одной кнопкой! Каждое нажатие меняет режим: обычный цикл (красный → жёлтый → зелёный по очереди), только красный (сигнал стоп), мигающий жёлтый (внимание/ночной режим) и выключено (обслуживание). Понятия: конечный автомат (переменная mode определяет, какое поведение выполняется), цикл режимов по модулю ((mode + 1) % 4 → 0→1→2→3→0), неблокирующий тайминг (фазы светодиодов отсчитываются через time.ticks_ms() + time.ticks_diff()).

5.0(1)
15
Антидребезг кнопки на Pico: IRQ против программного опроса
#159Средний
Premium

Антидребезг кнопки на Pico: IRQ против программного опроса

Нажимаешь кнопку один раз, а код насчитывает 3–4? Это дребезг контактов — металлические контакты колеблются доли миллисекунды! Сравни в мониторе два способа подсчёта рядом: сырой опрос (ловит дребезг и считает лишнее) и IRQ + программный антидребезг (точно). Изучи физику дребезга, программный антидребезг (ticks_diff() > 50ms), регистрацию прерывания Pin.irq(), триггер IRQ_FALLING, обработчики ISR и безопасность global в ISR.

0.0(0)
12
Плавное затухание светодиода с PWM на Pico (эффект дыхания)
#160Новичок

Плавное затухание светодиода с PWM на Pico (эффект дыхания)

Светодиод больше не просто мигает — он дышит! С помощью PWM (широтно-импульсной модуляции) Raspberry Pi Pico яркость светодиода медленно растёт и медленно падает, как огонёк спящего MacBook. Изучи класс machine.PWM, duty_u16() (16-битная яркость 0–65535), частоту PWM (freq(1000) — 1000 циклов вкл/выкл в секунду, мерцания не видно), циклы range() с шагом (вверх и вниз) и почти аналоговый выход с цифрового пина.

0.0(0)
4