Добрый день, уважаемые хабровчане! Хочу представить общественности мой проект — небольшая отладочная плата на базе STM32, но в форм-факторе Raspberry Pi. От других отладочных плат она отличается тем, что имеет совместимую с корпусами от Raspberry Pi геометрию и наличие ESP8266-модуля в качестве беспроводного модема. А также приятные дополнения в виде разъёма для micro-SD карты и стерео-усилителя. Для использования всего этого богатства я разработал высокоуровневую библиотеку и демонстрационную программу (на C++11). В статье я хочу подробно описать как аппаратную, так и программную части этого проекта. Кому этот проект может быть полезен? Наверное, только тем, кто захочет спаять эту плату сам, так как никакие варианты даже мелкосерийного производства я не рассматриваю. Это чисто хобби. На мой взгляд, плата покрывает достаточно широкий спектр задач, которые могут возникнуть в рамках небольших домашних поделок, использующих WiFi и звук. Для начала, попытаюсь ответить на вопрос, зачем это все. Основные мотиваторы этого проекта выглядят так: Как мне кажется, получилась достаточно симпатичная плата со следующими характеристиками и компонентами:    Затвор транзистора подключён к контакту PA15 контроллера. Это системный контакт JTDI контроллера, который интересен тем, что в исходном положении он сконфигурирован как выход с высоким уровнем (pull-up) напряжения. Так как вместо JTAG для программирования задействован SWD, данный контакт остаётся свободным, и его можно использовать для других целей, например, управления транзистором. Это удобно — при подаче питания на плату карта памяти обесточена, для её включения нужно подать низкий уровень на контакт PA15.    Конечно, ESP8266 сам по себе неплохой контроллер, но он всё-таки уступает STM32F4 как по производительности, так и по периферии. Да и размер с ценой этого модуля так и намекают, что это прямо-таки вылитый модемный блок для своего более старшего собрата. Модуль управляется по USRT с использованием текстового AT протокола. Пара фотографий:

 ESP8266 — вещь известная. Я уверен, что многие с ней уже знакомы, поэтому подробное руководство будет здесь лишним. В силу схематических особенностей подключения модуля ESP11 к плате, приведу только краткое руководство для тех, кто желает поменять его прошивку:  На github находится тестовая программа. Она делает следующее: На Хабре было много статей, посвящённых программированию STM32 на достаточно низком уровне (только управлением регистров или CMSIS). Например, из относительно последних: раз, два, три. Статьи, безусловно, очень качественные, но моё субъективное мнение — для разовой разработки какого-либо продукта этот подход, быть может, себя и оправдывает. Но вот для длительного хобби-проекта, когда хочется, чтобы всё было красиво и расширяемо, этот подход уж слишком низкоуровневый. Одна из причин популярности Ардуино именно как программной платформы, на мой взгляд, заключается в том, что авторы Ардуино ушли с такого низкого уровня на объектно-ориентированную архитектуру. Поэтому я решил пойти в этом же направлении и надстроить над библиотекой HAL достаточно высокоуровневую объектно-ориентированную прослойку.  Таким образом, получается три уровня программы: Что касается диалекта языка. Пока я несколько старомоден — остаюсь на C++11. Этот стандарт имеет несколько фишек, особенно полезных для разработки встроенного ПО: классы-перечисления (enum class), вызов конструкторов при помощи фигурных скобок для контроля типов передаваемых параметров, статические контейнеры типа std::array. Кстати, на Хабре есть замечательная статья на эту тему. Полный код библиотеки можно посмотреть на github. Здесь же я приведу только несколько небольших примеров, чтобы показать структуру, идею и проблемы, этой идеей порождённые. Первый пример — класс для периодического опроса состояния пина (например, кнопки) и вызова обработчика при изменении этого состояния: Конструктор определяет все параметры кнопки: Если обработка таких событий не является приоритетной задачей, то использование прерываний здесь явно лишнее. Поэтому различные сценарии нажатия (например, одиночное нажатие или удержание) реализованы в процедуре periodic, которая должна периодически вызываться из основного кода программы. periodic анализирует изменение состояния и синхронно вызывает виртуальный обработчик onButtonPressed, который должен быть реализован в основной программе: Основной плюс такого подхода — разнесение логики и кода детектирования события от его обработки. Для отсчёта времени здесь используется не HAL_GetTick, который в силу своего типа (uint32_t) сбрасывается по переполнению каждые 2^32 миллисекунд (каждые 49 дней). Я реализовал собственный класс RealTimeClock, который отсчитывает миллисекунды со старта программы, или включения контроллера, как uint64_t, что даёт примерно 5^8 лет.  Второй пример — работа с аппаратным интерфейсом, которых в контроллере несколько. Например, SPI. С точки зрения основной программы, очень удобно выбрать только нужный интерфейс (SPI1/SPI2/SPI3), а всё остальные параметры, которые зависят от этого интерфейса, сконфигурирует конструктор класса.  Параметры пинов и параметры интерфейса хранятся локально в классе. К сожалению, я выбрал не совсем удачный вариант реализации, когда настройка параметров в зависимости от конкретного интерфейса реализуется напрямую: По этой же схеме реализованы процедуры enableClock и disableClock, что плохо расширяемо и плохо переносимо на другие контроллеры. В данном случае лучше использовать шаблоны, где параметром шаблона являются HAL имя интерфейса (SPI1, SPI2, SPI3), параметры пинов (GPIO_AF5_SPI1), и что-то, что управляет включением/выключением тактирования. Здесь есть интересная статья по этой теме, хотя в ней рессматриваются контроллеры AVR, что, впрочем, принципиальной разницы не имеет.  Начало и окончание передачи контролируются двумя методами start/stop: Работа с аппаратным интерфейсом с использованием прерываний. Класс реализует I2S интерфейс с использованием DMA-контроллера. I2S (Inter-IC Sound) — это программно-аппаратная надстройка над SPI, которая сама, например, осуществляет подбор тактовой частоты и управление каналами в зависимости от аудио-протокола и битрейта. В данном случае, класс I2S наследуется от класса «порт», то есть I2S — это порт со специальными свойствами. Некоторые данные хранятся в структурах HAL (плюс к удобству, минус к объёму данных). Некоторые данные передаются из основного кода по ссылкам (например, структура irqPrio). Его конструктор задаёт все статические параметры: Начало передачи данных контролируются методам start, который отвечают за настройку параметров порта, тактирование интерфейса, настройку прерываний, старт DMA, старт самого интерфейса с заданными параметрами передачи.  Процедура stop делает всё наоборот: Здесь есть несколько интересных особенностей: Основная программа пишется с использованием вышеописанной библиотеки достаточно просто: Здесь мы инициализируем библиотеку HAL, все пины контроллера конфигурируем по умолчанию на вход (GPIO_MODE_INPUT/PULLDOWN). Устанавливаем частоту контроллера, запускаем тактирование (включая часы реального времени от внешнего кварца). После этого, немного в стиле Java, создаём экземпляр нашего приложения и вызываем его метод run, который реализует всю логику приложения. Отдельной секцией мы должны определить все используемые прерывания. Так как мы пишем на C++, а прерывания — это вещи из мира C, то их нужно соответственно маскировать: Класс MyApplication декларирует все используемые устройства, вызывает конструкторы для всех этих устройств, а также реализует необходимые обработчики событий: То есть, по сути, все используемы устройства декларируются статически, что потенциально ведёт к увеличению используемой памяти, но сильно упрощает доступ к данным. В конструкторе класса MyApplication необходимо вызвать конструкторы всех устройств, после чего, к моменту запуска процедуры run, все используемые устройства микроконтроллера будут инициализированы: В качестве примера обработчик события нажатия кнопки, по которой запускается/останавливается воспроизведение WAV файла:  Ну и, наконец, основной метод run завершает настройку устройств (например, устанавливает MyApplication в качестве обработчика событий), и запускает бесконечный цикл, где периодически обращается к тем устройствам, которые требуют периодического внимания: Интересный факт — микроконтроллер поддаётся частичному оверклокингу. Его максимальная частота — 168 MHz. Однако, играя параметрами тактирования, мне удавалось запускать его на 172 MHz и на 180 MHz, то есть инициализация тактирования с такой частотой выполняется без ошибок, и эта частота видна на осциллографе, если его подключить к пину выходного тактового сигнала MCO. Но при этом контроллер зависает, если использовать USART или I2S, что, быть может, просто программная проблема на уровне HAL.  Это самый больной вопрос во всей этой работе. На github есть список всех компонент платы. Чтобы получить хоть какую-то цифру, я скрупулёзно собрал цены всех компонент из этого списка с сайта Mouser (не сочтите за рекламу). На получившуюся цифру в 37 Евро без слёз смотреть не получается. К ней нужно ещё прибавить стоимость изготовления платы и время на пайку. То есть, по сравнению с массовыми демонстрационными платами от STM или Olimex, моя плата получилась очень уж дорогой. Наверняка я реализовал многие вещи очень неэффективным способом. В текущей версии я сам вижу проблемы, которые нужно исправлять: Проект опубликован на github под лицензией GPL v3: Спасибо за внимание! Математик-инженер