mbed ライブラリ内部構造
.
このノートブックは、mbed library internals の翻訳版です。
このドキュメントでは、mbed ライブラリの内部構造を解説します。以下の目的に向いています:
- 新しいマイクロコントローラ向けに mbed ライブラリのポーティングを行う
- 新しいペリフェラルのドライバを追加する
- 新しいツールチェインのサポートを提供する
このドキュメントは、プログラミング言語C の知識があることを前提にして記述されていますが、マイクロコントローラの深い知識は必ずしも必要ではありません。
設計の概要
mbed ライブラリはマイクロコントローラー (MCU) ハードウェア(特にペリフェラルのドライバ)の抽象化を提供し、以下のソフトウェアレイヤーと API に分割します。
mbed ライブラリの新しいマイクロコントローラへの移植では、上図で "MCU dependent" と記載されている2つのソフトウェアレイヤーを提供する必要があります。
ペリフェラルドライバーにおけるこれらのレイヤー各々の役割を示すために、LPC1768用の汎用入出力 (GPIO) のドライバーがボトムアップで、どのように組み立てられているかを示します。各々のレイヤーでは、同じシンプルなアプリケーション例(LEDをトグルする "blinky ")の実装を提供します。
オンライン IDE での mbed ライブラリのソース
mbed ライブラリをエディタで編集する場合は、プロジェクトから mbed ライブラリのバイナリビルドを消去し、ソースをインポートします:
Import librarymbed-dev
mbed library sources. Supersedes mbed-src.
ディレクトリ構造
以下のイメージ内から、オフィシャルに公開されている mbed ライブラリのソースコードのディレクトリ構造を参照することができます。 mbed github repository.
3つのターゲット非依存のディレクトリ:
mbed/api
: 実際の mbed ライブラリ API を定義しているヘッダファイルmbed/common
: mbed 共通ソースmbed/hal
: 全てのターゲットで実装される HAL API
2つのターゲット依存のディレクトリ:
mbed/targets/hal
: HAL の実装mbed/targets/cmsis
: CMSIS-CORE ソース
MCU レジスタ
ペリフェラル
ソフトウェアのバックグランドがあれば、ソフトウェアに対してマイクロコントローラのペリフェラルがどのように振る舞うかを理解したいと思うでしょうし、個別のコアで実行されているソフトウェアスレッドをペリフェラルのようにして考えることもできます。スレッドとの唯一の通信チャネルはメモリの共有エリアです。このメモリエリア内で個々に単一のバイトアドレッシングが可能で、ハードウェアとの通信に使用される領域をレジスタと呼びます。これらのレジスタをリードライトすることで、ペリフェラルと通信することができます。
LPC17xx GPIO
NXP のような半導体ベンダーは、個々のペリフェラルのレジスタを記載した詳細なユーザーズマニュアルを提供しています。
この LPC17xx user manual ページは、GPIO ペリフェラルのレジスタが記載されています:
レジスタを直接叩く Blinky の例
mbed LPC1768 のレジスタを叩いて、LED がどのように点滅するかを見てみましょう:
#include "mbed.h" // Reuse initialization code from the mbed library DigitalOut led1(LED1); // P1_18 int main() { unsigned int mask_pin18 = 1 << 18; volatile unsigned int *port1_set = (unsigned int *)0x2009C038; volatile unsigned int *port1_clr = (unsigned int *)0x2009C03C; while (true) { *port1_set |= mask_pin18; wait(0.5); *port1_clr |= mask_pin18; wait(0.5); } }
CMSIS-CORE
CMSIS-CORE ヘッダは、低レベルレジスタへアクセスするための適切なデータ構造を提供します:
typedef struct { __IO uint32_t FIODIR; uint32_t RESERVED0[3]; __IO uint32_t FIOMASK; __IO uint32_t FIOPIN; __IO uint32_t FIOSET; __O uint32_t FIOCLR; } LPC_GPIO_TypeDef; #define LPC_GPIO_BASE (0x2009C000UL) #define LPC_GPIO0 ((LPC_GPIO_TypeDef *) (LPC_GPIO_BASE + 0x00000)) #define LPC_GPIO1 ((LPC_GPIO_TypeDef *) (LPC_GPIO_BASE + 0x00020)) #define LPC_GPIO2 ((LPC_GPIO_TypeDef *) (LPC_GPIO_BASE + 0x00040)) #define LPC_GPIO3 ((LPC_GPIO_TypeDef *) (LPC_GPIO_BASE + 0x00060)) #define LPC_GPIO4 ((LPC_GPIO_TypeDef *) (LPC_GPIO_BASE + 0x00080))
この LPC_GPIO_TypeDef
構造体で示されるように GPIO ポートに関連する32バイトのレジスタは1対1でマップされています:
FIODIR : 4 bytes RESERVED0: 12 bytes FIOMASK : 4 bytes FIOPIN : 4 bytes FIOSET : 4 bytes FIOCLR : 4 bytes tot : 32 bytes = 0x20 bytes
例えば、GPIO のドキュメントにはポート1の FIOPIN レジスタのアドレスは 0x2009C034
と記載されています。
LPC_GPIO_TypeDef
を使って、この場所の内容にアクセスするには次のようになります:
#include "mbed.h" int main() { printf("LPC_GPIO1->FIOSET: %p\n", &LPC_GPIO1->FIOSET); }
上記プログラムは、以下のように表示を行います:
LPC_GPIO1->FIOSET: 2009c038
CMSIS-CORE を使用した Blinky の例
CMSIS-Core API を使用して、LPC1768 の LED がどのように点滅するかを見てみましょう:
#include "mbed.h" // Reuse initialization code from the mbed library DigitalOut led1(LED1); // P1_18 int main() { unsigned int mask_pin18 = 1 << 18; while (true) { LPC_GPIO1->FIOSET |= mask_pin18; wait(0.5); LPC_GPIO1->FIOCLR |= mask_pin18; wait(0.5); } }
mbed で CMSIS-CORE に追加した処理
mbed ライブラリは、CMSIS-CORE レイヤーに対して特定の追加処理を提供しています:
- サポートされているツールチェイン 毎のスタートアップファイル
- リンカファイルと Memory Map で定義されるサポート関数
- Nested Vectored Interrupt Controller (NVIC) と Vector Table Offset Register (VTOR) から割り込みサービスルーチン(ISR)のアドレスをセット、ゲットする関数
mbed HAL API
ターゲット非依存 API
ターゲット非依存 HAL API は、mbed ターゲット非依存ライブラリの基盤となるものです。GPIO HAL API の例を以下に示します:
typedef struct gpio_s gpio_t; void gpio_init (gpio_t *obj, PinName pin, PinDirection direction); void gpio_mode (gpio_t *obj, PinMode mode); void gpio_dir (gpio_t *obj, PinDirection direction); void gpio_write(gpio_t *obj, int value); int gpio_read (gpio_t *obj);
"Warning”
HAL API は、mbed ライブラリを新しいターゲットにポーティングするために役に立つ内部インターフェスで、変更されるかもしれません。もし「将来的な動作保証」が必要であれば、アプリケーション側からはこの API を使わず、代わりに mbed API を使用して下さい。
ターゲット依存部の実装
mbed HAL API の実装は、全てのターゲット依存部分に必要なコードを隠蔽します。
このような API を実装するには幾つかの方法があり、異なったトレードオフがあります。LPC プロセッサファミリ (LPC2368, LPC1768, LPC11U24) 向けの我々の実装では、全ての GPIO “メソッド” を小さく、速く、そしてターゲット非依存となるように、GPIO オブジェクトの初期化の中でこれらのプロセッサー間の違いをすべて保持することに決めました。加えて、実行速度を最適化するためにコンパイラによって GPIO 関数を完全にインライン展開しました。
このドキュメントでは、コードを明瞭に説明するために、ターゲット向けの条件付きコードとプリプロセッサ指示を削除しています。また、複数のソース(”.c” と “.h”)の関数定義はまとめて表示しています:
struct gpio_s { PinName pin; uint32_t mask; __IO uint32_t *reg_dir; __IO uint32_t *reg_set; __IO uint32_t *reg_clr; __I uint32_t *reg_in; }; void gpio_init(gpio_t *obj, PinName pin, PinDirection direction) { if(pin == NC) return; obj->pin = pin; obj->mask = gpio_set(pin); LPC_GPIO_TypeDef *port_reg = (LPC_GPIO_TypeDef *) ((int)pin & ~0x1F); obj->reg_set = &port_reg->FIOSET; obj->reg_clr = &port_reg->FIOCLR; obj->reg_in = &port_reg->FIOPIN; obj->reg_dir = &port_reg->FIODIR; gpio_dir(obj, direction); switch (direction) { case PIN_OUTPUT: pin_mode(pin, PullNone); break; case PIN_INPUT : pin_mode(pin, PullDown); break; } } void gpio_mode(gpio_t *obj, PinMode mode) { pin_mode(obj->pin, mode); } void gpio_dir(gpio_t *obj, PinDirection direction) { switch (direction) { case PIN_INPUT : *obj->reg_dir &= ~obj->mask; break; case PIN_OUTPUT: *obj->reg_dir |= obj->mask; break; } } void gpio_write(gpio_t *obj, int value) { if (value) *obj->reg_set = obj->mask; else *obj->reg_clr = obj->mask; } int gpio_read(gpio_t *obj) { return ((*obj->reg_in & obj->mask) ? 1 : 0); }
mbed の内部的な慣例
実際的な慣例として、各ターゲットの PinName
enum 定義は、port
および pin number
の両方の情報を含んでいます。この情報がどのように格納されるかは、ターゲット依存です。
例えば、LPC1768 の PinName
エントリは、port address
に加算した下位5ビットの pin number
です。LPC1768 の PinName
からポートアドレスを抽出するには、単純にマスクを作成して下位の5ビットを無視しています:
LPC_GPIO_TypeDef *port_reg = (LPC_GPIO_TypeDef *) ((int)pin & ~0x1F);
mbed API
mbed API は、エンドユーザにとって取り扱いやすくオブジェクト指向な API です。これは、mbed プラットフォーム上で開発され、大多数のプログラムで使用される API です。
また、基礎的な型と代入処理用に直感的なキャストを提供する基本的な演算子も定義しました。
class DigitalInOut { public: DigitalInOut(PinName pin) { gpio_init(&gpio, pin, PIN_INPUT); } void write(int value) { gpio_write(&gpio, value); } int read() { return gpio_read(&gpio); } void output() { gpio_dir(&gpio, PIN_OUTPUT); } void input() { gpio_dir(&gpio, PIN_INPUT); } void mode(PinMode pull) { gpio_mode(&gpio, pull); } DigitalInOut& operator= (int value) { write(value); return *this; } DigitalInOut& operator= (DigitalInOut& rhs) { write(rhs.read()); return *this; } operator int() { return read(); } protected: gpio_t gpio; };
mbed API を使用した Blinky の例
これが最後の例で、mbed ライブラリによって提供されるマイクロコントローラ非依存の GPIO の抽象化です:
#include "mbed.h" DigitalOut led1(LED1); int main() { while (true) { led1 = 1; wait(0.5); led1 = 0; wait(0.5); } }
C ライブラリのリターゲット
C 標準ライブラリ stdio モジュールは、入出力用途に多数の関数を提供しています:
C 標準ライブラリ実装は、いくつかのツールチェインによって提供され、入出力がどのように指定されリダイレクトされるかといったカスタマイズが可能です。このカスタマイズは、”C ライブラリ・リターゲット” と呼ばれる事もあります。
mbed ライブラリは、この C ライブラリリターゲットを mbed/src/common/stdio.cpp
ファイル中で定義しています。
これは、UART への(stdout、stdin 及び stderr)リターゲットの抜粋です:
#if DEVICE_SERIAL extern int stdio_uart_inited; extern serial_t stdio_uart; #endif static void init_serial() { #if DEVICE_SERIAL if (stdio_uart_inited) return; serial_init(&stdio_uart, STDIO_UART_TX, STDIO_UART_RX); serial_format(&stdio_uart, 8, ParityNone, 1); serial_baud(&stdio_uart, 9600); #endif } extern "C" FILEHANDLE PREFIX(_open)(const char* name, int openmode) { /* Use the posix convention that stdin,out,err are filehandles 0,1,2. */ if (std::strcmp(name, __stdin_name) == 0) { init_serial(); return 0; } else if (std::strcmp(name, __stdout_name) == 0) { init_serial(); return 1; } else if (std::strcmp(name,__stderr_name) == 0) { init_serial(); return 2; } [...] #if defined(__ICCARM__) extern "C" size_t __write (int fh, const unsigned char *buffer, size_t length) { #else extern "C" int PREFIX(_write)(FILEHANDLE fh, const unsigned char *buffer, unsigned int length, int mode) { #endif int n; // n is the number of bytes written if (fh < 3) { #if DEVICE_SERIAL if (!stdio_uart_inited) init_serial(); for (unsigned int i = 0; i < length; i++) { serial_putc(&stdio_uart, buffer[i]); } #endif n = length; } else { [...] #if defined(__ICCARM__) extern "C" size_t __read (int fh, unsigned char *buffer, size_t length) { #else extern "C" int PREFIX(_read)(FILEHANDLE fh, unsigned char *buffer, unsigned int length, int mode) { #endif int n; // n is the number of bytes read if (fh < 3) { // only read a character at a time from stdin #if DEVICE_SERIAL *buffer = serial_getc(&stdio_uart); #endif n = 1; } else { [...]
Please log in to post comments.