M5StickCでI2C通信をする

I2C通信を実験してみました。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

概要

ESP32は最大2組のI2Cが可能で、M5StickCは内部で1つ使っています。

入力専用のIO36を除き、外部接続可能な4PINはどんな組み合わせでもI2Cで通信が可能でした。

接続方法

M5StickCは上側に外部接続用のピンソケットと、下側にGrove端子がついています。IO26とIO0のような組み合わせも可能でしたが、通常はピンソケットのIO0とIO26、Grove端子のIO32とIO33の組み合わせで使うと思います。

おすすめピンアサイン

  • ピンソケット SDA:IO0, SCL:26
  • Grove端子 SDA:IO32, SCL:33

GroveのI2C端子が上記のアサインなので、逆に使うと混乱します。ピンソケット側はどちらでも構わないのですが、OfficialのI2Cを使ったHATがこのピンアサインだったので、こちらを使ったほうが無難だと思います。

同時に2系統使えるの?

内部のI2C通信を使わないのであれば、同時に利用が可能でした。

void setup() {
  M5.begin();

  Wire.begin(32, 33);
  Wire1.begin(0, 26);
}

Wireが1系統目のI2C通信で、Wire1が2系統目のI2C通信です。通常Wire1は電源管理などの内部接続用I2Cとして使われています。

M5.begin()で電源管理の初期化をしてしまえば、あとは接続していなくても問題ないかと思いますので、Wire1.begin()で別のピンにアサインしなおします。

3系統同時に使えないの?

#include <M5StickC.h>

void setup() {
  M5.begin();

  Wire.begin(32, 33);
}

