【M5Stack_Core2】日本語周り

そういえば、日本語入出力に関して、M5Core2で試してなかったのでやってみた。
過去日本語文字をディスプレーに表示することはしていたが、Serial通信を使って入出力してなかったのでその辺の検証

f:id:chiyoh:20201017140832p:plain

f:id:chiyoh:20201017140812p:plain

f:id:chiyoh:20201017140848p:plain

f:id:chiyoh:20201017141312p:plain

やっていることPCで日本語手打ち⇔Arduino IDEモニタで転送⇔M5Core2⇔液晶に日本語表示

  • 日本語(UTF-8) Serial通信の送受信
  • 液晶画面に日本語表示

まずは、こんな感じTeratermだと入力した文字が逐次送信されてしまうため打ち間違いがあるとCore2側で編集しないといけないがまだその環境がまだ整っていない。
逆にArduinoIDEでは、入力BOXを改行キーで1ライン送信なのでその辺は日本語入力やparameter入力としては便利かもしれない。
動かしているスケッチは下記のモノ

//
// smoothFont.ino
// 日本語フォントを使ってみる for M5Core2
// 2020/10/16 ver0.1 chiyo.h
//
#define ESP32
#include <M5Core2.h>
#include <utility/Sprite.h>
#include <stdio.h>

// Create Sprite object "img" with pointer to "tft" object
// M5.Lcdをsprite化する
TFT_eSprite img = TFT_eSprite(&M5.Lcd);

int rxBufferClear(){
  int sBuf = Serial.available();
  int dat;
  // read dummy.
  for(int i=0;i<sBuf;i++){
    dat = Serial.read();
  }
  return sBuf;
}

char inKey(){
  int rSize = Serial.available();
  if(rSize == 0){
    return 0;
  }
  char c = Serial.read();
  rSize--;
  for(int i=0;i<rSize;i++){
    char d = Serial.read();
  }
  return c;
}

int serialOverflowFlag = false;  // Serial通信でオーバーフロー起こしているかどうか

// UTF-8のcode並びの先頭であるか?
int utf8HeaderCheck(char c){
  int cByte;
  if(((c&0x80)^0x00)==0x00){cByte = 1;}       // 1byte
  else if(((c&0xe0)^0xe0)==0x00){cByte = 2;}  // 2bytes
  else if(((c&0xf0)^0xf0)==0x00){cByte = 3;}  // 3bytes
  else if(((c&0xf8)^0xf8)==0x00){cByte = 4;}  // 4bytes
  //else if((c==0xfe)||(c==0xff)){cByte = 0;}   // BOM?
  else cByte = -1;                            // 2~4bytes の続きcode
  return cByte;
}

// あまり検証できていないUTF-8を使った入力
void readSerial(char* buff){
  int cSize=0;               // 初期化であるが毎回行われる

  // rxBuffer確認
  int rSize = Serial.available();
  //Serial.printf("rxbuff size:%d\n",rSize);
  if(rSize==0){
    buff[0] = 0; //NULL
    return;  // 読み取りデータ無し
  }
  // 外部から確認してチェックされるまでそのままアラート出しておく
  if((serialOverflowFlag == true) || (rSize == 256)) serialOverflowFlag = true;

  // 残りrxBufferが3bytes
  while(rSize>3){
    buff[cSize] = Serial.read();
    cSize++;
    rSize--;
  }

  // 残り3bytes処理
  while(rSize>0){
    if(utf8HeaderCheck((char)Serial.peek())<=rSize){
      buff[cSize] = Serial.read();
      cSize++;
      rSize--;
    }else{
      break;
    }
  }
  buff[cSize] = 0; //NULL
  return;  // 読み取りデータ無し
}

