Embox auf der EFM32ZG_STK3200-Karte. So passen Sie RTOS in 4 KB RAM ein

Bild

Embox ist ein hoch konfigurierbares RTOS. Die Hauptidee von Embox ist es, Linux-Software überall transparent auszuführen, auch auf Mikrocontrollern. Unter den Errungenschaften ist OpenCV , Qt , PJSIP zu erwähnen , die auf STM32F7-Mikrocontrollern laufen. Der Start impliziert natürlich, dass keine Änderungen an diesen Projekten vorgenommen wurden und nur die Optionen bei der Konfiguration der ursprünglichen Projekte und der in der Embox-Konfiguration selbst festgelegten Parameter verwendet wurden. Es stellt sich jedoch die natürliche Frage, inwieweit Sie mit Embox im Vergleich zu demselben Linux Ressourcen sparen können. Letzteres ist schließlich auch recht gut konfigurierbar.



Um diese Frage zu beantworten, können Sie die minimale Hardwareplattform für die Ausführung von Embox auswählen. Als solche Plattform haben wir EMF32ZG_STK3200 von SiliconLabs ausgewählt. Diese Plattform verfügt über 32 KB ROM und 4 KB RAM. Und auch der Cortex-m0 + Prozessorkern. UARTs, benutzerdefinierte LEDs, Tasten und ein 128 x 128-Monochrom-Display sind an den Peripheriegeräten erhältlich. Unser Ziel ist es, jede benutzerdefinierte Anwendung zu starten, mit der wir sicherstellen können, dass Embox auf diesem Board funktioniert.



Um mit Peripheriegeräten und der Karte selbst arbeiten zu können, benötigen Sie Treiber und anderen Systemcode. Dieser Code kann Beispielen entnommen werden, die vom Chiphersteller selbst bereitgestellt wurden. In unserem Fall schlägt der Hersteller die Verwendung von SimplifyStudio vor. Es gibt auch Repository auf GitHub öffnen ). Wir werden diesen Code verwenden.



Embox verfügt über Mechanismen zur Verwendung des BSP des Herstellers beim Erstellen von Treibern. Dazu müssen Sie das BSP herunterladen und als Bibliothek in Embox erstellen. In diesem Fall können Sie verschiedene Pfade und Flags angeben, die für die Verwendung dieser Bibliothek in Treibern erforderlich sind.



Beispiel-Makefile zum Herunterladen von 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 zum Erstellen von 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 für EFM32ZG_STK3200 Board:



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",

...

}
      
      





Nach solch relativ einfachen Schritten können Sie den Code des Herstellers verwenden. Bevor Sie mit Treibern arbeiten, müssen Sie die Entwicklungstools und Architekturteile verstehen. Embox verwendet die üblichen Entwicklungstools gcc, gdb, openocd. Wenn Sie openocd starten, müssen Sie angeben, dass wir die efm32-Plattform verwenden:



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





Es gibt keine speziellen architektonischen Teile für unsere Schals, nur die Besonderheiten von cortex-m0 +. Dies wird vom Compiler festgelegt. Daher können wir den allgemeinen Code für cotrex-m0 festlegen, indem wir alles Unnötige deaktivieren, z. B. das Arbeiten mit Gleitkommazahlen.



     @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
      
      





Danach können Sie versuchen, Embox zu kompilieren und die Schritte mit dem Debugger durchzugehen, um zu überprüfen, ob wir die Parameter im Linker-Skript korrekt eingestellt haben



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

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





Der erste Treiber, der zur Unterstützung eines Boards in Embox implementiert wurde, ist normalerweise der UART. Unser Vorstand hat LEUART. Es reicht aus, wenn der Treiber mehrere Funktionen implementiert. Dabei können wir Funktionen aus dem BSP verwenden.



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





Damit die BSP-Funktionen verfügbar sind, müssen Sie dies nur in der Treiberbeschreibung, der Mybuild-Datei, angeben:



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
}
      
      





Nach der Implementierung des UART-Treibers steht Ihnen nicht nur die Ausgabe zur Verfügung, sondern auch die Konsole, über die Sie Ihre benutzerdefinierten Befehle aufrufen können. Dazu müssen Sie der Embox-Konfigurationsdatei lediglich einen kleinen Befehlsinterpreter hinzufügen:



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





Geben Sie außerdem an, dass Sie kein vollwertiges tty verwenden müssen, das über devfs verfügbar ist, sondern einen Stub, mit dem Sie auf das angegebene Gerät zugreifen können. Das Gerät ist auch in der Konfigurationsdatei mods.conf angegeben:



    @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
      
      





Ein weiterer sehr einfacher Treiber ist GPIO. Um es zu implementieren, können wir auch Aufrufe vom BSP verwenden. Dazu geben wir in der Treiberbeschreibung an, dass dies vom BSP abhängt:



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
}
      
      





Die Implementierung selbst:



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

}
      
      





Dies reicht aus, um den Befehl 'pin' von Embox zu verwenden. Mit diesem Befehl können Sie das GPIO steuern. Insbesondere kann damit das Blinken einer LED überprüft werden.



Fügen Sie den Befehl selbst zu mods.conf hinzu:



include embox.cmd.hardware.pin
      
      





Und lassen Sie es beim Start laufen. Fügen Sie dazu eine der Zeilen in die Konfigurationsdatei start_sctpt.inc ein:



<source ">" pin GPIOC 10 blink ",

Or. En



"pin GPIOC 11 blink",
      
      





Die Befehle sind gleich, nur die LED-Nummern sind unterschiedlich.



Versuchen wir auch, die Anzeige zu starten. Anfangs ist es einfach. Immerhin können wir wieder BSP-Aufrufe verwenden. Dazu müssen wir sie nur zur Beschreibung des Framebuffer-Treibers hinzufügen:



package embox.driver.video

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

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





Sobald wir jedoch einen Aufruf im Zusammenhang mit der Anzeige tätigen, z. B. DISPLAY_Init, erhöht sich unser .bss-Bereich um mehr als 2 kB bei einer RAM-Größe von 4 kB. Dies ist sehr wichtig. Nach dem Studium dieses Problems stellte sich heraus, dass im BSP selbst ein Framebuffer für die Anzeige zugewiesen ist. Das heißt, 128 x 128 x 1 Bit oder 2048 Byte.



An dieser Stelle wollte ich sogar damit aufhören, weil es an sich schon eine Errungenschaft ist, den Aufruf von Benutzerbefehlen mit einem einfachen Befehlsinterpreter in 4 kB RAM zu versehen. Aber ich habe beschlossen, es zu versuchen.



Zuerst entfernte ich die Shell und überließ nur den Aufruf des bereits erwähnten Pin-Befehls. Zu diesem Zweck habe ich die Konfigurationsdatei mods.conf wie folgt geändert:



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





Da ich für den benutzerdefinierten Start ein anderes Modul verwendet habe, habe ich den Befehlsstart in eine andere Konfigurationsdatei verschoben. Ich habe system_start.inc anstelle von start_script.inc verwendet.



Da ich dann keine Inodes mehr in der Shell sowie keine Timer mehr verwenden musste, habe ich sie mithilfe der Optionen in mods.config entfernt:



    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)
      
      





Da ich Befehle direkt und nicht über die Shell aufrief, konnte ich die Stapelgröße reduzieren:



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





Schließlich ließ ich die LED blinken und startete, und im Inneren gab es einen Anruf, um die Anzeige zu initialisieren.



Ich wollte etwas auf dem Display anzeigen. Ich dachte, das Embox-Logo wäre bezeichnend. Auf jeden Fall müssen Sie einen vollwertigen Framebuffer-Treiber verwenden und ein Bild aus einer Datei ausgeben, da sich all dies in Embox befindet. Aber es gab nicht genug Platz. Und zur Demonstration habe ich beschlossen, das Logo direkt in der Initialisierungsfunktion des Framebuffer-Treibers anzuzeigen. Darüber hinaus werden die Daten direkt in eine Bitmap konvertiert. Somit brauchte ich genau 2048 Bytes im ROM.



Der Code selbst verwendet nach wie vor 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;
}
      
      





Das ist alles. In einem kurzen Video sehen Sie das Ergebnis.





Der gesamte Code ist auf GitHub verfügbar . Wenn es ein Board gibt, kann dasselbe mit den im Wiki beschriebenen Anweisungen darauf reproduziert werden .



Das Ergebnis hat meine Erwartungen übertroffen. Immerhin haben wir es geschafft, Embox mit im Wesentlichen 2 KB RAM auszuführen. Dies bedeutet, dass mit den Optionen in Embox der Betriebssystem-Overhead minimiert werden kann. Darüber hinaus verfügt das System über Multitasking. Auch wenn es kooperativ ist. Timer-Handler werden schließlich nicht direkt im Interrupt-Kontext aufgerufen, sondern aus ihrem eigenen Kontext. Das ist natürlich ein Plus bei der Verwendung des Betriebssystems. Natürlich ist dieses Beispiel weitgehend künstlich. In der Tat wird mit solch begrenzten Ressourcen die Funktionalität begrenzt sein. Die Vorteile von Embox fordern zunehmend ihren Tribut auf leistungsstärkeren Plattformen. Gleichzeitig kann dies jedoch als Grenzfall für Embox angesehen werden.



All Articles