void loop() {
  byte error, address;
  int nDevices;

  Serial.println("Scanning... Wire");

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    Wire.beginTransmission(address);
    error = Wire.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  Serial.println("Scanning... Wire1");
  Wire1.begin(0, 26);

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    Wire1.beginTransmission(address);
    error = Wire1.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  Serial.println("Scanning... Wire1-2");
  Wire1.begin(21, 22);

  nDevices = 0;
  for (address = 1; address < 127; address++ )
  {
    Wire1.beginTransmission(address);
    error = Wire1.endTransmission();

    if (error == 0)
    {
      Serial.print("I2C device found at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.print(address, HEX);
      Serial.println("  !");

      nDevices++;
    }
    else if (error == 4)
    {
      Serial.print("Unknown error at address 0x");
      if (address < 16)
        Serial.print("0");
      Serial.println(address, HEX);
    }
  }
  if (nDevices == 0)
    Serial.println("No I2C devices found\n");
  else
    Serial.println("done\n");

  delay(5000);
}

I2Cのアドレススキャナで検証しましたが、使う前にbigin()でアドレス指定をすれば使えそうです。

ただ、複数使うのはトラブルになりそうなので、可能であればピンソケットかGroveのどちらか1系統だけを使ったほうが安全だと思います。

ESP32のPWM出力は255が最大じゃなかった

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

ledcWrite()の指定がちょっとモヤッていたのだが、上記をみてやっぱり255じゃないのがわかりました。

ESP32のPWM出力について

ESP32でPWM出力を行う場合には、事前に周波数と分解能をセットします。

#include <M5StickC.h>
 
int PIN = 26;
int PWMCH = 0;
 
void setup() {
  M5.begin();
 
  pinMode(PIN, OUTPUT);
  ledcSetup(PWMCH, 7812.5, 8); // 7812.5Hz, 8Bit(256段階)
  ledcAttachPin(PIN, PWMCH);
  ledcWrite(PWMCH, 128);  //  50%(1.7V)
}
 
void loop() {
}

上記の場合、ledcSetup(PWMCH, 7815.5, 8)で7812.5Hzの8ビット精度です。

7812.5Hzは1秒間に7812.5回の周波ですので、1回あたり128usになります。

上記がオシロスコープの結果ですが、1マスが10usで、ONとOFFのワンセットで13マス弱ですので128usぐらいですね。

8ビット精度ってことは256段階ですから、128usを更に256分割の0.5us単位で制御していることになります。

上記がledcWrite(PWMCH, 1)の結果です。128のときには256分の128なので、50%が電圧出力でしたが、今回は256分の1の出力になっています。

さらに拡大した図ですが、1マス100nsなので0.1usが5マスの0.5us間のパルスが出力されています。ledcWrite(PWMCH, 2)にすると倍の1us間のパルスが出力されるはずです。

最大値は8ビット精度の場合には256になります。DACは0から255までですが、8ビットの場合PWMは0から256までの範囲になるので注意してください。

分解能別最大周波数

Bit分解能最大周波数
16655361,220.70Hz
15327682,441.41Hz
14163844,882.81Hz
1381929,765.63Hz
12409619,531.25Hz
11204839,062.50Hz
10102478,125.00Hz
9512156,250.00Hz
8256312,500.00Hz
7128625,000.00Hz
6641,250,000.00Hz
5322,500,000.00Hz
4165,000,000.00Hz
3810,000,000.00Hz
2420,000,000.00Hz
1240,000,000.00Hz

最大周波数を利用したい場合にはビット数1で40MHzになります。この場合には分解能が2ですので、ONとOFFが交互にくる50%の矩形波しか出力できません。

一番分解能を上げると16ビットで1220.70Hzまで周波数が下がります。8ビットの場合には312.5KHzですので、この数値以下を指定して利用しましょう。

参考サイト

M5StickCのIOについて調べてみた

とりあえず1PIN単位で動くものだけ調べてみました。

※(2019/08/30)ごめんなさい、嘘書いてありました。アナログのpinMode()設定が違っていたので入力値がおかしかったみたいです。

最新情報はM5StickC非公式日本語リファレンスを確認おねがいします。

PIN配置

GROVEのVOUTは5Vでした。しなしながらIO32とIO33のアナログ入力は3.3Vまでなので、アナログのGROVEセンサーとかをつなげると物によってはちゃんと動かない気がします。

PIN設定

PINIOMapFunction
IO26I/OExtended IO portGPIO26, DAC_2, ADC2_CH9, RTC_GPIO7, EMAC_RXD1
IO36(SENSOR_VP)IExtended IO portGPIO36, ADC1_CH0, RTC_GPIO0
IO0I/OMicrophone SCL
Extended IO port
ADC2_CH1, TOUCH1, RTC_GPIO11, CLK_OUT1, EMAC_TX_CLK
IO32I/OGROVE SDA32K_XP (32.768 kHz crystal oscillator input), ADC1_CH4, TOUCH9, RTC_GPIO9
IO33I/OGROVE SCL32K_XN (32.768 kHz crystal oscillator output), ADC1_CH5, TOUCH8, RTC_GPIO8

外部からアクセスができる上記5PINが調査対象です。

調査結果

PINdigitalRead()analogRead()touchRead()dacWrite()digitalWrite()ledcWrite()
IO26NG
IO36NGNGNGNG
IO0NGNGNG
IO3233NG
IO3332NG

IO26はADC2_CH9に繋がっているのに、Wi-fi使っていない場合でもアナログ入力できませんでした。上側にあるポートからはアナログ入力できなそうですね。

タッチセンサーがまた微妙で、ESP32のデータシートがおそらく間違っていて、内部的に逆に接続されている気がします。

調査方法

digitalRead() デジタル入力 0(1.65V未満) or 1(1.65V以上)

#include <M5StickC.h>

int PIN = 32;

void setup() {
  M5.begin();

  pinMode( PIN, INPUT);
}

void loop() {
  Serial.printf("%04d\n", digitalRead(PIN) );
  delay(500);
}

上記コードのPIN変数を変えていって実験しました。digitalRead()はどのポートでも利用できます。

analogRead() アナログ入力 0(0V)-4095(3.3V)

#include <M5StickC.h>

int PIN = 32;

void setup() {
  M5.begin();

  pinMode(PIN, ANALOG);
}

void loop() {
  Serial.printf("%04d\n", analogRead(PIN) );
  delay(500);
}

アナログ入力はIO0が正しく取得できません。内部的にプルアップされている関係で、0と4095のどちらかの数値になります。

touchRead() 静電容量取得 0に近いほうがタッチ

#include <M5StickC.h>

int PIN = 32;
boolean touched = false;
int threshold = 16;

void gotTouch() {
  touched = true;
}

void setup() {
  M5.begin();

  pinMode( PIN, INPUT);
  touchAttachInterrupt(PIN, gotTouch, threshold);
}

void loop() {
  if (touched) {
    Serial.println("touch!");
    touched = false;
  }
  Serial.printf("%4d\n", touchRead(PIN) );
  delay(500);
}

touchRead()は触ると数値が小さくなるので、触らないときの数字と、触って下がったときの数字の中間か、やや低い値をthresholdに設定してください。

タッチは初めて実験してみましたが、混乱しました。IO32とIO33の結果が逆でした。

コードを見ても、データシートも見ても問題ないはずだったので、他のESP32 Devボードで実験してみたら、やっぱり逆です。

たぶんデータシートが間違っていますね。

typedef struct {
    uint8_t reg;      /*!< GPIO register offset from DR_REG_IO_MUX_BASE */
    int8_t rtc;       /*!< RTC GPIO number (-1 if not RTC GPIO pin) */
    int8_t adc;       /*!< ADC Channel number (-1 if not ADC pin) */
    int8_t touch;     /*!< Touch Channel number (-1 if not Touch pin) */
} esp32_gpioMux_t;

こんな構造体があって、ArduinoではGPIOを管理していました。

PINregrtcadctouch
00x4411111
10x88-1-1-1
20x4012122
30x84-1-1-1
40x4810100
50x6c-1-1-1
60x60-1-1-1
70x64-1-1-1
80x68-1-1-1
90x54-1-1-1
100x58-1-1-1
110x5c-1-1-1
120x3415155
130x3814144
140x3016166
150x3c13133
160x4c-1-1-1
170x50-1-1-1
180x70-1-1-1
190x74-1-1-1
200x78-1-1-1
210x7c-1-1-1
220x80-1-1-1
230x8c-1-1-1
240-1-1-1
250x24618-1
260x28719-1
270x2c17177
280-1-1-1
290-1-1-1
300-1-1-1
310-1-1-1
320x1c949
330x20858
340x1446-1
350x1857-1
360x0400-1
370x0811-1
380x0c22-1
390x1033-1

こんな感じのマトリクスで、内部レジスタのアドレスとか番号が並んでいます。

PINregrtcadctouch
320x1c949
330x20858

該当部分だけ抜きですと、RTCとTouchとかはPIN番号大きい方から割り当てていますが、ADCは逆です。Touchもデータシート上は上記の記述ですが、実際の実装はIO32がT8でIO33がT9になっていると思われます。

ここ以外のいろんな場所でも逆に定義されているので、ライブラリは修正されない気もします。

GROVE – タッチセンサ

一応商品としてはありますので、使うときにはIO32を取得してください。

dacWrite() アナログ出力 0(0V)-255(3.3V)

#include <M5StickC.h>

int PIN = 26;

void setup() {
  M5.begin();

  pinMode(PIN, OUTPUT);
}

void loop() {
  dacWrite(PIN, 0);
  delay(500);
  dacWrite(PIN, 128);
  delay(500);
  dacWrite(PIN, 255);
  delay(500);
}

dacWrite()はIO26でしか使えません。(内部的にはIO25でも可能)

IO26からの出力にLEDと抵抗に接続してものをオシロスコープで測定した結果です。若干電圧低下していますが、概ね255で3.3V程度、128でその半分の電圧が出力されています。

digitalWrite() デジタル出力 LOW(0V) or HIGH(3.3V)

#include <M5StickC.h>

int PIN = 32;

void setup() {
  M5.begin();

  pinMode(PIN, OUTPUT);
}

void loop() {
  digitalWrite(PIN, HIGH);
  delay(500);
  digitalWrite(PIN, LOW);
  delay(500);
}

IO36はIOがInputのみなので、出力には使えませんが、それ以外のPINでは使えました。

ledcWrite() PWM出力

#include <M5StickC.h>

int PIN = 32;
int PWMCH = 0;

void setup() {
  M5.begin();

  pinMode(PIN, OUTPUT);
  ledcSetup(PWMCH, 12000, 8);
  ledcAttachPin(PIN, PWMCH);
}

void loop() {
  ledcWrite(PWMCH, 0);    //   0%(0.0V)
  delay(500);
  ledcWrite(PWMCH, 128);  //  50%(1.7V)
  delay(500);
  ledcWrite(PWMCH, 256);  // 100%(3.3V)
  delay(500);
}

PWMはdigitalWrite()で出力を定期的にON/OFFさせることで、擬似的なdacWrite()に似た動きになります。PWMは4PIN同時に利用することが可能で、細かい設定はledcSetup()で行っています。サンプルは周波数が 12KHz で分解能が8ビット(0-256)になっています。

8ビットの場合、256分割してそのうち何個を出力するかの指定なので、出力しないの0と全部出力するの256までの257段階で制御が可能です。

上記はキャプチャ用に周波数50Hzに落として実験しましたが、上記のように128(50%)を指定するとONとOFFが交互にきて、平均すると半分の電圧相当になります。

LEDの明るさ調整とかであればdacWrite()でなくて、PWM制御で十分明るさが変わります。

まとめ

GROVEの電源が5Vなんで、ちょっと注意が必要そうですね。あと本家だといろいろな拡張HATが開発されているようですので、楽しみです。

今後はI2Cとか2PIN以上必要な通信を調べていきたいと思います。

Arduino(M5StickC)でefont Unicodeフォント表示 完結編

/efont/さんのUnicode Fontを利用して、Arduino用のフォントライブラリを作ってみました。

いろいろ実験した結果、文字単位で読み込むかを決めて、フォント用のテーブルから探す方式がメモリ効率と速度のバランスが一番良かったです。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

利用フォント

http://openlab.ring.gr.jp/efont/unicode/

上記のf16.bdfとb16.bdfを利用させていただき、16ピクセルフォントを作成しました。

作成物

https://github.com/tanakamasayuki/efont

フォントサイズ

対象オプション文字数フォント容量
すべてefontEnableAll.h21,727738,718
AsciiefontEnableAscii.h1916,494
CJK漢字efontEnableCJK.h19,379658,886
簡体字中国語efontEnableCn.h18,077614,618
日本語efontEnableJa.h10,835368,390
日本語(常用+α)efontEnableJaMini.h4,107139,638
韓国語efontEnableKr.h8,319282,846
繁体字中国語efontEnableTw.h13,555460,870

/efont/は2万文字強収録されており、256文字単位での読み込みだと使っていないエリアがたくさんあって無駄になっていたので、1文字単位で読み込んでいます。

16ピクセルフォントなので、1文字あたり32Byteの字形データ(PROGMEM領域)と、UTF16の文字コードテーブルで2Byte使うので、1文字あたり34Byteです。

このサイズだったら全部読み込んで、メモリが足りなくなってから使わない文字を減らす運用でも大丈夫かもしれません。

ちなみに0(U+0030)の文字が有効になっていない場合には、フォントの指定をしていないとみなして、efontEnableAll.hを読み込むようにしています。

実行結果

サンプルコード

#include <M5StickC.h>
#include "efontEnableAll.h"
//#include "efontEnableAscii.h"
//#include "efontEnableCJK.h"
//#include "efontEnableJa.h"
#include "efont.h"
#include "efontM5StickC.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.setCursor(0, 0);

  printEfont("Hello", 0, 16*0);
  printEfont("こんにちは", 0, 16*1);
  printEfont("你好", 0, 16*2);
  printEfont("안녕하세요", 0, 16*3);
  printEfont("Доброе утро", 0, 16*4);
  printEfont("Päivää", 0, 16*6);
  printEfont("Здравствуйте", 0, 16*7);
}

