macsbug

Archive for 5月 2019

M5Stack CoinMarketCap

leave a comment »

M5Stack で 仮想通貨の時価総額 ( CoinMarketCap ) を表示しました。 ORG:2019.05.26, rev:2019.05.30

.
CoinMarketCap ( 仮想通貨の時価総額 ) は リアルタイムに 数値とグラフを見る事ができます。
bitcoin, cardano, ethereum, litecoin, xrp(ripple) の5つを M5Stack に表示します。
オリジナルは mnett0氏の ESP32+ILI9341「CryptoMoneyTicker」です。mnett0氏に感謝!
この「CryptoMoneyTicker」を TTGO が TTGO-T4-DEMO 「T4_9341_NEW」に移植。
そして「TTGO-T4-DEMO」を基に M5Stack用に移植致しました。


.
手順:
1. 開発環境:Arduino IDE。
2. ArduinoJson を Arduino IDE の Library へ Install します。
_ version は 5 を使用します。ArduinoJson-5.13.5。注:version 6 では 動作しません。
3. 最下位にある スケッチ「M5Stack_CoinMarketCap」をコピーし
_ 「M5Stack_CoinMarketCap.ino」Folder を 作ります。
4. 以下の8つの表示画像を 用意し、SD File か HEX File か どちらかで行ないます。
_ 
_ 画像表示の為に 画像を microSD に入れて使用するか、HEX File に変換し
_ 「M5Stack_CoinMarketCap.ino」 Folder に入れます。
_ Free_Fonts.h は M5Stack/examples/Advanced/Display/Free_Font_Demo/
_ にありますので DL してください。
 
_ ⭕ SD File の場合:
_  
_  microSDに 「coin_image」 Folder を作り 以下の表示画像( jpg ) を入れます。
.
表示画像:

bitcoin_.jpg cardano_.jpg ethereum_.jpg litecoin_.jpg xrp_.jpg

.

wifi_1.jpg wifi_2.jpg wifi_3.jpg

.
_ ⭕ HEX Fileの場合:
_  
_  1. 8つの表示画像( jpg ) を 変換し DL(ダウンロード)します。事例:bitcoin_.jpg
_  2. ブラウザーで「PicturetoC_Hex_converter」に接続し jpg を hex に変換します。
_  3. your の選択ボタンで DL した File を選択します。
_  4. width は 「64」, Height は 「64」 を入力。
_  5. User for は 「256 color byte/pixel:RRRGGGBB, for EDM1」を選択します。
_  6. 「Get C String」ボタンを押すと Image data: が作成されます。
_  7. Image data (0xff 等) をコピーし 「bitcoin_.c」File を作ります。
_   ( 左下の「Download data」を選択し DL する方法は DL した bin を変換しますが
_    この方法は 変換方法が解らず 説明は省きます。)
_  8. 8つの Hex File を 「M5Stack_CoinMarketCap.ino」 Folder に入れます。
_

4. Arduino IDE で M5Stackに書き込みます。


.
追記:2019.05.30
⭕ HEX File 変換:その2:「LCD image Converter 」アプリで変換する。
_ 「LCD image Converter 」は Windowsアプリです。
_ 「lcd-image-converter 20180211-beta」を使用します。
_  メモ:投稿時 最新の20190317 は エラーで動作しません。
_ Mac では「Wine.app」を使用しWindows アプリケーションを動かすことができます。
_ Wine.app Downloads:例 Wine 2.0 を 選択し Download を押します。
_ 約5sec後に「Continue」が表示されますので「Continue」を選択します。
_ 約4sec後に 右上(のアイコン) を選択します。Wine_2.0.dmg 175.6MB が DLされます。
_ 解凍すると Wine.app ができます。
_

HEX File 変換方法:
1. Wine.app を起動し LCD image Converter を動作させます。
_ 事前に Wine.app や LCD image Converter の動かし方や 配置を把握しておきます。
_ 初めての場合は Winの配置等に慣れない為に時間を要するかと思います。
2. LCD image Converter:
_ File Menu から png 又は Jpg を読み込みます。読み込まれると画像が表示されます。
_ Option Menu を選択します。一番上の Preset: は「Color R5G6B5」を選択します。
_ Image を選択し Block size: は「8 bit」を選択します。「OK」を押します。
_ File Menu にある 「Convert… 」 で 例「bitcoin_.c」が出力されます。
_  事前に読み込み先 ong や 出力先を把握しておきます。
3. .c の編集:そのままではエラーが出ますので修正します。
_ 最下位にある 「const tImage ethereum_ = ,,, }; 」は コメントアウト「//)」します。
_ 上にある 例「static const uint8_t image_data_bitcoin_[8192] = {」は
_  名前の「image_data_」を削除すると短く見やすくなります。
_  「static const uint8_t bitcoin_[8192] = {」に変更します。
_ これにより 例「bitcoin_.c」を作成する事ができます。
_ 


.
移植メモ:
1. オリジナルの 「CryptoMoneyTicker」:
_ ログを CSV File で SDに保存できる。
_ ボタン操作で 表示を選択できる。
_ 表示の為の画像(bitmap.c) は そのままでは M5Stack に使用できない。

2. TTGO の TTGO-T4-DEMO 「T4_9341_NEW」:
_ ログを CSV File で SDに保存できない。
_ ボタン操作は 無し。
_ 表示の為の画像(bitmap.c) は そのままでは M5Stack に使用できない。

3. 移植の課題と対策:
_ 画像の問題:
_ 「CryptoMoneyTicker」や「T4_9341_NEW」にある画像 bitmap.c は
_  M5Stack では使用できません。さらに これまでの Arduino等にある HEX画像も
_  そのままでは使用できません。
_  理由は Display の表示に関し RGB の配置変更(BGR)や鏡面反転している為です。
_  素直に RGB 配置にすれば これまでの資産がスムーズに使用できますが
_  配置変更をした為 都度 画像の変換や 新規作成が必要となります。
_  これは 手間と時間を要し M5Stackの発展を妨げます。
_  参照:TFT_eSPI Library supports M5STACK
_  PC環境やOSにより 画像フォーマットが変化し 動作しない場合が考えられます。
_  OS環境により 不可視ファイルが出来る場合があります。

_ 画像の対策:
_ 1. M5Stack で使用可能な Jpg File を microSDに保存して使用する。
_  スケッチ例:M5.Lcd.drawJpgFile(SD,”/coin_image/bitcoin_.jpg”,0,0,64,64);
_ 2. ブラウザーで使用可能な png to hex 又は jpg to hex を使用し hex を作成する。
_  png to hex が可能で 「256 color byte/pixel:RRRGGGBB, for EDM1」の選択が
_  必要となります。この変換データーは大きくなりますが使用できます。
_  他に可能な方法があるかと思いますが 現在知り得る方法です。
_  スケッチ例:M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)bitcoin_);
_  それにしても 簡単な変換方法を探していますが なかなか思うように行きません。

_ 今回の機能:
_ 1. 画像は SD と hex 両方が使用出来る記述にしました。
_  必要に応じて 不要な場所は 削除や変更をしてください。
_ 2. ログの SD保存 は 特に必要なく追加しませんでした。
_  グラフに興味がある場合は SD へ CSV 保存すると便利になるかと思います。
_ 3. ボタンスイッチによる 表示の切り替えは無しにしました。
_  繰り返し表示の時間設定は 18行目の interval = 60000; です。
_  必要に応じて 数値を変更してください。
_  マニュアルで画面切り替えが 便利な場合は スイッチ操作を加えると
_  便利になるかと思います。


.
参照:
M5StickC docs:概要(電源操作、サポート ボーレート、ピンマップ)
github:M5StickC Library:M5StickC ライブラリー
Market Capitalization:データーのソース基。
CryptoMoneyTicker:オリジナル版 mnett0氏のページ。
TTGO-T40DEMO:TTGOのデモ
png to hex:Hex変換。
png to jpg:jpg変換。


.
感想:
仮想通貨は使用していませんので必要ありませんが、TTGO のサンプルの
表示が綺麗でなく M5Stack では どうなるか試してみました。

画像処理:M5Stack用の画像準備には時間がかかります。
_ もう少し簡単に出来る方法は無いかと模索しましたが 私の力量では
_ この位かと思います。
_ png は 透明処理が可能ですが M5Stack上ではマスター出来ませんでした。
_ よって 左上のアイコン画像は 背景部を透明にして試しましたが 表示
_ できない為に 背景を白で仕上げました。

当初 TTGO製のスケッチかと思いましたが mnett0氏の作品である事が解りました。
_ CryptoMoneyTicker_LITE ( ESP8266 + OLED ) もあり 小型でケースも出来ています。

勉強:
M5Stack の Github には Graphic のサンプルがあり 基本的な使用方法が あります。
今回の移植後に気づき、サンプルを先にマスターしておくべきだったかと 思っています。
 1. drawXBitmap/xbm.h:モノクロ画像を扱う。
 2. TFT_Flash_Bitmap:カラー画像を扱う。


.
スケッチ:M5Stack_CoinMarketCap : 2019.05.26 Transplant by macsbug

/*
The MIT License (MIT)

Copyright © 2018 Médéric NETTO

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, sublicense,
and/or sell copies of the Software, and 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.
*/
// M5Stack_CoinMarketCap : 2019.05.26 Transplant by macsbug
// https://macsbug.wordpress.com/2019/05/26/m5stack-coinmarketcap/
// https://coinmarketcap.com // Market Capitalization
// https://github.com/mnett0/CryptoMoneyTicker // CryptoMoneyTicker
// https://github.com/LilyGO/TTGO-T4-DEMO // TTGO-T40DEMO
// https://www.digole.com/tools/PicturetoC_Hex_converter.php // png to hex
// https://onlinejpgtools.com/convert-png-to-jpg // png to jpg
#include <M5Stack.h>
#include "M5StackUpdater.h"
#include "Free_Fonts.h"
#include <driver/dac.h>
#include <ArduinoJson.h> // with ArduinoJson 5.xx
#include <WiFi.h>
#include <WiFiClientSecure.h>
#include <TimeLib.h>
#define SD_ // with SD File
#if defined(SD_)
#else
  #include "bitcoin_.c"
  #include "cardano_.c"
  #include "ethereum_.c"
  #include "litecoin_.c"
  #include "xrp_.c" // ripple
  #include "wifi_1.c"
  #include "wifi_2.c"
  #include "wifi_3.c"
#endif
const char* ssid = "your wifi ssid";
const char* password = "your wifi password";
const char host[] = "api.coinmarketcap.com";
int COLOR;
#define CUSTOM_DARK 0x4228 // Background color
unsigned long previousMillis = 0;
long interval = 0;
int coin = 0;
String crypto[] = {"bitcoin", "cardano", "ethereum", "litecoin", "ripple"};
String oldPrice[5];

void setup() {
  M5.begin();
  Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}
  dac_output_disable(DAC_CHANNEL_1); // DAC OFF
  M5.Lcd.setRotation(0);
  M5.Lcd.fillScreen(CUSTOM_DARK); 
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setTextWrap(true);
  M5.Lcd.setCursor(0, 170);
  M5.Lcd.setFreeFont(FM9); 
  M5.Lcd.println(">>> Connecting to: ");
  M5.Lcd.println(" ");
  M5.Lcd.println(ssid);
  Serial.println("start");
  WiFi.begin(ssid, password);
  while (WiFi.status() != WL_CONNECTED) {
    #if defined(SD_)
      M5.Lcd.drawJpgFile(SD,"/coin_image/wifi_3.jpg",56,64,128,128);delay(250);
      M5.Lcd.drawJpgFile(SD,"/coin_image/wifi_2.jpg",56,64,128,128);delay(250);
      M5.Lcd.drawJpgFile(SD,"/coin_image/wifi_1.jpg",56,64,128,128);delay(250);
    #else
      M5.Lcd.pushImage(56, 64, 128, 128, (uint8_t *)wifi_3);delay(250);
      M5.Lcd.pushImage(56, 64, 128, 128, (uint8_t *)wifi_2);delay(250);
      M5.Lcd.pushImage(56, 64, 128, 128, (uint8_t *)wifi_1);delay(250);
      M5.Lcd.fillRect (56, 64, 128, 128, CUSTOM_DARK);
    #endif
  }
  //Serial.println("start");
  M5.Lcd.println(" ");
  M5.Lcd.print(">>> WiFi connected");
  //M5.Lcd.print("IP address: ");
  //M5.Lcd.println(WiFi.localIP());

  delay(1500);
  M5.Lcd.fillScreen(CUSTOM_DARK);
  M5.Lcd.setTextColor(YELLOW);
  M5.Lcd.setCursor(0, 150);
  M5.Lcd.setFreeFont(FM9);
  M5.Lcd.println("CryptoMoneyTicker_v1");
  M5.Lcd.println("porting : 2019.05.26");
  M5.Lcd.drawLine(0,130,240,130, WHITE);
  M5.Lcd.drawLine(0,185,240,185, WHITE);

  M5.Lcd.setFreeFont(FM9);
  M5.Lcd.setCursor(5, 307);
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.println("Data from: CoinMarketCap.com");
}