void setup() {

  M5.begin();   // 外部I2C off
  Serial.printf("\n");
  Serial.flush();
  rxBufferClear();  // serial read clear.
  delay(50);
  yield();

  // 液晶ディスプレ初期化
  M5.Lcd.fillScreen(TFT_WHITE);  // 画面塗りつぶし
  M5.Lcd.setTextColor(BLACK);    // printカラー指定
  M5.Lcd.setTextSize(1);         // printサイズ指定
  M5.Lcd.clear(WHITE);           // ?
  M5.Lcd.setCursor(0, 0);        // printカーソル指定(初期化:左上)

  // スプライト初期化
  img.createSprite(320,40);      // スプライト作成
  img.fillSprite(TFT_BLACK);     // スプライト塗りつぶし
  img.setTextColor(WHITE);       // printカラー指定
  img.setTextSize(1);            // printサイズ指定

  // "/data/font/NotoSansJPR36.vlw" をSD置きアップロードする
  String fnt = "data/font/NotoSansJPR36";
  //SPIFFS.begin();
  //M5.Lcd.loadFont(fnt, SPIFFS);  // flash memory
  M5.Lcd.setTextFont(2);
  M5.Lcd.setTextSize(2); 
  M5.Lcd.println(" loading japanese font.");
  M5.Lcd.loadFont(fnt, SD);        // SD card
  img.loadFont(fnt, SD);
  M5.Lcd.clear(WHITE);           // ?
  M5.Lcd.setCursor(0,4);
  //Serial.printf("%d,%d\n",M5.Lcd.getCursorX(),M5.Lcd.getCursorY());
  M5.Lcd.println(" 日本語のテスト!");
  M5.Lcd.println(" Japanese font.");
  M5.Lcd.setTextSize(1);         // printサイズ指定

  delay(10);
  yield();
}

void loop() {
  static char buff[256];
  //char c = inKey();
  readSerial(buff);
  if(buff[0] !=0){
    img.fillSprite(BLACK);
    img.setCursor(2,2);
    img.printf(buff);
    img.pushSprite(0,160);
    Serial.printf("%s\n",buff);
  }
  delay(1000);
  yield();
}

まあ、いつものようにその場でガシガシ書いたものなので動作検証用くらいに。

まとめ

  • 表示させている日本語Fontは、processingで作ったモノ。ESP32環境ではttf/otfフォントライブラリが無いのでベクトルフォントではなくラスターフォントになる。よって、Fontを変換しているわけ。
  • ESP32では、日本語のような扱う文字数が多い文字フォントはメインメモリには入らない。SDカードやSPIFSを使ってフラッシュからファイルアクセスする形式になる。
  • 文字数も多いし少し大きめのフォントサイズにするとそれだけのモノクロ無圧縮BMPを作ることになりそれを収めるファイルは大きさになる。
  • 今回使った、Noto Sans JP Regularの36ポイントで0~0xFFFFコードで取り込める数が16057/17850グリフで約16MB大きい。元のサイズが4MBなので4倍に膨らんでいる。
  • 17850文字に足りないのは、UNICODEにも異体字セレクタを扱える。旧字や異字などと呼ばれるやつ斎藤さんとか渡邊さんとかそういう同じコードで違うグリフも掲載しているフォントだからっぽい。
  • 初め、0xFFFF以降のコード化と思ったら、vlwフォーマットは32bits長を対応していることがわかった。しかし、TFT_eSPIのUNICODEの扱いが16bits長っぽいので1面以降を変換することにいまのところ意味(デメリットしか)がない。
  • processingのフォント作りは、ツールのやつでなくてTFT_eSPIの作者のスクリプトの改良版を使っている。SJISの文字全部対応させようとするとどの区域なのか大変そうなのでフォントが持っているCMAPをsearchするようにしたのが下のスクリプト

f:id:chiyoh:20201017144124p:plain

f:id:chiyoh:20201017144138p:plain

【M5Stack_Core2】Serial 入力制御

目的を失ってキャレット制御や、BASICやXon/Xoffなどとっ散らかってしまったのでシンプルに入力を

今回わかったこと
M5Core2で使われている、ESP-IDF3.3はRXbufferは256。FIFOでいったん受信したデータは取り出さない限り消えない。
また、bufferをオーバーするとそのデータは消えていく。Serialクラスでは、Xon/Xoffをサポートしていない。ESP32のハードウエアでは サポートしているっぽい。

//
// inkey
//
// 2020/10/11 chiyo.h
//
#include <M5Core2.h>
#include <utility/Sprite.h>
#include <stdio.h>
#define ESP32
TFT_eSprite spr = TFT_eSprite(&M5.Lcd);

int rxBufferClear(){
  int sBuf = Serial.available();
  int dat;
  // read dummy.
  for(int i=0;i<sBuf;i++){
    dat = Serial.read();
  }
  return sBuf;
}

void setup() {
  M5.begin();   // 外部I2C off
  Serial.printf("\n");
  Serial.flush();
  rxBufferClear();  // serial read clear.
  delay(50);
  yield();
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.clear(WHITE);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.printf("Serial Test!");
  Serial.println("Serial input test.");
  Serial.print(">");
  // スプライト定義
  spr.setColorDepth(8);       // カラーモード
  spr.createSprite(320, 240); // 大きさ
  spr.fillSprite(TFT_WHITE);

  delay(50);
  yield();
}

