Embox es un RTOS altamente configurable. La idea principal de Embox es ejecutar software Linux de forma transparente en todas partes, incluso en microcontroladores. Entre los logros, cabe mencionar OpenCV , Qt , PJSIP , que se ejecutan en microcontroladores STM32F7. Por supuesto, el lanzamiento implica que no se realizaron cambios en estos proyectos y solo se utilizaron las opciones al configurar los proyectos originales y los parámetros establecidos en la propia configuración de Embox. Pero surge una pregunta natural en qué medida Embox le permite ahorrar recursos en comparación con el mismo Linux. Después de todo, este último también es bastante configurable.
Para responder a esta pregunta, puede elegir la plataforma de hardware mínima para ejecutar Embox. Elegimos EMF32ZG_STK3200 de SiliconLabs como tal plataforma . Esta plataforma tiene una ROM de 32kB y una memoria RAM de 4kB. Y también el núcleo del procesador cortex-m0 +. Los periféricos disponen de UART, LED personalizados, botones y una pantalla monocromática de 128x128. Nuestro objetivo es lanzar cualquier aplicación personalizada que nos permita asegurarnos de que Embox funciona en esta placa.
Para trabajar con periféricos y la propia placa, necesita controladores y otro código del sistema. Este código se puede tomar de ejemplos proporcionados por el propio fabricante del chip. En nuestro caso, el fabricante sugiere utilizar SimplifyStudio. Tambien hay repositorio abierto en GitHub ). Usaremos este código.
Embox tiene mecanismos para utilizar el BSP del fabricante al crear controladores. Para hacer esto, necesita descargar el BSP y construirlo como una biblioteca en Embox. En este caso, puede especificar varias rutas y marcadores necesarios para utilizar esta biblioteca en los controladores.
Ejemplo de Makefile para descargar 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 para construir 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 para la placa 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", ... }
Después de pasos tan simples, puede usar el código del fabricante. Antes de comenzar a trabajar con controladores, debe comprender las herramientas de desarrollo y las partes arquitectónicas. Embox utiliza las herramientas de desarrollo habituales gcc, gdb, openocd. Al iniciar openocd, debe indicar que estamos usando la plataforma efm32:
sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg
No hay partes arquitectónicas especiales para nuestras bufandas, solo las especificaciones de cortex-m0 +. Esto lo establece el compilador. Por lo tanto, podemos configurar el código general para cotrex-m0 deshabilitando todo lo innecesario, por ejemplo, trabajar con coma flotante.
@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
Después de eso, puede intentar compilar Embox y seguir los pasos usando el depurador, verificando así si hemos configurado correctamente los parámetros en el script del vinculador.
/* region (origin, length) */
ROM (0x00000000, 32K)
RAM (0x20000000, 4K)
/* section (region[, lma_region]) */
text (ROM)
rodata (ROM)
data (RAM, ROM)
bss (RAM)
El primer controlador implementado para admitir cualquier placa en Embox suele ser el UART. Nuestro tablero tiene LEUART. Es suficiente que el conductor implemente varias funciones. Al hacerlo, podemos usar funciones del 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);
Para que las funciones BSP estén disponibles, solo necesita indicarlo en la descripción del controlador, el archivo 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 }
Después de implementar el controlador UART, no solo la salida está disponible para usted, sino también la consola donde puede llamar a sus comandos personalizados. Para hacer esto, solo necesita agregar un pequeño intérprete de comandos al archivo de configuración de 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")
Y también indique que necesita usar no un tty completo disponible a través de devfs, sino un stub que le permita acceder al dispositivo especificado. El dispositivo también se especifica en el archivo de configuración 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
Otro controlador muy simple es GPIO. Para implementarlo, también podemos usar llamadas del BSP. Para ello, en la descripción del controlador, indicaremos que depende del 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 }
La propia implementación:
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);
}
Esto es suficiente para usar el comando 'pin' de Embox. Este comando le permite controlar el GPIO. Y en particular, se puede utilizar para comprobar el parpadeo de un LED.
Agregue el comando en sí a mods.conf:
include embox.cmd.hardware.pin
Y hagámoslo funcionar al inicio. Para hacer esto, agregue una de las líneas en el archivo de configuración start_sctpt.inc:
<fuente ">" pin GPIOC 10 blink ",
O
"pin GPIOC 11 blink",
Los comandos son los mismos, solo que los números de LED son diferentes.
Intentemos iniciar la pantalla también. Al principio es simple. Después de todo, podemos volver a utilizar las llamadas BSP. Para hacer esto, solo necesitamos agregarlos a la descripción del controlador framebuffer:
package embox.driver.video @BuildDepends(platform.efm32.efm32_bsp) module efm32_lcd { ... source "efm32_lcd.c" @NoRuntime depends platform.efm32.efm32_bsp }
Pero tan pronto como hacemos cualquier llamada relacionada con la pantalla, por ejemplo DISPLAY_Init, nuestra sección .bss crece en más de 2 kB, con un tamaño de RAM de 4 kB, esto es muy significativo. Después de estudiar este problema, resultó que en el propio BSP se asigna un framebuffer para la pantalla. Es decir, 128x128x1 bits o 2048 bytes.
En este punto, incluso quería detenerme allí, porque es un logro en sí mismo ajustar la llamada de los comandos del usuario con un intérprete de comandos simple en 4kB RAM. Pero decidí intentarlo.
Primero, eliminé el shell y dejé solo la llamada al comando pin ya mencionado. Para hacer esto, modifiqué el archivo de configuración mods.conf de la siguiente manera:
//@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)
Como estaba usando un módulo diferente para el inicio personalizado, moví el comando de inicio a un archivo de configuración diferente. Usé system_start.inc en lugar de start_script.inc.
Luego, como ya no necesitaba usar inodos en el shell, así como temporizadores, me deshice de ellos usando las opciones en 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)
Como estaba llamando a los comandos directamente y no a través del shell, pude reducir el tamaño de la pila:
@Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=224) @Runlevel(0) include embox.kernel.stack(stack_size=448,alignment=4)
Finalmente, hice que el LED parpadeara y comencé, y adentro hubo una llamada para inicializar la pantalla.
Quería mostrar algo en la pantalla, pensé que el logo de Embox sería indicativo. Bueno, necesita usar un controlador de framebuffer completo y generar una imagen desde un archivo, porque todo esto está en Embox. Pero no había suficiente espacio. Y para la demostración, decidí mostrar el logotipo directamente en la función de inicialización del controlador framebuffer. Además, los datos se convierten directamente en un mapa de bits. Por lo tanto, necesitaba exactamente 2048 bytes en ROM.
El código en sí, como antes, usa 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;
}
Eso es todo. En un breve video puedes ver el resultado.
Todo el código está disponible en GitHub . Si hay una placa, la misma se puede reproducir en ella siguiendo las instrucciones descritas en la wiki .
El resultado superó mis expectativas. Después de todo, logramos ejecutar Embox en esencialmente 2kB de RAM. Esto significa que con las opciones de Embox, la sobrecarga del sistema operativo se puede minimizar. Además, el sistema tiene multitarea. Incluso si es cooperativo. Después de todo, los manejadores de temporizadores no se llaman directamente en el contexto de interrupción, sino desde su propio contexto. Lo que, naturalmente, es una ventaja de usar el sistema operativo. Por supuesto, este ejemplo es en gran parte artificial. De hecho, con recursos tan limitados, la funcionalidad será limitada. Los beneficios de Embox están comenzando a pasar factura en plataformas más poderosas. Pero al mismo tiempo, este puede considerarse el caso límite de Embox.