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/