デバイスドライバとは

組み込みシステムにおけるデバイスドライバとは

組み込みシステムのアプリケーションを作っている場合、CPUの機能だけを使うことはほぼないと言っていいでしょう。単純な計算だけだとしても、何らかの入力が必要であり、何らかの出力が存在します。通常、ハードウェアの操作を行うために用意するソフトウェアを「デバイスドライバ」と呼んでおり、パソコンなどでも頻繁に利用されています。

組み込みシステムにおいても同様で、さまざまなハードウェア・インタフェースを利用する場合に、デバイスドライバを利用します。ただ、パソコンのようなシステムで使うデバイスドライバと異なり、組み込みシステムの場合は、ON/OFFなどの操作からパケットの送信/受信といった抽象度の高い操作まで幅広く扱うものが多く、組み込みシステムのアプリケーションを組む上では、デバイスドライバの特性も理解しておく必要があります。そして、上流のトップダウン設計だけではうまく実装できないのも、ハードウェアを使用した組み込み機器ならでは。ぜひその辺りの勘所を掴んでもらえればと思います。

CPUコアやハードウェアに依存しないという考え方

RTOSやデバイスドライバの多くは、ハードウェアの差異を吸収する部分を持っており、大きく分けて2つの「依存部」が存在します。1つは、今回のようにCortex-AであるとかRISC-VとかCortex-RといったCPUコアに依存した部分のことを指す「CPU依存部」。もう1つが、デバイスの周辺機能や割り込みコントローラに対応した「デバイス依存部」です。この依存部が存在する理由は、アプリケーション開発において、極力CPUやデバイスに依存しないように作ることが組み込みシステムの基本的な考え方であり、デバイスが変わったとしてもアプリケーションの機能を維持することが可能だからです。

RTOSは、主にCPUのコア依存部を吸収することと、デバイスの依存部分を吸収することで、上位のアプリケーション部における依存部を減らすことに重きが置かれています。(図1)

図図1:CPUコアやデバイスの依存部が、機種依存を吸収

デバイスドライバは、大きく分けると3つのタイプが存在します。1バイトずつのデータを扱う【キャラクタ型デバイス】、複数バイトデータ単位のデータで扱う【ブロック型デバイス】、【それ以外のデバイスドライバ】です。

  • 【キャラクタ型デバイス】UART、I2C、SPIなど
  • 【ブロック型デバイス】Ethernet、USB、I2Sなど
  • 【それ以外のデバイスドライバ】割り込み、タイマ、RTC、PWMなど

Cortex-Aに代表されるレジスタが32ビット長のCPUが一般的になってから、周辺機能用のレジスタも32ビットに拡張されてきましたが、機能によっては、8ビットや16ビットのみが有効といったレジスタも存在します。

また、Arm提唱のCMSISに代表されるようなデバイスドライバのテンプレート化により、ベンダー各社のオリジナルボードや周辺機能においても、標準化の流れが一般的になっており、「BSP(Board Support Package)」という形で、提供してくれる状況になっています。

BSP(Board Support Package)の使い方

BSPは、組み込みシステムボード(ハードウェア)に搭載されている機能を使うために用意されたAPIを使うことが目的です。

今回のSOLID Starter Kit for RZ/A1Hには、ボードが付属していますが、CPUがもっている機能を全て試すことができるかというと、まだ十分とは言えません。RZ/A1Hはものすごく多くの機能を搭載しています。それを名刺サイズのボードにすることはできても、すべての機能を使えるようにすることとは異なります。今回SOLIDで用意しているBSP(図2)も、すべての機能を準備することは、とても大変です。

ですので、ボードに見合ったBSPを用意するのが一般的です。それ以外に必要な機能は、機能的に近いAPIに倣って、自分で作成すればいいのです。

図図2:SOLID Starter Kit for RZ/A1HでサポートしているBSP

自分で作成するメリットは、コードサイズの調整がしやすいことと、ハードウェアに対して、どのような操作をするか自分で決められることです。どんな順番でハードウェアにアクセスするか、どのようにしたら期待通りの動きになってくれるのか?を調べたり、突き詰めていくことが、組み込みシステムの醍醐味です。

