ここでは,タクトスイッチによる外部割込みをするプログラムの作成をしましょう.3個のタクトスイッチはすべて3入力のANDゲートに接続されており,一つでもスイッチが押されるとANDゲートの出力がLowになります.この信号をマイコンの外部割込み端子に接続してあります.従いまして,どれか一つでもタクトスイッチが押されたらそのタイミングで割込みが発生し,押されているタクトスイッチを特定することができます.3個のタクトスイッチをメインの処理でずっと見張っている(ポーリングといいます)のではなく,押されたら割込み,そこではじめてどれが押されているか確認することで,別のメイン処理を行うことができるため効率的にマイコンを使うことができます.

  1. 回路の構成

    まずは回路構成を確認しておきます.3個のタクトスイッチは以前の演習で動作を確認していると思います.今回,割込みを行うのがTS_PR(黒色で囲った端子)です.下のようにIRQ3-Bに接続されていることが確認できます.
    TactSwitchWithInterrupt01

    ANDゲートと接続されている回路図も下に示します.3個のタクトスイッチがすべて押されていない時にはHighがANDゲートに入力されますので,Highが出力されます.一つでも押されるとLowが出力されます.
    TactSwitchWithInterrupt02


  2. 外部割込み

    外部割込みをするにはいくつかのレジスタを操作しなければなりません.下の表に示す,関連レジスタについて次節以降で説明します.割込みに関係するレジスタは割込みコントローラ(ICU)に含まれます.下表にある構造体ICUはその名前由来です.
    名称 構造体 メンバ 役割
    インタラプトイネーブルレジスタ ICU IER 割込みを許可・禁止する
    インタラプトプライオリティレジスタ ICU IPR 割込みの優先順位を設定する
    ポートファンクションレジスタ IOPORT PFxIRQ 外部割込み端子として設定する
    IRQコントロールレジスタ ICU IRQCR 割込みするタイミングを設定する
    インタラプトレジスタ ICU IR 割込みの有無を確認できる
    入力バッファコントロールレジスタ PORTx ICR 入力バッファを有効・無効にする

    1. インタラプトイネーブルレジスタ(IER)

      割込みを許可もしくは禁止をするレジスタです.割込み要因は大変多く存在するため,このレジスタは配列となっています.簡単にIERを扱うことができるよう,iodefine.hにはIER用マクロIENが用意されています.このマクロを使えば,どの周辺機能が配列のどの要素番号なのか知る必要がなくなります.例えば,IRQ3の外部割込みを禁止にするのであれば,IEN(ICU, IRQ3)= 0;となります.つまり,IEN(割込みソース, 名前) = 代入したい値 というように書けばよいのです.割込みソース名および名前についてはiodefine.hを読んでください.

    2. インタラプトプライオリティレジスタ(IPR)

      RX62Nには割込みを行う優先順位を0~15までの16段階で決めることができ,複数の割込みが発生したとき順位が高い方を優先的に割り込む仕組みを持っています.このように,ある割り込みを処理しているときに別の割込みを行える仕組みを多重割込みといいます.この優先順位を決めるのがIPRです.IPRもIERと同様に,割込みの種類だけ存在するため,レジスタは配列となっています.そこで,簡単に扱えるマクロIPRがiodefine.hに定義されています.使い方はIENと同様で,IPR(割込みソース, 名前) = 優先度 というように書きます.優先度が高いほど,優先的に割り込みを行ってくれます.

    3. ポートファンクションレジスタ(PFxIRQ)

      RX62N144ピンには,IRQ3端子がポート1ビット3とポート3ビット3の2個あり,どちらを使うか決める必要があります.そのとき使うのがPF9IRQです.PFから始まるレジスタにはいくつかあり,そもそもPFとはポートファンクションですので,端子の役割を決めるためのレジスタなのです.そのうちPF8とPF9が外部割込み端子を決めるためのレジスタとなっております.

    4. IRQコントロールレジスタ(IRQCR)

      割り込みを発生させる基準は,Low,立ち下がりエッジ,立ち上がりエッジ,両エッジの4種類であり,これを決めなければなりません.そのために使用するのがIRQCRです.IRQCRは,割込み端子ごとに設定できるため,0から15まで存在します(RX62Nには0~15までの割込み端子があります).このため,IRQCRは配列となっています.しかしこちらはIERやIPRと異なりマクロは存在しません.例えばIRQ3を立ち下がりエッジで割込ませたい場合,ICU.IRQCR[3].BIT.IRQMD = 1;となります.

    5. インタラプトレジスタ(IR)

      割込みが発生したとき立ち上がるフラグが入っているレジスタがIRです.このレジスタもIENなどと同様,割込みの数だけ存在するため,マクロIRがiodefine.hに定義されています.使い方はIERと同様に,クリアする場合には,IR(割込みソース, 名前) = 0;と書きます.なお,割込み処理終了後はプログラマが明示的にフラグをクリア(0にすること)しなければなりません.忘れずに行ってください.

    6. 入力バッファコントロールレジスタ(ICR)[再掲]

      このレジスタはLEDの点灯および消灯でも説明したレジスタで,入力をする周辺機能を使用するときには有効にしておかなければなりません.今回,外部割込み(入力)を行いますので,該当する端子のICRを有効にしておきます.

  3. クラスの構成

    クラスの構成を下に示します.RedTactSwitchなど3個のクラスは,TactSwitchesとコンポジット(関連の一種で一心同体であることを表す関係)となっています.そして,TactSwitchesはIPressedEventListenerインタフェースを持っており,どれかボタンが押される割込まれ,割込み関数内でIPressedEventListenerのメンバ関数であるpressedTactSwitchをTactSwitches内で呼び出すようにプログラミングします.割込み関数はExcep_ICU_IRQ3です.この関数の使い方については後ほど補足します.
    TactSwitchWithInterrupt03
    1. 処理の流れ

      下に,割込みが発生し,処理をするまでの流れを示します.なお,左側にあるPressedEventListenerImplクラスは,IPressedEventListenerクラスを実現したクラスです.さて,最初にメイン関数では,TactSwitchesのインスタンスを得るため,getInstanceメンバ関数を呼び出し*swsを得ます.次に,PressedEventListenerImplをnewにより生成し*event_handlerとします.そして,event_handelrオブジェクトを使ってこの*swsをPressedEventListenerImplクラスのsetEventListenerメンバ関数で設定します.これにより,TactSwitchesクラスではPressedEventListenerImplのオブジェクトを持つことになります.その後,割込みが発生するとExcep_ICU_IRQ3関数が呼び出されますので,その関数内でpressedTactSwitchメンバ関数を呼び出します.この関数内で押されたボタンに応じたプログラムを記述しておきます.最後に割込み処理が終わりましたら,メインの処理に復帰させます.
      TactSwitchWithInterrupt04
    2. 割込み関数Excep_ICU_IRQ3

      割込み関数はCubeSuite+のプロジェクトではintprg.cに割込み関数が生成されます.今回,割込み処理Excep_ICU_IRQ3をTactSwitchesクラスのメンバ関数(に近いもの)にしたいため,intprg.c内のExcep_ICU_IRQ3を消します.そして,TactSwitchesクラスで利用できるようにします.子の節ではその方法を説明します.
      1. intprg.cからExcep_ICU_IRQ3を削除
        intprg.cにはすべての割込み関数が定義されています.今回はそのうちExcep_ICU_IRQ3を削除(もしくはコメントアウト)してください

      2. C言語の関数をC++言語のクラスで使えるようにする
        Excep_ICU_IRQ3関数はC言語の関数です.これをC++言語で使用するときにはextern "C"を使わなければなりません.具体的には,TactSwitches.hpp内に下記のような宣言をします.
        extern "C" {
        void Excep_ICU_IRQ3(void);
        }
        

        これで,C++言語でもExcep_ICU_IRQ3関数を使用することができます.

      3. TactSwitchesクラス内にExcep_ICU_IRQ3を追加
        TactSwitchesクラス内でExcep_ICU_IRQ3関数をメンバ関数として扱えれば,TactSwitchesのプライベートなメンバにもアクセスできて便利です.C++にはそのような扱いをすることができるのです.それがfriend関数です.friend関数にすると,所属するクラスのプライベートなメンバやメンバ関数を扱うことができるようになります.このように,C言語の割込み関数をC++関数のクラスに所属した(ように見せかける)にはfriendと書いてください.
        もうひとつ,注意すべきことがあります.それは関数名の頭に「::」をつけなければならないことです.この記号は名前空間のデリミタであり,関数の頭につけることでルートの名前空間の下にある関数である,ということを表しています.実は,C言語の関数をextern "C"をした場合,C++言語ではルートの名前空間の下にある関数とみなす,という決まりがあるのです.以上のことを踏まえてExcep_ICU_IRQ3をTactSwitchesクラスで宣言するとき下のようになります.なお,他のメンバ関数やメンバを省略してあります.
        class TactSwitches {
        public:
          friend void ::Excep_ICU_IRQ3(void);
        };
  4. 実装

    では実装をしていきましょう.今回作成するプログラムの動作は,以前作成した割込みなしのタクトスイッチのプログラムと同じで,赤色タクトスイッチを押すとLED7が点灯し,緑色タクトスイッチを押すとLED8が点灯し,青色タクトスイッチを押すとLED7およびLED8が消灯するようにしてください.ただし,今回はメイン関数の処理は無限ループ内で何も行わないようにしておき,割り込みが発生したら上記の動作をするようにしてください.こちらに関連するクラスを書いたastah*を置いておきますのでダウンロードしてください.その後スケルトンコードを生成し,デフォルトプロジェクトを流用したプロジェクトを新規作成してそこに追加してください.なお,LED7およびLED8については,以前作成したプログラムから必要なファイルをコピーしてください.
    1. TactSwitches.hpp

      前の節でも書きましたが,TactSwitches.hppにExcep_ICU_IRQ3関数をつかえるようにするため,extern "C"を記入してください.
    2. intprg.c

      Excep_ICU_IRQ3関数が既に宣言されていますので,コメントアウトしてください.
    3. TactSwitches.cpp

      TactSwitchesクラスはシングルトンであるため,コンストラクタ,代入演算子,デストラクタおよびgetInstanceメンバ関数はほぼ同じようになりますので,これまでの例を見ながら作成してください.
      1. _initializeメンバ関数
        下に_initializeメンバ関数で行う処理をアクティビティ図にまとめた図を示します.はじめに3個のタクトスイッチのインスタンスを取得し,メンバに代入しておきます.次に,イベントリスナをNULLで初期化しておきます.その後,割込みに関係するレジスタの操作を行います.まず,割り込みをマクロIENを使い禁止します.次に,IRQ3-B(ポート1ビット3)の端子に備わる入力バッファを有効にします.そして,マクロIPRを使ってIRQ3の割込み優先度を_DEFAULT_INTERRUPT_PRIORITYにします._DEFAULT_INTERRUPT_PRIORITYは定数です.次に,ポート1ビット3をIRQ3の端子とするため,ポートファンクションレジスタ9(PF9IRQ)を変更します.そして,IRQ3に立ち下がりエッジが入力されたときに割込みを発生させるべくIRQCRレジスタを変更します.最後に,割込み時に立ち上がるフラグをクリアするため,マクロIRを使います.以上の処理を_initializeメンバ関数に書いてください.
         initialize
      2. setInterruptAtPressedメンバ関数
        この関数では,タクトスイッチが押されたときに割込みを発生させるか設定できるようにするため,マクロIENを使ってIRQ3の割込みを許可/禁止を設定します.

      3. setInterruptPriorityメンバ関数
        この関数では割込み優先度を設定するため,マクロIPRを使います.

      4. setEventListenerメンバ関数
        この関数では,引数をイベントリスナとして記憶しておきます.具体的には,引数がevent_listener,メンバが_eventListenerの場合,_eventListener = event_listener; とすればよいです.

      5. Excep_ICU_IRQ3関数
        この関数はfriend関数であるため,通常のメンバ関数と異なりTactSwitchesクラス内には実装せず,従来のC言語の関数と同様,名前空間の外に書くことになります.さらに,Excep_ICU_IRQ3関数は割込み時の関数であるため,割込みベクタテーブルに記載されたアドレスに関数を配置する必要があります.ハードウェアマニュアルの表11.4にある割り込みのベクタテーブルによるとIRQ3はベクタ番号67に割り振られています.従いまして,コンパイラに対して,ベクタ番号67の位置にExcep_ICU_IRQ3を割り付けてくれるよう,命令をします.このとき用いるのが#pragma interruptです.これにより,Excep_ICU_IRQ3を67に割り付けることができます.下にExcep_ICU_IRQ3関数をTactSwitches.cppに記述した例を示します.
        namespace user_interface {
          namespace tact_switch {
          /* ここにTactSwitchesのメンバ関数を書く */
          }  // namespace tact_switch
        } // namespace user_interface
        
        #pragma interrupt (Excep_ICU_IRQ3(vect=67))
        void Excep_ICU_IRQ3(void){
          /* ここに割込み時の処理を書く */
        }
        

        なお,割込みベクタ番号67は,iodefine.hにてVECT_ICU_IRQ3として定義されていますので,67の代わりにVECT_ICU_IRQ3と書いてもよいです.
        さて,Excep_ICU_IRQ3関数内に書く処理について説明します.まず,イベントリスナ_eventListenerがNULLではないか確認します.もしNULLでなければイベントリスナが設定されていますので,その後の処理を行い,NULLであれば割込みフラグをクリアして関数を終了します.さて,イベントリスナが設定されている場合,タクトスイッチのチャタリングを防止するため,ウェイトを入れます.ここでは単純にfor文を10万回ほど回してください.このとき10万回繰り返す変数にvolatileを付けておいた方が無難です.そうでないと,最適化したときにこのfor文を省いてしまう可能性があるからです.あとは,3個のタクトスイッチが押されていたらtypeローカル変数にタクトスイッチの色を記憶しておいてください.typeローカル変数はあらかじめこの関数内のどこかで宣言しておいてください.最後に,タクトスイッチが押されたことを伝えるため,_eventListenerインタフェースに備わるpressedTactSwitchメンバ関数をtype引数とともに呼び出してください.
        Excep ICU IRQ3

    4. PressedEventListenerImpl.cpp


      この関数には,押されたタクトスイッチに応じてLEDを点灯もしくは消灯をするプログラムを書きます.下に示すアクティビティ図は,PressedEventListenerImplクラスにあるpressedTactSwitchメンバ関数の流れを表しています.仮引数pressed_tact_switchがREDならLED7を点灯,GREENならLED8を点灯,BLUEならLED7およびLED8を消灯するプログラムを書いてください.
      pressedTactSwitch
    5. main関数

      メイン関数には,下に示すアクティビティ図のようにプログラムを記述します.ここで「clrpsw_i関数」と「setpsw_i関数」を用いています.これらはともに組込み関数であり,前者はCPUに対して割込みを禁止,後者は割込み許可を行います.RXマイコンには,プロセッサステータスワード(PSW)という制御レジスタが存在し,その中にあるのがプロセッサ割込み優先レベル(IPL)です.IPLと,割込み優先度,例えばIRQ3の優先度とを比較し,IPLの方が小さければ割込みが発生し,同じもしくは大きい場合には割込みを発生しない仕組みとなっています.clrpsw_i関数はIPLを15とし,setpsw_i関数はIPLを0とします.これにより,clrpsw_i関数では割込みが禁止され,setpsw_i関数では割込みが許可されるのです.なお,組込み関数を利用するにはmachine.hをインクルードする必要がありますので,Main.cpp内の冒頭で#include<machine.h>を書いてください.
      main
JSN Teki is designed by JoomlaShine.com