void loop() {
}

ビルド結果

最大1310720バイトのフラッシュメモリのうち、スケッチが977224バイト(74%)を使っています。
最大327680バイトのRAMのうち、グローバル変数が14652バイト(4%)を使っていて、ローカル変数で313028バイト使うことができます。

ちなみにM5StickCのスケッチ例「HelloWorld」との比較。

スケッチフラッシュRAM
efont977,22414,652
HelloWorld226,74814,532
750,476120

んー、もう少しグローバル変数使っている気がするけれど、調べたらESP32実機だとconstつけたデータは、PROGMEMをつけていなくてもフラッシュ領域になるみたいですね。

まとめ

描画が遅いのはちょっとどうにかしないといけないのですが、描画周りはちょっと後にして他の機能を検証する予定です。

[実験] Arduino(M5StickC)でefont Unicodeフォント表示 SPIFFS版

/efont/さんのUnicode Fontを利用して、Arduino用のフォントライブラリを作ってみました。

PROGMEMだとプログラム転送時に毎回大きなフォントデータも転送する必要があるので、一度転送すればよいSPIFFSで作ってみました。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

注意

こちらは実験ですので、実際に使う場合には以下のページを参考にしてください。

利用フォント

http://openlab.ring.gr.jp/efont/unicode/

上記のf16.bdfとb16.bdfを利用させていただき、16ピクセルフォントを作成しました。

