STM32F10x Standard Peripherals Firmware Libraryを使う

出典: Wikimura

どんなマイコンでも、C言語が動くまでの部分や割り込みの部分はどうしてもハードウェアの知識が必要となります。 これに対しARMのCortex系マイコンでは、ハードウェア部分へのアクセスに標準を設けることで、開発を効率化し、再利用性を高めているそうです。CMSIS(Cortex Microcontoroller Software Interface Standards)では、マイコンのベンダが開発者に提供すべき機能や関数の命名規則などが定められています。

STM32マイコンでは、STMicroelectronicsが提供するCMSIS準拠のライブラリ、Standard Peripherals Firmware Libraryを使うことができます。 ハードウェアに近い部分である、割り込みやスタートアップルーチン、ペリフェラルのレジスタマップや、ドライバ関数などがライブラリとして提供されています。

ここでは、Standard Peripherals Firmware Libraryの解説を基に、このライブラリを使ってどのように開発をするのかをまとめます。


目次

入手

STMicroelectronics社のSTM32マイコン関連ファイルがダウンロードできる[1]のFirmware一覧から、 STM32F10x_StdPeriph_Libをダウンロードします。

ライブラリを構成するファイルはとても多いですが、 ライブラリ概要に書いてあるように、少しのマクロ定義とインクルードでハードウェア機能を使えるようになります。


ライブラリの提供する枠組み

このライブラリは下図のように、ユーザの書くソースからstm32f10x.hをインクルードするだけで済むよう工夫されています。 これをインクルードするだけで、必要な関数がインクルードされるようになるそうです。 図内の緑色で表わされた部分ががユーザの書く(手を加える)ソースです。これらはテンプレートが用意されています。 (stm32f10x.hについては実質的に手を加える必要はありません)

stm32f10x.hにはレジスタマップが定義されているので、これだけでレジスタを使ったアクセスが可能になります。 また、ペリフェラル操作に関数を使いたい場合は、stm32f10x_conf.hに使用したいペリフェラルのヘッダインクルードを記述します。このファイルは(USE_STDPERIPH_DRIVERマクロ定義で)自動的にstm32f10x.hからインクルードされます。 これがstm32f10x.hだけをインクルードすれば良い理由です。

ライブラリの仕組み

ライブラリ概要によれば、プリプロセッサでいくつかのマクロ定義を行うことで、機能設定をする必要があるとのことです。

マクロ定義は、3つあります。これらの意味については ライブラリ概要を参照してください。プロジェクトを作成したら、プリプロセッサオプションで(1)と(2)を指定しましょう。

  1. 対象プロセッサ選択: プリプロセッサオプションで指定
  2. ドライバ関数の使用選択: プリプロセッサオプションで指定
  3. 実行時チェック使用選択: 後述のstm32f10x_conf.hで指定

追記

上の図で「Provided by ARM」とある部分は、テンプレートのようです。

Standard Peripherals Firmware Libraryでは4つのファイルがテンプレートとして提供されています。 必要なファイルのインクルード、マクロ定義、割込ハンドラのテンプレートなどが用意されています。

main.c
stm32f10x.hのインクルード、assert_failed関数定義のテンプレートが用意されている。
stm32f10x_conf.h
ペリフェラル用ヘッダのインクルードリスト、assert_paramマクロの切替が用意されている。USE_STDPERIPH_DRIVERマクロが定義されると、stm32f10x.hからインクルードされるようになる。
stm32f10x_it.h.c
割り込みハンドラのヘッダとソースのテンプレート。ここに割り込みハンドラをまとめるらしい。


マイコンが正しく起動するためには、スタートアップルーチンやベクタテーブルが必要となります。 これもStandard Peripherals Firmware Libraryが提供してくれます。 「 CMSISファイルの説明」に書いたように、スタートアップファイルはGCC用のものがあり、High/Medium/Low-density用に3種類あります。

割り込みベクタテーブル
割り込みハンドラが登録されている。プログラマはここに登録された名前の割り込みハンドラ関数を定義するだけでよい。定義がない場合、Default_Handlerへのアドレスがテーブルに書き込まれる。これはGCCのweakアトリビュートで自動で行われるようになっている。セクション.isr_vectorに配置されるよう指定されているので、リンカスクリプトで正しい場所に配置されるよう設定すること。
リセットハンドラ
Reset_Handlerが定義されており、データ初期化とmain関数呼び出しを行う。
データ初期化
__Init_Data関数が、Cプログラムが動くために必要な、スタックポインタ設定、静的変数の初期化を行う。
デフォルトハンドラ
定義されていない割り込みハンドラに対し、デフォルト値としてベクタテーブルに登録される割り込みハンドラ。

