ソフトウェア開発を体験する ―― 『ディジタル・デザイン・テクノロジ』(No.1)に付属の基板をマイコン基板として活用する
tag: 組み込み ディジタル・デザイン
技術解説 2010年1月 8日
3. サンプルとして使用したマイコンの詳細
今回サンプルとして使用したマイコンの詳細について説明します.ソフトウェアは,すでに出来上がっているコードを使用しました.しかし実際の設計では,ハードウェアの仕様に合わせてソフトウェアを記述する必要があります.体験する段階からやや難しく感じるかもしれません.実務でマイコンを使う上で知っておくべきことをイメージしてみてください.
● ハードウェア構成を見る
今回のマイコン・システムは,MSBを用いて作成したLatticeMico32というCPUをベースとしています.図10にシステム構成を示します.FPGAである「LatticeXP2(LFXP2-5E 5TN144C)」に実装されます.
図10 サンプル・システムのハードウェア構成
LatticeMico32がCPUである.このシステムをFPGAに実装することで,FPGAがマイコンに化ける.
レジスタ・マップを表2に示します.プロセッサからこれらのアドレスを指定することで,それぞれのモジュールを制御できます.
表2 サンプル・システムのレジスタ・マップ
また,このシステムの入出力ピンの配置を表3に示します.
表3 サンプル・システムのピン配置
以下では,使用している機能ブロック(モジュール)について説明します.
(1) LatticeMico32
LatticeMico32は,CPUモジュールです.インスタンス名はLM32です.
キャッシュ・メモリが選択可能ですが,ここでは用いていません.WISHBONEというバスに接続され,周辺機能を操作します.
プロセッサと周辺機能には,33MHzのクロックを与えて動作させます.
(2) On-Chip Memory
On-Chip Memoryは,オンチップ・メモリを実現するモジュールです.インスタンス名はOnChip
Memoryです.
2Kバイトの読み書き可能なメモリ空間を実現します.このメモリには読み書きされるデータが配置されます.
(3) SPI Flash ROM
SPI Flash ROMは,SPIフラッシュ・メモリのインターフェース・モジュールです.インスタンス名はSPIFlashです.
LatticeMico32はこのインターフェースを介して,チップ外のSPIフラッシュ・メモリを読み書きします.
(4) GPIO
GPIOは,外付けのLEDを制御するために使用する汎用I/O(GPIO)インターフェースです.
このモジュールを二つ用いて,さらにそれぞれが一つの出力を実装することで,『ディジタル・デザイン・テクノロジ』誌(No.1)付属基板上の2個のLED(DL3とDL4)を制御します.それぞれのインスタンス名は,gpioled_DL3,gpioled_DL4です.このモジュールの出力値を変化させるには,オフセット0x0番地のレジスタに書き込みます.本システムではこのレジスタのLSBがLED出力になっています.
(5) Timer
Timerは32ビットのタイマです.このモジュールのインスタンス名はtimerです.
図11にタイマ・モジュールの制御レジスタを示します.
図11 タイマの制御レジスタ
タイマの動作を開始する前に,Periodレジスタに初期値を書き込んでおく.ControlレジスタのSTARTビットがセットされると,Periodレジスタにセットされたデータが内部のカウンタにセットされ,システム・クロック(33MHz)でカウント・ダウンが始まる.カウンタの値が0になると,StatusレジスタのTOビットがセットされ,ControlレジスタのITOビットがセットされていると,LatticeMico32へ割り込みが発生する.
タイマの動作を開始する前に,Periodレジスタに初期値を書き込んでおきます.ControlレジスタのSTARTビットがセットされると,Periodレジスタにセットされたデータが内部のカウンタにセットされ,システム・クロック(33MHz)でカウント・ダウンが始まります.このカウンタの値はSnapshotレジスタを読むと分かります.
カウンタの値が0になると,StatusレジスタのTOビットがセットされます.この際,ControlレジスタのITOビットがセットされていると,LatticeMico32へ割り込みが発生します.
割り込みを解除するためにはStatusレジスタに0を書き込みます.割り込みの完全解除にはLattice
Mico32の割り込み制御レジスタも操作する必要があります.
このモジュールは割り込みを発生可能で,IRQ0に割り付けています.
● ソフトウェア・コードの概要
今回使用したソフトウェアのソース・コードについて説明します.
まず,リセットからCPUが動作し始める段階について説明します.
リセット入力信号reset_nがアサートされると,CPUとその周辺機能がリセットされます.リセットの直後,CPUは0x0番地を読み出します.オンチップ・メモリは0x0番地にマップされていますから,OnChipMemoryモジュールが反応し,内部メモリから命令を読み出します.この際,最初に読み出される命令は例外ベクタと呼ばれるリセットの際に実行される特殊な命令列です.ここには,LatticeMico32と周辺モジュールを初期化するためのスタートアップ・ルーチンへの分岐命令を記述しておきます.
LatticeMico32はそのルーチンへ分岐し,システムを初期化します.スタートアップ・ルーチンではスタック・ポインタと呼ばれるC言語コードで暗に用いられるレジスタを初期化し,main関数へ分岐します.
その後,C言語で記述されたコードを実行していきます.ここまで来ると多くのソフトウェア技術者にとって記述が容易になります.
● スタートアップ・ルーチンstart.s
スタートアップ・ルーチンは,プロセッサの内部的な動作を設定したり,レジスタの値を直接変更したりする必要のあるコードを含むため,アセンブリ言語で記述されます.このアセンブリ言語にも文法がありますが,この文法のことをニーモニックと呼びます.ニーモニックは,マイコンのアーキテクチャにより異なり,マイコンの仕様書が規定しているのが一般的です.
今回のスタートアップ・ルーチンをリスト1に示します.
- 割り込みベクタ
- システム初期化とC関数への分岐
- 割り込み発生時の前後処理
からなります.
リスト1 スタートアップ・ルーチンstart.s
(1) 割り込みベクタ
割り込みベクタは,LatticeMico32への割り込みが発生した際に読まれる命令のリストです.Lattice
Mico32では,リセットも割り込みの一つとして分類されています.
(2) システム初期化とC関数への分岐
リセットが入力された直後,0x0番地の命令が実行されます.ここでは,__startupというラベルに分岐するbi命令があります.
__startupラベルからは,スタック・ポインタと呼ばれるC言語コードを実行する上で必要になるポインタをspレジスタに設定しています.最後にmainに分岐し,C言語コードの実行に移ります.
(3) 割り込み発生時の前後処理
その一方で,割り込みが発生したときには,例外ベクタの0xc0番地にある命令が実行されます.ここでは__interruptラベルに分岐する命令が記述されています.
__interruptラベル以降は,save_allというラベルを持つルーチンを実行して,レジスタの値を待避しています.これは,割り込みハンドラ関数内で自由にレジスタが使えるように,その内容を保存しておくことを目的としています.その後,C言語で記述されたinterrupt_handler関数が呼び出され,割り込みを処理し,先ほど待避したレジスタを復帰させ,割り込みが発生した番地に返ります.
● 組み込みC言語コードcq_dd3_practice.c
スタートアップ・ルーチンから呼び出されるmain関数と,割り込みベクタから呼び出されるinterrupt
_handler関数を記述したC言語コードをリスト2に示します.
リスト2 組み込みC言語コードcq_dd3_practice.c
main関数では,LED(DL3)をタイマで定期的に点滅させるために,タイマの割り込みを1秒で発生するように設定し,その後,LED(DL4)の点滅の無限ループに入ります.一方,interrupt_handler関数は,割り込みが発生すると呼び出され,タイマの割り込みであることを判定し,DL3を点灯・消灯し,再度タイマを1秒に設定しています.
メイン処理はC言語のような高級言語で記述しておくことで,開発・デバッグ効率を高めます.しかし,組み込みソフトウェア開発では,ハードウェアを直接操作するための特殊な記述が必要になります.このようなときには,C言語コードでアセンブラを呼び出すインライン・アセンブラの記述が必要です.
(1) アドレスの定義とvolatile
周辺機能を制御するには,その機能が割り当てられているアドレスを指すポインタを作成する必要があります(リスト2の①).
volatileというキーワードを使っているところに注目してください.これは,そのアドレスで指されるデータは揮発性で,アクセスするたびに値が変わることを示しています.
通常,コンパイラは,複数回同じアドレスにアクセスする場合に,最後の1回以外は無駄なコードであると解釈し,ほかを削除する最適化を行います.しかし,アクセスするたびに値が変わるのだとすると,最適化されてしまったら正しい処理が行えません.volatileを指定することで,必ずアクセスするようなコードを生成できます.
(2) インライン・アセンブラ
C言語ではマイコンのアーキテクチャに依存する記述は書けません.そのような記述が必要な場合はインライン・アセンブラという仕組みを使います.所望の命令をアセンブリ言語で記述し,Cコードの中に埋め込みます.
リスト2の②では,asmの中でwcsr IE, r1という命令を呼び出しています.このコードはvolatileで宣言されているので,コンパイラの最適化の影響を受けません.
● リンカ・スクリプトmemory.id
リスト3に,リンカ・スクリプトを示します.
リスト3 リンカ・スクリプトmemory.id
リンカ・スクリプトでは,命令コードやデータを配置するメモリ領域を指定するためのものです.
ここでは,オンチップメモリから命令を読み出すので,すべてのデータ,命令に関するセクションが,OnChipMemoryのアドレスにマップされています.
● 実行形式の作成で使用するMakefile
実行形式を作るための作業手順やファイルの関係を記述するMakefileを解説します.Makefileはリスト4に示すように,ターゲットと呼ばれるラベルをたどることで実行されていきます.allというラベルをまず,実行しようとします.その「:」の右側に記述されたファイルが必要になることを示しています.つまり,前述のスタートアップ・ルーチンとC言語コードから生成されたオブジェクト・ファイルが必要です.従って,「startup.o」と「.c.o」のラベルが先に実行されます.このように必要なファイルがそろうまで関連するラベルを実行していき,最後にallのラベルを実行します.
リスト4 実行形式の作成で使用するMakefile
やまぎわ・しんいち
高知工科大学 情報学部 准教授