void loop() {
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setCursor(1, 10);
  unsigned long currentMillis = millis();
  if (currentMillis - previousMillis >= interval) {
    previousMillis = currentMillis;
    interval = 60000; //  <<<--------------------  
    if(coin == 5 ){ coin = 0; }
  //Serial.print(">>> Connecting to ");
  //Serial.println(host);
  WiFiClientSecure client;
  const int httpsPort = 443;
  if (!client.connect(host, httpsPort)) {
    M5.Lcd.fillScreen(CUSTOM_DARK);
    M5.Lcd.println(">>> Connection failed");
    return;
  }
  //Serial.print("Requesting URL: ");
  //Serial.println("Connected to server!");
  client.println("GET /v1/ticker/" + crypto[coin] + "/ HTTP/1.1");
  client.println("Host: api.coinmarketcap.com");
  client.println("Connection: close");
  client.println();

  unsigned long timeout = millis();
  while (client.available() == 0) {
    if (millis() - timeout > 5000) {
      M5.Lcd.fillScreen(CUSTOM_DARK);
      M5.Lcd.println(">>> Client Timeout!");
      client.stop();
      return;
    }
  }

  String data;
  while(client.available()) {
    data = client.readStringUntil('\r');
    //Serial.println(data);
  }
  data.replace('[', ' ');
  data.replace(']', ' ');
  char buffer[data.length() + 1];
  data.toCharArray(buffer, sizeof(buffer));
  buffer[data.length() + 1] = '\0';
  const size_t bufferSize = JSON_OBJECT_SIZE(15) + 110;
  DynamicJsonBuffer jsonBuffer(bufferSize);
  JsonObject& root = jsonBuffer.parseObject(buffer);
  if (!root.success()) {
    M5.Lcd.println("parseObject() failed");
    return;
  }

  String name = root["name"];                          // "Bitcoin"
  String symbol = root["symbol"];                      // "BTC"
  String price_usd = root["price_usd"];                // "573.137"
  String percent_change_1h = root["percent_change_1h"];// "0.04"
  String last_updated = root["last_updated"];          // "1472762067"<--Unix Time Stamp
  String error = root["error"];                        // id not found

  printTransition();

  switch (coin) {
  case 0 : // Bitcoin
  #if defined(SD_)
    M5.Lcd.drawJpgFile(SD,"/coin_image/bitcoin_.jpg",0,0,64,64);
  #else
    M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)bitcoin_);
  #endif
  printName(name, symbol);
  printPrice(price_usd);
  printChange(percent_change_1h);
  printTime(last_updated);
  printPagination();
  printError(error);
  M5.Lcd.fillCircle(82, 300, 8, WHITE);
  break;

  case 1 : // cardano
  #if defined(SD_)
    M5.Lcd.drawJpgFile(SD,"/coin_image/cardano_.jpg",0,0,64,64);
  #else
    M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)cardano_);
  #endif
  printName(name, symbol);
  printPrice(price_usd);
  printChange(percent_change_1h);
  printTime(last_updated);
  printPagination();
  printError(error);
  M5.Lcd.fillCircle(100, 300, 8, WHITE);
  break;

  case 2 : // ethereum
  #if defined(SD_)
    M5.Lcd.drawJpgFile(SD,"/coin_image/ethereum_.jpg",0,0,64,64);
  #else
    M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)ethereum_);
  #endif
  printName(name, symbol);
  printPrice(price_usd);
  printChange(percent_change_1h);
  printTime(last_updated);
  printPagination();
  printError(error);
  M5.Lcd.fillCircle(118, 300, 8, WHITE);
  break;

  case 3 : // litecoin
  #if defined(SD_)
    M5.Lcd.drawJpgFile(SD,"/coin_image/litecoin_.jpg",0,0,64,64);
  #else
    M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)litecoin_);
  #endif
  printName(name, symbol);
  printPrice(price_usd);
  printChange(percent_change_1h);
  printTime(last_updated);
  printPagination();
  printError(error);
  M5.Lcd.fillCircle(136, 300, 8, WHITE);
  break;

  case 4 : // ripple ( xrp )
  #if defined(SD_)
    M5.Lcd.drawJpgFile(SD,"/coin_image/xrp_.jpg",0,0,64,64);
  #else
    M5.Lcd.pushImage(0, 0, 64, 64, (uint8_t *)ripple_);
  #endif
  printName(name, symbol);
  printPrice(price_usd);
  printChange(percent_change_1h);
  printTime(last_updated);
  printPagination();
  printError(error);
  M5.Lcd.fillCircle(154, 300, 8, WHITE);
  break;
  }
  oldPrice[coin] = price_usd;
  coin++;
  }
}

void printName(String name, String symbol) {
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.setCursor(75, 15);
  M5.Lcd.println(name);
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.setCursor(75, 42);
  M5.Lcd.print(symbol);
  M5.Lcd.drawLine(75, 54, 240, 54, WHITE);
}

void printPrice(String price_usd) {
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.setCursor(5, 85);
  M5.Lcd.print("Price:");
  if(price_usd != oldPrice[coin]){
    if(price_usd > oldPrice[coin]){
    COLOR = GREENYELLOW;
    }else{
    COLOR = RED;
    }
  }
  M5.Lcd.setFreeFont(FMB18);
  M5.Lcd.setTextColor(COLOR);
  M5.Lcd.setCursor(20, 126);
  M5.Lcd.print("$");
  M5.Lcd.println(price_usd.substring(0,8));
}

void printChange(String percent_change_1h) {
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.setCursor(5, 160);
  M5.Lcd.print("Change(1h):");
  if(percent_change_1h >= "0"){
    M5.Lcd.setTextColor(GREENYELLOW);
    }else{
    M5.Lcd.setTextColor(RED);
  }
  M5.Lcd.setFreeFont(FMB18);
  M5.Lcd.setCursor(20, 199);
  M5.Lcd.print(percent_change_1h);
  M5.Lcd.print("%");
}

void printTime(String last_updated) {
  long int timeData = last_updated.toInt();
  time_t t = timeData;
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setFreeFont(FM12);
  M5.Lcd.setCursor(5, 230);
  M5.Lcd.print("Last Updated:");
  M5.Lcd.setFreeFont(FMB18);
  M5.Lcd.setCursor(20, 266);
  /*
  M5.Lcd.setFreeFont(FM9);
  printDigits(day(t));  M5.Lcd.print("/");
  printDigits(month(t));M5.Lcd.print("/");
  M5.Lcd.print(year(t));M5.Lcd.print(" ");
  */
  printDigits(hour(t) + 1); // +1 for the French time
  M5.Lcd.print(":");
  printDigits(minute(t));
  //M5.Lcd.print(":");
  //printDigits(second(t));
}

void printPagination() {
  M5.Lcd.drawCircle( 82, 300, 8, WHITE);
  M5.Lcd.drawCircle(100, 300, 8, WHITE);
  M5.Lcd.drawCircle(118, 300, 8, WHITE);
  M5.Lcd.drawCircle(136, 300, 8, WHITE);
  M5.Lcd.drawCircle(154, 300, 8, WHITE);
}

void printError(String error) {
  M5.Lcd.setTextColor(WHITE);
  M5.Lcd.setFreeFont(FM9);
  M5.Lcd.setCursor(65, 22);
  M5.Lcd.println(error);
}

void printTransition(){
  M5.Lcd.fillScreen(CUSTOM_DARK);
  M5.Lcd.fillScreen(RED);
  M5.Lcd.fillScreen(GREEN);
  M5.Lcd.fillScreen(BLUE);
  M5.Lcd.fillScreen(CUSTOM_DARK);
}

void printDigits(int digits) {
 if (digits < 10)  // prints preceding colon and leading 0
 M5.Lcd.print('0');
 M5.Lcd.print(digits);
}

ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
M5Stack , CoinMarketCap , RGB , GBR png to hex , png to jpg , macsbug , TTGO ,
HEX , HEX File 変換 , LCD image Converter , Wine , Wine_2.0 ,

Written by macsbug

5月 26, 2019 at 12:00 pm

カテゴリー: M5STACK

M5StickC 3D COLOR CUBE

leave a comment »

M5StickC に 3D COLOR CUBE を表示しました。      2019.05.20

M5StickC の表示は 0.96″ 80×160 IPS (In Plane Switching) SPI ST7735S Display です。
色は 65K フルカラー ( 16bit color )です。
上下左右178度の広視野角、どの位置で見ても色の変化は ほとんどありません。

斜めの視野:明るく綺麗で見やすいです。


