液晶表示の無いマイコンボードのシリアル出力データを外付け液晶やパソコンではなく、外付けして単体でデータ確認できるシリアルモニターが欲しかったので「M5Stack BasicV2.6」で作りました。
「Atom」や「Arduino(5V→3.3V変換必要)」等、液晶表示の無いマイコンボードのデータが手軽に確認できて便利と思うので紹介します。
「M5Stack BasicV2.6」については以下のリンクで詳しく紹介しています。
1.概要
2.使い方
・結線方法
・動作紹介
3.サンプルプログラム(コピペ)
4.M5Stackのシリアル通信(UART)仕様
5.シリアル出力文字データ(UTF-8、3byte文字)
6.Arduinoで使用時の注意点(5V→3.3V変換)
7.動作確認用ATOMデータサンプル(ASCIIアート等)
8.まとめ
1.概要
「M5Stack」に使用されている「ESP32」には3系統の有線シリアル通信があります。
これらのシリアル通信の詳細は後ほど詳しく紹介します。
USB接続と兼用(初期状態)になっている「Serial」と、ほとんどの端子で自由に選択して使用できる「Serial1」「Serial2」を使って、どのシリアル通信からのデータも表示できるシリアルモニターにしました。
「ASCII文字」のみでも良かったのですが画面表示の「チラツキ」防止に「LovyanGFX」を使用したこともあり、せっかくなので日本語表示もできるようにしています。
「LovyanGFX」については以下のリンクで詳しく紹介しています。
「ASCII文字」以外(特に全角文字や特殊記号)は文字コードの判定が簡易的なため、文字化けや改行がズレることがあります。「ASCII文字」以外は「おまけ」と思ってご使用ください。
(ボタンAを押すと1行ずつスクロールできるようにしてます。ズレた場合は手動で調整できます。)
文字の表示は、1行の文字数を全角半角でカウントして右端までのカウント数になったら改行してスクロールする単純なものです。
このため、フォントは「等幅の16px ゴシック体(初期状態、ボタンBで選択)/明朝体(ボタンCで選択)」を使用しています。
GitHubの「TFT_Terminal」へのリンクは以下になります。
せっかく作ったので私は自分のを使います!www
今回は「M5Stack Basic V2.6」を使用していますが、「旧バージョンのBasic」や「GRAY」でもUSBドライバが違うだけなので、ドライバさえダウンロードすればどれでも「コピペ」で使用できます。
2.使い方
実際に「ATOM LITE」のシリアル出力を「M5Stack」の液晶に表示させた場合で結線方法や動作について紹介します。
動作確認のため2台の「ATOM LITE」を接続した画像が以下のようになります。
以下の動作確認では3系統あるシリアル通信のうち2つ「Serial1(GROVEコネクタ)」と「Serial2」に接続してどちらのデータも表示できるようにしています。
本体ボタンA(左)は手動スクロール、ボタンB(中)はフォントゴシック体選択、ボタンC(右)はフォント明朝体選択として使用します。
・結線方法
以下の結線例では「ATOM LITE」の「GROVEコネクタ」のG26をTX(送信)にG32をRX(受信)に設定しています。
「M5Stack」の標準の「Serial」を使用する場合は、下画像右側のように「M5Stack」のR0(G3)端子(破線)に「ATOM LITE」のG26(TX)を接続します。
「Serial2」を使用する場合は、R2(G16)端子に「ATOM LITE」のG26(TX)を接続し、どちらの場合もGND同士を接続します。
「Serial1」に設定した「GROVEコネクタ」を使用する場合は「GROVEコネクタ」同士を接続します。
コネクタ同士を接続するだけでGND同士の接続と電源供給もできるため便利です。
5Vを「M5Stack」と接続することで「M5Stack」から電源を供給することもできます。
・動作紹介
実際に「ATOM LITE」からシリアルデータを送信して「M5Stack」に表示すると下画像のようになります。
デモとしてASCIIアートやASCII文字、全角半角英数、全角ひらがな漢字、記号まで一覧で表示しています。
3.サンプルプログラム(コピペ)
下のコードを「コピペ」して書き込んでください。
※コピーは下コード(黒枠)内の右上角にある小さなアイコンのクリックでもできます。
#include <M5Stack.h> // CORE2の場合は <M5Core2.h>
#define LGFX_AUTODETECT // 自動認識(D-duino-32 XS, PyBadgeはパネルID読取れないため自動認識の対象から外れているそうです)
#define LGFX_USE_V1 // v1.0.0を有効に(v0からの移行期間の特別措置とのこと。書かない場合は旧v0系で動作)
#include <LovyanGFX.hpp> // lovyanGFXのヘッダを準備
#include <LGFX_AUTODETECT.hpp> // クラス"LGFX"を準備
static LGFX lcd; // LGFXのインスタンスを作成(クラスLGFXを使ってlcdコマンドでいろいろできるようにする)
static LGFX_Sprite canvas(&lcd); // スプライトを使う場合はLGFX_Spriteのインスタンスを作成
// 変数宣言
char c; // 受信データ(文字)格納用
int c_cnt = -1; // 文字数カウント
String row[15] = {}; // 行配列
bool disp_set = 0; // 画面出力フラグ
// 関数 ---------------------------------------------------------
/* --------------- 行スクロール処理関数 --------------- */
void scroll_disp() {
for (int i = 1; i < 15; i++) { // 最上行以外の14行分繰り返し
row[i-1] = row[i]; // 行配列を左へシフト(上スクロール)
}
row[14] = ""; // 最下行の表示を消去
}
/* ---------- スクロール後の行一括表示処理関数 ---------- */
void disp_out() { // 表示実行(15行一括表示)
canvas.fillScreen(BLACK); // 画面リセット(黒塗り潰し)
canvas.setCursor(0, 0); // カーソル原点
for (int i = 0; i < 15; i++) { // 15行分繰り返し
canvas.println(row[i]); // 行配列を改行しながら順番に表示
}
canvas.pushSprite(0, 0); // メモリ内に描画したcanvasを座標を指定して表示する
}
// 初期設定 ------------------------------------------------------
void setup() {
M5.begin(true,true,true,false); // 本体初期化(LCD, SD, Serial, I2C)※I2Cのみ無効
Serial.begin(9600); // シリアル通信初期化(USBと共用、初期値は RX=G3, TX=G1)
Serial1.begin(9600, SERIAL_8N1, 21, 22); // シリアル通信1初期化(RX, TX) ※BASIC, GRAYのポートA
// Serial1.begin(9600, SERIAL_8N1, 32, 33); // シリアル通信1初期化(RX, TX) ※CORE2のポートA
Serial2.begin(9600); // シリアル通信2初期化(初期値は RX=G16, TX=G17)※CORE2はG13,14
// LCD初期設定
lcd.init(); // LCD初期化
canvas.setColorDepth(8); // カラーモード設定
canvas.createSprite(lcd.width(), lcd.height()); // canvasサイズ(メモリ描画領域)設定(画面サイズに設定)
// スプライト表示設定(canvas.で指定してメモリ内の仮想画面に描画)
canvas.fillScreen(BLACK); // 背景塗り潰し
canvas.setTextColor(WHITE); // 文字色
// 液晶初期表示(シリアルデータ通信速度、使用端子番号)
canvas.setFont(&fonts::Font4); // フォント設定
canvas.println("Serial Monitor (9600bps)"); // 初期表示
canvas.setFont(&fonts::Font2); // フォント設定
canvas.println("Serial \n RX = 3(R0) / TX = 1(T0) / GND = G");
canvas.println("Serial1 (GROVE)\n RX = 21 / TX = 22 / GND = G"); // BASIC, GRAYの場合
// canvas.println("Serial1 (GROVE)\n RX = 32 / TX = 33 / GND = G"); // ※CORE2の場合
canvas.println("Serial2 \n RX = 16(R2) / TX = 17(T2) / GND = G\n"); // BASIC, GRAYの場合
// canvas.println("Serial2 \n RX = 13(R2) / TX = 14(T2) / GND = G\n"); // ※CORE2の場合
canvas.println("ButtonA : Scroll UP");
canvas.println("ButtonB : Gothic");
canvas.println("ButtonC : Mincho");
canvas.pushSprite(0, 0); // メモリ内に描画したcanvasを座標を指定して表示する
canvas.setFont(&fonts::lgfxJapanGothic_16); // フォント日本語ゴシック(16px、等幅)
}
// メイン ------------------------------------------------------
void loop() {
M5.update(); //本体のボタン状態更新
// 受信データ処理
if (Serial.available() || Serial1.available() || Serial2.available()) { // 受信データがあれば
disp_set = 1; // 画面出力フラグ1
if (int(Serial.available() || Serial1.available() || Serial2.available()) <= 64) { // 受信データ64byte(MAX)以下なら
if (Serial.available()) {c = Serial.read();} // 1文字づつ文字変数に格納(Serial)
if (Serial1.available()) {c = Serial1.read();} // 1文字づつ文字変数に格納(Serial1)
if (Serial2.available()) {c = Serial2.read();} // 1文字づつ文字変数に格納(Serial2)
}
// 行スクロール処理(改行、半角、全角判定)
if (c == '\n') { // 「改行」を受信したら
c_cnt = 0; // 文字数カウントを0リセット
scroll_disp(); // 行スクロール処理
} else { // 「改行」でなければ(文字コードから全角半角を判定して文字数カウント)
if (c <= 0x7F) { // ASCII文字(半角)なら
c_cnt++; // 文字数カウント+1
}
if (c >= 0xE0 && c <= 0xEF) { // 全角なら(半角カナは全角としてカウント)
c_cnt = c_cnt + 2; // 文字数カウント+2
}
if (c_cnt >= 39) { // 文字数カウントが39なら
c_cnt = 0; // 文字数カウント0リセット
scroll_disp(); // 行スクロール処理
disp_out(); // スクロール後の行一括表示処理
}
row[14] += c; // 行配列の最下行に1文字づつ追加して文字列へ
}
} else { // 受診データがなければ
if (disp_set == 1) { // 画面出力フラグが1なら
disp_set = 0; // 画面出力フラグ0セット
disp_out(); // スクロール後の行一括表示処理
}
}
// ボタン操作処理
if (M5.BtnA.wasPressed()) { // ボタンAが押されていれば
scroll_disp(); // 行スクロール処理
disp_out(); // スクロール後の行一括表示処理
}
if (M5.BtnB.wasPressed()) { // ボタンBが押されていれば
canvas.setFont(&fonts::lgfxJapanGothic_16); // 日本語ゴシック体(16px、等幅)
canvas.fillScreen(BLACK); // 背景塗り潰し
canvas.setCursor(0, 0); // カーソルを(0, 0)へ
canvas.println("フォント変更\nゴシック体"); // フォント設定表示
canvas.pushSprite(0, 0); // メモリ内に描画したcanvasを座標を指定して表示する
}
if (M5.BtnC.wasPressed()) { // ボタンCが押されていれば
canvas.setFont(&fonts::lgfxJapanMincho_16); // 日本語明朝体(16px、等幅)
canvas.fillScreen(BLACK); // 背景塗り潰し
canvas.setCursor(0, 0); // カーソルを(0, 0)へ
canvas.println("フォント変更\n明朝体"); // フォント設定表示
canvas.pushSprite(0, 0); // メモリ内に描画したcanvasを座標を指定して表示する
}
}
4.M5Stackのシリアル通信(UART)仕様
今回使用するシリアル通信「UART」には「I2C」のようにクロック信号用の配線が無く、お互いの通信速度を合わせて通信を行うため「非同期通信」と呼ばれます。
受信用(レシーブ)端子「RX」と送信用(トランスファー)端子「TX」の2本線を使用しますが、お互いのGND(0V)を接続しておく必要があります。
お互いの接続は以下のようにRXとTXが交差するように接続します。
受信用端子「TX」↔︎ 送信用端子「RX」
GND(0V)↔︎ GND(0V)
シリアルモニタの使い方については以下のリンクでも詳しく紹介しています。
「M5Stack」に使用されている「ESP32」には以下の3系統の有線シリアル通信があります。
シリアル通信の種類 | 初期端子 RX/TX | 詳細 | 注意事項 |
---|---|---|---|
Serial | G3/G1 | 標準のシリアル通信 (初期端子はUSBと共用) 任意の端子を割り付けられます。 | パソコンからUSBで書き込みや、 データ出力を行う時はG3(RX)の接続は 外す必要があります。 |
Serial1 | G9/G10 使用不可 | シリアル通信1 任意の端子を割り付けられます。 | 初期値のG9,10は使用不可のため 使用する端子を指定して使用します。 |
Serial2 | G16/G17 | シリアル通信2 任意の端子を割り付けられます。 | (特に理由がなければ初期値のまま使用 した方が動作は安定しているように 思います。) |
使用方法はサンプルプログラムの 35〜38行目のように「通信速度」と必要に応じて「通信仕様」「RX/TX端子番号」を指定して以下のように初期化を行って使用します。
Serial1.begin( 通信速度, 通信仕様, RX端子番号, TX端子番号 ) ; //シリアル通信1
Serial2.begin( 通信速度 ) ; //シリアル通信2、初期値G16(RX),G17(TX)
・通信速度:通信速度(ボーレート)を数値で指定します。
通信速度には以下のようなものがあります。(単位 bps)
1200/2400/4800/9600/19200/38400/57600/115200
※省略すると9600bpsとなります。
・通信仕様:以下の通信仕様を指定します。
データ長:送信データのビット数
パリティビット:エラー検出用(N:なし、E:偶数、O:奇数)
ストップビット:データの終端検出用(1、1.5、2)
※省略するとSERIAL_8N1となります。
SERIAL_8N1はデータ長8ビット、パリティなし、ストップビット1となります。
・RX端子番号, TX端子番号:使用する端子番号を数値で指定します。
5.文字データ(UTF-8、3byte文字)の判定
1行に表示できる文字は半角39文字として改行しています。
ASCII文字だけを表示する場合は受信データは1byteのみのため、単純に1byteづつカウントしていけばいいのですが、日本語等の全角は3byteで1文字のため、単純にカウントすると1行に表示する文字数が少なくなってしまうのと、3byte文字の途中で改行されると文字化けが発生します。
このため受信データがASCII文字か3byte文字かの判定を行い全角半角を判定して文字数をカウントする必要があります。
受信データの判定と文字数カウントはサンプルプログラムの 81〜86行目で以下のように行いました。
if (c <= 0x7F) { // ASCII文字(半角)なら
c_cnt++; // 文字数カウント+1
}
if (c >= 0xE0 && c <= 0xEF) { // 全角なら(半角カナは全角としてカウント)
c_cnt = c_cnt + 2; // 文字数カウント+2
}
ASCII文字(0x00〜0x7F)を受信したら文字数カウント+1
3byte文字で全角(ひらがな、漢字、記号等)の1byte目の「0xE0〜0xEF」を受信したら文字数カウントを+2しています。
3byte文字の文字コードについては「オレンジ工房」さんのサイトが見やすくて分かりやすかったので こちら(UTF-8の文字コード表) で確認させていただきました。
6.Arduinoで使用時の注意点(5V→3.3V変換)
「Arduino」で使用する場合は送受信に使用するRX、TX端子の電圧に注意が必要です。
「Arduino」の電圧は「5V」ですが「M5Stack」の電圧は「3.3V」のため、そのまま接続すると破損する可能性があります。
このため以下のように「電圧レベル変換モジュール」等を使用して「5V」を「3.3V」に変換してから接続してください。
7.動作確認用ATOMデータサンプル(ASCIIアート等)
動作確認のための送信データサンプルを「ATOM LITE」用として準備しました。
「ATOM LITE」に書き込んで「M5Stack」に接続して、ATOMの本体ボタンを押すとASCIIアート等が表示されるのが確認できます。
ASCIIアートは初心者なので微妙ですが・・・動作確認用として使用してください。
※コピーは下コード(黒枠)内の右上角にある小さなアイコンのクリックでもできます。
#include <M5Atom.h> //M5Atomのヘッダファイルを準備
CRGB dispColor(uint8_t r, uint8_t g, uint8_t b) { //FastLEDライブラリの設定(CRGB構造体)
return (CRGB)((r << 16) | (g << 8) | b);
}
// 初期設定 -----------------------------------------------
void setup() {
M5.begin(true, false, true); // 本体初期化(UART, I2C, LED)
Serial.begin(9600); // 標準のシリアル通信初期化(RX, TX)
Serial2.begin(9600, SERIAL_8N1, 32, 26); // シリアル通信2初期化(RX, TX)
// 初期LED白点灯(赤, 緑, 青)
M5.dis.drawpix(0, dispColor(20, 20, 20));
}
// メイン -------------------------------------------------
void loop() {
M5.update(); //本体のボタン状態更新
if (M5.Btn.wasPressed()) { //ボタンが押されていれば
M5.dis.drawpix(0, dispColor(0, 30, 30)); //LED水色
for (int i = 0; i < 12; i++) {Serial2.println("");} // スクロールクリア
Serial2.println(" ∧_∧ カタ"); delay(100);
Serial2.println("(´・ω・ `) / シリアルデータを"); delay(100);
Serial2.println(" U U \ M5Stackに表示!"); delay(100);
Serial2.println(" \ Hello \"); delay(100);
Serial2.println(" \ \ カタ"); delay(100);
Serial2.println(" カタ \ \"); delay(100);
Serial2.println(""); delay(100);
delay(3000);
for (int i = 0; i < 12; i++) {Serial2.println("");} // スクロールクリア
Serial2.print("01234567890123456789いろはにほへとちりぬるをわかよたれそつねならむうゐのおくやまけふこえてABCDEFGHIJKLMNOPQRSTUVWXYZABCDEFHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!\"#$%&'()*+,-./:;<=>?@[\\]^_`{|}~寿限無、寿限無、五劫のすりきれ海砂利水魚の水行末、雲来末、風来末食う寝るところに住むところやぶら小路のぶら小路パイポパイポ、パイポのシューリンガンシューリンガンのクーリンダイクーリンダイのポンポコナーのポンポコピーの長久命の長助『』【】〆→↓←↑≧≦〒・、。⊂⊃∪∩/\¥∴⊥∠≡…:‖_*▽▼≪∀ゞ≠§○△□×●▲■◇");
Serial2.println("");
delay(4000);
for (int i = 0; i < 12; i++) {Serial2.println("");} // スクロールクリア
Serial2.println(" ┏━━━━━━━━━━━━━━┓");
Serial2.println(" ┃┏━━━━━━━━━━━━┓┃");
Serial2.println(" ┃┃ / \ ┃┃");
Serial2.println(" ┃┃ / / \ ┃┃");
Serial2.println(" ┃┃ / / _ \ ┃┃");
Serial2.println(" ┃┃ \ \ / │ / ┃┃");
Serial2.println(" ┃┃ │ \ _/ / │ ┃┃");
Serial2.println(" ┃┃ │ \ / │ ┃┃");
Serial2.println(" ┃┃ │ │ ┃┃");
Serial2.println(" ┃┃ \ / ┃┃");
Serial2.println(" ┃┃ \ / ┃┃");
Serial2.println(" ┃┃ \ / ┃┃");
Serial2.println(" ┃┗━━━━━━━━━━━━┛┃");
Serial2.println(" ┗━━━━━━━━━━━━━━┛");
delay(1500);
for (int i = 0; i < 12; i++) {Serial2.println("");} // スクロールクリア
Serial2.println(" ロジカラブログ"); delay(100);
Serial2.println(" logikara.blog"); delay(100);
for (int i = 0; i < 5; i++) {Serial2.println(""); delay(100);} // スクロールクリア
Serial2.println(" ∧_∧"); delay(100);
Serial2.println("(´ >ω<`) / 使える・・"); delay(100);
Serial2.println(" U U \ ・・と思う・・"); delay(100);
Serial2.println(" \ \"); delay(100);
Serial2.println(" \ \ カタ"); delay(100);
Serial2.println("カタ \ \"); delay(100);
Serial2.println(""); delay(100);
} else { //ボタンが押されてなければ
M5.dis.drawpix(0, dispColor(20, 20, 20)); //LED(白)
}
delay(100); //100ms待機
}
8.まとめ
「M5Stack」を使用したシリアルデータ表示モニタについて紹介しました。
パソコンがある環境では必要のないものですが、パソコンのない環境でシリアルデータを確認したり、液晶表示とは別に内部の動作状態を確認したい時、シリアル通信のコマンドが正常に送信されているか等、いろいろ便利に使えると思います。
「M5Stack」には3系統のシリアル通信があるのでそれぞれの使い方や、動作について理解するのにも役立つと思います。
今回は等幅フォントの文字数をカウントして改行するという簡易なもので、文字データの判定が3byte文字の1byte目でしか行っていないため改行がずれることがあります。
この場合は「ボタンA」を押して手動でスクロールしてください。
表示できる文字フォントは「ボタンB」でゴシック体(初期状態)「ボタンC」で明朝体が選択できます。
「M5Stack」から送信側に5Vを供給して使うこともできますが、送信側にUSB等で同時に5Vが供給されると微妙な電位差が悪さをする可能性があるので「M5Stack」からの5Vの接続は外しましょう。
「Arduino」で使用する場合は信号電圧が「M5Stack」と違うため5V→3.3Vの電圧レベル変換モジュール等を使用して接続するようにしましょう。
最後に、今回動作確認のために初めて「ASCIIアート」に挑戦しました。
今回作ったシリアルモニターと組み合わせることで、動きのある「ASCIIアート」を作ることもできそうなので、サイト紹介等でショートストーリーを作ってみるのも面白いかなと思いました。(時間があればですが・・・)
使い方はいろいろと幅広いので便利に使ってみてください。
コメント