以前投稿した以下の記事のまとめ記事を作成しました。
完成品がこちら

完成したパチスロ実機データの収集機がこちらです。
このような、実機のゲームデータを保持しつつ、データが更新される度にPCへデータを送信する機器を開発します。

そんなに難しい仕組みじゃないのできっと理解できますよ
データ収集機の仕組み
データ収集機の仕組みを説明します。
一般的なデータカウンターは、専用のハーネスを介して実機の中にある『データ出力用の基板』と接続されています。
一方、本記事ではPCとデータ収集機(Arduino)とを繋ぎ、データ収集機からデータ出力用基板に接続することにします。
使用実機・ユニットは以下の通りです。
実機 | パチスロ 新世紀エヴァンゲリオン 〜まごころを、君に〜 |
Arduino | Arduino Nano Every |
PC | Windows11 64bit |
ソフトウェア開発環境 | Visual Studio Code |

データ出力用基板の内部回路は単純な電磁リレー回路になっていて、例えばメダルが1枚入力されるとINとGNDを繋ぐ電磁リレーが1度「カチッ」とON状態になり、すぐにOFF状態に戻ります。
ON状態の継続は、だいたい50msくらいです。


このデータ出力用基板とデータ収集機の接続にプルアップ回路を追加します。
これで、電磁リレーがONするとArduinoへの入力は0Vに、電磁リレーがOFFするとArduinoへの入力は5Vになります。これによって、電磁リレーのON/OFF状態をArduinoへ伝えることができます。

ちなみに、こちらがデータ出力用基板のピン配置です。
名前は勝手に付けたものなのでご了承ください。
IN | メダルの入力 |
OUT | メダルの払い出し |
RB | レギュラーボーナス発生フラグ |
BB | ビッグボーナス発生フラグ |
GND | グラウンド(=0V) |
これに対するArduino側とのピン対応は下表のようにしています。
データ出力用基板 | ピンNo. | データ収集機 | ピンNo. | |
---|---|---|---|---|
IN | 1 | ⇒ | D6 | 24 |
OUT | 2 | ⇒ | D5 | 23 |
RB | 3 | ⇒ | D4 | 22 |
BB | 4 | ⇒ | D3 | 21 |

データ収集機には、電磁リレーのON/OFFによって矩形波が入力されます。例えば、3枚掛けの機種であればレバーオンとともにINのリレーが「カチッ、カチッ、カチッ」と3回ONします。
INからの入力信号の場合は、1回の立ち下がりエッジが『1枚減った』ことを意味しており、これがOUTからの入力信号ならば『1枚増えた』ことを、RBやBBからの入力信号ならば『ボーナス中である』ことを意味します。
また、INの入力信号については、最初の立ち下がりだけをカウントするとゲーム数をカウントすることができます。

さらに、ボーナスフラグのBBとRBについては、ボーナス図柄を揃えたら立ち下がり↓、ボーナスが終了したら戻ります↑。

本機は、ビッグボーナス中にBB(上から3番目)がONになるとともに、RB(上から4番目)もONになります。
そして、一定枚数が払い出されるとRBが一瞬OFFに戻ります(動画参照)。
どうやらデータ出力用基板の仕様のようなのですが、「ビッグボーナス中はRBの入力信号を無視する」などの対策が必要になりそうです。
IN、OUT、RB、BBのチャタリング無視時間


