2025年10月31日金曜日

45mm玉用ガチャキット設計開始しました 3(組立マニュアルの作成)

  キット化する上で、必要となるのが、組立マニュアルです。このマニュアルは、設計で使用したFusion360を使い、画面キャプチャをして制作します。


表示色・ネジの追加

 Fusion360では、設計のパーツがモノクロが基本なので、「外観」から色を付けていきます。

 左図が色付け前、右図が色付け後です。

 組み立てるときに、タッピングネジを使用します。「どこに付けるのか」が分かるように、ネジを追加します。


 ネジを追加すると、完成した図になります。





組立図の作成

 組立用の図は、完成図をもとにして制作します。

この例で使用している「ギアケース」は、まず、「取っ手」部分から組み立てを行っていきますので、取っ手パーツのみを表示させ、それ以外は非表示にしておきます。




 パーツを移動させ、組み立てる構造が分かるようにします。

 右図のようにすると、「部品をどのようにして組み立てているのか」が図を見てわかるようになります。
 付ける色も、組み立てる工程ごとに重なる部分の色分けをすると、組立図が見やすくなります。

説明マニュアルの作成

 組み立てる様子の画像ができると、組立マニュアルを作成します。ワードを使い、画像を取り込みながら、説明文を入力していきます。



2025年10月24日金曜日

LEDマトリックス表示装置の設計・製作 6(改良)

  以前に完成したLEDマトリックス表示装置ですが、使用している中で、WiFiが使用できない場合、日付の設定ができないことがありました。

 この場合、何かの方法で日付設定が必要です。設計した基板に汎用ポートを付けていたため、このポートにタクトスイッチを付けて対応します。


 内部RTCの日付が違い、WiFiがなくても手動で日付の設定ができるように改良を加えました。

 これにより、どのような環境でも、日付・時刻を設定できるようになりました。





2025年10月23日木曜日

45mm玉用ガチャキット設計開始しました 2(パーツ作成・組立)

 「キットを組み立てるときに、手軽に、手間が少なくできること」がコンセプトにしています。

 3Dプリンタ、レーザ加工機でパーツを製作した後、キット用に使用しているM3のタッピングねじで取り付け始めました。

 設計の中で不具合もあり、調整しながら組み上げていきます。


 組み上げが完了しました。プロトタイプNo.1が出来上がりました。

 出来上がりましたが、所々ねじ頭がほかの部材と干渉して、一部ねじを止めていない部分があったり、製作するときに、ドライバを入れずらいところもあります。
 再度、不具合のある部分の設計を見直していきます。
 また、ねじの本数が100本近くあり、あまり力のかからない部分はねじの省略が必要そうです。


2025年10月20日月曜日

45mm玉用ガチャキット設計開始しました 1(設計)


  NT名古屋2025の展示会で、「ガチャガチャ」を見て頂く中で、「欲しい」という声を頂きました。友人などからも欲しいと言われ、製作したことがあります。

 講習会などでのキットや、販売に向けて、できるだけコンパクトになるように設計をし直し始めました。

Fusion360でのCAD設計

 今まで製作してきたものをもとにして、コンパクトにするうえで大きな要素のギア部分を改良しました。



 製作してきたギア部分は縦90mm×横90mmです。
 右図を見ると、上側と左側に少し余裕があります。この部分を無くすようにしました。
 このためコインを入れる部分が下がり、コインホルダーの改良を加えました。





 この結果、約縦80mm×横72mmに収めることができました。一回り小さくなっていることが分かります。



 これをもとにして、ケースの大きさもサイズを小さくして設計し直します。




 大きさは、幅180mm、高さ170mm、奥行き200mmとなり、縦横奥行とも200mm以内となります。

 これでできると、コンパクトとなるとともに、材料も少なくすることができます。

 パーツの製作

 一応、設計ができましたので、3Dプリンタの造形、アクリル板、MDF板のカット作業を始めます。

NT名古屋2025 出展しました

  2025年10月18日(土)、19日(日)の2日間、愛知県北名古屋市の西春駅近くの「ヨシヅヤ Yストア 西春店」4F 特設会場にてNT名古屋2025が開催されました。


初めての展示会への出展

 これまでものづくり関連の展示会に出展したことがありませんでした。職場の関係で長野県松本市で行われている「まつもと広域ものづくりフェア」でものづくり体験教室の参加、長野県岡谷市で行われている「諏訪圏工業メッセ」の展示などには参加をしたことがありました。
 個人的に展示会への参加は初めてで、ドキドキしながら出展しました。展示の様子です。

出品は自作した「ガチャガチャ」です。

  子供たちには楽しんで頂けたようです。この展示会は技術系の方が多いので、仕組みなどを解説しながら見て頂きました。

 

 地元の活動で行っているものづくり体験教室での製作キット「ビー玉ガチャキット」も見て頂き、購入もして頂きありがとうございました。

 

 この他、LED作品や電光掲示板なども展示しました。


 多くの皆様と交流させていただき大変ためになりました。





2025年10月17日金曜日

ESP32マイコンでビデオゲーム 4(テトリス)

 いよいよゲーム制作に入ります。まずは「テトリス」ゲームです。完成したゲームです。