ただし、スタートアップルーチンはいくつかのシンボルが定義されていなくてはならないと書いてあります。 以下のシンボルはリンカスクリプトで定義されなくてはなりません。

/* start address for the initialization values of the .data section. defined in linker script */
extern unsigned long _sidata;

/* start address for the .data section. defined in linker script */    
extern unsigned long _sdata;

/* end address for the .data section. defined in linker script */    
extern unsigned long _edata;
    
/* start address for the .bss section. defined in linker script */
extern unsigned long _sbss;

/* end address for the .bss section. defined in linker script */      
extern unsigned long _ebss;  
    
/* init value for the stack pointer. defined in linker script */
extern void _estack;  

実行時チェックの謎

このように予め用意されているのはありがたいのですが、良く分からない箇所が1つあります。

実行時チェックは、ペリフェラル用関数の引数をチェックする仕組みです。これは、引数をとる関数の最初の1行目でassert_paramを実行することで実現されます。 これはstm32f10x_conf.hで定義されます。

#ifdef  USE_FULL_ASSERT
    #define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
    void assert_failed(uint8_t* file, uint32_t line);
#else
    #define assert_param(expr) ((void)0)
#endif

stm32f10x_conf.hUSE_FULL_ASSERTを定義するかどうかで、ライブラリ関数内のassert_paramが意味のある関数に置換されるか、((void)0)に置換されるかが決まると書かれています。これはユーザがプログラムをビルドする段階で決まります。 つまり、ライブラリはこれより先にビルドできないということになります。

なぜなら、ペリフェラル関数は以下のように定義されているからです。

stm32f10x_exti.c----------------------------------------------------------------
void EXTI_Init(EXTI_InitTypeDef* EXTI_InitStruct)
{
  /* Check the parameters */
  assert_param(IS_EXTI_MODE(EXTI_InitStruct->EXTI_Mode));
  assert_param(IS_EXTI_TRIGGER(EXTI_InitStruct->EXTI_Trigger));
  assert_param(IS_EXTI_LINE(EXTI_InitStruct->EXTI_Line));  
  assert_param(IS_FUNCTIONAL_STATE(EXTI_InitStruct->EXTI_LineCmd));
...
}

stm32f10x_exti.h----------------------------------------------------------------
#define IS_EXTI_MODE(MODE) (((MODE) == EXTI_Mode_Interrupt) || ((MODE) == EXTI_Mode_Event))
#define IS_EXTI_TRIGGER(TRIGGER) (((TRIGGER) == EXTI_Trigger_Rising) || \
                                  ((TRIGGER) == EXTI_Trigger_Falling) || \
                                  ((TRIGGER) == EXTI_Trigger_Rising_Falling))

#define IS_EXTI_LINE(LINE) ((((LINE) & (uint32_t)0xFFF80000) == 0x00) && ((LINE) != (uint16_t)0x00))

これを先にビルドしてしまうと、assert_paramはプリプロセッサ置換できなくなります。 シンボル解決を待つ状態になってしまいます。 こうなると、stm32f10x.hをインクルードするだけでは済まなくなります。 これに対する解決法は2種類考えられます。

1つ目は、プロジェクトごとに必要なペリフェラルのソースファイルをコピーする方法です。 もはやインクルードパスの意味もないので、ソース・ヘッダ諸共コピーします。 毎回コピーすることになるのは面倒ですが、スタンドアロンで配布できるようになります。

2つ目は、予めassert_paramの有無で2種類のライブラリをビルドする方法です。 そして、実行時チェックの有無でリンクするライブラリを切り替えます。 こうすれば、プロジェクトごとの設定はライブラリパスとインクルードパスだけで済み、ファイルのコピーは不要になります。


プロジェクト作成方法(共通)

ここでは、STM32F10x Standard Peripherals Firmware Libraryを使ったプログラムを作るのに必要な作業を示します。 ペリフェラル関数を使う際に生じる実行時チェックの問題を回避するために、2種類の方法を考えました。 まずは、どちらの方法でも共通の作業を示します。

