Как мы запускали стандартные примеры из библиотеки STM32Cube

alexkalmuk 12 февраля в 18:22 5,7k
Добрый день! Не секрет, что стандартные примеры, работающие из коробки, — штука неплохая: загрузил на плату и наслаждайся. Это удобно для быстрого ознакомления. Но затем, если мы намерены сами что-то создать, нужно разбирать код примера, читать документацию, писать свой код, долго отлаживаться… Хочется этот этап как-то упростить. По этой причине, я хотел бы рассказать о том, как мы сделали интеграцию стандартных примеров из библиотеки STM32Cube в Embox.

Итак, Embox, как и любая ОС, предоставляет много приятных вещей, таких как потоки, управление памятью и т.д. STM32Cube, в свою очередь, обладает множеством примеров по использованию аппаратуры. Каждый пример представляет собой самодостаточный набор исходников с проектными файлами для MDK ARM, EWARM и SW4STM32 (что-то поверх Eclipse, вроде бы), т.е. нажал кнопку — все само собралось, на плату загрузилось и запустилось. Теперь вопрос: как же интегрировать такой пример в инфраструктуру операционной системы? Посмотрим более детально как устроены примеры.

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

Я буду опираться на пример для LCD-экрана и BSP для отладочной платы STM32F746G-Discovery. Рассмотрим пример для экрана. Помимо проектных файлов в дереве исходников находятся readme.txt, Inc/ и Src/. Они-то нам и нужны, переходим в папку с исходниками и смотрим, что там лежит:

# ls Projects/STM32746G-Discovery/Examples/LTDC/LTDC_Display_1Layer/Src/
main.c  stm32f7xx_hal_msp.c  stm32f7xx_it.c  system_stm32f7xx.c

Файл system_stm32f7xx.c имеется во всех примерах, в нем содержится инициализация встроенной флэш памяти, PLL, а также устанавливается SystemFrequency. Эти функции вызываются еще до main(). В Embox эти же настройки производятся при старте системы, поэтому этот файл мы просто не будем использовать. Идем далее, в файле stm32f7xx_hal_msp.c содержатся функции инициализации аппаратуры, специфичные для конкретной задачи. Это функции вида *_MspInit, которые определены изначально как WEAK, что позволяет их переопределить. Этот файл берем с собой в Embox. Далее, stm32f7xx_it.c — тут переопределяются обработчики аппаратных прерываний и исключений. И наконец, main.c — здесь все понятно — основная логика + некая инициализация, которую мы потом выбросим.

Перенос примера в Embox происходит с помощью скрипта import_stm32_cube_example.py.
После его запуска первым делом копируется в дерево исходников readme.txt, Inc/ и Src/. Теперь нужно сгенерить файл системы сборки Embox. Для этой цели используется шаблон, в который подставляется название примера, исходники, платформа (f7 или f4) и необходимые зависимости.

/* GENERATED FILE */

package stm32_PLATFORM_.cmd

@AutoCmd
@Cmd(name="_EXAMPLE_", help="")
@BuildDepends(third_party.bsp.stm_PLATFORM_cube.core)
module _EXAMPLE_ {
    source "Src/embox_main.c"
    depends _EXAMPLE__Lib
}

@BuildDepends(third_party.bsp.stm_PLATFORM_cube.core)
module _EXAMPLE__Lib {
    @Cflags("-Wno-unused")
    @IncludePath("$(ROOT_DIR)/platform/stm32_PLATFORM_/cmds/_EXAMPLE_/Inc")
    _SOURCES_

   depends third_party.bsp.stm_PLATFORM_cube.stm32_PLATFORM__discovery_bsp
   depends third_party.bsp.stm_PLATFORM_cube.stm32_PLATFORM__discovery_components
   depends third_party.bsp.stm_PLATFORM_cube.stm32_PLATFORM__discovery_utilities
}

Модули third_party.bsp.stmf7cube.stm32f7_discovery_bsp и third_party.bsp.stmf7cube.stm32f7_discovery_components содержат библиотеку BSP из Cube и разные вспомогательные библиотеки. Обычно в проектных файлах Cube эти исходники явно перечисляются, но у нас в системе они общие и подключаются одинаковым образом для любого примера все сразу. Эти библиотеки и все исходники примера будут скомпилированы в некоторую статическую библиотеку внутри Embox (в противном случае, нарушится линковка команд Embox с weak функциями Cube), поэтому для запуска примера будет сгенерирован файлик с одной лишь функцией main().