.
作成:
記事の最下位にある スケッチを Arduino IDE で ボード選択 M5StcicK-C で書き込みます。
_ スケッチ詳細:
_ 1. CUBE のサイズ:9行目の #define U 70 // size of cube で 数値を変更します。
_ 2. 回転速度:251 行目の delay(50); // Rotational speed で 50msecです。
_   回転速度の変更:delay の数値を変更してください。
_ 3. SOLID表示と WIREFRAME表示:選択は 5行目の #define WIREFRAME を
_   コメント(//) を入れるか 入れないかで 表示が変わります。
_ 4. M5Stack 用に 使用する時は // In the case of M5Stack と書かれているコメントを
_   削除してください。#include M5StickC.h は コメントアウトします。
_   237行目は SD_UPDATE 用です。修正は 合計 4箇所です。


.
参考:
M5StickC docs:概要(電源操作、サポート ボーレート、ピンマップ)
github:M5StickC Library:M5StickC ライブラリー
blog:3D CUBE with ESP8266 and OLED:2016.01.16:モノクロの立方体がグイグリ回ります。
blog:I2C OLED 128×64 SSD1306 in ESP8266:2016.03.20:ESP8266 と SSD1306。
blog:Using the WeMos I2C OLED 64×48:2016.04.01:WeMos I2C OLED 64×48 の使用例。
blog:Part 2 of the FM radio in the ESP8266:2016.04.06:cubeをradio displayに使用。
blog:Using the TFT LCD display in the ESP8266:2016.04.16:ESP8266 + TFT SPI ILI9341
blog:ESP32 Adapter Board and TFT Display:2017.02.11:ESP32 + 1.44″ TFT v2.1
LilyGO/TTGO-TS: TTGO-TS/ESP32_1.44/ESP32_1.44.ino:2018.08.11 確認:TTGOに使用される。
blog:Run IPS SPI display on M5Stack:2019.03.24:65K フルカラーです。
Adafruit:Adafruit Mini TFT – 0.96″ 160×80


.
M5Stack Basic:M5Stack Basic用に記載し表示しました。


.
感想:
IPSカラーディスプレーに CUBE を表示したく 作成しました。
M5StickCの動作確認やデモに 良いかと思います。
0.96 inch 128 x64 OLED のデモと同じく CUBE を表示しました。
立方体を2次元表示する為に 3つの四辺形で構成されます。
四辺形は 三角形を3つ抽画しています。

メモ:
_ チラツキ:スケッチに工夫が必要かと思います。
_  CUBE 抽画に M5.Lcd.drawTriangle を 9回(3×3)使用し時間がかかっています。
_  252行目の M5.Lcd.fillRect(x_min,y_min,x_max-x_min+3,y_max-y_min+3,BLACK);
_  は CUBE 全体を消し 時間がかかっています。
_ M5StickC:動作しなくなる時がある。
_  1. 突然動作せず、画面が黒いままになる。
_  2. USBは認識し書込みも出来るが、動作しない。set_up が動作していない。
_   起動ボタンを押したり USB CABLE を抜き差ししたり すると動作する事がある。
_   原因不明ですので 壊れたとか あわてない事です。
_  3. 電源管理チップ AXP192 の設定かと思われます。
_   jimmy Lai氏の情報:
_    There is problem from the setting for axp192, need to set a higher volt level for shutdown.
_    The early version test firmware’s bug.


.
スケッチ:M5StickC 3D COLOR CUBE : 2019.05.20 macsbug

// M5StickC 3D COLOR CUBE : 2019.05.20 macsbug
#include <M5StickC.h>          // In the case of M5StickC
// #include <M5Stack.h>        // In the case of M5Stack
// #include "M5StackUpdater.h" // In the case of M5Stack
//#define WIREFRAME   // WIREFRAME or SOLID Display
#pragma GCC optimize ("O3")
struct p3d{ int16_t x, y, z;};
struct surface{ uint8_t p[4]; int16_t z;};
struct p2d{ int16_t x, y; unsigned is_visible;};
#define U  70         // size of cube
#define ZS U          // eye to screen distance (fixed)

struct p3d cube[8] ={ // cube edge length is 2*U
  { -U, -U,  U },
  {  U, -U,  U },
  {  U, -U, -U },
  { -U, -U, -U },
  { -U,  U,  U },
  {  U,  U,  U },
  {  U,  U, -U },
  { -U,  U, -U },
};

struct surface s[6] = {// define the surfaces
  { {0, 1, 2, 3}, 0 }, // bottom
  { {4, 5, 6, 7}, 0 }, // top
  { {0, 1, 5, 4}, 0 }, // back
  { {3, 7, 6, 2}, 0 }, // front
  { {1, 2, 6, 5}, 0 }, // right
  { {0, 3, 7, 4}, 0 }, // left
};

struct p3d cube2[8];   // Define the structure of the cube,
struct p2d cube_[8];   // calculation will be done there

int16_t x_min, x_max;  // will contain a rectangle border
int16_t y_min, y_max;  //  of the box projection into 2D plane.

const int16_t sin_tbl[65] = {
       0,  1606,  3196,  4756,  6270,  7723,  9102, 10394, 
   11585, 12665, 13623, 14449, 15137, 15679, 16069, 16305,
   16384, 16305, 16069, 15679, 15137, 14449, 13623, 12665,
   11585, 10394,  9102,  7723,  6270,  4756,  3196,  1606, 
       0, -1605, -3195, -4755, -6269, -7722, -9101,-10393,
  -11584,-12664,-13622,-14448,-15136,-15678,-16068,-16304,
  -16383,-16304,-16068,-15678,-15136,-14448,-13622,-12664,
  -11584,-10393, -9101, -7722, -6269, -4755, -3195, -1605, 
       0
};

const int16_t cos_tbl[65] = {
   16384, 16305, 16069, 15679, 15137, 14449, 13623, 12665, 
   11585, 10394,  9102,  7723,  6270,  4756,  3196,  1606,
       0, -1605, -3195, -4755, -6269, -7722, -9101,-10393,
  -11584,-12664,-13622,-14448,-15136,-15678,-16068,-16304,
  -16383,-16304,-16068,-15678,-15136,-14448,-13622,-12664,
  -11584,-10393, -9101, -7722, -6269, -4755, -3195, -1605,
       0,  1606,  3196,  4756,  6270,  7723,  9102, 10394,
   11585, 12665, 13623, 14449, 15137, 15679, 16069, 16305,
   16384
};

void copy_cube(void){
  uint8_t i;
  for (i = 0; i < 8; i++){
    cube2[i] = cube[i];
  }
}

void rotate_cube_x(uint16_t w){
  uint8_t i;
  int16_t y, z;
  for (i = 0; i < 8; i++){
    y = ( (int32_t)cube2[i].y * (int32_t)cos_tbl[w] + 
          (int32_t)cube2[i].z * (int32_t)sin_tbl[w]) >> 14;
    z = (-(int32_t)cube2[i].y * (int32_t)sin_tbl[w] + 
          (int32_t)cube2[i].z * (int32_t)cos_tbl[w]) >> 14;
    cube2[i].y = y;
    cube2[i].z = z;
  }
}

void rotate_cube_y(uint16_t w){
  uint8_t i;
  int16_t x, z;
  for (i = 0; i < 8; i++){
    x = ( (int32_t)cube2[i].x * (int32_t)cos_tbl[w] +
          (int32_t)cube2[i].z * (int32_t)sin_tbl[w]) >> 14;
    z = (-(int32_t)cube2[i].x * (int32_t)sin_tbl[w] + 
          (int32_t)cube2[i].z * (int32_t)cos_tbl[w]) >> 14;
    //printf("%d: %d %d --> %d %d\n",i,cube2[i].x,cube2[i].z,x,z);
    cube2[i].x = x;
    cube2[i].z = z;
  }
}

void trans_cube(uint16_t z){
  uint8_t i;
  for (i = 0; i < 8; i++){
    cube2[i].z += z;
  }
}

void reset_min_max(void){
  x_min =  0x07fff;
  y_min =  0x07fff;
  x_max = -0x07fff;
  y_max = -0x07fff;
}

// calculate xs and ys from a 3d value
void convert_3d_to_2d(struct p3d *p3, struct p2d *p2){
  int ws = M5.Lcd.width();
  int hs = M5.Lcd.height();
  int32_t t;
  p2->is_visible = 1;
  if (p3->z >= ZS){
    t = ZS;
    t *= p3->x;
    t <<= 1;
    t /= p3->z;
    if (t >= -(ws/2) && t <= (ws/2) - 1){
      t += (ws/2);
      p2->x = t;
      if (x_min > t) x_min = t;
      if (x_max < t) x_max = t;
      t = ZS;
      t *= p3->y;
      t <<= 1;
      t /= p3->z;
      if (t >= -(hs/2) && t <= (hs/2) - 1){
        t += (hs/2);
        p2->y = t;
        if (y_min > t) y_min = t;
        if (y_max < t) y_max = t;
      }else{
        p2->is_visible = 0;
      }
    }else{
      p2->is_visible = 0;
    }
  }else{
    p2->is_visible = 0;
  }
}

void convert_cube(void){
  uint8_t i;
  reset_min_max();
  for (i = 0; i < 8; i++){
    convert_3d_to_2d(cube2 + i, cube_ + i);
  }
}

void calculate_z(void){
  uint8_t  i, j;
  uint16_t z;
  for (i = 0; i < 6; i++){
    z = 0;
    for (j = 0; j < 4; j++){
      z += cube2[s[i].p[j]].z;
    }
    z /= 4;
    s[i].z = z;
    //printf("%d: z=%d\n", i, z);
  }
}

void draw_cube(void){
  uint8_t i, ii;
  uint8_t skip_cnt = 3; // first 3 surfaces are invisible
  int16_t z, z_upper;
  uint16_t color;
  z_upper = 32767;
  for (;;){
    ii = 6;
    z = -32767;
    for (i = 0; i < 6; i++){
      if (s[i].z <= z_upper){
        if (z < s[i].z){
          z = s[i].z;
          ii = i;
        }
      }
    }
    if (ii >= 6) break;
    z_upper = s[ii].z;
    s[ii].z++;
    if (skip_cnt > 0){
      skip_cnt--;
    }else{
      color = M5.Lcd.color565((uint8_t)((( ii + 1) &  1)      * 255),
                              (uint8_t)((((ii + 1) >> 1) & 1) * 255),
                              (uint8_t)((((ii + 1) >> 2) & 1) * 255));
      #if defined(WIREFRAME) // WIREFRAME
        M5.Lcd.drawTriangle(cube_[s[ii].p[0]].x, cube_[s[ii].p[0]].y,
                            cube_[s[ii].p[1]].x, cube_[s[ii].p[1]].y,
                            cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            color);
        M5.Lcd.drawTriangle(cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            cube_[s[ii].p[3]].x, cube_[s[ii].p[3]].y,
                            cube_[s[ii].p[0]].x, cube_[s[ii].p[0]].y,
                            color);
        M5.Lcd.drawTriangle(cube_[s[ii].p[1]].x, cube_[s[ii].p[1]].y,
                            cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            cube_[s[ii].p[3]].x, cube_[s[ii].p[3]].y,
                            color);
      #else // SOLID
        M5.Lcd.fillTriangle(cube_[s[ii].p[0]].x, cube_[s[ii].p[0]].y,
                            cube_[s[ii].p[1]].x, cube_[s[ii].p[1]].y,
                            cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            color);
        M5.Lcd.fillTriangle(cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            cube_[s[ii].p[3]].x, cube_[s[ii].p[3]].y,
                            cube_[s[ii].p[0]].x, cube_[s[ii].p[0]].y,
                            color);
        M5.Lcd.fillTriangle(cube_[s[ii].p[1]].x, cube_[s[ii].p[1]].y,
                            cube_[s[ii].p[2]].x, cube_[s[ii].p[2]].y,
                            cube_[s[ii].p[3]].x, cube_[s[ii].p[3]].y,
                            color);
      #endif
    }
  }
}

void calc_and_draw(int16_t w, int16_t v){
  copy_cube();
  rotate_cube_y(w);
  rotate_cube_x(v);
  trans_cube(U * 8);
  convert_cube();
  calculate_z();
  draw_cube();
}

void setup(void){ 
  M5.begin();
//Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}//SD UPdate
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setRotation(3);
}

int16_t w = 0;
int16_t v = 0;

void loop(void){
  calc_and_draw(w, v >> 3);
  v += 3;
  v &= 511;
  w++;
  w &= 63;
  delay(50); // Rotational speed
  M5.Lcd.fillRect(x_min,y_min,x_max-x_min+3,y_max-y_min+3,BLACK);
}

———————————————————————————————–
M5StickC , 3D , COLOR , CUBE , IPS ,

Written by macsbug

5月 20, 2019 at 10:30 pm

カテゴリー: M5STACK

Make M5StickC Proto Module

leave a comment »

M5StickC の Proto Module を製作しました。               2019.05.16

費用:110円
サイズ:20 x 24 x 14 mm
スルーホール数:最大 54 個 ( 18 x 3 段 )


.
部品表:

No Name Price & Purchase Memo
1  ピンヘッダー L型 1×8  秋月:1×8 1個10円    10円
2  0.3mm ユニバーサル基板  秋月:Dタイプ(47×36mm)    60円
3  ケース ( カバー、ケース本体 )  自作:3D Printer 材料費    40円
4  クリップ 0.9mm 2本  事務用クリップ      0円
5  ピンヘッダー 1×8  秋月:1個40円 必要に応じて      0円
 =============== ================= ====
 費用総計   110円

.
1. 基板サイズ:スルーホール 8 x 6 の広さで使用します。
2. 基板の枚数:必要に応じて 1〜3枚を使用します。
3. 基板の固定:基板をケースにスライドして入れる 基板用の溝( 0.5mm )を設けます。
_       事務用のクリップを 0.9mm に切り 使用します。
_       クリップは ケースの軸穴と 基板の穴に通します。
4. 基板の連結:ピンヘッダー 1×8 を必要に応じて使用します。
.

ケース:CAD
3D Printer 出力時間:30分。材料:4g。
.


.
組み立て:


.
スケルトン版:


.
感想:
スルーホール数:
_ 1枚 18個。基板を重ね 3段で54個まで可能です。

M5StickC オリジナル:
_ コネクター:1段の為 安定が良く無いです。できれば 2段のコネクタが安定します。
_ ケース:コネクター部は少し湾曲している為 安定が良く無いです。
_ スルーホール数:1例として 10個です。

ケースの対策:
_  L型カバー:本体側に少し隙間 ( 0.5mm ) を空け安定化させました。
_   必要に応じて両面テープを張り 固定する事も可能です。

固定:オリジナルと同じく ピンヘッダー8本で支えています。

広さ:小さい為 広さを確保する為に 固定の為の ネジは使用しません。
_  固定は 事務用クリップを 0.9mm に切って使用しました。

サイズ:20 x 24 x 13.7 mm:
_ 20mm:M5文字のスイッチ部と同じサイズにし 全体のバランスをとりました。
_ ケースの色を M5StickC と同じ色にすると効果がでます。

———————————————————————


M5Stick-C , M5Stick C , M5StickC , Proto , Proto Module , macsbug ,

Written by macsbug

5月 16, 2019 at 1:00 am

カテゴリー: M5STACK

M5StickC Electronic Animated Eyes

with 2 comments

M5StickC で 電子アニメーションの目 を表示しました。      2019.05.08

M5StickC の Display は IPS ( In Plane Switching ) を使用しています。
明るく 視野角 広く 斜めから見ても とても綺麗です。

機能:
_ M5文字の A ボタンを押すと ウインクします。。
_ 2個あると 便利かと思います。


.
作成方法:
_ 詳細:M5Stack Electronic Animated Eyes:2019.05.04:M5Stack の事例と解説。

1. M5StickC_uncannyEyes.ino は ブログ最下位に記載しています。
2. screenshotToConsole.ino は ESP866_uncannyEyes からDLします。
3. defaultEye.h (eyeデーター) は ESP866_uncannyEyes からDLします。
_ 他の eyes データーも 試してみてください。
4. Arduino IDE / Tools 設定:
_  


.
右目と左目:追記:2019.05.21
_ 最初のスケッチは 右目表示です。左目を作成するには 以下を変更します。
_ 行番号は編集で変化しますので目安です。

125:  M5.Lcd.setRotation(1); // 変更前
125:  M5.Lcd.setRotation(3); // 変更後
.
138:  uint32_t screenX, screenY, scleraXsave; の下に追加
139:  uint32_t screenX_; // add macsbug // 追加
.
変更前
157:  if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX) <= lT) ||
158:     (pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX) <= uT)){
変更後
157:  screenX_ = SCREEN_WIDTH - 1 - screenX;
158:  if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX_) <= lT) ||
159:     (pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX_) <= uT)){
.
312:  eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128); // 変更前
313:  eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128); // 変更前
312:  eyeX = map(eyeX,1023,   0,0, SCLERA_WIDTH  - 128); // 変更後
313:  eyeY = map(eyeY,   0,1023,0, SCLERA_HEIGHT - 128); // 変更後

.
価格:1829円 ( $16.64 )
品名:2019 M5StickC ESP32 PICO Mini IoT Development Board
販売店:M5Stack Official Store

参考 及び 詳細:
_ M5StickC docs:概要(電源操作、サポート ボーレート、ピンマップ)
_ github:M5StickC Library:M5StickC ライブラリー
_ M5Stack Electronic Animated Eyes:2019.05.04:M5Stack の事例と解説。
_ M5StickC Product Documents:M5StIckC の ドキュメント
_ Run IPS SPI display on M5Stack:0.96″ IPS ST7735、1.3″ IPS ST7789。


.
感想:
IPS Display は ESPボードの中で 初めて M5StickC に採用されました。
表示は 明るく 視野角が広く 大変綺麗です。M5Stack より遥かに鮮やかです。
その 威力を発揮するサンプルを作成しました。
M5StickC のセンサーや 他とのインターフェースで面白い物が作れるかと思います。
スケッチの記述は 不足な所もあるかと思いますが お許し下さい。

メモ:このスケッチでの USB 5V の消費電流は 188mA です。
メモ:追記:2019.09.14
_  M5Stack や M5StickC の画面を綺麗に撮影する方法:M KAMERA アプリ。
_  光りを発光する M5Stack や M5StickC は カメラで綺麗に撮れませんが
_  IOS の M KAMERAを使用し マニュアルで調整すると綺麗に撮影出来ます。
_  iOS ( iPhone, iPad ) の Mカメラはマニュアルコントロール撮影が可能です。
_  ISO感度・露光時間(シャッタースピード), 焦点距離(ピント),
_  ホワイトバランス(色温度)を マニュアルで設定でき綺麗に撮影出来ます。
_  

課題:M5StickC 2台で Electronic Animated Eyes。
_ 2台のメガネ風を作ると 目の動きが別々になり 変な人になってしまいます。
_ どう 同期を取るかが課題で ワイヤー接続が簡単かと思案中。
_ さらに バッテリー容量が少ないので 電源をどうするかです。


.
リスト:
_ M5StickC_uncannyEyes : 2019.05.08 modify by macsbug

// Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display.
//
// This code currently does not "blink" the eye!
//
// Library used is here:
// https://github.com/Bodmer/TFT_eSPI
//
// To do, maybe, one day:
// 1. Get the eye to blink
// 2. Add another screen for another eye
// 3. Add varaible to set how wide open the eye is
// 4. Add a reflected highlight to the cornea
// 5. Add top eyelid shaddow to eye surface
// 6. Add aliasing to blur mask edge
//
// With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock)
// which is quite reasonable. Operation at an 80MHz SPI clock is possible but
// the display may not be able to cope with a clock rate that high and the
// performance improvement is small. Operate the ESP8266 at 160MHz for best
// frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance
// will be constrained by the increased memory access time.