作成物

https://github.com/tanakamasayuki/efontUTF16spiffs

上記にとりあえず置いてみました。

フォントサイズ

1.4Mぐらいになりました。SPIFFSなので/efont/が収録しているUTF16のBMP文字はすべて取り込んであります。

実行結果

PROGMEM版と同じなのですが、最初1文字単位だと遅かったので、複数の文字を転送してから描画しています。SPIFFSだとやっぱりファイルオープンに時間がかかりますね。

サンプルコード

#include <M5StickC.h>
//#define EFONT_BUF_SIZE 64
#include "efontUTF16spiffsM5StickC.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.setCursor(0, 0);

  printEfont("新しい朝が来た希望の朝がabcd12345()+-12345", 0, 16*0);
}

void loop() {
}

PROGMEM版と比べると、フォント指定がなくなって、バッファサイズの指定が増えています。4とかに変更して実行してもらえればわかりますが、結構遅いので最大文字数より大きくしたほうがいいです。

まとめ

描画自体も遅いのですが、やっぱりSPIFFSのファイルロード時間も結構気になってしまいます。

必要な文字単位でPROGMEMに保存する方が、データ検索に時間がかかりますが使いやすいかもしれないですね。ちょっとそっちも検討してみます。

[実験] Arduino(M5StickC)でefont Unicodeフォント表示 PROGMEM版