コントローラはESP32マイコンのライブラリに「PS3」があるため、PS3をBLE接続してコントローラとします。



 画面設計

 エクセルを使い、まずは画面設計を行います。 

(1)オープニング画面

 ブロックを配置できるエリアを横10×縦20ブロックにします。テトリスのブロックを1つあたり、8×8ドットにし、ブロック間を1ドット空けます。
 また、右側に得点やタイトルを表示させるエリアを取ります。
 全体の画面サイズは270×215ドットとしました。

(2)プレイ画面

 プレイ画面は右図です。左側がプレイエリア、右側にタイトルのほか、ネクストブロックを表示させます。
 初期のころはなかったのですが、改良されたテトリスでは「ホールド」機能があるということで、この機能を追加するためにエリアを追加しています。

(3)エンディング画面

 ゲーム終了のエンディング画面は、オープニング画面をベースにして、エンディング画像を表示するようにします。

 ゲームプログラミング

(1)画像の準備

 プログラミングで必要となる画像を作成します。ここで作成する画像は画面設計で表示されている画像です。この画像を「画像データ」に変換します。

(2)プログラム

 テトリスのプログラミングでは、プレイエリアを表現するのに2次元配列を使い、ブロックの有無や1列揃っているかなどの判断を行います。
 画面はグラフィックライブラリを使ってすべて描きます。

 Step.1 画面設計する
 Step.2 画面設計をもとに、プログラムする。
 Step.3 テトリスブロックを画面に置く。
 Step.4 テトリスブロックを下に動かす。(インベーダのプログラムが参考になります)
 Step.5 一番下に来たら(下に移動できなくなったら)、ブロックを固定する。
 Step.6 次のブロックを画面に置く。
 Step.7 Step.4からStep.6を繰り返す。

 ここまででも1行揃った判断はしていません。Step.5のブロック固定の時に、揃った判断をして、揃った行を消す。その後、上のブロックを1段下げると何となくテトリス風になります。

(3)ブロックを回転させる方法

 ブロックを回転させるために、ブロックのパターンを定義しています。
uint16_t Tetris_Block[][5] = {
  // 回転パターン,                , 色
  {0x0000, 0x0000, 0x0000, 0x0000, 0}, // dummy
  {0x4444, 0x0F00, 0x4444, 0x0F00, 1}, // I
  {0x4620, 0x06C0, 0x4620, 0x06C0, 2}, // S
  {0x2640, 0x0C60, 0x2640, 0x0C60, 3}, // Z
  {0x4E00, 0x4640, 0x0E40, 0x4C40, 4}, // T
  {0x88C0, 0x2E00, 0x6220, 0xE800, 5}, // L
  {0x44C0, 0xE200, 0xC880, 0x8E00, 6}, // J
  {0xCC00, 0xCC00, 0xCC00, 0xCC00, 7}  // O
};
 例えば、8行目の「L」形のブロックの定義の意味は次の図のようになります。


 




16進4桁の4つのデータを2進データに置き換えます。その"1"の部分(色塗り)に注目すると、ブロックの形状になっています。これが回転させたように4パターン定義しています。
配列要素のインデックスを変更するだけでパターンを変更できるようになります。

 この変換するプログラムを示します。
void Tetris_setBlock( int blk, int rot) {
  uint16_t dat = Tetris_Block[blk][rot % 4];
  uint16_t col = Tetris_Block[blk][4];
  for (int x = 0; x <= 3; x++) {
    for (int y = 0; y <= 3; y++) {
      BLOCK[x][y] = 0;
    }
  }
  if ( ( dat & 0x8000 ) != 0 ) BLOCK[0][0] = col;
  if ( ( dat & 0x4000 ) != 0 ) BLOCK[1][0] = col;
  if ( ( dat & 0x2000 ) != 0 ) BLOCK[2][0] = col;
  if ( ( dat & 0x1000 ) != 0 ) BLOCK[3][0] = col;
  if ( ( dat & 0x0800 ) != 0 ) BLOCK[0][1] = col;
  if ( ( dat & 0x0400 ) != 0 ) BLOCK[1][1] = col;
  if ( ( dat & 0x0200 ) != 0 ) BLOCK[2][1] = col;
  if ( ( dat & 0x0100 ) != 0 ) BLOCK[3][1] = col;
  if ( ( dat & 0x0080 ) != 0 ) BLOCK[0][2] = col;
  if ( ( dat & 0x0040 ) != 0 ) BLOCK[1][2] = col;
  if ( ( dat & 0x0020 ) != 0 ) BLOCK[2][2] = col;
  if ( ( dat & 0x0010 ) != 0 ) BLOCK[3][2] = col;
  if ( ( dat & 0x0008 ) != 0 ) BLOCK[0][3] = col;
  if ( ( dat & 0x0004 ) != 0 ) BLOCK[1][3] = col;
  if ( ( dat & 0x0002 ) != 0 ) BLOCK[2][3] = col;
  if ( ( dat & 0x0001 ) != 0 ) BLOCK[3][3] = col;
}
 ブロックのパターン番号、回転を与えてTetris_setBlock関数を呼び出すと、BLOCK[4][4]配列にパターンを展開します。
 このBLOCK配列を使いブロックを描くと、テトリスブロックを画面に描画できます。これをTetris_drawBlock関数しました。表示させる座標(X,Y)、ブロック番号、回転を与えて呼び出すとブロックを描いてくれます。