この例は、led.cのコードです。GPIOの先につながっているLEDを駆動するだけなので、CPUからのアクセスは書き込みのみで実装されています。

もう少し深く見ていきましょう。

GPIOには、RZA_IO_RegWrite_16というAPIが使われています。16ビット長で書き込むためのAPIであることが理解できます。同じく、RZA_IO_RegRead_16という読み出し専用のAPIも用意されています。これらは、io_reg.hに定義されているので、8ビット・16ビット・32ビット長のAPIが用意されています。

また、IO空間に関しては、動画で説明した通り、物理アドレスから仮想アドレスに変換されて扱われています。SOLIDが提供しているマクロには、事前にこの設定が反映されてるので、RZA_IO_RegRead/RegWriteのAPIを使用すれば、仮想アドレスを基準にしたIOレジスタアクセスが可能になっています。

図図3:GPIOがマッピングされている物理アドレスと仮想アドレス

宿題
bspフォルダ内のrza_io_regrw.cに実際の定義がありますので、どのように作られているのか調べて見ましょう。

RZ/A1H用のGPIO出力操作

実際は、どのようなAPIでGPIOが操作するのかみてみましょう。

GPIOを動かす前に、SOLID-IDE上にある「ソリューションエクスプローラー」をみてください。driver、boot、bsp、SOLIDApplicationの項目があります。そのなかのbspをクリックし、led.cを開きます。

  • LedInit() GPIOを使った初期化関数
  • LedOn() GPIO経由でLEDを点灯させる関数
  • LedOff() GPIO経由でLEDを消灯させる関数

この3つのAPIで、LEDは操作しています。回路としては、ActiveLowになっています。詳しくは下記を参照ください。

LEDによるActive HighとActive Low回路の違い

LEDをデバイスから点灯させるには、GPIOのピンに「1」あるいは「0」を書き込む必要があります。ここで注意しなければならないのが、「1」だから点灯する訳ではないということです。GPIOなどの汎用入出力ピンは、2種類の駆動方法があります。

図

上図のような電源とGPIOの間にLEDが入る場合を、「アクティブ・ロー(Active Low)」と呼びます。この場合は、GPIOに1を設定しても、電源とGPIOの出力電圧が変わらないため、電流変化がなく、LEDは点灯しません。GPIOに0を設定すると、電源とGPIOの間で電位差が発生します。その電位差を抵抗RでLEDを点灯させるのに必要な電流にして、GPIOの0出力端子に流れ込みます。

図

上図のようなGPIOの先に、LEDとGNDがつながった場合を、「アクティブ・ハイ(Active High)」と呼びます。この場合は、GPIOを1を設定すると、GPIOとGND間で電位差が発生します。Active Lowの0出力の時と同様に、電位差からLED駆動電流を抵抗Rに流してLEDを点灯させることができます。GPIOを0に設定すると、GPIOとGND間で電位差が生じないため、電流が流れません。そのため、LEDも点灯しません。

						void LedInit(void)
						{
						    /* ---- P1_10 : LED1 ---- */
						    /* Port initialize */
						    RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC14_SHIFT, GPIO_PIBC1_PIBC14);
						    RZA_IO_RegWrite_16(&GPIO.PBDC1, 0, GPIO_PBDC1_PBDC14_SHIFT, GPIO_PBDC1_PBDC14);
						    RZA_IO_RegWrite_16(&GPIO.PM1,   1, GPIO_PM1_PM14_SHIFT,     GPIO_PM1_PM14);
						    RZA_IO_RegWrite_16(&GPIO.PMC1,  0, GPIO_PMC1_PMC14_SHIFT,   GPIO_PMC1_PMC14);
						    RZA_IO_RegWrite_16(&GPIO.PIPC1, 0, GPIO_PIPC1_PIPC14_SHIFT, GPIO_PIPC1_PIPC14);
						    /* Mode : Port mode                          */
						    /* Terminal output level : High level output */
						    /* Port mode : Output mode                   */
						    RZA_IO_RegWrite_16(&GPIO.PBDC1, 0, GPIO_PBDC1_PBDC14_SHIFT, GPIO_PBDC1_PBDC14);
						    RZA_IO_RegWrite_16(&GPIO.P1,    1, GPIO_P1_P14_SHIFT,       GPIO_P1_P14);
						    RZA_IO_RegWrite_16(&GPIO.PM1,   0, GPIO_PM1_PM14_SHIFT,     GPIO_PM1_PM14);
						}

						void LedOn(void)
						{
						    RZA_IO_RegWrite_16(&GPIO.P1, 0, GPIO_P1_P14_SHIFT, GPIO_P1_P14);
						}


						void LedOff(void)
						{
						    RZA_IO_RegWrite_16(&GPIO.P1, 1, GPIO_P1_P14_SHIFT, GPIO_P1_P14);
						}
					

