Emboxは高度に構成可能なRTOSです。 Emboxの主なアイデアは、マイクロコンピューターを含め、あらゆる場所でLinuxソフトウェアを透過的に実行することです。成果の中で、STM32F7マイクロシステムで実行されているOpenCV、 Qt、 PJSIPについて言及する価値 があります。もちろん、起動は、これらのプロジェクトに変更が加えられておらず、元のプロジェクトとEmbox構成自体で設定されたパラメーターを構成するときにオプションのみが使用されたことを意味します。しかし、同じLinuxと比較して、Emboxでリソースをどの程度節約できるのかという自然な疑問が生じます。結局のところ、後者もかなりうまく構成可能です。
この質問に答えるために、Emboxを実行するための最小のハードウェアプラットフォームを選択できます。そのようなプラットフォームとして、SiliconLabsのEMF32ZG_STK3200を選択しました 。このプラットフォームには、32kBROMと4kBRAMメモリがあります。また、cortex-m0 +プロセッサコア。周辺機器からは、UART、カスタムLED、ボタン、および128x128モノクロディスプレイを利用できます。私たちの目標は、Emboxがこのボードで動作することを確認できるカスタムアプリケーションを起動することです。
周辺機器やボード自体を操作するには、ドライバーやその他のシステムコードが必要です。このコードは、チップメーカー自体が提供する例から取得できます。私たちの場合、メーカーはSimplifyStudioの使用を提案しています。もあります GitHubでリポジトリを開きます)。このコードを使用します。
Emboxには、ドライバーを作成するときにメーカーのBSPを使用するメカニズムがあります。これを行うには、BSPをダウンロードし、Emboxでライブラリとしてビルドする必要があります。この場合、ドライバーでこのライブラリを使用するために必要なさまざまなパスとフラグを指定できます。
BSPをダウンロードするためのMakefileの例:
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)
BSPビルド用のMybuild:
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 }
EFM32ZG_STK3200ボード用のMybuild:
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", ... }
このような非常に簡単な手順の後、製造元のコードを使用できます。ドライバの使用を開始する前に、開発ツールとアーキテクチャパーツを理解する必要があります。Emboxは、通常の開発ツールgcc、gdb、openocdを使用します。openocdを起動するときは、efm32プラットフォームを使用していることを示す必要があります。
sudo openocd -f /usr/share/openocd/scripts/board/efm32.cfg
私たちのスカーフには特別な建築部品はなく、cortex-m0 +の詳細のみです。これはコンパイラによって設定されます。したがって、浮動小数点の操作など、不要なものをすべて無効にすることで、cotrex-m0の一般的なコードを設定できます。
@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
その後、Emboxをコンパイルし、デバッガーを使用して手順を実行して、リンカースクリプトでパラメーターが正しく設定されているかどうかを確認できます。
/* region (origin, length) */
ROM (0x00000000, 32K)
RAM (0x20000000, 4K)
/* section (region[, lma_region]) */
text (ROM)
rodata (ROM)
data (RAM, ROM)
bss (RAM)
Emboxのボードをサポートするために実装された最初のドライバーは、通常UARTです。私たちのボードにはLEUARTがあります。ドライバーはいくつかの機能を実装するだけで十分です。そうすることで、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);
BSP機能を使用できるようにするには、ドライバーの説明である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 }
UARTドライバーを実装すると、出力だけでなく、カスタムコマンドを呼び出すことができるコンソールも使用できるようになります。これを行うには、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")
また、devfsから入手できる本格的なttyではなく、指定したデバイスにアクセスできるスタブを使用する必要があることも示します。デバイスは、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
もう1つの非常に単純なドライバーはGPIOです。これを実装するために、BSPからの呼び出しを使用することもできます。これを行うには、ドライバーの説明で、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 }
実装自体:
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);
}
これは、Emboxの「pin」コマンドを使用するのに十分です。このコマンドを使用すると、GPIOを制御できます。特に、LEDの点滅を確認するために使用できます。
コマンド自体をmods.confに追加します。
include embox.cmd.hardware.pin
そして、起動時に実行させましょう。これを行うには、start_sctpt.inc構成ファイルに次の行の1つを追加します。
<source ">"ピンGPIOC10点滅 "、
または
"pin GPIOC 11 blink",
コマンドは同じですが、LED番号だけが異なります。
表示も始めてみましょう。最初は簡単です。結局のところ、BSP呼び出しを再び使用できます。これを行うには、フレームバッファドライバの説明にそれらを追加するだけです。
package embox.driver.video @BuildDepends(platform.efm32.efm32_bsp) module efm32_lcd { ... source "efm32_lcd.c" @NoRuntime depends platform.efm32.efm32_bsp }
ただし、DISPLAY_Initなど、ディスプレイに関連する呼び出しを行うとすぐに、.bssセクションが2 kBを超えて増加し、RAMサイズは4kBになります。これは非常に重要です。この問題を調査した結果、BSP自体でフレームバッファがディスプレイに割り当てられていることが判明しました。つまり、128x128x1ビットまたは2048バイトです。
この時点で、私はそこでやめたいとさえ思っていました。なぜなら、ユーザーコマンドの呼び出しを4kBRAMのいくつかの単純なコマンドインタープリターに適合させること自体が成果だからです。しかし、私はそれを試してみることにしました。
まず、シェルを削除し、すでに説明したpinコマンドの呼び出しのみを残しました。これを行うために、mods.conf構成ファイルを次のように変更しました。
//@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)
カスタムスタートに別のモジュールを使用していたので、コマンドlaunchを別の構成ファイルに移動しました。start_script.incの代わりにsystem_start.incを使用しました。
次に、シェルでinodeとタイマーを使用する必要がなくなったため、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)
シェルを介さずに直接コマンドを呼び出していたため、スタックサイズを減らすことができました。
@Runlevel(0) include embox.arch.arm.armmlib.exception_entry(irq_stack_size=224) @Runlevel(0) include embox.kernel.stack(stack_size=448,alignment=4)
最後に、LEDが点滅して起動し、内部に表示を初期化するための呼び出しがありました。
ディスプレイに何かを表示したかったのですが、Emboxのロゴが参考になると思いました。良いレベルでは、本格的なフレームバッファドライバを使用し、ファイルから画像を出力する必要があります。これはすべてEmboxにあるためです。しかし、十分なスペースがありませんでした。そして、デモンストレーションのために、フレームバッファドライバの初期化機能に直接ロゴを表示することにしました。さらに、データは直接ビットマップに変換されます。したがって、ROMには正確に2048バイトが必要でした。
コード自体は、以前と同様に、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;
}
それで全部です。短いビデオで結果を見ることができます。
すべてのコードはGitHubで入手でき ます。ボードがある場合は、wikiに記載されている手順を使用して同じものを再現でき ます。
結果は私の期待を上回りました。結局のところ、基本的に2kBのRAMでEmboxを実行することができました。これは、Emboxのオプションを使用すると、OSのオーバーヘッドを最小限に抑えることができることを意味します。さらに、システムにはマルチタスクがあります。協力的であっても。結局のところ、タイマーハンドラーは、割り込みコンテキストで直接呼び出されるのではなく、独自のコンテキストから呼び出されます。これは当然、OSを使用することのプラスです。もちろん、この例は主に人工的なものです。確かに、そのような限られたリソースでは、機能が制限されます。Emboxの利点は、より強力なプラットフォームで犠牲になり始めています。しかし同時に、これはEmboxの限定的なケースと見なすことができます。