/efont/さんのUnicode Fontを利用して、Arduino用のフォントライブラリを作ってみました。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

注意

こちらは実験ですので、実際に使う場合には以下のページを参考にしてください。

利用フォント

http://openlab.ring.gr.jp/efont/unicode/

上記のf16.bdfとb16.bdfを利用させていただき、16ピクセルフォントを作成しました。

作成物

https://github.com/tanakamasayuki/efontUTF16progmem

上記にとりあえず置いてみました。

フォントサイズ

対象フォントファイル名フォント容量
UNICODE(BMP)全部1,441,792
フォント無しefontUTF16DisableAll.h0
漢字以外efontUTF16DisableCJK.h507,904
ハングル以外efontUTF16DisableHang.h1,081,344
漢字のみefontUTF16OnlyCJK.h1,007,616
常用漢字のみefontUTF16OnlyMiniJapanese.h753,664

全部入れたところ、1.4Mぐらいになりましたので、少し減らすオプションを作りました。指定ファイルを事前に読み込むことで、対象フォントを絞り込むことができます。

256文字単位で管理しているので、ざっくりとしか間引けませんので常用漢字など飛び飛びでいろんな場所に分散しているものは、1文字単位で読み込んだ方が良さそうです。

実行結果

サンプルコード

#include <M5StickC.h>
#include "efontUTF16progmemM5StickC.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.setCursor(0, 0);

  printEfont("Hello", 0, 16*0);
  printEfont("こんにちは", 0, 16*1);
  printEfont("你好", 0, 16*2);
  printEfont("안녕하세요", 0, 16*3);
  printEfont("Доброе утро", 0, 16*4);
  printEfont("Päivää", 0, 16*6);
  printEfont("Здравствуйте", 0, 16*7);
}

void loop() {
}

上記でUNICODE(BMP)全部が読み込まれます。描画部分は端末依存なのでM5StickC以外の端末で使う場合にはefontUTF16progmemM5StickC.h相当の処理を自作してください。

#include <M5StickC.h>
#include "efontUTF16progmemOnlyMiniJapanese.h"
#include "efontUTF16progmemM5StickC.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.setCursor(0, 0);

  printEfont("Hello", 0, 16*0);
  printEfont("こんにちは", 0, 16*1);
}

void loop() {
}

常用漢字しか使わないのであれば、上記のようにすればプログラムサイズを減らすことができます。

容量的にはSPIFFSで1.4Mのフォントデータを転送して使うのが楽そうな気がしますのでSPIFFSを使ったバージョンも作ってみたいと思います。

M5StickCのPartition Tablesを調べる

/efont/を使うときに、プログラムサイズを広げたかったので調べました。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

メモリーマップ

https://docs.espressif.com/projects/esp-idf/en/latest/api-guides/general-notes.html#application-memory-layout

上記がデフォルトのメモリマップです。First-stage bootloaderが最初に起動して、Second-stage bootloaderをメモリに展開して、Second-stage bootloaderを実行します。

その後Partition tablesを見て、app0からプログラムをロードして実行するのが通常の流れですが、ここは変更しないところなので、知らなくても大丈夫です。

Arduino IDEでPartition Tablesの指定方法

上記のメニューから、初期値、No OTA、Minimal SPIFFSが選択できるのが標準の状態で、自分で作成することで、「No OTA Minimal SPIFFS」などの自分専用の設定を追加することができます。

Arduino IDEで指定できるPartition Tables

