Embox на плате EFM32ZG_STK3200

image
Embox является сильно конфигурируемой RTOS. Основная идея Embox прозрачный запуск Linux программного обеспечения везде, в том числе и на микроконтроллерах. Из достижений стоит привести OpenCV, Qt, PJSIP запущенные на микроконтроллерах STM32F7. Конечно, запуск подразумевает, что в данные проекты не вносились изменения и использовались только опции при конфигурации оригинальных проектов и параметры задаваемые в самой конфигурации Embox. Но возникает естественный вопрос насколько Embox позволяет экономить ресурсы по сравнению с тем же Linux? Ведь последний также достаточно хорошо конфигурируется.

Для ответа на этот вопрос можно подобрать минимально возможную для запуска Embox аппаратную платформу. В качестве такой платформы мы выбрали EMF32ZG_STK3200 от компании SiliconLabs. Данная платформа имеет 32kB ROM и 4kB RAM память. А также процессорное ядро cortex-m0+. Из периферии доступны UART, пользовательские светодиоды, кнопки, а также 128×128 монохромный дисплей. Нашей целью является запуск любого пользовательского приложения, позволяющего убедиться в работоспособности Embox на данной плате.

Для работы с периферией и самой платой нужны драйвера и другой системный код. Данный код можно взять из примеров предоставляемых самим производителем чипа. В нашем случае производитель предлагает использовать SimplifyStudio. Есть также открытый репозиторий на GitHub). Этот код и будем использовать.

В Embox есть механизмы позволяющие использовать BSP производителя при создании драйверов. Для этого нужно скачать BSP и собрать его как библиотеку в Embox. При этом можно указать различные пути и флаги, необходимые для использования данной библиотеки в драйверах.

Пример Makefile для скачивания BSP:

PKG_NAME := Gecko_SDK PKG_VER := v5.1.2 PKG_ARCHIVE_NAME := $(PKG_NAME)-$(PKG_VER).tar.gz  PKG_SOURCES := https://github.com/SiliconLabs/$(PKG_NAME)/archive/v5.1.2.tar.gz  PKG_MD5     := 0de78b48a8da80931af1a53d401e74f5  include $(EXTBLD_LIB)

Mybuild для сборки BSP:

package platform.efm32  ... @BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/hardware/kit/common/bsp/") module bsp_get { }  @BuildDepends(bsp_get) @BuildDepends(efm32_conf) static module bsp extends embox.arch.arm.cmsis {  …      source "platform/emlib/src/em_timer.c",         "platform/emlib/src/em_adc.c",  …      depends bsp_get     depends efm32_conf } 

Mybuild для платы EFM32ZG_STK3200:

package platform.efm32.efm32zg_stk3200  @BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/platform/Device/SiliconLabs/EFM32ZG/Include") @BuildArtifactPath(cppflags="-I$(EXTERNAL_BUILD_DIR)/platform/efm32/bsp_get/Gecko_SDK-5.1.2/hardware/kit/EFM32ZG_STK3200/config") ... @BuildArtifactPath(cppflags="-D__CORTEX_SC=0") @BuildArtifactPath(cppflags="-DUART_COUNT=0") @BuildArtifactPath(cppflags="-DEFM32ZG222F32=1") module efm32zg_stk3200_conf extends platform.efm32.efm32_conf {     source "efm32_conf.h" }  @BuildDepends(platform.efm32.bsp) @BuildDepends(efm32zg_stk3200_conf) static module bsp extends platform.efm32.efm32_bsp {      @DefineMacro("DOXY_DOC_ONLY=0")     @AddPrefix("^BUILD/extbld/platform/efm32/bsp_get/Gecko_SDK-5.1.2/")     source         "platform/Device/SiliconLabs/EFM32ZG/Source/system_efm32zg.c",         "hardware/kit/common/drivers/displayls013b7dh03.c",  ...  }

После таких достаточно простых действий можно использовать код от производителя. Прежде чем приступить к работе с драйверами необходимо разобраться со средствами разработки и архитектурными частями. В Embox используются обычные средства разработки gcc, gdb, openocd. При запуске openocd нужно указать что мы используем платформу efm32:

sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg

Для нашей платки нет каких-то специальных архитектурных частей, только специфика cortex-m0+. Это задается компилятором. Поэтому мы можем задать общий код для cotrex-m0 отключив все лишнее, например, работу с плавающей точкой.

     @Runlevel(0) include embox.arch.generic.arch     include embox.arch.arm.libarch     @Runlevel(0) include embox.arch.arm.armmlib.locore     @Runlevel(0) include embox.arch.system(core_freq=8000000)     @Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=256)     @Runlevel(0) include embox.kernel.stack(stack_size=1024,alignment=4)     @Runlevel(0) include embox.arch.arm.fpu.fpu_stub

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