// Original header for this sketch is below. Note: the technical aspects of the
// text no longer apply to this modified version of the sketch:
/*
//--------------------------------------------------------------------------
// Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431)
// or 1.44" M5.Lcd LCD (#2088).  This uses Teensy-3.1-specific features and
// WILL NOT work on normal Arduino or other boards!  Use 72 MHz (Optimized)
// board speed -- OLED does not work at 96 MHz.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------
*/
// M5StickC_uncannyEyes : 2019.05.08 modify by macsbug
//  https://macsbug.wordpress.com/2019/05/08/m5stickc-electronic-animated-eyes/
// M5Stack_uncannyEyes  : 2019.05.06 Transplant by macsbug
//  https://macsbug.wordpress.com/2019/05/06/m5stack-electronic-animated-eyes/
// Bodmer/ESP8266_uncannyEyes/ESP8266_uncannyEyes.ino
//  https://github.com/Bodmer/ESP8266_uncannyEyes/blob/master/ESP8266_uncannyEyes.ino
// graphics : catEye.h,terminatorEye.h,doeEye.h,naugaEye.h,newtEye.h,owlEye.h
//  https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes/graphics
// Electronic Animated Eyes using Teensy 3.1/3.2
//  https://learn.adafruit.com/animated-electronic-eyes-using-teensy-3-1?view=all
// adafruit/Uncanny_Eyes :
//  https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes

#include <M5StickC.h>
//#include "M5StackUpdater.h" // comment out M5StickC 
//#include <driver/dac.h>     // comment out M5StickC 
// Enable ONE of these #includes for the various eyes:
#include "defaultEye.h"     // Standard human-ish hazel eye
//#include "noScleraEye.h"  // Large iris, no sclera
//#include "dragonEye.h"    // Slit pupil fiery dragon// -0x001 to 0x007F
//#include "goatEye.h"      // Horizontal pupil goat  // -0x001 to 0x007F
//#include "catEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "doeEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "terminatorEye.h"// #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "naugaEye.h"     // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "newtEye.h"      // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "owlEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=

#define DISPLAY_DC      D3 // Data/command pin for BOTH displays
#define DISPLAY_RESET   D4 // Reset pin for BOTH displays
#define SELECT_L_PIN    37 // LEFT  eye chip select pin // rev M5StickC 
//#define SELECT_R_PIN    37 // RIGHT eye chip select pin // comment out M5StickC 
// INPUT CONFIG (for eye motion -- enable or comment out as needed) --------
// The ESP8266 is rather constrained here as it only has one analogue port.
// An I2C ADC could be used for more analogue channels
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
//#define JOYSTICK_Y_PIN A0 // Analog pin for eye vert position (")
//#define JOYSTICK_X_FLIP   // If set, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If set, reverse stick Y axis
#define TRACKING          // If enabled, eyelid tracks pupil
#define IRIS_PIN       37 // Photocell or potentiometer (else auto iris)
#define IRIS_PIN_FLIP  37 // If set, reverse reading from dial/photocell
#define IRIS_SMOOTH       // If enabled, filter input from IRIS_PIN
#define IRIS_MIN      140 // Clip lower analogRead() range from IRIS_PIN
#define IRIS_MAX      160//260 // Clip upper "
#define WINK_L_PIN     37 // Pin for LEFT eye wink button // rev M5StickC 
#define BLINK_PIN      37 // Pin for blink button (BOTH eyes) // rev M5StickC 
//#define WINK_R_PIN     37 // Pin for RIGHT eye wink button // comment out M5StickC
#define AUTOBLINK         // If enabled, eyes blink autonomously

// Probably don't need to edit any config below this line, -----------------
// unless building a single-eye project (pendant, etc.), in which case one
// of the two elements in the eye[] array further down can be commented out.
// Eye blinks are a tiny 3-state machine.  Per-eye allows winks + blinks.
#define NOBLINK 0     // Not currently engaged in a blink
#define ENBLINK 1     // Eyelid is currently closing
#define DEBLINK 2     // Eyelid is currently opening
typedef struct {
  int8_t   pin;       // Optional button here for indiv. wink
  uint8_t  state;     // NOBLINK/ENBLINK/DEBLINK
  int32_t  duration;  // Duration of blink state (micros)
  uint32_t startTime; // Time (micros) of last state change
} eyeBlink;

struct {
  uint8_t     cs;     // Chip select pin
  eyeBlink    blink;  // Current blink state
} eye[] = { SELECT_L_PIN, { WINK_L_PIN, NOBLINK }//, // comment out M5StickC 
           // SELECT_R_PIN, { WINK_R_PIN, NOBLINK }  // comment out M5StickC 
};

#define NUM_EYES 1//2
uint32_t fstart = 0;  // start time to improve frame rate calculation at startup

// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
  M5.begin();
  //Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}//SD UPdate
                                       // comment out M5StickC 
  uint8_t e = 0;
  //dac_output_disable(DAC_CHANNEL_1); // rev M5StickC 
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setRotation(1);
  fstart = millis()-1; // Subtract 1 to avoid divide by zero later
  pinMode(37, INPUT);//pinMode(38, INPUT);pinMode(39, INPUT);
}

// EYE-RENDERING FUNCTION --------------------------------------------------
#define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye
void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
  // Use native 32 bit variables where possible as this is 10% faster!
  uint8_t  e,       // Eye array index; 0 or 1 for left/right
  uint32_t iScale,  // Scale factor for iris
  uint32_t scleraX, // First pixel X offset into sclera image
  uint32_t scleraY, // First pixel Y offset into sclera image
  uint32_t uT,      // Upper eyelid threshold value
  uint32_t lT) {    // Lower eyelid threshold value
  uint32_t screenX, screenY, scleraXsave;
  int32_t  irisX, irisY;
  uint32_t p, a;
  uint32_t d;
  uint32_t pixels = 0;
  uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16 bit
  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  // M5.Lcd.setAddrWindow(x axis, y axis, Horizontal width, Vertical width);
  M5.Lcd.setAddrWindow ( 16, -22, 128, 128 ); // rev M5StickC 
  //if (e == 1){ M5.Lcd.setAddrWindow (192,0,128,128);} // comment out M5StickC 
  // Now just issue raw 16-bit values for every pixel...
  scleraXsave = scleraX; // Save initial X value to reset on each line
  irisY       = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT) / 2;
  for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
    scleraX = scleraXsave;
    irisX   = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
      if((pgm_read_byte(lower + screenY * SCREEN_WIDTH + screenX) <= lT) ||
         (pgm_read_byte(upper + screenY * SCREEN_WIDTH + screenX) <= uT)){//Covered by eyelid
        p = 0;
      } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
        p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
      } else {                                          // Maybe iris...
        p = pgm_read_word(polar + irisY * IRIS_WIDTH + irisX);// Polar angle/dist
        d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
        if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
          p = pgm_read_word(iris + d * IRIS_MAP_WIDTH + a);// Pixel = iris
        } else {                                        // Not in iris
          p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);//Pixel= clear
        }
      }
      *(pbuffer + pixels++) = p>>8 | p<<8;
      if (pixels >= BUFFER_SIZE){yield();
          M5.Lcd.pushColors((uint8_t*)pbuffer,pixels*2);pixels=0;}//drawEye
    }
  }
   if (pixels) { M5.Lcd.pushColors(pbuffer, pixels); pixels = 0;}
}

// EYE ANIMATION -----------------------------------------------------------
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
    0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
    3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
   11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
   24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
   40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
   60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
   81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103,   // e
  104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127,   // c
  128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,   // J
  152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174,   // a
  175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195,   // c
  197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215,   // o
  216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231,   // b
  232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244,   // s
  245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252,   // o
  252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n

#ifdef AUTOBLINK
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
#endif

void frame( // Process motion for a single frame of left or right eye
  uint32_t        iScale) {     // Iris scale (0-1023) passed in
  static uint32_t frames   = 0; // Used in frame rate calculation
  static uint8_t  eyeIndex = 0; // eye[] array counter
  int32_t         eyeX, eyeY;
  uint32_t        t = micros(); // Time at start of function
  Serial.print((++frames * 1000) / (millis() - fstart));
  Serial.println("fps");        // Show frame rate
  if(++eyeIndex >= NUM_EYES) eyeIndex = 0; // Cycle through eyes, 1 per call
 // Autonomous X/Y eye motion
      // Periodically initiates motion to a new random point, random speed,
      // holds there for random period until next motion.
  static boolean  eyeInMotion      = false;
  static int32_t  eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
  static uint32_t eyeMoveStartTime = 0L;
  static int32_t  eyeMoveDuration  = 0L;
  int32_t dt = t - eyeMoveStartTime;      // uS elapsed since last eye event
  if(eyeInMotion) {                       // Currently moving?
    if(dt >= eyeMoveDuration) {           // Time up?  Destination reached.
      eyeInMotion      = false;           // Stop moving
      eyeMoveDuration  = random(3000000L); // 0-3 sec stop
      eyeMoveStartTime = t;               // Save initial time of stop
      eyeX = eyeOldX = eyeNewX;           // Save position
      eyeY = eyeOldY = eyeNewY;
    } else { // Move time's not yet fully elapsed -- interpolate position
      int16_t e = ease[255 * dt / eyeMoveDuration] + 1;   // Ease curve
      eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
      eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
    }
  } else {                                // Eye stopped
    eyeX = eyeOldX;
    eyeY = eyeOldY;
    if(dt > eyeMoveDuration) {            // Time up?  Begin new move.
      int16_t  dx, dy;
      uint32_t d;
      do {                                // Pick new dest in circle
        eyeNewX = random(1024);
        eyeNewY = random(1024);
        dx      = (eyeNewX * 2) - 1023;
        dy      = (eyeNewY * 2) - 1023;
      } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
      eyeMoveDuration  = random(50000,150000); // ~1/14sec
                       //random(72000,144000); // ~1/ 7sec
      eyeMoveStartTime = t;               // Save initial time of move
      eyeInMotion      = true;            // Start move on next frame
    }
  }

// Blinking
#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
    timeOfLastBlink = t;
    uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
    for(uint8_t e=0; e<NUM_EYES; e++) {
      if(eye[e].blink.state == NOBLINK) {
         eye[e].blink.state     = ENBLINK;
         eye[e].blink.startTime = t;
         eye[e].blink.duration  = blinkDuration;
      }
    }
    timeToNextBlink = blinkDuration * 3 + random(4000000);
  }
#endif

  if(eye[eyeIndex].blink.state) { // Eye currently blinking?
    // Check if current blink state time has elapsed
    if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration){
      // Yes -- increment blink state, unless...
      if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and...
        ((digitalRead(BLINK_PIN) == LOW) ||        // blink or wink held...
          digitalRead(eye[eyeIndex].blink.pin) == LOW)) {
        // Don't advance state yet -- eye is held closed instead
      } else { // No buttons, or other state...
        if(++eye[eyeIndex].blink.state > DEBLINK) {// Deblinking finished?
          eye[eyeIndex].blink.state = NOBLINK;     // No longer blinking
        } else { // Advancing from ENBLINK to DEBLINK mode
          eye[eyeIndex].blink.duration *= 2;//DEBLINK is 1/2 ENBLINK speed
          eye[eyeIndex].blink.startTime = t;
        }
      }
    }
  } else { // Not currently blinking...check buttons!
    if(digitalRead(BLINK_PIN) == LOW) {
      // Manually-initiated blinks have random durations like auto-blink
      uint32_t blinkDuration = random(36000, 72000);
      for(uint8_t e=0; e<NUM_EYES; e++) {
        if(eye[e].blink.state    == NOBLINK) {
           eye[e].blink.state     = ENBLINK;
           eye[e].blink.startTime = t;
           eye[e].blink.duration  = blinkDuration;
        }
      }
    } else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink!
      eye[eyeIndex].blink.state     = ENBLINK;
      eye[eyeIndex].blink.startTime = t;
      eye[eyeIndex].blink.duration  = random(45000, 90000);
    }
  }

  // Process motion, blinking and iris scale into renderable values
  // Iris scaling: remap from 0-1023 input to iris map height pixel units
  iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
           (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));

  // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  eyeX = map(eyeX, 0, 1023, 0, SCLERA_WIDTH  - 128);
  eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display

  // Horizontal position is offset so that eyes are very slightly crossed
  // to appear fixated (converged) at a conversational distance.  Number
  // here was extracted from my posterior and not mathematically based.
  // I suppose one could get all clever with a range sensor, but for now...
  eyeX += 4;
  if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  static uint8_t uThreshold = 128;
  uint8_t        lThreshold, n;

#ifdef TRACKING
  int16_t sampleX = SCLERA_WIDTH  / 2 - (eyeX / 2), // Reduce X influence
          sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
  // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  if(sampleY < 0) n = 0;
  else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
   pgm_read_byte(upper+sampleY*SCREEN_WIDTH+(SCREEN_WIDTH-1-sampleX)))/2;
  uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
  // Lower eyelid doesn't track the same way, but seems to be pulled upward
  // by tension from the upper lid.
  lThreshold = 254 - uThreshold;
#else // No tracking -- eyelids full open unless blink modifies them
  uThreshold = lThreshold = 0;
#endif
  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if(eye[eyeIndex].blink.state) { // Eye currently blinking?
    uint32_t s = (t - eye[eyeIndex].blink.startTime);
    if(s >= eye[eyeIndex].blink.duration) s = 255;// At or past blink end
    else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
    s          = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n          = (uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n          = uThreshold;
  }
  // Pass all the derived values to the eye-rendering function:
  drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
}

// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
#if !defined(IRIS_PIN) || (IRIS_PIN < 0)
// Autonomous iris motion uses a fractal behavior to similate both the major
// reaction of the eye plus the continuous smaller adjustments that occur.
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;