NamedefaultNo OTAMinimal SPIFFS
nvs20,48020,48020,480
otadata8,1928,1928,192
app01,310,7202,097,1521,966,080
app11,310,72001,966,080
eeprom4,0964,0964,096
spiffs1,503,2322,027,520192,512

上記が標準で入っている設定値です。

Partition解説

nvs(Non-volatile storage)

不揮発性ストレージで、電源を切っても保存される領域です。nvs_get_blob()関数などにより、名前をつけた値を保存することができます。ここのサイズはあまり変更しないようです。

Wi-Fiのアクセスポイントを保存するときとかに利用したりします。ただし、暗号化されていないのと、中身を取り出すことができるので、パスワードなどを保存して置くと、抜き出される可能性があります。

otadata

OTA(Over The Air)はWi-Fi経由でプログラムを更新する仕組みで、そのためのプログラムが入っている領域です。OTAを利用しない場合でも、この領域は必要で、固定サイズになります。

app0

実際のプログラムが入っている領域です。プログラムの領域が足りなくなった場合には、他の領域を減らして、ここの領域を増やすことができます。

通常は暗号化されていませんが、暗号化することも可能ですがちょっと複雑です。

app1

OTAを利用する場合には、app0とapp1の交互にプログラムを書き込んでいき、書き込みに失敗した場合でも、書き換え前のプログラムが残っている状態にします。

そのためapp0とapp1の大きさは同じにする必要があります。ただしOTAを利用しない場合には0で構いません。

eeprom(Electrically Erasable Programmable Read-Only Memory)

こちらもnvsと同じく不揮発性ストレージです。名前でアクセスする機能はなく、アドレス単位でのアクセスになります。構造体を使うことで簡単に複数の設定を保存したり、取得したいすることができます。

こちらも、通常は暗号化されていないのと、中身を取り出すことができるので、パスワードなどを保存して置くと、抜き出される可能性があります。

spiffs(Serial Peripheral Interface Flash File System)

SPIバス経由で接続されている内部フラッシュを利用した、ファイルシステムです。

ESP-WROOM-32 ( ESP32 ) SPIFFS アップローダープラグインの使い方

上記を参考に、dataフォルダを作って、ESP32 Sketch Data Uploadを実行するとフォルダの中身をESP32に転送してくれます。

spiffsの利点として、一度転送すれば上書きされることがないので、プログラムの転送サイズが減ります。OTAなどを利用した場合、プログラム内部にデータを内蔵しておくとapp0とapp1で同じデータが存在するので、spiffsにデータを置くことで容量を有効に使うことができます。

欠点として、初期状態で転送ツールがセットアップされないので、使うまでがちょっと面倒です。サイズが小さい場合にはプログラムの中に内蔵したほうがシンプルになると思います。

自分でPartition設定を作る

あまり自分でPartitionを編集する必要はないのですが、標準で用意されているプログラム容量は2Mまでなので、もっと大きなプログラムを転送したい場合には、自分で設定する必要があります。

C:\Users\%username%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.2\tools\partitions

Windowsの場合には、上記にPartition Tablesが保存されています。ベースになるものをコピーして、名前を変更してから書き換えます。

# Name,   Type, SubType, Offset,  Size, Flags
nvs,      data, nvs,     0x9000,  0x5000,
otadata,  data, ota,     0xe000,  0x2000,
app0,     app,  ota_0,   0x10000, 0x300000,
eeprom,   data, 0x99,    0x310000,0x1000,
spiffs,   data, spiffs,  0x311000,0xEF000,

上記がOTAを使わなくして、最大限app0のサイズを大きくしたものです。ただし各Partition最大が3Mまでの様で、3M以上を指定しても3Mとして動いています。

そのため、残りをspiffsに割り当てています。サイズを変更する場合には、増減したPartitionの次の領域のOffsetなどもずれるので、自分で計算して更新する必要があります。

ESP32はフラッシュが4Mですので、最後のOffsetとSizeを足した結果が4M以下になっている必要があります。

Arduino IDEに登録する

C:\Users\%username%\AppData\Local\Arduino15\packages\esp32\hardware\esp32\1.0.2\boards.txt

Windowsの場合には、上記に各ボードの設定ファイルがあるので、これを書き換えます。これはESP32のライブラリが更新されると上書きされるので、更新された場合には再度編集する必要があります。