以降、STM32F10x Standard Peripherals Firmware Libraryを展開したディレクトリをlib_dirと表します。

テンプレートをコピーする

以下の4ファイルをプロジェクトにコピーします。

  • lib_dir/Projects/Template
    • main.c
    • stm32f10x_conf.h
    • stm32f10x_it.h
    • stm32f10x_it.c

マクロ定義

前述した3つのマクロを定義します。

  • プロセッサ選択
  • ペリフェラル関数使用選択
  • 実行時チェック使用選択

プリプロセッサオプションで追加

stm32f10x.hをインクルードする全てのソースから見えてほしいマクロは、プリプロセッサオプションで追加します。 GCCの場合、プリプロセッサを制御するオプションに以下のオプションがあります。

-include file
fileが最初に最初にインクルードされる。
-imacros file
fileが最初に最初にインクルードされ、マクロのみ追加される。

このオプションを使って、プロセッサ選択ドライバ関数使用選択の2つのマクロを定義します。 ここではマクロ定義を行うファイルをmacros.hとします。まずはmacros.hを作ります。

macros.h
------------------------------------------------------------
// プロセッサ選択
// High-densityの場合
// #define STM32F10X_HD

// Medium-densityの場合
#define STM32F10X_MD   

// Low-densityの場合    
// #define STM32F10X_LD

// ドライバ関数使用選択
// を使用しない場合はコメントアウトする
#define USE_STDPERIPH_DRIVER

このファイルをプロジェクトに追加したら、下図のようにプロジェクトの設定を行ってください。 こうすることで、Eclipseの自動makeがソースを個別にコンパイルする場合でも、絶対にこれらのマクロが定義されます。

マクロ追加

stm32f10x_conf.hに追加

実行時チェック使用選択を行います。プロジェクトにコピーしたテンプレートのstm32f10x_conf.hを開きます。 実行時チェックを使用する場合は、以下のUSE_FULL_ASSERT定義部分のコメントを解除してください。 実行時チェックを使用しないのであれば、変更しません。

/* Exported constants --------------------------------------------------------*/
/* Uncomment the line below to expanse the "assert_param" macro in the 
   Standard Peripheral Library drivers code */
/* #define USE_FULL_ASSERT    1 */

スタートアップファイルをコピー

スタートアップファイルは以下の9種類があります。

armはアセンブリ言語で書かれ、gccはGCCの拡張機能を使用したC言語で書かれ、iarはIAR社のアセンブリ言語でかかれています。開発環境に合わせて選択します。

ファイル名のサフィックスhd/md/ldは、High/Medium/Low-densityに対応しています。これは使用マイコンによって選択します。

  • lib_dir/Libraries/CMSIS/Core/CM3/startup
    • arm
      • startup_stm32f10x_hd.s
      • startup_stm32f10x_md.s
      • startup_stm32f10x_ld.s
    • gcc
      • startup_stm32f10x_hd.c
      • startup_stm32f10x_md.c
      • startup_stm32f10x_ld.c
    • iar
      • startup_stm32f10x_hd.s
      • startup_stm32f10x_md.s
      • startup_stm32f10x_ld.s


リンカスクリプトを作成

スタートアップファイルは、いくつかのシンボルがリンカスクリプトで定義されることを要求しています。 そのために、マイコンに合わせたリンカスクリプトを作ることが必要となります。 これについてはSTM32用リンカスクリプトを書くを参照してください。


プロジェクト作成方法(必要なファイルをコピーするやり方)

スタートアップファイルとリンカスクリプトでCプログラムが動く下地ができました。 マクロ定義でstm32f10x.hがペリフェラル用ヘッダをインクルードしてくれるようにもなりました。 あとはコアやペリフェラル関数の本体があれば良いのですが...実行時チェックのために、予めコンパイルできませんでした。

ここでは、必要なソースファイルを全てプロジェクトへコピーすることで解決します。 どうせならヘッダ・ソース全てをコピーしてしまいます。 関連ファイルは以下の通りです。

  • lib_dir/Libraries/CMSIS/Core/CM3
    • stm32f10x.h
    • system_stm32f10x.h
    • system_stm32f10x.c
    • core_cm3.h
    • core_cm3.c
  • lib_dir/Libraries/STM32F10x_StdPeriph_Driver ←以下必要なペリフェラルのものだけコピー
    • inc
      • xxx.h ...
    • src
      • xxx.c ...