RZ/A1H用のGPIO入力操作

次に、GPIOピンを入力として機能するように設定してみましょう。GPIOには、単純な入出力以外にも、多くの機能が割り当てられています。どのような機能で使うかは、RZ/A1Hのハードウェアリファレンスマニュアルで確認してください。

GPIOは、Cortex-A9から見ると、内部バスに接続された機能ブロックとして見えています。そして、機能ブロックが配置されている場所には、アドレスが割り付けられています。CPUからアクセスする場合には、このアドレスに対してアクセスを行います。通常、機能ブロックとして割り付けられたベースアドレスの設定を確認します。SOLID Starter Kit for RZ/A1Hは、MMUを使用していますので、物理アドレス表記から仮想アドレスへの変換をして、アドレスを読み替える必要があります。

  • 物理ベースアドレス PORTn 0xFCFE3000 JPORT 0xFCFE7B00
  • 仮想ベースアドレス PORTn 0xF1203000 JPORT 0xF1207B00
  • nはポート番号 0から11番まである。

そして、GPIOのポート番号と各レジスタに合わせたアドレスにアクセスする必要があります。

略語 レジスタ名 オフセットアドレス
Pn ポートレジスタ PORT + 0000H + nx4
PSRn ポートセット/リセットレジスタ PORT + 0100H + nx4
PPRn ポート端子リードレジスタ PORT + 0200H + nx4
PMn ポートモードレジスタ PORT + 0300H + nx4
PMCn ポートモード制御レジスタ PORT + 0400H + nx4
PFCn ポート機能制御レジスタ PORT + 0500H + nx4
PFCEn ポート機能制御拡張レジスタ PORT + 0600H + nx4
PNOTn ポートNOTレジスタ PORT + 0700H + nx4
PMSRn ポートモードセット/リセットレジスタ PORT + 0800H + nx4
PMCSRn ポートモード制御セット/リセットレジスタ PORT + 0900H + nx4
PFCAEn ポート機能制御追加拡張レジスタ PORT + 0A00H + nx4
PIBCn ポート入力バッファ制御レジスタ PORT + 4000H + nx4
PBDCn ポート双方向制御レジスタ PORT + 4000H + nx4
PIPCn ポートIP制御レジスタ PORT + 4000H + nx4
JPPR0 ポート端子リードレジスタ JPORT + 0020H
JPMC0 ポートモード制御レジスタ JPORT + 0040H
JPMCSR0 ポートモード制御/リセットレジスタ JPORT + 0090H
JPIBC0 ポート入力バッファ制御レジスタ JPORT + 0400H
SNCR シリアルサウンドインタフェース/ノイズキャンセラ制御 JPORT + 0C00H
表1:RZ/A1HのGPIOレジスタアドレスマップ

例えば、ポート1のPPR1レジスタにアクセスするには、PPR1=仮想ベースアドレス+0x0200+1x4=0xf1203204にアクセスしなければいけません。

レジスタアクセスはできそうですね。では、ポートの初期化を見ていきましょう。GPIOの初期化手順や設定方法は、RZA/1Hのハードウェアリファレンスマニュアルに記載されていますが、概ね下記の設定をしています。

初期化
  • PIBCx ポート入力バッファ制御レジスタ
  • PBDCx ポート双方向制御レジスタ
  • PMx ポートモードレジスタ
  • PMCx ポートモード制御レジスタ
  • PIPCx ポートIP制御レジスタ
