まとめ記事のご案内 [2024.11.20]
こちらの記事に前編と後編の記事内容をまとめましたので、こちらをおすすめします。
前回の投稿記事で、パチスロの実機に接続したArduinoからPCへ実機データを送信するプログラムを作りました。

前回作ったArduinoのプログラムはIN信号が来れば現在の入力枚数を、OUT信号が来れば現在の出力枚数を文字列として送信するといったものでした。
しかし、データカウンターアプリにデータを送信するなら、入出力枚数やボーナス回数といった情報を一括送信した方が良さそうです。
他にも色々変更を加えて、このプログラムをブラッシュアップしたいと思います。
変更点
パチスロ実機のデータ送信基板のIN信号からは、以下のようなパルスが入力されます。
3枚掛けの機種だと1回のレバーオンで3枚減るので、Lowへの立下りが3回発生します。
レバーオン時には必ずこのような波形になるので、最初のLowだけをカウントして残り2回を無視すればゲーム数を数えることができそうです。

以下がIN信号の割込み発生時のアクティビティ図です。
基本は何もしないのですが、IN信号がLowになると割り込みが発生します。
このときに、前回の処理からTIN時間経過していれば入力枚数を更新し、前回の処理からTGAME時間経過していればゲーム数を更新します。

割り込み発生時のコールバック関数
Interrupt.cppで割り込みが発生したときにPachislot_DataCollector.ino側で処理を行いたいため、コールバック関数のポインタ配列を宣言します。
setup()
Intr_Init()の引数にコールバック関数のポインタ配列を渡します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
// ======================================================= // 割り込み発生時のコールバック関数 // ======================================================= static INTR_CALLBACK m_IntrPtr[] = { update_game, update_in, update_out, begin_rb, end_rb, begin_bb, end_bb }; /** * ======================================================= * @fn setup * @brief 初期化を行う * @date 2024-06-08 * ======================================================= */ void setup( void ) { Port_Init( ); // ポートを初期化する Intr_Init( m_IntrPtr ); // 割り込み機能を初期化する Serial_Init( ); // シリアル通信を初期化する Data_Init( ); // データ管理を初期化する } /** * ======================================================= * @fn loop * @brief 繰り返し処理を行う * @date 2024-06-26 * ======================================================= */ void loop( void ) { // 何もしない } |
allow_interrupt()
前回の割り込み発生時間からINTR_WAIT[ms]経過していたらtrue(未経過ならfalse)にする。
in_intr_occur()
前回のIN信号割り込み処理からINTR_WAIT[ms]経過していたらm_Func[1](つまりupdate_in())を呼び出す。
また、前回のIN信号割り込み処理からGAME_WAIT[ms]経過していたらm_Func[0](つまりupdate_game())を呼び出す。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
/** * ======================================================= * @fn Intr_Init * @brief 割り込み関係を初期化する * @date 2024-06-11 * ======================================================= */ void Intr_Init( INTR_CALLBACK *p_Func ) { m_Func = p_Func; // 関数ポインタ配列のアドレスを受け取る m_IsInRegularBonus = false; m_IsInBigBonus = false; // 3番~6番のピンを外部割込みに設定する // 割り込み発生は立ち下がりエッジ(FALLING)発生時とする attachInterrupt( digitalPinToInterrupt( IN_PIN ), in_intr_occur, FALLING ); attachInterrupt( digitalPinToInterrupt( OUT_PIN ), out_intr_occur, FALLING ); attachInterrupt( digitalPinToInterrupt( RB_PIN ), rb_intr_occur, CHANGE ); attachInterrupt( digitalPinToInterrupt( BB_PIN ), bb_intr_occur, CHANGE ); } /** * ======================================================= * @fn allow_interrput * @brief 前回の割り込みからの80ms経過判定 * @date 2024-06-25 * ======================================================= */ static bool allow_interrput( ulong32 p_WaitTime, ulong32 *p_PrevTime ) { ulong32 l_Interval; bool l_Allow; l_Interval = millis( ) - *p_PrevTime; // 前回の割込みからの経過時間を計算する if ( l_Interval >= p_WaitTime ) // 割込み待ち時間がINTR_WAIT[ms]を超えていたら { l_Allow = true; *p_PrevTime = millis( ); // 前回時間を更新しておく } else { l_Allow = false; } return l_Allow; } /** * ======================================================= * @fn in_intr_occur * @brief INの外部割込み * @date 2024-06-10 * ======================================================= */ static void in_intr_occur( void ) { static ulong32 l_INPrevtime = 0U; // 前回時間を初期化する static ulong32 l_GamePrevtime = 0U; if ( allow_interrput( INTR_WAIT, &l_INPrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 1 ]( ); // Func[1]:update_in()をコールバックする } if ( allow_interrput( GAMECOUNT_WAIT, &l_GamePrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 0 ]( ); // Func[0]:update_game()をコールバックする } } |
パチンコホールにあるデータカウンターは、ボーナス(ビッグおよびレギュラー)が始まると、ゲーム数は加算が止まりボーナス回数が増えます。
これを実現するために、内部に『ボーナス中』という状態を設け、ボーナス中になったらゲーム数のカウントを止めてボーナス回数を増やします。
一方、通常状態に戻ったらゲーム数をリセットして累計ゲーム数のカウントを再開します。
なお、『通常』⇒『ボーナス中』の状態遷移はBBおよびRB信号の立ち下がり(High⇒Low)、『ボーナス中』⇒『通常』の状態遷移はと立ち上がり(Low⇒High)で行います。
- 現在ゲーム数と累計ゲーム数のカウントを止める
- ビッグ、レギュラーのボーナス回数を+1する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 |
/** * ======================================================= * @fn update_game * @brief ゲーム数を更新する * @date 2024-06-26 * ======================================================= */ static void update_game( void ) { ulong32 l_CurrentGame; if ( Data_GetDuringBonus( ) == false ) // ボーナス中はゲーム数のカウントを止める { noInterrupts( ); // 他の割り込みを禁止する l_CurrentGame = Data_GetGame( ); // 現在のゲーム回数を取得する l_CurrentGame++; // 現在のゲーム回数に+1する Data_SetGame( l_CurrentGame ); // ゲーム回数を更新する l_CurrentGame = Data_GetTotalGame( ); // 現在の累計ゲーム回数を取得する l_CurrentGame++; // 現在の累計ゲーム回数に+1する Data_SetTotalGame( l_CurrentGame ); // 累計ゲーム回数を更新する Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn begin_rb * @brief RBが開始される * @date 2024-06-26 * ======================================================= */ static void begin_rb( void ) { ulong32 l_CurrentRB; if ( Data_GetDuringBB( ) == false ) { noInterrupts( ); // 他の割り込みを禁止する l_CurrentRB = Data_GetRB( ); // 現在のRB回数を取得する l_CurrentRB++; // 現在のRB回数に+1する Data_SetRB( l_CurrentRB ); // RB回数を更新する Data_SetDuringRB( true ); // RB中フラグを立てる Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn begin_bb * @brief BBが開始される * @date 2024-06-26 * ======================================================= */ static void begin_bb( void ) { ulong32 l_CurrentBB; noInterrupts( ); // 他の割り込みを禁止する l_CurrentBB = Data_GetBB( ); // 現在のBB回数を取得する l_CurrentBB++; // 現在のBB回数に+1する Data_SetBB( l_CurrentBB ); // BB回数を更新する Data_SetDuringBB( true ); // BB中フラグを立てる Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } |
- 現在のゲーム数を0にリセットする
- 累計ゲーム数のカウントを再開する
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
/** * ======================================================= * @fn update_game * @brief ゲーム数を更新する * @date 2024-06-26 * ======================================================= */ static void update_game( void ) { ulong32 l_CurrentGame; if ( Data_GetDuringBonus( ) == false ) // ボーナス中はゲーム数のカウントを止める { noInterrupts( ); // 他の割り込みを禁止する l_CurrentGame = Data_GetGame( ); // 現在のゲーム回数を取得する l_CurrentGame++; // 現在のゲーム回数に+1する Data_SetGame( l_CurrentGame ); // ゲーム回数を更新する l_CurrentGame = Data_GetTotalGame( ); // 現在の累計ゲーム回数を取得する l_CurrentGame++; // 現在の累計ゲーム回数に+1する Data_SetTotalGame( l_CurrentGame ); // 累計ゲーム回数を更新する Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn end_rb * @brief RBが終了される * @date 2024-06-26 * ======================================================= */ static void end_rb( void ) { if ( Data_GetDuringBB( ) == false ) { noInterrupts( ); // 他の割り込みを禁止する Data_SetDuringRB( false ); // RB中フラグを下ろす Data_SetGame( 0U ); // ゲーム数を0にリセットする Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn end_bb * @brief BBが終了される * @date 2024-06-26 * ======================================================= */ static void end_bb( void ) { noInterrupts( ); // 他の割り込みを禁止する Data_SetDuringBB( false ); // BB中フラグを下ろす Data_SetGame( 0U ); // ゲーム数を0にリセットする Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } |
PCへはボーレート9600bit/sのシリアル通信でデータを送信していましたが、正常に通信ができる範囲でもう少し速くしたいと思います。
今回は115200bit/sまでボーレートを上げます。

今回のプログラムは、入出力枚数はもちろんのこと、ボーナス回数、ゲーム数、ボーナス中フラグなどをまとめて送信します。
そのため、送信するデータはJSON形式で作成します。
データ内容は以下のようになります。
1 |
{"game":0, "totalgame":0, "in":0, "out":0, "diff":0, "rb":0, "bb":0, "duringrb":false, "duringbb":false} |
キー | 意味 | 使い方 |
---|---|---|
game | 現在のゲーム数 | データカウンター上に表示する |
totalgame | 累計ゲーム数 | データカウンター上に表示する |
in | 入力枚数 | データカウンター上に表示する |
out | 出力枚数 | データカウンター上に表示する |
diff | 差枚数 | スランプグラフとして表示する |
rb | レギュラーボーナス回数 | データカウンター上に表示する |
bb | ビッグボーナス回数 | データカウンター上に表示する |
duringrb | レギュラーボーナス中フラグ | ゲーム数加算の停止やゲーム数のゼロリセットを行う |
duringbb | ビッグボーナス中フラグ | ゲーム数加算の停止やゲーム数のゼロリセットを行う |
ビッグボーナス中はBB信号がLowになるのですが、このときRB信号も同じようにLowになります。
そして、何枚かメダルが払い出されると、RB信号が一瞬Highになってしまいます。
データ送信基板の仕様なのかわかりませんが、これではRB回数がカウントされてしまいます(下記動画参照)。
なお、上から3つ目のLEDがRB信号です。
なので、ビッグボーナス中はRB信号の入力を受け付けないように改良したいと思います。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
/** * ======================================================= * @fn begin_rb * @brief RBが開始される * @date 2024-06-26 * ======================================================= */ static void begin_rb( void ) { ulong32 l_CurrentRB; if ( Data_GetDuringBB( ) == false ) { noInterrupts( ); // 他の割り込みを禁止する l_CurrentRB = Data_GetRB( ); // 現在のRB回数を取得する l_CurrentRB++; // 現在のRB回数に+1する Data_SetRB( l_CurrentRB ); // RB回数を更新する Data_SetDuringRB( true ); // RB中フラグを立てる Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn end_rb * @brief RBが終了される * @date 2024-06-26 * ======================================================= */ static void end_rb( void ) { if ( Data_GetDuringBB( ) == false ) { noInterrupts( ); // 他の割り込みを禁止する Data_SetDuringRB( false ); // RB中フラグを下ろす Data_SetGame( 0U ); // ゲーム数を0にリセットする Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } |
各モジュールの責務を明確化します。
モジュール名 | 概要 | 責務 |
---|---|---|
Pachislot_DataCollector.ino | Arduinoのスケッチで最初に実行されるモジュール(ファイル) | モジュール間のデータ授受や呼び出しを制御する基幹コントローラー |
Interrupt.cpp | 割り込み関係モジュール(ファイル) | 割り込みの発生方法などの設定を行うモジュール |
Port.cpp | ポート関係モジュール(ファイル) | ポートの設定を行うモジュール |
Serial_Com.cpp | シリアル通信モジュール(ファイル) | シリアル通信の設定や送受信データを変換するモジュール |
DataManager.cpp | データ管理モジュール(ファイル) | 内部で持つデータを管理するモジュール |
特に着目すべきは、割り込み発生時の処理をInterrupt.cppではなくPachislot_DataCollector.inoで行うという点です。
Interrupt.cppはあくまで割り込みを発生させる役割であって、その後の処理はPachislot_DataCollector.inoで請け負います。
そのため、Interrupt.cppで割り込みが発生したらPachislot_DataCollector.inoの関数を呼んでもらうよう『コールバック関数のポインタ配列』を渡します。
これにより、Interrupt.cppで割り込みが発生すると、コールバックによりPachislot_DataCollector.inoの関数が呼び出され、そこで割り込み発生後の処理を行うことができます(下図参照)。

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 |
// ======================================================= // 割り込み発生時のコールバック関数 // ======================================================= static INTR_CALLBACK m_IntrPtr[] = { update_game, update_in, update_out, begin_rb, end_rb, begin_bb, end_bb }; /** * ======================================================= * @fn setup * @brief 初期化を行う * @date 2024-06-08 * ======================================================= */ void setup( void ) { Port_Init( ); // ポートを初期化する Intr_Init( m_IntrPtr ); // 割り込み機能を初期化する Serial_Init( ); // シリアル通信を初期化する Data_Init( ); // データ管理を初期化する } /** * ======================================================= * @fn update_game * @brief ゲーム数を更新する * @date 2024-06-26 * ======================================================= */ static void update_game( void ) { ulong32 l_CurrentGame; if ( Data_GetDuringBonus( ) == false ) // ボーナス中はゲーム数のカウントを止める { noInterrupts( ); // 他の割り込みを禁止する l_CurrentGame = Data_GetGame( ); // 現在のゲーム回数を取得する l_CurrentGame++; // 現在のゲーム回数に+1する Data_SetGame( l_CurrentGame ); // ゲーム回数を更新する l_CurrentGame = Data_GetTotalGame( ); // 現在の累計ゲーム回数を取得する l_CurrentGame++; // 現在の累計ゲーム回数に+1する Data_SetTotalGame( l_CurrentGame ); // 累計ゲーム回数を更新する Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } /** * ======================================================= * @fn update_in * @brief INを更新する * @date 2024-06-25 * ======================================================= */ static void update_in( void ) { ulong32 l_CurrentIN; noInterrupts( ); // 他の割り込みを禁止する l_CurrentIN = Data_GetIN( ); // 現在のIN枚数を取得する l_CurrentIN++; // 現在のIN枚数に+1する Data_SetIN( l_CurrentIN ); // IN枚数を更新する Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
// ======================================================= // ローカル変数 // ======================================================= static INTR_CALLBACK *m_Func; // 関数ポインタ配列 static bool m_IsInRegularBonus; static bool m_IsInBigBonus; /** * ======================================================= * @fn Intr_Init * @brief 割り込み関係を初期化する * @date 2024-06-11 * ======================================================= */ void Intr_Init( INTR_CALLBACK *p_Func ) { m_Func = p_Func; // 関数ポインタ配列のアドレスを受け取る m_IsInRegularBonus = false; m_IsInBigBonus = false; // 3番~6番のピンを外部割込みに設定する // 割り込み発生は立ち下がりエッジ(FALLING)発生時とする attachInterrupt( digitalPinToInterrupt( IN_PIN ), in_intr_occur, FALLING ); attachInterrupt( digitalPinToInterrupt( OUT_PIN ), out_intr_occur, FALLING ); attachInterrupt( digitalPinToInterrupt( RB_PIN ), rb_intr_occur, CHANGE ); attachInterrupt( digitalPinToInterrupt( BB_PIN ), bb_intr_occur, CHANGE ); } /** * ======================================================= * @fn in_intr_occur * @brief INの外部割込み * @date 2024-06-10 * ======================================================= */ static void in_intr_occur( void ) { static ulong32 l_INPrevtime = 0U; // 前回時間を初期化する static ulong32 l_GamePrevtime = 0U; if ( allow_interrput( INTR_WAIT, &l_INPrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 1 ]( ); // Func[1]:update_in()をコールバックする } if ( allow_interrput( GAMECOUNT_WAIT, &l_GamePrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 0 ]( ); // Func[0]:update_game()をコールバックする } } |
ソースコード
今回作成したプログラムは以下のGitHubからフルでダウンロードできます。
動作確認
それでは、プログラムの動作確認を行います。