RXマイコンのソフト開発(2)スイッチのチャタリング対策

RXマイコン(RX631)のソフト開発の2回目です。今回は、プッシュスイッチの入力を受けて、フルカラーLEDの点灯色を変更したいと思います。回路ではチャタリング除去をしていないため、ソフト側でチャタリング対策をしたいと思います。前回と同様にRX631のユーザーズマニュアル ハードウェア編を参照することをおススメします。

図1:基板上のプッシュスイッチ
図1:基板上のプッシュスイッチ

環境

  • パソコン: Windows10 64 bit
    • 統合開発環境: e2studio 2021-01をインストール
    • コンパイラ: Renesas CCRX v3.03.00をインストール
    • 書き込みソフト: Renesas Flash Programmer V3.08.01(無償版) をインストール
  • ターゲットデバイス: R5F5631PDDFL(RX631 48ピン)

スイッチの回路

図2:スイッチ部分の回路図
図2:スイッチ部分の回路図

作成したスイッチ部分の回路図は図2です。SWのポートはP35です。回路にはチャタリング除去するためのローパスフィルタがないので、24msごとにタイマー割り込みをしてスイッチの状態を確認したいと思います。スイッチオフからスイッチオンになったときにフルカラーLEDの点灯色を変えます。

プログラムのフロー

プログラムの処理の流れは図3です。レジスタプロテクトの設定は省略しています。

図3:プログラムのフロー
図3:プログラムのフロー

前回の記事でおこなったクロックの初期化とI/Oポートの設定を行い、今回用いるコンペアマッチタイマー0(CMT0)の初期化を行っています。その後、メインルーチンに入り、スイッチの押し込みを検知したらLEDの点灯色を変更します。スイッチの押し込みはCMT0を用いて24ms周期で検知します。

補足チャタリングの期間は長くても 10 数msといわれているので、スイッチ検知の周期は余裕をもって24msとしました。

プログラム

スイッチに関連するプログラム部分だけ示します。全てのソースコードGitHubの「2_sw」フォルダの中にソースコードがありますので、詳細を知りたい場合はご覧ください。

メイン関数

main.c:メイン関数のソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

void main(void){
    // 0:OFF, 1:赤, 2:青, 3:紫, 4:緑, 5:黄, 6:水色, 7:白
    unsigned char led_color = 0;  // フルカラーLEDの色

    init_rx631();  // RX631の初期化
    while(1){
        if(g_sw_chg){
            led_color++;
            led_color = led_color & 0x07;
            Full_color_LED(led_color);   // フルカラーLEDを点灯
            g_sw_chg = 0;  // スイッチの押し込みフラグを下げる
        }
    }
}

メイン関数では、RX631の初期化を行い、割り込み関数によってスイッチの押し込みフラグ(g_sw_chg)が立ったら、フルカラーLEDの点灯色を変更して、フラグを下げるようにしています。

CMT0の初期化

初期化関数のソースコードが以下です。ここではCMT0の初期化について説明したいと思います。CMT0に関してはユーザーズマニュアルのp.1085「28. コンペアマッチタイマ(CMT)」に記述があります。

init_rx631.c:初期化関数のソースコード

#include "init_rx631.h"
#include "iodefine.h"

/*** Function Declaration ***/
static void init_clock(void);
static void init_IO(void);
static void init_cmt0(void);

/*---- RX631 Initialization ----*/
void init_rx631(void){
    SYSTEM.PRCR.WORD = 0xA503;  // プロテクト解除

    // クロックの初期化
    init_clock();

    // I/O ポートの初期化
    init_IO();

    // CMT0 の初期化
    init_cmt0();

    SYSTEM.PRCR.WORD = 0xA500;  // 再プロテクト
}

~略~

/*---- CMT0 初期化 ----*/
void init_cmt0(void){

    MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

    CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
    CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

    IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
    IEN(CMT0,CMI0) = 1;  // 割り込み許可

    CMT.CMSTR0.BIT.STR0 = 1; // CMT0 開始
}

モジュールストップ機能の解除

MSTP(CMT0) = 0; // CMT0のモジュールストップを解除

モジュールストップ機能によって CMT0 が停止しているため、モジュールストップ状態を解除する必要があります。モジュールストップ機能の説明についてはユーザーズマニュアルのp.297「11. 消費電力低減機能」にあります。マクロが用意されていますので、MSTP(CMT0)=0 でモジュールストップを解除します。

CMT0の設定

CMT0.CMCR.WORD = 0x00C1; // 割り込み許可とPCLKを32分周
CMT0.CMCOR = 37499;      // 割り込み周期を24msに設定

CMCRレジスタを設定することで、割り込み許可とPCLK (PCLKB)を32分周しています。

CMCORレジスタには割り込みが入るカウント数を設定します。PCLKB:50MHz、分周比:32、割り込み周期を24ms としたいので、CMCORレジスタの値は以下のようになります。

\displaystyle{
{\rm CMT0.CMCOR} = (50\times10^6) / 32 \times 0.024 - 1 = 37499
}

注意:CMCRレジスタを設定する際は、7ビット目の説明に「書く場合、“1”としてください」と記述がありますので、WORD単位で設定する必要があります。ビット単位で設定すると、レジスタが書き換わりません。

割り込みコントローラの設定

IPR(CMT0,CMI0) = 10; // 優先レベルを10に設定
IEN(CMT0,CMI0) = 1;  // 割り込み許可