m5stick-c.menu.PartitionScheme.default=Default
m5stick-c.menu.PartitionScheme.default.build.partitions=default
m5stick-c.menu.PartitionScheme.no_ota=No OTA (Large APP)
m5stick-c.menu.PartitionScheme.no_ota.build.partitions=no_ota
m5stick-c.menu.PartitionScheme.no_ota.upload.maximum_size=2097152
m5stick-c.menu.PartitionScheme.min_spiffs=Minimal SPIFFS (Large APPS with OTA)
m5stick-c.menu.PartitionScheme.min_spiffs.build.partitions=min_spiffs
m5stick-c.menu.PartitionScheme.min_spiffs.upload.maximum_size=1966080
m5stick-c.menu.PartitionScheme.no_ota_min_spiffs=No OTA Minimal SPIFFS (Large APPS without OTA)
m5stick-c.menu.PartitionScheme.no_ota_min_spiffs.build.partitions=no_ota_min_spiffs
m5stick-c.menu.PartitionScheme.no_ota_min_spiffs.upload.maximum_size=3145728

最後の3行が追加した行です。既存の行に追加することで新しい設定が追加されます。

設定を反映させるためにはArduino IDEを再起動する必要がありますので、再起動したらメニューに追加されているはずです。

[実験] M5StickCで/efont/を使ってみた

東雲フォントを使おうかと思いましたが、どうせならUNICODEが使える/efont/を使えるか検証してみました。

http://openlab.ring.gr.jp/efont/

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

注意

こちらは実験ですので、実際に使う場合には以下のページを参考にしてください。

結果

できました!

16ドットフォントを入れてみましたが、これなら十分実用できる品質ですね。

サンプルコード

#include <M5StickC.h>
#include "efontUTF16.h"
#include "efontUTF16M5StickC.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(0);
  M5.Lcd.setCursor(0, 0);

  printEfont("新しい朝が来た希望の朝が");
  printEfont("新しい朝", 0, 16*4);
  printEfont("新しい朝", 0, 16*6, 2);
}

void loop() {
}

自作ライブラリ部分はまだ公開用に手をいれないといけないので、今後公開する予定です。フォントデータはベタッとUTF16の全フォントデータをフラッシュ領域に読み込んでいます。

全部だと2M弱、ハングル文字あたりを削ると1.3Mぐらい。コードサイズ的に通常だと動かないのでNo OTAとか、プログラム領域が大きいモードにしないと動きません。

転送時間も結構かかるので、SPIFFSから読み込むバージョンも作ってみたいと思います。

[実験] M5StickCでArduino用美咲フォントライブラを使ってみた

とりあえず、気軽に使えそうなライブラリで日本語実験です。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

注意

こちらは実験ですので、実際に使う場合には以下のページがおすすめです。

利用 ライブラリとフォント

上記をお借りいたしました。

コード

#include <M5StickC.h>
#include "misakiUTF16.h"

void setup() {
  M5.begin();
  M5.Lcd.setRotation(3);
  M5.Lcd.setCursor(0, 0);
  
  writeKnj("新しい朝がきた希望の朝が\n123\n");

  M5.Lcd.setTextSize(2);
  writeKnj("新しい朝がきた希望の朝が\n123\n");

  M5.Lcd.setTextSize(3);
  M5.Lcd.setTextColor(RED, BLUE);
  writeKnj("新しい朝がきた希望の朝が\n123\n");
}

void loop() {
}

void writeKnj(char *str) {
  int posX = M5.Lcd.getCursorX();
  int posY = M5.Lcd.getCursorY();
  uint8_t textsize = M5.Lcd.textsize;
  uint32_t textcolor = M5.Lcd.textcolor;
  uint32_t textbgcolor = M5.Lcd.textbgcolor;
  
  byte font[8];
  
  while( *str != 0x00 ){
    // 改行処理
    if( *str == '\n' ){
      // 改行
      posY += 8 * textsize;
      posX = M5.Lcd.getCursorX();
      str++;
      continue;
    }

    // 文字横幅
    int width = 8 * textsize;
    if( *str < 0x80 ){
      // 半角
      width = 4 * textsize;
    }

    // フォント取得
    str = getFontData( font, str );

    // 背景塗りつぶし
    M5.Lcd.fillRect(posX, posY, width, 8 * textsize, textbgcolor);

    // 取得フォントの描画
    for (uint8_t row = 0; row < 8; row++) {
      for (uint8_t col = 0; col < 8; col++) {
        if( (0x80 >> col) &amp; font[row] ){
          int drawX = posX + col * textsize;
          int drawY = posY + row * textsize;
          if( textsize == 1 ){
            M5.Lcd.drawPixel(drawX, drawY, textcolor);
          } else {
            M5.Lcd.fillRect(drawX, drawY, textsize, textsize, textcolor);
          }
        }
      }
    }

    // 描画カーソルを進める
    posX += width;
  }

  // カーソルを更新
  M5.Lcd.setCursor(posX, posY);
}

