小型のM5シリーズなら何でも良いのですが、 画面が必要なくて6軸センサが載っているのでAtom Matrixを使用しました。 6軸センサを使用した機能を使わないなら(後述)Atom liteやM5StickCなど手元にあるものを使えばいいと思います。
GLASS Unit
https://www.switch-science.com/collections/m5stack/products/8866
透明なガラスに青い光が浮かび上がるクールな奴。 もともと射撃時に距離を測定して表示する方法を考えてて、 ダットサイトが理想的なんだけど作るの無理なんだよな~って思ってた時に見てコレダ!ってなって買いました。
LIDAR07 測距(ToF)モジュール
https://wiki.dfrobot.com/TOF_IR_Distance_Sensor_0.2_12m_SKU_SEN0413
Amazonで売ってるToFモジュールの中で12mで4000円くらいとバランスが良かったのでこれを選びました。 残念ながら現在は売り切れです・・・
同じようなモジュールとしてはこちらがいいかもしれません
LIDAR07 測距(ToF)モジュールのライブラリを使用します。
https://github.com/DFRobot/DFRobot_LIDAR07
サンプルからコードをつまむとこんな感じで使えます。
#include "DFRobot_LIDAR07.h"
DFRobot_LIDAR07_IIC LIDAR07(Wire);
uint16_t distance = 0;
void setup(void) {
Wire.begin(26, 32);
M5.begin(true, false, false);
LIDAR07.begin();
LIDAR07.startFilter();
LIDAR07.startMeasure();
if (LIDAR07.getValue()) {
distance = LIDAR07.getDistanceMM();
Serial.printf("Distance:%6dmm ", LIDAR07.getDistanceMM());
}
M5StackシリーズのあるあるとしてM5.begin()でI2C(Wire)を初期化すると 不具合が起きたりするのでM5.begin()と別で初期化した方がいいです。
公式のサンプルコードを改変してスプライトを利用しています
m5-docs https://docs.m5stack.com/en/unit/Glass%20Unit
M5Unit-GLASS/examples/Unit_GLASS_M5Atom/Unit_GLASS_M5Atom.ino at main · m5stack/M5Unit-GLASS · GitHub https://github.com/m5stack/M5Unit-GLASS/blob/main/examples/Unit_GLASS_M5Atom/Unit_GLASS_M5Atom.ino
#include <M5Atom.h>
#include <UNIT_GLASS.h>
M5UnitGLASS display;
LGFX_Sprite sprite(&display);
int width, height;
uint16_t distance = 9999;
void setup(void) {
Wire.begin(26, 32);
M5.begin(true, false, false);
display.init(GPIO_NUM_26, GPIO_NUM_32, 400000u);
display.setRotation(2);
width = display.width();
height = display.height();
Serial.printf("width=%d,height=%d\n", width, height);
sprite.createSprite(width, height);
sprite.fillScreen(TFT_BLACK);
sprite.setTextSize(1);
sprite.setCursor(width / 2, height / 2);
sprite.printf("%dmm",distance);
sprite.pushSprite(0, 0);
}
これで画面の中央に距離が表示されます。
NURFは弾道が山なりなのでグレネードランチャーのサイトをイメージします
単純に距離に応じた目盛りを表示しても面白くないので、 距離に応じて十字が上下してフロントサイトと合わせて照準できるようにしたいと思いました。
全ての描画要素は距離、角度に応じて動かせるようにするためにwidth,heightを使って相対指定にしてあります。 (ただし正確ではない)
あと距離を7セグフォントで表示したかったのですが、 spriteNum.setFont(&fonts::Font7); &fonts::Font7は文字が大きくてUNIT GLASSの解像度には収まらないので、 数字表示用のspriteに描画してから縮小して合成しています。
// setup()内で実行
display.setRotation(2);
width = display.width();
height = display.height();
Serial.printf("width=%d,height=%d\n", width, height);
sprite.createSprite(width, height);
spriteNum.createSprite(width * 2, height / 2);
spriteNum.setCursor(0, 0);
spriteNum.setPivot(width * 2, 0);
// 以下描画処理
// 左右の目盛り
sprite.drawLine(0, 0, 10, 0, TFT_WHITE);
sprite.drawLine(0, height * 0.2, 10, height * 0.2, TFT_WHITE);
sprite.drawLine(0, height * 0.4, 10, height * 0.4, TFT_WHITE);
sprite.drawLine(0, height * 0.6, 10, height * 0.6, TFT_WHITE);
sprite.drawLine(0, height * 0.8, 10, height * 0.8, TFT_WHITE);
sprite.drawLine(0, height, 10, height, TFT_WHITE);
sprite.drawLine(width, 0, width - 10, 0, TFT_WHITE);
sprite.drawLine(width, height * 0.2, width - 10, height * 0.2, TFT_WHITE);
sprite.drawLine(width, height * 0.4, width - 10, height * 0.4, TFT_WHITE);
sprite.drawLine(width, height * 0.6, width - 10, height * 0.6, TFT_WHITE);
sprite.drawLine(width, height * 0.8, width - 10, height * 0.8, TFT_WHITE);
sprite.drawLine(width, height, width, height, TFT_WHITE);
// 中央の十字
sprite.drawLine(width / 2, height / 2, width / 2, height / 2 + 5, TFT_WHITE);
sprite.drawLine(width / 2, height / 2, width / 2, height / 2 - 5, TFT_WHITE);
sprite.drawLine(width / 2, height / 2, width / 2 - 10, height / 2, TFT_WHITE);
sprite.drawLine(width / 2, height / 2, width / 2 + 10, height / 2, TFT_WHITE);
// 距離の表示
spriteNum.setTextSize(1);
spriteNum.setCursor(0, 0);
spriteNum.setFont(&fonts::Font7);
spriteNum.print("99.9");
// 距離描画用spriteを縮小してspriteに描画する
spriteNum.pushRotateZoomWithAA(&sprite, width * 0.9, height * 0.02, 0, 0.35, 0.35);
sprite.setCursor(width * 0.85, height * 0.05);
sprite.setFont(&fonts::Font2);
sprite.print("m");
sprite.pushSprite(0, 0);
NURFはライフリングも無いし割と単純な放物線を描くだろう・・・という事で 放物線の式y=ax^2でザックリと計算しました 典型的なaは重力加速度の9.8のはずですが、 距離を測りながら水平射撃して大体実測に合うように調整しました。
本来は仰角(グラフは水平発射なので俯角になる)に合わせて+記号を上下させようとおもってたのですが、 結局使わずにyで上下に動かすようにしました。
距離に応じてディスプレイに反映します。
// 距離を取得
if (canMeasure) {
LIDAR07.startMeasure();
if (LIDAR07.getValue()) {
distance = LIDAR07.getDistanceMM();
// Serial.printf("Distance:%6dmm ", LIDAR07.getDistanceMM());
}
}
// 距離に応じた変化を計算y=ax^2(と比率調整)
int y = 1.0 * pow((float)distance, 2) * 0.00001;
// 距離に応じたサイトの変化量を計算
// 5mで250mm下降するので最大とする
// 250mm時に画面の高さheight*0.7分上に動かす
int distanceShift = map(y, 0, 250, 0, -height * 0.7);
// サイトの基準位置
// xは画面中央yは画面の上から90%の位置を基準とする
// 頬付けした場合大体その位置にフロントサイトが来る
int sightX = width / 2;
int sightY = height * 0.9;
// sightYにdistanceShiftを足して上下に動かす
sprite.drawLine(sightX, sightY + distanceShift, sightX - 10, sightY + 5 + distanceShift, TFT_WHITE);
sprite.drawLine(sightX, sightY + distanceShift, sightX + 10, sightY - 5 + distanceShift, TFT_WHITE);
sprite.drawLine(sightX, sightY + distanceShift, sightX - 10, sightY - 5 + distanceShift, TFT_WHITE);
sprite.drawLine(sightX, sightY + distanceShift, sightX + 10, sightY + 5 + distanceShift, TFT_WHITE);
これにより+サイトとフロントサイトを重ねて構えることで適正な俯角を取ることができます。