Embox é um RTOS altamente configurável. A ideia principal da Embox é executar o software Linux de forma transparente em qualquer lugar, inclusive em microcontroladores. Entre as realizações, vale citar OpenCV , Qt , PJSIP , rodando em microcontroladores STM32F7. Obviamente, o lançamento implica que nenhuma alteração foi feita nesses projetos e apenas as opções foram usadas ao configurar os projetos originais e os parâmetros definidos na própria configuração da Embox. Mas surge uma pergunta natural até que ponto o Embox permite que você economize recursos em comparação com o mesmo Linux? Afinal, o último também é bastante bem configurável.
Para responder a esta pergunta, você pode escolher a plataforma de hardware mínima para executar o Embox. Escolhemos EMF32ZG_STK3200 da SiliconLabs como tal plataforma . Esta plataforma possui ROM de 32kB e memória RAM de 4kB. E também o núcleo do processador cortex-m0 +. UARTs, LEDs personalizados, botões e uma tela monocromática de 128x128 estão disponíveis nos periféricos. Nosso objetivo é lançar qualquer aplicativo personalizado que nos permita ter certeza de que Embox funciona nesta placa.
Para trabalhar com periféricos e a própria placa, você precisa de drivers e outros códigos de sistema. Este código pode ser obtido a partir de exemplos fornecidos pelo próprio fabricante do chip. Em nosso caso, o fabricante sugere o uso de SimplifyStudio. Há também repositório aberto no GitHub ). Usaremos este código.
A Embox possui mecanismos para usar o BSP do fabricante ao criar drivers. Para fazer isso, você precisa baixar o BSP e construí-lo como uma biblioteca no Embox. Nesse caso, você pode especificar vários caminhos e sinalizadores necessários para usar esta biblioteca nos drivers.
Exemplo de Makefile para baixar 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 compilação 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 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", ... }
Após essas etapas bastante simples, você pode usar o código do fabricante. Antes de começar a trabalhar com drivers, você precisa entender as ferramentas de desenvolvimento e as partes arquitetônicas. A Embox usa as ferramentas de desenvolvimento usuais gcc, gdb, openocd. Ao iniciar o openocd, você precisa indicar que estamos usando a plataforma efm32:
sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg
Não há peças arquitetônicas especiais para nossos lenços, apenas as especificações do córtex-m0 +. Isso é definido pelo compilador. Portanto, podemos definir o código geral para cotrex-m0 desabilitando todas as coisas desnecessárias, por exemplo, trabalhar com ponto flutuante.
@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
Depois disso, você pode tentar compilar o Embox e seguir as etapas usando o depurador, verificando se definimos corretamente os parâmetros no script do vinculador
/* region (origin, length) */
ROM (0x00000000, 32K)
RAM (0x20000000, 4K)
/* section (region[, lma_region]) */
text (ROM)
rodata (ROM)
data (RAM, ROM)
bss (RAM)
O primeiro driver implementado para dar suporte a qualquer placa da Embox é geralmente o UART. Nosso conselho tem LEUART. Basta que o driver implemente várias funções. Ao fazer isso, podemos usar funções do 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 as funções do BSP estejam disponíveis, basta indicar isso na descrição do driver, o arquivo 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 }
Depois de implementar o driver UART, não apenas a saída fica disponível para você, mas também o console onde você pode chamar seus comandos personalizados. Para fazer isso, você só precisa adicionar um pequeno interpretador de comando ao arquivo de configuração 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")
E também indique que você não precisa usar um tty completo disponível através do devfs, mas um stub que permite acessar o dispositivo especificado. O dispositivo também é especificado no arquivo de configuração 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
Outro driver muito simples é o GPIO. Para implementá-lo, também podemos usar chamadas do BSP. Para isso, na descrição do driver, indicaremos que depende do 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 }
A própria implementação:
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);
}
Isso é o suficiente para usar o comando 'pin' da Embox. Este comando permite que você controle o GPIO. E, em particular, pode ser usado para verificar o piscar de um LED.
Adicione o próprio comando a mods.conf:
include embox.cmd.hardware.pin
E vamos fazê-lo funcionar na inicialização. Para fazer isso, adicione uma das linhas no arquivo de configuração start_sctpt.inc:
<source ">" pin GPIOC 10 blink ",
Ou
"pin GPIOC 11 blink",
Os comandos são os mesmos, apenas os números dos LEDs são diferentes.
Vamos tentar iniciar a exibição também. É simples no início. Afinal, podemos usar chamadas BSP novamente. Para fazer isso, precisamos apenas adicioná-los à descrição do driver do framebuffer:
package embox.driver.video @BuildDepends(platform.efm32.efm32_bsp) module efm32_lcd { ... source "efm32_lcd.c" @NoRuntime depends platform.efm32.efm32_bsp }
Mas assim que fizermos qualquer chamada relacionada ao display, por exemplo DISPLAY_Init, nossa seção .bss aumenta em mais de 2 kB, com um tamanho de RAM de 4 kB, isso é muito significativo. Depois de estudar essa questão, descobriu-se que no próprio BSP um framebuffer é alocado para a exibição. Ou seja, 128x128x1 bits ou 2.048 bytes.
Neste ponto, eu até queria parar por aí, porque é uma conquista em si mesmo caber a chamada de comandos do usuário com algum interpretador de comandos simples em 4kB de RAM. Mas decidi tentar.
Primeiro, removi o shell e deixei apenas a chamada para o comando pin já mencionado. Para fazer isso, modifiquei o arquivo de configuração mods.conf da seguinte maneira:
//@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 estava usando um módulo diferente para o início personalizado, movi o comando de inicialização para um arquivo de configuração diferente. Usei system_start.inc em vez de start_script.inc.
Então, como eu não precisava mais usar inodes no shell, bem como timers, me livrei deles usando as opções em 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 estava chamando comandos diretamente e não por meio do shell, consegui reduzir o tamanho da pilha:
@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, fiz o LED piscar e liguei, e dentro havia uma chamada para inicializar o display.
Queria exibir algo no visor. Achei que o logotipo da Embox seria indicativo. Tudo bem, você precisa usar um driver de framebuffer completo e gerar uma imagem de um arquivo, porque tudo isso está no Embox. Mas não havia espaço suficiente. E para demonstração, decidi exibir o logotipo diretamente na função de inicialização do driver do framebuffer. Além disso, os dados são convertidos diretamente em um bitmap. Portanto, eu precisava de exatamente 2.048 bytes em ROM.
O próprio código, 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;
}
Isso é tudo. Em um pequeno vídeo você pode ver o resultado.
Todo o código está disponível no GitHub . Se houver um quadro, o mesmo pode ser reproduzido nele usando as instruções descritas no wiki .
O resultado superou minhas expectativas. Afinal, conseguimos executar o Embox em essencialmente 2kB de RAM. Isso significa que, com as opções do Embox, a sobrecarga do SO pode ser minimizada. Além disso, o sistema possui multitarefa. Mesmo que seja cooperativo. Afinal, os manipuladores de cronômetro não são chamados diretamente no contexto de interrupção, mas a partir de seu próprio contexto. O que é naturalmente uma vantagem de usar o sistema operacional. Claro, este exemplo é amplamente artificial. Na verdade, com tais recursos limitados, a funcionalidade será limitada. Os benefícios da Embox estão começando a afetar as plataformas mais poderosas. Mas, ao mesmo tempo, este pode ser considerado o caso limite da Embox.