モード設定
  • PBDCx ポート双方向制御レジスタ
  • Px ポートレジスタ
  • PMx ポートモードレジスタ
  

実際に動画で使っているSW入力は、下記の設定をしています。P1_8とPI_9の2つを使用しています。まずは、ポートの初期化です。

						RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC18_SHIFT, GPIO_PIBC1_PIBC18);
						RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC19_SHIFT, GPIO_PIBC1_PIBC19);

						RZA_IO_RegWrite_16(&GPIO.PBDC1, 0, GPIO_PBDC1_PBDC18_SHIFT, GPIO_PBDC1_PBDC18);
						RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC19_SHIFT, GPIO_PIBC1_PIBC19);
	
						RZA_IO_RegWrite_16(&GPIO.PM1, 1, 8, GPIO_PMC1_PMC18);
						RZA_IO_RegWrite_16(&GPIO.PM1, 1, 9, GPIO_PMC1_PMC19);

						RZA_IO_RegWrite_16(&GPIO.PMC1, 0, GPIO_PMC1_PMC18_SHIFT, GPIO_PMC1_PMC18);
						RZA_IO_RegWrite_16(&GPIO.PMC1, 0, GPIO_PMC1_PMC19_SHIFT, GPIO_PMC1_PMC19);

						RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC18_SHIFT, GPIO_PIBC1_PIBC18);
						RZA_IO_RegWrite_16(&GPIO.PIBC1, 0, GPIO_PIBC1_PIBC19_SHIFT, GPIO_PIBC1_PIBC19);
					

次に、入力モードへの設定です。(最終的にはIRQとして使用したいため、IRQ機能の設定にしています。)

						RZA_IO_RegWrite_16(&GPIO.PMC1, 1, GPIO_PMC1_PMC18_SHIFT, GPIO_PMC1_PMC18);
						RZA_IO_RegWrite_16(&GPIO.PMC1, 1, GPIO_PMC1_PMC19_SHIFT, GPIO_PMC1_PMC19);

						RZA_IO_RegWrite_16(&GPIO.PM1, 1, 8, GPIO_PMC1_PMC18);
						RZA_IO_RegWrite_16(&GPIO.PM1, 1, 9, GPIO_PMC1_PMC19);

						RZA_IO_RegWrite_16(&GPIO.PIBC1, 1, GPIO_PIBC1_PIBC18_SHIFT, GPIO_PIBC1_PIBC18);
						RZA_IO_RegWrite_16(&GPIO.PIBC1, 1, GPIO_PIBC1_PIBC19_SHIFT, GPIO_PIBC1_PIBC19);

						RZA_IO_RegWrite_16(&GPIO.PFCAE1, 0, GPIO_PFCAE1_PFCAE18_SHIFT, GPIO_PFCAE1_PFCAE18);
						RZA_IO_RegWrite_16(&GPIO.PFCAE1, 0, GPIO_PFCAE1_PFCAE19_SHIFT, GPIO_PFCAE1_PFCAE19);
	
						RZA_IO_RegWrite_16(&GPIO.PFCE1, 1, GPIO_PFCE1_PFCE18_SHIFT, GPIO_PFCE1_PFCE18);
						RZA_IO_RegWrite_16(&GPIO.PFCE1, 1, GPIO_PFCE1_PFCE19_SHIFT, GPIO_PFCE1_PFCE19);

						RZA_IO_RegWrite_16(&GPIO.PFC1, 0, GPIO_PFC1_PFC18_SHIFT, GPIO_PFC1_PFC18);
						RZA_IO_RegWrite_16(&GPIO.PFC1, 0, GPIO_PFC1_PFC19_SHIFT, GPIO_PFC1_PFC19);
					

これで、P1_8P1_9は入力として使用することができるようになりました。では、ハードウェアとしてSWをつないでみましょう。

SW入力回路

SWは、ボタンの状態によって信号レベルを変えるために使用することができます。

