Изучаем BlackIce II, отладочную плату с STM32 и ICE40

16 апреля 2018

Разбираясь, какие сейчас существуют отладочные платы на базе ICE40, я обратил особое внимание на BlackIce II. Дело в том, что мне стало надоедать постоянно возиться с проводами при подключении сторонних модулей к iCEstick. Для решения этой проблемы BlackIce II имеет множество Pmod-разъемов, является совместимой с Arduino-шилдами, а также имеет распаянные 4 Мбит SRAM и разъем для подключения SD-карты. Интересно, что на плате есть не только FPGA ICE40HX4K, но и микроконтроллер STM32L433. Так получилось, что микроконтроллеры STM32 меня тоже интересуют. Было бы удобно иметь устройство «два в одном». Наконец, плата является полностью открытым проектом, и самое главное — она черная! В общем, я решил обзавестись такой.

Интересные особенности BlackIce II

Давайте внимательно посмотрим на плату:

Отладочная плата BlackIce II

Конечно же, бросается в глаза наличие сразу двух портов microUSB. Из них основным является тот, что на фото находится левее. Он используется при конфигурации FPGA. Сам процесс мы более подробно рассмотрим далее. Порт, что находится правее, идет к чипу CH340, и оттуда к ICE40HX4K. Если вам понадобится, чтобы FPGA мог поговорить с компьютером по UART, это будет проще всего сделать с помощью данного USB-порта. Строго говоря, второй порт можно было не делать, а просто подключать к плате какой-то внешний USB-UART, когда нужно. Но для нашего удобства дизайнеры платы решили разместить его прямо так, отсюда и «лишний» порт.

Слева можно разглядеть (снизу вверх): шесть светодиодов, две кнопки, четыре переключателя, а также кнопку Reset и светодиод Power. На приведенном фото не видно, но под ними находится разъем для SD-карты. Упомянутый SRAM на 4 Мбит также находится с обратной стороны:

Плата BlackIce II, вид снизу

Мне нравится, что снизу у платы нет каких-нибудь резисторов или иных мелких компонентов. Обычно они отлетают в первую очередь.

Большинство аналогичных отладочных плат для конфигурации FPGA имеют чип FT2232 и какой-то SPI-flash. Но на BlackIce II вы не найдете ни первого, ни второго. Дело в том, что разработчики платы обратили внимание на следующее. По стоимости эти компоненты сопоставимы со стоимостью микроконтроллера STM32L433. Однако последний не только имеет флэш-память и превосходно справится с задачей конфигурации FPGA, но и может быть перепрограммирован выполнять другие задачи. Плюс к этому, STM32L433 имеет много классной периферии, те же FPU, 12-и битный ADC, два 12-и битных DAC, RTC, и не только.

FPGA хорошо справляются со всевозможной обработкой сигналов. В этом микроконтроллеры не так уж хороши. Зато микроконтроллеры мастерский выполняют замороченную бизнес-логику. При совместном использовании микроконтроллеров и FPGA возникает синергетический эффект.

Конфигурируем FPGA

По умолчанию в STM32L433 залита прошивка под названием iceboot. Эта прошивка превращает микроконтроллер в устройство для конфигурации FPGA. При подключении по USB микроконтроллер притворяется последовательным портом, благодаря чему конфигурация FPGA может быть выполнена командами:

stty -F /dev/ttyACM0 raw
cat main.bin > /dev/ttyACM0

Микроконтроллер принимает содержимое файла и заливает его в FPGA. В этом есть что-то гениальное, вы не находите?

Из особенностей конкретно BlackIce II стоит отметить, что FPGA в нем работает с кварцем на 100 МГц. Учитывайте это при настройке PLL. Про настройку PLL и проверку конфигурации с помощью утилиты icetime ранее было в посте Учим iCEstick передавать видео-сигнал по VGA.

В остальном разработка ничем не отличается от разработки под iCEstick или любую другую отладочную плату на базе FPGA серии ICE40. Этот вопрос ранее уже был подробно рассмотрен в заметке Знакомимся с iCEstick и полностью открытым ПО для разработки под FPGA. Поэтому здесь мы на нем подробно останавливаться не будем. Файл .pcf и схему платы вы найдете в репозитории проекта на GitHub.

