Товары из Китая

Raspberry Pi Pico — Arduino на стероидах


  • Цена: €4,13 плюс доставка €0,61
  • Во времена моей молодости говорили — легко, как два байта переслать. Другие — чуть по-другому — легко, как два пальца об асфальт, подозревая под этим нечто третье.

    Не так давно Raspberry Pi Foundation выкатил собственный микроконтроллер, за рекордно низкую по нынешним временам цену для устройства с такими параметрами — сама микросхема стоит 1 доллар. Сейчас мы будем разбираться, легко ли в этом микроконтроллере два байта переслать.

    Настоящие специалисты, которые в комментариях уже не раз меня называли ардуинщиком-недоучкой, в этом опусе вряд ли найдут что-то полезное, а остальные — добро пожаловать.

    В начале было слово была платка от Raspberry на базе этого микроконтроллера под названием Raspberry Pi Pico, на котором были только сам микроконтроллер, 2 мегабайта флеш-памяти, светодиод, кварц, микро USB разъем и кнопка перехода в режим загрузки. И все это с объявленной ценой без налогов и пересылки в 4 USD.

    Потом поперло — таких платок разного вида просто понесло потоком — и уменьшенных размеров, и с кучей разной периферии. Очевидно, очень скоро этот микроконтроллер заинтересовал ардуинщиков и его поддержка была включена в длинный список поддерживаемых микроконтроллеров.

    Raspberry Pi Pico — Arduino на стероидах

    Теперь лирическое отступление.

    Для начала, что за мелкоконтроллер на платке?

    Обозвали его RP2040, что по мнению Raspberry Pi Foundation, обозначает:

    • 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 для загрузки и отладки
    • Поддержка спящих режимов и пониженной частоты для снижения потребления

    В списке отсутствует еще один вид памяти — 16МБ, масочная или однократная — не знаю. Там хранится программное обеспечение USB, загрузчики и библиотека для работы с плавающей запятой.

    Откуда надерганы IP (Intellectual property — так в мире проектирования микросхем так называют готовые блоки-библиотеки)

    • 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)

    С таймерами, на первый взгляд, большая напряженка — единственные таймер общего назначения с очень маленьким набором функций. Впридачу к нему имеются WDT и RTC.

    Но это только на первый взгляд — 16 PWM, собственные таймеры у контроллера DMA и программируемые машины состояний меняют дело кардинально.

    Микропроцессор, на самом деле, очень необычен именно в плане периферии. Главная фишка — это блоки программируемого ввода вывода, PIO, каждый из которых содержит 4 машины состояний 8 32-разрядных FIFO регистров, память программ. Каждый из таких блоков программируется на собственном ассемблере. С помощью PIO вы можете добавить недостающий интерфейс или сделать совершенно новый — например, аппаратный интерфейс для WS8212 или что вам в голову придет.

    Меня терзают смутные сомнения, что часть аппаратных интерфейсов этого микропроцессора именно так и сделаны — абсолютно такие же PIO, только вместо памяти программ с возможностью записи, там применена масочная память. Или OTP, которая позволяет менять конфигурацию микропроцессора уже на существующем кремнии.

    При таком подходе разработка сильно упрощается. И я подозреваю, что, в основном, все было разработано одним человеком в течении примерно года.

    Товарищ Сталин говорил — «У каждой ошибки есть имя и фамилия»

    Речь не об ошибке, но тем не менее фамилия быть должна. К сожалению, не нашел. Но, скорее всего, зовут его Graham. И проектировал он на Verilog. У меня есть один знакомый — бывший коллега, который такую вещь вполне мог бы спроектировать за несколько месяцев, но это явно не он.

    Небольшую статейку от человека, который вертелся рядом с разработчиком, можно найти здесь.

    Он пишет, что проектирование начали в середине 2017 года. Процессор поступил в продажу в начале 2021 года. Год-полтора, наверно, ушло на запуск производства и тестирование. Значит, на проектирование ушло еще полтора-два года. Если, конечно, кремний пошел с первой попытки.

    Если кому интересно, можно посмотреть картину по слоям, как делают транзистор

    Raspberry Pi Pico — Arduino на стероидах

    Вы все поняли и решили, что вы будете первым, кто сделает микросхему на кухне? К сожаление, вы опоздали, такой деятель уже нашелся, вы будете не первым 🙂

    Raspberry Pi Pico — Arduino на стероидах

    Но еще можете свой процессор слепить, вот RP2040. Как видите, все очень просто, большую часть площади занимает RAM.

    Raspberry Pi Pico — Arduino на стероидах

    Здесь можно посмотреть картинку получше.

    По словам руководства Raspberry Pi, выход годных микроконтроллеров с пластины около 20 000 штук. Пластина по 40-нм технологии в TSMC стоит около 2300 долларов. Партия, очевидно, не одна штука.

    Можете считать барыши 🙂 Но не забудьте прибавить стоимость тестирования, резки, корпусирования. А еще за подготовку к производству надо заплатить и разработчики тоже кушать хотят.

    Ну ладно, с присказкой закончим.

    Raspberry Pi Pico — Arduino на стероидах

    Raspberry Pi Pico — Arduino на стероидах

    При первом включении плата распознается, как USB диск, для того, чтобы запустить свою программу, достаточно скомпилированный файл в формате uf2 закинуть на этот диск, выполнение начнется немедленно.

    При последующих включениях будет запущена именно эта программа. Чтобы загрузить новую программу, нужно перейти в режим загрузки снова, для этого нужно отключить плату от USB, зажать кнопку BOOTSEL, подключить платку к USB и затем отпустить кнопку. Процедура нудная, и разъем недолго продюжит, если вы отлаживаете свою программу.

    На этом сайте вы найдете программу, которая приводит все в привычное для ардуино представление — достаточно загрузить программу один раз. После этого среда ардуино уже будет компилировать вашу программу в в формате uf2, загрузчик будет переводить плату в режим mass storage device и закидывать ваш файл на образовавшуюся флешку. Ваша программа сразу получит управление — никаких лишних телодвижений, все очень привычно.

    Raspberry Pi Pico — Arduino на стероидах

    Мне удавалось несколько раз убить загрузчик — но ничего страшного, опять переходим в режим загрузки и закидываем файл и продолжаем, как ни в чем не бывало.

    Если что-то сильно накосячили и ничего и никак не работает (но BOOTSEL работает) — нужно загрузить файл flash_nuke.uf2 который вычистит флеш и вернет все в первоначальное состояние. SWD мы не будем обсуждать во избежания лишнего усложнения.

    Я не ортодоксальный ардуинщик, поэтому предпочитаю использовать PlatformIO. Если честно, я никогда не использовал Arduino IDE для разработки своих программ, только загрузить что-то готовое. Кто пытался написать что-то более-менее сложное в Arduino IDE понимает, что это разновидность мазохизма. Хотя версия IDE 2.х выглядит уже гораздо лучше и похоже на настоящий редактор. Даже отладку позволяет — конечно, для тех контроллеров, где это предусмотрено.

    Платка получена, подпаиваем к ней гребенку выводов и кнопку аппаратного сброса — ну что эта за адруина и чтобы без кнопки сброса?

    Raspberry Pi Pico — Arduino на стероидах

    Для начала просто их примеров загружаем blink — светодиод на плате мигает. Удивительно, но факт.

    Raspberry Pi Pico — Arduino на стероидах

    Теперь бы пару байтов на выход переслать. И вот тут — засада. У большинства процессоров — это самая простая операция, что можно придумать. А тут управление заточено на управления битами. Каждый бит обслуживается 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 — это не регистр вывод, а исключающее или, полезная штука.

    Все, кушать подано, садитесь жрать, пожалуйста.

    Raspberry Pi Pico — Arduino на стероидах

    А теперь бы все это сделать побыстрее — выбросить из памяти прямо на выход, используя прямой доступ в память. Надо с 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

    Чай не баре — берите, компилируйте и пользуйтесь на доброе здоровье.

    Для совсем ленивых — нашелся добрый человек, сделал онлайн компилятор.

    Raspberry Pi Pico — Arduino на стероидах

    Начнем разбираться — собственно, на этом я собираюсь и закончить с обзором программного обеспечения. Пример выдран отсюда, все лишнее выброшено, осталась только суть

    Итак, пишем (только не спрашивайте, что это значит) 🙂

    Это наш 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 — Arduino на стероидах

    Raspberry Pi Pico — Arduino на стероидах

    Как заключение: если вам достаточно обычного ардуино и не возникает желания делать чего-то более сложного, то Raspberry Pi Pico можно использовать прямо сейчас.

    Если хочется странного — то лучше пока воздержаться, пока документация будет доведена до кондиции и примеров на все случаи жизни будет больше.

    А продавец — молодец, как в Али в комментариях обычно пишут.

    Но если честно — посылка из Китая еще не дошла, через несколько дней, после того, как я заказал платку на Али, я разместил заказ еще в одном магазине, и через несколько дней он уже у меня был. Ну очень любопытно было, а ждать не хотелось.

    Raspberry Pi Pico — Arduino на стероидах

    И не стесняйтесь ставить минусы, если вам кажется, что обзор не нужен. Стереть его можно значительно быстрее, чем написать 🙂


СМОТРИ ТАКЖЕ

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *