libmaple下位レイアライブラリ
はじめに
LeafLabsのlibmaple(GitHubのソースコード)は、ARM Cortex M3マイクロコントローラのSTM32シリーズ用に開発したライブラリです。 その高水準インターフェースは、ArduinoおよびWiring開発ボード用に書かれたAVRライブラリとほとんど互換性があります。
libmapleは、純粋なCで書かれた下位レベルのレイヤー(ソースリポジトリのlibmaple /ディレクトリにあるlibmapleを適切なものと呼ぶ)と、その上に書かれたWiringスタイルのC ++ API(Wirish ワリッシュ/)からなります。
libmapleはMaple IDEにバンドルされています。 しかし、私たちはそれを個別に開発し、IDEの「スケッチ」プログラミングモデルで暴走するかもしれない上級ユーザーには、スタンドアロンでリリースします。
デザイン目標
libmapleプロジェクトの中心的な目標は、STM32ライン上のさまざまな周辺機器を扱うための快適で安定したインターフェースを提供することです。
基礎から始めましょう。 STM32の低レベルの詳細に関心があるなら、ST RM0008を通して多くの質の高い時間を費やすつもりです。 そのドキュメントは、ツールボックス内の最も重要なツールです。 これは、STM32ラインの機能と登録インタフェースの権威ある文書です。 おそらくあなたはそれを詳細には読んでいないかもしれませんが、多分あなたは何が起こっているのかについて何らかの理解を得るために、いくつかのセクションを少なくとも摘んでいます。 もしあなたがそれを済ませていれば、それを覚えていれば、すべての周辺機器に特有の無限のレジスターコレクションの中で、すべてを制御することができます。 USARTにはデータレジスタがあります。 (いくつかの)タイマにはキャプチャ/コンペアレジスタがあり、GPIOには出力データレジスタなどがあります。 ほとんどの場合、Wirishはあなたからこの真実を隠すことができるすべてを行います。 ロボットが飛ぶようにしたり、LEDを点滅させたり、FMシンセサイザを合成したり http://xhosxe.free.fr/IxoxFMSynth.mp3したいときには、おそらくレジスターを悩ますことはほとんどありません。 それはいいです! 実際、Wirishが十分なものであることを明示的に目標としているため、ほとんどの人がlibmapleを知る必要はありません。 結局のところ、ボードをできるだけ簡単にプログラミングしたいと思っています。 しかし、サポートされていない周辺機器用のライブラリを追加したい場合や、予想しなかったことをしたい場合や、あなたのクリティカルセクションからもう少しスピードを絞りたい場合があります プログラム。 または、あなたはちょうど興味がありますか?
上の段落で何かが記述されている場合は、RM0008に関する知識をソフトウェアに変換する方法が必要であることがわかります。 私たちは、(あなたが私たちのような人なら)おそらくその翻訳をすることができる時間を最小限に抑えたいと考えています。 理想的には、デザインを完成させたらすぐに、コードを読み書きしてすぐに始めることができます。
上記の目標を達成するために選択した中心的な抽象化は、レジスタマップとデバイスです。 レジスタマップは、ペリフェラルのレジスタに対応するIOマップされたメモリ領域のレイアウトをカプセル化する単なる構造体です。 デバイスは、ペリフェラルのレジスタマップと、それを操作するために必要なその他の必要な情報をカプセル化します。 ペリフェラルサポートルーチンは、通常、レジスタマップではなくデバイス上で動作します。
デバイス
最高レベルでは、デバイスを取り扱うことになります。ここで、「デバイス」とは、遭遇する可能性のある特定のハードウェアの総称です。 したがって、たとえば、アナログ/デジタルコンバータはデバイスです。 USARTもそうです。 GPIOポートもそうです。 このセクションでは、仮説的な「xxx」デバイスについて検討します。
最初に知る必要があるのは、xxxデバイスを扱うためのヘッダーファイルは、必然的にxxx.hと呼ばれているということです。 したがって、ADCとのインターフェイスをとる場合は、#include "adc.h"だけです。
code:adc_dev.h
/** ADC device type. */
typedef struct adc_dev {
adc_reg_map *regs; /**< Register map */
rcc_clk_id clk_id; /**< RCC clock information */
} adc_dev;
ADCは特に複雑ではありません。 ADCデバイスのために追跡しているのは、そのレジスタマップ(すべてのレジスタのビットを追跡します;詳細は以下を参照してください)へのポインタと、RCCに指示する識別情報です(リセットとクロック制御 )インターフェイスを使用してADCをオンにし、レジスタをデフォルト値にリセットする方法について説明します。
STM32ラインのタイマーはADCよりも複雑なので、timer_devはもう少し情報を追跡しなければなりません:
code:timer_dev.h
/** Timer device type */
typedef struct timer_dev {
timer_reg_map regs; /**< Register map */
rcc_clk_id clk_id; /**< RCC clock information */
timer_type type; /**< Timer's type */
voidFuncPtr handlers[]; /**< User IRQ handlers */
} timer_dev;
しかし、ご覧のように、ADCとタイマの両方のデバイスには1つの方式で名前が付けられ、同様の情報が格納されます。
xxx.hはまた、対処が必要な実際のデバイス(XXX1、XXX2など)へのポインタを宣言します(1つしかない場合はXXX)(*1)。 たとえば、Mapleのマイクロコントローラ(STM32F103RBT6)には、2つのADCがあります。 したがって、adc.hには、ADCデバイス1と2を扱う宣言があります。
code:adc.h
extern const adc_dev *ADC1;
extern const adc_dev *ADC2;
一般に、各デバイスは使用する前に初期化する必要があります。 libmapleは各周辺機器xxxに対してこの初期化ルーチンを提供します。 その名前はxxx_init()です。 これらの初期化ルーチンは、デバイスのクロックをオンにし、レジスタ値をデフォルト設定に復元します。 いくつかの例があります:
code:exsample1.c
/* From dma.h */
void dma_init(dma_dev *dev);
/* From gpio.h */
void gpio_init(gpio_dev *dev);
void gpio_init_all(void);
特定の種類の使用可能なすべての周辺機器に対して追加の初期化ルーチンが存在することがあります。
多くの周辺機器は、使用する前に追加設定が必要です。 これらの関数は、通常、xxx_enable()の行に沿って何かと呼ばれ、多くの場合、周辺機器の特定の設定を指定する追加の引数をとります。 いくつかの例:
code:exsample2.c
/* From usart.h */
void usart_enable(usart_dev *dev);
/* From i2c.h */
void i2c_master_enable(i2c_dev *dev, uint32 flags);
ペリフェラルを初期化して有効にした後は、そのペリフェラルを使用し始めましょう。 xxx.hファイルには、xxxデバイスを扱うための他の便利な関数が含まれています。 たとえば、adc.hのいくつかを以下に示します。
code:adc.h_01.h
void adc_set_sample_rate(const adc_dev *dev, adc_smp_rate smp_rate);
uint32 adc_read(const adc_dev *dev, uint8 channel);
libmapleのユーザは、抽象化を破らずに個々のレジスタを検討するのではなく、可能な限りデバイスを使用して周辺機器とやりとりすることを目指しています。 しかし、常に低レベルのアクセスが必要です。 これを可能にするために、libmapleは、レジスタとそのビットを処理するための一貫した名前と抽象のセットとしてレジスタマップを提供します。
レジスタマップ
レジスタマップは、ペリフェラルのレジスタに名前を付けてアクセスを提供する単なるC構造体です。 これらのレジスタは、通常、メモリの連続領域にマッピングされます(ただし、周辺領域のレジスタ間に使用できない領域や予約領域が存在することもあります)。 dac.hのレジスタマップの例を示します(__ioは、レジスタ値を参照するときのlibmapleのvolatileの意味です):
code:dac.h_dac_reg_map.h
/** DAC register map. */
typedef struct dac_reg_map {
__io uint32 CR; /**< Control register */
__io uint32 SWTRIGR; /**< Software trigger register */
__io uint32 DHR12R1; /**< Channel 1 12-bit right-aligned data
holding register */
__io uint32 DHR12L1; /**< Channel 1 12-bit left-aligned data
holding register */
__io uint32 DHR8R1; /**< Channel 1 8-bit left-aligned data
holding register */
__io uint32 DHR12R2; /**< Channel 2 12-bit right-aligned data
holding register */
__io uint32 DHR12L2; /**< Channel 2 12-bit left-aligned data
holding register */
__io uint32 DHR8R2; /**< Channel 2 8-bit left-aligned data
holding register */
__io uint32 DHR12RD; /**< Dual DAC 12-bit right-aligned data
holding register */
__io uint32 DHR12LD; /**< Dual DAC 12-bit left-aligned data
holding register */
__io uint32 DHR8RD; /**< Dual DAC 8-bit right-aligned data holding
register */
__io uint32 DOR1; /**< Channel 1 data output register */
__io uint32 DOR2; /**< Channel 2 data output register */
} dac_reg_map;
ここで注目すべき2つのことがあります。 まず、RM0008がレジスタDAC_FOOに名前を付ける場合、dac_reg_mapにはFOOという名前のフィールドがあります。 したがって、チャネル1の12ビットの右揃えのデータレジスタ(RM0008:DAC_DHR12R1)は、dac_reg_mapのDHR12R1フィールドです。 第2に、RM0008がレジスタを「フーバーレジスタ」として記述する場合、対応するフィールドのドキュメントは同じ記述を有する。 この一貫性により、特定のレジスタを簡単に検索することができます。ソースファイルで使用されているものがあれば、その名前に基づいて何が起こっているかを確かめることができます。
だから、あなたがxxx.hをインクルードしていて、特定のレジスタを混乱させたいとしましょう。 あなたが望むxxx_reg_map変数の名前は何ですか? それは複数のxxxがあるかどうかによって決まります。 xxxが1つしかない場合、libmapleは次のような#defineが存在することを保証します:
code:xxx.h
#define XXX_BASE ((struct xxx_reg_map*)0xDEADBEEF) つまり、(唯一の)xxx_reg_mapへのポインタがあり、XXX_BASEと呼ばれることが保証されています。 (0xDEADBEEFはレジスタマップのベースアドレス、またはレジスタマップが始まるメモリ内の固定位置です)。dac.hの具体的な例を次に示します。
code:dac.h_reg_map.h
#define DAC_BASE ((struct dac_reg_map*)0x40007400) どのようにこれらを使用できますか? これはおそらく例で最もよく説明されます。
チャネル1の12ビット左アライメントデータ保持レジスタ(RM0008:DAC_DHR12L1)に2048を書き込むには、次のように記述します。
code:c
DAC_BASE->DHR12L1 = 2048;
DAC制御レジスタを読み取るには、次のように記述します。
code:c
uint32 cr = DAC_BASE->CR;
マイクロコントローラは、レジスタのIOマップされたメモリ領域からの読み書きを、対応するハードウェアレジスタへの読み書きに変換します。
これは、単一のxxxペリフェラルがある場合をカバーしています。 複数ある場合(たとえば、nがある場合)、xxx.hは次のようになります。
code:c
#define XXX1_BASE ((struct xxx_reg_map*)0xDEADBEEF) #define XXX2_BASE ((struct xxx_reg_map*)0xF00DF00D) ...
#define XXXn_BASE ((struct xxx_reg_map*)0x13AF1AB5) adc.hの例をいくつか紹介します。
code:c
#define ADC1_BASE ((struct adc_reg_map*)0x40012400) #define ADC2_BASE ((struct adc_reg_map*)0x40012800) ADC1の通常のデータレジスタ(ADC変換の結果が格納されている)から読み出すには、次のように記述します。
code:c
uint32 converted_result = ADC1_BASE->DR;
レジスタビットの定義
xxx.hには、レジスタビット定義と呼ばれる興味深いxxxレジスタのビットを処理するためのさまざまな#defineもあります。 これらはXXX_REG_FIELDスキームに従って命名されます。ここで、「REG」はレジスタを指し、「FIELD」は特別なREGのビットを指します。
繰り返しますが、これはおそらく例で最もよく説明されています。 各ダイレクトメモリアクセス(DMA)コントローラのレジスタマップには、いくつかのチャネル構成レジスタ(RM0008:DMA_CCRx)があります。 これらのチャネル構成レジスタのそれぞれにおいて、ビット14はMEM2MEMビットと呼ばれ、ビット13および12は優先レベル(PL)ビットである。 これらのフィールドのレジスタビット定義は次のとおりです。
code:c
/* From dma.h */
#define DMA_CCR_MEM2MEM BIT(DMA_CCR_MEM2MEM_BIT) #define DMA_CCR_PL_MEDIUM (0x1 << 12) #define DMA_CCR_PL_HIGH (0x2 << 12) #define DMA_CCR_PL_VERY_HIGH (0x3 << 12) したがって、DMAコントローラ1のチャネルコンフィギュレーションレジスタ2(RM0008:DMA_CCR2)にMEM2MEMビットがセットされているかどうかを確認するには、
code:c
if (DMA1_BASE->CCR2 & DMA_CCR_MEM2MEM) {
/* MEM2MEM is set */
}
特定のレジスタ値は複数のビットを占有します。 例えば、DMAチャネルの優先レベル(PL)は、対応するチャネル構成レジスタのビット13およびビット12によって決定される。 上に示したように、libmapleは個々のPLビットをマスキングしてそれらの意味を決定するためのいくつかのレジスタビット定義を提供します。 たとえば、DMA転送の優先度を確認するには、次のように記述します。
code:c
switch (DMA1_BASE->CCR2 & DMA_CCR_PL) {
case DMA_CCR_PL_LOW:
/* handle low priority case */
case DMA_CCR_PL_MEDIUM:
/* handle medium priority case */
case DMA_CCR_PL_HIGH:
/* handle high priority case */
case DMA_CCR_PL_VERY_HIGH:
/* handle very high priority case */
}
もちろん、その前に、同じタスクを実行するためのデバイスレベルの機能がないことを確認する必要があります。
次は何をすべきか?
このページを読んだら、libmaple APIのリストに進むことができます。 そこから、ドキュメントを読んで、libmapleのGitHubページにあるそれらのファイルの現在のソースコードへのリンクをたどることができます。
脚注
(*1):RM0008との一貫性のために、GPIOポートは数字ではなく文字(GPIO1とGPIO2の代わりにGPIOAとGPIOBなど)が付いています。
このドキュメントはleafLabs, LLC.が執筆し、たま吉が翻訳・一部加筆修正したものです。