いろいろ作るよ!

ものづくりの記録

サイバー行先表示器を作ってみた

M5Stack社の新製品、M5Dialのデザインに惹かれました。これを使ってSF感あふれる行先表示器を製作しました。 部品表、3Dモデル、プログラムを公開しているので、M5Dialを使う作品の参考にして頂けると嬉しいです。(ライセンスはCC BYです)

M5Dialとは

M5シリーズの中の1つで、ロータリーノブやタッチパネルを有する、機能的には尖った方向性のモジュールです。メインコントローラーとしてM5 stamp S3が内蔵されています。公式動画がかっこよいです。

公式動画】【公式HP

作品のコンセプト

入退場管理システムを作るつもりでM5Dialを手配しましたが、触っているうちにダイヤル機能を活かして行先表示器を作ることにしました。

行先表示器と言えば、電車やバスの行先を表示する装置のことです。これの古い操作盤がダイヤル式でした。 画像引用元:方向幕 - Wikipedia

これを現代風に再構築します。作品のコンセプトは次の3つです。

デザインの過程

初期のデザインスケッチです。ダイソーのUSBライトにM5Dialを組み合わせ、アクリル板を選択的に光らせる構造です。 アクリル板のデザイン案です。攻殻機動隊のUIを意識して、バーコード風の装飾を入れることにしました。

構造図と部品表

2つの3Dプリント製ボディで、アクリル板とNeoPixel LEDを挟み込む構造です。M5Dialはボディ2に本体付属のネジ輪で固定されています。 ダイソーUSBライトの凸部に合わせてボディ1の凹部が嵌る構造になっています。 部品表は以下の通りです。NeoPixelのLEDの間隔に合わせて設計しており、この間隔が変わると設計変更が必要です。

部品 型番/仕様 数量
M5Dial - 1
ダイソークリップ付きUSBライト - 1
NeoPixel LED WS2812B LEDテープライト 5050 1m当たり144LED LED24個分
GROVEコネクタ付きケーブル - 1
アクリル板 板厚3mm 幅180mm長さ320mm程度あれば切り出し可
六角穴付きボルト M3×10 8
六角ナット M3 8

3Dデータ

ブラウザ版Fusion360ビューワから、STEPファイル等の中間ファイルをダウンロードできます。Fusion360等の3DCADを用いて3DプリンタSTLファイルを出力してください。

Fusion360ビューワリンク

アクリル板の加工

アクリル板はレーザー加工機で加工が必要です。加工用SVGファイルをGoogleドライブに保存しました。レーザー加工機や加工外注サービスに合わせて、ご自由にお使いください。赤線が切断線、黒塗部が彫刻エリアです。 drive.google.com

配線と組立

1) ダイソーLEDライト基板の改造

傘を捩じって外し、LEDライトに繋がる配線をM5Dialの電源に利用します。 ただし、基板上でLEDライト用にチップ抵抗を経由しており、そのままでは電圧降下により使用できません。赤い配線を一度外して、チップ抵抗を経由しないように配線し直す必要があります。 ※基板の改造ではなく、元の基板とケーブルを外してTypeCケーブルに交換する方法でも良いかもしれません。

※M5Dialの電源電圧の仕様は6~36Vのため、電源(モバイルバッテリーなど)によっては、USB経由の5V給電では起動できない可能性があります。

2) M5Dial電源端子の接続

M5Dial付属の緑色の端子に、ダイソーLED基板から伸びている赤と白の配線を接続します。±に注意する必要があります。

3) NeoPixel配線

NeoPixelをLED24個分で切断し、Groveケーブルを半田付けします。Groveケーブルは両端Grove端子になっているケーブルを購入し、半分に切断すると2個分利用できます。 Grove端子に爪が付いている場合は、M5Dialに挿入する際に邪魔になるので、ニッパーで切断しておきます。

4) 配線組立

NeoPixelをボディ1にはめ込みます。 ボディ2にM5Dialを固定し、電源と、AポートにNeoPixelを接続します。

5) 全体組立

ボディ1とボディ2を六角穴付きボルトで軽く締結します。この段階では隙間がある状態にしておきます。 アクリル板を1枚ずつ挿入し、六角穴付きボルトを締めていきます。

システム構成

Blynkを使ってスマートフォンと連携する仕組みを採用しています。BlynkとはM5Stack(ESP32)等のIoT機器をネットワーク経由で制御できるサービスです。

Blynk設定

1) コンソール画面側設定

VirtualPin V1を設定しています。

2) スマートフォンアプリ側設定

各ボタンのDetastreamにはVirtual Pin V1を割り当て、それぞれONにて1~8、OFFにて0(帰宅のみON/OFFとも0)を設定しています。

プログラム

Blynk環境、Wifi環境に合わせて適時見直してください。

/* Blynk Device Info */
#define BLYNK_TEMPLATE_ID   "*****" //Blynk設定に合わす
#define BLYNK_TEMPLATE_NAME "*****" //Blynk設定に合わす
#define BLYNK_AUTH_TOKEN    "*****"  //Blynk設定に合わす