void Tetris_drawBlock( int x, int y, int blk, int rot){
  Tetris_setBlock( blk, rot); // BLOCK配列にブロック状態を展開
  for(int j=0;j<4; j++){
    for(int i=0;i<4; i++){
      if( BLOCK[i][j]>0 ){
        // ブロックがあるときはブロックを描く
        Vout.drawRect( (x+i)*9  , (y+j)*9  , 8, 8, Tetris_Block_Color[BLOCK[i][j]] );
        Vout.fillRect( (x+i)*9+2, (y+j)*9+2, 4, 4, Tetris_Block_Color[BLOCK[i][j]] );
      }else{
        // ブロックが無い時は黒で塗潰す
        Vout.fillRect( (x+i)*9  , (y+j)*9  , 8, 8,  BLACK  );        
      }
    }
  }
}
 例えば、ブロック番号を変えて、Tetris_drawBlock関数を呼び出すサンプルです。
int blockX=5, blockY=8, blockNo=1, blockRot=0;
void loop(void) {
  // ブロックを表示    X座標 ,  Y座標 , ブロックNo, 回転
  Tetris_drawBlock( blockX, blockY, blockNo, blockRot);
  Vout.display();
  blockNo=blockNo+1;
  if( blockNo > 7 ) blockNo =1;
  delay(300);
}
 これで画面にテトリスブロックが順番に表示されます。

 変数「blockRot」を0から3まで順番に変化させるとテトリスのブロックが回転します。



完成させてみましょう

 機能を追加するときに、一つの機能ごとに関数化して作成してください。この関数がしっかりと機能していれば、使用するプログラムも安定します。また関数化によりプログラムが見やすくなります。

 あとは「ブロックをコントローラで動かす」、「ブロックが移動できるか」、「一列揃っているかの確認」「得点を付ける」、「ゲームオーバーの判断をする」などの機能がまだまだ必要です。

※プログラムを掲載してもよいのですが、プログラミングの実力を付けるには、一歩ずつです。参考にしてプログラムしてみてください。

2025年10月16日木曜日

ESP32マイコンでビデオゲーム 3(インベーダ表示)

 スペースインベーダのキャラクタを動かす

 画像が表示できることができると、昭和レトロ「スペースインベーダ」のキャラクタを動かしたくなりました。
 キャラクタ「いか」、「かに」、「たこ」の画像を作り、データ化します。

 あとはキャラクタを移動させながら、2つのキャラクタを交互に表示させるを懐かしいキャラクタが画面に表示されます。

#include "img_ika1.h"
#include "img_ika2.h"
#include "img_kani1.h"
#include "img_kani2.h"
#include "img_tako1.h"
#include "img_tako2.h"

LGFX gfx;

void setup() {
  gfx.init();
}
int x=5, y=10;
void loop() {
  gfx.drawPng((std::uint8_t*)img_ika1, sizeof(img_ika1), x+48, y);
  gfx.drawPng((std::uint8_t*)img_ika1, sizeof(img_ika1), x+26, y);
  gfx.drawPng((std::uint8_t*)img_ika1, sizeof(img_ika1), x+4, y);
  gfx.drawPng((std::uint8_t*)img_kani1, sizeof(img_kani1), x+2, y+20);
  gfx.drawPng((std::uint8_t*)img_tako1, sizeof(img_tako1), x, y+40);
  delay(300);
  x++;
  gfx.drawPng((std::uint8_t*)img_ika2, sizeof(img_ika2), x+48, y);
  gfx.drawPng((std::uint8_t*)img_ika2, sizeof(img_ika2), x+26, y);
  gfx.drawPng((std::uint8_t*)img_ika2, sizeof(img_ika2), x+4, y);
  gfx.drawPng((std::uint8_t*)img_kani2, sizeof(img_kani2), x+2, y+20);
  gfx.drawPng((std::uint8_t*)img_tako2, sizeof(img_tako2), x, y+40);
  delay(300);
  x++;
  if( x> gfx.width() ) x = 5;
}
 実行させると、画像のようになります。


 ここまでの準備で、画面に「グラフィックを描く」、「テキストを表示する」、「画像を表示する」ことができるようになりました。






 YouTubeにINVADERが動く様子の動画をアップしました。



2025年10月15日水曜日

ESP32マイコンでビデオゲーム 2(グラフィックス命令)

基本グラフィックス命令

 らびやんさんのLovyanGFXライブラリを使用するときに、たなかまさゆきさんのブログ「Lang-ship」に「LovyanGFX入門 その1 基本描画系」のサイトが参考になりました。
    書き込んだサンプルプログラムや上記サイトを見るとdrawLine」、「drawRect」、「fillRect」、「drawCircle」、「fillCircle」を使うと、グラフィックを使用できることが分かります。また、文字も「print」命令で出力できるため、この命令だけでかなりのUI(User Interface)を作ることができます。