char inKey(){
  int rSize = Serial.available();
  if(rSize == 0){
    return 0;
  }
  char c = Serial.read();
  rSize--;
  for(int i=0;i<rSize;i++){
    char d = Serial.read();
  }
  return c;
}

uint32_t color = TFT_BLACK;
void selectColor(uint32_t c){
    switch(c){
    case 'B':
      color = TFT_BLUE;
      break;
    case 'R':
      color = TFT_RED;
      break;
    case 'G':
      color = TFT_GREEN;
      break;
    case 'K':
      color = TFT_BLACK;
      break;
  }
}

void loop() {
  char c = inKey();
  if(c != 0){
    Serial.println(c);
    selectColor(c);   // PC端末の押したキーによって制御を変える
    spr.drawChar(0,0,c,color,TFT_WHITE,4);
    spr.pushSprite(64,64);
    spr.fillSprite(TFT_WHITE);
    Serial.print('>');
  }
  delay(10);
  yield();
}

まとめ:毎回parameter変更してコンパイルしなおすよりキーボード制御でモード切替をして動かすほうがいいかな。

【M5Stack_Core2】Serial I/F周り その2

そういえば、昔GPIO0をHにしておけばいいという感じの記事を見た気がした。
しかし、これはRESETを回避する話ではなかったようだ。

f:id:chiyoh:20201011140601p:plain

今回分かったとこスピーカーを使うとMPUのGPIO0と/DTSがショートレベルになること。あまりよろしくない感じ

【M5Stack_Core2】Serial I/F周り

シリアル入出力を自由に使えないとdebugがはかどらないので少し調べた。

f:id:chiyoh:20201011102625p:plain

なぜか回路検証をしていた。

f:id:chiyoh:20201011102859p:plain

test_serial.ino

//
// Serial 周り確認 
//
// 2020/10/11 chiyo.h
//
#include <M5Core2.h>
#include <utility/Sprite.h>
#include <stdio.h>
#include <touch.h>
#define ESP32
TFT_eSprite spr = TFT_eSprite(&M5.Lcd);

int gpio0 = 0;
void setup() {
  M5.begin();   // 外部I2C off
  Serial.printf("\n");
  Serial.flush();
  delay(50);
  yield();
  M5.Lcd.fillScreen(WHITE);
  M5.Lcd.setTextColor(BLACK);
  M5.Lcd.setTextSize(2);
  M5.Lcd.clear(WHITE);
  M5.Lcd.setCursor(10, 10);
  M5.Lcd.printf("Serial Test!");
  
  // スプライト定義
  spr.setColorDepth(8);       // カラーモード
  spr.createSprite(320, 240); // 大きさ
  spr.fillSprite(TFT_WHITE);

  // Serial動作抑制
  Serial.end();
  // WDT無効
  disableCore0WDT();
  disableCore1WDT();

  pinMode(gpio0, INPUT);   // 入力モード
  delay(50);
  yield();
}

void loop() {
  int val = digitalRead(gpio0);
  spr.drawNumber(val, 0, 0, 4);
  spr.pushSprite(0,0);  // グラフ
  spr.fillSprite(TFT_WHITE);
  //Serial.println("");
  delay(200);
  yield();
}

GPIO0は、I2Sスピーカーにもつながっている。 ちょっと調べた感じでは、モノ出力スピーカに対してCTRL信号で、ICのON/OFF及びI2Sの出力R/L切り替えを行う。つまり、ステレオにしたい場合は、このモジュールを2つ用意する。 CTRLを3値制御だがデジタル出力なので右/OFFの2制御になっている。LRCKは、サンプリング周波数及びH/LでR/Lのデータを切り替えを制御。BCLKは、搬送クロック。SADTAはPCMデータ。つまり、スピーカを使っているときにはGPIO0には、サンプリング周波数でMPU側から周波数信号が出ている。

Serial通信に、PC側からデータ溢れチェックのRTSをGPIO0でCTS信号として使えるがそれ用に使っている感じではなさそう。

実際に回路を動かして確認してみる 昔の定番ターミナルにTeraTermというソフトがある。組み込みエンジニアにはおなじみなのかもしれない。昔懐かしいVT100コマンドも対応しているのでArduino IDEモニターではできないような高性能なことがCOMポートに対して可能。WiFi使ってTCP/IP接続の場合もこの端末は使える。で、ここではこいつのマクロを使用してDTS/RTS制御を行う。

