M5StickVでデレステを自動化した
始めに
自動化が好きなのでデレステの自動化を再び試みることにします。
↑の動画で解説しきれなかった部分を補足説明しています。
まずは動画を見よう、な!
一応前回の記事の続きです。
材料
・M5StickV
今回の主役です。
真似する対象がまだまだ少ないのでみんな買って遊んで作例増やして(切実)。
・M5Stack
実は今回いなくてもいい子ですが、紹介したかったので無理やり使った。
電子工作の入門これ一台。
・リレータッチボード×5個
前回同様、これがいなくては始まりません。実は裏面の金属パッドをリレーでGNDに落としているだけなので、アルミホイル等で自作しようと思えばできそうですね。買った方が早いけどな!
・74HC595(8bitシフトレジスタ)×1(相当品でOK)
なぜか家にいっぱいあったので。M5StickVから直接出力をすればいらない子ではありますが、扱い慣れてるので。
・LED(抵抗内蔵型がオススメ)
出力のパイロットランプとして使ってるだけなのでなくても構いません。
ただあった方が試作はやりやすいです(思った出力が出てるか目で見てわかるので)。抵抗内蔵型を選んでおくとブレッドボードもごちゃごちゃになりにくいです(ならないとは言ってない)。
・ジャンパワイヤ(少々)
・ブレッドボード(適当なサイズ)
・両面テープ(普通のやつ)
・カメラスタンド(↓こういう小さいやつで十分)
・セラミックコンデンサ(0.1μF)
動画に入れるの忘れてました。シフトレジスタのVCC-GND間に入れましょう。まぁ最悪無くても動くとは思います(オイ)。
リレーをそこそこ使うので、電源にバイパスコンデンサを入れといた方がベターかもしれません。今回忘れましたが。
回路および配線
ブレッドボード配線
M5StickVとM5Stackの間はおなじみのGroveケーブルで繋ぎます。
M5Stackとシフトレジスタの間はの信号線(SER,SRCLK,RCLK)が3本+電源(VCC,GND)の2本、簡単ですね。
後はシフトレジスタの出力ピンをリレータッチボードのENにそれぞれ繋ぐだけです。出力確認用のLEDはそれと並列に繋ぎましょう。(わかんなかったらなくてもいいです。)
リレーのコモンを切るためのスイッチを挟んでますが、なくてもいいです。
動作説明
M5StickVの動作
リズムアイコンを認識(しきい値で判定)。
認識領域を5つに分けてその範囲にアイコンがいれば相当するbitに1を書き込んで8bitのデータを作成します。(上位3bitは常に0)
この場合は「00010001」が作成されます。
普通のリズムアイコン(単発)と長押し(ロング+軌跡)では色が違うので判定をわけていますが、欲しい情報は結局タッチボードがON/OFFすべきかだけなので、最終的にOR(論理和)をとって合体します。
後は8bitのデータをM5Stackにシリアル通信で渡すだけ。ね、簡単でしょ?
M5Stackの動作
M5StickVから受け取った8bitのデータをシフトレジスタ(74HC595)に渡します。シフトレジスタ関してはこのページが詳しいので見て。
後は勝手にシフトレジスタがシリアル通信されたデータをパラレル出力してくれます。やったぜ!
見ての通り、M5Stackは信号をあくまで8bitのデータを中継しているだけなので、M5StickV側で直接出力を制御すれば不要です。
I2CのI/Oエキスパンダとか使えばいいと思う。使ったことないけど。
プログラム
結果的にやってることはごくシンプルなので、プログラム的に別段高度なことはやってません。
・M5StickVのプログラム
しきい値判定して8bitのデータをシリアルで送信
import sensor
import image
import lcd
import utime
from fpioa_manager import fm
from machine import UART
fm.register(35,fm.fpioa.UART2_TX,force = True)
fm.register(34,fm.fpioa.UART2_RX,force = True)
lcd.init()
lcd.rotation(2)#ディスプレイ表示の向き
sensor.reset()
sensor.set_pixformat(sensor.RGB565)
sensor.set_framesize(sensor.QVGA)
sensor.set_brightness(1)#カメラの明るさ
sensor.set_saturation(0)#カメラの飽和度
sensor.set_contrast(2)#カメラのコントラスト
sensor.run(1)
red_threshold = (100, 40, 2, 82, -19, 22)#通常ノーツ(赤)の閾値
yellow_threshold = (100, 53, -12, 46, -39, 61)#ロングノーツ(黄)の閾値
uart_Port = UART(UART.UART2,115200,8,None,1,timeout = 1000,read_buf_len = 4096)#UART設定
b = 0b00000000#送信データ初期化
y_Low = 90
y_High = 115
while True:#無限ループってこわくね?
img = sensor.snapshot()#画像オブジェクト作成
#画面に認識領域用の矩形を描画
img.draw_rectangle(0 ,y_Low ,69 ,25 ,color = (0,255,0))
img.draw_rectangle(69 ,y_Low ,54 ,25 ,color = (0,255,0))
img.draw_rectangle(133 ,y_Low ,54 ,25 ,color = (0,255,0))
img.draw_rectangle(197 ,y_Low ,54 ,25 ,color = (0,255,0))
img.draw_rectangle(251 ,y_Low ,69 ,25 ,color = (0,255,0))
blobs_r = img.find_blobs([red_threshold])
blobs_y = img.find_blobs([yellow_threshold])
b = 0b00000000#送信ビットデータ初期化
if blobs_r:
for r in blobs_r:
if (r[2] * r[3] < 180):#幅*高さで面積を出し、小さいものは除外
continue#forの頭に戻る
if (y_Low < r[6] and r[6] < y_High):#認識領域の中心y座標blob[6]が緑枠内のとき
tmp = img.draw_rectangle(r[0:4],color=(128,0,0))#矩形を描画
tmp = img.draw_cross(r[5], r[6],color=(128,0,0))#十字を描画
c = img.get_pixel(r[5], r[6])
if r[5] < 68:#左からレーン1
b = b | 0b00000001
continue
if 69 < r[5] and r[5] < 123:#レーン2
b = b | 0b00000010
continue
if 133 < r[5] and r[5] < 187:#レーン3
b = b | 0b00000100
continue
if 197 < r[5] and r[5] < 241:#レーン4
b = b | 0b00001000
continue
if 251 < r[5]:#レーン5
b = b | 0b00010000
if blobs_y:
for y in blobs_y:
if (y[2] * y[3] < 240):#幅*高さで面積を出し、小さいものは除外
continue#forの頭に戻る
if (y_Low < y[6] and y[6] < y_High):#認識領域の中心y座標blob[6]が緑枠内のとき
tmp = img.draw_rectangle(y[0:4],color=(128,0,0))#矩形を描画
tmp = img.draw_cross(y[5], y[6],color=(128,0,0))#十字を描画
c = img.get_pixel(y[5], y[6])
if y[5] < 68:#左からレーン1
b = b | 0b00000001
continue
if 69 < y[5] and y[5] < 123:#レーン2
b = b | 0b00000010
continue
if 133 < y[5] and y[5] < 187:#レーン3
b = b | 0b00000100
continue
if 197 < y[5] and y[5] < 241:#レーン4
b = b | 0b00001000
continue
if 251 < y[5]:#レーン5
b = b | 0b00010000
uart_Port.write(b.to_bytes(1,'big'))
lcd.display(img)#ディスプレイ表示
print(bin(b))
・M5Stackのプログラム
シリアル通信で受け取った8bitデータをシフトレジスタへ出力。
1byte(8bit)のデータをシリアルで送受信するのはクソ簡単です。2byte以上だと一気にめんどくさくなるんですが。
#include <M5Stack.h>
const int SER = 2; // SERピンの番号宣言
const int SRCLK = 5; // SRCLKピンの番号宣言
const int RCLK = 26; // RCLKピンの番号宣言
void setup() {
M5.begin();
M5.Power.begin();
Serial.begin(115200);//AruduinoIDEのUSBシリアルモニタ用
Serial1.begin(115200, SERIAL_8N1, 21, 22);// M5StickVとのシリアル通信用
dacWrite(25, 0);// スピーカノイズ対策にOFF(25番ピンは使用しない)
initHc595();// シフトレジスタを初期化
}
void loop() {
if (Serial1.available()) {
uint8_t b;// 符号なし8bit整数
b = Serial1.read();// M5StickVからのデータ読み込み
Serial.println(b, BIN);// USBシリアルモニタに表示(二進数)
outputOneByte(b, MSBFIRST);// シフトレジスタへ出力(MSBから)
}
}
// 74HC595の制御用ピンおよび74HC595を初期化する
void initHc595()
{
pinMode(SER, OUTPUT); // 74HC595制御用ピンを出力モードにする
pinMode(SRCLK, OUTPUT);
pinMode(RCLK, OUTPUT);
digitalWrite(SRCLK, LOW); // SRCLKとRCLKをLOWにする
digitalWrite(RCLK, LOW);
outputOneByte(0, LSBFIRST); // 0を送信して74HC595のパラレル出力ピンを全て0にする
}
// 74HC595に1バイトを送信する
// valが送信するデータ
// bitOrderにLSBFIRSTを指定するとLSBファーストで送信
// bitOrderにMSBFIRSTを指定するとMSBファーストで送信
void outputOneByte(uint8_t val, uint8_t bitOrder)
{
shiftOut(SER, SRCLK, bitOrder, val); // シフトレジスタにデータを送信
digitalWrite(RCLK, HIGH); // RCLKを立ち上げる(パラレル出力)
digitalWrite(RCLK, LOW ); // RCLKを立ち下げる(次の出力のため)
}
感想
今回の動画で一番言いたかったことはこれ。
失礼、間違えました。こっちです。
画像認識と言えばなんかごっついGPUとか積んだハイスペックなPCでゴリゴリやるイメージでしたが、このM5StickVを使うとその認識が変わります。
今回やってることは結局既存の明るさセンサや色センサでも可能なことに過ぎません。
アイコンを感知してON/OFFを出力してくれる……コンピュータというより、もはや”部品”と言えるでしょう。電子工作の世界を広げてくれる新たな部品「画像認識」、色々使って遊んでいきたいですね。
参考文献+偉大な先駆者
最後に
ここまで読んでくださり、(途中で飛ばした人も)ありがとうございます。
今回デレステを一応クリア可能なことは示せました。偉大な先駆者様方のおかげです。フリック入力ができないのが最大にして唯一の欠点ではありますが、ソレノイドやサーボでタップやフリックを実現しようとするとどうしてもメカニカルな機構が必要になり、それに伴って設計や製作も結構大変です。(特に私のように回路は組めるけどメカが苦手な人にとっては特に)。
カメラをセットして画面にタッチボードを貼り付けるだけなら割とできそうじゃん?と思ってもらえれば幸いです。ただし真似するときはあまり本気でやりすぎないようにしてくださいね。BANされても責任はとれませんよ。
一応今回でデレステの自動化は一区切りですが、また何か思いついたらやるかもしれません。
長々とお付き合いありがとうございました。
以上
この記事が気に入ったらサポートをしてみませんか?