オリジナル画像を表示させる

 参考にさせて頂いているブログ「Lang-ship」を読み進めると、「LovyanGFX入門 その5 画像描画」の記事に、LovyanGFXライブラリを使うとpng画像などの画像ファイルを「drawPng」命令で表示できるとあり、まずはサンプルを実行させました。
 
 自分で作成した画像を表示させます。ブログに従い、データファイルを作ります。

 Lang-shipサイトの「画像変換」を開き、画像ファイルを指定します。
 データ形式は「RAW Dump」にしました。

 
 送信ボタンを押すと、データが出来上がります。
 このデータをコピーして、ArduinoIDEの新規タブからファイル名「image.h」追加して、貼り付けます。
 「xxx.ino」のプログラムの先頭で「image.h」をインクルードします。これで、画像ファイルを組み込むことができます。

 実行したプログラム(setup、loop部のみ)

#include "image.h"
LGFX gfx;

void setup() {
  gfx.init();
}
void loop() {
  int x = rand() % gfx.width();
  int y = rand() % gfx.height();
  gfx.drawPng((std::uint8_t*)img, sizeof(img), x, y);
  delay(300);
}

 画像を表示させる命令は「drawPng」命令です。画像ファイルと座標を指定しています。
 データを作成するときの「RAW Dump」は画像データをそのまま16進データに変換していますので、メモリを必要とします。メモリが限られるマイコンなどでは色数を落としてデータを作る必要があります。RGB565では、1ピクセル16ビットデータに変換されます。


 画像をランダムな座標に表示しています。

ESP32マイコンでビデオゲーム 1(ビデオ出力回路)

 2年ほど前に作成した昭和レトロのビデオゲームについてまとめます。

 ビデオゲームが出始めたころは、ビデオ出力をテレビに接続してゲームをしていました。WindowsなどのOSが無い時代のパソコンはBASIC言語が主流で、画面をデザインするときには、LINE命令やCIRCLE命令などを使って作成していました。
 

NTSCビデオ出力する

 ESP32マイコンを使い、少ないI/Oでゲームなどができないかと考えていた時に、PICマイコンで「ブレイクアウト(ブロック崩し)」があったことを思い出し、PICマイコンでできたならば、CPUクロック、メモリとも充実しているESP32マイコンでもできるのではないかと思い、開発を始めました。また、テレビ画面やプロジェクタなどの大画面でゲームをやり、みんなで楽しむということもあります。
 検索していると、あすきさんのブログ「試行錯誤な日々」に「ESP32でAVケーブル(RCA、NTSC)を介して画面表示」があり、早速実験しました。この中ではらびやんさんの「画面制御ライブラリLovyanGFX」が使われており、このライブラリはグラフィックスライブラリで、LINE命令、CIRCLE命令などで、画面にグラフィックを描くものです。

 今回使用するNTSCビデオ出力(アナログ信号)が、地上デジタル化して、アナログ信号を扱う入力端子がテレビなどから無くなっていくのではと思っていましたが、安価なDVDプレーヤなどはまだNTSC信号を使っていることからなのか、今でもビデオ入力があります。

 ESP32マイコンでNTSC出力をする場合に、あすきさんのブログにもありますが、1本の信号で、テレビなどに出力できます。
 RCA端子によるビデオ入力には、ビデオ信号の他に、音声信号も入力できますので、もう1ポートを追加して、音声も追加しました。
 ESP32マイコンのビデオ出力は、DACを使って信号を作成しているため、対応しているポート25か、26のみです。
 
 プログラムもサンプルとして拝借をして、実行しました。

#define LGFX_USE_V1
#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device
{
public:

  lgfx::Panel_CVBS _panel_instance;

  LGFX(void)
  {
    { // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();    // 表示パネル設定用の構造体を取得します。

      // 出力解像度を設定;
      cfg.memory_width  = 240; // 出力解像度 幅
      cfg.memory_height = 160; // 出力解像度 高さ

      // 実際に利用する解像度を設定;
      cfg.panel_width  = 208;  // 実際に使用する幅   (memory_width と同値か小さい値を設定する)
      cfg.panel_height = 128;  // 実際に使用する高さ (memory_heightと同値か小さい値を設定する)

      // 表示位置オフセット量を設定;
      cfg.offset_x = 16;       // 表示位置を右にずらす量 (初期値 0)
      cfg.offset_y = 16;       // 表示位置を下にずらす量 (初期値 0)

      _panel_instance.config(cfg);


// 通常は memory_width と panel_width に同じ値を指定し、 offset_x = 0 で使用します。;
// 画面端の表示が画面外に隠れるのを防止したい場合は、 panel_width の値をmemory_widthより小さくし、offset_x で左右の位置調整をします。;
// 例えば memory_width より panel_width を 32 小さい値に設定した場合、offset_x に 16 を設定することで左右位置が中央寄せになります。;
// 上下方向 (memory_height , panel_height , offset_y ) についても同様に、必要に応じて調整してください。;

    }

    {
      auto cfg = _panel_instance.config_detail();

      // 出力信号の種類を設定;
      // cfg.signal_type = cfg.signal_type_t::NTSC;
      cfg.signal_type = cfg.signal_type_t::NTSC_J;
      // cfg.signal_type = cfg.signal_type_t::PAL;
      // cfg.signal_type = cfg.signal_type_t::PAL_M;
      // cfg.signal_type = cfg.signal_type_t::PAL_N;

      // 出力先のGPIO番号を設定;
      cfg.pin_dac = 26;       // DACを使用するため、 25 または 26 のみが選択できます;

      // PSRAMメモリ割当の設定;
      cfg.use_psram = 1;      // 0=PSRAM不使用 / 1=PSRAMとSRAMを半々使用 / 2=全部PSRAM使用;

      // 出力信号の振幅の強さを設定;
      cfg.output_level = 128; // 初期値128
      // ※ GPIOに保護抵抗が付いている等の理由で信号が減衰する場合は数値を上げる。;
      // ※ M5StackCore2 はGPIOに保護抵抗が付いているため 200 を推奨。;

      // 彩度信号の振幅の強さを設定;
      cfg.chroma_level = 128; // 初期値128
      // 数値を下げると彩度が下がり、0で白黒になります。数値を上げると彩度が上がります。;

      // バックグラウンドでPSRAMの読出しを行うタスクの優先度を設定;
      // cfg.task_priority = 25;

      // バックグラウンドでPSRAMの読出しを行うタスクを実行するCPUを選択 (APP_CPU_NUM or PRO_CPU_NUM);
      // cfg.task_pinned_core = PRO_CPU_NUM;

      _panel_instance.config_detail(cfg);
    }

    setPanel(&_panel_instance);
  }
};