f:id:chiyoh:20201011104726p:plain

serial_check.ttl

;
; Serial I/F周り確認
;
; ==============================================
; (0) リセットボタン押す
; ==============================================
;
; ==============================================
; (1)  まずは、状態確認
; ==============================================
getmodemstatus val
sprintf2 str '%d(%x) , result:%d' val val result
messagebox str "(1) Modem status"
;
; GPIO0 = 1
;
; M5側     端末側
; RTS(NC)->CTS(Low)
; DTR(NC)->DSR(Low)
;
;bit
;1  CTS( 送信可)信号がオンです。
;2  DSR( データセットレディ)信号がオンです。
;4  呼び出し信号がオンです。
;8  RLSD( 受信線信号検出)信号がオンです。
;
;  値が0ということは、M5側がRTS off DTR offということ
;

;
; ==============================================
; (2) READY PC端末の正常状態(フロー在りのとき)
; ==============================================
; DTR on(端末ON) RTS on(送信要求)
setdtr 1
setrts 1
messagebox "DTR1 RTS1" "(2) (II)"
;
; GPIO0 = 1
; 
; 特に変化しない
;


;
; ==============================================
; (3) STOP 送信STOP要求 RTS off
; ==============================================
; 
;setdtr 1
setrts 0
messagebox "DTR1 RTS0" "(3) (IV)"
;
; GPIO0 = 0
;
; GPIO0が0に変化
;

;
; ==============================================
; (4) 端末OFF DTR off RTS off
; ==============================================
; ※端末OFFなのにRTS on(送信リクエスト)にはならないので
; 
setdtr 0
setrts 0
messagebox "DTR0 RTS0" "(4) (I)"
;
; GPIO0 = 1
;
; GPIO0が1に変化
;


;
; ==============================================
; (5) RESET DTR off RTS on
; ==============================================
; ※異常状態
; 
setdtr 0
setrts 1
messagebox "DTR0 RTS1" "(5) (III)"
;
; GPIO0 = 1
;
; 回路的には、RESETボタンが押されている状態
; リセットになっているのは、MPUに対してだけなので
; 液晶や周辺回路は変化しない。MPUも立ち上がりedgeで
; リセット動作になるのでここでは変化ない
;


;
; ==============================================
; (6) リセット動作1 DTR off RTS on→off
; ==============================================
;
setrts 0
messagebox "DTR0 RTS0" "(6) (III->I)"
;
; GPIO0 = 1
;


;
; ==============================================
; (7) リセット動作2 DTR off RTS on→off
; ==============================================
;
setdtr 1
setrts 1
setdtr 0
messagebox "DTR0 RTS1" "(7) (II->III)"
setdtr 1
messagebox "DTR1 RTS1" "(7) (III->II)"
;
; リセット動作
;


;
; ==============================================
; (8) リセット動作3 download boot
; ==============================================
; (GIO0=0状態でリセット)
setdtr 1
setrts 1
setdtr 0
messagebox "DTR0 RTS1" "(8) (II->III)"
setdtr 1
setrts 0
messagebox "DTR1 RTS1" "(8) (III->II->IV)"
;
;
; download リセット動作
;


; ==============================================
; (9) リセットリセットボタンを押す
; ==============================================

log.txt

; ==============================================
; (0) リセットボタン押す
; ==============================================


ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:8896
load:0x40080400,len:5816
entry 0x400806ac
M5Core2 initializing...axp: vbus limit off
axp: gpio1 init
axp: gpio2 init
axp: rtc battery charging enabled
axp: esp32 power voltage was set to 3.35v
axp: lcd backlight voltage was set to 2.80v
axp: lcd logic and sdcard voltage preset to 3.3v
axp: vibrator voltage preset to 2v
OK

; ==============================================
; (6) リセット動作1 DTR off RTS on→off
; ==============================================

ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:8896
load:0x40080400,len:5816
entry 0x400806ac
M5Core2 initializing...axp: vbus limit off
axp: gpio1 init
axp: gpio2 init
axp: rtc battery charging enabled
axp: esp32 power voltage was set to 3.35v
axp: lcd backlight voltage was set to 2.80v
axp: lcd logic and sdcard voltage preset to 3.3v
axp: vibrator voltage preset to 2v
OK