void split( // Subdivides motion path into two sub-paths w/randimization
  int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int16_t  endValue,   // Iris scale value at end
  uint32_t startTime,  // micros() at start
  int32_t  duration,   // Start-to-end time, in microseconds
  int16_t  range) {    // Allowable scale value variance when subdividing

  if(range >= 8) {     // Limit subdvision count, because recursion
    range    /= 2;     // Split range & time in half for subdivision,
    duration /= 2;     // then pick random center point within range:
    int16_t  midValue = (startValue + endValue - range)/2+random(range);
    uint32_t midTime  = startTime + duration;
    split(startValue, midValue, startTime, duration, range);//First half
    split(midValue  , endValue, midTime  , duration, range);//Second half
  } else {             // No more subdivisons, do iris motion...
    int32_t dt;        // Time (micros) since start of motion
    int16_t v;         // Interim value
    while((dt = (micros() - startTime)) < duration) {
      v = startValue + (((endValue - startValue) * dt) / duration);
      if(v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
      else if(v > IRIS_MAX) v = IRIS_MAX;
      frame(v);        // Draw frame w/interim iris scale value
    }
  }
}
#endif // !IRIS_PIN

// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
#if defined(IRIS_PIN) && (IRIS_PIN >= 0) // Interactive iris
  uint16_t v = 512; //analogRead(IRIS_PIN);// Raw dial/photocell reading
#ifdef IRIS_PIN_FLIP
  v = 1023 - v;
#endif
  v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  irisValue = ((irisValue * 15) + v) / 16;
  frame(irisValue);
#else // Unfiltered (immediate motion)
  frame(v);
#endif // IRIS_SMOOTH
#else  // Autonomous iris scaling -- invoke recursive function
  newIris = random(IRIS_MIN, IRIS_MAX);
  split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  oldIris = newIris;
#endif // IRIS_PIN
//screenshotToConsole();
}

——————————————————————————————


M5Stack , M5StickC , IPS , Electronic Animated Eyes , TFT_eSPI , ESP866_uncannyEyes , macsbug , DAC, nois , ノイズ , 綺麗 , eye ,

Written by macsbug

5月 8, 2019 at 1:00 pm

カテゴリー: M5STACK

M5Stack Electronic Animated Eyes

leave a comment »

M5Stack で 電子アニメーションの目 を表示しました。 org 2019.05.06, rev.1 5.08, rev.2 5.10, rev.3 6.26

1. 原作は adafruit の Electronic Animated Eyes using Teensy 3.1/3.2 です。
2. これから Bodmer氏が TFT_eSPI で ESP8266_uncannyEyes を作成しました。
_ Bodmer氏に感謝致します。
3. これに基づき ESP866_uncannyEyes を M5Stackに移植しました。
機能:
_ BUTTON A:左目のウインク
_ BUTTON B:両目のウインク
_ BUTTON C:右目のウインク


.
目の種類:

defaultEye
noScleraEye dragonEye goatEye
catEye newtEye terminatorEye
naugaEye newtEye owlEye

.
移植:
_ ESP866_uncannyEyes を M5Stack で動作する様に移植します。
_ フォルダーの構成は以下の様になります。
_  

1. ESP866_uncannyEyes の 修正 及び 追加 をしました。
_ スケッチのリストは ブログ最下位に記載しています。
_ SD_Update 対応です。
_ 名前は「ESP866_uncannyEyes」から「M5Stack_uncannyEyes」にしました。

2. Eyes データー:ESP866_uncannyEyes の中の Eyes データーに
_        defaultEye, dragonEye, goatEye, goatEye があり 2つ修正が必要です。
_ リアルな目の defaultEye だけの使用のみならば Eyes データーの編集や追加は不要です。
_ 各自使用のエデッターで編集してください。
_ 修正:dragonEye : 「-0x001」 は 誤記で「0x007F」に変更。
_ 修正:goatEye : 「-0x001」 は 誤記で「0x007F」に変更。

3. Eyes データーの追加:adafruit/Uncanny_Eyes にある catEye, doeEye, terminatorEye,
_ naugaEye, newtEye, owlEye の6つに 追加と修正をします。
_ defaultEye.h と dragonEye.h は正常に動作していますので この記述を参照します。
_ Eyes データーの配列の構成は sclera, upper, lower, polar の 4つでできています。
_ 追加: 「#include pgmspace.h」 の追加。
_ 修正:「][」 を 「 * 」 , 「 =」 を 「PROGMEM=」に変更。


.
抽画メモ:
_ drawEye ルーチン:
_ 左の表示ウインドウを設定する:if (e == 0){M5.Lcd.setAddrWindow( 0,0,128,128);}
_ 右の表示ウインドウを設定する:if (e == 1){M5.Lcd.setAddrWindow(192,0,128,128);}
_  M5.Lcd.setAddrWindow( x axis, y axis, Horizontal width, Vertical width );
_ 画像を抽画する:M5.Lcd.pushColors((uint8_t*)pbuffer,pixels*2); pixels=0;}
_         2 は 縦を2倍にしています。
_ 左目の処理:左右反転。screenX
_  右目:if (e == 1){screenX_ = screenX;}
_  左目:if (e == 0){screenX_ = SCREEN_WIDTH – 1 – screenX;}
_  次の処理:
_   if((pgm_read_byte(lower+screenY * SCREEN_WIDTH + screenX_) <= lT) ||
_    (pgm_read_byte(upper+screenY * SCREEN_WIDTH + screenX_) <= uT))
_ frame ルーチン:瞳, eyeX
_  左の瞳:if (eyeIndex == 0){eyeX = map(eyeX,1023,0,0,SCLERA_WIDTH – 128);}
_  右の瞳:if (eyeIndex == 1){eyeX = map(eyeX,0,1023,0,SCLERA_WIDTH – 128);}


.
レンズ:プラスチック レンズを使用しました。
価格:2個378円 ( 1個 189円 , $ 1.70 )。 仕様:23mm。 販売:ebay 2012topdeal shop:


.
感想:
M5Stack を使用しない時 飾りに置くのも良いかと思います。
機能:BUTTON A,B、C で 目のウインクができます。他のアイデアでセンサー等と
_  接続すると 面白い物が出来るかと思います。
左目と右目:
_ 原作の adafruit は 表示装置を2つ使用し 右と左を用意しています。
_ ESP866_uncannyEyes も 2つ使用する構造になっています。
_ 今回 M5Stack の中に 2つ同じデーターを表示する為、左の画像が左右逆になっています。
_ 左の画像を左右逆にするには レベルが高く 今回はこの程度にしておきました。
_ 次回、2019/05/08 では 綺麗なSPI DisplayのM5Stick版を記載しました。
_  M5StickC Electronic Animated Eyes
_   

メモ:この画像を表示しますと DISPLAY の SPI MOSI に複雑な信号がでます。
_ これにより MBUS MOSI (GPIO_23) の端子から 放射ノイズ が多くなり
_ 隣にある DAC(25) Pin とパターンへ 放射ノイズが乗りやすくなります。
_ Pin と PROTO基板等の条件により スピーカーからのノイズが多くなります。
_ ソフトの対策として
_   #include , dac_output_disable(DAC_CHANNEL_1); を記載し
_  DAC をオフにして スピーカーから ノイズを出さない様にしています。
_
_ M5Stackのスピーカーノイズの原因と対策:追記、2019.09.11
_  要素: NS4150Bの基盤設計の不足等、幾つかの要素はありますが、
_   大きな要素は、MBUS MOSI (GPIO_23) と MBUS DAC(25) が 隣
_   (2.54mm) にある為に、MBUS MOSIの放射ノイズによりスピーカー
_   からノイズがでます。MBUS DAC(25) ピンがあるだけでも誘導します。
_   MBUS DAC(25) のアンプ入力の配置と配線は アンプの入力にアンテナを
_   立てている事と同じです。
_   原則:MBUS MOSI と MBUS DAC は 隣に配置してはいけない。
_   レイアウトに於ける設計ミスと判断しています。
_  確認:テスター等で誘導電圧が確認できます。23と25間にワイヤーを近接する
_   と誘導される事が解ります。周波数成分の大きいMOSI信号時も同様です。
_  原因:基盤設計における放射ノイズやレイアウトの技術不足の為です。
_   たとえば MBUS MOSI (GPIO_23) と MBUS SCK (GPIO_18) を逆配置にする
_   とノイズは発生しません。放射ノイズを受けない距離の為です。
_  情報:この件は 私のFaceBookメモから M5Stackチームが気がつき
_   ある製品開発では MBUS DAC(25) ピンの配線をしていません。
_   M5Stack Community では BOTTOM を外すというアドバイスがある。
_  対策:MBUS DAC(25)ピンをカットします。
_   MBUS DAC(25)ピンをカットすると ノイズの無いPROTO基盤が可能です。
_  補足:mはげ氏は M5Stack音割れ問題 の調査に努力され、ノイズ発生の
_   少ないDACの入力方法を編み出しています。mはげ氏に感謝致します。
_   NS4150Bを入力抵抗30KΩのNS4148へ交換を実施してみましたが
_   ノイズの問題は解決しませんでした。NS4150BやNS4148はスマホの
_   アンプに使用されています。よってM5の基板設計の問題と判断しています。
_   たとえば GNDパターンに誘導があり 正しい GNDが形成されていません。
_
_  自作設計者へ:
_   この件を把握していない為に スイッチサイエンスで委託販売している
_   PROTO基盤の製品はノイズ対策がされていない為にノイズがでるはずです。
_   販売されている基盤の MBUS DAC(25) のレイアウトを見ると解ります。
_   MBUS DAC(25)ピンがあるだけでも発生し、50x50mmの基盤上にパターン
_   を設けると確実にノイズが発生します。ある基板は 23Pin と 25Pin間に
_   スルーホールを配置し 1.27mm以下の距離になりノイズを増加させています。
_   さらに フラットケーブルで延長している製品は確実にノイズが発生します。


.
リスト:
_ M5Stack_uncannyEyes :
_  org ___ 2019.05.06 Transplant by macsbug
_  rev.1 _ 2019.05.08 : add SD_Updat
_  rev.2 _ 2019.05.10 : Left eye position and movement correction
_  rev.3 _ 2019.06.26 : Correct the up and down movement of the pupil

追記:2019.05.08 rev.1 : スケッチ変更。
_ スケッチの org バージョンは TFT_eSPI Library 用でしたが
_ M5Stack Library 用に変更しました。 これにより SD_Update が可能です。
追記:2019.05.10 rev.2 : スケッチ変更。
_ 左目の位置を正常な位置(右の逆)に変更。目の移動も逆に設定。
追記:2019.06.26 rev.3 : スケッチ変更。
_ robo8080氏の指摘:157行目 変更。
_ 瞳の動きが上下逆になっているバグの修正。
_ irisX は削除し
_ irisY = scleraY – (SCLERA_HEIGHT – IRIS_HEIGHT ) / 2; // add : rev.3 2019.06.26
_ robo8080氏へ感謝!!。ありがとうございます。

// Adapted by Bodmer to work with a NodeMCU and ILI9341 or ST7735 display.
//
// This code currently does not "blink" the eye!
//
// Library used is here:
// https://github.com/Bodmer/M5Display
//
// To do, maybe, one day:
// 1. Get the eye to blink
// 2. Add another screen for another eye
// 3. Add varaible to set how wide open the eye is
// 4. Add a reflected highlight to the cornea
// 5. Add top eyelid shaddow to eye surface
// 6. Add aliasing to blur mask edge
//
// With one lidded eye drawn the code runs at 28-33fps (at 27-40MHz SPI clock)
// which is quite reasonable. Operation at an 80MHz SPI clock is possible but
// the display may not be able to cope with a clock rate that high and the
// performance improvement is small. Operate the ESP8266 at 160MHz for best
// frame rate. Note the images are stored in SPI FLASH (PROGMEM) so performance
// will be constrained by the increased memory access time.

// Original header for this sketch is below. Note: the technical aspects of the
// text no longer apply to this modified version of the sketch:
/*
//--------------------------------------------------------------------------
// Uncanny eyes for PJRC Teensy 3.1 with Adafruit 1.5" OLED (product #1431)
// or 1.44" M5.Lcd LCD (#2088).  This uses Teensy-3.1-specific features and
// WILL NOT work on normal Arduino or other boards!  Use 72 MHz (Optimized)
// board speed -- OLED does not work at 96 MHz.
//
// Adafruit invests time and resources providing this open source code,
// please support Adafruit and open-source hardware by purchasing products
// from Adafruit!
//
// Written by Phil Burgess / Paint Your Dragon for Adafruit Industries.
// MIT license.  SPI FIFO insight from Paul Stoffregen's ILI9341_t3 library.
// Inspired by David Boccabella's (Marcwolf) hybrid servo/OLED eye concept.
//--------------------------------------------------------------------------
*/
// M5Stack_uncannyEyes : org   : 2019.05.06 Transplant by macsbug
// M5Stack_uncannyEyes : rev.1 : 2019.05.08 modify by macsbug
// M5Stack_uncannyEyes : rev.2 : 2019.05.10 modify by macsbug
// M5Stack_uncannyEyes : rev.3 : 2019.06.26 modify by macsbug
//  https://macsbug.wordpress.com/2019/05/06/m5stack-electronic-animated-eyes/
// Bodmer/ESP8266_uncannyEyes/ESP8266_uncannyEyes.ino
//  https://github.com/Bodmer/ESP8266_uncannyEyes/blob/master/ESP8266_uncannyEyes.ino
// graphics : catEye.h,terminatorEye.h,doeEye.h,naugaEye.h,newtEye.h,owlEye.h
//  https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes/graphics
// Electronic Animated Eyes using Teensy 3.1/3.2
//  https://learn.adafruit.com/animated-electronic-eyes-using-teensy-3-1?view=all
// adafruit/Uncanny_Eyes :
//  https://github.com/adafruit/Uncanny_Eyes/tree/master/uncannyEyes

#include <M5Stack.h>
#include "M5StackUpdater.h"
#include <driver/dac.h>
// Enable ONE of these #includes for the various eyes:
#include "defaultEye.h"     // Standard human-ish hazel eye
//#include "noScleraEye.h"  // Large iris, no sclera
//#include "dragonEye.h"    // Slit pupil fiery dragon// -0x001 to 0x007F
//#include "goatEye.h"      // Horizontal pupil goat  // -0x001 to 0x007F
//#include "catEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "doeEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "terminatorEye.h"// #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "naugaEye.h"     // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "newtEye.h"      // #include <pgmspace.h>,][ to *,= to PROGMEM=
//#include "owlEye.h"       // #include <pgmspace.h>,][ to *,= to PROGMEM=

