SNACKSって?
河本が参加している「インスタ部」が主催しているグループ展の一つ「SNACKS vol.2」が2019年4月13日~14日開催された。
他にもHOMEWORKSというグループ展があるが、こちらの作品が大型化し、作品を作るハードルが高くなったため、「スナック感覚で楽しめるイベントがあるといいね」となって、「基本的にテーブルの上で完結する作品、実験作もOK」という縛りを設けたイベント。今回が2回目。
今回、人間から河本と佐々木が参加したので、イベントや作品について説明する。
ちなみに、vol.2ではそのことを言い忘れたため大型化してしまったらしい。
hoehoeさんいわく「言い忘れたなー。ま、いいか。」とのこと。
他の出展者のレポート
「寄席の箱」というブラウン管TVを使ったデジタルアート作品を展示しました
インスタ部(忘れた頃にアーカイブが上がるかも)
ほかにもあったら教えてください。
河本の作品
「連打ゲーム(mbedとRS485の習作)」を作った。ソースはbitbucketに上げてます。
使っているモジュールも上げているので、要ライセンス確認。
ハードウェア構成
サーバー x1
NUCLEO-F303K8
SparkFun Transceiver Breakout – RS-485
Adafruit Mini 0.8″ 8×8 LED Matrix w/I2C Backpack – Yellow-Green
クライアント x2
NUCLEO-F303K8
SparkFun Transceiver Breakout – RS-485
マル信無線 MS-30
ネットワークケーブル x2
cat5eのSTPケーブル 1[m]
購入リンク
ST Nucleo Board STM32F303K8T6 – スイッチサイエンス
SP3485搭載RS-485-シリアル変換モジュール – スイッチサイエンス
Adafruit I2C通信の8×8ミニLEDマトリックス基板(緑色) – スイッチサイエンス
MS-30-R0 プッシュスイッチ 1個 マル信無線電機 【通販モノタロウ】 75933076
↑シリコンハウス共立で売ってましたが、eleshopでは見つからず・・・
LANケーブルカテゴリー5e/1000BASE-T対応(STP) ブルー / HLC-TES-RB2-LB
とにかくmbedとRS485を使いたかった
mbedを選んだ理由
10年ほど前、前職の上司から「mbedはいいぞ」と言われたのをずっと覚えていて、Arduinoで作品を作るたびに思い出していた。
Arduinoで本格的なプログラミングをしようとすると、すぐにメモリが溢れたり、処理速度が追いつかなかったりした。
ひとつのブレッドボードで完結したかったため、小ささと手に入りやすさでNUCLEO-F303K8を選んだ(NUCLEO-L432KCが手に入りにくい?)
RS485を選んだ理由
工場のオートメーションでRS485という通信が使われているということも聞いていたのを思い出した。
インスタレーション作品の展示では無線通信は鬼門で、出展者や参加者の持ち込む無線デバイスにより通信が安定しなくなる。
有線通信の選択肢を増やすためにも挑戦することにした。
今回の用途ではEthernetでもOK。
にんげんとふえでの実績もあり、mbedでも使える。
なぜ連打?
河本の作品は、
「アクション(入力)にリアクション(出力)があると気持ちいい」
という原始的な感動を大切にしている。(小さな子供が打楽器を叩いて遊んでいる様子を想像してほしい)
入力を物理デバイスにした場合、最低限の操作感が保証されるので最近よく使う。
シリコンハウス共立で入力デバイスを探し、ジョイスティックのプッシュボタンを見つけた。
コンピュータの構成からネットワークを使った対戦ゲームになるだろうなと思っていたので、
複雑さを感じさせない単純に「押すだけ」のゲームにすることにした。
プッシュボタンは過去にも買って試したことがあるが、押しボタンのために、しっかりとした筐体を作る必要があり、作品には繋がらなかった。(タッパーに穴を開ける程度のスキルしかなかった)
HOMEWORKS2018に出展したフラクタルテレビでレーザーカッターで筐体をつくり、自信をつけていたため採用することにした。
ジョイスティックのプッシュボタンは連打への耐久性があるため、そのまま「連打ゲーム」とした。
対戦ゲームの場合、ゲームのレベルデザインを考えなくても面白いものになるため、面白さへの心配はなかった。
ましてや「速い方が勝ち」という昔からあるゲームルールなので、
一旦ゲーム部分を忘れ、構成要素を詰めていく作業に入る。
mbedのオンラインコンパイラのこと
mbedのバージョンは大きく二つある。(実質mbed OS 2の切り捨て)
・mbed OS 2(mbed classicと呼ばれていた)
・mbed OS 5(リアルタイムOSをサポート)
NUCLEO-F303K8はメモリ容量の関係でmbed OS 2しか対応していなかった。
mbedのオフラインコンパイラ(mbed cli)で作るつもりだったが、mbed OS 5しか対応していないと聞き、諦めた。(今調べたら、できるらしい Working with Mbed CLI)
openFrameworksのノリでガシガシとコードを書き始めたところ、エラーが大量発生。
C++98しか使えないことがわかった。
C++98の98は1998年のこと、30年前の仕様。
VC++6.0で仕事をしていたこともあるため、それを思い出しながら進めた。
(おそらくオフラインコンパイラでは問題ない。Build profiles)
システムの設計
実際には頭の中で設計しただけなのでUMLは後付け。
通信周りのこと
RS485変換チップ
このチップはあまりメジャーなものではなかった。(ググってもあまり情報が出ない)
SP3485のデータシートを睨みながら簡単なエコーバックプログラムを試した。
オシロスコープに反応が現れず、数時間悩んだ後、RXとTXを逆につないでいたことに気づく。(変換チップにとってのRXはマイコンのTXにつなぐべきだった)
この時点での送信プログラムは送信前にRTSをHIGHにし、送信後にしばらく待って(wait)LOWにするというもの。
Serialクラスにもともと付いているフロー制御機能が使えるかと色々試したが、時間が溶けていくため諦めた。
通信頻度を秒間100回(往復)と設定し、最低でも10[ms]以内に1往復することを目標にしていたが、軽くクリアできそうであったためwait入りのプログラムのまま進めることにした。
なんとか低レイヤーの目処が立ち、設計に立ち返る。
半二重通信のため、通信において以下の要件を定義した
・サーバーからポーリングすること、クライアントは返信しかできない
・ポーリング頻度は100[往復/s]を目指す
・パケットをロスしてもゲームの進行に影響がないこと
・パケットの化けを検出できること(パケット化けはパケットロス扱いする)
・習作であることから、汎用の通信フォーマットを設計すること
・通信ライブラリ(特にパケットパーサー)は自動テストを通すこと
通信が肝となりそうだったため、(ドットマトリクス以外では)唯一の出力であるマイコンに載ったLEDを受信時に反転することにした。(後々のデバッグでも役にたった)
RendaCommunicationライブラリの作成
パケットの設計にあたって、データリンク層を定義した。
パケットをバイナリのまま送りたかったが、エスケープ処理などが煩雑なため、テキストとして送ることにした。
Web業界の人間なので、テキストでバイナリを送る場合、真っ先に思いつくのがBase64だが、
生データの可読性が落ちるため、テキスト化した16進数を送ることにした。
テキスト化した16進数はバイナリと比べて容量が2倍になるが、デバッグのしやすさはそれ以上のメリットだと考えた。
RS-485を使うPLC制御用のプロトコルであるModbusもテキスト化した16進数を送っているというのも心強かった。
パケットの化け対策のためにbccという仕組みを導入した。bccは自身以外を全てXORしたもの。
<バイナリパケット> ::= <パケットヘッダ> <パケットボディ> <シリアル通信で実際に送るデータ> ::= <STX> <バイナリパケットを16進数でテキスト化したもの> <ETX>
シリアル通信で送るときはテキストデータをSTX、ETXではさみ、受信するときはSTXとETXの間を読み取る。
パケットヘッダに定義済みのsizeとbccからパケットの破損を確認し、問題なければ正常に受信したものとして扱う。
参考:
シリアル通信でバイト列を扱うときのフレーム構造
RS-232Cとは?
テストコードはMac上で実行するため、C++11を使った。
Makefileを書くことがあまりないため、更新が楽なMakefileを調べつつ組み立てた。
以下Makefile。
CXX = g++ CXXFLAGS = -std=c++11 LDLIBS = -lcppunit SOURCES = $(wildcard *.cpp) OBJECTS = $(SOURCES:.cpp=.o) all : test test : $(OBJECTS) $(CXX) $^ -o $@ $(LDFLAGS) $(LDLIBS) %.o : %.cpp $(CXX) $(CPPFLAGS) $(CXXFLAGS) -o $@ -c $< clean : rm *.o $(EXEC)
参考:
汎用のMakefile ー これひとつで手間いらず ー
必要な機能からパケットヘッダはすぐに決まった。
以下パケット定義。
#pragma pack(push) #pragma pack(1) #define COMMAND_REPLY_MODIFIER 0x01 #define COMMAND_GET_COUNT 0x02 #define COMMAND_GET_COUNT_REPLY (COMMAND_GET_COUNT | COMMAND_REPLY_MODIFIER) struct PacketHeader{ unsigned char from; unsigned char to; unsigned char command; //total packet size unsigned char size; //Block Check Charactor //xor all bytes. //fill zero before block check. unsigned char bcc; }; struct PacketGetCountCommand{ PacketHeader header; }; struct PacketGetCountReply{ PacketHeader header; unsigned char count; }; #pragma pack(pop)
これを16進数テキストに変換し、STXとETXではさんで送受信する。
以下、ログから一部を抜き出したもの。
S[0102020504]
R[020103060d0b]
S[0103020505]
R[030103060403]
パケット定義を元にsizeとbccを無視して読むと
1から2へ、GET_COUNT
2から1へ、GET_COUNT_REPLY 0x0b(10進数で11)
1から3へ、GET_COUNT
3から1へ、GET_COUNT_REPLY 0x03(10進数で3)
という通信内容。読みやすい。
バイト単位の取りこぼしの発生
10[Mbps]まで対応しているチップを使っているため1[Mbps]で通信していたが、複数バイトを送るようになってから、バイト単位での取りこぼしが発生した。
1バイトずつwaitしつつ送ると問題が発生しないため、受信割り込みでの読み出しが間に合わないことによるバッファあふれと判断。
通信時間に余裕があるため、115200[bps]という無難な速度で通信することにした。(結果的にノイズにも強くなるはず)
通信ケーブルの準備
RS485はシールドされたツイストペアのケーブルが必要となる。
入手のしやすさからイーサネット用のSTPケーブルを使うことが多いそうなので、それに習う。
余った信号線はGNDと結線する必要があるらしい。
まっすぐのまま半田付けしようとしてなんども失敗した。
カラゲてから半田付けをすると一発だった。カラゲ大切。
ドットマトリクス
タイトル画面は文字を使うことにしたが、
文字は5×7なので、勝敗結果を文字で表すことが難しいと判断。
頑張って5×8でドット絵を描き、勝った方に寄せて表示することにした。
_____o__ ___ooooo ____ooo_ ___o___o ___ooooo ____ooo_ _____o__ ____ooo_ ________ ________ ________ ________ ___ooooo ____ooo_ _____o__ ____ooo_
消費電力問題
F303K8はPCに繋がないかぎり、100[mA]までしか出せない、という情報があり、ビクビクしていた。
単体動作するマイコンがお互いに通信しているという点にロマンがあるので、どうしてもPCからは切り離したかった。
まずは消費電力の確認。
全点灯実測値で消費電力が170[mA]だった。
LEDマトリクスはダイナミック点灯方式のため、列か行のどちらかを一斉に点灯しているはず(それを避ければ消費電力のピークを避けれるはず)と予想し、一列点灯、一行点灯を両方試した。
結果は予想とは違い、どちらの場合も100[mA]だった。(コンデンサが吸収している?)
考えているだけでは仕方がないので、実際に動かしてみることにした。
170[mA]のパターンはACアダプタでも動作。
300[mA]流すように計算した抵抗を直接Vccにつなぎ、負荷をかけてみる。
普通に動く。
拍子抜け。
ひょっとするとUSBのネゴシエーションの有無が問題で、2[A]出力可能な充電用ACアダプタはUSBホストとしてネゴシエーションを行なっているからかもしれない。
参考:
How To Fire Up F303K8 by Using Mobile Battery
最低限の外装をつくる
MDFの調達
いつも使ってる2.5[mm]のMDFはスーパービバホームの建材コーナーで購入。
サイズはサブロクとよばれるサイズ(900[mm]x1800[mm]、3[尺]x6[尺])なので、レーザーカッターに入るサイズの800[mm]x450[mm]にカットしてもらう。
Tinkercadでのラフ設計
Adobe Illustratorでのデザイン
箱のサイズが決まったため、雛形をMaker Caseで作成する。
新しいアルゴリズムにバグがあったので、Maker Case old versionで作成した。
その後はAdobe Illustratorでパスデータを作成する。
Tinkercadでの仮組み
カットして組み立て
いつもの谷6Fabさんでカット、直前の土日が予約で埋まっていたため、通常営業日ではないにも関わらず月曜に対応してもらった。
店長がレーザーカッターのオペレーションを行ってくれるので安心感がある。
今回は特に手直しもなく一発でカットできた。
木工用ボンドで接着し、輪ゴムで固定。
やっとゲームロジック作り
ステートパターンで実装。
イベントはシンプルに2つだけ。
・パケット受信
・描画
散々頭の中で練った設計だったので、一気に書いた。
以下定義の一部。
状態遷移図、クラス図とあわせて読むとわかりやすい。
struct RendaGameContextInterface{ virtual void setNextState(RendaGameStateBase* inNextState)=0; virtual int getClientTotalCount(size_t inIndex)=0; virtual void clearClientTotalCount(size_t inIndex)=0; virtual void updateClientCount(size_t inIndex,unsigned char inCount,bool inIsCountUp)=0; virtual Adafruit_8x8matrix* getMatrix()=0; }; class RendaGameStateBase{ protected: RendaGameContextInterface* mContext; RendaGameStateBase(RendaGameContextInterface* inContext):mContext(inContext){ } void updateMatrixForBattle(); public: virtual void onBegin()=0; virtual void onEnd()=0; virtual void onUpdateClientCount(size_t inIndex,unsigned char inCount)=0; virtual void onUpdateMatrix()=0; };
システムテスト
設営
当日のトラブル
出展者の友達グループがパンチングマシンぐらいの強さで叩き始めた。(集団心理からか?)
箱の心配をするレベルの強さだったが、直方体なので上からの力には強く、箱は壊れなかった。
念の為に調べるとそのボタンだけあきらかにチャタリングが発生するようになっていた。
左右を入れ替えても同じ結果だったため、原因箇所をボタンと特定。交換した。
まとめ
・オフラインコンパイラがないと不安。
・ゲーム部分は無難に作れた。
・やっぱり可動部の予備は必要。
・RS485は使えるけど、速度を上げるなら割り込みタイミングを気にしてあげる必要がある(マシン性能によっては不可能)
・RS485のRTSのあたりが謎なのでフロー制御と組み合わせれないかを試したい。定石を知りたい。
次回予定など
SNACKSは年二回、HOMEWORKSは年一回のペースでやっていくとのこと。
次はHOMEWORKS2019で8/31(土) 9/1(日)の予定。
次回のSNACKS Vol.3は今年の秋か冬ごろらしい。