Embox na placa EFM32ZG_STK3200. Como ajustar o RTOS em 4kB de RAM

imagem

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.



All Articles