С системой сборки разобрались, теперь нужно как-то переделать обработчики прерываний из Cube, чтобы ОС их приняла. Формат обработчика прерываний в Cube void (*handler)(void), в то время как в Embox сигнатура другая. На примере обработчика DMA2_Stream7_IRQHandler в Embox это можно реализовать так:

static
irq_return_t embox_DMA2_Stream7_IRQHandler(unsigned int irq_nr, void *data) {
    DMA2_Stream7_IRQHandler();
    return IRQ_HANDLED;
}

А затем зарегистрируем эту обертку:

irq_attach(DMA2_Stream7_IRQn + 16, embox_DMA2_Stream7_IRQHandler, 0, NULL,
"DMA2_Stream7_IRQHandler");

Можно видеть, что названия обработчиков прерываний в Cube имеют вполне определенную структуру — *_IRQHandler (как и названия номеров прерываний — *_IRQn). Поэтому чтобы автоматически найти все обработчики прерываний для данного примера, мы сначала проходимся по файлу препроцессором cpp, и в полученном файле находим все *_IRQHandler. А далее, import_stm32_cube_example.py сгенерирует файл Src/embox_stm32f7xx_it_lib.c, в котором будут все необходимые обработчики прерываний и глобальная функция embox_stm32_setup_irq_handlers, вызов которой регистрирует обработчики прерываний.

Помимо прерываний от периферии нужно еще не забыть про аппаратный таймер, так как в обработчике таймера Cube (называется он SysTick_Handler) могут происходить действия типа заполнения аудио буферов. Поэтому обработчик первого уровня — это обработчик в Embox, который в конце уже вызовет SysTick_Handler.

Наконец, нужно исправить Src/main.c. Во-первых, функция main в примерах Cube всегда содержит 3 стандартных вызова — CPU_CACHE_Enable(), HAL_Init(), SystemClock_Config(). Эти функции и так вызываются при старте Embox, поэтому в примере они крайне вредны, поэтому автоматически оказываются закомментированы. И последнее, добавляется регистрация обработчиков прерываний, т.е. вызов вида

if (0 != embox_stm32_setup_irq_handlers()) {
    printf("embox_stm32_setup_irq_handlers error!\n");
}

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

Для демонстрации всего вышеперечисленного я выбрал, как уже говорил, два примера — 1) Экран LCD 2) Пример с записью звука из BSP.

Для желающих поиграться с STMкой ниже даю ссылки и рецепты. Вероятно, тут пригодится статья о том, как собрать и запустить Embox.

Пример LCD на STM32F746G-Discovery

Переходим в папку с Embox, и добавляем пример:

# ./scripts/stm32/import_stm32_cube_example.py f7 ./build/extbld/third_party/bsp/stmf7cube/core/STM32Cube_FW_F7_V1.5.0/Projects/STM32746G-Discovery/Examples/LTDC/LTDC_Display_1Layer

Эта директория с длинным названием появится после того как вы первый раз соберете Embox, при сборке скачается архив с STM32Cube под F7 и cкопируется куда нужно.

Теперь в папке platform/stm32f7/cmds/ появится пример LTDC_Display_1Layer. Можете сами зайти в папку и убедиться если не верите :)

Добавляем его в конфиг conf/mods.config как stm32f7.cmd.LTDC_Display_1Layer. Пересобираем Embox и загружаем на стмку. В консоли minicom после загрузки Еmbox запускаем пример командой LTDC_Display_1Layer. На экране нарисуется картинка с девушками.

Пример со звуком на STM32F746G-Discovery (+ Touchscreen)

Переходим в папку с Embox, и добавляем пример:

# ./scripts/stm32/import_stm32_cube_example.py f7 ./build/extbld/third_party/bsp/stmf7cube/core/STM32Cube_FW_F7_V1.5.0/Projects/STM32746G-Discovery/Examples/BSP

Теперь в папке platform/stm32f7/cmds/ появится пример BSP. В этом примере продемонстрированы многие возможности STM32F7-Discovery.

Добавляем его в конфиг как stm32f7.cmd.BSP. Пересобираем Embox и загружаем на стмку. В консоли minicom после загрузки Еmbox запускаем пример командой BSP. После запуска вы можете выбрать нужный пример нажатиями на User Button (синий джойстик) и начать запись звука с микрофонов (MEMS-микрофоны, миниатюрные микрофоны встроенные в плату), после чего через несколько секунд запись воспроизведется.

Видео с некоторыми примерами из BSP.


Pеализацию можно посмотреть здесь.

На этом все. Будем рады вопросам.
Проголосовать:
+23
Сохранить: