Embox sur la carte EFM32ZG_STK3200. Comment adapter RTOS à 4 Ko de RAM

image

Embox est un RTOS hautement configurable. L'idée principale d'Embox est d'exécuter en toute transparence des logiciels Linux partout, y compris sur des microcontrôleurs. Parmi les réalisations, il convient de mentionner OpenCV , Qt , PJSIP , fonctionnant sur des microcontrôleurs STM32F7. Bien entendu, le lancement implique qu'aucune modification n'a été apportée à ces projets et que seules les options ont été utilisées lors de la configuration des projets d'origine et des paramètres définis dans la configuration Embox elle-même. Mais une question naturelle se pose dans quelle mesure Embox vous permet d'économiser des ressources par rapport au même Linux? Après tout, ce dernier est également assez bien configurable.



Pour répondre à cette question, vous pouvez choisir la plate-forme matérielle minimale pour exécuter Embox. Nous avons choisi EMF32ZG_STK3200 de SiliconLabs en tant que telle plate-forme . Cette plate-forme dispose de 32 Ko de ROM et de 4 Ko de mémoire RAM. Et aussi le cœur du processeur cortex-m0 +. Des UART, des LED personnalisées, des boutons et un écran monochrome 128x128 sont disponibles à partir des périphériques. Notre objectif est de lancer toute application personnalisée qui nous permet de nous assurer qu'Embox fonctionne sur cette carte.



Pour travailler avec les périphériques et la carte elle-même, vous avez besoin de pilotes et d'autres codes système. Ce code peut être tiré d'exemples fournis par le fabricant de puces lui-même. Dans notre cas, le fabricant propose d'utiliser SimplifyStudio. Il y a aussi dépôt ouvert sur GitHub ). Nous utiliserons ce code.



Embox dispose de mécanismes pour utiliser le BSP du fabricant lors de la création de pilotes. Pour ce faire, vous devez télécharger le BSP et le créer en tant que bibliothèque dans Embox. Dans ce cas, vous pouvez spécifier divers chemins et indicateurs requis pour utiliser cette bibliothèque dans les pilotes.



Exemple de Makefile pour télécharger 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 pour construire 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 pour la carte 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",

...

}
      
      





Après ces étapes assez simples, vous pouvez utiliser le code du fabricant. Avant de commencer à travailler avec des pilotes, vous devez comprendre les outils de développement et les éléments architecturaux. Embox utilise les outils de développement habituels gcc, gdb, openocd. Lors du démarrage d'openocd, vous devez indiquer que nous utilisons la plateforme efm32:



sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg
      
      





Il n'y a pas de pièces architecturales spéciales pour nos foulards, seulement les spécificités du cortex-m0 +. Ceci est défini par le compilateur. Par conséquent, nous pouvons définir le code général de cotrex-m0 en désactivant toutes les choses inutiles, par exemple en travaillant avec la virgule flottante.



     @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
      
      





Après cela, vous pouvez essayer de compiler Embox et parcourir les étapes à l'aide du débogueur, vérifiant ainsi si nous avons correctement défini les paramètres dans le script de l'éditeur de liens



/* region (origin, length) */
ROM (0x00000000, 32K)
RAM (0x20000000, 4K)

/* section (region[, lma_region]) */
text   (ROM)
rodata (ROM)
data   (RAM, ROM)
bss    (RAM)
      
      





Le premier pilote implémenté pour prendre en charge n'importe quelle carte dans Embox est généralement l'UART. Notre conseil d'administration a LEUART. Il suffit que le conducteur implémente plusieurs fonctions. Ce faisant, nous pouvons utiliser des fonctions du 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);
      
      





Pour que les fonctions BSP soient disponibles, il vous suffit de l'indiquer dans la description du pilote, le fichier 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
}
      
      





Après avoir implémenté le pilote UART, non seulement la sortie est disponible pour vous, mais également la console où vous pouvez appeler vos commandes personnalisées. Pour ce faire, il vous suffit d'ajouter un petit interpréteur de commandes au fichier de configuration d'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")
      
      





Et indiquez également que vous devez utiliser non pas un tty à part entière disponible via devfs, mais un stub qui vous permet d'accéder au périphérique spécifié. Le périphérique est également spécifié dans le fichier de configuration 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
      
      





Un autre pilote très simple est GPIO. Pour l'implémenter, nous pouvons également utiliser des appels du BSP. Pour ce faire, dans la description du pilote, nous indiquons que cela dépend du 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 mise en œuvre elle-même:



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);

}
      
      





Cela suffit pour utiliser la commande 'pin' d'Embox. Cette commande vous permet de contrôler le GPIO. Et en particulier, il peut être utilisé pour vérifier le clignotement d'une LED.



Ajoutez la commande elle-même à mods.conf:



include embox.cmd.hardware.pin
      
      





Et faisons-le fonctionner au démarrage. Pour ce faire, ajoutez une des lignes du fichier de configuration start_sctpt.inc:



<source ">" pin GPIOC 10 blink ",

Ou



"pin GPIOC 11 blink",
      
      





Les commandes sont les mêmes, seuls les numéros de LED sont différents.



Essayons également de démarrer l'affichage. C'est simple au début. Après tout, nous pouvons à nouveau utiliser les appels BSP. Pour ce faire, il suffit de les ajouter à la description du pilote de framebuffer:



package embox.driver.video

@BuildDepends(platform.efm32.efm32_bsp)
module efm32_lcd {
...

    source "efm32_lcd.c"
    @NoRuntime depends platform.efm32.efm32_bsp
}
      
      





Mais dès que l'on fait un appel lié à l'affichage, par exemple DISPLAY_Init, notre section .bss augmente de plus de 2 ko, avec une taille de RAM de 4 ko, c'est très significatif. Après avoir étudié ce problème, il s'est avéré que dans le BSP lui-même, un framebuffer est alloué pour l'affichage. Autrement dit, 128x128x1 bits ou 2048 octets.



À ce stade, je voulais même m'arrêter là, car c'est un exploit en soi d'adapter l'appel de commandes utilisateur avec un simple interpréteur de commandes dans 4 ko de RAM. Mais j'ai décidé de l'essayer.



Tout d'abord, j'ai supprimé le shell et laissé uniquement l'appel à la commande pin déjà mentionnée. Pour ce faire, j'ai modifié le fichier de configuration mods.conf comme suit:



    //@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)
      
      





Comme j'utilisais un module différent pour le démarrage personnalisé, j'ai déplacé le lancement de la commande vers un fichier de configuration différent. J'ai utilisé system_start.inc au lieu de start_script.inc.



Ensuite, comme je n'avais plus besoin d'utiliser des inodes dans le shell, ainsi que des minuteries, je me suis débarrassé d'eux en utilisant les options de 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)
      
      





Comme j'appelais des commandes directement et non via le shell, j'ai pu réduire la taille de la pile:



    @Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=224)
    @Runlevel(0) include embox.kernel.stack(stack_size=448,alignment=4)
      
      





Finalement, j'ai fait clignoter la LED et j'ai démarré, et à l'intérieur, il y avait un appel pour initialiser l'affichage.



Je voulais afficher quelque chose sur l'écran, je pensais que le logo Embox serait indicatif. À un bon niveau, vous devez utiliser un pilote de framebuffer à part entière et générer une image à partir d'un fichier, car tout cela est dans Embox. Mais il n'y avait pas assez de place. Et pour démonstration, j'ai décidé d'afficher le logo directement dans la fonction d'initialisation du pilote framebuffer. De plus, les données sont converties directement en un bitmap. Ainsi, j'avais besoin d'exactement 2048 octets en ROM.



Le code lui-même, comme auparavant, utilise 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;
}
      
      





C'est tout. Dans une courte vidéo, vous pouvez voir le résultat.





Tout le code est disponible sur GitHub . S'il y a un tableau, celui-ci peut être reproduit dessus en utilisant les instructions décrites sur le wiki .



Le résultat a dépassé mes attentes. Après tout, nous avons réussi à exécuter Embox sur essentiellement 2 Ko de RAM. Cela signifie qu'avec les options d'Embox, la surcharge du système d'exploitation peut être minimisée. De plus, le système est multitâche. Même si c'est coopératif. Après tout, les gestionnaires de minuterie ne sont pas appelés directement dans le contexte d'interruption, mais à partir de leur propre contexte. Ce qui est naturellement un plus de l'utilisation du système d'exploitation. Bien entendu, cet exemple est largement artificiel. En effet, avec des ressources aussi limitées, la fonctionnalité sera limitée. Les avantages d'Embox commencent à faire des ravages sur des plateformes plus puissantes. Mais en même temps, cela peut être considéré comme le cas limite d'Embox.



All Articles