【オーメダル&フルボトル連動】プログライズライターをつくる
あけましておめでとうございます。今年も宜しくお願い申し上げます。
さて、今回は2020年最初の作品、『プログライズライター』をご紹介します。
作品概要
『ライター(書き込み機)』の名の通りで、プログライズキーに対してデータの書き込みを行うマシン(の、玩具)です。書き込みを行うために、作成するプログライズキーと同じ生物をモチーフとするオーメダル又はフルボトルを使用するというのが、今回の作品のポイントです。例えば『バッタ』のオーメダルを使用すると、書き込みによって『ライジングホッパー』として機能するプログライズキーとなり、『ウルフ』のフルボトルを使用すると、書き込みによって『シューティングウルフ』として機能するプログライズキーが出来上がります。
『ライター』とは言っているものの、あくまで玩具ということで、実際に全ての音声データや発光色のデータを毎回ライターからプログライズキーに書き込んでいるわけではありません。詳細は後述しますが、ライターからプログライズキーに書き込んでいるデータは、『このプログライズキーをどのプログライズキーとして機能させるか』を示すID情報(整数値)のみです。
つまり、(現時点で対応している)全てのプログライズキーの音声データや発光色のデータは、初めからプログライズキー側に全て格納されていて、ライターはどのプログライズキーにするかを選択しているだけ、ということになります。
そのため、このプログライズキーは単体で何にでもなり得る『マルチプログライズキー』と言えるものになっているのですが、ただボタンをポチポチ押して切り替えても面白くないな、と思いましたので、共通する生物モチーフの多いオーメダルと連動させることをまず思いつきました。
ただ、それだけだと対応できないプログライズキーがそこそこ存在しましたので(シューティングウルフ、スパーキングジラフ、ガトリングヘッジホッグなど)、それらをフォローするためにフルボトルにも対応させることにしました。
使用するアイテムに合わせて、プログライズキーも『TYPE: O MEDAL』と『TYPE: FULLBOTTLE』の二種類を用意しました。と言っても、この二つはラベルを貼り替えているだけで、実態としては一つしか作っていません。好きな方を選んで使用する形になります。
ハードウェア解説
まずはプログライズキー側の解説です。
ベースはDX版ならなんでも良かったのですが、基本黒で塗装するつもりだったので、ベース色がそれに近い灰色である『パンチングコング』をベースにしています。
内側のシールは剥がしてしまい、外側の塗装はMr.カラーのうすめ液を染み込ませた布で拭き取りました。クリアパーツ上の塗装なので、うっかり除光液とかで塗装を溶かしてしまうと、クリアパーツまで白く変色してしまうので注意です。
余談ですが、上記の塗装の剥がし方をを豆知識としてTwitterに投稿してみたところ、179件のいいねがついたので、「やってみたいけどやり方がわからない」という人が結構多かったのだなあという感じです。自分はガシャット改造のときにこのやり方を知りたかったのですが、なかなか見つけられなくて、結局いくつかのガシャットを犠牲にして自力でこのやり方に到達しました。お役に立ったようで何よりです。
そして『TYPE: O MEDAL』と『TYPE: FULLBOTTLE』の二つのラベルですが、作成にあたり、ZXライドウォッチの際に昭和ライダーのデザインを引き受けてくださった方に再びご協力頂きました。こちらからイメージをお伝えしたものを実際にデザインとして起こしてもらい、それを見ながら、双方のアイデアを取り入れながら最終的に仕上げて頂きました。いつもご協力頂きありがとうございます。
プログライズキーということで、”~ing”という名前のルールだけは最低限守った方が良いかとも思ったのですが、無理矢理そのフォーマットに押し込むとどの名前も何だかイマイチな感じで、また既に『アサルトウルフ』という例外も存在していることから、そこは拘らずに、最終的には素直に作品名を入れることで落ち着きました。上記の写真は試行錯誤の跡です。
プログライズキー自体は一つしか作っていないため、この二つのラベルは必要に応じて貼り替えて使用する形になりますが、簡単に貼り替えられるように、スマホ画面の保護フィルムでよく使われている自己吸着シートを利用しています。
100均で売っているもので充分です。プリンタで印刷したラベルをこのシートに貼ってからプログライズキーに貼り付けるようにすれば、しっかり張り付いてくれますし、剥がすときも綺麗に剥がすことができます。また、今回はやっていませんが、既存のプログライズキーのデザインのラベルを作れば、ベース色は黒になってしまいますが、完全にそのプログライズキーとして遊ぶことも可能になります。
シンボルの部分については、TYPE : O MEDALの場合は、オーメダルをそのままはめ込むことで、オーメダルが元になっている感(?)を出しています。ただ、メダルをそのままはめ込むのはちょっと厳しかったので、分解して絵柄のパーツのみをはめ込むようにしています。
一方、TYPE: FULLBOTTLEの場合は、フルボトルをそのままはめ込むようなことはもちろんできません。
どうしたものかと思ってWeb上を色々探していたところで、フルボトルのデザインを正確にイラストに起こされている画像を発見しました。これを是非使わせてもらいたいと思って製作者の方を探したところ、ブラジルにお住まいのデザイナー、markolios様が作成されたものとわかりました。そこで、利用についてご相談したところご快諾頂き、今回使用させて頂きました。ご協力大変感謝致します。歯車がデザインされていることで、すごくビルド感が出て、とても気に入っています。オーメダルとフルボトルで重複する生物(ゴリラ、トラなど)については、どちらも対応しているのですが、シンボル部分で違いが出てくるので面白いです。
続いて内部構造のお話です。まずはいつもの、基本の具材のご紹介からです。
最後のスピーカーですが、玩具に元々組み込まれているスピーカーと直径がほぼ一緒で、かつ、薄くて音が元々のものより良い(気がする)ので、最近多用しています。
それから、今回特徴的な具材は以下の2つです。
ホールセンサは『オーソライズ』ギミックのために今回使用してみました。試していませんが、リードスイッチでも代用できるかもしれません。
こういう部品の組み込みはガシャット、ライドウォッチとやってきましたが、ガシャットと同等、ライドウォッチと比べるとちょっと大変、という感じでした。スペース的にはかなり余裕がないです。
ガシャット、ライドウォッチからもはや伝統の『二つ並んだベルト連動スイッチ』ですが、いつもここの認識スイッチをディテクタスイッチなどで自作して調整に苦労していたのですが、今回は「玩具の基板を破壊して、そこだけそのまま使う」という作戦に出ました。おかげでここは調整不要でラクに作業できました。
今回はネオディケイド・ネオディエンドライドウォッチのときと同様、後からプログラムの修正や音声データの追加が発生するタイプだったので、SDカードのスロットやArduinoの書き込みコネクタにはアクセスしやすい形でまとめています。ついでに充電もここから行えるようにしています。このあたりだったり、あとホールセンサやLEDの位置がレイアウト上の制約になって、なかなか大変でした。
そして今回の書き込み機能実現のポイントになっているのが、ここから顔を出している赤外線受信モジュールです。クウガライドウォッチ、ZXライドウォッチに続いて三回連続での採用です。それだけ、使いこなせるようになると工作の幅が広がる部品と言えます。プログライズキーライター側の赤外線LEDの点滅に乗って送られてくる整数値を受信すると、その値に応じて再生する音声やLEDの発光色を切り替えるようになっています。
回路図としては、こんな感じになります。
続いてライター側です。
筐体は適当な大きさのケースを探してきて加工する、という手もありましたが、内部構造を考えると結局中は自作しないといけなかったので、オールプラ板&プラ棒で自作してみました。最後に黒く塗装しましたが、スプレー缶丸々一本使ってしまいました。黒いプラ板とかないのかしら。。。
今回のプログライズキー側もそうでしたが、今までは既にある玩具の中にいかに部品を詰め込むかというところで頑張っていたので、当初「今回は好きなサイズで作れるからスペース余裕でラクだな~」とか思っていたのですが、そんな過去の自分をグーで殴ってやりたいぐらいに大変でした。プラ板工作が初めてということもありましたが、寸法計算するのはめんどくさいしキレイにカットするのも大変、いざ組み合わせてみると部品は干渉するし、ネジとナットは飛んでいくし想定より全然足りなくて何度も買い足すし、プラ板の切り屑がリビングまで付いてきてしまって奥様に何してんだと問われたりもするし、とにかく大変でした。とりあえず、P板カッターとピンバイスは大活躍でした。
実際に筐体作成に使った部品も一応紹介しておきます。
あと、ネジは2mm x 10mmが基本で、要所要所で2mm x 6mm、2mm x 15mmを使っています。これらはホームセンターで大量に買い足しました。ヘビーに使わないのであれば全部接着剤で済ます、という手もあったのですが、今回は後々子供にも遊んでほしかったので、できるだけ頑丈に作るようにしました。
全体的なデザインは、オーメダルとフルボトルを使うというところが、何となくコインでジュースを買うのに対応するような感じがしたので、一般的な自販機っぽい雰囲気になるようにしたいと思いました。その上で、自分が小さい頃にギリギリオモチャ屋さんで見た記憶のある、ファミコンディスクシステムのディスクライター的な雰囲気も出したいなと思って、こんな形になりました。あんまり複雑なデザインはそもそも作れない、というのもありますけれど。
個人的に拘ったのは、このプログライズキーの挿入部の蓋です。内部構造を見えにくくするという効果はありますが、別になくても機能的には何も問題なく遊べます。ただ、先程のディスクシステム然り、自分が子供の頃にアホほど遊んでいたファミコン、スーファミ、64然り、何かしらカートリッジを差し込むものには蓋があった方が遊ぶときに絶対気持ち良い(?)と思ったので、プログライズキーの抜き差しに応じて自動で開閉する蓋を付けてみました。
機構的な設計は苦手なので、重力と磁石を使うとかもっと上手い方法はあったのかもしれませんが、とりあえずバネを使って試行錯誤して何とかしました。
続いて内部構造のお話です。使用した電子部品はこちらになります。
ライター側はスペースの制約があまりなかったので、Groveシステムをベースにプロトを作ってしまって、それをそのまま筐体に突っ込んでしまうことにしました。LEDバーは機能の本質には全く関係なくて、ただ進捗っぽいものを表示して書き込んでいる感を出すためだけのものですが、やっぱりあって良かったと思います。
Groveシステムにも赤外線LEDはあるのですが、使い方がよくわからなかったので、使い慣れたものを使っています。
ライター側の音声再生には、おなじみDFPlayerではなく、同じ会社が作っているMP3ボイスモジュールを今回初めて採用してみました。DFPlayerはモジュール単体は安いのですが、動かすのにマイクロSDカードが必要なので、意外とコストがかかります。特に、再生する音声データがそこまで大きくない場合は、確実にオーバースペックです。一方、こちらは音声データの容量は8MBとかなり控えめですが、効果音を少し鳴らしたいとかなら必要十分な容量ですので、SDカードを買い足さなくてよい分、コスト的にはお買い得です。ただ、サイズが意外と大きいので玩具への組み込みは意外と難しいかもしれないのと、あと開発環境によってはArduinoからプログラムで制御するときにちょっとハマるかもしれないので、意外と使いこなしが難しいかもしれません。プログラムについては、詳細は最後のソースコードの417~427行目のコメント文に記載していますが、曲の番号を指定してもその番号の曲が素直に再生されない、という現象が起こります。ただ、普通に使えている人もいるようなので、Mac特有の現象なのかもしれません。
挿入したプログライズキーの固定のために、プログライズホルダーを丸々埋め込んでいます。これのあるなしで、プログライズキーの挿し心地と安定感が段違いです。
さて、ライター側の肝になるのが、オーメダルとフルボトルの認識です。まずオーメダルの認識ですが、これを実現するために、
中古で買ったDXオースキャナーの基板を丸々そのまま突っ込んでいます。市販のRFIDリーダーの部品を買ってきて頑張る、という手もあったのかもしれませんが、それで確実にオーメダルをスキャンできる保証はありませんし、また電波を使用する関係上、それが日本の技適を通過したものかとかを気にするのも面倒だったので、玩具の基板をそのまま使うのが最も確実でラクかな、と思ってこうしました。
オースキャナーの基板は、メイン制御部と思われる基板と、メダル(RFID)スキャン用の基板の二つに分かれていて、ちゃんと解析すればスキャン用基板だけを使うこともできるのだと思いますが、今回はそこまで頑張る必要もないかなと思いましたので、スキャン用基板がメイン基板に送っている信号をArduinoにも流して、その信号をArduinoで解読することにしました。詳しくはソフトウェア解説の方で説明します。
一方フルボトルの方は、過去にトランスチームガン・カスタムという作品を作ったときに一度認識はやっていて、今回も認識用のスイッチとフルボトルの装填部分はトランスチームガンの部品をそのまま突っ込んでいます。トランスチームガン・カスタムの調子が悪くなっていたので、潔くトランスチームガン・カスタムを破壊して、部品をこちらに移植しました。
回路図としては、こんな感じになります。
ソフトウェア解説
だいぶ長くなってしまいましたが、最後、ソフトウェアの解説です。ソースコードの全文は、プログライズキー側とライター側、まとめて本記事の最後に掲載しています。
まずはプログライズキー側です。ソフト的に特筆すべきことは特にないのですが、とりあえずとにもかくにもプログライズキーの状態遷移がわかっていないと何もプログラムが書けないので、まずは挙動解析からです。ボタンは天面部をA、ドライバー連動部の上からB, Cとしています。
プログライズキーの状態は大きく「オーソライズ前」と「オーソライズ後」に分かれていて、それぞれの状態からスイッチの押し方によって、オーソライズ前だと武器用とフォースライザー用、オーソライズ後だとゼロワンドライバー用とショットライザー用に分岐していく形になります。
細かいところですが、プログラムの組み方としては、ガシャットとライドウォッチのときは結構厳密にやっていて、「今のボタンの押され方の組み合わせから次のボタンの押され方に変わるとき、今どういう状態か」を網羅的に調べて処理を分岐させていたのですが、今回は厳密に調べるのが面倒だったので、「今の状態で、今のボタンの押され方から次にどういうボタンの押され方をするか」という形で、玩具的に必要そうな部分だけを調べて記述するようにしています。そのため、厳密には玩具と異なる挙動になるところもあるかもしれませんが、普通に遊ぶ分には特に問題なさそうだったので、これでOKとしています。
あと、今回ちょっとハマったところとして、赤外線受信のところがあります。赤外線信号を「いつでも」受信できるようにしようと思うと、Arduinoのloop関数の中の処理を極力軽くしておかないと、Arduinoが赤外線信号の受信をチェックするインターバルが長くなってしまい、ライター側の赤外線LEDが発光してもそれをキャッチできなくなってしまいます。そのため、全体を「書き込み前」と「書き込み後」に分けて、「書き込み前」のときは赤外線信号を逃さずキャッチできるように極力looo処理を軽くして、メインの発光やら音声再生の処理は「書き込み後」にだけ行われるようにしています。
「書き込み後」から「書き込み前」の状態に戻すために、特別に”Initialize(初期化)”という操作を加えています。上部のボタンを5秒程長押しすると「書き込み前」の状態に戻って、再び赤外線信号を受信してプログライズキーの書き込みができるようになります。このイニシャライズの発動条件は結構悩んで、最初はライターにキーを差し込んだときにドライバー連動ボタンが同時に押されるなどして、書き込む前に自動的にイニシャライズが走るようにしようかと思ったのですが、それだとフォースライザーで遊ぶときに勝手にイニシャライズが走ってしまったりしたので、誤作動しないように長押し5秒という条件で発動するようにしました。
続いて、プログライズキーライター側です。こちらのメインは、「どうやってオーメダルをArduinoに認識させるか」というところです。
先に述べたとおり、オースキャナーの基板は、メイン制御用とメダルスキャン用の2つに分かれています。この間は5つの線で繋がれていて、GND(–), VDD(+), CLOCK, DATA, ABLENと記載されています。前2つは電源関連で良いとして、あとの3つからArduinoでどう情報を引き出すかです。
スキャン用の基板の方を見てみると水晶振動子の部品が付いているので、「スキャン用基板はメイン基板からはABLEN経由で指示を受けて、メダルを読み取ったらCLOCK信号にDATAを同期させる形でメイン基板に信号を送っているのでは」と推測しました。そして、何枚かのメダルをスキャンさせたときのCLOCKとDATAの01の変化を観察してみると、完全に理解したわけではありませんが、少なくともメダルを1枚読み込んだときには01の変化に以下のような法則性があることがわかりました。
今回は複数メダルを認識させてコンボ発動とかまではする必要はなかったので、ここまでの理解でプログラムを組んでみました。
続いてフルボトルの方の認識ですが、こちらは先に述べたとおり、以前にトランスチームガン・カスタムという作品を作ったときに、一度プログラムは作成しています。ただ、改めてトランスチームガン・カスタムで遊んでみると、フルボトルを全然認識してくれなくなっており、どうやらスイッチ部分の経年劣化に対して非常に弱い認識プログラムになっていたことがわかりました。
というわけで、認識ピンの認識アルゴリズムを全面的に見直して、一から組み直しました。結果、誤認識することはまだそこそこありますが、前よりはちゃんと認識してくれるようになりました。詳細は省略しますが、簡単に言うと「認識ピンのあるときは0と1の値がバタつくが、認識ピンのないときは0しか現れない」という、ある種当たり前の前提に則ってプログラムを書くようにしています。
最後に赤外線信号の送信ですが、プログライズキー側の受信失敗の可能性を減らすために5回連続の再送処理は追加しているものの、 基本はZXライドウォッチのときとほぼ変わっていませんので、詳しくはそちらをご参照ください。オーメダル or フルボトルを認識して得られたID(整数値)を送っているだけです。
念のため補足しておくと、書き込みの効果音が流れている間中ずっと赤外線信号を送り続けているわけではなく、実際に信号を送っているのは音楽が終わった直後の最後の一瞬だけです。LEDバーが示している進捗状況も、「それっぽさ」を出すためだけの、完全なフェイクです。まあ、あくまでオモチャということで。
まとめ
ということで、『プログライズキーライター』のご紹介でした。今回は「プログライズキーを作り出す工程」を作品化したので、「何かすごいプログライズキーそのもの」を期待されていた方は、申し訳ありません。全く新しいプログライズキーを創り出そうと思うと、必然的に音声データを作成しないといけなくなるのですが、自分はデータの編集は出来ても、ゼロから音声を作り出すことはできません。その制約の中で最大限楽しく遊べるものを目指した結果、今回はこういう形になりました。実際、長男も楽しそうに遊んでくれているので、まー良かったかなと思っています。
ちなみに、今回は作り込んでいませんが、レジェンドライダーのメダルやボトルを使ってプログライズキーを生成できるように機能拡張するのも面白いかもしれません。丁度オフィシャルにレジェンドライダープログライズキーも発表されたことですし。他にも、セルメダルを使ってゼツメライズキーを作ったり、というのもアリだと思います。今回、なかなか汎用性のある仕組みを作れたかな、と思っています。
というわけで、今年もこんな感じで適当に間を空けつつ続けられれば良いなあと思いますので、引き続き宜しくお願い致します。
ソースコード
最後に参考としてソースコードの全文を掲載しておきます。まずはプログライズキー側です。
////////// 基本定義 ////////////////////////////////////////////////////////////
#define SW_A_PIN 4
#define SW_B_PIN 5
#define SW_C_PIN 6
#define AUTHORIZE_WAITING_TIME 30000
#define AUTHORIZE_KEEPING_TIME 30000
#define ZEROONEDRIVER_CHANGING_TIME 10000
#define SHOTRISER_CHANGING_TIME 10000
// 3つのボタン入力の表現
#define N_BUTTON 3
#define ON LOW
#define OFF HIGH
const uint8_t OFF_OFF_OFF[] = {OFF, OFF, OFF};
const uint8_t OFF_OFF_ON[] = {OFF, OFF, ON};
const uint8_t OFF_ON_OFF[] = {OFF, ON, OFF};
const uint8_t OFF_ON_ON[] = {OFF, ON, ON};
const uint8_t ON_OFF_OFF[] = {ON, OFF, OFF};
const uint8_t ON_OFF_ON[] = {ON, OFF, ON};
const uint8_t ON_ON_OFF[] = {ON, ON, OFF};
const uint8_t ON_ON_ON[] = {ON, ON, ON};
uint8_t prev_sw[] = {OFF, OFF, OFF};
uint8_t sw[] = {OFF, OFF, OFF};
// 赤外線信号受信前と受信後で制御内容が大きく分かれる
boolean is_initialized = true;
#define INITIALIZE_TIME 5000
unsigned long initialize_start_time = 0;
// 状態の定義
#define STATE_SINGLE_A 1
#define STATE_SINGLE_B 2
#define STATE_WEAPON_READY 3
#define STATE_WEAPON 4
#define STATE_WEAPON_FINISH 5
#define STATE_FORCERISER_READY 6
#define STATE_FORCERISER 7
#define STATE_FORCERISER_FINISH_READY 8
#define STATE_FORCERISER_FINISH 9
#define STATE_AUTHORIZED_A 10
#define STATE_AUTHORIZED_B 11
#define STATE_ZEROONEDRIVER_READY 12
#define STATE_ZEROONEDRIVER 13
#define STATE_ZEROONEDRIVER_FINISH_READY 14
#define STATE_ZEROONEDRIVER_FINISH 15
#define STATE_SHOTRISER_READY 16
#define STATE_SHOTRISER 17
#define STATE_SHOTRISER_FINISH_READY 18
#define STATE_SHOTRISER_FINISH 19
uint8_t prev_state = STATE_SINGLE_A;
uint8_t state = STATE_SINGLE_A;
boolean is_authorize_ready = false;
unsigned long authorize_waiting_start_time = 0;
unsigned long authorize_keeping_start_time = 0;
unsigned long changed_time = 0;
uint8_t progrise_key_id = 0;
////////// IR信号の受信 ////////////////////////////////////////////////////////////
#define IR_RX_PIN 7
#define IR_ON LOW
#define IR_OFF HIGH
#define LEN_IR_DATA 8
// 使用する赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める
#define IR_LONG_DURATION_MICRO_SEC 600
////////// 音声処理 ////////////////////////////////////////////////////////////////
#include <SoftwareSerial.h>
#include <DFRobotDFPlayerMini.h>
#define SOUND_VOLUME_DEFAULT 20 // 0〜30 通常は23
// 共通音声
#define SOUND_AUTHORIZE 1
#define SOUND_ERRORRISE 2
#define SOUND_ZETSUMERISE 3 // まだ音声はないが、確保しておく
#define SOUND_INITIALIZE 4 // オリジナル音声
#define SOUND_SHOTRISER_FINISH_READY 5
// 固有音声
#define SOUND_ABILITY 1
#define SOUND_NAME_ABILITY 2
#define SOUND_PUN_NAME_DESCRIPTION 3
#define SOUND_NAME_DESCRIPTION 4
#define SOUND_NAME 5
#define SOUND_FINISH_START 6
#define SOUND_FINISH_LONG 7
#define SOUND_FINISH_SHORT 8
#define SOUND_FINISH_SHOTRISER 9
#define SOUND_OFFSET_HOPPER 5
#define SOUND_OFFSET_WOLF 14
#define SOUND_OFFSET_CHEETAH 23
#define SOUND_OFFSET_SCORPION 32
#define SOUND_OFFSET_FALCON 41
#define SOUND_OFFSET_SHARK 50
#define SOUND_OFFSET_KONG 59
#define SOUND_OFFSET_TIGER 68
#define SOUND_OFFSET_HORNET 77
#define SOUND_OFFSET_BEAR 86
#define SOUND_OFFSET_MAMMOTH 95
#define SOUND_OFFSET_HERCULES 104
#define SOUND_OFFSET_GIRAFFE 113
#define SOUND_OFFSET_HEDGEHOG 122
#define SOUND_OFFSET_BUFFALO 131
#define SOUND_OFFSET_SPIDER 140
#define SOUND_OFFSET_STAG 149
#define SOUND_OFFSET_PENGUIN 158
#define SOUND_OFFSET_WHALE 167
#define SOUND_OFFSET_LION 176
#define SOUND_OFFSET_PANDA 185
#define SOUND_OFFSET_KANGAROO 194
#define SOUND_OFFSET_HOPPER_2 203
// 無音時間の調整
#define SOUND_WAIT_MS_AUTHORIZE 700 // 1000 -> 800 -> 700
#define SOUND_WAIT_MS_WEAPON 3200 // 3000 -> 3200
#define SOUND_WAIT_MS_ZEROONEDRIVER 3000
#define SOUND_WAIT_MS_SHOTRISER 4700 // 5000 -> 4900 -> 4700
#define SOUND_WAIT_MS_FORCERISER 2800 // 3000 -> 2900 -> 2800
#define SOUND_WAIT_MS_WEAPON_FINISH 3140 // 3240 -> 3140
#define SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_1 800 // 1000 -> 900 -> 850 -> 800
#define SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_2 5580 // 5680 -> 5580
#define SOUND_WAIT_MS_SHOTRISER_FINISH_READY 1500 // 1000 -> 1300 -> 1500
#define SOUND_WAIT_MS_SHOTRISER_FINISH 800 // 1000 -> 900 -> 800
#define SOUND_WAIT_MS_FORCERISER_FINISH_1 600 // 1000 -> 800 -> 700 -> 600
#define SOUND_WAIT_MS_FORCERISER_FINISH_2 2440 // 2540 -> 2440
#define SOUND_WAITING_STATE_NONE 0
#define SOUND_WAITING_STATE_AUTHORISE 1
#define SOUND_WAITING_STATE_WEAPON 2
#define SOUND_WAITING_STATE_ZEROONEDRIVER 3
#define SOUND_WAITING_STATE_SHOTRISER 4
#define SOUND_WAITING_STATE_FORCERISER 5
#define SOUND_WAITING_STATE_WEAPON_FINISH 6
#define SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1 7
#define SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2 8
#define SOUND_WAITING_STATE_SHOTRISER_FINISH_READY 9
#define SOUND_WAITING_STATE_SHOTRISER_FINISH 10
#define SOUND_WAITING_STATE_FORCERISER_FINISH_1 11
#define SOUND_WAITING_STATE_FORCERISER_FINISH_2 12
unsigned long sound_wait_start_time = 0;
uint8_t sound_waiting_state = SOUND_WAITING_STATE_NONE;
uint8_t sound_offset = 0;
SoftwareSerial ss_mp3_player(2, 3); // RX, TX
DFRobotDFPlayerMini mp3_player;
void control_sound(uint8_t offset, unsigned long now_ms){
if(prev_state != state){ // 状態遷移直後の処理
switch(state){
case STATE_SINGLE_A:
if(prev_state == STATE_SINGLE_B){
mp3_player.playMp3Folder(offset + SOUND_NAME_ABILITY);
}else{
mp3_player.pause();
}
break;
case STATE_SINGLE_B:
if(progrise_key_id != 0){
mp3_player.playMp3Folder(offset + SOUND_ABILITY);
}else{
mp3_player.playMp3Folder(SOUND_ERRORRISE);
}
break;
case STATE_WEAPON_READY:
switch(prev_state){
case STATE_SINGLE_A:
case STATE_SINGLE_B:
mp3_player.pause();
break;
default:
;
}
break;
case STATE_WEAPON:
switch(prev_state){
case STATE_SINGLE_A:
case STATE_SINGLE_B:
case STATE_WEAPON_READY:
sound_waiting_state = SOUND_WAITING_STATE_WEAPON;
sound_wait_start_time = now_ms;
break;
default:
;
}
break;
case STATE_WEAPON_FINISH:
mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
sound_waiting_state = SOUND_WAITING_STATE_WEAPON_FINISH;
sound_wait_start_time = now_ms;
break;
case STATE_FORCERISER_READY:
mp3_player.pause();
break;
case STATE_FORCERISER:
sound_waiting_state = SOUND_WAITING_STATE_FORCERISER;
sound_wait_start_time = now_ms;
break;
case STATE_FORCERISER_FINISH_READY:
break;
case STATE_FORCERISER_FINISH:
sound_waiting_state = SOUND_WAITING_STATE_FORCERISER_FINISH_1;
sound_wait_start_time = now_ms;
break;
case STATE_AUTHORIZED_A:
if(prev_state != STATE_AUTHORIZED_B){
mp3_player.pause();
sound_waiting_state = SOUND_WAITING_STATE_AUTHORISE;
sound_wait_start_time = now_ms;
}
break;
case STATE_AUTHORIZED_B:
mp3_player.playMp3Folder(offset + SOUND_ABILITY);
break;
case STATE_ZEROONEDRIVER_READY:
mp3_player.pause();
break;
case STATE_ZEROONEDRIVER:
sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER;
sound_wait_start_time = now_ms;
break;
case STATE_ZEROONEDRIVER_FINISH_READY:
break;
case STATE_ZEROONEDRIVER_FINISH:
sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1;
sound_wait_start_time = now_ms;
break;
case STATE_SHOTRISER_READY:
break;
case STATE_SHOTRISER:
if(prev_state == STATE_SHOTRISER_READY){
sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER;
sound_wait_start_time = now_ms;
}
break;
case STATE_SHOTRISER_FINISH_READY:
mp3_player.playMp3Folder(offset + SOUND_ABILITY);
sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER_FINISH_READY;
sound_wait_start_time = now_ms;
break;
case STATE_SHOTRISER_FINISH:
mp3_player.pause();
sound_waiting_state = SOUND_WAITING_STATE_SHOTRISER_FINISH;
sound_wait_start_time = now_ms;
break;
default:
;
}
}else{ // 状態遷移後の時間経過処理
switch(sound_waiting_state){
case SOUND_WAITING_STATE_NONE:
break;
case SOUND_WAITING_STATE_AUTHORISE:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_AUTHORIZE){
mp3_player.playMp3Folder(SOUND_AUTHORIZE);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_WEAPON:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_WEAPON){
mp3_player.playMp3Folder(offset + SOUND_NAME_ABILITY);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_ZEROONEDRIVER:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER){
mp3_player.playMp3Folder(offset + SOUND_PUN_NAME_DESCRIPTION);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_SHOTRISER:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER){
mp3_player.playMp3Folder(offset + SOUND_NAME_DESCRIPTION);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_FORCERISER:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER){
mp3_player.playMp3Folder(offset + SOUND_NAME);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_WEAPON_FINISH:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_WEAPON_FINISH){
mp3_player.playMp3Folder(offset + SOUND_FINISH_SHORT);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_1:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_1){
mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
sound_waiting_state = SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2;
sound_wait_start_time = now_ms;
}
break;
case SOUND_WAITING_STATE_ZEROONEDRIVER_FINISH_2:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_ZEROONEDRIVER_FINISH_2){
mp3_player.playMp3Folder(offset + SOUND_FINISH_LONG);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_SHOTRISER_FINISH_READY:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER_FINISH_READY){
mp3_player.playMp3Folder(SOUND_SHOTRISER_FINISH_READY);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_SHOTRISER_FINISH:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_SHOTRISER_FINISH){
mp3_player.playMp3Folder(offset + SOUND_FINISH_SHOTRISER);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
case SOUND_WAITING_STATE_FORCERISER_FINISH_1:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER_FINISH_1){
mp3_player.playMp3Folder(offset + SOUND_FINISH_START);
sound_waiting_state = SOUND_WAITING_STATE_FORCERISER_FINISH_2;
sound_wait_start_time = now_ms;
}
break;
case SOUND_WAITING_STATE_FORCERISER_FINISH_2:
if(now_ms - sound_wait_start_time >= SOUND_WAIT_MS_FORCERISER_FINISH_2){
mp3_player.playMp3Folder(offset + SOUND_FINISH_LONG);
sound_waiting_state = SOUND_WAITING_STATE_NONE;
}
break;
default:
;
}
}
}
////////// 発光処理 ////////////////////////////////////////////////////////////////
#define LED_COLOR_PIN 8
#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1
struct color_rgb {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct color_rgb COLOR_OFF = { 0, 0, 0};
struct color_rgb COLOR_WHITE = {255,255,255};
struct color_rgb COLOR_RED = {255, 0, 0};
struct color_rgb COLOR_BLUE = { 0, 0,255};
struct color_rgb COLOR_GREEN = { 0,255, 0};
struct color_rgb COLOR_YELLOW = {255,255, 0};
struct color_rgb COLOR_PURPLE = {255, 0,255};
struct color_rgb COLOR_BROWN = {255,102, 0}; // {153, 51, 0}
struct color_rgb COLOR_TEAL = { 0,255,255};
struct color_rgb COLOR_ORANGE = {255,165, 0};
struct color_rgb COLOR_SKYBLUE = { 0,191,255};
struct color_rgb COLOR_CHARTREUSE = {127,255, 0};
struct color_rgb COLOR_GOLD = {255,215, 0};
struct color_rgb *color = &COLOR_OFF;
boolean is_led_active = false;
unsigned long led_pattern_start_time = 0;
unsigned long prev_blink_time = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
boolean is_inc = false;
uint8_t prev_r = 0;
uint8_t prev_g = 0;
uint8_t prev_b = 0;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, LED_COLOR_PIN, NEO_GRB);
void update_color(uint8_t r, uint8_t g, uint8_t b){
if(prev_r != r || prev_g != g || prev_b != b){
pixels.setPixelColor(0, pixels.Color(r,g,b));
}
prev_r = r;
prev_g = g;
prev_b = b;
}
void led_base_pattern_on(struct color_rgb *color){
inc_dim_start_time = 0;
update_color(color->r,color->g,color->b);
}
void led_base_pattern_off(){
inc_dim_start_time = 0;
update_color(0,0,0);
}
void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int interval_ms){
inc_dim_start_time = 0;
if(now_ms - prev_blink_time >= interval_ms){
if(is_lighting){
update_color(0,0,0);
}else{
update_color(color->r,color->g,color->b);
}
is_lighting = !is_lighting;
prev_blink_time = now_ms;
}
}
void led_base_pattern_inc(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
if(inc_dim_start_time == 0){
inc_dim_start_time = now_ms;
}
int ms_per_step = interval_ms / steps;
int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
update_color(r_step*current_step, g_step*current_step, b_step*current_step);
}
void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
if(inc_dim_start_time == 0){
inc_dim_start_time = now_ms;
}
int ms_per_step = interval_ms / steps;
int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
update_color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step));
}
void led_base_pattern_blink_slowly(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
if(inc_dim_start_time == 0){
inc_dim_start_time = now_ms;
}
int ms_per_step = interval_ms / steps;
int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
if(current_step > steps){
current_step = steps;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
if(is_inc){
update_color(r_step*current_step, g_step*current_step, b_step*current_step);
}else{
update_color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step));
}
if(now_ms - inc_dim_start_time > interval_ms){
is_inc = !is_inc;
inc_dim_start_time = 0;
}
}
void led_pattern_simple_on_off(struct color_rgb *color, unsigned long delay_ms){
led_base_pattern_on(color);
pixels.show();
delay(delay_ms);
led_base_pattern_off();
pixels.show();
}
// プログライズキーにおいては、発光が必要なのは
// STATE_SINGLE_A, STATE_SINGLE_ B, STATE_AUTHORIZED_A, STATE_AUTHORIZED_Bのみ
void led_pattern_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 500){ led_base_pattern_on(color);}
else if(500 < passed_ms && passed_ms <= 1200){ led_base_pattern_dim(color, now_ms, 700, 10);}
else{ led_base_pattern_off();}
}
void led_pattern_name_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 500){ led_base_pattern_on(color);}
else if( 500 < passed_ms && passed_ms <= 900){ led_base_pattern_dim(color, now_ms, 400, 10);}
else if( 900 < passed_ms && passed_ms <= 1600){ led_base_pattern_on(color);}
else if(1600 < passed_ms && passed_ms <= 1800){ led_base_pattern_dim(color, now_ms, 200, 10);}
else if(1800 < passed_ms && passed_ms <= 2800){ led_base_pattern_on(color);}
else if(2800 < passed_ms && passed_ms <= 3600){ led_base_pattern_dim(color, now_ms, 800, 10);}
else{ led_base_pattern_off();}
}
void led_pattern_authorize(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 1000){ led_base_pattern_inc(color, now_ms, 1000, 10);}
else if(1000 < passed_ms && passed_ms <= 1800){ led_base_pattern_on(color);}
else if(1800 < passed_ms && passed_ms <= 2800){ led_base_pattern_dim(color, now_ms, 1000, 10);}
else{ led_base_pattern_blink_slowly(color, now_ms, 800, 10);}
}
void led_pattern_authorize_ability(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 500){ led_base_pattern_on(color);}
else if(500 < passed_ms && passed_ms <= 1000){ led_base_pattern_dim(color, now_ms, 500, 10);}
else{ led_base_pattern_blink_slowly(color, now_ms, 800, 10);}
}
void control_led(struct color_rgb *color, unsigned long now_ms){
if(prev_state != state){
if(prev_state != STATE_SINGLE_B && state == STATE_SINGLE_A){
// STATE B以外からのSTATE Aへの状態遷移では、LEDを発光させない
is_led_active = false;
led_base_pattern_off();
}else if(state == STATE_SHOTRISER_READY){
; // ショットライザーの変身待機は例外でリセットしない
}else{
// 基本的には状態が遷移するとリセットしてLEDを有効にする
is_led_active = true;
led_pattern_start_time = now_ms;
}
}
unsigned long passed_ms = now_ms - led_pattern_start_time;
switch(state){
case STATE_SINGLE_A:
if(is_led_active){
led_pattern_name_ability(color, now_ms, passed_ms);
}
break;
case STATE_SINGLE_B:
led_pattern_ability(color, now_ms, passed_ms);
break;
case STATE_WEAPON_READY:
led_base_pattern_off();
break;
case STATE_WEAPON_FINISH:
led_base_pattern_off();
break;
case STATE_FORCERISER_READY:
led_base_pattern_off();
break;
case STATE_FORCERISER:
led_base_pattern_off();
break;
case STATE_FORCERISER_FINISH_READY:
led_base_pattern_off();
break;
case STATE_FORCERISER_FINISH:
led_base_pattern_off();
break;
case STATE_AUTHORIZED_A:
led_pattern_authorize(color, now_ms, passed_ms);
break;
case STATE_AUTHORIZED_B:
led_pattern_authorize_ability(color, now_ms, passed_ms);
break;
case STATE_ZEROONEDRIVER_READY:
led_base_pattern_off();
break;
case STATE_ZEROONEDRIVER:
led_base_pattern_off();
break;
case STATE_ZEROONEDRIVER_FINISH_READY:
led_base_pattern_off();
break;
case STATE_ZEROONEDRIVER_FINISH:
led_base_pattern_off();
break;
case STATE_SHOTRISER_READY:
led_pattern_authorize(color, now_ms, passed_ms);
break;
case STATE_SHOTRISER:
led_base_pattern_off();
break;
case STATE_SHOTRISER_FINISH_READY:
led_base_pattern_off();
break;
case STATE_SHOTRISER_FINISH:
led_base_pattern_off();
break;
default:
;
}
pixels.show();
}
////////// ホールセンサ処理 ////////////////////////////////////////////////////////////////
// ホールIC SK8552G SIP-3を使用
#define HALL_SENSOR_PIN A0
#define HALL_SENSOR_THRESHOLD 500
#define HALL_SENSOR_OFF 0
#define HALL_SENSOR_ON 1
uint8_t hall_sensor = HALL_SENSOR_OFF;
uint8_t prev_hall_sensor = HALL_SENSOR_OFF;
////////// ユーティリティ関数 /////////////////////////////////////////////////////
void authorize_ready(unsigned long now_ms){
is_authorize_ready = true;
authorize_waiting_start_time = now_ms;
}
void reset_state(){
if(state == STATE_SINGLE_B){
// BからAへの変化は通常は音声が鳴るため、初期化としてのAへの変化のときのみ音声を鳴らさないための配慮
prev_state = STATE_SINGLE_A;
}
state = STATE_SINGLE_A;
is_authorize_ready = false;
sound_waiting_state = SOUND_WAITING_STATE_NONE;
is_led_active = false;
}
void print_state(){
if(prev_state != state){
switch(state){
case STATE_SINGLE_A: Serial.println(F("State: SINGLE_A")); break;
case STATE_SINGLE_B: Serial.println(F("State: SINGLE_B")); break;
case STATE_WEAPON_READY: Serial.println(F("State: WEAPON_READY")); break;
case STATE_WEAPON: Serial.println(F("State: WEAPON")); break;
case STATE_WEAPON_FINISH: Serial.println(F("State: WEAPON_FINISH")); break;
case STATE_FORCERISER_READY: Serial.println(F("State: FORCERISER_READY")); break;
case STATE_FORCERISER: Serial.println(F("State: FORCERISER")); break;
case STATE_FORCERISER_FINISH_READY: Serial.println(F("State: FORCERISER_FINISH_READY")); break;
case STATE_FORCERISER_FINISH: Serial.println(F("State: FORCERISER_FINISH")); break;
case STATE_AUTHORIZED_A: Serial.println(F("State: AUTHORIZED_A")); break;
case STATE_AUTHORIZED_B: Serial.println(F("State: AUTHORIZED_B")); break;
case STATE_ZEROONEDRIVER_READY: Serial.println(F("State: ZEROONEDRIVER_READY")); break;
case STATE_ZEROONEDRIVER: Serial.println(F("State: ZEROONEDRIVER")); break;
case STATE_ZEROONEDRIVER_FINISH_READY: Serial.println(F("State: ZEROONEDRIVER_FINISH_READY")); break;
case STATE_ZEROONEDRIVER_FINISH: Serial.println(F("State: ZEROONEDRIVER_FINISH")); break;
case STATE_SHOTRISER_READY: Serial.println(F("State: SHOTRISER_READY")); break;
case STATE_SHOTRISER: Serial.println(F("State: SHOTRISER")); break;
case STATE_SHOTRISER_FINISH_READY: Serial.println(F("State: SHOTRISER_FINISH_READY")); break;
case STATE_SHOTRISER_FINISH: Serial.println(F("State: SHOTRISER_FINISH")); break;
default: Serial.println(F("State: Unknow_msn"));
}
}
}
////////// メインのループ処理 ////////////////////////////////////////////////////////////
void loop_main(){
////////////////////////////// メインの状態遷移管理 //////////////////////////////
unsigned long now_ms = millis();
uint8_t input_port = PIND; // ピン0〜7のデジタル入力の同時読み取り
sw[0] = bitRead(input_port, SW_A_PIN);
sw[1] = bitRead(input_port, SW_B_PIN);
sw[2] = bitRead(input_port, SW_C_PIN);
// ボタン操作による状態遷移管理。ホールセンサと時間経過による遷移は別で扱う
switch(state){
case STATE_SINGLE_A:
if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
state = STATE_SINGLE_B;
authorize_ready(now_ms);
}else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_WEAPON_READY;
}
}else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_WEAPON;
}
}else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_FORCERISER_READY;
}
}
}
break;
case STATE_SINGLE_B:
if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
state = STATE_SINGLE_A;
authorize_ready(now_ms);
}else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_WEAPON_READY;
}
}else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_WEAPON;
}
}else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
if(progrise_key_id != 0){
state = STATE_FORCERISER_READY;
}
}
}
break;
case STATE_WEAPON_READY:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_WEAPON;
}
}
break;
case STATE_WEAPON:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
state = STATE_WEAPON_READY;
}else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_WEAPON_FINISH;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_WEAPON_FINISH:
if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_WEAPON;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_FORCERISER_READY:
if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_FORCERISER;
}
}
break;
case STATE_FORCERISER:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_FORCERISER_FINISH_READY;
}
}
break;
case STATE_FORCERISER_FINISH_READY:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_FORCERISER_FINISH;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_FORCERISER_FINISH:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_FORCERISER_FINISH_READY;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_AUTHORIZED_A:
if(memcmp(prev_sw, OFF_OFF_OFF, N_BUTTON) == 0){
if(memcmp(sw, ON_OFF_OFF, N_BUTTON) == 0){
state = STATE_AUTHORIZED_B;
}else if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0){
state = STATE_ZEROONEDRIVER_READY;
}else if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_ZEROONEDRIVER;
changed_time = now_ms;
}else if(memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_SHOTRISER_READY;
}
}
break;
case STATE_AUTHORIZED_B:
if(memcmp(prev_sw, ON_OFF_OFF, N_BUTTON) == 0){
if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
state = STATE_AUTHORIZED_A;
}
}
break;
case STATE_ZEROONEDRIVER_READY:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_ZEROONEDRIVER;
changed_time = now_ms;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
state = STATE_SINGLE_A;
}
}
break;
case STATE_ZEROONEDRIVER:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_ZEROONEDRIVER_FINISH_READY;
}
}
break;
case STATE_ZEROONEDRIVER_FINISH_READY:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 ){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
if(now_ms - changed_time >= ZEROONEDRIVER_CHANGING_TIME){
state = STATE_ZEROONEDRIVER_FINISH;
}
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_ZEROONEDRIVER_FINISH:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_ZEROONEDRIVER_FINISH_READY;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_SHOTRISER_READY:
if(memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_SHOTRISER;
changed_time = now_ms;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_SHOTRISER:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 || memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0 ){
if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0 || memcmp(sw, ON_OFF_ON, N_BUTTON) == 0 || memcmp(sw, ON_ON_ON, N_BUTTON) == 0){
if(now_ms - changed_time >= SHOTRISER_CHANGING_TIME){
state = STATE_SHOTRISER_FINISH_READY;
}
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_SHOTRISER_FINISH_READY:
if(memcmp(prev_sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(prev_sw, OFF_OFF_ON, N_BUTTON) == 0 ){
if(memcmp(sw, OFF_ON_ON, N_BUTTON) == 0){
state = STATE_SHOTRISER_FINISH;
}else if(memcmp(sw, ON_ON_OFF, N_BUTTON) == 0 || memcmp(sw, ON_OFF_ON, N_BUTTON) == 0){
prev_state = STATE_SHOTRISER; // 擬似的に状態変化を起こす
state = STATE_SHOTRISER_FINISH_READY;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
case STATE_SHOTRISER_FINISH:
if(memcmp(prev_sw, OFF_ON_ON, N_BUTTON) == 0){
if(memcmp(sw, OFF_ON_OFF, N_BUTTON) == 0 || memcmp(sw, OFF_OFF_ON, N_BUTTON) == 0){
state = STATE_SHOTRISER;
}else if(memcmp(sw, OFF_OFF_OFF, N_BUTTON) == 0){
reset_state();
}
}
break;
default:
;
}
////////////////////////////// ホールセンサによる状態遷移管理 //////////////////////////////
uint16_t hall_sensor_value = analogRead(HALL_SENSOR_PIN);
if(hall_sensor_value < HALL_SENSOR_THRESHOLD){
hall_sensor = HALL_SENSOR_OFF;
}else{
hall_sensor = HALL_SENSOR_ON;
}
switch(state){
case STATE_SINGLE_A:
case STATE_SINGLE_B:
if(progrise_key_id != 0 && is_authorize_ready && prev_hall_sensor == HALL_SENSOR_OFF && hall_sensor == HALL_SENSOR_ON){
state = STATE_AUTHORIZED_A;
authorize_keeping_start_time = now_ms;
}
break;
case STATE_AUTHORIZED_A:
case STATE_AUTHORIZED_B:
if(prev_hall_sensor == HALL_SENSOR_OFF && hall_sensor == HALL_SENSOR_ON){
prev_state = STATE_SINGLE_A; // 擬似的に状態変化を起こす
state = STATE_AUTHORIZED_A;
authorize_keeping_start_time = now_ms;
}
break;
default:
;
}
////////////////////////////// 時間経過による状態遷移管理 //////////////////////////////
// 1. オーソライズ待機の解除
switch(state){
case STATE_SINGLE_A:
case STATE_SINGLE_B:
if(is_authorize_ready && now_ms - authorize_waiting_start_time >= AUTHORIZE_WAITING_TIME){
reset_state();
}
break;
default:
;
}
// 2. オーソライズの解除
switch(state){
case STATE_AUTHORIZED_A:
case STATE_AUTHORIZED_B:
if(now_ms - authorize_keeping_start_time >= AUTHORIZE_KEEPING_TIME){
reset_state();
}
break;
default:
;
}
////////////////////////////// 初期化処理 //////////////////////////////
if(sw[0] == OFF){
initialize_start_time = now_ms;
}
if(sw[0] == ON && now_ms - initialize_start_time >= INITIALIZE_TIME){
is_initialized = true;
Serial.println(F("Initialize."));
progrise_key_id = 0;
reset_state();
mp3_player.playMp3Folder(SOUND_INITIALIZE);
led_pattern_simple_on_off(&COLOR_WHITE, 1500);
}
////////// デバッグ用表示 ////////////////////
//print_state();
//Serial.println(hall_sensor_value);
////////// 音声再生処理 ////////////////////
control_sound(sound_offset, now_ms);
////////// LED発光処理 ////////////////////
control_led(color, now_ms);
////////// 処理状態の保持 ////////////////////
prev_sw[0] = sw[0];
prev_sw[1] = sw[1];
prev_sw[2] = sw[2];
prev_hall_sensor = hall_sensor;
prev_state = state;
}
////////// サブのループ処理(主に赤外線受信処理) ////////////////////////////////////////////////////////////
void loop_sub(){
////////////////////////////// ボタン処理 //////////////////////////////
sw[0] = digitalRead(SW_A_PIN);
if(prev_sw[0] == OFF && sw[0] == ON){
mp3_player.playMp3Folder(SOUND_ERRORRISE);
led_pattern_simple_on_off(&COLOR_WHITE, 1500);
}
prev_sw[0] = sw[0];
////////////////////////////// IR信号受信処理 //////////////////////////////
while(1){ // 常にIR信号の受信待ちにする
// スタートビット(1)チェック
if(digitalRead(IR_RX_PIN) == IR_ON){ // とりあえず何かしらのIR信号を受信した
delayMicroseconds(400);
}else{
break; // 何もIR信号を受信していないときはここで終了
}
if(digitalRead(IR_RX_PIN) == IR_ON){ // 400μs待ってまたスタートビットの信号が続いているかチェックする
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}else{
break; // 受信した信号はスタートビットではなかったので、ここで終了
}
// スタートビットが確認できたので、送信された整数値を読み取る
uint8_t number = 0;
for(uint8_t i=0;i<LEN_IR_DATA;i++){
number = number | (!digitalRead(IR_RX_PIN) << i); // 8bitの右端から受信する
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}
// ストップビット(1010)チェック
if(digitalRead(IR_RX_PIN) == IR_ON){
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}else{
break; // ストップビットが不正なので、ここで終了
}
if(digitalRead(IR_RX_PIN) == IR_OFF){
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}else{
break; // ストップビットが不正なので、ここで終了
}
if(digitalRead(IR_RX_PIN) == IR_ON){
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}else{
break; // ストップビットが不正なので、ここで終了
}
if(digitalRead(IR_RX_PIN) == IR_OFF){
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}else{
break; // ストップビットが不正なので、ここで終了
}
Serial.print(F("ID: "));
Serial.println(number);
// ストップビットまで確認できたので、送信されたオーメダルorフルボトルのIDを確認
switch(number){
case 3: // バッタ(オーメダル)
progrise_key_id = 1; // ライジングホッパー
sound_offset = SOUND_OFFSET_HOPPER;
color = &COLOR_YELLOW;
break;
case 49: // ウルフ(フルボトル)
progrise_key_id = 2; // シューティングウルフ
sound_offset = SOUND_OFFSET_WOLF;
color = &COLOR_BLUE;
break;
case 6: // チーター(オーメダル)
progrise_key_id = 3; // ラッシングチーター
sound_offset = SOUND_OFFSET_CHEETAH;
color = &COLOR_ORANGE;
break;
case 28: // サソリ(オーメダル)
progrise_key_id = 4; // スティングスコーピオン
sound_offset = SOUND_OFFSET_SCORPION;
color = &COLOR_PURPLE;
break;
//case X: // サメ(オーメダル)
case 15: // サメ(フルボトル)
progrise_key_id = 6; // バイティングジャーク
sound_offset = SOUND_OFFSET_SHARK;
color = &COLOR_TEAL;
break;
case 8: // ゴリラ(オーメダル&フルボトル) ... フルボトルは本来の番号から振替
progrise_key_id = 7; // パンチングコング
sound_offset = SOUND_OFFSET_KONG;
color = &COLOR_WHITE;
break;
case 5: // トラ(オーメダル)
case 25: // トラ(フルボトル)
progrise_key_id = 8; // フレイミングタイガー
sound_offset = SOUND_OFFSET_TIGER;
color = &COLOR_RED;
break;
case 29: // ハチ(オーメダル)
case 43: // ハチ(フルボトル)
progrise_key_id = 9; // ライトニングホーネット
sound_offset = SOUND_OFFSET_HORNET;
color = &COLOR_YELLOW;
break;
case 26: // シロクマ(オーメダル)
case 51: // クマ(フルボトル)
progrise_key_id = 10; // フリージングベアー
sound_offset = SOUND_OFFSET_BEAR;
color = &COLOR_SKYBLUE;
break;
case 9: // ゾウ(オーメダル)
progrise_key_id = 11; // ブレイキングマンモス
sound_offset = SOUND_OFFSET_MAMMOTH;
color = &COLOR_WHITE;
break;
case 23: // カブトムシ(フルボトル)
progrise_key_id = 12; // アメイジングヘラクレス
sound_offset = SOUND_OFFSET_HERCULES;
color = &COLOR_CHARTREUSE;
break;
case 55: // キリン(フルボトル)
progrise_key_id = 13; // スパーキングジラフ
sound_offset = SOUND_OFFSET_GIRAFFE;
color = &COLOR_YELLOW;
break;
case 64: // ハリネズミ(フルボトル)
progrise_key_id = 14; // ガトリングヘッジホック
sound_offset = SOUND_OFFSET_HEDGEHOG;
color = &COLOR_GREEN;
break;
case 32: // ウシ(オーメダル)
progrise_key_id = 15; // クラッシングバッファロー
sound_offset = SOUND_OFFSET_BUFFALO;
color = &COLOR_RED;
break;
case 19: // スパイダー(フルボトル)
progrise_key_id = 16; // トラッピングスパイダー
sound_offset = SOUND_OFFSET_SPIDER;
color = &COLOR_PURPLE;
break;
case 1: // クワガタ(オーメダル)
case 94: // クワガタ(フルボトル)
progrise_key_id = 17; // エキサイティングスタッグ
sound_offset = SOUND_OFFSET_STAG;
color = &COLOR_ORANGE;
break;
case 27: // ペンギン(オーメダル)
case 59: // ペンギン(フルボトル)
progrise_key_id = 18; // ストーミングペンギン
sound_offset = SOUND_OFFSET_PENGUIN;
color = &COLOR_SKYBLUE;
break;
//case X: // クジラ(オーメダル)
case 31: // クジラ(フルボトル)
progrise_key_id = 19; // スプラッシングホエール
sound_offset = SOUND_OFFSET_WHALE;
color = &COLOR_BLUE;
break;
case 4: // ライオン(オーメダル)
case 62: // ライオン(フルボトル)
progrise_key_id = 20; // ダイナマイティングライオン
sound_offset = SOUND_OFFSET_LION;
color = &COLOR_RED;
break;
case 34: // パンダ(オーメダル & フルボトル) ... フルボトルは本来の番号から振替
progrise_key_id = 21; // スカウティングパンダ
sound_offset = SOUND_OFFSET_PANDA;
color = &COLOR_WHITE;
break;
case 35: // カンガルー(オーメダル)
progrise_key_id = 22; // ホッピングカンガルー
sound_offset = SOUND_OFFSET_KANGAROO;
color = &COLOR_GREEN;
break;
case 122: // スーパーバッタ(オーメダル)
progrise_key_id = 23; // シャイニングホッパー
sound_offset = SOUND_OFFSET_HOPPER_2;
color = &COLOR_GOLD;
break;
default:
;
}
// 認識完了効果
mp3_player.playMp3Folder(sound_offset + SOUND_PUN_NAME_DESCRIPTION);
led_base_pattern_on(color);
pixels.show();
delay(11000);
led_base_pattern_off();
pixels.show();
reset_state();
is_initialized = false;
}
}
////////// メイン処理 ////////////////////////////////////////////////////////////
void setup(){
Serial.begin(115200);
pinMode(SW_A_PIN, INPUT_PULLUP);
pinMode(SW_B_PIN, INPUT_PULLUP);
pinMode(SW_C_PIN, INPUT_PULLUP);
pinMode(IR_RX_PIN, INPUT_PULLUP);
pinMode(LED_COLOR_PIN, OUTPUT);
// ---------- MP3プレイヤーセットアップ ----------
ss_mp3_player.begin(9600);
if (!mp3_player.begin(ss_mp3_player)) { //Use softwareSerial to communicate with mp3.
Serial.println(F("Unable to begin music_player:"));
Serial.println(F("1.Please recheck the connection!"));
Serial.println(F("2.Please insert the SD card!"));
while(true);
}
Serial.println(F("mp3_player online."));
mp3_player.setTimeOut(500); //Set serial communictaion time out 500ms
mp3_player.volume(SOUND_VOLUME_DEFAULT); //Set volume value (0~30).
// フルカラーLED初期化
pixels.begin();
pixels.clear();
// 起動エフェクト
led_pattern_simple_on_off(&COLOR_WHITE, 1000);
}
void loop(){
if(is_initialized){
loop_sub();
}else{
loop_main();
}
}
続いてプログライズライター側です。
////////// 基本定義 ////////////////////////////////////////////////////////////
#define MP3_RX_PIN 2
#define MP3_TX_PIN 3
#define BUTTON_INPUT_PIN 4
#define BUTTON_COLOR_PIN 5
#define LED_BAR_DATA_PIN 6
#define LED_BAR_CLOCK_PIN 7
#define MEDAL_CLOCK_PIN 8
#define MEDAL_DATA_PIN 9
#define MEDAL_CTRL_PIN 10
#define BOTTLE_A_PIN 11
#define BOTTLE_B_PIN 12
#define BOTTLE_C_PIN 13
#define IR_TX_PIN A0
#define BOTTLE_LED_PIN A1
#define WRITING_TIME_MEDAL_MS 8600
#define WRITING_TIME_BOTTLE_MS 16800
#define STATE_READ_READY 0
#define STATE_WRITE_READY 1
#define STATE_WRITING 2
uint8_t state = STATE_READ_READY;
uint8_t prev_state = STATE_READ_READY;
#define BTN_ON HIGH
#define BTN_OFF LOW
uint8_t btn_state = BTN_OFF;
uint8_t prev_btn_state = BTN_OFF;
unsigned long writing_start_time = 0;
////////// オーメダル読み取り処理関係 ////////////////////////////////////////////////////////////////
#define MEDAL_READ_STAGE_BEFORE 0
#define MEDAL_READ_STAGE_1ST 1
#define MEDAL_READ_STAGE_2ND 2
#define MEDAL_READ_STAGE_3RD 3
#define MEDAL_READ_STAGE_AFTER 4
uint8_t prev_medal_clock_value = 1; // 初期値は1
uint8_t prev_medal_data_value = 1; // 初期値は1
uint8_t prev_medal_ctrl_value = 1; // 初期値は1
uint8_t medal_clock_value = 1;
uint8_t medal_data_value = 1;
uint8_t medal_ctrl_value = 1;
uint8_t medal_read_stage = MEDAL_READ_STAGE_BEFORE;
uint8_t zero_counter = 0;
uint8_t one_counter = 0;
uint8_t bit_index = 0;
uint8_t saved_zero_counter = 0;
uint8_t saved_one_counter = 0;
uint8_t temp_medal_id = 0;
uint8_t medal_id = 0;
uint8_t prev_medal_id = 0;
void reset_count(){
zero_counter = 0;
one_counter = 0;
}
////////// フルボトル読み取り処理関係 ////////////////////////////////////////////////////////////////
#define PIN_ON LOW
#define PIN_OFF HIGH
#define N_READER_PINS 3
#define BOTTLE_READER_DELAY_MS 1 // フルボトル読み取り中にだけ適用するディレイ
#define BOTTLE_GROUP_NONE 0
#define BOTTLE_GROUP_A 1
#define BOTTLE_GROUP_C 2
#define BOTTLE_READ_STAGE_BEFORE 0
#define BOTTLE_READ_STAGE_1ST 1
#define BOTTLE_READ_STAGE_2ND 2
#define BOTTLE_READ_STAGE_3RD 3
#define BOTTLE_READ_STAGE_4TH 4
#define BOTTLE_READ_STAGE_AFTER 5
#define RESET_COUNT 200
uint8_t pin_A = PIN_OFF;
uint8_t pin_B = PIN_OFF;
uint8_t pin_C = PIN_OFF;
uint8_t before_pin_A = PIN_OFF;
uint8_t before_pin_B = PIN_OFF;
uint8_t before_pin_C = PIN_OFF;
uint8_t bottle_group = BOTTLE_GROUP_NONE;
uint8_t bottle_read_stage = BOTTLE_READ_STAGE_BEFORE;
uint8_t read_values[4] = {0,0,0,0};
uint16_t big_digit_counter = 0;
uint16_t little_digit_counter = 0;
uint16_t reset_counter = 0;
uint8_t bottle_id = 0;
void off_on_count(){
switch(bottle_group){
case BOTTLE_GROUP_A:
if(before_pin_B == PIN_OFF && pin_B == PIN_ON){
little_digit_counter++;
}
if(before_pin_C == PIN_OFF && pin_C == PIN_ON){
big_digit_counter++;
}
break;
case BOTTLE_GROUP_C:
if(before_pin_A == PIN_OFF && pin_A == PIN_ON){
little_digit_counter++;
}
if(before_pin_B == PIN_OFF && pin_B == PIN_ON){
big_digit_counter++;
}
break;
default:
;
}
}
void fix_value(){
uint8_t value = 0;
if(little_digit_counter > 0){
if(big_digit_counter == 0){
value = 0;
}else{
value = 2;
}
}else if(big_digit_counter > 0){
value = 1;
}
// 値の格納
read_values[bottle_read_stage-1] = value;
}
void reset_digit_counter(){
big_digit_counter = 0;
little_digit_counter = 0;
}
void identify_bottle(){
bottle_id = read_values[0]*27 + read_values[1]*9 + read_values[2]*3 + read_values[3] + 1;
if(bottle_group == BOTTLE_GROUP_C){
bottle_id += 81;
}
// Serial.println(read_values[0]); // Debug
// Serial.println(read_values[1]); // Debug
// Serial.println(read_values[2]); // Debug
// Serial.println(read_values[3]); // Debug
Serial.print("Fullbottle ID: ");
Serial.println(bottle_id);
}
void print_bottle_read_stage(){
Serial.print(F("Fullbottle Read Stage: "));
Serial.println(bottle_read_stage);
}
void print_digit_counter(){
Serial.print(F("BigDigit: "));
Serial.println(big_digit_counter);
Serial.print(F("LittleDigit: "));
Serial.println(little_digit_counter);
}
////////// LEDバー処理関係 ////////////////////////////////////////////////////////////////
#include <LED_Bar.h>
LED_Bar bar(LED_BAR_CLOCK_PIN, LED_BAR_DATA_PIN);
////////// フルカラーLED(キー)処理関係 ////////////////////////////////////////////////////////////////
#include <Adafruit_NeoPixel.h>
#define N_COLOR_LED 1
struct color_rgb {
uint8_t r;
uint8_t g;
uint8_t b;
};
struct color_rgb COLOR_OFF = { 0, 0, 0};
struct color_rgb COLOR_WHITE = {127,127,127};
struct color_rgb COLOR_RED = {127, 0, 0};
struct color_rgb COLOR_BLUE = { 0, 0,127};
struct color_rgb COLOR_GREEN = { 0,127, 0};
struct color_rgb COLOR_YELLOW = {127,127, 0};
struct color_rgb COLOR_PURPLE = {127, 0,127};
struct color_rgb COLOR_BROWN = {153, 51, 0};
struct color_rgb *color = &COLOR_OFF;
unsigned long led_pattern_start_time = 0;
unsigned long prev_blink_time = 0;
unsigned long inc_dim_start_time = 0;
boolean is_lighting = false;
Adafruit_NeoPixel pixels = Adafruit_NeoPixel(N_COLOR_LED, BUTTON_COLOR_PIN, NEO_GRB);
void led_base_pattern_on(struct color_rgb *color){
pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));
}
void led_base_pattern_off(){
pixels.setPixelColor(0, pixels.Color(0,0,0));
}
void led_base_pattern_blink(struct color_rgb *color, unsigned long now_ms, int interval_ms){
if(now_ms - prev_blink_time >= interval_ms){
if(is_lighting){
pixels.setPixelColor(0, pixels.Color(0,0,0));
}else{
pixels.setPixelColor(0, pixels.Color(color->r,color->g,color->b));
}
is_lighting = !is_lighting;
prev_blink_time = now_ms;
}
}
void led_base_pattern_dim(struct color_rgb *color, unsigned long now_ms, int interval_ms, uint8_t steps){
if(inc_dim_start_time == 0){
inc_dim_start_time = now_ms;
}
int ms_per_step = interval_ms / steps;
int current_step = (now_ms - inc_dim_start_time) / ms_per_step;
if(current_step >= steps){
current_step = steps;
inc_dim_start_time = 0;
}
uint8_t r_step = color->r/steps;
uint8_t g_step = color->g/steps;
uint8_t b_step = color->b/steps;
pixels.setPixelColor(0, pixels.Color(r_step*(steps-current_step), g_step*(steps-current_step), b_step*(steps-current_step)));
}
void led_pattern_writing_medal(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 850){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(1);}
else if( 850 < passed_ms && passed_ms <= 1700){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(2);}
else if(1700 < passed_ms && passed_ms <= 2550){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(3);}
else if(2550 < passed_ms && passed_ms <= 3400){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(4);}
else if(3400 < passed_ms && passed_ms <= 4250){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(5);}
else if(4250 < passed_ms && passed_ms <= 5100){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(6);}
else if(5100 < passed_ms && passed_ms <= 5950){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(7);}
else if(5950 < passed_ms && passed_ms <= 6800){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(8);}
else if(6800 < passed_ms && passed_ms <= 7650){ led_base_pattern_dim(color, now_ms, 850, 10);
bar.setLevel(9);}
else if(7650 < passed_ms && passed_ms <= 8500){ led_base_pattern_on(color);
bar.setLevel(10);}
else{ led_base_pattern_on(color);}
}
void led_pattern_writing_bottle(struct color_rgb *color, unsigned long now_ms, uint16_t passed_ms){
if(passed_ms <= 1750){ led_base_pattern_blink(color, now_ms, 440);
bar.setLevel(1);}
else if( 1750 < passed_ms && passed_ms <= 3500){ led_base_pattern_blink(color, now_ms, 440);
bar.setLevel(2);}
else if( 3500 < passed_ms && passed_ms <= 5250){ led_base_pattern_blink(color, now_ms, 440);
bar.setLevel(3);}
else if( 5250 < passed_ms && passed_ms <= 7000){ led_base_pattern_blink(color, now_ms, 440);
bar.setLevel(4);}
else if( 7000 < passed_ms && passed_ms <= 7900){ led_base_pattern_blink(color, now_ms, 340);
bar.setLevel(5);}
else if( 7900 < passed_ms && passed_ms <= 8800){ led_base_pattern_blink(color, now_ms, 340);
bar.setLevel(6);}
else if( 8800 < passed_ms && passed_ms <= 9700){ led_base_pattern_blink(color, now_ms, 340);
bar.setLevel(7);}
else if( 9700 < passed_ms && passed_ms <= 10600){ led_base_pattern_blink(color, now_ms, 340);
bar.setLevel(8);}
else if(10600 < passed_ms && passed_ms <= 11500){ led_base_pattern_blink(color, now_ms, 340);
bar.setLevel(9);}
else if(11500 < passed_ms && passed_ms <= 12400){ led_base_pattern_on(color);
bar.setLevel(10);}
else{ led_base_pattern_on(color);}
}
void control_led(unsigned long now_ms){
if(prev_state != state){
switch(state){
case STATE_READ_READY:
led_base_pattern_off();
bar.setLevel(0);
break;
case STATE_WRITE_READY:
if(medal_id != 0){
switch(medal_id){
case 1: color = &COLOR_GREEN; break; // クワガタ
case 3: color = &COLOR_GREEN; break; // バッタ
case 4: color = &COLOR_YELLOW; break; // ライオン
case 5: color = &COLOR_YELLOW; break; // トラ
case 6: color = &COLOR_YELLOW; break; // チーター
case 8: color = &COLOR_WHITE; break; // ゴリラ
case 9: color = &COLOR_WHITE; break; // ゾウ
case 26: color = &COLOR_WHITE; break; // シロクマ
case 27: color = &COLOR_BLUE; break; // ペンギン
case 28: color = &COLOR_PURPLE; break; // サソリ
case 29: color = &COLOR_YELLOW; break; // ハチ
case 32: color = &COLOR_WHITE; break; // ウシ
case 34: color = &COLOR_WHITE; break; // パンダ
case 35: color = &COLOR_YELLOW; break; // カンガルー
//case XX: color = &COLOR_BLUE; break; // サメ
//case XX: color = &COLOR_BLUE; break; // クジラ
case 122: color = &COLOR_GREEN; break; // スーパーバッタ
default: color = &COLOR_OFF; // その他
}
}else if(bottle_id != 0){
switch(bottle_id){
case 3: color = &COLOR_BROWN; break; // ゴリラ
case 9: color = &COLOR_WHITE; break; // パンダ
case 15: color = &COLOR_BLUE; break; // サメ
case 19: color = &COLOR_PURPLE; break; // スパイダー
case 23: color = &COLOR_BROWN; break; // カブトムシ
case 25: color = &COLOR_YELLOW; break; // トラ
case 31: color = &COLOR_BLUE; break; // クジラ
case 43: color = &COLOR_YELLOW; break; // ハチ
case 49: color = &COLOR_WHITE; break; // ウルフ
case 51: color = &COLOR_YELLOW; break; // クマ
case 55: color = &COLOR_YELLOW; break; // キリン
case 59: color = &COLOR_BLUE; break; // ペンギン
case 62: color = &COLOR_YELLOW; break; // ライオン
case 64: color = &COLOR_WHITE; break; // ハリネズミ
case 94: color = &COLOR_GREEN; break; // クワガタ
default: color = &COLOR_OFF; // その他
}
}
led_base_pattern_on(color);
break;
case STATE_WRITING:
led_pattern_start_time = now_ms;
break;
default:
;
}
}
if(state == STATE_WRITING){
unsigned long passed_ms = now_ms - led_pattern_start_time;
if(medal_id != 0){
led_pattern_writing_medal(color, now_ms, passed_ms);
}else if(bottle_id != 0){
led_pattern_writing_bottle(color, now_ms, passed_ms);
}
}
pixels.show();
}
////////// 音声処理 ////////////////////////////////////////////////////////////////
#include <SoftwareSerial.h>
#define SOUND_VOLUME_DEFAULT 20 // 0〜30
#define SOUND_POWER_ON 1
#define SOUND_MEDAL_OTHERS 2
#define SOUND_MEDAL_WRITING 3
#define SOUND_BOTTLE_OTHERS 4
#define SOUND_BOTTLE_WRITING 5
#define SOUND_BOTTLE_EJECT 6
#define SOUND_MEDAL_STAG 7
#define SOUND_MEDAL_HOPPER 8
#define SOUND_MEDAL_LION 9
#define SOUND_MEDAL_TIGER 10
#define SOUND_MEDAL_CHEETAH 11
#define SOUND_MEDAL_GORILLA 12
#define SOUND_MEDAL_ELEPHANT 13
#define SOUND_MEDAL_WHITE_BEAR 14
#define SOUND_MEDAL_PENGUIN 15
#define SOUND_MEDAL_SCORPION 16
#define SOUND_MEDAL_BEE 17
#define SOUND_MEDAL_BUFFALO 18
#define SOUND_MEDAL_PANDA 19
#define SOUND_MEDAL_KANGAROO 20
#define SOUND_MEDAL_SHARK 21
#define SOUND_MEDAL_WHALE 22
#define SOUND_MEDAL_SUPER_HOPPER 23
#define SOUND_BOTTLE_GORILLA 24
#define SOUND_BOTTLE_PANDA 25
#define SOUND_BOTTLE_SHARK 26
#define SOUND_BOTTLE_SPIDER 27
#define SOUND_BOTTLE_BEETLE 28
#define SOUND_BOTTLE_TIGER 29
#define SOUND_BOTTLE_WHALE 30
#define SOUND_BOTTLE_BEE 31
#define SOUND_BOTTLE_WOLF 32
#define SOUND_BOTTLE_BEAR 33
#define SOUND_BOTTLE_GIRAFFE 34
#define SOUND_BOTTLE_PENGUIN 35
#define SOUND_BOTTLE_LION 36
#define SOUND_BOTTLE_HEDGEHOG 37
#define SOUND_BOTTLE_STAG 38
SoftwareSerial ss_mp3_player(MP3_RX_PIN, MP3_TX_PIN);
void mp3_play(uint8_t track){
// この定義だと再生曲数は255に限定されるので注意。
// Upper-Byte, Lower-Byteをちゃんと処理してやれば65535曲まで対応可能だが、
// 容量的にそこまで使うことはほぼないと思われる。
// なぜか、指定したトラックに対して以下のようにファイルが再生される
// 指定:1 -> 再生:01.mp3
// 指定:2 -> 再生:不可
// 指定:3 -> 再生:02.mp3
// 指定:4 -> 再生:不可
// 指定:5 -> 再生:03.mp3
// 指定:6 -> 再生:不可
// 指定:7 -> 再生:04.mp3
// 指定:8 -> 再生:不可
// ...
// 原因は不明だが、とりあえず上記仕様に合わせるようにしておく
uint8_t temp_track = track*2 - 1;
uint8_t play[6] = {0xAA,0x07,0x02,0x00,temp_track,temp_track+0xB3};
ss_mp3_player.write(play,6);
Serial.print(F("MP3 Play: No."));
Serial.println(track);
}
void mp3_stop(){
unsigned char stop[4] = {0xAA,0x04,0x00,0xAE};
ss_mp3_player.write(stop,4);
Serial.println(F("MP3 Stop"));
}
void mp3_set_volume(uint8_t vol){
uint8_t volume[5] = {0xAA,0x13,0x01,vol,vol+0xBE};
ss_mp3_player.write(volume,5);
}
void control_sound(){
if(prev_state != state){
switch(state){
case STATE_READ_READY:
break;
case STATE_WRITE_READY:
if(medal_id != 0){
switch(medal_id){
case 1: mp3_play(SOUND_MEDAL_STAG); break; // クワガタ
case 3: mp3_play(SOUND_MEDAL_HOPPER); break; // バッタ
case 4: mp3_play(SOUND_MEDAL_LION); break; // ライオン
case 5: mp3_play(SOUND_MEDAL_TIGER); break; // トラ
case 6: mp3_play(SOUND_MEDAL_CHEETAH); break; // チーター
case 8: mp3_play(SOUND_MEDAL_GORILLA); break; // ゴリラ
case 9: mp3_play(SOUND_MEDAL_ELEPHANT); break; // ゾウ
case 26: mp3_play(SOUND_MEDAL_WHITE_BEAR); break; // シロクマ
case 27: mp3_play(SOUND_MEDAL_PENGUIN); break; // ペンギン
case 28: mp3_play(SOUND_MEDAL_SCORPION); break; // サソリ
case 29: mp3_play(SOUND_MEDAL_BEE); break; // ハチ
case 32: mp3_play(SOUND_MEDAL_BUFFALO); break; // ウシ
case 34: mp3_play(SOUND_MEDAL_PANDA); break; // パンダ
case 35: mp3_play(SOUND_MEDAL_KANGAROO); break; // カンガルー
//case XX: mp3_play(SOUND_MEDAL_SHARK); break; // サメ
//case XX: mp3_play(SOUND_MEDAL_WHALE); break; // クジラ
case 122: mp3_play(SOUND_MEDAL_SUPER_HOPPER); break; // スーパーバッタ
default: mp3_play(SOUND_MEDAL_OTHERS); // その他
}
}else if(bottle_id != 0){
switch(bottle_id){
case 3: mp3_play(SOUND_BOTTLE_GORILLA); break; // ゴリラ
case 9: mp3_play(SOUND_BOTTLE_PANDA); break; // パンダ
case 15: mp3_play(SOUND_BOTTLE_SHARK); break; // シャーク
case 19: mp3_play(SOUND_BOTTLE_SPIDER); break; // スパイダー
case 23: mp3_play(SOUND_BOTTLE_BEETLE); break; // カブトムシ
case 25: mp3_play(SOUND_BOTTLE_TIGER); break; // トラ
case 31: mp3_play(SOUND_BOTTLE_WHALE); break; // クジラ
case 43: mp3_play(SOUND_BOTTLE_BEE); break; // ハチ
case 49: mp3_play(SOUND_BOTTLE_WOLF); break; // ウルフ
case 51: mp3_play(SOUND_BOTTLE_BEAR); break; // クマ
case 55: mp3_play(SOUND_BOTTLE_GIRAFFE); break; // キリン
case 59: mp3_play(SOUND_BOTTLE_PENGUIN); break; // ペンギン
case 62: mp3_play(SOUND_BOTTLE_LION); break; // ライオン
case 64: mp3_play(SOUND_BOTTLE_HEDGEHOG); break; // ハリネズミ
case 94: mp3_play(SOUND_BOTTLE_STAG); break; // クワガタ
default: mp3_play(SOUND_BOTTLE_OTHERS); // その他
}
}
break;
case STATE_WRITING:
if(medal_id != 0){
mp3_play(SOUND_MEDAL_WRITING);
}else if(bottle_id != 0){
mp3_play(SOUND_BOTTLE_WRITING);
}
break;
default:
;
}
}
}
////////////////////////// IR送信関係 //////////////////////////
// 対向の赤外線受信モジュール(OSRB38C9AA)の中心周波数
// (キャリア周波数)が37.9kHzなので、それに応じて定数を定める
// IR_SHORT_DURATION_MICRO_SECは、
// 16MHzのArduinoなら10、8MHzのArduinoなら5ぐらい。
#define IR_SHORT_DURATION_MICRO_SEC 10
#define IR_LONG_DURATION_MICRO_SEC 600
#define NUM_IR_ON_OFF 23 // 26μs/回 * 23回 = 598μs
#define LEN_IR_DATA 8 // 今回は8bitで表現される整数値をおくる
#define IR_SEND_RETRY 5 // 一回で受信してもらえる保証がないので、繰り返し送るようにする
void send_bit_0(){
digitalWrite(IR_TX_PIN, LOW);
delayMicroseconds(IR_LONG_DURATION_MICRO_SEC);
}
void send_bit_1(){
// 1回のループでおよそ26μsになるようにIR_SHORT_DURATION_MICRO_SECを設定。
// (←うまく動かないときはまずこの値を調整する)
// ループの全体でおよそIR_LONG_DURATION_MICRO_SEC(=600)になるようにする
for(uint8_t i=0;i<NUM_IR_ON_OFF;i++){
digitalWrite(IR_TX_PIN, HIGH);
delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
digitalWrite(IR_TX_PIN, LOW);
delayMicroseconds(IR_SHORT_DURATION_MICRO_SEC);
}
}
void send_ir(uint8_t number){
// スタートビットの送信
send_bit_1();
// 整数値(8bit)の送信
for(uint8_t i=0;i<LEN_IR_DATA;i++){
uint8_t send_bit = bitRead(number, i); // 8bitの右端から送信する
if(send_bit == 1){
send_bit_1();
}else{
send_bit_0();
}
}
// ストップビットの送信
send_bit_1();
send_bit_0();
send_bit_1();
send_bit_0();
// 送信完了表示
Serial.print(F("Sent: "));
Serial.println(number);
}
////////// メイン処理 ////////////////////////////////////////////////////////////
void setup(){
Serial.begin(115200);
pinMode(BUTTON_INPUT_PIN, INPUT_PULLUP);
pinMode(MEDAL_CLOCK_PIN, INPUT_PULLUP);
pinMode(MEDAL_DATA_PIN, INPUT_PULLUP);
pinMode(MEDAL_CTRL_PIN, INPUT_PULLUP);
pinMode(BOTTLE_A_PIN, INPUT_PULLUP);
pinMode(BOTTLE_B_PIN, INPUT_PULLUP);
pinMode(BOTTLE_C_PIN, INPUT_PULLUP);
pinMode(IR_TX_PIN, OUTPUT);
pinMode(BOTTLE_LED_PIN, OUTPUT);
// MP3プレイヤーセットアップ
ss_mp3_player.begin(9600);
mp3_set_volume(SOUND_VOLUME_DEFAULT);
// フルカラーLED(ボタン)初期化
pixels.begin();
pixels.clear();
pixels.show();
// LEDバー初期化
bar.setLevel(0);
// LEDセットアップ
digitalWrite(IR_TX_PIN, LOW);
digitalWrite(BOTTLE_LED_PIN, LOW);
Serial.println(F("Read Stage: Before"));
print_bottle_read_stage(); // Debug
mp3_play(SOUND_POWER_ON);
}
void loop(){
unsigned long now_ms = millis();
uint8_t input_port = PINB; // ピン8〜13のデジタル入力の同時読み取り
//////////////////// オーメダル読み取り処理 ////////////////////
medal_clock_value = bitRead(input_port, 0);
medal_data_value = bitRead(input_port, 1);
medal_ctrl_value = bitRead(input_port, 2);
// CTRL信号での状態遷移管理
if(prev_medal_ctrl_value == 1 && medal_ctrl_value == 0){
medal_read_stage = MEDAL_READ_STAGE_1ST;
Serial.println(F("Read Stage: 1st"));
}else if(prev_medal_ctrl_value == 0 && medal_ctrl_value == 1){
medal_read_stage = MEDAL_READ_STAGE_BEFORE;
Serial.println(F("Read Stage: Before"));
temp_medal_id = 0;
bit_index = 0;
reset_count();
}
// CLOCKとDATAからの値読み込み
if(prev_medal_clock_value != medal_clock_value || prev_medal_data_value != medal_data_value){
//Serial.print(medal_clock_value);
//Serial.print(medal_data_value);
//Serial.println(medal_ctrl_value);
// CLOCKが1のときのDATAの値を使用する
if(medal_clock_value == 1){
if(medal_data_value == 0){
saved_one_counter = one_counter;
zero_counter += 1;
one_counter = 0;
}else{
saved_zero_counter = zero_counter;
zero_counter = 0;
one_counter += 1;
}
}
switch(medal_read_stage){
case MEDAL_READ_STAGE_BEFORE:
// この段階では、何もしない。CTRL信号での状態遷移を待つ
;
break;
case MEDAL_READ_STAGE_1ST:
if(saved_zero_counter == 8 && one_counter == 1){
medal_read_stage = MEDAL_READ_STAGE_2ND;
Serial.println(F("Read Stage: 2nd"));
}
break;
case MEDAL_READ_STAGE_2ND:
if(saved_one_counter == 2 && zero_counter == 1){
medal_read_stage = MEDAL_READ_STAGE_3RD;
Serial.println(F("Read Stage: 3rd"));
reset_count();
}
break;
case MEDAL_READ_STAGE_3RD:
if(saved_one_counter == 0 && zero_counter == 1){
// 0確定
bit_index++;
reset_count();
}else if(saved_one_counter > 1 && zero_counter == 1){
// 1確定
uint8_t num_of_1 = saved_one_counter - 1;
for(uint8_t i=0;i<num_of_1;i++){
temp_medal_id = temp_medal_id | (B10000000 >> bit_index);
bit_index++;
}
reset_count();
}
// 読み取り完了
if(bit_index == 8){
if(prev_medal_id != temp_medal_id){
medal_id = temp_medal_id;
Serial.print(F("Medal ID: "));
Serial.println(medal_id);
bottle_id = 0; // このタイミングでボトルのIDはリセットしておく
bit_index = 0;
medal_read_stage = MEDAL_READ_STAGE_AFTER;
Serial.println(F("Read Stage: AFTER"));
state = STATE_WRITE_READY;
prev_medal_id = medal_id;
}
}
break;
case MEDAL_READ_STAGE_AFTER:
// 本来なら後処理のデータを読み取る必要があるが、今回は省略する
break;
default:
;
}
}
prev_medal_clock_value = medal_clock_value;
prev_medal_data_value = medal_data_value;
prev_medal_ctrl_value = medal_ctrl_value;
//////////////////// フルボトル読み取り処理 ////////////////////
pin_A = bitRead(input_port, 3);
pin_B = bitRead(input_port, 4);
pin_C = bitRead(input_port, 5);
//if((before_pin_A != pin_A) || (before_pin_B != pin_B) || (before_pin_C != pin_C)){
// Serial.print(pin_A);
// Serial.print(pin_B);
// Serial.println(pin_C);
//}
switch(bottle_read_stage){
case BOTTLE_READ_STAGE_BEFORE:
if(before_pin_A == PIN_OFF & pin_A == PIN_ON){
bottle_group = BOTTLE_GROUP_A;
Serial.println(F("Fullbottle Read Group A Ready."));
bottle_read_stage++;
print_bottle_read_stage(); // Debug
}else if(before_pin_C == PIN_OFF && pin_C == PIN_ON){
bottle_group = BOTTLE_GROUP_C;
Serial.println(F("Fullbottle Read Group C Ready."));
bottle_read_stage++;
print_bottle_read_stage(); // Debug
}
break;
case BOTTLE_READ_STAGE_1ST: // 1
case BOTTLE_READ_STAGE_2ND: // 2
case BOTTLE_READ_STAGE_3RD: // 3
// OFF-ON変化のカウント(チャタリング前提)
off_on_count();
if((bottle_group == BOTTLE_GROUP_A && before_pin_A == PIN_OFF && pin_A == PIN_ON) ||
(bottle_group == BOTTLE_GROUP_C && before_pin_C == PIN_OFF && pin_C == PIN_ON)){
if(big_digit_counter > 0 || little_digit_counter > 0){
//print_digit_counter(); // Debug
fix_value();
reset_digit_counter();
bottle_read_stage++;
print_bottle_read_stage(); // Debug
}
}
delay(BOTTLE_READER_DELAY_MS);
break;
case BOTTLE_READ_STAGE_4TH: // 4
off_on_count();
if((bottle_group == BOTTLE_GROUP_A && before_pin_A == PIN_ON && pin_A == PIN_OFF) ||
(bottle_group == BOTTLE_GROUP_C && before_pin_C == PIN_ON && pin_C == PIN_OFF)){
fix_value();
//print_digit_counter(); // Debug
reset_digit_counter();
// ボトルの特定
identify_bottle();
medal_id = 0; // このタイミングでメダルのIDはリセットしておく
digitalWrite(BOTTLE_LED_PIN, HIGH);
state = STATE_WRITE_READY;
bottle_read_stage++;
print_bottle_read_stage(); // Debug
}
delay(BOTTLE_READER_DELAY_MS);
break;
case BOTTLE_READ_STAGE_AFTER: // 5
if(pin_A == PIN_OFF && pin_B == PIN_OFF && pin_C == PIN_OFF){
reset_counter++;
}else{
reset_counter = 0;
}
if(reset_counter > RESET_COUNT){
mp3_play(SOUND_BOTTLE_EJECT);
digitalWrite(BOTTLE_LED_PIN, LOW);
reset_counter = 0;
bottle_read_stage = BOTTLE_READ_STAGE_BEFORE;
bottle_group = BOTTLE_GROUP_NONE;
reset_digit_counter();
for(int i=0;i<4;i++){
read_values[i] = 0;
}
state = STATE_READ_READY;
bottle_id = 0;
print_bottle_read_stage(); // Debug
}
delay(BOTTLE_READER_DELAY_MS);
break;
default:
;
}
before_pin_A = pin_A;
before_pin_B = pin_B;
before_pin_C = pin_C;
//////////////////// ボタン処理 ////////////////////
btn_state = digitalRead(BUTTON_INPUT_PIN);
if(state == STATE_WRITE_READY && prev_btn_state == BTN_OFF && btn_state == BTN_ON){
Serial.println(F("Pushed."));
if(medal_id != 0 || bottle_id != 0){
state = STATE_WRITING;
writing_start_time = now_ms;
}
}
prev_btn_state = btn_state;
//////////////////// 時間経過処理 ////////////////////
if(state == STATE_WRITING){
if(medal_id != 0 && now_ms - writing_start_time >= WRITING_TIME_MEDAL_MS){
for(uint8_t i=0;i<IR_SEND_RETRY;i++){
send_ir(medal_id);
delay(100);
}
state = STATE_READ_READY;
medal_id = 0;
prev_medal_id = 0;
}else if(bottle_id != 0 && now_ms - writing_start_time >= WRITING_TIME_BOTTLE_MS){
// ゴリラとパンダだけは、他のメダルのIDとのバッティングを防ぐため、番号を振り替えて送信する
if(bottle_id == 3){
bottle_id = 8; // ゴリラのメダルIDと同じ
}else if(bottle_id == 9){
bottle_id = 34; // パンダのメダルIDと同じ
}
for(uint8_t i=0;i<IR_SEND_RETRY;i++){
send_ir(bottle_id);
delay(100);
}
state = STATE_READ_READY;
bottle_id = 0;
}
}
////////// 音声処理 ////////////////////
control_sound();
////////// LED発光処理(ボタン&バー) ////////////////////
control_led(now_ms);
prev_state = state;
}
ディスカッション
コメント一覧
万丈ライドウォッチのときに比べて音声再生までのディレイが減っているような気がするのですが、何か変えた部品とかがあるのでしょうか?
P 様
日中は仕事があり、何度コメントを頂いてもすぐに返事できるものではありません。ご了承頂けますと幸いです。
部品に変更はありません。ただ、使っているうちに、「SDカードへのMP3ファイルの追加・削除を何度も繰り返していると、どうもディレイが大きくなってくるようだ」ということが体感的にわかってきました。特に、ネオディケイド・ネオディエンドライドウォッチを作っているときにそれが顕著に現れてきました。理由は不明ですが、以後はディレイを感じるようになったら、一度SDカードを上書きフォーマットして真っさらにしてから、改めてMP3ファイルの全コピーを行うようにしています。これで極端に大きなディレイが発生することはなくなった気がしますが、それでも多少のディレイは依然として発生するようなので、完全な対応方法は今も不明です。少なくとも自分の環境ではこれで多少マシになりますが、他の人の環境でも同様に適用できるかどうは不明ですので、ご了承ください。
コメントの仕組みをわかっておらず、反映がなされていないと思い込んでいました。何度もコメントしてしまい申し訳ありません。返答を急かすつもりではありませんでした。
返信ありがとうございます。いつも楽しく見させていただいています。SDカードにもキャッシュデータ的なものがあるのですかね。カードに対するアクションが内部に積もっているのかもしれません。素人なのでよくわかりませんが。何はともあれおかげさまで疑問が解決しました。この度はご返答ありがとうございました。参考にさせていただきます。
P 様
こちらこそ、コメントの仕組みがわかりにくいUIとなっており、申し訳ありませんでした。今回の件を受け、試験的にですがコメント受付の条件を緩めてみました。今は、即時反映になっていると思います。これでもしスパムや誹謗中傷が増えてしまうことがあれば、また元に戻すかもしれません。
自分がMac環境だからかもしれませんが、SDカード上でファイルを削除しても、それが「SDカード上のゴミ箱」に入っているように見えます。それがなぜ影響してしまうのかはよくわかりませんが、それも含めて消そうと思うと、今のところ上書きフォーマットぐらいしか手が思いつきません。
あと、使うモジュールを変えるという手もあるかと思います。例えば、DFPlayerの代わりに、今回プログライズライター側で初めて使用したMP3ボイスモジュールを使うとか。こちらは、容量が小さかったり、また開発環境によっては変な挙動を示すこともありますが、ディレイという点では今のところ体感ではあまり感じていません。今後、使用を重ねれば色々出てくる可能性もありますが。
返信ありがとうございます。僕もわからないことだらけなので、やっぱり色々試してみようと思います。ありがとうございました。
SDカードにプログライズキーの全音声が入っているとのことですが、どのようにオリジナルのキーから音声を抽出されたのでしょうか?
自分がよくやる簡単な方法は、スマートフォンの録音アプリ(例えばiPhoneならボイスメモ)を使って録音することです。録音した音声をPCに転送して、PCでMP3ファイルに変換しています。多少雑音が入ることもありますが、これで済む場合も多いです。
どうしてもこの方法では品質的に満足できない場合には、玩具のスピーカー出力をPCのマイク入力にダイレクトに繋げてPCで録音するということもありますが、面倒ですしそこまで品質が向上するわけでもないので、あまりやりません。
コメント失礼します。
オリジナル音声のプログライズキーを作りたいのですが、プログラミングはどのようなものを学べばよいのでしょうか?
38 様
どれぐらいのものを作りたいかにもよります。例えば食玩仕様のようなシンプルなもので良いのなら、手段によってはプログラミング不要で作れたりもします。
DX仕様のやや複雑なものを作りたいなら、プログラミングが必要になります。
プログラミング言語も色々ありますが、電子工作用途であれば、Arduinoで使用することになるC言語を使うのが、Web上での情報も豊富で良いのではないかと思います。
コメント失礼します。
音声処理について、2つ以上の音声を連続で再生させる処理はどういう仕組みなのでしょうか。
ライジングホッパーキーならば「飛び上がライズ」「ライジングホッパー」「a jump …」というように3つの音声が連続で再生されると思うのですがヒダカ様のソースコードではそのようになっているのでしょうか。
とても初歩的なことかもしれませんが教えていただければ幸いです。
なんこつ様
いえ、初歩的ではないと思います。とはいえ、このプログライズキーにおいては、難しいことはしていません。「飛び上がライズ」「ライジングホッパー」「a jump …」の3つの音声を予め一つのMP3ファイルにまとめていて、それを再生しているだけです。
したがって、「ソースコードではそのようになっているのでしょうか」という質問の答えは「いいえ」なのですが、「3つに分けた音声ファイルを順に再生する」という処理自体は、実は今改造しているガイアメモリの方で出てきます。なので、もし必要であれば、そちらを参照頂くのが良いと思います。今公開に向けて準備中ですので、多分、一ヶ月以内には公開できると思います。今しばらくお待ちください。
返信ありがとうございます。
最近Arduinoを触り始めたのでとても参考になります!
また質問をすることがありましたらよろしくお願いします。
コメント失礼します。
プログライズキー側のソースコードを参考にseeeduino xiaoでプログライズキー(IR関連無,INITIALIZE無,単色発光,)を作成しようとしたのですが、[メインの状態遷移管理 658行 (uint8_t input_port = PIND; // ピン0〜7のデジタル入力の同時読み取り)]がエラーメッセージ[‘PIND’ was not declared in this scope]が表示されコンパイルできませんでした。
Arduino pro miniにはPIND(ポートD入力レジスタ)があり、seeeduino xiao には該当するものがないのでエラーが出たと考えられます。
Seeeduino xiao で稼働させたいのですが、どのようにソースコードを修正すればいいでしょうか?
hoge様
そうですね、移植性を考えたらあまり良いコードではなかったです。入力同時読み取りを重視してこんな感じで書いていますが、おそらく素直に
sw[0] = digitalRead(SW_A_PIN);
sw[1] = digitalRead(SW_B_PIN);
sw[2] = digitalRead(SW_C_PIN);
で動作すると思います(uint8_t input_port = PIND; は不要)
返信、回答ありがとうございます。
seeeduino xiao で3スイッチともに稼働できました。
コメント失礼します。
初めまして、はざーどと申します。
当方Arduinoもプログラミングも電子工作も知識がないものの、DXに準拠した仕様のオリジナル音声プログライズキーを作製したいと考えています。
ヒダカ様のTwitterも拝見しています。
過去にツイートで「プログライズライターの記事内のソースコードから赤外線センサー関連の部分を抜けばただのDXプログライズキーに準拠したコードになる」という旨を仰られていました(うろ覚えのままコメントしてますのでややニュアンスが違うかもしれません)が、具体的に何行目から何行目を抜くことで、、、というより、要はソースコードをどのように改変するとDX準拠のコードになるのでしょうか?
注釈があるので見当がつかないこともないのですが、前述の通り知識がないのであまり自信がありません。
また、書き込みギミックによる変身音などの切り替えの都合上、ソースコードの中に大量の変身音をそれぞれ区別するための定義?の部分がありますが、これも変身音ひとつ分に絞ってしまえばいいのでしょうか?
また、回路においても記事内の画像の構成からそのまま赤外線センサを抜くだけでいいのでしょうか。
知識不足故に言葉の用途違いで質問の意図が伝わらないなどありましたら申し訳ありません。
一度に複数の質問をした故に読み苦しい文章になっているかもしれませんが、差し支えなければご教示くださると幸いです。よろしくお願いします。
はざーど様
すみません、一つ一つを丁寧に解説している余裕がないので、簡単にポイントを羅列します。
> 具体的に何行目から何行目を抜くことで、、、というより、要はソースコードをどのように改変するとDX準拠のコードになるのでしょうか?
要した結果が「赤外線センサー関連の部分を抜く」なのですが、具体的には以下の箇所になります。
・まず、1214〜1218行目のようなloop処理の分岐は必要なくなり、loop_subの方が完全に不要になります。よって、loop_subの中身である962〜1181行目は不要です
・赤外線に関する64行目〜75行目も不要です
大きく抜く部分はこの2箇所かと思います。これで動かしてみて、エラーが出たりうまく動かないところがあれば、あとは地道にデバック頂くのが良いと思います。
> ソースコードの中に大量の変身音をそれぞれ区別するための定義?の部分がありますが、これも変身音ひとつ分に絞ってしまえばいいのでしょうか?
そうですね。音声切り替えしないのであれば、#define SOUND_OFFSET_XXXXX が並んでいるところは、先頭の #define SOUND_OFFSET_HOPPER 5 の部分だけ残せば他は不要です。
> 回路においても記事内の画像の構成からそのまま赤外線センサを抜くだけでいいのでしょうか。
はい、IR Receiverがつながっている配線を抜くだけで良いです。
返信ありがとうございます。挑戦してみようと思います!