LGFX gfx;

void setup(void) {
  gfx.init();

  for (int x = 0; x < gfx.width(); ++x) {
    int v = x * 256 / gfx.width();
    gfx.fillRect(x, 0 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(v, v, v));
    gfx.fillRect(x, 1 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(v, 0 ,0));
    gfx.fillRect(x, 2 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(0, v, 0));
    gfx.fillRect(x, 3 * gfx.height() >> 3, 7, gfx.height() >> 3, gfx.color888(0, 0, v));
  }
  delay(1000);
  gfx.drawLine(0,0, gfx.width() - 1, gfx.height() - 1, TFT_GREEN);
  gfx.drawRect(0,0, gfx.width(), gfx.height(), TFT_RED);
  gfx.drawRect(1,1, gfx.width() - 2, gfx.height() - 2, TFT_GREEN);
  gfx.drawRect(2,2, gfx.width() - 4, gfx.height() - 4, TFT_BLUE);
  gfx.fillRect(gfx.width()/2, (gfx.height() >> 3) * 4, gfx.height() / 3, gfx.height() / 3, TFT_PURPLE);
}

void loop(void) {
  gfx.setCursor(20, (gfx.height() >> 3) * 4);
  gfx.setTextSize(0);
  gfx.println("hello at " + String(millis()));
}
 クラス「LGFX」を追加しています。このクラスがNTSCに出力する機能を追加しています。
 接続回路に応じて変更する点は48行目の出力ピンの設定です。
 らびやんさんの画面制御ライブラリ「LovyanGFX」を使用していますので、ライブラリを追加しておきます。

 プログラムを書き込み、出力端子をテレビに接続すると、これだけで出力できました。


2025年10月11日土曜日

ゲームコントローラの設計・製作

  ESP32マイコンのBLEを使い、ゲームコントローラの製作を行いました。


 右図は完成品です。PlayStationの操作ボタンをイメージして、設計しています。

 単3電池用の電池ケースを内蔵しています。


機能と回路設計

 基本はボタン10個です。操作したい本体が、ボタンのオン・オフで動作するので、アナログジョイスティックはありません。
 ボタンの他に、LEDを内蔵しています。電池の電圧を測定し、電池の容量が低下して、電圧が下がると、LEDで表示させる機能です。
 このため、回路はLED(電流制限抵抗を直列接続)とスイッチをESP32マイコンに接続しているだけです。

ケース設計

 コントローラのケースは、Fusion360で設計し、造形しました。基板はタッピングネジM3で止める構造です。






 基板を内蔵して完成となります。















プログラミング

 ゲーム本体とコントローラともESP32マイコンです。ゲーム本体をマスター、コントローラをスレーブとして、BLEのUART通信機能を使い通信を行います。マスター側からスレーブ側に要求があると、スイッチ情報とバッテリ情報を返すプログラムを作成します。

(1)ライブラリ「BluetoothSerial」

 ESP32マイコン同士をBLEの通信「BluetoothSerial」ライブラリをインクルードすると、スケッチ例としてマスタ側「SerialToSerialBTM」、スレーブ側「SerialToSerialBT」が利用できます。

 それぞれのプログラムを2台のESP32マイコンに書き込むと、お互いにペアリングして、通信できるようになります。
 右が通信している様子です。それぞれのシリアルモニタから、メッセージを入力すると、相手のモニタに表示されます。

(2)スイッチ情報をBLE通信で送る

 まずは、コントローラ(スレーブ側)に、スイッチ情報を読み取り、1秒ごとにマスター側に送信するプログラムに改良します。
 プログラムの追加は、ポート設定、ポート読み込み・BLE送信です。スレーブ名も「ESP32-Controller-Slave」に変更しました。
#define LED_R   23
#define LED_Y   17
#define LED_B   16
#define SW_START  33
#define SW_SELECT 32
#define SW_LU 27    // Left side UP Button
#define SW_LD 13    // Left side DOWN Button
#define SW_LR 12    // Left side RIGHT Button
#define SW_LL 14    // Left side LEFT Button
#define SW_RU 22    // Right side UP Button
#define SW_RD 18    // Right side DOWN Button
#define SW_RR 19    // Right side RIGHT Button
#define SW_RL 21    // Right side LEFT Button
#define DC_VOLT 35

