macsbug

Just another WordPress.com site

Archive for the ‘ESP32’ Category

TETRIS with M5STACK

leave a comment »

TETRIS ゲームを M5STACK に移植しました。     2018.01.20

Aボタン=左。Bボタン=右。Cボタンで START と ROTATE です。

GitHub の 「MhageGH」 に 「esp32_ILI9328_Tetris」 があります。
ESP32 用で Arduino IDE で使用できます。
基の仕様は ESP32 + ILI9328 TFT Display で M5STACK ( ILI9341 TFT ) に
移植致しました。Mhage氏に感謝致します。


.
準備:
1. ハード:M5STACK
2. ハード:microSD ( FAT32, Class 10, 16GB ) ( 約40KByte の画像保存用です)
3. スケッチ:ブログ下のスケッチを Arduino IDE で M5STACK へ書き込みます。
_ 移植の為の修正部分が多数の為 全リストを表示しました。
4. 背景画像:microSD に「tetris.jpg」を保存します。
_ 以下の画像をDLし microSD に「tetris.jpg」で保存し M5の SD SLOT に入れます。
_ DLは PCやブラウザー経由の為か情報が変化し jpg でも表示しない場合があります。
_ その場合は 手間がかかりますが 追加手順で画像変換して使用します。

5. 追加手順:背景画像が表示されない場合。
_ M5STACK を起動し 背景画像がでない場合は「tetris.jpg」を変換します。
_ Convertimage に接続し 上記の画像を変換します。
_ 1. 1 > Choose the picture output format:「JPG」を選択します。
_ 2. 2 > Select your image:上記の 「tetris.jpg」を選択します。
_ 3. 「I agree to the terms of use」のボタンを「YES」にします。
_ 4. Convert this image のボタンを押します。
_ 5. 画像を DLし microSD に「tetris.jpg」で保存します。

メモ:
_ 開発環境:iMac 27 mid 2010, MacOS 10.12.6 Sierra。Arduino IDE 1.8.5。
_ PC上での 画像作成や変換で M5STACKで動作する画像フォーマットを
_ どう作成するか理解できていない状態です。
_ iMac上で jpg の背景画像を作りますが M5では表示しない時があります。
_ ネット上の「Convertimage」で変換すると表示します。
_ さらに変換画像をブログに表示し DL して使用すると 表示しません。
_ DLした画像を「Convertimage」で変換すると表示します。


.
移植:
移植基のスケッチ:「esp32_ILI9328_Tetris
移植スケッチの構成:スケッチと「BackgroundImage.h」「BlockImage.h」です。
_ コントローラーは WiFi接続で制御できるスケッチになっています。
_ 今回、外部コントロールは使用しません。

1. 背景画像変更:「BackgroundImage.h」は jpg画像 ( R.G.B 320×240 Pixel ) に
_ 変更し SD へ保存します。
2. BLOCK変更:「BlockImage.h」は 計算で作成します。( void setup 内 )
_ 色の値を変更すると 好きな色に変更できます。
3. SD:tetris.jpg と言う名前の画像を入れて使用します。
_ 同一名の画像を入れ替える事により、好きな画面に簡単に変更する事ができます。
_ SD の使用条件は microSD FAT32 Format で 16GB Class 10 を使用しました。
_ 32GB でも動きますが M5 の使用条件になっていないかも知れません。
_ 速度:Class 10 の速いものを使用した方が良いです。
_ SD を入れる向き:M5 FRONT を上にして SD は裏側で 水平に入れてください。
_ SD がない場合:画像がない場合は表示しません。背景画像の無いゲームになります。
_ 画像フォーマット: jpg でも多種類あり M5 の使用条件に合わないと 表示しません。
_ 画像処理アプリがない場合は ネット上に jpg 変換場所があり これを使用して
_ 正しく表示する jpg に変更します。


.
表示:M5STACK の ディスプレー画素数は 320×240 pixel です。
_  BLOCK が 動く画面は 中央の 120×240 pixel のエリアです。
_  背景画像は M5STACK の画素 320×240 pixel です。
_  BLOCK は 8つの種類があります。BLOCK は 4個の正方形で作られ
_  1つは 12×12 pixel です。
速度:BLOCK の落下速度は int game_speed = 25; で設定しています。
_  速度を速くするには 数値を減らしてください。
操作:Aボタン、Bボタン、Cボタン を使用します。
_  ボタン数の制約により BLOCKのDOWN 機能はありません。
注意:ボタンを酷使すると壊れるかも知れません。
_  M5STACK のスイッチは耐久性が少ないかも知れません。
_  操作方法は WiFi や BLE コントローラー等のアイデアがあるかと思います。
_  microSD の挿入は丁寧に行なってください。


.
参考:
Mhage氏:MhageGH/esp32_ILI9328_Tetris:今回の基です。
Mhage氏:ESP32でテトリスを作ってみた
Mhage氏:【ゆっくり解説】テトリスの作り方【ゲームプログラミング】
Mhage氏:【ゆっくり解説】 WiFiの使い方2 【電子工作】。:これは最高です!

Qiita:@inachi 氏の記事。1 – 5。
1. 2018.01.19:M5stackでMicroPythonを使えるようにする
2. 2018.01.07:M5Stack MicroPython API調査記録 ボタン編
3. 2018.01.08:M5Stack MicroPython API調査記録 RTC編
4. 2018.01.08:M5Stack MicroPython API調査記録 スレッド編
5. 2018.01.11:M5Stack MicroPython API調査記録 LCD編
この中で LCD の仕様で参考になる記事がありました。:配線やサイズ等です。


.
感想:
M5STACK の API や 画像構成、特に 画面配置と色の理解ができていません。
M5STACK のディスプレー ILI9341 は 他の ILI9341 TFT と何かが異なるようです。
_ 理由:
_ M5STACK + 外部ディスプレー + 外部ライブラリーの表示は 正常に動きます。
_ M5STACK + 外部ディスプレー + M5STACKライブラリーの表示は 正常に動きません。
_ M5STACK + TFT外部ライブラリーの表示は 正常に動きません。
_  240以上 の所で 表示がされません。
_ M5STACK の ディスプレーの構造や配線が解れば 理解できるのですが現在不明です。
_ M5STACK の ディスプレーが 一般的に販売されている TFT と仕様が異なると
_ 他の製品にした方が良い場面が出てきます。


.
スケッチ