/* region (origin, length) */ ROM (0x00000000, 32K) RAM (0x20000000, 4K)  /* section (region[, lma_region]) */ text   (ROM) rodata (ROM) data   (RAM, ROM) bss    (RAM)

Первым драйвером реализуемым для поддержки какой-нибудь платы в Embox обычно является UART. На нашей плате есть LEUART. Для драйвера достаточно реализовать несколько функций. При этом мы можем использовать функции из BSP.

static int efm32_uart_putc(struct uart *dev, int ch) {     LEUART_Tx((void *) dev->base_addr, ch);     return 0; }  static int efm32_uart_hasrx(struct uart *dev) { ... }  static int efm32_uart_getc(struct uart *dev) {     return LEUART_Rx((void *) dev->base_addr); }  static int efm32_uart_setup(struct uart *dev, const struct uart_params *params) {      LEUART_TypeDef      *leuart = (void *) dev->base_addr;     LEUART_Init_TypeDef init    = LEUART_INIT_DEFAULT;      /* Enable CORE LE clock in order to access LE modules */     CMU_ClockEnable(cmuClock_HFPER, true);    ...      /* Finally enable it */     LEUART_Enable(leuart, leuartEnable);      return 0; }  ...  DIAG_SERIAL_DEF(&efm32_uart0, &uart_defparams);

Для того чтобы функции BSP были доступны нужно просто указать это в описании драйвера, файл Mybuild:

package embox.driver.serial  @BuildDepends(platform.efm32.efm32_bsp) module efm32_leuart extends embox.driver.diag.diag_api {     option number baud_rate      source "efm32_leuart.c"      @NoRuntime depends platform.efm32.efm32_bsp     depends core     depends diag }

После реализации драйвера UART вам доступны не только вывод, но и консоль где вы можете вызвать свои пользовательские команды. Для этого вам достаточно добавить в конфигурационный файл Embox маленький командный интерпретатор:

    include embox.cmd.help     include embox.cmd.sys.version      include embox.lib.Tokenizer     include embox.init.setup_tty_diag     @Runlevel(2) include embox.cmd.shell     @Runlevel(3) include embox.init.start_script(shell_name="diag_shell")

А также указать, что нужно использовать не полноценный tty доступный через devfs, а заглушку, которая позволяет обращаться к заданному устройству. Устройство задается также в конфигурационном файле mods.conf:

    @Runlevel(1) include embox.driver.serial.efm32_leuart     @Runlevel(1) include embox.driver.diag(impl="embox__driver__serial__efm32_leuart")     include embox.driver.serial.core_notty

Еще один очень простой драйвер это GPIO. Для его реализации мы также можем воспользоваться вызовами из BSP. Для этого в описании драйвера укажем что он зависит от BSP:

package embox.driver.gpio  @BuildDepends(platform.efm32.efm32_bsp) module efm32_gpio extends api {     option number log_level = 0      option number gpio_chip_id = 0     option number gpio_ports_number = 2      source "efm32_gpio.c"      depends embox.driver.gpio.core     @NoRuntime depends platform.efm32.efm32_bsp }

Сама реализация:

static int efm32_gpio_setup_mode(unsigned char port, gpio_mask_t pins, int mode) { ... }  static void efm32_gpio_set(unsigned char port, gpio_mask_t pins, char level) {     if (level) {         GPIO_PortOutSet(port, pins);     } else {         GPIO_PortOutClear(port, pins);     } }  static gpio_mask_t efm32_gpio_get(unsigned char port, gpio_mask_t pins) {      return GPIO_PortOutGet(port) & pins; } ...  static int efm32_gpio_init(void) { #if (_SILICON_LABS_32B_SERIES < 2)   CMU_ClockEnable(cmuClock_HFPER, true); #endif  #if (_SILICON_LABS_32B_SERIES < 2) \   || defined(_SILICON_LABS_32B_SERIES_2_CONFIG_2)   CMU_ClockEnable(cmuClock_GPIO, true); #endif     return gpio_register_chip((struct gpio_chip *)&efm32_gpio_chip, EFM32_GPIO_CHIP_ID);  }

Этого достаточно чтобы использовать команду ‘pin’ из Embox. Данная команда позволяет управлять GPIO. И в частности, может использоваться для проверки мигания светодиодом.

Добавляем саму команду в mods.conf:

include embox.cmd.hardware.pin

И сделаем так, чтобы она запускалась при старте. Для этого в конфигурационном файле start_sctpt.inc добавим одну из строчек:

<source">«pin GPIOC 10 blink»,
Или

"pin GPIOC 11 blink",

Команды одинаковые, просто номера светодиодов разные.

Попробуем запустить еще и дисплей. Сначала все просто. Ведь мы опять можем использовать вызовы BSP. Для этого нам нужно только добавить их в описание драйвера фреймбуфера:

package embox.driver.video  @BuildDepends(platform.efm32.efm32_bsp) module efm32_lcd { ...      source "efm32_lcd.c"     @NoRuntime depends platform.efm32.efm32_bsp }

Но как только мы делаем любой вызов связанный с дисплеем например DISPLAY_Init у нас секция .bss увеличивается больше чем на 2 kB, при размерах RAM 4 kB, это очень существенно. После изучения данного вопроса, выяснилось, что в самом BSP выделен фреймбуфер размером под дисплей. То есть 128x128x1 бит или 2048 байт.

В этот момент я даже хотел остановиться на достигнутом, ведь уместить в 4kB RAM вызов пользовательских команд с каким-то простым командным интерпретатором само по себе достижение. Но все-таки решил попробовать.

Первым я убрал командный интерпретатор и оставил только вызов уже упомянутой команды pin. Для этого я изменил конфигурационный файл mods.conf следующим образом:

    //@Runlevel(2) include embox.cmd.shell     //@Runlevel(3) include embox.init.start_script(shell_name="diag_shell")     @Runlevel(3) include embox.init.system_start_service(cmd_max_len=32, cmd_max_argv=6)

Поскольку я использовал другой модуль для пользовательского старта, я перенес запуск команд в другой конфигурационный файл. Вместо start_script.inc использовал system_start.inc.

Затем, поскольку уже не требовалось использовать индексные дескрипторы в командном интерпретаторе, а также таймеры, я с помощью опций в mods.config, избавился и от них:

    include embox.driver.common(device_name_len=1, max_dev_module_count=0)     include embox.compat.libc.stdio.file_pool(file_quantity=0)  …     include embox.kernel.task.resource.idesc_table(idesc_table_size=3)     include embox.kernel.task.task_no_table      @Runlevel(1) include embox.kernel.timer.sys_timer(timer_quantity=1) ...     @Runlevel(1) include embox.kernel.timer.itimer(itimer_quantity=0)

Поскольку я вызывал команды напрямую, а не через командный интерпретатор, я смог уменьшить размер стека:

    @Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=224)     @Runlevel(0) include embox.kernel.stack(stack_size=448,alignment=4)

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

Мне хотелось вывести что нибудь на дисплейю Я подумал, что логотип Embox будет показателен. По хорошему нужно использовать полноценный драйвер фреймбуфера и выводить изображение из файла, ведь все это есть в Embox. Но места совсем не хватало. И для демонстрации я решил вывести логотип прямо в функции инициализации драйвера фреймбуфера. Причем данные конвертировав напрямую в битовый массив. Таким образом мне потребовалось ровно 2048 байт в ROM.

Сам код, как и ранее, использует BSP:

extern const uint8_t demo_image_mono_128x128[128][16];  static int efm_lcd_init(void) {     DISPLAY_Device_t      displayDevice;     EMSTATUS status;     DISPLAY_PixelMatrix_t pixelMatrixBuffer;      /* Initialize the DISPLAY module. */     status = DISPLAY_Init();     if (DISPLAY_EMSTATUS_OK != status) {         return status;     }      /* Retrieve the properties of the DISPLAY. */     status = DISPLAY_DeviceGet(DISPLAY_DEVICE_NO, &displayDevice);     if (DISPLAY_EMSTATUS_OK != status) {         return status;     }     /* Allocate a framebuffer from the DISPLAY device driver. */     displayDevice.pPixelMatrixAllocate(&displayDevice,             displayDevice.geometry.width,             displayDevice.geometry.height,             &pixelMatrixBuffer); #if START_WITH_LOGO     memcpy(pixelMatrixBuffer, demo_image_mono_128x128,             displayDevice.geometry.width * displayDevice.geometry.height / 8 );      status = displayDevice.pPixelMatrixDraw(&displayDevice,             pixelMatrixBuffer,             0,             displayDevice.geometry.width,             0,             displayDevice.geometry.height); #endif     return 0; }

Собственно все. На коротком видео можно увидеть результат.

Весь код доступен на GitHub. Если есть плата, то же самое можно воспроизвести на ней с помощью инструкции описанной на wiki.

Результат превзошел мои ожидания. Ведь удалось запустить Embox по сути на 2kB RAM. Это означает, что с помощью опций в Embox накладные расходы на использование ОС можно свести к минимуму. При этом в системе присутствует многозадачность. Пусть даже она и кооперативная. Ведь обработчики таймеров вызываются не напрямую в контексте прерывания, а из собственного контекста. Что естественно является плюсом использования ОС. Конечно, данный пример во многом искусственный. Ведь при столь ограниченных ресурсах и функциональность будет ограниченной. Преимущества Embox начинают сказываться на более мощных платформах. Но в то же время это можно считать предельным случаем работы Embox.

ссылка на оригинал статьи https://habr.com/ru/company/embox/blog/537408/

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

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