Прошиваем микроконтроллер

Ничто не мешает перепрошить STM32L433 и использовать BlackIce II чисто как отладочную плату на базе данного микроконтроллера. Берем программатор STLink v2 и подключаем его пины RST, SWDIO, GND и SWCLK к пинам RST, SWD, GND и SWC отладочной платы. Если вы посмотрите на первое фото BlackIce II, то эти пины находятся среди пинов в нижней части платы, в нижнем из двух рядов. Запитать плату можно по USB, от программатора (ищите пин с подписью +5V), или даже сразу от обоих источников. Это безопасно, так как источники питания изолированы друг от друга диодами.

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

st-flash read ~/backup/iceboot.bin 0x8000000 0x1ffff

Загружаем последнюю версию прошивки с GitHub’а:

git clone https://github.com/mystorm-org/BlackIce-II.git
cd BlackIce-II/firmware/iceboot

Сделаем небольшое изменение в файле iceboot.c, чтобы проверить, точно ли прошивка обновилась:

// #define VER "<Iceboot 0.4> "
#define VER "<Iceb00t 0.4> "

Собираем и заливаем прошивку:

make raw
st-flash write output/iceboot.raw 0x8000000

Проверяем работу прошивки, попытавшись залить конфигурацию FPGA. Если затем сказать:

cat /dev/ttyACM0

… то увидим:

<Iceb00t 0.4>

В случае чего, вернуть все как было можно командой:

st-flash write ~/backup/iceboot.bin 0x8000000

Заметьте, что если вы будете делать проект с нуля, вам наверняка захочется использовать внешний кварцевый резонатор. Для этого в STM32CubeMX во вкладке Pinout слева находим RCC → High Speed Clock (HSE) и выбираем Crystal / Ceramic Resonator. Затем во кладке Clock Configuration напротив блока HSE вводим в Input Frequency значение 12 (это число МГц), в блоге PLL Source Mux выбираем HSE, а в выпадающем списке PLLM выбираем /3. В итоге должно получиться примерно как на этой картинке:

Настройка часов для STM32L433

В результате SPI и прочая периферия будут работать на не слишком позорной скорости :) Кстати, при настройке периферии следует проявлять бдительность. Например, STM32CubeMX почему-то по умолчанию сделал мне тот же SPI четырехбитным вместо нормального восьмибитного. В любой непонятной ситуации можно открыть еще одно окно с проектом iceboot и посмотреть, как сделано в нем.

Как видите, никаких отличий от обычной разработки под STM32. Ранее эта тема была подробно рассмотрена в заметке Готовим «взрослую» среду разработки под STM32 в Linux, поэтому здесь я на ней не останавливаюсь. Пример файла .ioc для STM32CubeMX с настройкой всей периферии ищите в том же репозитории на GitHub.

Общение между микроконтроллером и FPGA

Выше что-то там говорилось про синергию. Давайте выясним, как с ней обстоят дела на практике. Например, в заметке Превращаем VGA-монитор в «большой OLED-экранчик» с помощью iCEstick описывался весьма занятный пример совместного использования микроконтроллера и FPGA. Посмотрим, удастся ли спортировать его на BlackIce II.

Моей первой мыслью было просто сконфигурить FPGA, а затем прошить микроконтроллер. К сожалению, оказалось, что это так просто не работает. Дело в том, что микроконтроллер не запускается с новой прошивкой, если не дернуть ему Reset. А Reset этот соединен одновременно с микроконтроллером и FPGA. Никакой перемычки, чтобы их разъединить, не предусмотрено. FPGA хранит свою конфигурацию в SRAM, и она сбрасывается при попытке перезапустить микроконтроллер.

Можно придумать несколько решений. Например, можно просто форкнуть iceboot. Но я решил пойти немного иным путем. У микроконтроллера есть флэш-память, верно? Давайте просто положим конфигурацию во флэш-память, а при старте вольем сохраненную конфигурацию в FPGA. Таким образом, плата никак не будет зависеть от компьютера, который что-то там должен передавать по UART.

С помощью утилиты xxd конфигурацию можно сконвертировать в заголовочный файл для языка C:

xxd -i main.bin | \
  sed -e 's/unsigned/const unsigned/' > ../stm32/Inc/ice40_config.h

Заметьте, что массив байт должен быть объявлен, как const, чтобы компилятор просто положил его во флэш-память безо всяких попыток создания копии в RAM (куда этот массив не влезет) при запуске прошивки.

Конфигурация FPGA серии ICE40 производится по SPI-подобному протоколу. Он весьма прост и подробно описан в документе iCE40 Programming and Configuration [PDF]. Самая суть содержится в иллюстрации «Figure 15. Application Processor Waveforms for SPI Peripheral Mode Configuration Process».

Моя реализация получилась такой:

// Write to SPI in multiple chunks (HAL_SPI_Transmit length is
// limited to 16 bits). Copy-pasted from iceboot.
int spi_write(uint8_t *p, uint32_t len) {
    int ret;
    uint16_t n;

    ret = HAL_OK;
    n = 0x8000;
    while (len > 0) {
        if (len < n)
            n = len;
        ret = HAL_SPI_Transmit(&hspi1, p, n, HAL_MAX_DELAY);
        if (ret != HAL_OK)
            return ret;
        len -= n;
        p += n;
    }  
    return ret;
}

// 0 = OK , < 0 = error
int ice40_configure(const unsigned char* bin,
                    unsigned int bin_size) {
    // SS = LOW
    HAL_GPIO_WritePin(GPIOA, GPIO_PIN_15, GPIO_PIN_RESET);

    // CRESET = LOW
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_RESET);
    // Keep CRESET low for at least 200 nsec
    HAL_Delay(1);
    // CRESET = HIGH
    HAL_GPIO_WritePin(GPIOB, GPIO_PIN_6, GPIO_PIN_SET);
    // Give ICE40 at least 800 usec to clear internal
    // configuration memory
    HAL_Delay(850);

    // check CDONE is LOW
    if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) != GPIO_PIN_RESET) {
        return -1;
    }

    // send the configuration over SPI
    int res = spi_write((uint8_t*)bin, bin_size);
    if(res != HAL_OK) {
        return -2;
    }

    // send at least 49 dummy bits
    unsigned char dummy[7] = {0};
    res = spi_write(dummy, sizeof(dummy));
    if(res != HAL_OK) {
        return -3;
    }
    // check CDONE is HIGH
    if(HAL_GPIO_ReadPin(GPIOB, GPIO_PIN_7) != GPIO_PIN_SET) {
        return -4;
    }

    return 0;
}

Важно! Обратите внимание на передачу фиктивных данных в конце процедуры. Если вы используете в своей конфигурации SPI, часть этих данных может быть воспринята ею, как реальные данные. Для решения проблемы следует как-то синхронизировать работу конфигурации и микроконтроллера, например, с помощью еще одного пина.

Конфигурация уже содержит в себе контрольную сумму. Если вдруг передача произойдет с ошибкой, FPGA отвергнет конфигурацию и ничего не будет делать. А на стороне микроконтроллера мы узнаем о проблеме по низкому напряжению на пине DONE после завершения передачи. Стоит также отметить, что в файле конфигурации содержится немало нулевых байт. Поэтому при желании ее можно неплохо сжать даже банальным RLE.

Дополнение: В итоге я добавил сжатие даже еще более банальным методом, просто кодирующим повторяющиеся нули. Коэффициент сжатия при этом составляет где-то от 3 до 20, в зависимости от сжимаемой конфигурации. Сам алгоритм ищите в полной версии исходного кода.

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

Fun fact! Используя приведенные здесь наработки, конфигурацию FPGA можно менять на лету, превращая ICE40 то в одно устройство, то в другое.

Как между собой соединены микроконтроллер и FPGA вы узнаете из схемы платы. Кроме того, вас может заинтересовать страница Connections between the STM32 and the iCE40 на wiki-сайте проекта.

Заключение

В целом, платой я крайне доволен. Есть хорошие шансы, что она станет моей любимой отладочной платой на ближайшее время.

Ссылки по теме:

Исходники к этому посту лежат здесь.

Дополнение: Вас также могут заинтересовать посты Конфигурация FPGA в качестве RISC-V процессора и Подключаем плату Alinx AN108 с АЦП и ЦАП к BlackIce II.

Метки: , , .


Вы можете прислать свой комментарий мне на почту, или воспользоваться комментариями в Telegram-группе.