Raspberry Pi Pico — Arduino на стероидах
- Цена: €4,13 плюс доставка €0,61
- RP: Raspberry Pi
- 2: два ядра
- 0: ядра M0+
- 4: минимум 256 КБ памяти
- 0: нет встроенной флеш-памяти
- Два ядра Arm Cortex-M0+ @ 133 МГц
- 264 КБ памяти (на самом деле 284 КБ, но часть используется для кеширования и USB)
- Поддержка до 16МБ внешней флеш-памяти с QSPI интерфейсом, реально на платке запаяно 2МБ
- DMA контроллер
- 4 x 12-разрядных аналоговых входа (на Pico доступно для пользователя 3 из них)
- 2 × UART
- 2 × SPI
- 2 × I2C
- 16 × PWM каналов
- Встроенный сенсор температуры
- Всего 30 GPIO пинов
- USB 1.1 контроллер с поддержкой хоста
- 2 × PIO блока для своих собственных интерфейсов
- 2 x PLL (один для USB, второй для остального)
- Поддержка UF2 для загрузки программ
- Поддержка SWD для загрузки и отладки
- Поддержка спящих режимов и пониженной частоты для снижения потребления
- ARM Limited (M0+, UART, SPI)
- Synopsys, Inc. (I2C, SSI)
- Taiwan Semiconductor Manufacturing Company Limited (TSMC) (standard cells, memories)
- Dolphin Design SAS (Voltage Regulator, Power-on Reset/Brown-out Detector)
- Aragio Solutions (GPIO and Crystal Pad library)
- Silicon Creations (PLL)
- GF Micro (ADC, TS, USB PHY)
Во времена моей молодости говорили — легко, как два байта переслать. Другие — чуть по-другому — легко, как два пальца об асфальт, подозревая под этим нечто третье.
Не так давно Raspberry Pi Foundation выкатил собственный микроконтроллер, за рекордно низкую по нынешним временам цену для устройства с такими параметрами — сама микросхема стоит 1 доллар. Сейчас мы будем разбираться, легко ли в этом микроконтроллере два байта переслать.
Настоящие специалисты, которые в комментариях уже не раз меня называли ардуинщиком-недоучкой, в этом опусе вряд ли найдут что-то полезное, а остальные — добро пожаловать.
В начале было слово была платка от Raspberry на базе этого микроконтроллера под названием Raspberry Pi Pico, на котором были только сам микроконтроллер, 2 мегабайта флеш-памяти, светодиод, кварц, микро USB разъем и кнопка перехода в режим загрузки. И все это с объявленной ценой без налогов и пересылки в 4 USD.
Потом поперло — таких платок разного вида просто понесло потоком — и уменьшенных размеров, и с кучей разной периферии. Очевидно, очень скоро этот микроконтроллер заинтересовал ардуинщиков и его поддержка была включена в длинный список поддерживаемых микроконтроллеров.
Теперь лирическое отступление.
Для начала, что за мелкоконтроллер на платке?
Обозвали его RP2040, что по мнению Raspberry Pi Foundation, обозначает:
Что мы имеем внурях:
В списке отсутствует еще один вид памяти — 16МБ, масочная или однократная — не знаю. Там хранится программное обеспечение USB, загрузчики и библиотека для работы с плавающей запятой.
Откуда надерганы IP (Intellectual property — так в мире проектирования микросхем так называют готовые блоки-библиотеки)
С таймерами, на первый взгляд, большая напряженка — единственные таймер общего назначения с очень маленьким набором функций. Впридачу к нему имеются WDT и RTC.
Но это только на первый взгляд — 16 PWM, собственные таймеры у контроллера DMA и программируемые машины состояний меняют дело кардинально.
Микропроцессор, на самом деле, очень необычен именно в плане периферии. Главная фишка — это блоки программируемого ввода вывода, PIO, каждый из которых содержит 4 машины состояний 8 32-разрядных FIFO регистров, память программ. Каждый из таких блоков программируется на собственном ассемблере. С помощью PIO вы можете добавить недостающий интерфейс или сделать совершенно новый — например, аппаратный интерфейс для WS8212 или что вам в голову придет.
Меня терзают смутные сомнения, что часть аппаратных интерфейсов этого микропроцессора именно так и сделаны — абсолютно такие же PIO, только вместо памяти программ с возможностью записи, там применена масочная память. Или OTP, которая позволяет менять конфигурацию микропроцессора уже на существующем кремнии.
При таком подходе разработка сильно упрощается. И я подозреваю, что, в основном, все было разработано одним человеком в течении примерно года.
Товарищ Сталин говорил — «У каждой ошибки есть имя и фамилия»
Речь не об ошибке, но тем не менее фамилия быть должна. К сожалению, не нашел. Но, скорее всего, зовут его Graham. И проектировал он на Verilog. У меня есть один знакомый — бывший коллега, который такую вещь вполне мог бы спроектировать за несколько месяцев, но это явно не он.
Небольшую статейку от человека, который вертелся рядом с разработчиком, можно найти здесь.
Он пишет, что проектирование начали в середине 2017 года. Процессор поступил в продажу в начале 2021 года. Год-полтора, наверно, ушло на запуск производства и тестирование. Значит, на проектирование ушло еще полтора-два года. Если, конечно, кремний пошел с первой попытки.
Если кому интересно, можно посмотреть картину по слоям, как делают транзистор
Вы все поняли и решили, что вы будете первым, кто сделает микросхему на кухне? К сожаление, вы опоздали, такой деятель уже нашелся, вы будете не первым 🙂
Но еще можете свой процессор слепить, вот RP2040. Как видите, все очень просто, большую часть площади занимает RAM.
Здесь можно посмотреть картинку получше.
По словам руководства Raspberry Pi, выход годных микроконтроллеров с пластины около 20 000 штук. Пластина по 40-нм технологии в TSMC стоит около 2300 долларов. Партия, очевидно, не одна штука.
Можете считать барыши 🙂 Но не забудьте прибавить стоимость тестирования, резки, корпусирования. А еще за подготовку к производству надо заплатить и разработчики тоже кушать хотят.
Ну ладно, с присказкой закончим.
При первом включении плата распознается, как USB диск, для того, чтобы запустить свою программу, достаточно скомпилированный файл в формате uf2 закинуть на этот диск, выполнение начнется немедленно.
При последующих включениях будет запущена именно эта программа. Чтобы загрузить новую программу, нужно перейти в режим загрузки снова, для этого нужно отключить плату от USB, зажать кнопку BOOTSEL, подключить платку к USB и затем отпустить кнопку. Процедура нудная, и разъем недолго продюжит, если вы отлаживаете свою программу.
На этом сайте вы найдете программу, которая приводит все в привычное для ардуино представление — достаточно загрузить программу один раз. После этого среда ардуино уже будет компилировать вашу программу в в формате uf2, загрузчик будет переводить плату в режим mass storage device и закидывать ваш файл на образовавшуюся флешку. Ваша программа сразу получит управление — никаких лишних телодвижений, все очень привычно.
Мне удавалось несколько раз убить загрузчик — но ничего страшного, опять переходим в режим загрузки и закидываем файл и продолжаем, как ни в чем не бывало.
Если что-то сильно накосячили и ничего и никак не работает (но BOOTSEL работает) — нужно загрузить файл flash_nuke.uf2 который вычистит флеш и вернет все в первоначальное состояние. SWD мы не будем обсуждать во избежания лишнего усложнения.
Я не ортодоксальный ардуинщик, поэтому предпочитаю использовать PlatformIO. Если честно, я никогда не использовал Arduino IDE для разработки своих программ, только загрузить что-то готовое. Кто пытался написать что-то более-менее сложное в Arduino IDE понимает, что это разновидность мазохизма. Хотя версия IDE 2.х выглядит уже гораздо лучше и похоже на настоящий редактор. Даже отладку позволяет — конечно, для тех контроллеров, где это предусмотрено.
Платка получена, подпаиваем к ней гребенку выводов и кнопку аппаратного сброса — ну что эта за адруина и чтобы без кнопки сброса?
Для начала просто их примеров загружаем blink — светодиод на плате мигает. Удивительно, но факт.
Теперь бы пару байтов на выход переслать. И вот тут — засада. У большинства процессоров — это самая простая операция, что можно придумать. А тут управление заточено на управления битами. Каждый бит обслуживается 2 регистрами по 32 бита. 64 бита на одну ножку, Карл! И это еще далеко не все, прерывания — это отдельная песня, в итоге — читать документацию вспотеешь. Если еще учесть, что она сырая и что-то понять и найти концы очень сложно.
В итоге все-таки находится регистр, который можно зацепить к выводам и переслать нашу пару байтов.
Но выглядит это так:
void setup()
{
for (uint8_t pin = 0; pin < 16; pin++)
{
uint32_t *PIN_CTRL_REG = (uint32_t*)IO_BANK0_BASE + pin * 2 + 1;
*PIN_CTRL_REG = 5; // 5 = SIO function
}
sio_hw->gpio_oe = 0xFFFF;
sio_hw->gpio_out = 0x05555;
}
void loop()
{
sleep_us(1);
sio_hw->gpio_togl = 0xFFFF;
}
sio_hw->gpio_togl — это не регистр вывод, а исключающее или, полезная штука.
Все, кушать подано, садитесь жрать, пожалуйста.
А теперь бы все это сделать побыстрее — выбросить из памяти прямо на выход, используя прямой доступ в память. Надо с DMA разбираться, однако. Для начала упрощенный вариант — из памяти в память.
Разобраться с регистрами позднее будем, а пока вызываем функции богомерзких библиотек
#include <Arduino.h>
#include "hardware/dma.h"
#define LED_BUILTIN 25
const char src[] = "Hello, world! (from DMA)";
char dst[count_of(src)];
void hello_dma(void)
{
// Get a free channel, panic() if there are none
int chan = dma_claim_unused_channel(true);
dma_channel_config c = dma_channel_get_default_config(chan);
channel_config_set_transfer_data_size(&c, DMA_SIZE_8);
channel_config_set_read_increment(&c, true);
channel_config_set_write_increment(&c, true);
dma_channel_configure(
chan, // Channel to be configured
&c, // The configuration we just created
dst, // The initial write address
src, // The initial read address
count_of(src), // Number of transfers; in this case each is 1 byte.
true // Start immediately.
);
}
void setup()
{
Serial.begin(115200);
while (!Serial);
pinMode(LED_BUILTIN, OUTPUT);
hello_dma();
dma_channel_wait_for_finish_blocking(chan);
Serial.print(src);
Serial.print(" -> ");
Serial.println(dst);
}
void loop()
{
digitalWrite(LED_BUILTIN, HIGH);
delay(500);
digitalWrite(LED_BUILTIN, LOW);
delay(500);
}
Все работает и копируется. Дело за малым — в качестве места, куда данные копируются, нужно определить, как регистр sio_hw→gpio_out
Ну, размечтался! А накусь выкусь!
Оказывается, в этот регистр DMA просто не умеет писать!
А что делать? Raspberry рекомендует написать программку буквально из одной строчки для программируемого ввода-вывода, писать свои данные в него, а так программа уже все сама передаст на выход.
The CortexM0+ SIO address space is not accessible from the bus matrix and thus you can’t DMA to it.
If you want to DMA to the pins, then make a one line PIO program that outputs data to the pins, and DMA to the PIO SM.
Вот это номер, особенно учитывая, что разобраться с программированием этого ввода-вывода не так просто — документация не самая лучшая. К одном месте кто-то обращался в поддержке — дескать, в вашей документации заявлена поддержка последовательных интерфейсов. А пример можно? Ответ был каким-то не очень дружелюбным — отойди, мальчик, не мешай работать. Как руки дойдут — так и сделаем.
— Спасибо, красивое ©.
Встроенной в ардуино поддержки ассемблера пока нет (для Python — есть). Но есть исходный код ассемблера с SDK
Чай не баре — берите, компилируйте и пользуйтесь на доброе здоровье.
Для совсем ленивых — нашелся добрый человек, сделал онлайн компилятор.
Начнем разбираться — собственно, на этом я собираюсь и закончить с обзором программного обеспечения. Пример выдран отсюда, все лишнее выброшено, осталась только суть
Итак, пишем (только не спрашивайте, что это значит) 🙂
Это наш ws8212_single.pio файл, этот текст просто в левую часть онлайн компилятора вставить надо
ws8212_single.pio
.program ws2812
.side_set 1
.define public T1 2
.define public T2 5
.define public T3 3
.lang_opt python sideset_init = pico.PIO.OUT_HIGH
.lang_opt python out_init = pico.PIO.OUT_HIGH
.lang_opt python out_shiftdir = 1
.wrap_target
bitloop:
out x, 1 side 0 [T3 - 1] ; Side-set still takes place when instruction stalls
jmp !x do_zero side 1 [T1 - 1] ; Branch on the bit we shifted out. Positive pulse
do_one:
jmp bitloop side 1 [T2 - 1] ; Continue driving high, for a long pulse
do_zero:
nop side 0 [T2 - 1] ; Or drive low, for a short pulse
.wrap
% c-sdk {
#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
%}
То, что появится в правой части, нужно скопировать и вставить в свой файл ws2812.pio.h в ту же папку, где исходник теста
ws2812.pio.h
// -------------------------------------------------- //
// This file is autogenerated by pioasm; do not edit! //
// -------------------------------------------------- //
#pragma once
#if !PICO_NO_HARDWARE
#include "hardware/pio.h"
#endif
// ------ //
// ws2812 //
// ------ //
#define ws2812_wrap_target 0
#define ws2812_wrap 3
#define ws2812_T1 2
#define ws2812_T2 5
#define ws2812_T3 3
static const uint16_t ws2812_program_instructions[] = {
// .wrap_target
0x6221, // 0: out x, 1 side 0 [2]
0x1123, // 1: jmp !x, 3 side 1 [1]
0x1400, // 2: jmp 0 side 1 [4]
0xa442, // 3: nop side 0 [4]
// .wrap
};
#if !PICO_NO_HARDWARE
static const struct pio_program ws2812_program = {
.instructions = ws2812_program_instructions,
.length = 4,
.origin = -1,
};
static inline pio_sm_config ws2812_program_get_default_config(uint offset) {
pio_sm_config c = pio_get_default_sm_config();
sm_config_set_wrap(&c, offset + ws2812_wrap_target, offset + ws2812_wrap);
sm_config_set_sideset(&c, 1, false, false);
return c;
}
#include "hardware/clocks.h"
static inline void ws2812_program_init(PIO pio, uint sm, uint offset, uint pin, float freq, bool rgbw) {
pio_gpio_init(pio, pin);
pio_sm_set_consecutive_pindirs(pio, sm, pin, 1, true);
pio_sm_config c = ws2812_program_get_default_config(offset);
sm_config_set_sideset_pins(&c, pin);
sm_config_set_out_shift(&c, false, true, rgbw ? 32 : 24);
sm_config_set_fifo_join(&c, PIO_FIFO_JOIN_TX);
int cycles_per_bit = ws2812_T1 + ws2812_T2 + ws2812_T3;
float div = clock_get_hz(clk_sys) / (freq * cycles_per_bit);
sm_config_set_clkdiv(&c, div);
pio_sm_init(pio, sm, offset, &c);
pio_sm_set_enabled(pio, sm, true);
}
#endif
И сам тест остался
#include "ws2812.pio.h"
#define WS2812_PIN 0
#define IS_RGBW false
static inline void put_pixel(uint32_t pixel_grb)
{
pio_sm_put_blocking(pio0, 0, pixel_grb << 8u);
}
void setup()
{
PIO pio = pio0;
int sm = 0;
uint offset = pio_add_program(pio, &ws2812_program);
ws2812_program_init(pio, sm, offset, WS2812_PIN, 800000, IS_RGBW);
}
void loop()
{
//for (uint i = 0; i < 3; ++i)
put_pixel(0xff00AA);
sleep_ms(10);
}
Ну и любуемся на картинку — можно подключать адресуемые светодиоды.
Как заключение: если вам достаточно обычного ардуино и не возникает желания делать чего-то более сложного, то Raspberry Pi Pico можно использовать прямо сейчас.
Если хочется странного — то лучше пока воздержаться, пока документация будет доведена до кондиции и примеров на все случаи жизни будет больше.
А продавец — молодец, как в Али в комментариях обычно пишут.
Но если честно — посылка из Китая еще не дошла, через несколько дней, после того, как я заказал платку на Али, я разместил заказ еще в одном магазине, и через несколько дней он уже у меня был. Ну очень любопытно было, а ждать не хотелось.
И не стесняйтесь ставить минусы, если вам кажется, что обзор не нужен. Стереть его можно значительно быстрее, чем написать 🙂
- Elfeland 24W LED - потолочный светильник на 24Вт с регулировкой температуры света и яркости - за $12.80
- Мягкий чехол на сиденье унитаза. Или новогодняя сказка о пользе инструкций.
- Устройство удалённого контроля температуры для дачи, через WI-FI в смартфон
- Мои первые TWS наушники Ugreen HiTune H3 (обзор не для аудиофилов)
- Зарядное устройство QC3.0 вместо прикуривателя в Ford Focus 3
- Узел промежуточного (полного) отбора ХД/4. Нестандартное применение.
- Каша из топора? Нет! Зарядка из утюга!!!
- Костюм спортивный мужской
- Простой и недорогой контроллер для паяльника на жалах HAKKO T12.
- Аналоговый контроллер температуры "616 mini"

