Embox является сильно конфигурируемой RTOS. Основная идея Embox — прозрачный запуск Linux программного обеспечения везде, в том числе и на микроконтроллерах. Из достижений стоит привести OpenCV, Qt, PJSIP, запущенные на микроконтроллерах STM32F7. Конечно, запуск подразумевает, что в данные проекты не вносились изменения и использовались только опции при конфигурации оригинальных проектов и параметры задаваемые в самой конфигурации Embox. Но возникает естественный вопрос насколько Embox позволяет экономить ресурсы по сравнению с тем же Linux? Ведь последний также достаточно хорошо конфигурируется.
Для ответа на этот вопрос можно подобрать минимально возможную для запуска Embox аппаратную платформу. В качестве такой платформы мы выбрали EMF32ZG_STK3200 от компании SiliconLabs. Данная платформа имеет 32kB ROM и 4kB RAM память. А также процессорное ядро cortex-m0+. Из периферии доступны UART, пользовательские светодиоды, кнопки, а также 128x128 монохромный дисплей. Нашей целью является запуск любого пользовательского приложения, позволяющего убедиться в работоспособности 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.
dilvar
Зацепила фраза «Основная идея Embox — прозрачный запуск Linux программного обеспечения везде, в том числе и на микроконтроллерах.»
Поясните, что имелось в виду?
abondarev Автор
Берем обычное приложение Qt компилируем его в составе Embox выкинув все лишнее и умещаемся внутрь микроконтроллера.
predator86
Сколько ресурсов забрала сама Embox?
abondarev Автор
Точно не замеряли. Но исходя из того что есть всего 2 kB один из которых идет на стек, сколько то еще тратится на сами данные BSP. то получается несколько сотен байт. Скажем 512. Половина это данные для трех легких потоков. Один основной, один для таймеров и еще для обработки GPIO как кнопки по прерываниям
predator86
Так есть ли смысл использовать Embox на таких мелких контроллерах?
А какие МК вы бы рекомендовали для использования с Embox?
abondarev Автор
Полностью согласен. Так и написал в заключении:
В принципе достаточно хорошо уже начинает сказываться на stm32vl-discovery (128кб RAM). А на каком нибудь stm32f4-discovery (196 кб) вообще удается много чего запустить.
Но все конечно должно исходить не от аппаратной платформы, а от задачи. Чем больший функционал требуется тем лучше проявляются преимущества
predator86
А по сравнению с FreeRTOS или другой подобной ОС, удастся ли получить какие-нибудь отличительные преимущества?
abondarev Автор
Конечно, об преимуществах я и говорю.
В Embox можно использовать готовый софт из Linux или разработать его не на плате в на хосте со всеми удобствами. А потом легким движением руки перенести его на целевую платформу.
Повторюсь, если вам потребовался функционал OpenCV, у вас есть две возможности, долго разрабоатывать свой непереносимый код на небольшой плате или взять и быстро запустить OpenCV на Linux на большой плате. Embox позволяет достаточно быстро запустить на небольшой и надежной платформе.
predator86
Что мешает разрабатывать только библиотеки типа OpenCV (и т.п.) для уже популярных и зарекомендовавших себя ОС для МК? В чём преимущества самой Embox как ОС?
abondarev Автор
Ничто не мешает.
Не понимаю в чем вопрос, поясните пожалуйста. Приведите пример преимущества которое есть на Ваш взгляд у любой популярной ОС для МК.
Я даже не говорю, что Embox это не ОС для МК («в том числе для МК» это не значит, что она только для МК). И, что преимуществом является сам факт того, что OpenCV не нужно разработывать под эту ОС, а можно просто взять готовое и отлаженное решение. Просто хотелось бы понять, в чем по вашему мнению преимущества FreeRTOS или любой другой RTOS на ваш выбор?
predator86
Просто интересно наблюдать за проектом. Так что извините за назойливость, нахожусь в поиске «идеальной» ОС для МК.
abondarev Автор
Не так давно делал доклад на конференции OSDAY под названием «Какая она идеальная ОС для встроенных систем?». Вроде на сайте есть трансляция.
Я не сказал, что Embox эта самая идеальная ОС. Сказал что идеальной не существует, но в Embox были учтены многие особенности подобных систем и мы стараемся удовлетворять потребности разработчиков. Для МК критична экономия ресурсов, собственно все RTOS ориентированны на этот параметр. У нас изначально был другой стимул мы старались обеспечить только требуемую функциональность в Linux (немного в статье «Многообразный мир embedded systems и место Embox в нем» ), но в результате удалось добиться приведенных в данной статье результатов.
По поводу идеальности Embox для МК скажу так. все зависит от задачи, но Embox позволяет оторваться от ОС. То есть надоел Embox, запустили на другой ОС. Большинство RTOS, как Вы сами правильно заметили, заставляют разрабатывать под конкретную ОС. И выбор той или иной ОС это дело вкуса и привычки конкретного разработчика.
chnav
А если писать свои библиотеки, то отлаживать их в Linux намного проще, а потом практически готовое решение скормить Embox. Как по мне — очень интересная идея. Поправьте если ошибся.
abondarev Автор
Вы абсолютно правы. Спасибо!
predator86
Хотел узнать, почему для внедрения в МК линух библиотек потребовалось создавать целую ОС, почему бы не направить силы в развитие уже готовых и просто снабдить их этим функционалом?
abondarev Автор
Потому что это невозможно. Архитектура, точнее даже подход или идеалогия, не позволяет добавить подобные возможности в FreeRTOS и аналаги. Там можно добавить POSIX слой, но это не позволит запускать как отдельные приложения. Аналогично с Linux, да, он модульный, но все равно ядро будет монолитное и весить минимум несколько мегабайт. Да есть попытки приблизить RT-Linux, uCLinux, но идеалогия другая. Требуется предусмотреть эти возможности и заложить их в архитектуру.
Собственно идея не нова, мы предлагаем один из подходов, который показывает хорошие результаты. Есть и другие eCos, NuttX, если говорить о RTOS. Или zephyr который использует близкий к линуксовому devtree.