小型コントローラについて(NT金沢で販売済品)

DIY設計優秀賞を頂きました! 小型コントローラ for M5ATOM/Stick | ProtoPedia

6/26:タイトル変更と赤字部分を追記しました
随時追記/更新していき、NT金沢後1週間(+α)以内に空欄は埋める予定です。

・説明
M5のATOMとStickに対応した最大3ボタン2軸ジョイスティックの小型コントローラ基板です。
ケースやグリップの3DモデルはThingiverseで公開済なので自作/カスタム可能です。

ボタン配置は合計12通りから選択可能。(現実的なのは6通り程度想定)

・対応製品
Atom S3/S3Lite:全機能使用可能、サンプルスケッチあり
Atom Matrix/Lite/Echo:スティック垂直1軸orWiFi使用NG(※2)
StickC/C+:要追加工、サンプルスケッチあり、スティックと2種類のボタン同時押しNG(※1)、スティック水平1軸orWiFi使用NG(※2)
※1.スティックとボタンが同じポートの為、ボタンを押している間はスティックの値が0になります。推奨角度範囲内(ケースを使っている場合)では0にはならず区別は可能です。
2.JoyStickにAnalog2を使用している為、1軸かWiFiが排他でどちらかしか使用できません。BLEならesp-idf使えば可能という情報もありますが、未検証です。


・回路図(アートワーク)

 

スイッチはA, B, C, Dの4種類あり、ABは6x6mmで各2か所、Cはサイドタイプで3か所、Dはジョイスティックの押込みスイッチです。すべてGNDに落としているのでサンプルプログラムの通りマイコン側でプルアップして下さい。
SJ1とSJ2を短絡させるとStick向け仕様になり、SWAかBを押すとジョイスティックのVR1,2がGNDに接続され0になります。G0端子は標準でSWDに接続されているので必要に応じてSWCと短絡させて下さい。(後述)
外形図はDXFで出します。

・ケース3Dモデル

Thingverseで公開しました。 ←追記

・Stick向け改造方法
SJ1とSJ2をショート、SWCとG0をショートします。

 

・サンプルプログラム(末尾にあります)
せっかくハンズオンも参加したのでM5UnifiedでAtom/Stick兼用ソースコードを書きたかったですが、検証の時間確保できずAtomS3版を先行して末尾に貼ります。動作検証してからGitHubで公開予定。

・部品表
すべて秋月で購入できますが、一般的なフットプリントを選択しているので好みの物に変更して下さい。販売品は以下URLの物を使用しています。

|*番号|*名前|*型番(URL)|*価格|
|*1|キャップ付きタクトスイッチ|TVDP01-G73BB   |20円x2個|
|*2|サイドスイッチ|THBP07-043CB|5個入り100円=20円|
|*3|ジョイスティック|RKJXV122400R|190円|
|*4|ピンヘッダ|通常品(販売品) or 低プロファイル|10円ぐらい|
合計260円


・販売品ラインナップ
NT金沢では現地特別価格で販売しましたが、今後は以下の値段のを予定です。販売先は検討中ですが、スイッチサイエンスさんの予定です。
完成品(実装済基板+ケース)x4:5000円
キット品(未組立 全部品)x4:3000円
半自作キット品(基板+電子部品+スティック先端)x4:2000円

基板のみ:600円

・基板や付属部品の注意点
ATOMS3/S3Lite以外では全機能を使用できません(後述)
付属部品は形状サンプルです。抵抗値やフィーリングなどは簡単にテスト済ですが、耐久性などは保証できません。
ジョイスティックは角度制限やデッドバンド必要です(後述)

・ケースの注意点
3Dプリンタ/PLA製の為、耐久性はありません。メネジは数回~10回程度でダメになるとお考え下さい。究極は瞬着でお願いします。

ジョイスティックの注意点
角度制限について:データシートの通り23°以上は傾けないで下さい。ケースやスティックなどで角度制限必要です。制限以上倒すと引っかかるような感触ありますが保証外です。
デッドバンド:データシートの通り軸を傾けて戻したときに0°丁度には戻らず、±5%の範囲内に戻るので必要に応じ処理必要です。サンプルを参考に実装下さい。

・サンプルコード(M5AtomS3用)

#include <M5Unified.h>

#define STICK_H 8
#define STICK_V 7
#define STICK_SW 6
#define TRG_SW 5
#define OK_SW 38
#define NG_SW 39
 
const float deadband = 0.1;
float joy_h, joy_v;
float joy_h_sum, joy_v_sum;
char cnt=0;
const char cnt_max = 10;
float joy_h_log[cnt_max], joy_v_log[cnt_max];

float deadbanded( float dbd )
{
  if( dbd < deadband && dbd > -deadband ) dbd = 0;
  else if( dbd > 0 ) dbd -= deadband;
  else dbd += deadband;

  return dbd;
}

void setup(void)
{
  auto cfg = M5.config();
  M5.begin(cfg);
 
  pinMode(STICK_V, ANALOG);
  pinMode(STICK_H, ANALOG);
  pinMode(OK_SW, INPUT_PULLUP);
  pinMode(NG_SW, INPUT_PULLUP);
  pinMode(STICK_SW, INPUT_PULLUP);
  pinMode(TRG_SW, INPUT_PULLUP);

  M5.Display.setTextSize(2);
}

void loop(void)
{
  delay(4);
 
  joy_h_log[cnt] = 1.0 - 2.0*analogRead(STICK_H)/4096, joy_v_log[cnt] = 1.0 - 2.0*analogRead(STICK_V)/4096;
  if( ++cnt >= cnt_max ) cnt = 0;

  for( int i=0; i<=cnt_max; i++ ){ joy_h_sum += joy_h_log[i]; joy_v_sum += joy_v_log[i]; }

  joy_h = deadbanded( joy_h_sum / cnt_max );
  joy_v = deadbanded( joy_v_sum / cnt_max );
  joy_h_sum = joy_v_sum = 0;

  M5.update();
  M5.Display.startWrite();
  M5.Display.setCursor(0, 0);

  if( !M5.BtnA.isPressed() ) M5.Display.println("LCD_SW :  "); else M5.Display.println("LCD_SW : X");
  if( digitalRead(OK_SW) ) M5.Display.println(" OK_SW :  "); else M5.Display.println(" OK_SW : X");
  if( digitalRead(NG_SW) ) M5.Display.println(" NG_SW :  "); else M5.Display.println(" NG_SW : X");
  if( digitalRead(STICK_SW) ) M5.Display.println("JOY_SW :  "); else M5.Display.println("JOY_SW : X");
  if( digitalRead(TRG_SW) ) M5.Display.println("TRG_SW :  "); else M5.Display.println("TRG_SW : X");
  if( !cnt ) M5.Display.printf("\nJOY_H %3.1f\nJOY_V %3.1f", joy_h , joy_v );

  M5.Display.endWrite();
}