#include <M5Dial.h>
#include <FastLED.h>         // For NeoPixel LED
#include <WiFi.h>
#include <WiFiClient.h>
#include <BlynkSimpleEsp32.h>

/* Wifi設定 */
char ssid[] = "*****"; //Wifi環境に合わす
char pass[] = "*****"; //Wifi環境に合わす

/* NeoPixelse設定 */
#define NUM_LEDS 24 //LED個数
CRGB leds[NUM_LEDS];
#define NEO_PIXEL_PIN 13   //NeoPixelピン

/* encoder初期値 */
long oldPosition = 0;

/* タッチ初期化 */
static m5::touch_state_t prev_state;
bool touched = false;

/* 行先設定 */
int oldDist = 1;
int blynk_dist = 0;

//blynk用初期化
BlynkTimer timer;
int blynkWrite = 0;

/* Virtual Pin V1変更時に実行 */
BLYNK_WRITE(V1)
{
  int value = param.asInt();
  blynk_dist = value;

}

/* Blynk接続時に実行 */
BLYNK_CONNECTED()
{
  //V1ピン更新(BLYNK_WRITE(V1)を実行させる)
  Blynk.syncVirtual(V1);
}

/* 100msec毎に実行 */
void myTimerEvent()
{
  if (blynkWrite){ //blynkWrite条件の場合だけ実行
    //Blynk書き込み
    blynkWrite = 0;
    Blynk.virtualWrite(V1, blynk_dist);
  }
}

void setup() {
  auto cfg = M5.config();
  M5Dial.begin(cfg, true, false);
    
  //NeoPixel初期化
  FastLED.addLeds<NEOPIXEL, NEO_PIXEL_PIN>(leds, NUM_LEDS);

  //blynk初期化
  Blynk.begin(BLYNK_AUTH_TOKEN, ssid, pass);

  //blynkタイマー初期化
  timer.setInterval(100L, myTimerEvent);

  //Neopixel点灯
  neoPixel(0);

  //初期画面描画
  M5Dial.Display.fillScreen(0x212833U);
  M5Dial.Display.fillCircle(120, 120, 100, 0x151515U);
  M5Dial.Display.setTextDatum(middle_center);
  M5Dial.Display.setTextFont(&fonts::lgfxJapanGothicP_36);
  M5Dial.Display.setTextSize(2);
  drawMonitor(1,0);
}

void loop() {

  M5Dial.update();

  //Blynk更新
  Blynk.run();
  timer.run();

  int newDist = blynk_dist;

  //エンコーダ変化判定 ・・・potionを-3~3の範囲に限定。3→0 or -1→0 でカウントアップ,1→0 or -3→0でカウントダウン
  int newPosition = M5Dial.Encoder.read()%4;
  if (newPosition != oldPosition) {
    if ((newPosition == 0 && oldPosition == 3)||(newPosition == 0 && oldPosition == -1)){
      newDist++;
    } else if((newPosition == 0 && oldPosition == 1)||(newPosition == 0 && oldPosition == -3)){
      newDist--;
    }
    if (newDist==9) {newDist = 1;}
    if (newDist==0) {newDist = 8;}

    oldPosition = newPosition;

  }

  //タップ判定
  auto t = M5Dial.Touch.getDetail();
  if (prev_state != t.state){
    prev_state = t.state;
    if (t.state == m5::touch_state_t::touch_end){
      // タップ
      if(newDist == 0){
        newDist = 1;
      } else {
        newDist = 0;
      }
    }
  }

  //行先表示が変更になった場合の処理
  if (newDist != oldDist){
    blynk_dist = newDist;
    neoPixel(newDist-1);
    drawMonitor(newDist,oldDist);

    oldDist = newDist;
    blynkWrite=1;
  }

}

/* NeoPixel点灯処理 */
void neoPixel(int num){
  for(int i=0; i<NUM_LEDS; i++) { // For each pixel...
    if((i>=(num)*3)&&(i<(num)*3+3)){
      leds[i] = CRGB(0, 120, 80);
    }else{
      leds[i] = CRGB(0, 0, 0);  
    }
  }
  FastLED.show(); 
}

/* 画面描画処理 */
void drawMonitor(int num, int num2){
  
  switch(num){
    case 0:

      M5Dial.Display.fillArc(120, 120, 100, 120, num2*45+135, num2*45+45+135, 0x212833U);
      M5Dial.Display.drawCircle(120,120,100,0x212833U);
      M5Dial.Display.setTextColor(0x202020U);
      break;
    default:
      M5Dial.Display.fillArc(120, 120, 100, 120, num2*45+135, num2*45+45+135, 0x212833U);
      M5Dial.Display.fillArc(120, 120, 100, 120, num*45+135, num*45+45+135, 0x00B0F0U);
      M5Dial.Display.drawCircle(120,120,100,0x00B0F0U);
      M5Dial.Display.setTextColor(0xFFFFFFU);
  }
      M5Dial.Display.drawString("名前", 120, 120);

}

結果とまとめ

ダイヤルを回して行先表示を切り替えたり、スマートフォンから行先表示を切り替えたりできる仕組みが完成しました。M5Dialの機能やデザインを活かすガジェットを作れたので満足です。このあと会社で使う予定です。