#include "BluetoothSerial.h"
BluetoothSerial SerialBT;

String device_name = "ESP32-Controller-Slave";

void setup() {
  Serial.begin(115200);
  SerialBT.begin(device_name);
  Serial.printf("The device with name \"%s\" is started.\nNow you can pair it with Bluetooth!\n", device_name.c_str());
  pinMode(LED_R,OUTPUT);
  pinMode(LED_Y,OUTPUT);
  pinMode(LED_B,OUTPUT);
  pinMode(SW_START,INPUT_PULLUP);  
  pinMode(SW_SELECT,INPUT_PULLUP);  
  pinMode(SW_LU,INPUT_PULLUP);  
  pinMode(SW_LD,INPUT_PULLUP);  
  pinMode(SW_LR,INPUT_PULLUP);  
  pinMode(SW_LL,INPUT_PULLUP);  
  pinMode(SW_RU,INPUT_PULLUP);  
  pinMode(SW_RD,INPUT_PULLUP);  
  pinMode(SW_RR,INPUT_PULLUP);  
  pinMode(SW_RL,INPUT_PULLUP);  
  pinMode(DC_VOLT,INPUT);  
}

void loop() {
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  int sw_lu=digitalRead(SW_LU);
  int sw_ld=digitalRead(SW_LD);
  int sw_ll=digitalRead(SW_LL);
  int sw_lr=digitalRead(SW_LR);
  int sw_ru=digitalRead(SW_RU);
  int sw_rd=digitalRead(SW_RD);
  int sw_rl=digitalRead(SW_RL);
  int sw_rr=digitalRead(SW_RR);
  int sw_str=digitalRead(SW_START);
  int sw_sel=digitalRead(SW_SELECT);
  int dcv=analogRead(DC_VOLT);
  SerialBT.printf("%1d%1d%1d%1d%1d%1d%1d%1d%1d%1d:%4d\n",
    sw_lu, sw_ld, sw_lr, sw_ll, sw_ru, sw_rd, sw_rl, sw_rr, sw_str, sw_sel, dcv);
  delay(1000);
}
 マスター側は、スレーブ名を変更するだけで受信できます。

 スイッチ情報は、単純に「0」、「1」で送信しています。
 1秒ごとに、スイッチ情報を受信していることが分かります。最後の4桁はバッテリ情報です。

 これをゲーム本体に組み込むことで、コントローラ機能として働きます。



(3)送受信の改良

 これで送受信できるのですが、よくシリアルモニタを見ていると、タイムラグが発生していることが分かります。

void loop() {
  if (Serial.available()) {
    SerialBT.write(Serial.read());
  }
  if (SerialBT.available()) {
    Serial.write(SerialBT.read());
  }
  delay(20);
}
 上記の受信部のプログラムを見ると、データがあるか判断(available関数)して、データがあるときには、受信(read関数)し、相手に送信(write関数)する処理をしています。ここでは、1バイトごとしか受信処理ができません。
 このためスイッチ情報を送るプログラムでは、相手にスイッチ情報を15バイト(シリアルモニタの1行あたりの文字数)送信しています。つまり、15文字×20mS=300mS(0.3秒)受信にかかる計算になります。
 ここの部分はプロトコルを設計し、「16進数で送信する」、「改行を入れ、改行まで受信させる」などの改良が必要です。
 改良の例として、サンプル例の「Communication」にある「SerialEvent」のSerialEvent関数は参考になりそうです。

void serialEvent() {
  while (Serial.available()) {
    // get the new byte:
    char inChar = (char)Serial.read();
    // add it to the inputString:
    inputString += inChar;
    // if the incoming character is a newline, set a flag so the main loop can
    // do something about it:
    if (inChar == '\n') {
      stringComplete = true;
    }
  }
}
 このサンプル例では、改行コード「\n」を受信するまで定期的に処理し、改行コードを受信したら、受信フラグ「stringComplete」を「true」にする処理をしています。これで1文字ずつではなく、スイッチ情報ごとの受信ができるようになります。
 これを組み入れたマスター側のプログラム(loop部)が次です。
boolean stringComplete=false;
String inputString;
void loop() {
  serialBTEvent();
  if (stringComplete) {
    Serial.println(inputString);
    inputString = "";
    stringComplete = false;
  }
  delay(20);
}

void serialBTEvent() {
  while (SerialBT.available()) {
    char inChar = (char)SerialBT.read();
    if (inChar == '\n') {
      stringComplete = true;
    }else{
      inputString += inChar;
    }
  }
}
 プログラムを動作させると、受信したときの表示処理が速くなっていることが分かります。


 このプログラムは、まだ読み込むサンプルなので、16進にして送信するなどの処理を行っていませんが、スイッチ情報を計算で16進にして、SerialBT.printf関数のフォーマット指定子でで「%03x」とすると、16進で送信できます。
 右の図では、スイッチ10ビット、バッテリ情報8ビットで処理しています。

2025年10月7日火曜日