割り込みコントローラについてはユーザーズマニュアルのp.361「15. 割り込みコントローラ(ICUb)」に説明があります。割り込み要求を許可するために、「15.2.2 割り込み要求許可レジスタ m(IERm)」を設定して、割り込み優先度を設定するために、「15.2.3 割り込み要因プライオリティレジスタ n(IPRn)」を設定します。これらについてもマクロが用意されていますので、

  • IEN(<割り込み要求発生元>,<割り込みの名称>) = 1
  • IPR(<割り込み要求発生元>,<割り込みの名称>) = 10

で割り込み許可と優先度の設定をします。

最後に、CMSTR0レジスタのSTR0ビットに"1"を設定して、「28.2.4 コンペアマッチタイマカウンタ(CMCNT)」をカウントアップさせます。CMCNTとCMCORが一致することで割り込みが発生します。

割り込み関数の登録

割り込み関数のソースコードは以下です。実処理は別の関数(SW_status_check)で行っていますが、割り込み関数の登録はこのソースコードで行っています。

interrupt.c:割り込み関数のソースコード

#include "led_and_sw.h"
// 関数のセクションを割り込み関数の領域に変更
#pragma section P PIntPRG 

/*** 関数宣言 ***/
static void CMT0_CMI0(void);  // CMT0 CMI0

// CMT0 CMI0
// スイッチの状態を確認する割り込み関数
#pragma interrupt (CMT0_CMI0(vect=28)) // CMT0_CMI0を割り込み関数に登録
void CMT0_CMI0(void){
    SW_status_check();
}

#pragma section P PIntPRG という記述は、この記述以下の関数を割り込み関数のメモリ領域に置くというものです。

#pragma interrupt (CMT0_CMI0(vect=28))という記述は、指定した関数を割り込みインターフェースに登録します。vect=<割り込み要因に対応したベクタ番号>で、割り込み要因を指定します。割り込みベクタテーブルに関してはユーザーズマニュアルのpp.386-393に記述があります。

補足#pragma interrupt で宣言した関数内に直接実処理を記述したほうが、処理時間は短くなりますが、可読性がよくなると思って実処理は別の関数に記述しています。少しでも処理を速くしたいひとは、直接記述してもいいと思います。

注意:このまま、ビルドをすると、ベクタ番号28が複数回定義されているとして、エラーが生じます。generate/vect.hに以下のような記述があるので、コメントアウトする必要があります。

// CMT0 CMI0
#pragma interrupt (Excep_CMT0_CMI0(vect=28))
void Excep_CMT0_CMI0(void);

割り込み関数の内容について

led_and_sw.c:LEDとスイッチに関するソースコード

#include "define_wakaba.h"
#include "led_and_sw.h"

/*** Global variables ***/
volatile unsigned char g_sw_chg = 0;  // スイッチの押し込みフラグ

// フルカラーLEDの点灯
void Full_color_LED(char color){
    switch(color){
        case OFF:
            LED_RED   = LED_OFF;
~~略~~

// 割り込み関数の実処理
// スイッチの状態を確認をする
void SW_status_check(void){
    // 前のスイッチの状態
    static unsigned char pre_sw_status = SW_OFF;

    // スイッチが押し込まれたとき
    if(PUSH_SW==SW_ON && pre_sw_status==SW_OFF){
        g_sw_chg = 1;  // フラグを上げる
    }
    // 前のスイッチの状態に現在の状態を代入
    pre_sw_status = PUSH_SW;
}

割り込み関数の実処理(SW_status_check)では、24ms 前のスイッチの状態をstatic変数(pre_sw_status)で保持して、

  • 現在のスイッチの状態(PUSH_SW) = ON
  • 24ms前のスイッチの状態(pre_sw_status) = OFF

のときに、スイッチの押し込みフラグ(g_sw_chg)を立てます。

最後にpre_sw_statusに現在の状態を代入して、処理を終了します。

補足グローバル変数g_sw_chg)に型修飾子volatileを付けているのは、割り込み関数によるグローバル変数の変更が必ず反映されるようにするためです。「A.1.4 通常時と割り込み時に使用する変数を定義する」によると、コンパイラによってメモリアクセスが省略されることがあるので、割り込み関数がメモリのデータを書き換えても、メイン関数ではメモリにあるデータを取りにいかないことが起きるそうです。

補足:PSWについて

CMT0の初期化では割り込み許可を以下のようにしました。

  • CMCRレジスタのCMIEビットを"1"として、コンペアマッチの割り込み許可
  • IER03.IEN4ビットを”1”として、CMT0に対してCPUへの割り込み要求を許可

最後にCPU自体の割り込み許可が必要です。割り込みを許可するには、ユーザーズマニュアル p.135「2.2.2.4 プロセッサステータスワード(PSW)」の割り込み許可ビットを"1"にしないといけません。しかし、これについては generate/resetprg.c の中にあるスタートアッププログラム PowerON_Reset_PC で設定がされています。スタートアッププログラム中の CC-RXの組み込み関数 set_psw(PSW_init)PSW の割り込み許可ビットを"1"にしています。もし、割り込みが発生しない場合は、PSWを確認してみてもいいかもしれません。

おわりに

プログラムを書き込んだところ、プッシュスイッチを押すとLEDの点灯色が変わるようになりました。 次回はスピーカーから音を鳴らしてみたいと思います。

参考文献

お問い合わせフォーム プライバシーポリシー

© 2021 Setoti All rights reserved.