//========================================================================
// TETRIS with M5STACK : 2018.01.20 Transplant by macsbug
// Controller : Buttons A = LEFT, B = RIGHT, C = START, ROTATE
// Display    : Left = 100x240, Center = 120x240, Right = 100x240
// Block      : 8ea, 12x12 pixel
// SD         : tetris.jpg : BackGround Image : R.G.B 320x240 pixel
//========================================================================
#include <M5Stack.h>                                       // M5STACK
uint16_t BlockImage[8][12][12];                            // Block
uint16_t backBuffer[240][120];                             // GAME AREA
const int Length = 12;     // the number of pixels for a side of a block
const int Width  = 10;     // the number of horizontal blocks
const int Height = 20;     // the number of vertical blocks
int screen[Width][Height] = {0}; //it shows color-numbers of all positions
struct Point {int X, Y;};
struct Block {Point square[4][4]; int numRotate, color;};
Point pos; Block block;
int rot, fall_cnt = 0;
bool started = false, gameover = false;
boolean but_A = false, but_LEFT = false, but_RIGHT = false;
int game_speed = 25; // 25msec
Block blocks[7] = {
  {{{{-1,0},{0,0},{1,0},{2,0}},{{0,-1},{0,0},{0,1},{0,2}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,1},
  {{{{0,-1},{1,-1},{0,0},{1,0}},{{0,0},{0,0},{0,0},{0,0}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},1,2},
  {{{{-1,-1},{-1,0},{0,0},{1,0}},{{-1,1},{0,1},{0,0},{0,-1}},
  {{-1,0},{0,0},{1,0},{1,1}},{{1,-1},{0,-1},{0,0},{0,1}}},4,3},
  {{{{-1,0},{0,0},{0,1},{1,1}},{{0,-1},{0,0},{-1,0},{-1,1}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,4},
  {{{{-1,0},{0,0},{1,0},{1,-1}},{{-1,-1},{0,-1},{0,0},{0,1}},
  {{-1,1},{-1,0},{0,0},{1,0}},{{0,-1},{0,0},{0,1},{1,1}}},4,5},
  {{{{-1,1},{0,1},{0,0},{1,0}},{{0,-1},{0,0},{1,0},{1,1}},
  {{0,0},{0,0},{0,0},{0,0}},{{0,0},{0,0},{0,0},{0,0}}},2,6},
  {{{{-1,0},{0,0},{1,0},{0,-1}},{{0,-1},{0,0},{0,1},{-1,0}},
  {{-1,0},{0,0},{1,0},{0,1}},{{0,-1},{0,0},{0,1},{1,0}}},4,7}
};
//========================================================================
void setup(void) {
  Serial.begin(115200);         // SERIAL
  M5.begin();                   // M5STACK INITIALIZE
  M5.Lcd.setBrightness(200);    // BRIGHTNESS = MAX 255
  M5.Lcd.fillScreen(BLACK);     // CLEAR SCREEN
  M5.Lcd.setRotation(0);        // SCREEN ROTATION = 0
  //----------------------------// Make Block ----------------------------
  make_block( 0, BLACK);        // Type No, Color
  make_block( 1, 0x00F0);       // DDDD     RED
  make_block( 2, 0xFBE4);       // DD,DD    PUPLE 
  make_block( 3, 0xFF00);       // D__,DDD  BLUE
  make_block( 4, 0xFF87);       // DD_,_DD  GREEN 
  make_block( 5, 0x87FF);       // __D,DDD  YELLO
  make_block( 6, 0xF00F);       // _DD,DD_  LIGHT GREEN
  make_block( 7, 0xF8FC);       // _D_,DDD  PINK
  //----------------------------------------------------------------------
  M5.Lcd.drawJpgFile(SD, "/tetris.jpg");     // Load background from SD
  PutStartPos();                             // Start Position
  for (int i = 0; i < 4; ++i) screen[pos.X + 
   block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
  Draw();                                    // Draw block
}
//========================================================================
void loop() {
  if (gameover) return;
  Point next_pos;
  int next_rot = rot;
  GetNextPosRot(&next_pos, &next_rot);
  ReviseScreen(next_pos, next_rot);
  M5.update();
  delay(game_speed);                                      // SPEED ADJUST
}
//========================================================================
void Draw() {                               // Draw 120x240 in the center
  for (int i = 0; i < Width; ++i) for (int j = 0; j < Height; ++j)
   for (int k = 0; k < Length; ++k) for (int l = 0; l < Length; ++l)
    backBuffer[j * Length + l][i * Length + k] = BlockImage[screen[i][j]][k][l];
    M5.Lcd.drawBitmap(100, 0, 120, 240, (uint16_t *)backBuffer);
}
//========================================================================
void PutStartPos() {
  pos.X = 4; pos.Y = 1;
  block = blocks[random(7)];
  rot = random(block.numRotate);
}
//========================================================================
bool GetSquares(Block block, Point pos, int rot, Point* squares) {
  bool overlap = false;
  for (int i = 0; i < 4; ++i) {
    Point p;
    p.X = pos.X + block.square[rot][i].X;
    p.Y = pos.Y + block.square[rot][i].Y;
    overlap |= p.X < 0 || p.X >= Width || p.Y < 0 || p.Y >= 
      Height || screen[p.X][p.Y] != 0;
    squares[i] = p;
  }
  return !overlap;
}
//========================================================================
void GameOver() {
  for (int i = 0; i < Width; ++i) for (int j = 0; j < Height; ++j)
  if (screen[i][j] != 0) screen[i][j] = 4;
  gameover = true;
}
//========================================================================
void ClearKeys() { but_A=false; but_LEFT=false; but_RIGHT=false;}
//========================================================================
bool KeyPadLoop(){
  if(M5.BtnA.wasPressed()){ClearKeys();but_LEFT =true;return true;}
  if(M5.BtnB.wasPressed()){ClearKeys();but_RIGHT=true;return true;}
  if(M5.BtnC.wasPressed()){ClearKeys();but_A    =true;return true;}
  return false;
}
//========================================================================
void GetNextPosRot(Point* pnext_pos, int* pnext_rot) {
  bool received = KeyPadLoop();
  if (but_A) started = true;
  if (!started) return;
  pnext_pos->X = pos.X;
  pnext_pos->Y = pos.Y;
  if ((fall_cnt = (fall_cnt + 1) % 10) == 0) pnext_pos->Y += 1;
  else if (received) {
    if (but_LEFT) { but_LEFT = false; pnext_pos->X -= 1;}
    else if (but_RIGHT) { but_RIGHT = false; pnext_pos->X += 1;}
    else if (but_A) { but_A = false;
      *pnext_rot = (*pnext_rot + block.numRotate - 1)%block.numRotate; 
    }
  }
}
//========================================================================
void DeleteLine() {
  for (int j = 0; j < Height; ++j) {
    bool Delete = true;
    for (int i = 0; i < Width; ++i) if (screen[i][j] == 0) Delete = false;
    if (Delete) for (int k = j; k >= 1; --k) 
    for (int i = 0; i < Width; ++i) screen[i][k] = screen[i][k - 1];
  }
}
//========================================================================
void ReviseScreen(Point next_pos, int next_rot) {
  if (!started) return;
  Point next_squares[4];
  for (int i = 0; i < 4; ++i) screen[pos.X + 
    block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = 0;
  if (GetSquares(block, next_pos, next_rot, next_squares)) {
   for (int i = 0; i < 4; ++i){
     screen[next_squares[i].X][next_squares[i].Y] = block.color;
   }
   pos = next_pos; rot = next_rot;
  }
  else {
   for (int i = 0; i < 4; ++i) screen[pos.X + 
    block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
   if (next_pos.Y == pos.Y + 1) {
    DeleteLine(); PutStartPos();
    if (!GetSquares(block, pos, rot, next_squares)) {
     for (int i = 0; i < 4; ++i) screen[pos.X + 
      block.square[rot][i].X][pos.Y + block.square[rot][i].Y] = block.color;
      GameOver();
    }
   }
  }
  Draw();
}
//========================================================================
void make_block( int n , uint16_t color ){            // Make Block color       
  for ( int i =0 ; i < 12; i++ ) for ( int j =0 ; j < 12; j++ ){
    BlockImage[n][i][j] = color;                           // Block color
    if ( i == 0 || j == 0 ) BlockImage[n][i][j] = 0;       // BLACK Line
  } 
}
//========================================================================


.

広告

Written by macsbug

1月 20, 2018 at 6:52 pm

カテゴリー: ESP32

esp32 snake with M5STACK

leave a comment »

esp32-snaker ゲームを M5STACK に移植しました。     2018.01.14


Cボタンで  START と RETRY。Aボタン=左。Bボタン=右。
A + C ボタン=上。B + Cボタン=下 です。

1970年代後半に登場したビデオゲームの古典。プレイヤーはヘビ
(1キャラ分から始まり) を操作しエサを回収します。エサを回収
するたびにヘビの身体は1キャラずつ長くなっていきます。

GitHub の 「HailTheBDFL」 に 「esp32-snaker」 があります。

ESP32 用で Arduino IDE で使用できます。
基の仕様は ESP32 + ILI9314 TFT Display で M5STACK ( ILI9314 TFT ) に
移植致しました。 初心者にも解りやすいスケッチですので 勉強にもなる
かと思います。「HailTheBDFL」 に感謝致します。


準備:
esp32-snake」を ダウンロードします。


移植:合計 15箇所で 削除 8箇所、変更 4つ、追加 3箇所 です。

1. 削除:もしくはコメントアウト( // ) します。:8箇所です。
_ 01. #include <Adafruit_GFX.h>
_ 02. #include <Adafruit_ILI9341.h>
_ 03. #include <SPI.h>
_ 04. Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI,
_     TFT_CLK, TFT_RST, TFT_MISO);
_ 05. touchAttachInterrupt(27, left, threshold); //Touch input
_ 06. touchAttachInterrupt(12, right, threshold);
_ 07. touchAttachInterrupt(14, up, threshold);
_ 08. ttouchAttachInterrupt(15, select, threshold);

2. 変更:DISPLAYを Adafruit_ILI9341 から M5STACK用に 3つ変更します。
_ 09. void setup() の「tft.begin(); 」 は 「 M5.begin(); 」に変更します。
_ 10. 全ての「 tft 」を 「 M5.Lcd 」に変更します。
_   Arduino IDE の「編集」の「検索」で 「検索テキスト = tft. 」,
_   「 置換テキスト = M5.Lcd. 」, 「 全て置換 」で 一気に変更できます。
_ 11. 「 tft.setRotation(3); 」は「 M5.Lcd.setRotation(0); 」:3 から 0
_   にします。これは 画面の向きです。
_ 12. 場所:void setup(){ の一番下。
_  「 randomSeed(analogRead(6)); 」は「 randomSeed(analogRead(26)); 」
_   6 から 26にします。

3. 追加:M5STACK用に 3箇所追加します。
_ 13. 場所:宣言部の一番上に追加。

  #include <M5Stack.h>

_ 14. 場所:void loop(){ の一番上に追加。

  if(M5.BtnA.isPressed() && M5.BtnC.isPressed()){up  ();}
  if(M5.BtnB.isPressed() && M5.BtnC.isPressed()){down();}
  if(M5.BtnA.isPressed()){left  ();}
  if(M5.BtnB.isPressed()){right ();}
  if(M5.BtnC.isPressed()){select();}

_ 15. 場所:void loop(){ の一番下に追加。

  M5.update();

コントロール:
_ 原作のコントロールは ESP32 の Touch input 端子を使用しています。
_  Touch input 端子は TOUCH 0 – TOUCH 9 まで 10個あります。
_  GPIO 0,2,4,12,13,14,15,27,32,33 です。
_ コントロールの内容は up, down, left, right, select の 4つです。
_ M5STACK の 全面フロントにある 左から A , B , C ボタンに置き換えます。
_ ボタンは 3つですので
_   left = A, right = B, select = C, up = A + C, down = B + C にしました。
_ 結果:スタート と リトライ は C ボタンです。
_    左は A、右は B、上は A+C、下は B+C です。
_ ボタン操作:
_  if(M5.BtnC.isPressed()) { select() ;}
_      C ボタンを「押している間は」 次の命令(select)を実行。
_  if(M5.BtnC.wasPressed(); { select() ;}
_      C ボタンを「押した時に」   次の命令(select)を実行。
_  他に、read, isReleased, wasReleased, pressedFor, releasedFor,
_     lastChange の便利な命令があります。
_ 速度:「float gameSpeed = 6; 」で 6 のスピードです。


感想:
ゲームスピード「float gameSpeed = 6; 」では 早いので 2 にしましたが
こういう分野のゲームは 忙しく不得意ですので勝てません。

ESP32の知りたいテクニック:
_ ESP32 + SD + DISPLAY のあるボードで、SD内にあるスケッチを起動する
_ 方々は無い物でしょうか?
_ ESP32起動時は 画面にスケッチのリストが出て、あるスケッチを選択すると
_ そのスケッチが起動する仕組みが欲しいです。
_ これが出来れば Arduino IDE 開発環境で コンパイルや書込みをせず
_ プログラム入りSD を配布するだけで多数のスケッチが動く様になります。
_ これって 今あるコンピューターのファインダーみたいな機能でしょうか。


.
スケッチ:追記:2018.02.17
_ SNAKE with M5STACK : 2018.01.14 Transplant by macsbug

//////////////////////////////////////////////////////////////////////////
//  A Simple Game of Snake
//  written by Tyler Edwards for the badge created in Hackerbox 0020,
//  but should work on any ESP32 and Adafruit ILI9341 screen
//  Tyler on GitHub: https://github.com/HailTheBDFL
//  Hackerboxes: http://www.hackerboxes.com/
//  To begin the game, press the select/start/fire/A button on HB badge (default pin 15)
//========================================================================
// SNAKE with M5STACK : 2018.01.14 Transplant by macsbug
// Controller: LEFT=Buttons A,     RIGHT=B,     START/RETRY=C
// Controller: UP  =Buttons A + C, DOWN =B + C
// Github    : https://macsbug.wordpress.com/2018/01/14/esp32-snake-with-m5stack/
//========================================================================
#include <M5Stack.h>
float gameSpeed = 2;       //Higher numbers are faster
int threshold = 40;        //threshold for touch
boolean start = false;     //will not start without say-so
unsigned long offsetT = 0; //time delay for touch
unsigned long offsetM = 0; //time delay for main loop
float gs;
int headX = 1;             //coordinates for head
int headY = 1;
int beenHeadX[470];        //coordinates to clear later
int beenHeadY[470];
int changeX = 0;           //the direction of the snake
int changeY = 1;
boolean lastMoveH = false; //to keep from going back on oneself
int score = 1;
int foodX;                 //coordinates of food
int foodY;
boolean eaten = true;      //if true a new food will be made
int loopCount = 0;         //number of times the loop has run
int clearPoint = 0;        //when the loopCount is reset
boolean clearScore = false;
//========================================================================
void setup() {
  gs = 1000 / gameSpeed;     //calculated gameSpeed in milliseconds
  memset(beenHeadX, 0, 470); //initiate beenHead with a bunch of zeros
  memset(beenHeadY, 0, 470);
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(ILI9341_BLACK);
  M5.Lcd.setTextColor(0x5E85);
  M5.Lcd.setTextSize(4);
  M5.Lcd.setCursor(80, 90);
  M5.Lcd.print(">START<");
  M5.Lcd.setTextColor(ILI9341_BLACK); //Score keeper
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(5, 3);
  M5.Lcd.print("Length: ");
  printScore();
  randomSeed(analogRead(26));        //make every game unique
}
//========================================================================
void loop() {
  if(M5.BtnA.isPressed() && M5.BtnC.isPressed()){up  ();}
  if(M5.BtnB.isPressed() && M5.BtnC.isPressed()){down();}
  if(M5.BtnA.isPressed()){left  ();}
  if(M5.BtnB.isPressed()){right ();}
  if(M5.BtnC.isPressed()){select();}
  if (clearScore and start) { //resets score from last game, won't clear
    score = 1;                //until new game starts so you can show off
    printScore();             //your own score
    clearScore = false;
  }
  if (millis() - offsetM > gs and start) {
    beenHeadX[loopCount] = headX;  //adds current head coordinates to be
    beenHeadY[loopCount] = headY;  //covered later   
    headX = headX + (changeX);  //head moved
    headY = headY + (changeY);    
    if (headX - foodX == 0 and headY - foodY == 0) { //food
      score += 1;
      printScore();
      eaten = true;
    }
    loopCount += 1; //loopCount used for addressing, mostly    
    if (loopCount > 467) {            //if loopCount exceeds size of
      clearPoint = loopCount - score; //beenHead arrays, reset to zero
      loopCount = 0;
    }   
    drawDot(headX, headY);           //head is drawn   
    if (loopCount - score >= 0) {    //if array has not been reset
      eraseDot(beenHeadX[loopCount - score], beenHeadY[loopCount - score]);
    }  //covers end of tail
    else {
      eraseDot(beenHeadX[clearPoint], beenHeadY[clearPoint]);
      clearPoint += 1;
    }
    if (eaten) {     //randomly create a new piece of food if last was eaten
      foodX = random(2, 26);
      foodY = random(2, 18);
      eaten = false;
    }
    drawDotRed(foodX, foodY); //draw the food
    if (headX > 26 or headX < 1 or headY < 1 or headY > 18) { //Boudaries
      endGame();
    }
    if (loopCount - score < 0) {         //check to see if head is on tail
      for (int j = 0; j < loopCount; j++) {
        if (headX == beenHeadX[j] and headY == beenHeadY[j]) {
          endGame();
        }
      }
      for (int k = clearPoint; k < 467; k++) {
        if (headX == beenHeadX[k] and headY == beenHeadY[k]) {
          endGame();
        }
      }
    }
    else {
      for (int i = loopCount - (score - 1); i < loopCount; i++) {
        if (headX == beenHeadX[i] and headY == beenHeadY[i]) {
          endGame();
        }
      }
    }   
    offsetM = millis(); //reset game loop timer
  }
  M5.update();
}
//========================================================================
void endGame() {
  M5.Lcd.fillRect(3, 21, 316, 226, ILI9341_BLUE); //deletes the old game  
  eaten = true;                                   //new food will be created 
  M5.Lcd.setCursor(80, 90);                       //Retry message
  M5.Lcd.setTextSize(3);
  M5.Lcd.setTextColor(ILI9341_WHITE);
  M5.Lcd.print("RETRY?");
  M5.Lcd.setTextColor(ILI9341_BLACK); //sets back to scoreboard settings
  M5.Lcd.setTextSize(2);
  M5.Lcd.setCursor(5, 3);
  M5.Lcd.print("Length: ");
  headX = 1;                 //reset snake
  headY = 1;
  changeX = 0;
  changeY = 1;
  lastMoveH = false;
  memset(beenHeadX, 0, 470); //clear the beenHead arrays
  memset(beenHeadY, 0, 470); //probably not necessary
  loopCount = 0;
  clearScore = true;
  start = false;             //stops game
}
//========================================================================
void drawDot(int x, int y) {
  M5.Lcd.fillRect(12*(x-1)+5, 12*(y-1)+23, 10, 10, ILI9341_WHITE);
}
//========================================================================
void drawDotRed(int x, int y) {
  M5.Lcd.fillRect(12*(x-1)+5, 12*(y-1)+23, 10, 10, ILI9341_RED);
}
//========================================================================
void eraseDot(int x, int y) {
  M5.Lcd.fillRect(12*(x-1)+5, 12*(y-1)+23, 10, 10, ILI9341_BLUE);
}
//========================================================================
void printScore() {
  M5.Lcd.fillRect(88, 3, 50, 16, ILI9341_WHITE);//clears old score
  M5.Lcd.setCursor(88, 3);
  M5.Lcd.print(score);                         //prints current score
}
//========================================================================
void up() {
  //lastMoveH makes sure you can't go back on yourself
  if (millis() - offsetT > gs and lastMoveH) {
    changeX = 0; changeY = -1;  //changes the direction of the snake
    offsetT = millis();
    lastMoveH = false;
  }
}
//========================================================================
void down() {
  if (millis() - offsetT > gs and lastMoveH) {
    changeX = 0; changeY = 1;
    offsetT = millis();
    lastMoveH = false;
  }
}
//========================================================================
void left() {
  if (millis() - offsetT > gs and !lastMoveH) {
    changeX = -1; changeY = 0;
    offsetT = millis();
    lastMoveH = true;
  }
}
//========================================================================
void right() {
  if (millis() - offsetT > gs and !lastMoveH) {
    changeX = 1; changeY = 0;
    offsetT = millis();
    lastMoveH = true;
  }
}
//========================================================================
void select() {
  if (millis() - offsetT > gs and !start) {
    M5.Lcd.fillRect(80, 90, 126, 24, ILI9341_BLUE); //Erase start message
    start = true;                                   //allows loop to start
    offsetT = millis();
  }
}
//========================================================================

 

Written by macsbug

1月 14, 2018 at 2:23 pm

カテゴリー: ESP32

esp32 spaceShooter with M5STACK

leave a comment »

esp32-spaceShooter ゲームを M5STACK に移植しました。   2018.01.12

SHOOTボタンで スタート と シュート。LEFTボタンで左。RIGHTで右移動です。
敵を全て破壊すると点数が加わり 次のステージに進む事ができます。

GitHub の 「HailTheBDFL」 に 「esp32-spaceShooter」 があります。

ESP32 用で Arduino IDE で使用できます。
基の仕様は ESP32 + ILI9314 TFT Display で M5STACK ( ILI9314 TFT ) に
移植致しました。 初心者にも解りやすいスケッチですので 勉強にもなる
かと思います。「HailTheBDFL」 に感謝致します。


準備:
esp32-spaceShooter-v1_0 」を ダウンロードします。


移植:合計 24箇所で 削除 13箇所、変更 3つ、追加 8箇所 です。

1. 削除:もしくはコメントアウト( // ) します。:13箇所です。
_ 01. #include <Adafruit_GFX.h>
_ 02. #include <Adafruit_ILI9341.h>
_ 03. #define TFT_CS 19
_ 04. #define TFT_DC 22
_ 05. #define TFT_MOSI 23
_ 06. #define TFT_CLK 26
_ 07. #define TFT_RST 21
_ 08. #define TFT_MISO 25
_ 09. Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_MOSI,
_     TFT_CLK, TFT_RST, TFT_MISO);
_ 10. touchAttachInterrupt(27, left, threshold); //Touch input
_ 11. touchAttachInterrupt(12, right, threshold);
_ 12. touchAttachInterrupt(14, up, threshold);
_ 13. ttouchAttachInterrupt(15, select, threshold);

2. 変更:DISPLAYを Adafruit_ILI9341 から M5STACK用に 3つ変更します。
_ 14. void setup() の「tft.begin(); 」 は 「 M5.begin(); 」に変更します。
_ 15. 全ての「 tft 」を 「 M5.Lcd 」に変更します。
_   Arduino IDE の「編集」の「検索」で 「検索テキスト = tft. 」,
_   「 置換テキスト = M5.Lcd. 」, 「 全て置換 」で 一気に変更できます。
_ 16. 「 tft.setRotation(3); 」は「 M5.Lcd.setRotation(0); 」:3 から 0
_   にします。これは 画面の向きです。

3. 追加:M5STACK用に 7箇所追加します。
_   場所:宣言部の最初の行:#include <Adafruit_ILI9341.h> のあった所。
_ 17. #include <M5Stack.h>
_   場所:setup() の1番下に追加します。ボタンをプルアップします。
_ 18. pinMode(BUTTON_A_PIN, INPUT_PULLUP);
_ 19. pinMode(BUTTON_B_PIN, INPUT_PULLUP);
_ 20. pinMode(BUTTON_C_PIN, INPUT_PULLUP);
_   場所:void loop() {  の最初の行に追加します。ボタン操作です。
_ 21. if(M5.BtnA.isPressed()) { left () ;}
_ 22. if(M5.BtnB.isPressed()) { right () ;}
_ 23. if(M5.BtnC.isPressed()) { select() ;}
_   場所:void loop() {  の最後の行に追加します。
_      ボタンやスピーカーを使用した時は M5.update を入れます。
_ 24. M5.update();


コントロール:
_ 原作のコントロールは ESP32 の Touch input 端子を使用しています。
_  Touch input 端子は TOUCH 0 – TOUCH 9 まで 10個あります。
_  GPIO 0,2,4,12,13,14,15,27,32,33 です。
_ コントロールの内容は up, down, left, right, select の 4つです。
_ M5STACK の 全面フロントにある 左から A , B , C ボタンに置き換えます。
_ ボタンは 3つですので left = A, right = B, select = C にしました。
_ 結果:スタート と シュートは C ボタンです。左移動は A, 右は B です。
_   SHOOTのCボタンは ボタンを痛めない様に 押している時は連射する
_   様に「isPressed」にしました。

_ ボタン操作:
_  if(M5.BtnC.isPressed()) { select() ;}
_      C ボタンを「押している間は」 次の命令(select)を実行。
_  if(M5.BtnC.wasPressed(); { select() ;}
_      C ボタンを「押した時に」   次の命令(select)を実行。
_  他に、read, isReleased, wasReleased, pressedFor, releasedFor,
_     lastChange の便利な命令があります。


感想:
スケッチは 解り安い内容で 各自のアイデアを盛り込める事が出来そうです。
音を追加したり 画像をもっとリアルで綺麗に 変更できるかと思います。
高速画像操作では M5STACKのサンプルにある「TFT_Flash_Bitmap」の
drawIcon命令は サンプルを動かすと高速である事が体験できます。
M5STACK には多様なライブラリーがありますので これをマスターすると
素晴らしい表示が出来るかと思います。

音:SHOOT の音を追加して雰囲気をだそうとしましたが、うるさくなり
_ 逆に雰囲気を下げるのでやめました。


.
スケッチ:追記:2018.02.14:
_ Space Shooter with M5STACK : 2018.01.12 Transplant by macsbug

//======================== intro =======================================
//      Space Shooter, basically knock-off Space Invaders
//             and also maybe a bit of Galaga
//   Written by Tyler Edwards for the Hackerbox #0020 badge
//  But should work on any ESP32 and Adafruit ILI9341 display
//        I am sorry for the messy code, you'll just
//                  have to live with it
//      Tyler on GitHub: https://github.com/HailTheBDFL/
//          Hackerboxes: http://hackerboxes.com/
//=========================== setup ===================================
// Space Shooter with M5STACK : 2018.01.12 Transplant by macsbug
// Controller   : Buttons A = LEFT, B = RIGHT, C = START or SHOOTING
// Github:https://macsbug.wordpress.com/2018/01/12/esp32-spaceshooter-with-m5stack/
//===================================================================
#include <M5Stack.h>
//============================= game variables =========================
unsigned long offsetM = 0;
unsigned long offsetT = 0;
unsigned long offsetF = 0;
unsigned long offsetB = 0;
unsigned long offsetA = 0;
unsigned long offsetAF = 0;
unsigned long offsetAB = 0;
unsigned long offsetS = 0;
int threshold = 40;
boolean startPrinted = false;
boolean beginGame = false;
boolean beginGame2 = true;
boolean play = false;
int score = 0;
int scoreInc = 10;
int level = 1;
//---------------------Player---------------------------------------
int shipX = 147;
int shipY = 190;
int oldShipX = 0;
int oldShipY = 0;
int changeShipX = 0;
int changeShipY = 0;
int shipSpeed = 50;
boolean doSplode = false;
boolean fire = false;
int fFireX[5] = {0, 0, 0, 0, 0};
int fFireY[5] = {0, 0, 0, 0, 0};
int fFireAge[5] = {0, 0, 0, 0, 0};
//--------------------------Aliens----------------------------------
boolean alienLive[18];
int alienLiveCount = 18;
int alienX = 7;
int alienY = 25;
int oldAlienX = 7;
int oldAlienY = 25;
int changeAlienX = 6;
int changeAlienY = 0;
int alienSpeed = 200;
int oldAlienSpeed;
int aFireX[5];
int aFireY[5];
boolean aFireAge[5];
int chanceOfFire = 2;
//================================ bitmaps ========================
//your starship
const int shipImgW = 14;
const int shipImgH = 16;
char shipImg[] = "ZZZZZZWWZZZZZZZZZZZYWWYZZZZZZZZZZWWWWZZZZZZZZZZ"
 "WWWWZZZZZZZZZWWWWWWZZZZZZZZWWWWWWZZZZZYZZWWWWWWZZYZZYZZWWWWWWZZ"
 "YZWWZZWWBBWWZZWWWWZZWBBBBWZZWWWWZWWBBBBWWZWWWWZWWWWWWWWZWWWWWWW"
 "WWWWWWWWWWRWWWWWWWWWWRWZZWWWWWWWWWWZZZZZWRRWWRRWZZZ";
//flames
const int flamesImgW = 12;
const int flamesImgH = 6;
char flamesImg[] = "RZZZZZZZZZZRRZRYYRRYYRZRRZRYYRRYYRZRZZRYRZZRY"
 "RZZZZZRZZZZRZZZZZZRZZZZRZZZ";
//alien
const int alienImgW = 14;
const int alienImgH = 11;
char alienImg[] = "GGGZZZZZZZZGGGZZZGZZZZZZGZZZZZGGGGGGGGGGZZZGGG"
 "GGGGGGGGGZGGGZGGGGGGZGGGGGGZZGGGGZZGGGGGGGGGGGGGGGGGGGGGGGGGGGG"
 "GGGGZZZGGZZGGZZZGZGGZGGZZGGZGGZZZZZZGZZGZZZZZ";
//ship 'sploded
const int splodedImgW = 14;
const int splodedImgH = 16;
char splodedImg[] = "ZZZZZZWWZZZZZZZZZZRYWWYRZZZYZZZRRWWRRRRZRWYZ"
 "RRRRRYYRRRZWYZRYRYYRYYRRRZWWRYYYRYYYYYRZWWRYYRYRYYYYRRWWRYYYRWR"
 "YBRRZRRRYRRWWWRYRWZZRYYRRBBWRYRWWZZRYYBBBRRYBWWRZZRYYYRRYYZZWZR"
 "RWRYYRBYRZZWZZRYBRYYYYYRRZZRWWYYYWWRRRZZZZWRRWWRRRWZZZ";
//=============================== setup and loop ==================
void setup() {
  memset(alienLive, true, 18);
  memset(aFireX, 0, 5);
  memset(aFireY, 0, 5);
  memset(aFireAge, 0, 5);
  M5.begin();
  M5.Lcd.setRotation(0);//M5.Lcd.setRotation(3);
  M5.Lcd.fillScreen(ILI9341_BLACK);
  M5.Lcd.setTextColor(0x5E85);
  M5.Lcd.setTextSize(4);
  randomSeed(analogRead(6));
  pinMode(BUTTON_A_PIN, INPUT_PULLUP);
  pinMode(BUTTON_B_PIN, INPUT_PULLUP);
  pinMode(BUTTON_C_PIN, INPUT_PULLUP);
}
//==================================================================
void loop() {
  if(M5.BtnA.isPressed()) { left  () ;}
  if(M5.BtnB.isPressed()) { right () ;}
  if(M5.BtnC.isPressed()) { select() ;}
  //-------------Start Screen--------------
  if (millis() - offsetS >= 900 and !beginGame) {
    if (!startPrinted) {
      M5.Lcd.setCursor(77, 105);
      M5.Lcd.print(">START<");
      startPrinted = true;
      offsetS = millis();
    }
    else {
      M5.Lcd.fillRect(77, 105, 244, 32, ILI9341_BLACK);
      startPrinted = false;
      offsetS = millis();
    }
  }
  if (beginGame and beginGame2) {
    M5.Lcd.fillRect(77, 105, 244, 32, ILI9341_BLACK);
    beginGame2 = false;
    play = true;
  }
  //-------------Player-----------------------------------------------
  if (millis() - offsetM >= shipSpeed and play) {
    moveShip();
    offsetM = millis();
  }
  if (oldShipX != shipX or oldShipY != shipY) {
    M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK);
    oldShipX = shipX;
    oldShipY = shipY;
    drawBitmap(shipImg, shipImgW, shipImgH, shipX, shipY, 2);
  }
  if (fire and play) { fireDaLazer();}
  if (millis() - offsetB >= 50) {
   for (int i = 0; i < 5; i++) {
    if (fFireAge[i] < 20 and fFireAge[i] > 0){keepFirinDaLazer(i);}
    if (fFireAge[i] == 20) { stopFirinDaLazer(i);}
   }
   offsetB = millis();
  }  
  if (millis() - offsetT > 50) {
    changeShipX = 0;
    changeShipY = 0;
  }
  //---------------Aliens--------------------------------------------
  if (millis() - offsetA >= alienSpeed and play) {
    moveAliens(); offsetA = millis();
  }
  if (findAlienX(5) >= 294){changeAlienX = -3;changeAlienY = 7;}
  if (alienX <= 6){changeAlienX = 3; changeAlienY = 7;}
  alienLiveCount = 0;
  for (int i = 0; i < 18; i++) {
   if (alienLive[i]) {
    alienLiveCount += 1;
     if (alienShot(i)) {
      M5.Lcd.fillRect(findOldAlienX(i),findOldAlienY(i),28,22,BLACK);
      alienLiveCount -= 1;
      alienLive[i] = false;
      score += scoreInc;
     }
     if (onPlayer(i) or exceedBoundary(i)) {
      gameOver();
    }
   }
  }
  if (alienLiveCount == 1) {
    oldAlienSpeed = alienSpeed;
    if (alienSpeed > 50) {
      alienSpeed -= 10;
    }
    else {
      alienSpeed = 20;
    }
  }
  if (alienLiveCount == 0) {
    levelUp();
  }
  M5.update();
}
// functions =======================================================
void gameOver() {
  play = false;
  if (doSplode) {
    drawBitmap(splodedImg,splodedImgW,splodedImgH,shipX,shipY,2);
  }
  M5.Lcd.fillScreen(ILI9341_BLACK);
  drawScore(false);
  delay(1000);
  M5.Lcd.setCursor(17, 168);
  M5.Lcd.setTextSize(2);
  M5.Lcd.print("(Reset device to replay)");
  while (1) { }
}
//==================================================================
void drawScore(boolean win) {
  M5.Lcd.setCursor(53, 40);
  M5.Lcd.setTextColor(ILI9341_WHITE);
  M5.Lcd.setTextSize(4);
  if (win) {
    M5.Lcd.print("LEVEL UP!");
  }
  else {
    M5.Lcd.print("GAME OVER");
  }
  for (;millis() - offsetM <= 1000;)
  M5.Lcd.setCursor(59, 89);
  M5.Lcd.setTextSize(3);
  M5.Lcd.print("Score: "); M5.Lcd.print(score);
  offsetM = millis();
  for (;millis() - offsetM <= 1000;) {
  }
  M5.Lcd.setCursor(71, 128);
  M5.Lcd.print("Level: "); M5.Lcd.print(level);
}
//==================================================================
void levelUp() {
  play = false;
  memset(alienLive, true, 18);
  memset(aFireX, 0, 5);
  memset(aFireY, 0, 5);
  memset(aFireAge, 0, 5);
  alienX = 7;
  alienY = 25;
  oldAlienX = 7;
  oldAlienY = 25;
  alienSpeed = oldAlienSpeed;
  if (alienSpeed > 100) {
    alienSpeed -= 10; chanceOfFire -= 10;
  }
  else if (alienSpeed > 50) {
    alienSpeed -= 10; chanceOfFire -=5;
  }
  else if (alienSpeed > 25) {
    alienSpeed -= 5; chanceOfFire -=1;
  }
  score += 50;     scoreInc += 5;
  changeShipX = 0; changeShipY = 0; 
  for (unsigned long i = millis(); millis() - i <= 1600;) {
    if (millis() - offsetM >= 20) {
      M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK);
      drawBitmap(shipImg,shipImgW,shipImgH,shipX,shipY,2);
      drawBitmap(flamesImg,flamesImgW,flamesImgH,shipX + 1,
                 shipY + 32,2);
      oldShipX = shipX; oldShipY = shipY;
      shipY -= 6;
      offsetM = millis();
    }
  }
  drawScore(true);
  level += 1;
  shipX = 147;
  shipY = 190;
  for (; millis() - offsetM <= 4000;) {
  }
  M5.Lcd.fillScreen(ILI9341_BLACK);
  offsetM = millis();
  play = true;
}
//==================================================================
boolean alienShot(int num) {
  for (int i; i < 5; i++) {
    if (fFireAge[i] < 20 and fFireAge[i] > 0) {
      if (fFireX[i] > findAlienX(num) - 4 and fFireX[i] < 
         findAlienX(num) + 28 and fFireY[i] < findAlienY(num) + 
         22 and fFireY[i] > findAlienY(num) + 4) {
        fFireAge[i] = 20;
        return true;
      }
    }
  }
  return false;
}
//==================================================================
boolean onPlayer(int num) {
  if (findAlienX(num) - shipX < 24 and findAlienX(num) - 
      shipX > -28 and findAlienY(num) - shipY < 32 and 
      findAlienY(num) - shipY > -22) {
    doSplode = true;
    return true;
  } else { return false;}
}
//==================================================================
boolean exceedBoundary(int num) {
  if (findAlienY(num) > 218) { return true;
  } else { return false;
  }
}
//==================================================================
void moveAliens() {
  for (int i = 0; i < 18; i++) {
   if (alienLive[i]) {
    M5.Lcd.fillRect(findOldAlienX(i),findOldAlienY(i),28,22,BLACK);
    drawBitmap(alienImg,alienImgW,alienImgH,findAlienX(i),
               findAlienY(i),2);
   }
  }
  oldAlienX = alienX; oldAlienY = alienY;
  alienX += changeAlienX; alienY += changeAlienY;
  if (changeAlienY != 0) { changeAlienY = 0; }
}
//==================================================================
int findAlienX   (int num) { return alienX + 42*(num % 6); }
//==================================================================
int findAlienY   (int num) { return alienY + 33*(num / 6); }
//==================================================================
int findOldAlienX(int num) { return oldAlienX + 42*(num % 6); }
//==================================================================
int findOldAlienY(int num) { return oldAlienY + 33*(num / 6); }
//---------------------------Player---------------------------------
void fireDaLazer() {
  int bulletNo = -1;
  for (int i = 0; i < 4; i++) {
    if (fFireAge[i] == 0) { bulletNo = i;}
  }
  if (bulletNo != -1) {
   fFireAge[bulletNo] = 1;
   fFireX[bulletNo] = shipX + 13;
   fFireY[bulletNo] = shipY - 4;
   M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,3,MAGENTA);
  }
  fire = false;
}
//==================================================================
void keepFirinDaLazer(int bulletNo) {
  M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,BLACK);
  fFireY[bulletNo] -= 8;
  M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,MAGENTA);
  fFireAge[bulletNo] += 1;
}
//==================================================================
void stopFirinDaLazer(int bulletNo) {
  M5.Lcd.fillRect(fFireX[bulletNo],fFireY[bulletNo],4,4,BLACK);
  fFireAge[bulletNo] = 0;
}
//==================================================================
void moveShip() {
  if (shipX + changeShipX < 288 and shipX + changeShipX > 
      6 and changeShipX != 0){
    shipX += changeShipX;
  }
  if (shipY + changeShipY > 24 and shipY + changeShipY < 
      192 and changeShipY != 0){
    shipY += changeShipY;
  }
  if (oldShipX != shipX or oldShipY != shipY) {
    M5.Lcd.fillRect(oldShipX, oldShipY, 28, 44, ILI9341_BLACK);
    oldShipX = shipX; oldShipY = shipY;
    drawBitmap(shipImg, shipImgW, shipImgH, shipX, shipY, 2);
  }
}
//==================================================================
void drawBitmap(char img[],int imgW,int imgH,int x,int y,int scale){
  uint16_t cellColor;
  char curPix;
  for (int i = 0; i < imgW*imgH; i++) {
    curPix = img[i];
    if (curPix == 'W') {      cellColor = ILI9341_WHITE; }
    else if (curPix == 'Y') { cellColor = ILI9341_YELLOW; }
    else if (curPix == 'B') { cellColor = ILI9341_BLUE; }
    else if (curPix == 'R') { cellColor = ILI9341_RED; }
    else if (curPix == 'G') { cellColor = 0x5E85; }
    if (curPix != 'Z' and scale == 1) {
      M5.Lcd.drawPixel(x + i % imgW, y + i / imgW, cellColor);
    }
    else if (curPix != 'Z' and scale > 1) {
      M5.Lcd.fillRect(x + scale*(i%imgW),y + 
         scale*(i/imgW),scale,scale,cellColor);
    }
  }
}
//=========================== button functions =====================
void up() {
  if (millis() - offsetT >= 50 and play) {
    changeShipX = 0; changeShipY = -6; offsetT = millis();
  }
}
//==================================================================
void down() {
  if (millis() - offsetT >= 50 and play) {
    changeShipX = 0; changeShipY = 6; offsetT = millis();
  }
}
//==================================================================
void left() {
  if (millis() - offsetT >= 50 and play) {
    changeShipX = -6; changeShipY = 0; offsetT = millis();
  }
}
//==================================================================
void right() {
  if (millis() - offsetT >= 50 and play) {
    changeShipX = 6; changeShipY = 0; offsetT = millis();
  }
}
//==================================================================
void select() {
  if (millis() - offsetF >= 500 and play) {
    fire = true; offsetF = millis();
  }
  if (!beginGame) { beginGame = true;}
}
//==================================================================

 

Written by macsbug

1月 12, 2018 at 2:14 pm

カテゴリー: ESP32

PacketMonitor32 with M5STACK

with 2 comments

PacketMonitor32 を M5STACK に移植しました。          2018.01.11

PacketMonitor とは 選択したWiFiチャンネルにあるデバイスの トラフィックを表示します。
表示は [ Channel ], [ RSSI (平均) ], [ Packet per Second ], [ death packets ], [ SD Card enabled ]
ボタンをクリックすると、監視している WiFiチャンネル (1-14 ch) が変更されます。
トラフィックをキャプチャする為の SDカードのサポートがあります。

M5STACK ( ESP32 + 320×240 TFT + SD Slot + Speaker + SW + BAT ):$35
M5STACK は Micro SD を入れるだけで使用できます。

LOLIN32 Pro ( ESP32-WROVER + SD Slot ):$10.78
0.96″ 128×64 OLED SSD1306 を追加しました。

TTGO T2 ( ESP-WROOM-32 + 0.95″ 96×64 Color OLED + SD Slot ):$13.55
Color OLED と SD Slot 付きです。Color OLED は小型ですが綺麗です。

PacketMonitor32spacehuhn氏により作成されたもので ハードの構成は
D-duino-32 SD + 128×64 OLED SSD1306 + micro SD card です。
spacehuhn氏に感謝致します。


マイクロSDカード:
1. 条件:16MB , FAT32 フォーマット , CLASS 10:高速なSDである事。
2. カードを取り出してボードを稼働させたい場合は「SD」が表示されなくなるまで
_ ボタンを2秒間押し続けます。
_ ボードが起動するたびに SDのルートフォルダに新しい.pcapファイルが作成されます。
_ これらのファイルは 「Wireshark 」で開くことができます。
_ 全てのパケットを常に保存することはできません。
_ 保存できるパケットが増えれば、パケットは廃棄されます。


移植:
1. ディスプレー :SSD1306 のドライバーを ILI9341 の記述に変更しました。
_ 例:display — M5.Lcd。変数の配置変更。
_ グラフ表示:ディスプレーの大きさに合わせて サイズを変更しました。
_ 例:display..drawString — M5.Lcd.drawString 。変数の配置変更。
_ 例:display..drawLine —— M5.Lcd.drawLine 。 変数の配置変更。

2. SD MMC:SD の記述に変更しました。( MMC を理解していません )
_ 例:SD_MMC — SD
_ M5STACK SDの配線
_  GPIO23=MOSI(CMD),19=MISO(D0), GPIO18=CLK, GPIO4=CS(CD/D3)

3. 条件:Packet は 毎秒の為、表示や 特にSDが高速である事が重要です。
_ シリアルモニターに「24546 bytes written for 86 ms」とか表示されます。
_ SPIの通信方式や SDの速度、ESP32ボードの構成により異なります。
_ 1秒間に行う動作:パケット受信 と 表示 と SDへの書込み。
_ SDへの書込時間が短い事が必要です。

書き込み データー:
_ 書き込みデーターの容量:1回に 約25KB です。
_ 書き込み速度:66 〜 92msec。約50sec に一度 500 〜 1000msec が起きます。


動作内容:
1. パケットの検出。
2. ディスプレーの表示。
3. SD へ書き込み。
_ 1秒間に パケット検出、ディスプレー表示、SD書き込み が行われます。
_ ディスプレーの表示 と SD へ書き込み が高速である事が 望まれます。
_ スケッチの中で
_ SD への書き込み速度が 450msec とかになると 書き込まれません。
_ テクニック:初期設定で SPIの速度「 SD.begin(sd_ss, SPI, 20000000);」
_  にする。SPI.begin 設定の前に SPI.end(); で SDを確実に設定します。
_  SPI は 通常 4MHz ですが 例として「SD.begin(sd_ss, SPI, 24000000);」
_  で 24MHz に設定し高速になります。

SD書き込み速度:シリアルモニターで観測。
_ 書込み速度が速い程 Packet のモニターに時間を使用する事が出来ます。
_ たまにSDへの書込み速度が低下する場所があります。
_ M5STACK は 約150msec。
_ LOLIN32 Pro は 約50msec。ただし不安定です。
_ TTGO T2 は 約50msec。

Wireshark を使用した 解析表示の例:


課題:
1. スケッチの書き方で速度が変わります。特に SDの setup の設定です。
_ SPI の設定が複数ある場合、SPI.end(); が必要です。
2. LOLIN32 Pro は SD の認識でロスする場合があり リトライで対応しています。
3. 速度や間欠部は 課題です。


参考:
1. acketMonitor32 Interface Explaination:表示とボタンの説明。
2. spacehuhn / PacketMonitor32
3. spacehuhn / ESP8266 PakcketMonitor
4. tindle:WiFi Packet Monitor V3 (Preflashed D-duino-32 SD)
5. Aliexpress:DSTIKE Store:WiFi Packet Monitor V3(Preflashed D-duino-32 SD)
6. YouTube:PacketMonitor32
7. M5STACK Pinout


感想:
1. PacketMonitor32 を作成された spacehuhn氏のリストは奥深く
_ 私には 理解できない高度なレベルの記述が多々あります。
_ よって 知りえる程度でスケッチを変更しました。
_ 特に感心したのは グラフと横スクロール の方法です。
_ この方法をマスターすると素晴らしい表現が出来るかと思います。
2. SD と SDMMC:
_ SD式の信号は D3/CD, MOSI(CMD), CLK, MISO/D0
_ で 4本配線の回路になっています
_ SDMMC式の信号は D2, C2/D3, MOSI/CMD, CLK, MISO(D0), D1
_ で 6本配線の回路になっています
_ 6本配線は 「D-Duino32 SD」と「魔法の大鍋氏のESP32ボード」があります。
_ 魔法の大鍋氏は製作時に ESP社の基本に基づき作られました。
_ PacketMonitor32 のボードは SDMMC回路のボードと思われます。
_ LOLIN32 Pro の回路は SDMMC接続の回路になっています。
_ D2=GPIO12, C2/D3=13, MOSI/CMD=15, CLK=14, MISO(D0)=2,D1=4
_ です。
_ M5STACK は SD 回路です。
_ TTGO T2 は SD 回路です。
_ 他の SD 付きの ESP32 ボードは SD式で構成されています。
2. SD CARD:
_ 使用した SD は マイクロSD 16G FAT32 Class 10 です。
_ SD を理解していなく 又 手持ちにある SD は マイクロSDです。
_ ESP32 のライブラリーに SD と SDMMC のサンプルがあります。
_ これは マイクロSDで動作します。(意味が解っていないかも)
_ PacketMonitor32 のスケッチは SDMMC を使用しています。
_ この SDMMC の記述では書込みエラーがでて使用できません。
_ SDMMC の記述を SD に変更して動くようにしました。
3. 画面の大きさ:
_ 小さい画面の場合、スクロール等の工夫が必要でプログラムが負担です。
_ M5STACK の画面は大きく 縦側に数値を表示し見やすくなりました。
_ 工夫すると見栄えの良い物ができますし高機能になります。
4. アクリル台:
_ ダイソー:L型 カードスタンド です。6.5 x4cm。2個で100円。


スケッチ:PacketMonitor32 of M5STACK.

訂正事項:77行目の M5.Lcd.drawString( “Made with <_3 by”, 24, 44);
_ の 「<_3 」は「 _ 」 を取り除いて「<3」に変更してください。
_ 装置がネットに接続されいますと 画面に素晴らしい文字が表示されます。
_ 205行目の「:D_ *」は「_ 」 を取り除いて「:D *」にしてください。emojiです。
補足:フォント “Free_Fonts.h” をスケッチ内に入れておきます。
補足:LOLIN32 Pro と TTGO T2 のリストは省略致します。

// PacketMonitor32 of M5STACK : 2018.01.11 macsbug
// spacehuhn/PacketMonitor32 : 
//  https://github.com/spacehuhn/PacketMonitor32/
// DISPLAY: Channel,RSSI,Packet per Second,deauth packets,SD Card enabled
// Button : click to change channel hold to dis/enable SD
// SD : GPIO4=CS(CD/D3), 23=MOSI(CMD), 18=CLK, 19=MISO(D0)
//--------------------------------------------------------------------
#include <M5Stack.h>
#include "Free_Fonts.h"
#include <SPI.h>
#include "freertos/FreeRTOS.h"
#include "esp_wifi.h"
#include "esp_wifi_types.h"
#include "esp_system.h"
#include "esp_event.h"
#include "esp_event_loop.h"
#include "nvs_flash.h"
#include <stdio.h>
#include <string>
#include <cstddef>
#include <Preferences.h>
#define MAX_CH 14     // 1-14ch(1-11 US,1-13 EU and 1-14 Japan)
#define SNAP_LEN 2324 // max len of each recieved packet
#define BUTTON_PIN 39 // button to change the channel
#define MAX_X 315     // 128
#define MAX_Y 230     //  51
#if CONFIG_FREERTOS_UNICORE
#define RUNNING_CORE 0
#else
#define RUNNING_CORE 1
#endif
#include "Buffer.h"
#include "FS.h"
#include "SD.h"
enum { sd_sck = 18, sd_miso = 19, sd_mosi = 23, sd_ss = 4 };
esp_err_t event_handler(void* ctx,system_event_t* event){return ESP_OK;}
/* ===== run-time variables ===== */
Buffer sdBuffer;
Preferences preferences;
bool useSD = false;
bool buttonPressed = false;
bool buttonEnabled = true;
uint32_t lastDrawTime;
uint32_t lastButtonTime;
uint32_t tmpPacketCounter;
uint32_t pkts[MAX_X]; // here the packets per second will be saved
uint32_t deauths = 0; // deauth frames per second
unsigned int ch = 1;  // current 802.11 channel
int rssiSum;

// ===== main program ================================================
void setup() {
  // Serial ----------------------------------------------------------
  Serial.begin(115200);
  // System & WiFi ---------------------------------------------------
  nvs_flash_init();
  tcpip_adapter_init();
  wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
  ESP_ERROR_CHECK(esp_event_loop_init(event_handler, NULL));
  ESP_ERROR_CHECK(esp_wifi_init(&cfg));
  //ESP_ERROR_CHECK(esp_wifi_set_country(WIFI_COUNTRY_EU));
  ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
  ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_NULL));
  ESP_ERROR_CHECK(esp_wifi_start());
  esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
  // display -------------------------------------------------------
  M5.begin();
  dacWrite(25, 0); // Speaker OFF
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(WHITE, BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setRotation(0);
  /* show start screen */
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.drawString( "PacketMonitor32", 6, 24);
  M5.Lcd.drawString( "Made with <_3 by", 24, 44);
  M5.Lcd.drawString( "@Spacehuhn", 29, 64);
  delay(3000);
  M5.Lcd.fillScreen(BLACK);
  M5.Lcd.setFreeFont(FM9);
  int s = 10, a = 0;
  M5.Lcd.setTextColor( WHITE, BLACK);  // Pkts Scale
  for ( int ypos = MAX_Y; ypos > 120; ypos = ypos - s ){
    M5.Lcd.setTextDatum(MR_DATUM);
    M5.Lcd.drawString(String( MAX_Y - ypos ),30, ypos - 1 - a);
    a = a + 10;
  }
  M5.Lcd.setFreeFont(FM9);
  M5.Lcd.setTextDatum(TL_DATUM);
  M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
  // SD card ---------------------------------------------------------
  SPI.end();
  SPI.begin(sd_sck, sd_miso, sd_mosi, sd_ss); 
  SD.begin(sd_ss, SPI, 24000000);
  if(!SD.begin(sd_ss,SPI)){
    Serial.println("Card Mount Failed");return;
  }
  sdBuffer = Buffer();
  if (setupSD()){sdBuffer.open(&SD);Serial.println(" SD CHECK OPEN");
  }
  // I/O -----------------------------------------------------------
  pinMode(BUTTON_PIN, INPUT_PULLUP);
  // second core ----------------------------------------------------
  xTaskCreatePinnedToCore(
    coreTask,               /* Function to implement the task */
    "coreTask",             /* Name of the task */
    2500,                   /* Stack size in words */
    NULL,                   /* Task input parameter */
    0,                      /* Priority of the task */
    NULL,                   /* Task handle. */
    RUNNING_CORE);          /* Core where the task should run */
  // start Wifi sniffer ---------------------------------------------
  esp_wifi_set_promiscuous_rx_cb(&wifi_promiscuous);
  esp_wifi_set_promiscuous(true);
}
// ===== main program ================================================
void loop() {
  vTaskDelay(portMAX_DELAY);
  
}
// ===== functions ===================================================
double getMultiplicator() {
  uint32_t maxVal = 1;
  for (int i = 0; i < MAX_X; i++) {
    if (pkts[i] > maxVal) maxVal = pkts[i];
  }
  if (maxVal > MAX_Y) return (double)MAX_Y / (double)maxVal;
  else return 1;
}
// ===== functions ===================================================
void setChannel(int newChannel) {
  ch = newChannel;
  if (ch > MAX_CH || ch < 1) ch = 1;
  preferences.begin("packetmonitor32", false);
  preferences.putUInt("channel", ch);
  preferences.end();
  esp_wifi_set_promiscuous(false);
  esp_wifi_set_channel(ch, WIFI_SECOND_CHAN_NONE);
  esp_wifi_set_promiscuous_rx_cb(&wifi_promiscuous);
  esp_wifi_set_promiscuous(true);
}
// ===== functions ===================================================
bool setupSD() {
  if (!SD.begin(sd_ss, SPI)) {
    Serial.println("Card Mount Failed"); return false;
  }
  uint8_t cardType = SD.cardType();
  if (cardType == CARD_NONE) {
    Serial.println("No SD_MMC card attached"); return false;
  }
  Serial.print("SD_MMC Card Type: ");
  if (cardType == CARD_MMC){         Serial.println("MMC");
  } else if (cardType == CARD_SD){   Serial.println("SDSC");
  } else if (cardType == CARD_SDHC){ Serial.println("SDHC");
  } else {                           Serial.println("UNKNOWN");
  }
  uint64_t cardSize = SD.cardSize() / (1024 * 1024);
  Serial.printf("SD_MMC Card Size: %lluMB\n", cardSize);
  return true;
}
// ===== functions ===================================================
void wifi_promiscuous(void* buf, wifi_promiscuous_pkt_type_t type) {
  wifi_promiscuous_pkt_t* pkt = (wifi_promiscuous_pkt_t*)buf;
  wifi_pkt_rx_ctrl_t ctrl = (wifi_pkt_rx_ctrl_t)pkt->rx_ctrl;
  if (type == WIFI_PKT_MGMT && 
     (pkt->payload[0] == 0xA0 || pkt->payload[0] == 0xC0 )) deauths++;
  if (type == WIFI_PKT_MISC) return;   // wrong packet type
  if (ctrl.sig_len > SNAP_LEN) return; // packet too long
  uint32_t packetLength = ctrl.sig_len;
  if (type == WIFI_PKT_MGMT) packetLength -= 4; 
  // fix for known bug in the IDF
  // https://github.com/espressif/esp-idf/issues/886
  //Serial.print(".");
  tmpPacketCounter++;
  rssiSum += ctrl.rssi;
  if (useSD) sdBuffer.addPacket(pkt->payload, packetLength);
}
// ===== functions ===================================================
void draw() {
  double multiplicator = getMultiplicator();
  int len, rssi;
  if (pkts[MAX_X - 1] > 0) rssi = rssiSum / (int)pkts[MAX_X - 1];
  else rssi = rssiSum;
  String p = (String)ch + " | " + (String)rssi + " | Pkts " +
     (String)tmpPacketCounter + " [" + deauths + "]" +
     (useSD ? " | SD" : "");
  M5.Lcd.setTextColor(WHITE,BLUE);                    // packet
  M5.Lcd.drawString(p + "  ", 10, 2);                 // string DRAW
  M5.Lcd.drawLine(40,MAX_Y-200,MAX_X,MAX_Y-200,GREEN);// MAX LINE DRAW
  for (int i = 40; i < MAX_X; i++) {                  // LINE DRAW
    len = pkts[i] * multiplicator;
    len = len * 2;
    if ( (MAX_Y - len) < (MAX_Y - 200)){ len = 200;}  // over flow
    M5.Lcd.drawLine(i, MAX_Y, i, 31, TFT_BLACK);      // LINE EARSE
    M5.Lcd.drawLine(i, MAX_Y, i, MAX_Y - len , GREEN);// LINE DRAW
    if (i < MAX_X - 1) pkts[i] = pkts[i + 1];
  }
}
// ===== functions ===================================================
void coreTask( void * p ) {
  uint32_t currentTime;
  while (true) {
    currentTime = millis();
    /* bit of spaghetti code, have to clean this up later :D_ */
    // check button
    if (digitalRead(BUTTON_PIN) == LOW) {
      M5.Lcd.fillRect(0, 0, 320, 20, BLUE);
      if (buttonEnabled) {
        if (!buttonPressed) {
          buttonPressed = true;
          lastButtonTime = currentTime;
        } else if (currentTime - lastButtonTime >= 2000){
          if (useSD) {
            useSD = false;
            sdBuffer.close(&SD);
            draw();
          } else {
            if (setupSD())
              sdBuffer.open(&SD);
            draw();
          }
          buttonPressed = false;
          buttonEnabled = false;
        }
      }
    } else {
      if (buttonPressed) {
        setChannel(ch + 1);
        draw();
      }
      buttonPressed = false;
      buttonEnabled = true;
    }
    // save buffer to SD
    if (useSD) sdBuffer.save(&SD);
    // draw Display
    if ( currentTime - lastDrawTime > 1000 ) {
      lastDrawTime = currentTime;
      // Serial.printf("\nFree RAM %u %u\n", 
      // heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT),
      // heap_caps_get_minimum_free_size(MALLOC_CAP_32BIT));
      // for debug purposes
      pkts[MAX_X - 1] = tmpPacketCounter;
      draw();
      //Serial.println((String)pkts[MAX_X - 1]);
      tmpPacketCounter = 0;
      deauths = 0;
      rssiSum = 0;
    }
    // Serial input
    if (Serial.available()) {
      ch = Serial.readString().toInt();
      if (ch < 1 || ch > 14) ch = 1;
      setChannel(ch);
    }
  }
}
// ===================================================================


Written by macsbug

1月 11, 2018 at 1:00 am

カテゴリー: ESP32

Expansion connector of M5STACK

leave a comment »

M5STACK の拡張コネクターを作りました。             2018,.01.09

オスピン と メスピン を作り M5STACK のコネクターにピッタリ入ります。
インターフェースのピンは M5STACKの内側に刺して使用します。
インターフェースの種類によりコネクターの基板側に配線をします。
使用例は Audio Spectrum Display with M5STACK を参考してください。
これで長時間の試験後に インターフェースを Proto Module に組み込むと良いかと思います。

 


販売:
M5STACK の拡張は 以下の3つが販売されています。特に Proto Module
直ぐ使用でき便利です。 デザインは素晴らしく綺麗に仕上がります。
ケースには 幾つかの 穴 や切り欠き があり ワイヤーを外部に出す時に
便利です。 空気穴 もあり 環境センサーを使用する時に効果があり
各所に決め細かな配慮がされています。

1. Proto Module:$9.99
2. PROT Board  :$2.50
3. 2×15 Pin Headers Socket 2.54mm Male & Female:$10.00 / 4ea


製作したコネクター:



身近な部品を使用しました。


準備:

No Nomen Link  Price
1  切れる基板
_ 0.3mm厚
_ 両面スルーホール
_ ユニバーサル基板
 秋月電子通商  60円
2  ピンソケット(メス)
_ 1×14 ( 14p )
 秋月電子通商     50円
3  細ピンソヘッダ
_ 1×14 ( 14p )
 秋月電子通商     35円
4  ピンソケット(メス)
_ 1×8 ( 8P )
 秋月電子通商    30円
4  ProtoBoard Shield
. 8p Female pin socket
. 8p male pin socket
 Aliexpress
. GREATZT
. $0.43
   112円
/ 6個

 


書き込めない時 や Timed out エラーの 対策:
問題無し:マックでは 書込みに問題は無く エラーはありません。
問題あり:この装置に限らず Windows では 多くの製品で 機種やOSのバージョンに
_ より動作しない製品が多い話を聞いており この事を理解しておく必要があります。
_ オシロスコープで AUTO PROG回路を測定し波形分析すれば問題点が明らかに
_ なります。恐らくAUTO PROG回路の書込みのタイミングに対し Windows 側が
_ 把握していないからだと想像します。
_ 波形を分析すれば解る事ですが この分析をした記事はまだ見かけていません。
_ この事をせず コンデンサーを追加するというレベルになっているのが多いです。

書き込みエラー や Timeout のメッセージがでる人がいるようです。
ハード的な解決方法として RSTとGND間に 起動用のコンデンサーを追加します。

M5STACK のRST(EN) には ESPの基本設計である 1nF が 回路に入っています。
簡単な方法は RSTとGND間に100pF 又は 0.1μF を追加するとよいでしょう。
他の原因:
_ ある時 書き込めたのでエラーかと思ったら USB Driver を入れ忘れていた。
_ CP2104 Driver のバージョンによる。
_ USB-C コネクター や ケーブルの不良。
_ 電源供給の無いUSBハブを使用している。
_ PC側の設定:開発環境のセットアップの間違い。
_ 古い esptool を使用している。現在は ver 2.1 でリトライ機能があります。
_ Arduino – ESP32 の最新版は、1回目のコンパイル書込みで必ず失敗する。
_ Arduino IDE の Serial Monitor の表示を使用している。
_ Arduino IDE の Board 選択を M5stack-Core-ESP32 にしていない。
_ 信号を解析せず 大きなコンデンサーを付けている。
_ SH2017の例:中国製の偽物の CP2102 を使用した場合。保護DIODE無く
_  USB切断時によるスパイクはESP32をリセットする。
_  HACKAY Aug 14,2017 :HANDS ON WITH THE SHACAMP 2017 BADGE


ピン に Tip Capacitor 100nF と 0.1μF を取り付け 2種類作りました。
ピンを曲げて Capacitor をハンダ付けしますので Wire は不要です。

M5 CONNECTOR の GND — RST 間に装着します。外から操作するので簡単です。

M5Stack twitter 情報:2018.01.09
Simple Fix Core Upload Fails:
_ Downgrade the cp2104 driver, or Add a 2.2uf Cap between RST and GND.
_ For units which already sold, here is the solution for upload fails.
_ The upload failure issue will be solved in future manufactured units.

事例:matsujirushi’s blog:M5StackのTimed outエラー対策


感想:
Proto Module を購入すれば よいですが、ちょっと下準備や試したい時
最終的にケースをどうするか考ている時 費用を安く済ませたい時に
自作コネクターで良いかもも知れません。
理想的には Proto Module  を低価格で販売して欲しいです。そうすれば
多くの方が 多くの作品を作る事ができ M5STACK が盛り上がります。

書き込めない問題:逆にプロテクションになるかもしれません。


Written by macsbug

1月 9, 2018 at 10:46 am

カテゴリー: ESP32

M5Stack Faces Pocket Computer

leave a comment »

M5STACK FACES Pocket Computer with Keyboard/Gameboy/Calculator      2018.01.02

M5STACK による FACES Pocket Computer は 以下の構成です。
M5STACK に TELEC のシールが あります。番号は 211-171004 です。

内容:左上から
1.  FACES POCKET 以外のケース。
2.  FACES POCKET のケース。
3.  Charge Base:電源供給とバッテリー充電が可能。
4.  FACES Pocket Computer:M5STACK+マザーボード+バッテリー。
5.  Gameboy Panel。
6.  Number Panel。
7.  KeyBoard Panel。
8.  Type C Cable
9.  Strap。
10. Cable Connector:M5STACKの拡張コネクターに直ぐ使用できます。
11. USB Serial Board:AVR ISP。 Panel 開発用です。
12. Screw M3-20mm:FACES Pocket の裏から止める6角のネジです。
13. HEX Wrench。

販売:Faces Pocket Computer with Keyboard/Gameboy/Calculator
_ 構成等、詳細が記載されています。
価格:$79.11(SALE)。通常 $ 89.90。輸送期間 21日。


FACES メインボード:
_ 裏の6個の黒丸のシールは滑り止め、内側には磁石。
_ キーボード インターフェース と メインBUSの接続。
_ バッテリー内蔵。
_ 外部への丸ピンは Charge Base にある USB-C への 接続コネクター。
_ 色字のピン名称と番号。
_ 内側は 6.4 mm の厚さ。
_ スタック時にSCREWの出っ張りが4つの穴に収まる位置(位置止めに利用)。
_ 手の平のフィット感が気持ちいい。
_ レイアウト と 部品配置が素晴らしいです。
_ 左にスタックしてある M5STACK は 取り外し可能です。


Faces Pocket の Panel :
_ KeyBoard, Calculator, GameBoy Panel があり ATMEGA328P が
_ 使用されています。
_ これらの KeyBoard や Panel により ハード製作に時間を費やす事無く
_ 多彩な装置を 容易に作る事ができます。
_ KIT には AVR ISP ( USB 書き込み装置 )も付属しています。
_ そして GitHub には FACES の スケッチが 既に用意さています。

Faces Panel をアップグレードする方法:
_ 参考:M5Stack facebook


ESP32-NESEMU, a Nintendo Entertainment System emulator for the ESP32:
Github に NESEMU のリストが公開されています。
m5stack/M5Stack-nesemu

YouTube:Playing Super Mario on M5Stack !


M5STACK BACK COVER:
同じ信号の端子を「メスピン」と「オスピン」で 両サイドに配置。
そして 番号と名前を 対比できるデザインは 賢いです。


M5Stack Web IDE:
_ M5STACK の Githubには 新たに 「M5Cloud」が追加されました。
_ M5Cloud:M5STACK を「M5Cloud」で動かし、 PC上のブラウザー上で
_ 「M5Stack Web IDE」を動かし開発する事ができるらしいです。

_ M5Cloud Device Management

参考:
Qiita:inachi:M5stackでMicroPythonを使えるようにする
M5Cloud へ接続して行います。


感想:
FACES Pocket Computer は Kickstarter から始まり 長年に渡る開発により 完成度
は高く、外見や内部を見ると 随所にアイデアや 詰めに詰めた深さがあります。
これまでの ESP の製品の中で 実に良く出来た製品です。

沢山購入すると 費用は高くなりますが 即動作、高機能、ハードとライブラリー
の 一体化が優れています。特に ライブラリーは 多彩な機能があります。
各部の作り具合や 部品費を考えると 購入した方が 安いです。

さらに 国内での ESP32ボード や 周辺装置を組み合わせた製品の総額、雑誌の
付録等にある ESP32ボードの 不完全な設計や 組み立てに かかる時間、完成度
や綺麗さを考慮すると M5STACK や FACES Pocket を購入した方が 経済的です。

M5STACK のケースは 美しく 完成品は誰にでも見せられる物になります。

M5STACK 追加情報:スピーカーからプツプツ音が出る。
アナログ入力(例:analogRead(A0); ) を実施すると スピーカーから プツプツ
_ という音がでます。
_ 対策:setup にて dacWrite(25, 0); を記載します。
_ 結果:スピーカーの出力がゼロ(オフ)になります。
_ 理由:GPIO_25(DAC1) にアンプを通してスピーカーが接続されています。
_    GPIO_25 (DAC1), GPIO_26 (DAC2) は 2ch 8bit DAC(アナログ出力)
_    端子です。GPIO_25 は 通常3mVac で無音ですが analogRead(A0);
_    をすると 10mVac になりプツプツ音が聞こえる様になります。
_    使用する命令は dacWrite(DAC1 or DAC2, value(0-255)) です。
_    電源ラインや信号ラインのリップルで無い事は確認しています。


Written by macsbug

1月 2, 2018 at 4:58 pm

カテゴリー: ESP32

Audio Spectrum Display with M5STACK

with one comment

M5STACK で Audio Spectrum Display を作りました。            2017.12.31

音をディスプレー にスペクトラム表示します。
音を「Sound Detection Sensor 」( FC-04 ) ( 322円 ) で受けて、その信号を
M5STACK の GPIO36 に入力し スペクトラム を ディスプレー に 表示します。

スケッチ:
_ tobozo氏の「ESP32_Spectrum_Display_03.ino」を M5STACK に移植しました。
_ tobozo氏のソースは G6EJD氏の「ESP32_Spectrum_Display_02.ino」です。
_ 謝辞:G6EJD氏 と tobozo氏に感謝致します。

_ tobozo氏の使用した ディスプレーは 128×64 OLED SSD1306 です。
_ M5STACKのディスプレーは 320×240 TFT ILI9341 です。


.
準備:

No  Nomen  Link、Description   Price
1  M5STACK BASIC  Aliexpress:M5Stack Store ( $33.25 ) ( SALE )  3767円
2  Sound Detection Sensor  
2  ebay:shieldsfans ( AU $1.00)   88円
2  ebay:alice1101983 ( US $0.99)  112円
2  Ali:3C Top-rated Seller (US $0.79)  112円
4  ピンソケット(メス)
1×8 ( 8P )
 秋月電子通商    30円
5  切れる基板
0.3mm厚 両面スルーホール
ユニバーサル基板
 秋月電子通商    60円
 —————————–  ————————————————-   ——
6  G6EJD/
_ Audio-Spectrum-Display
  ESP32_Spectrum_Display_03.ino
7  kosme/arduinoFFT   arduinoFFT

.


.
配線:
GPIO36 は入力専用で SENSOR_VP と言う名前です。
参考:ESP32 Hardware Design Guidelines:Page 27 4.1.2 Pin Definition

_ VCC(FC-04) = 3V3(M5), GND = GND, OUT = AD(GPIO36)へ接続します。
_ FC-04 の電源は 3.3V で使用します。
_ FC-04 の出力は ESP32 のGPIO36 ( SENSOR_VP ) へ入ります。

秋月のハサミで切れる基板と ピンソケットで製作しました。

FC-04の調整:
_ FC-04 のボリュームの調整は 電源を入れ 緑のLEDが点滅する
_ 場所に設定します。スペクトラム表示を見ながらも調整してください。


.
マイクとアンプ:
マイクのアナログ信号は 最適な値である事が 重要です。
_ 以下5つを試し使用できる製品は 左の「FC-04」という製品でした。
_ 販売:右は 秋月電子通商の製品です。他は ebay や Aliexpress です。
_ FC-04 以外は ゲインレベルが正しく取れず 表示範囲が狭いです。
_ 原因は ゲインが取り難い事や飽和しやすく動作範囲が狭い為です。
FC-04:OUT はアナログでは無くデジタルです。何故 デジタルが 動作に
_ 適しているか 分析していません。

_ tobozo氏は 以下の様に述べています。
_「正しい結果が得る方法は、約50mV〜100mV pk-pkのオーディオを
_ DCオフセットなしでADCポートに直接供給することです。」

マイクアンプについての 記事は macsbug:「 FC-04 回路メモ 」にあります。


.
スケッチ変更内容:
_ 1. 「tft.drawFastHLine」を「M5.Lcd.drawFastHLine」に変更します。
_ 2. 「tft.fillRect」 を「M5.Lcd.fillRect」に変更します。
_ 資料:M5STACK の命令は 「M5Stack.h」に定義されています。


.
M5STACK analogRead 時の Speaker nois 問題と解決方法。
問題:analogRead() 時に Speaker (GPIO25) からノイズがでます。
_  例:vReal[i] = analogRead(A0);
方法:dacWrite(25, 0); // Speaker OFF //
解説:
_ GPIO25 (Channel 1) と GPIO26 (Channel 2) は two 8-bit DAC
_ (digital to analog converter) channels です。( 16bit )。
_ GPIO25 の 出力 (サウンド) は NS4148 AUDIO AMPLIFER に入り
_ AUDIO OUTPUT は SPEAKER に接続さています。
_ 資料:M5STACKの回路図
_ 資料:ESP32 Datasheet:Page 8, 2.2 Pin Description:GPIO25=DAC_1
_ GPIO25 は DAC端子ですので dacWrite(25, 0); で出力を制御します。
_ 信号解析:GPIO25 は 無音で 3mVac、analogRead(A0),で 10mVac 発生し
_ スピーカーからプツプツ音がでます。電源は問題なく GPIO25 からの出力
_ が原因の様です。


.
参考:
Github:G6EJD/ESP32-8266-Audio-Spectrum-Display
Github:tobozo/ESP32-8-Octave-Audio-Spectrum-Display
YouTube:G6EJD:Tech Note 077 – ESP32 8-Octave Audio Spectrum Display
YouTube:tobozo tagada:ESP32 Audio Spectrum from saturation to silence
YouTube:tobozo tagada:ESP32 Audio Spectrum on WROVER Kit V3
YouTube:Tech Note 069 – Using the ESP32 ADC and some of its more advanced functions
_ ADCの使用方法、線形性の説明、減衰設定の説明、ADCチャンネルで
_ 使用されるピンの変更方法を示し、多項式を使用して精度を1%未満に
_ 改善する関数を示します。
G6EJD / ESP32-ADC-Accuracy-Improvement-function

macsbug:SplitRadixRealP FFT Analyzer of Arduino DUE
_ Arduino Due + SplitRadixRealP の例。


.
ESP32 MH-ET LIVE D1 mini + OLED 128×64 で Audio Spectrum Display を製作しました。
_ QI で ワイヤレス電源にしてみました。
_ macsbug:Arduinoの電源をワイヤレス化
価格:ESP32 MH-ET LIVE D1 mini: $7.2 ( 811円 )
価格:OLED 128×64:$2.91 ( 328円 )


.
感想:
1.  ESP32 による「ESP32 Spectrum Display」
_ 基本は G6EJD氏により「ESP32_Spectrum_Display_01.ino」と
_  ESP32_Spectrum_Display_02.ino」が作成されました。そして
_ tobozo氏により「ESP32_Spectrum_Display_03.ino」が作成されました。
_ 基本と高速化に G6EJD氏 と tobozo氏 に感謝致します。

2. マイクとアンプ:
_ 音のサンプリングはゲインと帯域がないと 充分に機能しません。
_ 動作範囲の広いボードを使用するべきですが 最善のボードが見つかりません。
_ 課題: AGC機能のあるアンプを使用する必要があると思います。
_ 現在 他のボードを注文 及び 調査中です。

3. M5STACK の ケース。
_ M5STACK はケースが綺麗で素晴らしいです。
_ PROT Module は ありますが REAR CASE がありません。
_ M5STACK の強みは綺麗さです。綺麗に仕上げる為に 本体周辺の部品や
_ ケースを販売して欲しいです。止めネジや やや斜めに於ける台も欲しいです。
_ Jimmy Lai さん に期待しています。

4. FC-04の電源:
_ VCC = 5V での検証はしていません。

5. メモ:高速抽画。
_ 全画面消去の「M5.Lcd.fillScreen」を使用するとチラつきが発生します。
_ 対策は「M5.Lcd.fillScreen」を使用せず 同じ抽画を2度操作します。
_ 横線の高速抽画:
_  1. 前回の線を「drawFastHLine」+ 黒で抽画する。
_  2. 今回の値を前回に保存。
_  3. 今回の線を「drawFastHLine」で 抽画する。
_ 横線複数の高速抽画:
_  1. 前回の線全てを「fillRect」+ 黒で塗りつぶす。
_  2. 今回の値を前回に保存。
_  3. 今回の線を「drawFastHLine」で 抽画する。


.
スケッチ:

/* ESP8266/32 Audio Spectrum Analyser on an SSD1306/SH1106 Display
 * The MIT License (MIT) Copyright (c) 2017 by David Bird. 
 * The formulation and display of an AUdio Spectrum using an ESp8266 or ESP32 and SSD1306 or SH1106 OLED Display using a Fast Fourier Transform
 * Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files 
 * (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, 
 * publish, distribute, but not to use it commercially for profit making or to sub-license and/or to sell copies of the Software or to 
 * permit persons to whom the Software is furnished to do so, subject to the following conditions:  
 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
 * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 
 * See more at http://dsbird.org.uk 
*/
// https://github.com/tobozo/ESP32-8-Octave-Audio-Spectrum-Display/tree/wrover-kit
// https://github.com/G6EJD/ESP32-8266-Audio-Spectrum-Display
// https://github.com/kosme/arduinoFFT


#include "arduinoFFT.h"          // Standard Arduino FFT library 
arduinoFFT FFT = arduinoFFT();
#include <M5Stack.h>
#define SAMPLES 512              // Must be a power of 2
#define SAMPLING_FREQUENCY 40000 
// Hz, must be 40000 or less due to ADC conversion time.
// Determines maximum frequency that can be analysed by the FFT Fmax=sampleF/2.

struct eqBand {
  const char *freqname;
  uint16_t amplitude;
  int peak;
  int lastpeak;
  uint16_t lastval;
  unsigned long lastmeasured;
};

eqBand audiospectrum[8] = {
  //Adjust the amplitude values to fit your microphone
  { "125Hz", 500, 0, 0, 0, 0},
  { "250Hz", 200, 0, 0, 0, 0},
  { "500Hz", 200, 0, 0, 0, 0},
  { "1KHz",  200, 0, 0, 0, 0},
  { "2KHz",  200, 0, 0, 0, 0},
  { "4KHz",  100, 0, 0, 0, 0},
  { "8KHz",  100, 0, 0, 0, 0},
  { "16KHz", 50,  0, 0, 0, 0}
};

unsigned int sampling_period_us;
unsigned long microseconds;
double vReal[SAMPLES];
double vImag[SAMPLES];
unsigned long newTime, oldTime;
uint16_t tft_width  = 320; // ILI9341_TFTWIDTH;
uint16_t tft_height = 240; // ILI9341_TFTHEIGHT;
uint8_t bands = 8;
uint8_t bands_width = floor( tft_width / bands );
uint8_t bands_pad = bands_width - 10;
uint16_t colormap[255]; // color palette for the band meter (pre-fill in setup)

void setup() {
  Serial.begin(115200);
  M5.begin();
  dacWrite(25, 0); // Speaker OFF
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setTextColor(YELLOW, BLACK);
  M5.Lcd.setTextSize(1);
  M5.Lcd.setRotation(0);
  sampling_period_us = round(1000000 * (1.0 / SAMPLING_FREQUENCY));
  delay(2000);
  for(uint8_t i=0;i<tft_height;i++) {
    colormap[i] = M5.Lcd.color565(tft_height-i*.5, i*1.1, 0);
  }
  for (byte band = 0; band <= 7; band++) {
    M5.Lcd.setCursor(bands_width*band + 2, 0);
    M5.Lcd.print(audiospectrum[band].freqname);
  }
}

void loop() {
  for (int i = 0; i < SAMPLES; i++) {
    newTime = micros()-oldTime;
    oldTime = newTime;
    vReal[i] = analogRead(A0); // A conversion takes about 1uS on an ESP32
    vImag[i] = 0;
    while (micros() < (newTime + sampling_period_us)) { 
      // do nothing to wait
    }
  }
  FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD);
  FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD);
  FFT.ComplexToMagnitude(vReal, vImag, SAMPLES);

  for (int i = 2; i < (SAMPLES/2); i++){ 
    // Don't use sample 0 and only first SAMPLES/2 are usable. 
    // Each array eleement represents a frequency and its value the amplitude.
    if (vReal[i] > 1500) { // Add a crude noise filter, 10 x amplitude or more
      byte bandNum = getBand(i);
      if(bandNum!=8) {
        displayBand(bandNum, (int)vReal[i]/audiospectrum[bandNum].amplitude);
      }
    }
  }
  
  long vnow = millis();
  for (byte band = 0; band <= 7; band++) {
    // auto decay every 50ms on low activity bands
    if(vnow - audiospectrum[band].lastmeasured > 50) {
      displayBand(band, audiospectrum[band].lastval>4 ? audiospectrum[band].lastval-4 : 0);
    }
    if (audiospectrum[band].peak > 0) {
      audiospectrum[band].peak -= 2;
      if(audiospectrum[band].peak<=0) {
        audiospectrum[band].peak = 0;
      }
    }
    // only draw if peak changed
    if(audiospectrum[band].lastpeak != audiospectrum[band].peak) {
      // delete last peak
     M5.Lcd.drawFastHLine(bands_width*band,tft_height-audiospectrum[band].lastpeak,bands_pad,BLACK);
     audiospectrum[band].lastpeak = audiospectrum[band].peak;
     M5.Lcd.drawFastHLine(bands_width*band, tft_height-audiospectrum[band].peak,
                           bands_pad, colormap[tft_height-audiospectrum[band].peak]);
    }
  } 
}

void displayBand(int band, int dsize){
  uint16_t hpos = bands_width*band;
  int dmax = 200;
  if(dsize>tft_height-10) {
    dsize = tft_height-10; // leave some hspace for text
  }
  if(dsize < audiospectrum[band].lastval) {
    // lower value, delete some lines
    M5.Lcd.fillRect(hpos, tft_height-audiospectrum[band].lastval,
                    bands_pad, audiospectrum[band].lastval - dsize, BLACK);
  }
  if (dsize > dmax) dsize = dmax;
  for (int s = 0; s <= dsize; s=s+4){
    M5.Lcd.drawFastHLine(hpos, tft_height-s, bands_pad, colormap[tft_height-s]);
  }
  if (dsize > audiospectrum[band].peak) {
    audiospectrum[band].peak = dsize;
  }
  audiospectrum[band].lastval = dsize;
  audiospectrum[band].lastmeasured = millis();
}

byte getBand(int i) {
  if (i<=2 )             return 0;  // 125Hz
  if (i >3   && i<=5 )   return 1;  // 250Hz
  if (i >5   && i<=7 )   return 2;  // 500Hz
  if (i >7   && i<=15 )  return 3;  // 1000Hz
  if (i >15  && i<=30 )  return 4;  // 2000Hz
  if (i >30  && i<=53 )  return 5;  // 4000Hz
  if (i >53  && i<=200 ) return 6;  // 8000Hz
  if (i >200           ) return 7;  // 16000Hz
  return 8;
}

.
CARD STAND:
ダイソーにある 2個100円の L型カードスタンド。64 x 45 x 20 mm。
M5STACK にピッタリ です!


Written by macsbug

12月 31, 2017 at 9:00 am

カテゴリー: ESP32, ESP8266