#define DISPLAY_DC      D3 // Data/command pin for BOTH displays
#define DISPLAY_RESET   D4 // Reset pin for BOTH displays
#define SELECT_L_PIN    38 // LEFT  eye chip select pin
#define SELECT_R_PIN    39 // RIGHT eye chip select pin
// INPUT CONFIG (for eye motion -- enable or comment out as needed) -------
// The ESP8266 is rather constrained here as it only has one analogue port.
// An I2C ADC could be used for more analogue channels
//#define JOYSTICK_X_PIN A0 // Analog pin for eye horiz pos (else auto)
//#define JOYSTICK_Y_PIN A0 // Analog pin for eye vert position (")
//#define JOYSTICK_X_FLIP   // If set, reverse stick X axis
//#define JOYSTICK_Y_FLIP   // If set, reverse stick Y axis
#define TRACKING          // If enabled, eyelid tracks pupil
#define IRIS_PIN       37 // Photocell or potentiometer (else auto iris)
#define IRIS_PIN_FLIP  37 // If set, reverse reading from dial/photocell
#define IRIS_SMOOTH       // If enabled, filter input from IRIS_PIN
#define IRIS_MIN      140 // Clip lower analogRead() range from IRIS_PIN
#define IRIS_MAX      260 // Clip upper "
#define WINK_L_PIN     39 // Pin for LEFT eye wink button
#define BLINK_PIN      38 // Pin for blink button (BOTH eyes)
#define WINK_R_PIN     37 // Pin for RIGHT eye wink button
#define AUTOBLINK         // If enabled, eyes blink autonomously

// Probably don't need to edit any config below this line, -----------------
// unless building a single-eye project (pendant, etc.), in which case one
// of the two elements in the eye[] array further down can be commented out.
// Eye blinks are a tiny 3-state machine.  Per-eye allows winks + blinks.
#define NOBLINK 0     // Not currently engaged in a blink
#define ENBLINK 1     // Eyelid is currently closing
#define DEBLINK 2     // Eyelid is currently opening
typedef struct {
  int8_t   pin;       // Optional button here for indiv. wink
  uint8_t  state;     // NOBLINK/ENBLINK/DEBLINK
  int32_t  duration;  // Duration of blink state (micros)
  uint32_t startTime; // Time (micros) of last state change
} eyeBlink;

struct {
  uint8_t     cs;     // Chip select pin
  eyeBlink    blink;  // Current blink state
} eye[] = { SELECT_L_PIN, { WINK_L_PIN, NOBLINK },
            SELECT_R_PIN, { WINK_R_PIN, NOBLINK } 
};

#define NUM_EYES 2    // pcs eye : 1 = 1 eye, 2 = 2 eye
uint32_t fstart = 0;  // start time to improve frame rate calculation at startup

// INITIALIZATION -- runs once at startup ----------------------------------
void setup(void) {
  M5.begin();
  Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}//SD UPdate
  uint8_t e = 0;
  M5.Speaker.begin();
  //M5.Speaker.setVolume(0);
  //M5.Speaker.mute();
  dac_output_disable(DAC_CHANNEL_1);
  M5.Lcd.init();
  M5.Lcd.fillScreen(TFT_BLACK);
  M5.Lcd.setRotation(1);
  fstart = millis()-1; // Subtract 1 to avoid divide by zero later
  pinMode(37, INPUT);pinMode(38, INPUT);pinMode(39, INPUT);
}

// EYE-RENDERING FUNCTION --------------------------------------------------
#define BUFFER_SIZE 256 // 64 to 512 seems optimum = 30 fps for default eye
void drawEye( // Renders one eye.  Inputs must be pre-clipped & valid.
  // Use native 32 bit variables where possible as this is 10% faster!
  uint8_t  e,      // Eye array index; 0 or 1 for left/right:0/1 = left/right
  uint32_t iScale, // Scale factor for iris
  uint32_t scleraX,// First pixel X offset into sclera image:Center of right and left
  uint32_t scleraY,// First pixel Y offset into sclera image:Upper and lower centers
  uint32_t uT,     // Upper eyelid threshold value:Up and down
  uint32_t lT)     // Lower eyelid threshold value:Up and down
  {
  uint32_t screenX, screenY, scleraXsave;
  uint32_t screenX_; // add macsbug
  int32_t  irisX, irisY;
  uint32_t p, a;
  uint32_t d;
  uint32_t pixels = 0;
  uint16_t pbuffer[BUFFER_SIZE]; // This one needs to be 16 bit
  // Set up raw pixel dump to entire screen.  Although such writes can wrap
  // around automatically from end of rect back to beginning, the region is
  // reset on each frame here in case of an SPI glitch.
  if (e == 0){ M5.Lcd.setAddrWindow (  1,0,128,128);} // set left  window
  if (e == 1){ M5.Lcd.setAddrWindow (191,0,128,128);} // set right window
  // Now just issue raw 16-bit values for every pixel...
  scleraXsave = scleraX;
  irisY = scleraY - (SCLERA_HEIGHT - IRIS_HEIGHT ) / 2;  // add : rev.3 2019.06.26
  for(screenY=0; screenY<SCREEN_HEIGHT; screenY++, scleraY++, irisY++) {
    scleraX = scleraXsave;
    irisX = scleraXsave - (SCLERA_WIDTH - IRIS_WIDTH) / 2;
    for(screenX=0; screenX<SCREEN_WIDTH; screenX++, scleraX++, irisX++) {
      if (e == 1){screenX_ =                    screenX;}
      if (e == 0){screenX_ = SCREEN_WIDTH - 1 - screenX;}
      if((pgm_read_byte(lower+screenY * SCREEN_WIDTH + screenX_) <= lT) ||
         (pgm_read_byte(upper+screenY * SCREEN_WIDTH + screenX_) <= uT)){
        p = 0;  //Covered by eyelid
      } else if((irisY < 0) || (irisY >= IRIS_HEIGHT) ||
                (irisX < 0) || (irisX >= IRIS_WIDTH)) { // In sclera
        p = pgm_read_word(sclera + scleraY * SCLERA_WIDTH + scleraX);
      } else {                                          // Maybe iris...
        p = pgm_read_word(polar+irisY*IRIS_WIDTH+irisX);// Polar angle/dist
        d = (iScale * (p & 0x7F)) / 128;                // Distance (Y)
        if(d < IRIS_MAP_HEIGHT) {                       // Within iris area
          a = (IRIS_MAP_WIDTH * (p >> 7)) / 512;        // Angle (X)
          p = pgm_read_word(iris+d*IRIS_MAP_WIDTH+a);   // Pixel = iris
        } else {                                        // Not in iris
          p = pgm_read_word(sclera+scleraY*SCLERA_WIDTH+scleraX);//Pixel=clear
        }
      }
      *(pbuffer + pixels++) = p>>8 | p<<8;
      if (pixels >= BUFFER_SIZE){yield();
          M5.Lcd.pushColors((uint8_t*)pbuffer,pixels*2); pixels=0; //drawEye    
      }
    }
  }
   if (pixels) { M5.Lcd.pushColors(pbuffer, pixels); pixels = 0;}
}

// EYE ANIMATION -----------------------------------------------------------
const uint8_t ease[] = { // Ease in/out curve for eye movements 3*t^2-2*t^3
    0,  0,  0,  0,  0,  0,  0,  1,  1,  1,  1,  1,  2,  2,  2,  3,   // T
    3,  3,  4,  4,  4,  5,  5,  6,  6,  7,  7,  8,  9,  9, 10, 10,   // h
   11, 12, 12, 13, 14, 15, 15, 16, 17, 18, 18, 19, 20, 21, 22, 23,   // x
   24, 25, 26, 27, 27, 28, 29, 30, 31, 33, 34, 35, 36, 37, 38, 39,   // 2
   40, 41, 42, 44, 45, 46, 47, 48, 50, 51, 52, 53, 54, 56, 57, 58,   // A
   60, 61, 62, 63, 65, 66, 67, 69, 70, 72, 73, 74, 76, 77, 78, 80,   // l
   81, 83, 84, 85, 87, 88, 90, 91, 93, 94, 96, 97, 98,100,101,103,   // e
  104,106,107,109,110,112,113,115,116,118,119,121,122,124,125,127,   // c
  128,130,131,133,134,136,137,139,140,142,143,145,146,148,149,151,   // J
  152,154,155,157,158,159,161,162,164,165,167,168,170,171,172,174,   // a
  175,177,178,179,181,182,183,185,186,188,189,190,192,193,194,195,   // c
  197,198,199,201,202,203,204,205,207,208,209,210,211,213,214,215,   // o
  216,217,218,219,220,221,222,224,225,226,227,228,228,229,230,231,   // b
  232,233,234,235,236,237,237,238,239,240,240,241,242,243,243,244,   // s
  245,245,246,246,247,248,248,249,249,250,250,251,251,251,252,252,   // o
  252,253,253,253,254,254,254,254,254,255,255,255,255,255,255,255 }; // n

#ifdef AUTOBLINK
uint32_t timeOfLastBlink = 0L, timeToNextBlink = 0L;
#endif

void frame( // Process motion for a single frame of left or right eye
  uint32_t        iScale) {     // Iris scale (0-1023) passed in
  static uint32_t frames   = 0; // Used in frame rate calculation
  static uint8_t  eyeIndex = 0; // eye[] array counter
  int32_t         eyeX, eyeY;
  uint32_t        t = micros(); // Time at start of function
  Serial.print((++frames * 1000) / (millis() - fstart));
  Serial.println("fps");        // Show frame rate
  if(++eyeIndex >= NUM_EYES) eyeIndex = 0;// Cycle through eyes, 1 per call
  // Autonomous X/Y eye motion
  // Periodically initiates motion to a new random point, random speed,
  // holds there for random period until next motion.
  static boolean  eyeInMotion      = false;
  static int32_t  eyeOldX=512, eyeOldY=512, eyeNewX=512, eyeNewY=512;
  static uint32_t eyeMoveStartTime = 0L;
  static int32_t  eyeMoveDuration  = 0L;
  int32_t dt = t - eyeMoveStartTime;      // uS elapsed since last eye event
  if(eyeInMotion) {                       // Currently moving?
    if(dt >= eyeMoveDuration) {           // Time up?  Destination reached.
      eyeInMotion      = false;           // Stop moving
      eyeMoveDuration  = random(3000000L);// 0-3 sec stop
      eyeMoveStartTime = t;               // Save initial time of stop
      eyeX = eyeOldX = eyeNewX;           // Save position
      eyeY = eyeOldY = eyeNewY;
    } else { // Move time's not yet fully elapsed -- interpolate position
      int16_t e = ease[255 * dt / eyeMoveDuration] + 1;   // Ease curve
      eyeX = eyeOldX + (((eyeNewX - eyeOldX) * e) / 256); // Interp X
      eyeY = eyeOldY + (((eyeNewY - eyeOldY) * e) / 256); // and Y
    }
  } else {                                // Eye stopped
    eyeX = eyeOldX;
    eyeY = eyeOldY;
    if(dt > eyeMoveDuration) {            // Time up?  Begin new move.
      int16_t  dx, dy;
      uint32_t d;
      do {                                // Pick new dest in circle
        eyeNewX = random(1024);
        eyeNewY = random(1024);
        dx      = (eyeNewX * 2) - 1023;
        dy      = (eyeNewY * 2) - 1023;
      } while((d = (dx * dx + dy * dy)) > (1023 * 1023)); // Keep trying
      eyeMoveDuration  = random(50000,150000); // ~1/14sec
                       //random(72000,144000); // ~1/ 7sec
      eyeMoveStartTime = t;               // Save initial time of move
      eyeInMotion      = true;            // Start move on next frame
    }
  }

// Blinking
#ifdef AUTOBLINK
  // Similar to the autonomous eye movement above -- blink start times
  // and durations are random (within ranges).
  if((t - timeOfLastBlink) >= timeToNextBlink) { // Start new blink?
    timeOfLastBlink = t;
    uint32_t blinkDuration = random(36000, 72000); // ~1/28 - ~1/14 sec
    // Set up durations for both eyes (if not already winking)
    for(uint8_t e=0; e<NUM_EYES; e++) {
      if(eye[e].blink.state == NOBLINK) {
         eye[e].blink.state     = ENBLINK;
         eye[e].blink.startTime = t;
         eye[e].blink.duration  = blinkDuration;
      }
    }
    timeToNextBlink = blinkDuration * 3 + random(4000000);
  }
#endif

  if(eye[eyeIndex].blink.state) { // Eye currently blinking?
    // Check if current blink state time has elapsed
    if((t - eye[eyeIndex].blink.startTime) >= eye[eyeIndex].blink.duration){
      // Yes -- increment blink state, unless...
      if((eye[eyeIndex].blink.state == ENBLINK) && // Enblinking and...
        ((digitalRead(BLINK_PIN) == LOW) ||        // blink or wink held...
          digitalRead(eye[eyeIndex].blink.pin) == LOW)) {
        // Don't advance state yet -- eye is held closed instead
      } else { // No buttons, or other state...
        if(++eye[eyeIndex].blink.state > DEBLINK) {// Deblinking finished?
          eye[eyeIndex].blink.state = NOBLINK;     // No longer blinking
        } else { // Advancing from ENBLINK to DEBLINK mode
          eye[eyeIndex].blink.duration *= 2;//DEBLINK is 1/2 ENBLINK speed
          eye[eyeIndex].blink.startTime = t;
        }
      }
    }
  } else { // Not currently blinking...check buttons!
    if(digitalRead(BLINK_PIN) == LOW) {
      // Manually-initiated blinks have random durations like auto-blink
      uint32_t blinkDuration = random(36000, 72000);
      for(uint8_t e=0; e<NUM_EYES; e++) {
        if(eye[e].blink.state    == NOBLINK) {
           eye[e].blink.state     = ENBLINK;
           eye[e].blink.startTime = t;
           eye[e].blink.duration  = blinkDuration;
        }
      }
    } else if(digitalRead(eye[eyeIndex].blink.pin) == LOW) { // Wink!
      eye[eyeIndex].blink.state     = ENBLINK;
      eye[eyeIndex].blink.startTime = t;
      eye[eyeIndex].blink.duration  = random(45000, 90000);
    }
  }

  // Process motion, blinking and iris scale into renderable values
  // Iris scaling: remap from 0-1023 input to iris map height pixel units
  iScale = ((IRIS_MAP_HEIGHT + 1) * 1024) /
           (1024 - (iScale * (IRIS_MAP_HEIGHT - 1) / IRIS_MAP_HEIGHT));

  // Scale eye X/Y positions (0-1023) to pixel units used by drawEye()
  if (eyeIndex == 0){eyeX = map(eyeX,1023,0,0,SCLERA_WIDTH - 128);}// left
  if (eyeIndex == 1){eyeX = map(eyeX,0,1023,0,SCLERA_WIDTH - 128);}// right
  eyeY = map(eyeY, 0, 1023, 0, SCLERA_HEIGHT - 128);
  if(eyeIndex == 1) eyeX = (SCLERA_WIDTH - 128) - eyeX; // Mirrored display

  // Horizontal position is offset so that eyes are very slightly crossed
  // to appear fixated (converged) at a conversational distance.  Number
  // here was extracted from my posterior and not mathematically based.
  // I suppose one could get all clever with a range sensor, but for now...
  eyeX += 4;
  if(eyeX > (SCLERA_WIDTH - 128)) eyeX = (SCLERA_WIDTH - 128);

  // Eyelids are rendered using a brightness threshold image.  This same
  // map can be used to simplify another problem: making the upper eyelid
  // track the pupil (eyes tend to open only as much as needed -- e.g. look
  // down and the upper eyelid drops).  Just sample a point in the upper
  // lid map slightly above the pupil to determine the rendering threshold.
  static uint8_t uThreshold = 128;
  uint8_t        lThreshold, n;

#ifdef TRACKING
  int16_t sampleX = SCLERA_WIDTH  / 2 - (eyeX / 2), // Reduce X influence
          sampleY = SCLERA_HEIGHT / 2 - (eyeY + IRIS_HEIGHT / 4);
  // Eyelid is slightly asymmetrical, so two readings are taken, averaged
  if(sampleY < 0) n = 0;
  else n = (pgm_read_byte(upper + sampleY * SCREEN_WIDTH + sampleX) +
   pgm_read_byte(upper+sampleY*SCREEN_WIDTH+(SCREEN_WIDTH-1-sampleX)))/2;
  uThreshold = (uThreshold * 3 + n) / 4; // Filter/soften motion
  // Lower eyelid doesn't track the same way, but seems to be pulled upward
  // by tension from the upper lid.
  lThreshold = 254 - uThreshold;
#else // No tracking -- eyelids full open unless blink modifies them
  uThreshold = lThreshold = 0;
#endif
  // The upper/lower thresholds are then scaled relative to the current
  // blink position so that blinks work together with pupil tracking.
  if(eye[eyeIndex].blink.state) { // Eye currently blinking?
    uint32_t s = (t - eye[eyeIndex].blink.startTime);
    if(s >= eye[eyeIndex].blink.duration) s = 255;// At or past blink end
    else s = 255 * s / eye[eyeIndex].blink.duration; // Mid-blink
    s          = (eye[eyeIndex].blink.state == DEBLINK) ? 1 + s : 256 - s;
    n          = (uThreshold * s + 254 * (257 - s)) / 256;
    lThreshold = (lThreshold * s + 254 * (257 - s)) / 256;
  } else {
    n          = uThreshold;
  }
  // Pass all the derived values to the eye-rendering function:
  drawEye(eyeIndex, iScale, eyeX, eyeY, n, lThreshold);
}