図4は、SW入力回路の代表的な接続方法です。GPI(General Porpuse Input)は、汎用入力ポートの入力のみで使用するという意味です。SWはGPIに入力として接続しています(図4)。SOLID Starter KitのRZ/A1Hでは、CN1にP1_8P1_9が引き出されていますので、そのピンを接続しています。

図図4:SW回路

SWが押されていなければ、電流IはGNDへは流れませんので、Lowを認識します。SWが押されて入れば、Vccから抵抗Rを通してGNDに向かって電流Iが流れるので、Highとして認識します。

デバイスによって、Highの認識レベルおよびLowの認識レベルが異なります。実装では、Rに2KOhmを使用しています。GPIには、約1.65mAの電流が流入します。RZ/A1Hの電気的特性のDC特性では、最大2mAとなっていますので、仕様内であると言えます。

デバッガとは

SOLIDを使用して、デバッグを行うにはアプリケーションのビルドが完了していなければいけません。ビルドができていないものをデバッグはできません。

コンパイラの機能で、文法的な誤りを検出して、コーディングのミスを防ぐようにはできますが、プログラムの流れやロジック、APIの呼び出しタイミングなど、実行させないとわからないことがでてきます。そんな時に役に立つツールが「デバッガ」です。

デバッガは、ソフトウェアツールの名称ですが、組み込みシステム機器開発の場合、ターゲットと呼ばれるボードと接続します。デバッガとターゲットを接続する機器が「ICE(アイス)」と呼ばれています。今は、ほとんどの機器がJTAG経由で接続することになっているので、「JTAG-ICE(ジェータグアイス)」と呼んでいます(図5)。

図図5:JTAG-ICEとターゲットの接続

SOLID Starter Kit for RZ/A1Hには、京都マイクロコンピュータ社製JTAG-ICE「PARTNER-Jet2 Model10」が同梱されています。国内のデバッガベンダーでも屈指の機能を兼ね備えている大変高機能なJTAG-ICEなのです。これを同梱してあるということは、できないことはほとんどないと言っていいくらいです。

動画では、デバッガの使い方を紹介しています。デバッガモードに移行すると、SOLID-IDEは画面が切り替わります。この時、ソースコードの編集はできないように保護されます。そして、ソースコードの行番号の箇所で、「Ctrl+8」かダブルクリックをしてみましょう。がつきます。「ブレークポイント」と呼ばれる目印で、このポイントの直前までが実行されて、このポイントでプログラムが一時停止します(図6)。

図図6:デバッガによるブレークポイントの設定

通常、このようにブレークポイントをいくつも設定することで、プログラムの流れやシーケンスが期待した通りの動きをしているかの確認や、その時の変数の値やメモリの内容を確認します。

JTAG-ICEを使わない方法でデバッグすると、いわゆる「printfデバッグ」と呼ばれる方法で、変数をシリアル経由で表示することになります。ただし、表示するだけの機能になるので、変数を書き換えてシーケンスを変えたりすることはできません。

PARTNER コマンド ウィンドウを使いこなそう!

デバッガを使ってデバイスドライバのデバッグをしていると、レジスタの値が正しい値なのかどうか知りたくなる時があります。そんな時に役に立つのが、「PARTNER コマンド ウィンドウ」です。デバッガのメニューから、選択します(図7)。

図図7:PARTNER コマンド ウィンドウを呼び出し

図図8:PARTNER コマンド ウィンドウの画面

PARTNER コマンド ウィンドウの中では、下記のコマンドが使用できます。

  • pib 8ビットレジスタに対する読み込みコマンド
  • piw 16ビットレジスタに対する読み込みコマンド
  • pid 32ビットレジスタに対する読み込みコマンド
  • pob 8ビットレジスタに対する書き込みコマンド
  • pow 16ビットレジスタに対する書き込みコマンド
  • pod 32ビットレジスタに対する書き込みコマンド

今回は、デバイスドライバとデバッガについて学んでみました。デバッガについては講座の後半でもう少し詳細に解説する予定です。組み込み機器の特性上、ハードウェア・ソフトウェアの両方に対してアプローチすることが重要であることを理解してもらえたらと思います。

前の記事を読む