プロジェクト作成方法(2種類のライブラリを作るやり方)

STM32F10x Standard Peripherals Firmware LibraryのLibrariesディレクトリに、ライブラリのソースとヘッダが入っています。 ただし、CMSISの方はテンプレートとして提供されているファイルもあることから注意が必要です。 ライブラリをビルドするときは、テンプレートのソースは含まないようにします。 テンプレートかどうかはCMSIS: Cortex Microcontroller Software Interface Standard CMSISのドキュメントに書かれています。

手順

ファイルの用意

まず必要なファイルを用意します。

  • Librariesx/STM32F10x_StdPeriph_Driver
    • inc: 全てのヘッダ
    • src: 全てのソース
  • Libraries/CMSIS/Core/CM3
    • stm32f10x.h: 全てのヘッダがインクルード
    • system_stm32f10x.h: stm32f10x.hが勝手にインクルード
      • このファイルは元々何も定義されていないのでデフォルトのまま
      • ソースの方は不要...初期化などをカスタマイズするのに使うため、ライブラリの時点で決定できない。
    • core_cm3.h
    • core_cm3.c


マクロ定義

次に、マクロ定義ファイルを作成します。High/Medium/Low-densityの選択や、実行時チェックの有無の選択はマクロで行います。これらは各ソースのコンパイル時に「最初」に与えられていなりません。というのも、どのソースからも、最初にインクルードされるstm32f10x.hよりも早くマクロが定義されなくては、ifdefが正しく動作しないからです。

ソースの編集を行わずにこれを行うためには、GCCの-imacrosオプションを使います。-imacrosオプションは、コンパイル時に最初に指定されたファイルのマクロ定義を読みだします。そのため、全てのソースでプロセッサ定義やassert_paramを真っ先に行うことができます。

ライブラリを作る段階では、プロセッサ選択とassert_paramをどうするかだけ決めればOKです。 以下はMedium-density + 実行時チェックなしの設定です。

#ifndef MACROS_H_
#define MACROS_H_
// プロセッサ選択
// High-densityの場合
// #define STM32F10X_HD

// Medium-densityの場合
#define STM32F10X_MD

// Low-densityの場合
// #define STM32F10X_LD

// assert_paramをどうするか→Project/Template/stm32f10x_conf.hを参考にした
// 実行時チェックをしない場合
#define assert_param(expr) ((void)0)

// 実行時チェックをする場合
//#define assert_param(expr) ((expr) ? (void)0 : assert_failed((uint8_t *)__FILE__, __LINE__))
//void assert_failed(uint8_t* file, uint32_t line);
#endif /* MACROS_H_ */

なお、USE_STDPERIPH_DRIVERマクロはここでは不要です。理由は、これを定義するとstm32f10x.hstm32f10x_conf.hを勝手にインクルードしようとしてしまうためです。stm32f10x_conf.hはインクルードするペリフェラルヘッダの洗濯や、assert_paramを何に置換するかを設定するだけのファイルです。 ペリフェラルのソースでは必要なヘッダをインクルードしているので、わざわざ回り道する必要はありません。

Eclipseプロジェクト

新規Cプロジェクトで「Static Library」のEmpty projectを選択してください。 コンパイラやアセンブラ、アーカイバの設定はいつもどおりARM用に設定します。 コンパイラ設定の際は、マクロ読み込みのための-imacrosを忘れずに行ってください。

これらの設定ができれば、ビルドが通るはずです。 上記のファイルをまとめたのが以下のファイルとなります。

ライブラリの利用について

TBD.

ビルド済みライブラリ

Code SourceryのSoucery G++ 2009q1ビルド(GCC4.3.3)でビルドしたライブラリを置いておきます。 動作は保証できませんが...とりあえず手元では動きました。

これらはSVNリポジトリにも置いてあります。

コメント

  • ライブラリをビルドして使う方法もありそうですが、とりあえずコピーして使っても支障がなさそうです。ソースレベルデバッグもしやすいですので → ビルドしてしまっても、ソースを参照できればデバッグ時にソースレベルでバッグも可能でした。
  • ライブラリをビルドする方法については今後まとめます→まとめ中
  • Sourcery G++ 2009q1ビルドでビルドしたライブラリをSVNリポジトリに置きました。

参考文献

TBD.

個人用ツール