Archive for 4月 2018
M5STACK WiFiAnalyzer
ESP8266WiFiAnalyzer を M5STACK に移植しました。 2018.04.30
ORIGINALは moononournation/ESP8266WiFiAnalyzer です。
instructables : 陳亮氏の記事:ESP8266WiFiAnalyzer
moononournation、陳亮氏 に感謝致します。
オリジナルのディスプレーは 2.4 inch 320×240 です。
M5STACK も 320×200 ですが 2.0 inch の為 文字が小さく見づらいですが 良しとします。
Free_Fonts.h は M5STACK Library の中にあります。
SD にアプリを入れて起動する為に SD Update のスケッチが入っています。
スケッチ:
// ESP8266 WiFi Analyzer // Revise from ESP8266WiFi WiFiScan example. // Require ESP8266 board support, Adafruit GFX and ILI9341 library. // http://www.instructables.com/id/ESP8266-WiFi-Analyzer/ // moononournation/ESP8266WiFiAnalyzer // https://github.com/moononournation/ESP8266WiFiAnalyzer // Github:https://macsbug.wordpress.com/2018/04/30/m5stack-wifianalyzer/ #include "WiFi.h" #include <M5Stack.h> #include "M5StackUpdater.h" // SD Update #include "Free_Fonts.h" #define RSSI_CEILING -40 // Graph constant #define RSSI_FLOOR -100 #define GRAPH_BASELINE 222 #define GRAPH_HEIGHT 188 #define CHANNEL_WIDTH 20 #define NEAR_CHANNEL_RSSI_ALLOW -70 // Channel color mapping from channel 1 to 14 uint16_t channel_color[] = {RED,ORANGE,YELLOW,GREEN,CYAN, MAGENTA,RED,ORANGE,YELLOW,GREEN,CYAN,MAGENTA,RED,ORANGE }; void setup() { Serial.begin(115200);delay(500); M5.begin(); Wire.begin(); if(digitalRead(BUTTON_A_PIN) == 0){ // SD Update updateFromFS(SD); ESP.restart(); // SD Update } // SD Update M5.Lcd.setTextColor(TFT_BLACK, TFT_WHITE); M5.Lcd.fillScreen(TFT_WHITE); //M5.Lcd.setFreeFont(FSSB12); // FreeMono18pt7b //M5.Lcd.setFreeFont(FSS9); // FreeSans9pt7b M5.Lcd.fillScreen(BLUE); // init banner //M5.Lcd.setTextSize(1); M5.Lcd.setTextColor(WHITE, RED); M5.Lcd.setCursor(0, 0); M5.Lcd.print(" M5"); M5.Lcd.setTextColor(WHITE, ORANGE); M5.Lcd.print("STACK "); M5.Lcd.setTextColor(WHITE, GREEN); M5.Lcd.print(" WiFi "); M5.Lcd.setTextColor(WHITE, BLUE); M5.Lcd.print(" Analyzer"); WiFi.mode(WIFI_STA); WiFi.disconnect(); delay(100); } void loop() { uint8_t ap_count[] = {0,0,0,0,0,0, 0, 0, 0, 0, 0, 0, 0, 0}; int32_t max_rssi[] = {-100,-100,-100,-100,-100,-100,-100, -100,-100,-100,-100,-100,-100,-100}; int n = WiFi.scanNetworks(); M5.Lcd.fillRect(0, 16, 320, 224, BLACK); // clear old graph M5.Lcd.setTextSize(1); if (n == 0) { M5.Lcd.setTextColor(BLACK); M5.Lcd.setCursor(0, 16); M5.Lcd.println("no networks found"); } else { for (int i = 0; i < n; i++) { // plot found WiFi info int32_t channel = WiFi.channel(i); int32_t rssi = WiFi.RSSI(i); uint16_t color = channel_color[channel - 1]; int height = constrain(map(rssi, RSSI_FLOOR, RSSI_CEILING, 1, GRAPH_HEIGHT), 1, GRAPH_HEIGHT); ap_count[channel - 1]++; // channel stat if (rssi > max_rssi[channel - 1]) { max_rssi[channel - 1] = rssi; } M5.Lcd.drawLine(channel * CHANNEL_WIDTH, GRAPH_BASELINE - height, (channel - 1) * CHANNEL_WIDTH, GRAPH_BASELINE + 1, color); M5.Lcd.drawLine(channel * CHANNEL_WIDTH, GRAPH_BASELINE - height, (channel + 1) * CHANNEL_WIDTH, GRAPH_BASELINE + 1, color); M5.Lcd.setTextColor(color); // SSID, signal strengh and if not encrypted M5.Lcd.setCursor((channel - 1) * CHANNEL_WIDTH, GRAPH_BASELINE - 10 - height); M5.Lcd.print(WiFi.SSID(i)); M5.Lcd.print('('); M5.Lcd.print(rssi); M5.Lcd.print(')'); //if (WiFi.encryptionType(i) == ENC_TYPE_NONE) { // M5.Lcd.print('*'); //} delay(10); } } M5.Lcd.setTextColor(WHITE); // print WiFi stat M5.Lcd.setCursor(0, 16); M5.Lcd.print(n); M5.Lcd.print(" networks found, suggested channels: "); bool listed_first_channel = false; for (int i = 1; i <= 11; i++) { // channels 12-14 may not available // check previous channel signal strengh if ((i == 1) || (max_rssi[i - 2] < NEAR_CHANNEL_RSSI_ALLOW)) { // check next channel signal strengh if ((i == sizeof(channel_color)) || (max_rssi[i] < NEAR_CHANNEL_RSSI_ALLOW)) { if (ap_count[i - 1] == 0) { // check no AP exists in same channel if (!listed_first_channel) { listed_first_channel = true; } else {M5.Lcd.print(", ");} M5.Lcd.print(i); } } } } // draw graph base axle M5.Lcd.drawFastHLine(0, GRAPH_BASELINE, 320, WHITE); for (int i = 1; i <= 14; i++) { M5.Lcd.setTextColor(channel_color[i - 1]); M5.Lcd.setCursor((i * CHANNEL_WIDTH) - ((i < 10)?3:6), GRAPH_BASELINE + 2); M5.Lcd.print(i); if (ap_count[i - 1] > 0) { M5.Lcd.setCursor((i * CHANNEL_WIDTH) - ((ap_count[i - 1] < 10)?9:12), GRAPH_BASELINE + 11); M5.Lcd.print('('); M5.Lcd.print(ap_count[i - 1]); M5.Lcd.print(')'); } } delay(5000); // Wait a bit before scanning again }
TFT_eSPI Library supports M5STACK
TFT_eSPI Library は M5STACK の Display をサポートしました。 2018.04.17
TFT Display と Library:
_ TFT Display の Library は 幾つかありますが その中で
_ TFT_eSPI Library は 速度、機能、多種対応 で ナンバーワンとも言えます。
_ M5STACK(Custom ILI9341), Raspberry Pi用TFT(ILI9486) を含めて8種の
_ SPI TFT や Parallel interface TFT, ePaper の多種対応、高速化、スプライト、
_ アンチエイリアスフォント機能が実施されています。
_ 詳細は eSPI Library に記載されており 読み応えがあります。
主題の前に M5STACKの解説を致します。( 少し長いです )
M5STACK の TFT Display:
TFT Driver は 320×240 ILI9341 です。ただし「Custom ILI9341」です。
m5stack/M5Stack_TFT_ILI9341 には 「Custom ILI9341」 と書かれています。
低価格で販売されている 2.4 inch TFT SPI 320×240 ILI9341 とは 異なる点が
あります。MISOの配線は無し。 2 inch で 人によっては小さいです。
2 inch M5STACK Display | 2.4 inch 320×240 TFT Display Module |
![]() |
![]() |
1. 色の配置:BGR
_ ハード的に異なります。RGB ( ILI9341 Data Sheet ) が BGR に
_ なっています。つまり R(赤) と B(青) が 逆になっています。
_ これは RGB Color Codes Chart が使用できない事を示しています。
_ bin タイプの画像やデーターは互換性が無くなります。
2. 表示の向き:鏡面反転表示
_ 鏡面反転表示 (左右反転)します。これもハードの構成による物と思われます。
M5STACK の現象:
1. M5STACK の Display Library で 外部に接続した市販の TFT を表示すると
_ 色ずれと鏡面反転し正常に表示しません。
2. 従来の TFT Library で M5STACK の TFT を表示すると同様に色ずれと
_ 鏡面反転し正常に表示しません。
理由は 解りませんが 私はプロテクションと判断しています。
_ 昔 aitendo の TFT で この方法を使用した TFT Display があり 不便でした。
M5STACK の 現状と対策:
_ TFT Driver IC に対し カスタムの配線は 従来の TFT Library を使用した
_ スケッチの活用が出来なくなります。
_ スケッチの移植が困難になり 先人の資産を有効活用できません。
_ 従来の TFT とは異なる設計は 大変不便です。
_ その中で、 Tetris, spaceShooter, Pacman 等の移植を実施しました。
_ 例として Tetris は これまでの画像を使用できず 新たにM5STACK用の
_ 画像を作りました。調査と作成に大変時間を要しました。
![]() |
![]() |
![]() |
_ SOKOBAN を移植された robo8080氏も 左右反転に気がつき スケッチの
_ サブプログラムで処理をされ 動作しています。
_ robo8080氏の対応策は素晴らしく、感謝致します。
robo8080氏のプログラム:左右反転。
void fillImage(void *image, int x, int y, int w, int h) { uint16_t* p = (uint16_t*)image; M5.Lcd.startWrite(); // Requires start/end transaction now M5.Lcd.setAddrWindow(x, y, x+w-1, y+h-1); for ( int i = 0; i < h; i++) { for (int j = 0; j < w; j++) { M5.Lcd.writePixel(p[i*w+j]); } } M5.Lcd.endWrite(); // End last TFT transaction }
![]() |
robo8080氏のプログラムを 基に
FACES version of M5Stack_sokoban を作らせて頂きました。 |
色の対策: RGB -> GBR
_ Kongduino / M5-Colors-Demo:デモは RGBを BGR に変換しています。
_ Kongduino氏は Colours.h で RGB の定義をし void demoColour で
_ GBR に変換した 5-Colors-Demo を作成しています。
_ M5CheatSheet にも Color の 定義が記載されています。
例:Tetris:make_block( 3, 0xFF00); // D__,DDD BLUE
_ Colours.h:TFT_BLUE 0x001F /* 0, 0, 255 */ : Color の定義
_ void demoColour(char *colourName, uint16_t clr) {
_ uint16_t reverse = clr ^ 0xFFFF; // Color の反転
M5STACK Display Library の詳細:
TFT 左右表示:アドレスは 0x36 で 名称は ILI9341_MADCTL です。
_ M5 Display.h に定義されています。
_ Memory Access Control : MADCTL : 左右表示
_ M5 Display.h:#define ILI9341_MADCTL 0x36
設定:M5 Display.cpp で 以下の様に 0x08 です。
_ 本来は 0x48 で行われています。
writecommand(ILI9341_MADCTL); // Memory Access Control writedata(0x08); // writedata(0x48);
追記:2018.05.02
M5STACK Community :メモ:transparency 。
_ M5STACK の TFT LCD の呼び出しにはアルファ値
_ (透明性:transparent)がありません。
_ 透明色を渡すには TFT_eSPI Library のスプライト関数を使用します。
_ 例:pushSprite(int32_t x, int32_t y, uint16_t transparent);
.
=== [ 主題 ] SUBJECT === eSPI Library supports M5STACK =========
eSPI Library の M5STACK対応:
M5STACK 販売当初、eSPI Library は M5 に正しく表示できませんでしたが
2018.04 (Update 2nd April 2018) にて M5STACK 対応が実施され
表示が可能になりました。 Bodmer氏に感謝致します。
対応ディスプレー:8種類のTFT と ePaper 対応。
_ ILI9341, ILI9163, ST7735, S6D02A1, ILI9481, ILI9488,ILI9486, HX8357
_ ePaper display
eSPI Library と 多種TFTの記述方法:User_Setups
TFTの設定は 通常 スケッチの宣言部と setup で行いますが eSPI Library は
User_Setups に記載された内容で 行なわれます。
TFT_eSPI/User_Setups/Setup12_M5Stack.h
TFTの宣言と設定を User_Setups に集約する事で便利になっています。
既に 対応する TFT の User_Setups も用意されています。
多種類の TFT 対応が可能である事と プログラムが簡単です。
これにより 従来の eSPI のスケッチがそのまま動作します。
SPI FREQUENCY は 40MHz で 動作します。
例:User_Setups:M5STACK の 設定:
#define ILI9341_DRIVER #define M5STACK #define TFT_MISO -1 #define TFT_MOSI 23 #define TFT_SCLK 18 #define TFT_CS 14 // Chip select control pin #define TFT_DC 27 // Data Command control pin #define TFT_RST 33 // Reset pin (could connect to Arduino RESET pin) #define TFT_BL 32 // LED back-light #define SPI_FREQUENCY 40000000
鏡面反転表示 (左右反転) から 正しい表示:
サンプル:多数のサンプルがあります。
_ 具体的な画像は TFT display for Raspberry pi with ESP32 を参照ください。
注目する機能:
1. Sprite :スプライト
_ 複雑なグラフィックにて フリッカーの無い表示が可能です。
_ スクロール機能のサンプルもあり参考になります。
2. ESP32_PARALLEL
_ User_Setups の中に #define ESP32_PARALLEL の設定があります。
_ Control pin 3つ、R/W 2つ、DATA 8つ 計 13本で パラレル操作が可能です。
_ TFT は ILI9481 and ILI9341 で PARALLEL 方式の TFT を入手できますと
_ ESP32 で PARALLEL 操作が可能になり 高速表示を期待する事が出来ます。
_ 対応するボードは 8 bit parallel interface TFT ILI9341 が販売されています。
3. M5STACK 内蔵の Display や M5STACKに外部接続した Display も表示が
_ 可能です。
M5STACK TFT Driver ILI9341:
更に詳しい TFT Driver の中味は TFT_Driver Folder の中に
_ ILI9341_Defines.h, ILI9341_init.h, ILI9341_Rotation.h があります。
ILI9341_init.h には M5STACK用の設定が宣言されています。
#ifdef M5STACK writedata(0xA8); // Rotation 0 (portrait mode) #else writedata(0x48); // Rotation 0 (portrait mode) #endif #ifdef M5STACK // Turn on the back-light LED digitalWrite(TFT_BL, HIGH); pinMode(TFT_BL, OUTPUT); #endif
ILI9341_Rotation.h にも M5STACK の設定が多数宣言されています。
#ifdef M5STACK writedata(TFT_MAD_MY | TFT_MAD_MV | TFT_MAD_BGR); #else writedata(TFT_MAD_MX | TFT_MAD_BGR); #endif
この内容を見ると eSPI Library の著者である Bodmer氏は M5STACK を解析し
丁寧に作られており 驚くばかりです。
速度:
M5STACK での M5 Display Library と eSPI Library の速度を比較しました。
速度は ほぼ同じです。恐らく内部の CODE は 同じ構成だと想像します。
eSPI の bug:バグと修正方法。
3.5 ” 480×320 SPI TFT ILI9486 RPi touch Display を使用する場合、ここに
示されたVersion では表示しません。ブログにコメントされた ohno氏 が
発見し解析により解決方法が判明致しました。
詳細は TFT display for Raspberry pi with ESP32 を参照ください。
ohno氏 に感謝致します。
変更内容:以下の eSPI.cpp を eSPI.h の先頭に移動。
#ifdef RPI_ILI9486_DRIVER #define CMD_BITS 16-1 #else #define CMD_BITS 8-1 #endif
他、Parallel interface TFT に於いて TFT_CS が出力されない不具合があり、
eSPI.h の TFT_CS の変更が必要です。
参考:
mgo-tec電子工作:mgo-tec氏 が解析され RGB -> BGR の詳細が記載されています。
_ M5stack(ESP32搭載)の液晶ディスプレイ ILI9341 をちょっと解明してみた
_ M5STACK発売当時からの疑問が 同様である事が解りました。
_ 詳細な分析と解説をありがとうございました。mgo-tec 氏に感謝致します。
TFT display for Raspberry pi with ESP32:ラズベリーパイ用 3.5 inch 480×320
Mini Grafx Library : Daniel Eichhorn:高度な命令がある TFT Library
Arduino Library List:ライブラリー集
instructable:ARDUINO TFT DISPLAY AND FONT LIBRARY:Arduino by Bodmer
感想:
eSPI Library ( version 0.20.10 ) 2018.04 Version:
_ M5STACK で使用できなく困っていましたが 使用できる様になり便利に
_ なりました。M5STACK や e-Paper に対する 対応をされ Bodmer氏に
_ 感謝致します。
eSPI Library:
_ rev : 0.20.10
_ rev : 0.20.13 : 2013.04.21 : TFT_DC , TFT_CS
_ rev : 0.20.14 : 2013.04.24 : ePaper, SD, drawXBitmap, Fix111(not font)
_ Bug : TST_CS does not output in ESP32_PARALLEL : 2018.04.24
M5STACK のサンプル:
_ M5STACK発売後に M5の全サンプルを画像表示しようかと思いましたが
_ 既にある eSPI Library と同じである為 M5の紹介では表示しませんでした。
_ 時期的に eSPI Library の方が早く M5のサンプルは eSPI Library の
_ サンプルを使用した物と思われます。
.
Make a battery module for M5STACK
M5STACK の バッテリーモジュール を作りました。 2018.04.12
容量は 1050mAh, 1300mAh, 2600mAh です。
費用は 567円, 567円, 786円 です。
Li-IONバッテリーは 使用方法により火災の原因になりますので注意してください。
使用するLi-IONバッテリーに合わせた充電回路を設計する必要があります。
又 並列接続してはいけません。
1050 mAh ( 567円 ) | 1300 mAh ( 567円 ) |
![]() |
![]() |
2600 mAh ( 786円 ) 要注意! | Connector Side |
![]() |
![]() |
M5STACK OscilloScope へ 2600 mAh を装備。
重要な注意:
_ Li-IONバッテリーは 並列接続してはいけません。火災の原因になります。
_ 2600mA 並列接続は 単にレイアウトの件で表示しました。
_ 並列接続の場合は 適切な回路を搭載する事が必要です。
_ 今回、この適切な回路は掲示していません。
.
準備:
No | NOMEN | 販売店 | 価格 | Note |
1 | PROT Frame ( height 6.5 mm ) | 自作(31分) | 11円 | OBJET |
2 | PROT Frame ( height 15.0 mm ) | 自作(59分) | 19円 | OBJET |
3 | Rear Cover ( height 4.0 mm ) | 自作(31分) | 11円 | OBJET |
4 | 0.3mm厚基板 C Type 72x47mm | 秋月電子通商 | 120円 | 0.3mm厚 |
5 | BATTERY NP-70 3.7V 1050mA ( height 7 mm ) | 杉元ガレージ | 200円 | 秋葉原 |
6 | BATTERY NP-40 3.7V 1300mA ( height 9 mm ) | 杉元ガレージ | 200円 | 秋葉原 |
7 | ピンソケット 2×22 44P | 秋月電子通商 | 150円 | 秋葉原 |
8 | JST 1.25mm 2-Pin Wire and connector | ebay | 22円 | 20ea 425円 |
9 | M2 Hex Head Tapping Screw ( 4 ea ) | ebay | 28円 | |
10 | M3 Stainless Steel Hex Screws ( 2ea ) | ebay | 25円 | |
11 | ワイヤー等 | 在庫品 | 0円 | |
– | ————————————— | —————- | ——- | ———– |
– | 1050 mAh TOTAL | 567円 | ||
– | 1300 mAh TOTAL | 567円 | ||
– | 2600 mAh TOTAL | 786円 |
注意:電源は 極性に注意が必要です。接続前に必ずテスターで極性を確認します。
_ JST 1.25mm Battery Connector Pin は 通常 [ + RED ], [- BLACK ] Wire です。
_ M5STACK 本体の Baterry Connector は この逆の並びで 要注意です。
秋葉原の杉元ガレージ(日曜日)では NP-40 が沢山あり 10個購入して おまけで
1500円 + 2個でした。1個 125円です。2018.04.15
.
工作:
_ 参照:Make a case of M5STACK ( 2018.03.10 )
_ 参照:Make M5STACK’s PROT Module ( 2018.03.15 )
_ 参照:Make a case of M5STACK ESP32 Oscilloscope ( 2018.04.03 )
_ バッテリーの接続は JST 1.25mm 2-Pin で 行うと便利です。
_ 重要事項:配線後 及び 接続前に必ずテスターで極性を確認してください。
.
感想:
_ 販売品:M5STACK Store の Battery Module は 850mAh ( $16.90 )です。
_ 作ると M5STACK の設計の良さを体験できます。
_ 携帯:M5STACK を 携帯で使用する時は バッテリー接続が便利です。
_ 装備する フレームによっては M5STACK Store に無いサイズの物が必要に
_ なり 今回の様な 使用方法も便利かと思います。
_ 並列接続:Li-IONバッテリーは 並列接続してはいけません。火災の原因になります。
_ 理由は、リチウムイオン電池が破損し内部抵抗が小さくなる場合、
_ 並列で繋げた他の電池から電気が流れ込むことがあります。
_ この時に電流が大きくなるとその分、熱を持ちます。並列で繋がった
_ リチウムイオン電池は、1つが壊れると一気に壊れた電池に電流が流れ
_ 込み、触れないぐらいの熱をもったり、燃えたりする可能性があります。
_ 参考:瀧/TAKI’s Blog:
_ 「知っておくべきモバイルバッテリーの12の法則(改訂版:2014/12/23)」
_
_ 費用:秋葉原の中古屋にある 200円の Battery と 3D Printer の 材料代です。
_ メモ:オシロスコープは 1KHz 程度の性能ですから あまり期待しない事。
Make a case of M5STACK ESP32 Oscilloscope
M5STACK ESP32 Oscilloscope のケースを作りました。 2018.04.03
rev 1. 2018.08.01
rev 2. 2019.12.01 Prevent reboot every 5 seconds.
botofan calin氏 作成の「M5Stack-ESP32-Oscilloscope」が
2018年3月31日に公開されました。待ち望んだアプリの公開です。
botofan calin氏 に 感謝致します。
費用:1196円。大きな費用は BNC コネクター 4個で400円です。
Input は 2CH ( GPIO 35, 36) で Output は 2CH ( GPIO 2, 5 )です。
仕様:メニューは 以下の項目があり SELECT で 詳細な設定ができます。
- Run/Stop
- Channel 1 range : 1, 0.5, 0.2, 0.1V, 50mV
- Channel 2 range : 1, 0.5, 0.2, 0.1V, 50mV
- Channel 1 and 2 rate : 5, 10, 20ms/DIV
- Channel 1 mode (Normal, Inverted, Off)
- Channel 2 mode (Normal, Inverted, Off)
- Channel 1 Offset
- Channel 2 Offset
- Trigger Channel : 1 or 2
- Trigger mode Auto, Normal, Scan
- Trigger Level
- Trigger Edge: Rising (UP) or Falling (DN)
追記:2018.04.06 「M5Stack-ESP32-Oscilloscope」のアップデートがありました。
_ MENU が追加され 操作しやすくなりました。
_
使用例:左は INPUT。右下は OUTPUT です。
準備:
No | NOMEN | 販売店 | 価格 | Note |
1 | OscilloScope PROT Frame (高さ 15mm) | 自作(59分) | 19円 | OBJET |
1 | OscilloScope PROT Frame (高さ 15mm) | 自作(73分) | 11円 | uPrint |
1 | OscilloScope PROT Frame (高さ 15mm) | 自作(48分) | 0円 | Replicator |
2 | Battery PROT Frame (高さ 6.5mm) | 自作(31分) | 11円 | OBJET |
3 | Rear Cover PROT Frame (高さ 4mm) | 自作(42分) | 55円 | OBJET |
4 | 0.3mm厚基板 C Type 72x47mm | 秋月電子通商 | 120円 | 0.3mm厚 |
5 | 1.0mm厚基板 C Type 72x47mm | 秋月電子通商 | 120円 | 1.0mm厚 |
6 | ピンヘッダ (オス) 2×30 (60P) | 秋月電子通商 | 25円 | |
7 | ロープロファイルピンソケット 2×15, 30P | 秋月電子通商 | 40円 | |
8 | ピンソケット 2×20 40P | 秋月電子通商 | 150円 | |
9 | BNC コネクタ ( 1個 100円) 4 個 | 秋月電子通商 | 400円 | |
10 | M2 Hex Head Tapping Screw (8 ea ) | ebay | 56円 | |
11 | BATTERY NP-40 3.7V 1300mA | 杉元ガレージ | 200円 | 秋葉原 |
12 | RF ケーブル, ワイヤー等 | 在庫品 | 0円 | |
– | ————————————— | —————- | ——- | ———– |
– | TOTAL | 1196円 | OBJET |
他の部品:
No | NOMEN | 販売店 | 価格 | Note |
1 | オシロスコープ ケーブル | CompuAce | 秋葉原 | |
1 | BNC コネクター | ラジオストアー2F 山本電機 | 150円 | 秋葉原 |
1 | BNC SMA 変換コネクター | ラジオストアー2F 山本電機 | 250円 | 秋葉原 |
1 | BNC SMA 変換コネクター | CompuAce | 124円 | 秋葉原 |
2 | RFコネクターケーブル | CompuAce | 秋葉原 |
部品の入手方法:
コネクター:
_ 一番安い店は 地下鉄「末広町駅」近く、秋葉原の「CompuAce」です。
バッテリー:
_ カメラ用の四角いものが最適です。ただし 通常は高いです。
_ 秋葉原の杉元ビルの 杉元ガレージ (中古品)で 200〜300円で入手できます。
_ このバッテリーは M5STACK の PROT Module の大きさに入ります。
_ 容量のあるバッテリーを使用すると便利になります。バッテリーは 価格が高い為
_ 事前に品質良く、安い製品を探しておくと良いです。新品で購入しやすい製品は
_ ロワ・ジャパン ROWA @ JAPAN の NP-70 1250mAh Li-ion です。
![]() |
![]() |
組み立て:
_ INPUT は GPIO 35, 36 へ配線します。OUTPUT は GPIO 2, 5 へ配線します。
![]() |
![]() |
![]() |
![]() |
![]() |
![]() |
メモ:
コネクターの高さ:
_ スタック構造ですので コネクターの高さが重要です。
_ 接続コネクターによって 高さが異なることや インターフェースの高さも影響します。
_ これらを考慮して 幾つかの FRAME や コネクターを用意して 高さを調整します。
_ 基板の厚さも重要です。 使用しているのは 1mm厚と 0.3mm厚の基板です。
_ SG = 1KHz の例:twitter: 周波数範囲を理解して使用します。
_
再設計:2018.08.01
M5STACK OSCILLOSCOPE PROTO :
部品は全て国内部品を使用し 再設計しました。
ケース高 15mm + ピンソケット2×15 5.7mm x 2個 でスタック可能です。
感想:
botofan calin氏 作成の「M5Stack-ESP32-Oscilloscope」は 多数の項目があり
_ それぞれの項目で電圧やレンジを変更できます。操作して その出来具合に
_ 感心しています。
_ M5SATCK の目的の1つにオシロスコープの製作がありますが
_ botofan calin氏により 作成されました。 私の自作案より 遥かに完成度が高く
_ 感激しています。botofan calin氏に感謝致します。
_ 公開当初、スピーカーから異音がでる不具合がありましたが その後 他の件
_ も含め Revision が実施されています。commit には 多数の Revision があり
_ 敬服致します。
_ M5STACK は ケースに入っているので使用しています。それに合わせて
_ 綺麗なケースを作りたくなります。
スケッチ:
// rev 2. 2019.12.01
// https://macsbug.wordpress.com/2018/04/03/make-a-case-of-m5stack-oscilloscope/
// 2019.12.01 : Prevent reboot every 5 seconds : macsbug // 2018.04.03 : macsbug : // https://macsbug.wordpress.com/2018/04/03/make-a-case-of-m5stack-oscilloscope/ // M5Stack Community : // http://forum.m5stack.com/topic/147/m5stack-2-channel-oscilloscope/20 // 2 May 2018 : botofancalin // https://github.com/botofancalin/M5Stack-ESP32-Oscilloscope #include <M5Stack.h> #include "M5StackUpdater.h" #include <esp32/ulp.h> // ULP : Prevent reboot every 5 seconds. rev 2019.11.30 #include <driver/rtc_io.h> // ULP : #include <driver/dac.h> // ULP : #include <soc/rtc.h> // ULP : #include <math.h> // ULP : const int LCD_WIDTH = 320; const int LCD_HEIGHT = 240; const int SAMPLES = 320; const int DOTS_DIV = 30; const int ad_ch0 = 35; // Analog 35 pin for channel 0 const int ad_ch1 = 36; // Analog 36 pin for channel 1 const long VREF[] = { 250, 500, 1250, 2500, 5000 }; const int MILLIVOL_per_dot[] = { 33, 17, 6, 3, 2 }; const int MODE_ON = 0; const int MODE_INV = 1; const int MODE_OFF = 2; const char *Modes[] = {"NORM","INV","OFF"}; const int TRIG_AUTO = 0; const int TRIG_NORM = 1; const int TRIG_SCAN = 2; const char *TRIG_Modes[] = {"Auto","Norm","Scan"}; const int TRIG_E_UP = 0; const int TRIG_E_DN = 1; #define RATE_MIN 0 #define RATE_MAX 13 const char *Rates[] = {"F1-1","F1-2"," F2"," 5ms","10ms","20ms","50ms", "0.1s","0.2s","0.5s","1s","2s","5s","10s"}; #define RANGE_MIN 0 #define RANGE_MAX 4 const char *Ranges[] = {" 1V","0.5V","0.2V","0.1V","50mV" }; int range0 = RANGE_MIN; short range1 = RANGE_MIN; short ch0_mode = MODE_ON; short ch0_off = 0; short ch1_mode = MODE_ON; short ch1_off = 0; short rate = 3; short trig_mode = TRIG_AUTO; short trig_lv = 40; short trig_edge = TRIG_E_UP; short trig_ch = 0; short Start = 1; short menu = 19; short data[4][SAMPLES]; // keep twice of the number of channels to make it a double buffer short sample = 0; // index for double buffer int amplitude = 0; int amplitudeStep = 5; TaskHandle_t LedC_Gen; TaskHandle_t SigmaDeltaGen; /////////////////////////////////////////////////////////////////////////////////////////////// #define CH1COLOR YELLOW #define CH2COLOR CYAN #define GREY 0x7BEF //--------------------------------------------------------------------------------------------- void DrawText(){ M5.Lcd.setTextColor(WHITE); M5.Lcd.setTextSize(1); M5.Lcd.fillRect(270, 19, 70, 121, BLACK); M5.Lcd.fillRect(270, menu, 70, 10, BLUE); M5.Lcd.setCursor(270, 20); M5.Lcd.println(Start == 0 ? "Stop" : "Run"); M5.Lcd.setCursor(270, 30); M5.Lcd.println(String(String(Ranges[range0]) + "/DIV")); M5.Lcd.setCursor(270, 40); M5.Lcd.println(String(String(Ranges[range1]) + "/DIV")); M5.Lcd.setCursor(270, 50); M5.Lcd.println(String(String(Rates[rate]) + "/DIV")); M5.Lcd.setCursor(270, 60); M5.Lcd.println(Modes[ch0_mode]); M5.Lcd.setCursor(270, 70); M5.Lcd.println(Modes[ch1_mode]); M5.Lcd.setCursor(270, 80); M5.Lcd.println("OFS1:" + String(ch0_off)); M5.Lcd.setCursor(270, 90); M5.Lcd.println("OFS2:" + String(ch1_off)); M5.Lcd.setCursor(270, 100); M5.Lcd.println(trig_ch == 0 ? "T:1" : "T:2"); M5.Lcd.setCursor(270, 110); M5.Lcd.println(TRIG_Modes[trig_mode]); M5.Lcd.setCursor(270, 120); M5.Lcd.println("Tlv:" + String(trig_lv)); M5.Lcd.setCursor(270, 130); M5.Lcd.println((trig_edge == TRIG_E_UP) ? "T:UP" : "T:DN"); } //--------------------------------------------------------------------------------------------- void CheckSW(){ M5.update(); if (M5.BtnB.wasPressed()){(menu < 129) ? (menu += 10) : (menu = 19);return; }else if (M5.BtnA.wasPressed()){ switch (menu){ case 19:if (Start == 0){Start = 1;}else{Start = 0;}break; case 29:if (range0 > 0){range0--;}break; case 39:if (range1 > 0){range1--;}break; case 49:if (rate > 0){rate--;}break; case 59:if (ch0_mode > 0){ch0_mode--;}break; case 69:if (ch1_mode > 0){ch1_mode--;}break; case 79:if (ch0_off > -1023){ch0_off -= 1024 / VREF[range0];}break; case 89:if (ch1_off > -1023){ch1_off -= 1024 / VREF[range1];}break; case 99:if (trig_ch == 0){trig_ch = 1;}else{trig_ch = 0;}break; case 109:if (trig_mode > 0){trig_mode--;}else{trig_mode = TRIG_SCAN;}break; case 119:if (trig_lv > 0){trig_lv--;}break; case 129:if (trig_edge == TRIG_E_UP){trig_edge = TRIG_E_DN;}else{trig_edge = TRIG_E_UP;}break; } return; } if (M5.BtnC.wasPressed()){ switch (menu){ case 19:if (Start == 0){Start = 1;}else{Start = 0;}break; case 29:if (range0 < RANGE_MAX){range0++;}break; case 39:if (range1 < RANGE_MAX){range1++;}break; case 49:if (rate < RATE_MAX){rate++;}break; case 59:if (ch0_mode < 2){ch0_mode++;}break; case 69:if (ch1_mode < 2){ch1_mode++;}break; case 79:if (ch0_off < 1023){ch0_off += 1024 / VREF[range0];}break; case 89:if (ch1_off < 1023){ch1_off += 1024 / VREF[range1];}break; case 99:if (trig_ch == 0){trig_ch = 1;}else{trig_ch = 0; }break; case 109:if (trig_mode < TRIG_SCAN){trig_mode++;}else{trig_mode = 0;}break; case 119:if (trig_lv < 60){trig_lv++;}break; case 129:if (trig_edge == TRIG_E_UP){trig_edge = TRIG_E_DN;}else{trig_edge = TRIG_E_UP;}break; } return; }else{return;} DrawText(); } //--------------------------------------------------------------------------------------------- void DrawGrid(){ for (int x = 0; x <= SAMPLES; x += 2){ // Horizontal Line{ for (int y = 0; y <= LCD_HEIGHT; y += DOTS_DIV){M5.Lcd.drawPixel(x, y, GREY);CheckSW();} if (LCD_HEIGHT == 240){M5.Lcd.drawPixel(x,LCD_HEIGHT - 1,GREY);} } for (int x = 0; x <= SAMPLES; x += DOTS_DIV){ // Vertical Line{ for (int y = 0; y <= LCD_HEIGHT; y += 2){M5.Lcd.drawPixel(x, y, GREY);CheckSW();} } } //--------------------------------------------------------------------------------------------- void DrawGrid(int x){ if ((x % 2) == 0){ for (int y = 0; y <= LCD_HEIGHT;y += DOTS_DIV){M5.Lcd.drawPixel(x,y,GREY);} } if ((x % DOTS_DIV) == 0){ for (int y = 0; y <= LCD_HEIGHT; y += 2){M5.Lcd.drawPixel(x, y, GREY);} } } //--------------------------------------------------------------------------------------------- void ClearAndDrawGraph(){ int clear = 0; if (sample == 0){clear = 2;} for (int x = 0; x < (SAMPLES - 1); x++){ M5.Lcd.drawLine(x, LCD_HEIGHT - data[clear + 0][x], x + 1, LCD_HEIGHT - data[clear + 0][x + 1], BLACK); M5.Lcd.drawLine(x, LCD_HEIGHT - data[clear + 1][x], x + 1, LCD_HEIGHT - data[clear + 1][x + 1], BLACK); if (ch0_mode != MODE_OFF){ M5.Lcd.drawLine(x, LCD_HEIGHT - data[sample + 0][x], x + 1, LCD_HEIGHT - data[sample + 0][x + 1], CH1COLOR); } if (ch1_mode != MODE_OFF){ M5.Lcd.drawLine(x, LCD_HEIGHT - data[sample + 1][x], x + 1, LCD_HEIGHT - data[sample + 1][x + 1], CH2COLOR); } CheckSW(); } } //--------------------------------------------------------------------------------------------- void ClearAndDrawDot(int i){ int clear = 0; if (i <= 1){return;} if (sample == 0){clear = 2;} M5.Lcd.drawLine(i - 1, LCD_HEIGHT - data[clear + 0][i - 1], i, LCD_HEIGHT - data[clear + 0][i], BLACK); M5.Lcd.drawLine(i - 1, LCD_HEIGHT - data[clear + 1][i - 1], i, LCD_HEIGHT - data[clear + 1][i], BLACK); if (ch0_mode != MODE_OFF){ M5.Lcd.drawLine(i - 1, LCD_HEIGHT - data[sample + 0][i - 1], i, LCD_HEIGHT - data[sample + 0][i], CH1COLOR); } if (ch1_mode != MODE_OFF){ M5.Lcd.drawLine(i - 1, LCD_HEIGHT - data[sample + 1][i - 1], i, LCD_HEIGHT - data[sample + 1][i], CH2COLOR); } DrawGrid(i); } //--------------------------------------------------------------------------------------------- void DrawGraph(){ for (int x = 0; x < SAMPLES; x++){ M5.Lcd.drawPixel(x, LCD_HEIGHT - data[sample + 0][x], CH1COLOR); M5.Lcd.drawPixel(x, LCD_HEIGHT - data[sample + 1][x], CH2COLOR); } } //--------------------------------------------------------------------------------------------- void ClearGraph(){ int clear = 0; if (sample == 0){clear = 2;} for (int x = 0; x < SAMPLES; x++){ M5.Lcd.drawPixel(x, LCD_HEIGHT - data[clear + 0][x], BLACK); M5.Lcd.drawPixel(x, LCD_HEIGHT - data[clear + 1][x], BLACK); } } //--------------------------------------------------------------------------------------------- inline long adRead(short ch, short mode, int off){ long a = analogRead(ch); a = (((a + off) * VREF[(ch == ad_ch0) ? range0 : range1]) / 10000UL) + 30; a = ((a >= LCD_HEIGHT) ? LCD_HEIGHT : a); if (mode == MODE_INV){return LCD_HEIGHT - a;} return a; } //--------------------------------------------------------------------------------------------- void ledcAnalogWrite(uint8_t channel, uint32_t value, uint32_t valueMax = 255){ uint32_t duty = (8191 / valueMax) * min(value, valueMax); ledcWrite(channel, duty); } //--------------------------------------------------------------------------------------------- // Make a PWM generator task on core 0 // Signal generator pin 2 void LedC_Task(void *parameter){ ledcSetup(0, 50, 13); ledcAttachPin(2, 0); for (;;){ ledcAnalogWrite(0, amplitude); amplitude = amplitude + amplitudeStep; if (amplitude <= 0 || amplitude >= 255){amplitudeStep = -amplitudeStep;} delay(30); } vTaskDelete(NULL); } //--------------------------------------------------------------------------------------------- void SigmaDelta_Task(void *parameter){ sigmaDeltaSetup(0, 312500); sigmaDeltaAttachPin(5, 0); sigmaDeltaWrite(0, 0); for (;;){ static uint8_t i = 0; sigmaDeltaWrite(0, i++); delayMicroseconds(50); } } //--------------------------------------------------------------------------------------------- void setup(){ M5.begin(); dacWrite(25, 0); // Speaker OFF Wire.begin(); if(digitalRead(BUTTON_A_PIN)==0){updateFromFS(SD);ESP.restart();} M5.Lcd.fillScreen(BLACK); disableCore0WDT(); // Prevent reboot every 5 seconds. rev 2019.11.30 disableCore1WDT(); // Prevent reboot every 5 seconds. rev 2019.11.30 DrawGrid(); DrawText(); M5.Lcd.setBrightness(100); xTaskCreatePinnedToCore( LedC_Task, /* Task function. */ "LedC_Task", /* name of the task, a name just for humans */ 8192, /* Stack size of task */ NULL, /* parameter of the task */ 2, /* priority of the task */ &LedC_Gen, /* Task handle to keep track of the created task */ 0); /*cpu core number where the task is assigned*/ xTaskCreatePinnedToCore( SigmaDelta_Task, /* Task function. */ "SigmaDelta_Task", /* name of task, a name just for humans */ 8192, /* Stack size of task */ NULL, /* parameter of the task */ 2, /* priority of the task */ &SigmaDeltaGen, /* Task handle to keep track of the created task */ 0); /*cpu core number where the task is assigned*/ } //--------------------------------------------------------------------------------------------- void loop(){ if (trig_mode != TRIG_SCAN){ unsigned long st = millis(); short oad; if (trig_ch == 0){oad = adRead(ad_ch0, ch0_mode, ch0_off); }else{oad = adRead(ad_ch1, ch1_mode, ch1_off);} for (;;){ short ad; if (trig_ch == 0){ad = adRead(ad_ch0, ch0_mode, ch0_off); }else{ad = adRead(ad_ch1, ch1_mode, ch1_off);} if (trig_edge == TRIG_E_UP){ if (ad >= trig_lv && ad > oad){break;} }else{if (ad <= trig_lv && ad < oad){break;}} oad = ad; CheckSW(); if (trig_mode == TRIG_SCAN){break;} if (trig_mode == TRIG_AUTO && (millis() - st) > 100){break;} } } // sample and draw depending on the sampling rate if (rate <= 5 && Start){ // change the index for the double buffer if (sample == 0){sample = 2;}else{sample = 0;} if (rate == 0){ // full speed, channel 0 only unsigned long st = millis(); for (int i = 0; i < SAMPLES; i++){ data[sample + 0][i] = adRead(ad_ch0, ch0_mode, ch0_off); } for (int i = 0; i < SAMPLES; i++){data[sample + 1][i] = 0;} // full speed, channel 1 only }else if (rate == 1){ unsigned long st = millis(); for (int i = 0; i < SAMPLES; i++){ data[sample + 1][i] = adRead(ad_ch1, ch1_mode, ch1_off); } for (int i = 0; i < SAMPLES; i++){data[sample + 0][i] = 0;} // full speed, dual channel }else if (rate == 2){ unsigned long st = millis(); for (int i = 0; i < SAMPLES; i++){ data[sample + 0][i] = adRead(ad_ch0, ch0_mode, ch0_off); data[sample + 1][i] = adRead(ad_ch1, ch1_mode, ch1_off); } } // .5ms, 1ms or 2ms sampling else if (rate >= 3 && rate <= 5){ const unsigned long r_[] = { 5000 / DOTS_DIV, 10000 / DOTS_DIV, 20000 / DOTS_DIV }; unsigned long st0 = millis(); unsigned long st = micros(); unsigned long r = r_[rate - 3]; for (int i = 0; i < SAMPLES; i++){ while ((st - micros()) < r) ; st += r; data[sample + 0][i] = adRead(ad_ch0, ch0_mode, ch0_off); data[sample + 1][i] = adRead(ad_ch1, ch1_mode, ch1_off); } } ClearAndDrawGraph(); CheckSW(); DrawGrid(); DrawText(); }else if (Start){ // 5ms - 500ms sampling // copy currently showing data to another if (sample == 0){ for (int i = 0; i < SAMPLES; i++){ data[2][i] = data[0][i]; data[3][i] = data[1][i]; } }else{ for (int i = 0; i < SAMPLES; i++){ data[0][i] = data[2][i]; data[1][i] = data[3][i]; } } const unsigned long r_[] = { 50000 / DOTS_DIV, 100000 / DOTS_DIV, 200000 / DOTS_DIV, 500000 / DOTS_DIV, 1000000 / DOTS_DIV, 2000000 / DOTS_DIV, 5000000 / DOTS_DIV, 10000000 / DOTS_DIV }; unsigned long st0 = millis(); unsigned long st = micros(); for (int i = 0; i < SAMPLES; i++){ while ((st - micros()) < r_[rate - 6]){ CheckSW(); if (rate < 6){break;} } // sampling rate has been changed if (rate < 6){M5.Lcd.fillScreen(BLACK);break;} st += r_[rate - 6]; // sampling rate has been changed to shorter interval if (st - micros() > r_[rate - 6]){st = micros();} if (!Start){i--;continue;} data[sample + 0][i] = adRead(ad_ch0, ch0_mode, ch0_off); data[sample + 1][i] = adRead(ad_ch1, ch1_mode, ch1_off); ClearAndDrawDot(i); } DrawGrid(); DrawText(); }else{CheckSW();} M5.update(); } //---------------------------------------------------------------------------------------------
M5Stack , Oscilloscope ,