実行結果

さすがに8ドットフォントだと文字が小さいですね。東雲フォントの16ドットぐらいだときれいに表示されるかな?

実験としては描画は問題なさそうなので、もう少し他のフォントで実用レベルのライブラリを作りたいと思います。

SPIFFSは事前転送とかが面倒そうなので、オンメモリで動かすことを前提として進めていく予定です。

[実験] M5StickC組み込み漢字をテスト

実はM5StickCのライブラリには組み込みの漢字フォントが入っています。

※現時点の情報ですので、最新情報はM5StickC非公式日本語リファレンスを確認してください。

注意

実際に漢字などのフォントを利用する場合には、以下のページがおすすめです。

フォントデータについて

PROGMEM指定されているので、利用するときにはRAMではなくて、FLASHに保存されていて、プログラムからの参照がなければ、実際には転送されないみたいです。

#include <M5StickC.h>

void setup() {
  Serial.println( sizeof( HZK16 ) );
}

void loop() {
}

ちなみに上記コードだと267616って返ってきますが、実際のフォントデータはありません。

#include <M5StickC.h>

void setup() {
  for( int i = 0 ; i < 3 ; i++ ){
    Serial.println( HZK16[i] );
  }
}

void loop() {
}

上のコートでもスケッチのサイズが増えていないので、データはありません。

#include <M5StickC.h>

void setup() {
  for( int i = 0 ; i < 4 ; i++ ){
    Serial.println( HZK16[i] );
  }
}

void loop() {
}

for文のループ回数を4回以上にすると、スケッチサイズが増えてフォントがFLASH領域に転送されます。

これはコンパイラの最適化とかの関係なのかな?

ちなみにfor文じゃなくて中身の行を並べても、スケッチサイズは増えませんでしたので、フォントは転送されていません。

M5.Lcd.loadHzk16();

正規のフォントロード関数を呼び出すと、実際に転送されるので使うときにはちゃんとロード関数を呼びましょう。呼び出していないときには、メモリ上少しだけ無駄になっていますが、大きなフォントデータが読み込まれていることはないと思います。

サンプルプログラム

#include <M5StickC.h>

void setup() {
  // 表示文字列セット
  char* AscStr="ASCII: \nABCDEFG12";
  char GbkStr[11] = { 0xB4, 0xF3, // 大
                      0xD0, 0xDC, // 熊
                      0xC3, 0xA8, // 猫
                      0xB4, 0xF3, // 大
                      0xBA, 0xC3, // 好
                      0x00
                    };
  
  M5.begin();

  // フォントデータロード
  M5.Lcd.loadHzk16();

  // 文字色白、背景色黒に設定
  M5.Lcd.setTextColor(WHITE, BLACK);
  
  // ハイライトカラーを赤に設定
  M5.Lcd.setHighlightColor(RED);
  
  // Hzk Font1(16ピクセル)でサイズ等倍、カーソル左上でASCII文字列表示
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0,0,1);
  M5.Lcd.writeHzk(AscStr);

  // ハイライトして同じものを表示
  M5.Lcd.highlight(true);
  M5.Lcd.setCursor(0,32);
  M5.Lcd.writeHzk(AscStr);

  // 通常Font2(16ピクセル)でASCII文字列表示
  M5.Lcd.setTextSize(1);
  M5.Lcd.setCursor(0,64,2);
  M5.Lcd.print(AscStr);
  M5.Lcd.setTextFont(1);

  // ハイライトオフで漢字表示
  M5.Lcd.highlight(false);
  M5.Lcd.setCursor(0,102);
  M5.Lcd.writeHzk(GbkStr);
  
  // ハイライトオンで漢字表示
  M5.Lcd.highlight(true);
  M5.Lcd.setCursor(0,122);
  M5.Lcd.writeHzk(GbkStr);
}

void loop() {
}

サンプルのHZK16を少しだけ改造してあります。HZKフォントは等角フォントなので、標準のプロポーショナルフォントとはASCII文字でも表示が異なります。

実行結果

表示されていますね。ただし、これってGBK文字コードで、フォントも中国語しかないので、まあ使えません。

仕組み的には同じ方法で、日本語フォントもプログラム内蔵できるので今度チャレンジしてみたいと思います。