絵を入れ替えできる迷路パズル

  よくある迷路パズルは片面だけですが、空いている面を活用して、両面の迷路パズルにしました。デザインは「XINXENDO SW」風です。

 右図の上が表からみた図、下が裏からみた図です。この図では見ずらいのですが、丸印をつけた部分で、ボールが表と裏を行き来することができます。

 この通路を使うと、すべての場所にボールを移動させることができるようにしています。


絵を入れ替える仕組み

 この迷路パズルの特徴は中の絵を入れ替えることができることです。表面についている止めネジを外すと、上のバーが開くようになっています。

 このバーを開けると、中の印刷した用紙を取り出すことができるようになります。

 これを利用すると、お気に入りの写真や押し画像を印刷して入れると、オリジナルの迷路パズルとなります。
 この時に用紙ボールが通る部分を切り抜いておく必要があります。


 この構造は、迷路をアクリル板で挟み込んでいるところです。特に、内側のアクリル板(本来は透明ですが、図ではわかるように紺色にしています)は3枚にして、迷路の間に隙間を空けていることです。この隙間に印刷した画像をはめ込むことで、絵を入れ替えることができる仕組みです。

迷路もオリジナルに

 この迷路パズルはオリジナルにすることができる設計です。 
 図のように迷路が、12×12と10×12のサイズです。
 12×12のように正方形にすることで、迷路を回転させてもパズルに組み入れることができるようになります。
 また、10×12の迷路も上下を逆にしてもパズルに組み入れることができます。入れ方で簡単な迷路になったり、難しくなったりします。
 迷路パズルのルートを覚えてしまったら、迷路を回転させて入れ替えることも可能です。

 迷路の作成方法は、投稿済みの「立体迷路(ものづくりキットNo.2)」をご覧ください。

2025年10月6日月曜日

AMS購入しました

  自宅に3Dプリンタを昨年の12月に購入しました。Bambu社の「A1 mini」です。

 いろいろな人に、「最近の3Dプリンタが手軽で高性能になっている」と聞き、購入しました。

 それからまだ1年になりませんが、3Dプリンタというと「フィラメントが詰まる」などのトラブルがあり、なかなか成形するのに手間がかかるという印象でしたが、特に問題なく使えていることに満足していました。ただ、2色で造形するときや異なる色のフィラメントにするときには、フィラメントを入れ替える必要があり、手間が必要と感じていました。


Bambu社から「秋のセール」のメールが届く

 9月末にBambu社から「秋のセール」始まるメールを受けとりました。
 特に最近は、フィラメントを入れ替えて使うことが多く、職場でAMSを使っていると、その便利さを感じてしまい、AMSを注文しました。
 数日で到着して、取り付けるとすぐにAMSを認識しました。
 AMSを使い始めると、フィラメントを入れ替えるときに、3Dプリンタが動作中でも、使っていないフィラメントを入れ替えできたり、複数色の造形も手軽に作ることができ、改めてその便利さを認識しました。

バーサライタ方式の電光掲示板3 (フォントデータの第2水準対応)

 「バーサライタ方式の電光掲示板2」では、第1水準漢字まで対応するようにしました。

その後、BDFデータが第2水準漢字まで対応しているため、第2水準漢字まで変換できるようにしました。


第2水準漢字対応の時のメモリ領域

 第2水準まで対応させると、データ量が多くなるため、より多くのフラッシュメモリを必要とします。


 単純に合計するとデータ量は1.93MBのため、「Partition Scheme」を「No OTA(2MB APP/2MB SPIFFS)」にして、LittleFSにデータを転送すると、エラーとなります。

 1レコードのブロック長が4096バイトのため、4096バイト単位でファイルの読み書きが行われることになります。このため、1バイト書き込んでも、4096バイト必要になります。
 また、LittlesFSで確保されるサイズは0x1e0000=480ブロック(0x1e0000/4096=480)となります。
 今回、書き込むファイルから、必要となるブロック数は493ブロックのため、13ブロック、わずか52kB不足することになります。


 第2水準漢字に対応するには「Partition Scheme」を「No OTA(1MB APP/3MB SPIFFS)」として、LittleFSの領域を3MBにする必要があります。
 その分、プログラムを格納するエリアが1MBとなり、WiFiやWebServerなどライブラリの容量を多く使う場合に、プログラム領域が不足することになります。
 このため、以前の投稿では第1水準のみにして、「Partition Scheme」を標準の「Default 4MB with spiffs(1.2MB APP/1.5MB SPIFFS)」でも動作するようにしました。

第2水準対応のライブラリとサンプルプログラム

 第2水準対応のライブラリ・サンプルプログラム(ファイル名: disp_jiskan24dot-jis2.zip)をアップします。
 メインとなるプログラムは、データチェック用も兼ねて、半角・全角文字を表示させる部分を残してあります(コメントアウトしています)。
 

2025年10月5日日曜日

バーサライタ方式の電光掲示板2 (フォントデータの作成)

  「LEDマトリックス表示装置」では、東雲フォント16ビットを使用して表示させていました。

 今回はドット数が30ドットあるため、24ビットのビットマップフォントを使い、表示させるようにします。

 mgo-tec様のサイトで紹介されているライブラリ「ESP32_SPIFFS_ShinonomeFNT」をもとにして変更します。

 ここで紹介するプログラムは、ESP32マイコンであれば動作します。