// AUTONOMOUS IRIS SCALING (if no photocell or dial) -----------------------
#if !defined(IRIS_PIN) || (IRIS_PIN < 0)
// Autonomous iris motion uses a fractal behavior to similate both the major
// reaction of the eye plus the continuous smaller adjustments that occur.
uint16_t oldIris = (IRIS_MIN + IRIS_MAX) / 2, newIris;

void split( // Subdivides motion path into two sub-paths w/randimization
  int16_t  startValue, // Iris scale value (IRIS_MIN to IRIS_MAX) at start
  int16_t  endValue,   // Iris scale value at end
  uint32_t startTime,  // micros() at start
  int32_t  duration,   // Start-to-end time, in microseconds
  int16_t  range) {    // Allowable scale value variance when subdividing

  if(range >= 8) {     // Limit subdvision count, because recursion
    range    /= 2;     // Split range & time in half for subdivision,
    duration /= 2;     // then pick random center point within range:
    int16_t  midValue = (startValue + endValue - range)/2+random(range);
    uint32_t midTime  = startTime + duration;
    split(startValue, midValue, startTime, duration, range);//First half
    split(midValue  , endValue, midTime  , duration, range);//Second half
  } else {             // No more subdivisons, do iris motion...
    int32_t dt;        // Time (micros) since start of motion
    int16_t v;         // Interim value
    while((dt = (micros() - startTime)) < duration) {
      v = startValue + (((endValue - startValue) * dt) / duration);
      if(v < IRIS_MIN)      v = IRIS_MIN; // Clip just in case
      else if(v > IRIS_MAX) v = IRIS_MAX;
      frame(v);        // Draw frame w/interim iris scale value
    }
  }
}
#endif // !IRIS_PIN

// MAIN LOOP -- runs continuously after setup() ----------------------------
void loop() {
#if defined(IRIS_PIN) && (IRIS_PIN >= 0)   // Interactive iris
  uint16_t v = 512; //analogRead(IRIS_PIN);// Raw dial/photocell reading
#ifdef IRIS_PIN_FLIP
  v = 1023 - v;
#endif
  v = map(v, 0, 1023, IRIS_MIN, IRIS_MAX); // Scale to iris range
#ifdef IRIS_SMOOTH // Filter input (gradual motion)
  static uint16_t irisValue = (IRIS_MIN + IRIS_MAX) / 2;
  irisValue = ((irisValue * 15) + v) / 16;
  frame(irisValue);
#else  // Unfiltered (immediate motion)
  frame(v);
#endif // IRIS_SMOOTH
#else  // Autonomous iris scaling -- invoke recursive function
  newIris = random(IRIS_MIN, IRIS_MAX);
  split(oldIris, newIris, micros(), 10000000L, IRIS_MAX - IRIS_MIN);
  oldIris = newIris;
#endif // IRIS_PIN
//screenshotToConsole();
}

——————————————————————————————


M5Stack , Electronic Animated Eyes , TFT_eSPI , ESP866_uncannyEyes , macsbug , DAC, nois , ノイズ

Written by macsbug

5月 6, 2019 at 5:00 pm

カテゴリー: M5STACK

M5stack tetris with sound

with 3 comments

M5Stack TETRIS に BGM  を追加しました。          2019.05.04

DEMO MODE ( gif 画像にて 音はでません )

bitluni 氏より ULPSoundESP32 が Apr 7, 2019 に公開されました。
ESP32 の ULP ( Ultra Low Power Coprocessor ) を使用し BGM を出力します。
ULPの使用により メインプロセッサの負担無く スムーズに動作します。
スケッチは ULPSoundESP32 の ULPSoundMonoSamples を使用し
TETRIS に 組み込みました。

DEMO 又は GAME

を選択します。

OK で設定されます。

DEMO は

自動的に繰り返します。

BGMが流れる中

Volume の調整。

レベルは 0 – 63。

Down / Up で調整。

0 は 無音です。

OK で設定されます。


.
M5Stack と TETRIS:
1. ESP32 の TETRIS は MhageGH氏により作成されました。
_ 2017.06.20:esp32_ILI9328_Tetris by MhageGH:MhageGH 氏に感謝致します。
2. Tuan PM氏によりM5Stack用のTETRIS が動作。2017.12.02.ソースは未公開。
3. M5Stack 用に変換しました。
_ TETRIS with M5STACK : 2018.01.20 Transplant by macsbug
_ M5Stack発売時に GAME なき為 先人のスケッチを幾つか移植させて頂きました。
4. M5Stack Github に載せていただきました。
_ Tetris:2018.01.30:M5Stack Team 及び Jimmy氏に感謝致します。
5. shikarunochi 氏により機能追加が行われました。
_ スコアー と 次のブロック表示が追加されました。
_ M5Stack:テトリス改造:2018.05.03:しかるのち氏に感謝致します。
6. TETRIS_v2 : M5stack tetris with sound : 2019.05.03 Add sound by macsbug
_ bitluni 氏の ULPSoundMonoSamples を追加しました。bitluni 氏に感謝致します。
_ 機能が追加された しかるのち氏のスケッチを使用しました。
_ DEMO と GAME の選択、 Volume調整 (0-63, 0=無音)を 追加。
_ 操作は M5Stack の A,B,C ボタンとしました。Joystick UNIT等は各自で追加願います。


.
作成:


1. Arduino IDE 開発環境 と M5Stack ライブラリー を準備します。
2. TETRIS_v2.ino をコピーして「TETRIS_v2」フォルダーを作成します。
_ TETRIS_v2 は ブログの下に記載してあります。
_ SD_update 対応になっています。
3. tetris.h:サウンド データー。
_ ULPSoundESP32 の ULPSoundMonoSamples
_ tetris.h を DL し TETRIS_v2 フォルダーに入れます。
_ 注: DLは URL を前とかに たどり DL できる場所で 入手します。
4. Free_Fonts.h:フォントデーター。
_ M5Stack/examples/Advanced/Display/All_Free_Fonts_Demo/
_ Free_Fonts.h を DL し TETRIS_v2 フォルダーに入れます。
5. tetris_img.ctetris_img.c:TETRIS 背景画像。
_ tetris_img.c を DL し TETRIS_v2 フォルダーに入れます。
メモ:Github を使用していない(解らない)為、ブログだけのリストになります。


.
参考:
hackster.io:This Sound Driver Runs on the ESP32’s ULP Coprocessor
_ サウンドドライバはESP32のULPコプロセッサで動作します:
_  Bitluni氏は、コプロセッサ上で動作するサウンドドライバを開発し、
_  メインプロセッサの負担を取り除き、
_  より多くのリソースを必要とするタスクを自由に処理することができました。
bitluni:ULPSoundESP32:今回のスケッチがあります。
espressif:espressif/esp-iot-solution:ULPコプロセッサおよびアセンブリ環境設定の概要
espressif:esp32 ulp programming:esp32 ulpプログラミング
docs.espressif:ULP coprocessor instruction set:ULPコプロセッサ命令セット
{CODINGFIELD}:ULP (ESP32) : a simple example:ULP(ESP32):簡単な例。
Vida de Silício:Ultra Low Power coprocessor (ULP) – ESP32:ULPのプログラム方法。
Rabbit Note:ESP32 の ULP コプロセッサを使って超低消費電力 I2C 通信
mはげ(MhageGH):【ゆっくり解説】パラレル処理【電子工作】:ULP パラレル処理の説明。
mはげ(MhageGH):【ゆっくり解説】ディジタル音声の使い方 ~音の基礎からAIまで~:I2S の説明。
MhageGH/M5Stack_Tetris:2019.05.05 追記。
_ mはげ氏による TetrisにBGM追加:M5StackのExamplesのTetrisにBGMを付けました。
_  サンプリングレート8kHz、8bitのwaveファイルをコードに埋め込み、I2S+DACで鳴らしています。
MhageGH/M5Stack_Tetris:2019.05.09 追記。
_ パラレル化更新:LCD表示のSPIをポーリングからDMA割り込みトランザクション。


.
感想:
_ 画像表示:bitluni氏のお陰で 音を出力でき 画像表示の遅れも無く動作しています。
_ ULPの応用:ULPSoundESP32 の ULPSoundMonoSamples を参考に
_       Sound を用意すると 他の Sound も自在に出力できますね。
_ 音:M5Stack の アンプとスピーカーは ノイズが問題になる場合があります。
_   ノイズの要素は 1つでなく幾つかあります。( 詳細は省略 )
_   音量は DAC の内部を操作し無音か出力かにしています。
_    無音:dac_output_disable(DAC_CHANNEL_1);
_    出力:dac_output_enable(DAC_CHANNEL_1);
_
_ メモ:約5KHz+脈を打つノイズの原因は M-BUS DAC 25ピンの配置。
_    複雑な画像時に MOSI信号(23)の輻射波が GPIO-25(DAC OUT)からAMPへ入る。
_    これは PROTO Module の GPIO-25 PIN と 配線の 2つの影響を受けます。
_    NS4150B-4 pin (INN) – C44 の PGND は M5Stack BASIC と GRAY で Open。
_    ただし GRAY(2017.11 )でGNDもあり 基板の日付を述べる必要がある。
_    M5Stack FIRE(2018.2A) は GNDで C44(100nf) は 未装着です。
_    これに対し Jimmy氏は “we remove this cap for good sound.” と答えています。
_    結果 pin 4 (INN) は Openで良く GNDすると条件によりノイズが大きくなります。
_    
_    対策:DAC 25 ピン:削除する。
_       M-BUSの設計:MOSI ( 23 ) と DAC ( 25 ) は 距離を空ける。
_       PROTO基板の設計:DAC ( 25 ) は ピンを立てず 配線しない事。
_       ノイズ防止PROTO基板:25ピンの無い基板をスタックして使用する。


.
リスト:

// TETRIS_v2.ino : M5stack tetris with sound : 2019.05.03 Add sound by macsbug