; ==============================================
; (7) リセット動作2 DTR off RTS on→off
; ==============================================

ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x17 (SPI_FAST_FLASH_BOOT)
configsip: 0, SPIWP:0xee
clk_drv:0x00,q_drv:0x00,d_drv:0x00,cs0_drv:0x00,hd_drv:0x00,wp_drv:0x00
mode:DIO, clock div:1
load:0x3fff0018,len:4
load:0x3fff001c,len:1044
load:0x40078000,len:8896
load:0x40080400,len:5816
entry 0x400806ac
M5Core2 initializing...axp: vbus limit off
axp: gpio1 init
axp: gpio2 init
axp: rtc battery charging enabled
axp: esp32 power voltage was set to 3.35v
axp: lcd backlight voltage was set to 2.80v
axp: lcd logic and sdcard voltage preset to 3.3v
axp: vibrator voltage preset to 2v
OK


; ==============================================
; (8) リセット動作3 download boot
; ==============================================


ets Jul 29 2019 12:21:46

rst:0x1 (POWERON_RESET),boot:0x7 (DOWNLOAD_BOOT(UART0/UART1/SDIO_REI_REO_V2))
waiting for download

この確認と少し調べてわかったこと

  • M5がUSB接続されていてフリーズした場合、PC端末からリブートが行える。
  • シリアル回りの回路構成は、ESP32のプログラマ(マイコンにプログラムをUPLOADするための仕組み)なのでほとんど共通
  • USBケーブルを抜き差しするときにrebootがかかるのはRTOSのWDTかこの回路が原因

【M5Stack_Core2】Arduino-esp32-1.0.4の比較

M5Stack_Core2で使用しているライブラリの話

Arduinoがインストールされている環境やパスを追っていくとesp32\1.0.4というものを使用していることがわかります。

これは、 github.com を使用しているみたいです。

また、M5Stack_Core2になってからM5Stack社はカスタマイズ版のesp32\1.0.4を独自に展開しているみたいです。

今回は、’ESP32 Arduino 1.0.4 (IDF 3.2)’とgithubにあるファイルとArduinoにインストールされているファイルとM5Stack社はカスタマイズ版の3つについて比較してみたいと思います。

Arduino-esp32-1.0.4の比較

githubから落としてきたTAG1.04のデータ とArduino-esp32フォルダのデータ

  • 1.0.4\docsフォルダが無い
  • 1.0.4\libraries\AzureIoTがある
  • 1.0.4\packageが無い
  • 1.0.4\tools\get.exe get.py platformio-build.py が無い
  • 1.0.4\ CMakeLists.txt component.mk Kconfig.projbuild libraries LICENSE.md Makefile.projbuild package.json README.mdが無い
  • platform.txt 内容が違う versionとtools.esptool_py.pathの値

githubから落としてきたTAG1.04のデータ とM5Stackのカスタマイズ版 Arduino-esp32フォルダのデータ

  • 1.0.4\cores\esp32\core_version.h が存在する
  • 1.0.4\docsフォルダが無い
  • 1.0.4\libraries\AzureIoTがある
  • 1.0.4\packageが無い
  • 1.0.4\tools\get.exe get.py platformio-build.py が無い
  • 1.0.4\variants 他製品のファイルが無い
  • 1.0.4\variants のm5stack_core_esp32 m5stack_fire m5stack_cのファイルの改行コードが違う
  • 1.0.4\ CMakeLists.txt component.mk Kconfig.projbuild libraries LICENSE.md Makefile.projbuild package.json README.mdが無い
  • 1.0.4\boards.txt m5stack製品のみになっている。
  • platform.txt 内容が違う versionとtools.esptool_py.pathの値

Arduino-esp32フォルダのデータ とM5Stackのカスタマイズ版 Arduino-esp32フォルダのデータ

  • 1.0.4\variants 他製品のファイルが無い
  • 1.0.4\variants のm5stack_core_esp32 m5stack_fire m5stack_cのファイルの改行コードが違う
  • 1.0.4\boards.txt m5stack製品のみになっている。
  • platform.txt 内容が違う name=M5Stack Arduinoになっている

結果的には、

公式?ファイルは、

  • ビルドできるファイルがある。
  • libraries\AzureIoTがある
  • docsフォルダがある

過去にM5Stack_gray等でインストールしたときに追加されたファイルは、

  • ビルドできるファイル
  • libraries\AzureIoT
  • docsフォルダ が削除されている

さらに、M5Stackのカスタマイズ版では

  • M5Stack以外のESP32製品情報のカット
  • Core2の設定が追加されている