ビットマップフォント24bitの取得

 まず、24ドットのピットマップフォントをダウンロードします。ファイル名は「intlfonts-1.4.2.tar.gz」です。ファイルを解凍すると様々なBDFフォントが含まれています。この中のフォルダ「Japanese.X」に、24ドットのBDFフォントが入っています。
 全角フォント「jiskan24.bdf」
 半角フォント「12x24rk.bdf」

 同じファイル名でも、取得するサイト(ファイル)により、BDFのファイル構造が異なっています。先頭からの位置がずれてしまいますので、他から取得する場合には注意が必要です。

フォント取得ライブラリの作成

 ライブラリ「ESP32_SPIFFS_ShinonomeFNT」をもとに変更させていただきました。
ライブラリを変更するにあたり、BDFフォントのファイル形式を知る必要があります。こちらも、mgo-tec様の「日本語漢字ビットマップフォント、東雲フォントについて」というサイトに説明があり、大変参考にさせて頂きました。

(1)クラス名の変更

 今回使用するフォントが東雲フォントから「jiskan24.bdf」になることから、クラス名を「 ESP32_LittleFS_JisKanjiFNT」にしました。

(2)フォント読み出し関数

 東雲フォントでは、「SjisToShinonome16FontRead」関数でフォントの読み出しを行っています。24ビットに対応するため、「SjisToJiskanji24FontRead」関数にして、フォントデータを32ビット長(uint32_t)のbuf[24]配列に格納するようにしました。
 この関数内から全角の場合には「LittleFS_Flash_JiskanjiFNTread_FHN」関数を呼び出しています。また、半角の場合には「LittleFS_Flash_JiskanjiFNTread_Half_FHN」関数呼び出しています。この関数内も24ビット対応させます。

(3)フォントのアドレス計算

 フォントを読み出すときに、アドレスの計算が必要になります。これを行っているのが、関数「SjisToJiskanjiFNTadrs」です。このチェックをするため、バイナリエディタでフォントファイルを開き、エディタのアドレスと計算しているアドレスをチェックして、すべてが対応するようにします。この作業が大変でした。

 ここまでのフォントを表示するプログラムをアップします。
   ファイル名 disp_jiskan24dot.zip


 実行するときに、LittleFSにデータをアップロードしておいてください。
 書き込み、実行するとシリアルモニタにフォントが表示されます。


LED表示装置でのフォント表示

 フォントは、ビットデータとして、配列 buf[] にを読み出されます。これを表示させることで、LED表示装置に表示させることができます。



2025年10月4日土曜日

バーサライタ方式の電光掲示板1 (コンセプトと回路設計)

バーサライタ

 バーサライタは、少ないLEDで文字や画像などの情報を表示させる表示方式です。

 今回は、バーサライタ方式を使った電光掲示板を紹介します。
 


使用するテープLED

 使用するテープLEDは「WS2812」と呼ばれるマイコン内蔵のフルカラーLEDです。

 マイコンから1ポートのみで出力できるため、よく見かけるようになりました。このテープLEDは、テープの途中で切断して利用できるため、必要な長さで設計できます。

 今回は、60LEDs/mのタイプで、1mに60個LEDが付いているものです。


どのように表示させるか

 データをどのように表示させるのかは、右図のように一部をエリアをテープLEDに表示させます。

 表示させるデータをスクロースさせることで、少ない列のテープLEDに表示されていき、人の残像現象を利用して、文字が見えるようにさせることです。
 ここがバーサライタの文字を見ることができる機能を利用しているところです。




回路構成

 表示する文字を手軽に変更したいことから、以前に「LEDマトリックス表示装置」で投稿した時と同じ「ESP32S3マイコン」とし、外部にUSBメモリを接続できるようにします。
 表示部は余っていたテープLEDを使用しているため、5列(テープ5本)×30LEDsとしました。
 その他に、他励式圧電ブザー、スイッチ2個、ボリュームを付けています。
 これで設計した回路を示します。


 画像をクリックすると、PDFで回路を見ることができます。



 これをもとに製作し、完成した表示装置が次の図です。





 右側にESP32S3マイコンがあり、そこから左にテープLEDがつながっています。マイコンの下にUSB接続コネクタと電源端子があります。マイコンの右にスイッチとボリューム(半固定抵抗)が付いています。

完成動画

 完成した動画をアップしていますので、ご覧ください。

 
 表示がうまく見えていませんか。
いろいろな場所で実験したところ、掲示板からの距離でスクロールする速度が速すぎたり、遅すぎたりします。近い場所ではスクロース速度がゆっくりでないとうまく見えません。遠いところでは、ある程度のスクロース速度が速くないと、遅すぎて見えずらくなります。
 これは、付けてあるボリュームで調整できるようにプログラミングしています。

LEDマトリックス表示装置の設計・製作 9(画像表示)

  LEDマトリックスに画像を表示させるには、いろいろな画像形式を知る必要があります。現在、よく使われるJPG形式やPNG形式は圧縮処理されて保存されています。これに対して、BMP形式やTIFF形式は非圧縮形式で、画像データがそのまま保存されています。このため、ファイルは大きくな...