//========================================================================
// TETRIS with M5STACK : 
//            : 2017.06.20 esp32_ILI9328_Tetris by MhageGH
//            : 2018.01.20 Transplant by macsbug
//            : 2018.05.03 Modified by @shiakrunochi
// TETRIS_v2  : M5stack tetris with sound
//            : 2019.05.03 Add sound by macsbug
//            : https://macsbug.wordpress.com/2019/05/04/m5stack-tetris-with-sound/
// 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
// Github     : (reference) https://github.com/MhageGH/esp32_ILI9328_Tetris
// Github     : (Original) https://macsbug.wordpress.com/2018/01/20/tetris-with-m5stack/
// Github     : (M5Stack) https://github.com/m5stack/M5Stack/tree/master/examples/Games/Tetris
// Github     : (revision) http://shikarunochi.matrix.jp/?p=2296
// How to     : How to make tetris https://www.nicovideo.jp/watch/sm17983957
// Sound      : Sound processing with ULP co-processor
// Github     : bitluni/ULPSoundESP32:https://github.com/bitluni/ULPSoundESP32
// tetris.h   : Sound : 2.4MB
//========================================================================
#include <M5Stack.h>                                       // M5STACK
#include "M5StackUpdater.h"                                //
#include "Free_Fonts.h"                                    // Font 12
uint16_t BlockImage[8][12][12];                            // Block
uint16_t backBuffer[240][120];                             // GAME AREA
uint16_t nextBlockBuffer[60][48];                          // NEXT BLOCK 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 nextBlockType = -1;
long score = 0;
Block nextBlock;
int rot, fall_cnt = 0;
bool started = false, gameover = false;
boolean but_A = false, but_LEFT = false, but_RIGHT = false, but_DOWN = 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}
};
extern uint8_t tetris_img[];

// - ULP -
#include <esp32/ulp.h>
#include <driver/rtc_io.h>
#include <driver/dac.h>
#include <soc/rtc.h>
#include <math.h>
#include "tetris.h"
String mode_ = "GAME";
unsigned long samplingRate = 44100;
const int opcodeCount = 17;
const int dacTableStart1 = 2048 - 512;
const int dacTableStart2 = dacTableStart1 - 512;
const int totalSampleWords = 2048 - 512 - (opcodeCount + 1);
const int totalSamples = totalSampleWords * 2;
const int indexAddress = opcodeCount;
const int bufferStart = indexAddress + 1;
int v = 24;// volume

void startULPSound(){
  //calculate the actual ULP clock
  unsigned long rtc_8md256_period = rtc_clk_cal(RTC_CAL_8MD256, 1000);
  unsigned long rtc_fast_freq_hz = 
      1000000ULL * (1 << RTC_CLK_CAL_FRACT) * 256 / rtc_8md256_period;
  dac_output_enable(DAC_CHANNEL_1);      //initialize DACs
  dac_output_enable(DAC_CHANNEL_2);
  dac_output_voltage(DAC_CHANNEL_1, 128);
  dac_output_voltage(DAC_CHANNEL_2, 128);
  int retAddress1 = 13;
  int loopCycles  = 84;
  Serial.print("Real RTC clock: ");
  Serial.println(rtc_fast_freq_hz);
  int dt = (rtc_fast_freq_hz / samplingRate) - loopCycles;
  if(dt < 0)
    Serial.println("Sampling rate too high"); 
    Serial.print("dt: ");Serial.println(dt);
const ulp_insn_t mono[] = {
  I_MOVI(R3, 0),             //reset offset register
  I_DELAY(dt),               //delay to get the right sampling rate // 6 + dt
  I_MOVI(R0, 0),             //reset sample index         // 6
  I_ST(R0, R3, indexAddress),//write the index back to memory for the main cpu // 8
  I_RSHI(R2, R0, 1),         //divide index by two since we store two samples in each dword // 6
  I_LD(R1, R2, bufferStart), //load the samples           // 8
  I_ANDI(R2, R0, 1),         //get if odd or even sample  // 6
  I_LSHI(R2, R2, 3),         //multiply by 8              // 6
  I_RSHR(R1, R1, R2),        //shift the bits to have the right sample in the lower 8 bits // 6
  I_ANDI(R1, R1, 255),       //mask the lower 8 bits      // 6
  I_LSHI(R1, R1, 1),         //multiply by 2              // 6
  I_ADDI(R1, R1, dacTableStart1),//add start position     // 6
  I_BXR(R1),                 //jump to the dac opcode     // 4
                             //here we get back from writing a sample
  I_ADDI(R0, R0, 1),         //increment the sample index // 6
  I_BGE(-13, totalSamples),  //if reached end of the buffer, jump relative to index reset // 4
  //wait to get the right sample rate (2 cycles more to compensate the index reset)
  I_DELAY((unsigned int)dt + 2), // 8 + dt
  I_BXI(3)};                 //if not, jump absolute to where index is written to memory // 4
// write io and jump back another 12 + 4

  size_t load_addr = 0;
  size_t size = sizeof(mono)/sizeof(ulp_insn_t);
  ulp_process_macros_and_load(load_addr, mono, &size);
//  this is how to get the opcodes
//  for(int i = 0; i < size; i++)
//    Serial.println(RTC_SLOW_MEM[i], HEX);
  for(int i = 0; i < 256; i++){   //create DAC opcode tables
    RTC_SLOW_MEM[dacTableStart1 + i * 2] = 0x1D4C0121 | (i << 10); //dac0
    RTC_SLOW_MEM[dacTableStart1 + 1 + i * 2] = 0x80000000 + retAddress1 * 4;
  }
  for(int i = 0; i < totalSampleWords; i++) //set all samples to 128 (silence) RTC_SLOW_MEM[bufferStart + i] = 0x8080; RTC_SLOW_MEM[indexAddress] = 0; //start ulp_run(0); while(RTC_SLOW_MEM[indexAddress] == 0) delay(1); } long currentSample = 0; unsigned char nextSample(){ static long pos = 0; if(pos >= sampleCount)
    pos = 0;
  return (unsigned char)((int)(samples[pos++] + 128)/v); // add v
}

int lastFilledWord = 0;

void fillSamples(){
  int currentSample = RTC_SLOW_MEM[indexAddress] & 0xffff;
  int currentWord = currentSample >> 1;
  while(lastFilledWord != currentWord){
    unsigned int w = nextSample();
    w |= nextSample() << 8; RTC_SLOW_MEM[bufferStart + lastFilledWord] = w; lastFilledWord++; if(lastFilledWord == totalSampleWords) lastFilledWord = 0; } } // - ULP - void mode_set(){ M5.Lcd.setFreeFont(FM12); M5.lcd.setCursor(68,85);M5.lcd.setTextColor(WHITE); M5.lcd.printf("Demo or Game"); M5.lcd.setCursor(68,125);M5.lcd.printf("Please select"); M5.lcd.setCursor(37,232);M5.lcd.setTextColor(WHITE); M5.lcd.printf("DEMO"); M5.lcd.drawRect(35,212,60,28, BLACK); M5.lcd.setCursor(146,232);M5.lcd.setTextColor(GREEN); M5.lcd.printf("OK"); M5.lcd.drawRect(130,212,60,28,GREEN); M5.lcd.setCursor(228,232);M5.lcd.setTextColor(WHITE); M5.lcd.printf("GAME"); M5.lcd.drawRect(225,212,60,28,BLACK); b: M5.update(); fillSamples(); if (M5.BtnA.isPressed()) { mode_ = "DEMO";} if (M5.BtnC.wasPressed()){ mode_ = "GAME";} if (M5.BtnB.wasPressed()){ M5.lcd.setCursor(135,165);M5.lcd.setTextColor(BLACK); M5.lcd.print(" "); M5.lcd.setCursor(135,165);M5.lcd.setTextColor(RED); M5.lcd.print(mode_); delay(250);return;} M5.lcd.setCursor(135,165);M5.lcd.setTextColor(WHITE); M5.lcd.print(mode_); delay(25); M5.lcd.setCursor(135,165);M5.lcd.setTextColor(BLACK); M5.lcd.print(mode_); goto b; } void volume_set(){ M5.Lcd.setFreeFont(FM12); M5.lcd.setTextColor(WHITE); M5.lcd.setCursor(68,85); M5.lcd.printf("Adjust volume"); M5.lcd.setCursor(120,125);M5.lcd.printf("0 - 63"); M5.lcd.setCursor(37,232); M5.lcd.printf("Down"); M5.lcd.drawRect(35,212,60,28,BLACK); M5.lcd.setCursor(146,232);M5.lcd.setTextColor(GREEN); M5.lcd.printf("OK"); M5.lcd.drawRect(130,212,60,28,GREEN); M5.lcd.setCursor(242,232);M5.lcd.setTextColor(WHITE); M5.lcd.printf("Up"); M5.lcd.drawRect(225,212,60,28,BLACK); b: M5.update(); fillSamples(); if (M5.BtnA.isPressed()) {if(v> 0 && v<64){v++;};} // Down if (M5.BtnC.wasPressed()){if(v> 1 && v<65){v--;};} // Up
  if (M5.BtnB.wasPressed()){M5.lcd.setCursor(50,30); // OK 
      M5.lcd.setTextColor(WHITE);M5.lcd.printf("Volume complete");
      if (v == 64){dac_output_disable(DAC_CHANNEL_1);}// Volume OFF
      ;return;}
  M5.lcd.setCursor(145,165);M5.lcd.setTextColor(WHITE);
  M5.lcd.print(64-v); delay(25);
  M5.lcd.setCursor(145,165);M5.lcd.setTextColor(BLACK);
  M5.lcd.print(64-v);
  goto b;
}

void restart_() {
  score = 0;                                    // Clear Score
  M5.Lcd.drawJpg(tetris_img, 34215); // Load background from file data
  PutStartPos();                                // Start Position
  for (int i=0;i<Width;++i){for(int j=0;j<Height;++j){
    if (screen[i][j] != 0) screen[i][j] = 0;}   // Clear data
  }
  gameover = false;
  if (v != 64){dac_output_enable(DAC_CHANNEL_1);// Sound ON
               startULPSound();}                // ULP co-processor
}

void gameover_display(){
  dac_output_disable(DAC_CHANNEL_1);            // Sound OFF
  M5.Lcd.setFreeFont(FM24); 
  M5.lcd.setCursor(40,130);M5.lcd.setTextColor(WHITE);
  M5.lcd.printf("GAME OVER");delay(6000);
  M5.Lcd.setFreeFont(FM12); 
}

//========================================================================
void setup(void) {
  M5.begin();                   // M5STACK INITIALIZE
  Wire.begin();if(digitalRead(39)==0){updateFromFS(SD);ESP.restart();}//SD UPdate
  M5.Lcd.setBrightness(200);    // BRIGHTNESS = MAX 255
  M5.Lcd.fillScreen(BLACK);     // CLEAR SCREEN
  M5.Lcd.setRotation(1);        // 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
  M5.Lcd.drawJpg(tetris_img, 34215); // Load background from file data
  startULPSound();                           // ULP co-processor
  mode_set();                                // demo or manual game
  M5.Lcd.drawJpg(tetris_img, 34215); // Load background from file data
  volume_set();                              // volume setup
  M5.Lcd.drawJpg(tetris_img, 34215); // Load background from file data
  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
  DrawNextBlock();
}

//========================================================================
void loop() {
  fillSamples();
  if (gameover) restart_();
  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.pushImage(100, 0, 120, 240, (uint16_t *)backBuffer);
}
void DrawNextBlock() {  
  for(int x = 0; x < 48;x++) {
    for(int y = 0; y < 60;y++){
      nextBlockBuffer[y][x]=0;
    }
  }
  nextBlock = blocks[nextBlockType];
  int offset = 6 + 12;
  
  for (int i = 0; i < 4; ++i) {
      for (int k = 0; k < Length; ++k) for (int l = 0; l < Length; ++l){
        nextBlockBuffer[60 - (nextBlock.square[0][i].X * Length + l + 
        offset)][nextBlock.square[0][i].Y * Length + k + offset] = 
        BlockImage[nextBlockType + 1][k][l];
      }
  }
  M5.Lcd.pushImage(26, 100, 48, 60, (uint16_t *)nextBlockBuffer);
  M5.Lcd.fillRect(2, 76, 96, 19, BLACK);
  M5.Lcd.setCursor(0, 92);//M5.Lcd.setCursor(10, 78);
  M5.Lcd.printf("%7d",score);
}
//========================================================================
void PutStartPos() {
  pos.X = 4; pos.Y = 1;
  if (nextBlockType == -1){
    block = blocks[random(7)];
  }else{
    block = blocks[nextBlockType];
  }
  nextBlockType = 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; gameover_display(); } //======================================================================== void ClearKeys() { but_A=false; but_LEFT=false; but_RIGHT=false; but_DOWN=false;} //======================================================================== bool KeyPadLoop(){ if ( mode_ == "GAME" ){ 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;} if (Serial.available()) { char r = Serial.read(); while(Serial.read() != -1); if (r == 'z') { ClearKeys(); but_A=true; } //else but_A=false; if (r == '2') { ClearKeys(); but_DOWN=true; } //else but_DOWN=false; if (r == '4') { ClearKeys(); but_LEFT=true; } //else but_LEFT=false; if (r == '6') { ClearKeys(); but_RIGHT=true; } //else but_RIGHT=false; return true; } return false; } // RANDOM int rnd_key = random(3*8); if (rnd_key == 0){ClearKeys();but_LEFT =true;return true;} if (rnd_key == 1){ClearKeys();but_RIGHT=true;return true;} if (rnd_key == 2){ClearKeys();but_A =true;return true;} } //======================================================================== 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_DOWN) { but_DOWN = false; pnext_pos->Y += 1;}
    else if (but_A) { but_A = false;
      *pnext_rot = (*pnext_rot + block.numRotate - 1)%block.numRotate; 
    }
  }
}
//========================================================================
void DeleteLine() {
  int deleteCount = 0;
  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]; } } deleteCount++; } } switch (deleteCount){ case 1:score = score + 40;break; case 2:score = score + 100;break; case 3:score = score + 300;break; case 4:score = score + 1200;break; } if(score > 9999999){score = 9999999;}
}
//========================================================================
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();DrawNextBlock();
    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
  } 
}
//========================================================================

**************************************************************************************************************
M5Stack , macsbug , M5stack tetris with sound , tetris , ESP32 , ULP , テトリス ,


Written by macsbug

5月 4, 2019 at 12:00 pm

カテゴリー: M5STACK