データ出力用基板に載っている電磁リレーは機械式接点ですので、当然チャタリングが発生します。
例えば、レバーオン時のD6(IN)の波形は図のように半周期50 msの矩形波になっています。
見た目には普通に見えますが、拡大するとかなりチャタリングしていることがわかります。チャタリングが続く時間は350 us(=0.35 ms)くらいなのですが、立ち下がり↓の時だけでなく戻り↑の時にもチャタリングは発生しますので、こちらも無視する必要があります。
そのため、立ち下がり↓が発生したら、次の立ち下がり↓が発生する直前まで無視したいところです。
したがって、チャタリングを無視する時間は80 msとします。
ゲーム数のチャタリング無視時間
レバーオン時のINの入力信号の最初の立ち下がり↓の後、次のレバーオンまで割り込みを無視できれば、ゲーム数をカウントすることができます。
INの入力信号3回立ち下がり↓が落ち着くのが、約250 ms経過後なので2倍程度のマージンを持たせて500 msとします。
データ名 | 意味 | 算出方法 |
---|---|---|
game | 現在のゲーム数 | レバーオン時のINの3つの立ち下がり↓パルスのうち、最初だけカウント |
totalgame | 累計ゲーム数 | gameと同様 |
in | 入力枚数 | レバーオン時に発生するINの立ち下がり↓をカウント |
out | 出力枚数 | レバーオン時に発生するOUTの立ち下がり↓をカウント |
diff | 差枚数 | 出力枚数ー入力枚数 |
rb | レギュラーボーナス回数 | RBの立ち下がり↓でレギュラーボーナス回数をカウント |
bb | ビッグボーナス回数 | BBの立ち下がり↓でビッグボーナス回数をカウント |
duringrb | レギュラーボーナス中フラグ | RBの立ち下がり↓でtrueにし、立ち上がり↑でfalse |
duringbb | ビッグボーナス中フラグ | BBの立ち下がり↓でtrueにし、立ち上がり↑でfalse |
PCへのデータ転送
データ収集機ーPC間の通信ボーレートは『115200 bit/s』とします。
転送するデータは、JSON形式のデータにして転送します。
1 2 3 4 5 6 7 8 9 10 11 |
{ "game":0, "totalgame":0, "in":0, "out":0, "diff":0, "rb":0, "bb":0, "duringrb":false, "duringbb":false } |
なお、ArduinoからのJSON形式のデータ転送についてはこちらをご参照ください。
PCには最新の情報を表示してほしいので、データ収集機からPCへのデータ転送のタイミングは、『内部データが更新されたとき』とします。
データ収集機への入力信号の変化を外部割込みに設定しておき、いざ割り込みが発生した際に割り込み処理で内部データを更新した後、PCへデータを転送します。
ソースコード
データ収集機のプログラムを作成したので、プログラムの解説をしていきます。なお、Visual Studio CodeでのArduino開発環境の構築の仕方については、こちらの記事をご参照ください。
ファイル名 | 概要 | 詳細 |
---|---|---|
Pachislot_DataCollector.ino | Arduinoスケッチ | データ収集機(Arduino)のメインとなるスケッチファイルです。 他のモジュールを操作するコントローラとしての役割を担っています。 |
Interrupt.cpp | 割込み関連モジュール | データ収集機(Arduino)への入力信号(INやOUTなど)を検知して割込み処理を発生させる役割を担っています。 ただし、割込み後の処理はコントローラに任せます。 |
Port.cpp | ポート関連モジュール | データ収集機(Arduino)への入力信号のポート設定を行います。 |
Serial_Com.cpp | シリアル通信関連モジュール | PCとのシリアル通信のボーレート設定やJSON形式データのエンコード処理を行います。 |
DataManager.cpp | データ管理関連モジュール | データ収集機(Arduino)内で保持しておくデータを一括管理します。 |
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 |
#include "inc/Port.h" #include "inc/Interrupt.h" #include "inc/Serial_Com.h" #include "inc/DataManager.h" // ======================================================= // ローカル関数 // ======================================================= static void update_game( void ); static void update_in( void ); static void update_out( void ); static void begin_rb( void ); static void end_rb( void ); static void begin_bb( void ); static void end_bb( void ); // ======================================================= // 割り込み発生時のコールバック関数 // ======================================================= static INTR_CALLBACK m_IntrPtr[] = { update_game, update_in, update_out, begin_rb, end_rb, begin_bb, end_bb }; void setup( void ) { Port_Init( ); // ポートを初期化する Intr_Init( m_IntrPtr ); // 割り込み機能を初期化する Serial_Init( ); // シリアル通信を初期化する Data_Init( ); // データ管理を初期化する } void loop( void ) { // 何もしない } 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( ); // 他の割り込みを許可する } } 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( ); // 他の割り込みを許可する } static void update_out( void ) { ulong32 l_CurrentOUT; noInterrupts( ); // 他の割り込みを禁止する l_CurrentOUT = Data_GetOUT( ); // 現在のOUT枚数を取得する l_CurrentOUT++; // 現在のOUT枚数に+1する Data_SetOUT( l_CurrentOUT ); // OUT枚数を更新する Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } 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( ); // 他の割り込みを許可する } } static void end_rb( void ) { if ( Data_GetDuringBB( ) == false ) { noInterrupts( ); // 他の割り込みを禁止する Data_SetDuringRB( false ); // RB中フラグを下ろす Data_SetGame( 0U ); // ゲーム数を0にリセットする Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } } 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( ); // 他の割り込みを許可する } static void end_bb( void ) { noInterrupts( ); // 他の割り込みを禁止する Data_SetDuringBB( false ); // BB中フラグを下ろす Data_SetGame( 0U ); // ゲーム数を0にリセットする Serial_Send( &( Data_GetAllData( ) ) ); // すべてのゲーム情報をPCへ送信する interrupts( ); // 他の割り込みを許可する } |
Interrupt.cpp
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 |
#include "inc/Interrupt.h" // ======================================================= // ローカル変数 // ======================================================= static INTR_CALLBACK *m_Func; // 関数ポインタ配列 static bool m_IsInRegularBonus; static bool m_IsInBigBonus; // ======================================================= // ローカル関数 // ======================================================= static bool allow_intrrput( ulong32 p_WaitTime, ulong32 *p_PrevTime ); static void in_intr_occur( void ); static void out_intr_occur( void ); static void rb_intr_occur( void ); static void bb_intr_occur( void ); // 初期化 void Intr_Init( INTR_CALLBACK *p_Func ) { m_Func = p_Func; // 関数ポインタ配列のアドレスを受け取る m_IsInRegularBonus = false; // レギュラーボーナス中フラグをfalseに設定する m_IsInBigBonus = false; // ビッグボーナス中フラグをfalseに設定する // 3番~6番のピンを外部割込みに設定する // INとOUTの割り込み発生は立ち下がりエッジ(FALLING)発生時とする // RBとBBの割り込み発生は両エッジ(CHANGE)とする 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 ); } // 前回割り込みから指定時間経過したかどうかを判定 static bool allow_intrrput( ulong32 p_WaitTime, ulong32 *p_PrevTime ) { ulong32 l_Interval; bool l_Allow; l_Interval = millis( ) - *p_PrevTime; // 前回の割込みからの経過時間を計算する if ( l_Interval >= p_WaitTime ) // 割込み待ち時間を超えていたら { l_Allow = true; *p_PrevTime = millis( ); // 前回時間を更新しておく } else { l_Allow = false; } return l_Allow; } // INの入力信号の立ち下がりで割り込み処理 static void in_intr_occur( void ) { static ulong32 l_INPrevtime = 0U; // 前回時間を初期化する static ulong32 l_GamePrevtime = 0U; // 前回時間を初期化する if ( allow_intrrput( INTR_WAIT, &l_INPrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 1 ]( ); // Func[1]:update_in()をコールバックする } if ( allow_intrrput( GAMECOUNT_WAIT, &l_GamePrevtime ) == true ) // 前回の割り込みから時間が十分経過していたら { m_Func[ 0 ]( ); // Func[0]:update_game()をコールバックする } } // OUTの入力信号の立ち下がりで割り込み処理 static void out_intr_occur( void ) { static ulong32 l_OUTPrevtime = 0U; if ( allow_intrrput( INTR_WAIT, &l_OUTPrevtime ) == true ) { m_Func[ 2 ]( ); // Func[2]:update_out()をコールバックする } } // RBの入力信号の両エッジで割り込み処理 static void rb_intr_occur( void ) { static ulong32 l_RBPrevtime = 0U; if ( allow_intrrput( INTR_WAIT_B, &l_RBPrevtime ) == true ) { if ( m_IsInRegularBonus == false ) // レギュラーボーナス中じゃないなら { m_Func[ 3 ]( ); // Func[3]:begin_rb()をコールバックする m_IsInRegularBonus = true; // レギュラーボーナス中フラグを立てる } else // レギュラーボーナス中なら { m_Func[ 4 ]( ); // Func[4]:end_rb()をコールバックする m_IsInRegularBonus = false; // レギュラーボーナス中フラグを下す } } } // BBの入力信号の両エッジで割り込み処理 static void bb_intr_occur( void ) { static ulong32 l_BBPrevtime = 0U; if ( allow_intrrput( INTR_WAIT_B, &l_BBPrevtime ) == true ) { if ( m_IsInBigBonus == false ) // レギュラーボーナス中じゃないなら { m_Func[ 5 ]( ); // Func[5]:begin_bb()をコールバックする m_IsInBigBonus = true; // ビッグボーナス中フラグを立てる } else // ビッグボーナス中なら { m_Func[ 6 ]( ); // Func[6]:end_bb()をコールバックする m_IsInBigBonus = false; // ビッグボーナス中フラグを下す } } } |
Port.cpp
1 2 3 4 5 6 7 8 9 |
#include "inc/Port.h" void Port_Init( void ) { pinMode( IN_PIN, INPUT ); // 6番ピンを"IN"信号の入力モードに設定する pinMode( OUT_PIN, INPUT ); // 5番ピンを"OUT"信号の入力モードに設定する pinMode( RB_PIN, INPUT ); // 4番ピンを"RB"信号の入力モードに設定する pinMode( BB_PIN, INPUT ); // 3番ピンを"BB"信号の入力モードに設定する } |
Serial_Comm.cpp
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 |
#include "inc/Serial_Com.h" // 初期化 void Serial_Init( void ) { Serial.begin( 115200 ); // PCとのシリアル通信ボーレートを115200bpsに設定 } // PCへデータ転送 void Serial_Send( GAME_INFO *p_GameInfo ) { JSONVar l_Jobj; // JSON形式のオブジェクトを宣言する String l_SendStrMsg; l_Jobj[ "game" ] = p_GameInfo->Game; // 現在のゲーム数をセットする l_Jobj[ "totalgame" ] = p_GameInfo->TotalGame; // 累計ゲーム数をセットする l_Jobj[ "in" ] = p_GameInfo->IN; // 入力枚数をセットする l_Jobj[ "out" ] = p_GameInfo->OUT; // 出力枚数をセットする l_Jobj[ "diff" ] = p_GameInfo->Diff; // 差枚数をセットする l_Jobj[ "rb" ] = p_GameInfo->RB; // レギュラーボーナス回数をセットする l_Jobj[ "bb" ] = p_GameInfo->BB; // ビッグボーナス回数をセットする l_Jobj[ "duringrb" ] = p_GameInfo->DuringRB; // レギュラーボーナス中フラグをセットする l_Jobj[ "duringbb" ] = p_GameInfo->DuringBB; // ビッグボーナス中フラグをセットする l_SendStrMsg = JSON.stringify( l_Jobj ); // JSON形式をString型に変換する Serial.println( l_SendStrMsg ); // String型でメッセージを送る } |
DataManager.cpp
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 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
#include "inc/DataManager.h" // ======================================================= // 静的変数 // ======================================================= static volatile ulong32 m_Game; static volatile ulong32 m_TotalGame; static volatile ulong32 m_IN; static volatile ulong32 m_OUT; static volatile ulong32 m_RB; static volatile ulong32 m_BB; static volatile bool m_DuringRB; static volatile bool m_DuringBB; // 初期化 void Data_Init( void ) { m_Game = 0U; m_TotalGame = 0U; m_IN = 0U; m_OUT = 0U; m_RB = 0U; m_BB = 0U; m_DuringRB = false; m_DuringBB = false; } // プレイデータエンティティクラスにデータをセットして渡す GAME_INFO Data_GetAllData( void ) { GAME_INFO l_DataInfo; l_DataInfo.Game = m_Game; l_DataInfo.TotalGame = m_TotalGame; l_DataInfo.IN = m_IN; l_DataInfo.OUT = m_OUT; l_DataInfo.Diff = ( slong32 )m_OUT - ( slong32 )m_IN; l_DataInfo.RB = m_RB; l_DataInfo.BB = m_BB; l_DataInfo.DuringRB = m_DuringRB; l_DataInfo.DuringBB = m_DuringBB; return l_DataInfo; } // ゲーム数取得 ulong32 Data_GetGame( void ) { return m_Game; } // 累計ゲーム数取得 ulong32 Data_GetTotalGame( void ) { return m_TotalGame; } // 入力枚数取得 ulong32 Data_GetIN( void ) { return m_IN; } // 出力枚数取得 ulong32 Data_GetOUT( void ) { return m_OUT; } // レギュラーボーナス回数取得 ulong32 Data_GetRB( void ) { return m_RB; } // ビッグボーナス回数取得 ulong32 Data_GetBB( void ) { return m_BB; } // レギュラーボーナス中フラグ取得 bool Data_GetDuringRB( void ) { return m_DuringRB; } // ビッグボーナス中フラグ取得 bool Data_GetDuringBB( void ) { return m_DuringBB; } // ボーナス中かどうかを取得 bool Data_GetDuringBonus( void ) { bool l_IsBonus; if ( m_DuringRB == true || m_DuringBB == true ) // BB中またはRB中であれば { l_IsBonus = true; // ボーナス中フラグを立てる } else // ボーナス中でなければ { l_IsBonus = false; // ボーナス中フラグを下ろす } return l_IsBonus; } // 現在のゲーム数をセット void Data_SetGame( ulong32 p_Game ) { if ( p_Game >= 0U ) { m_Game = p_Game; } } // 累計ゲーム数をセット void Data_SetTotalGame( ulong32 p_TotalGame ) { if ( p_TotalGame >= 0U ) { m_TotalGame = p_TotalGame; } } // 入力枚数をセット void Data_SetIN( ulong32 p_IN ) { if ( p_IN >= 0U ) { m_IN = p_IN; } } // 出力枚数をセット void Data_SetOUT( ulong32 p_OUT ) { if ( p_OUT >= 0U ) { m_OUT = p_OUT; } } // レギュラーボーナス回数をセット void Data_SetRB( ulong32 p_RB ) { if ( p_RB >= 0U ) { m_RB = p_RB; } } // ビッグボーナス回数をセット void Data_SetBB( ulong32 p_BB ) { if ( p_BB >= 0U ) { m_BB = p_BB; } } // レギュラーボーナス中フラグをセット void Data_SetDuringRB( bool p_DuringRB ) { m_DuringRB = p_DuringRB; } // ビッグボーナス中フラグをセット void Data_SetDuringBB( bool p_DuringBB ) { m_DuringBB = p_DuringBB; } |
ソースコードはこちらに置いています。