M5Stack FIRE (PSRAM付き)およびIPSタイプのLCD ILI9342Cを使ってみた

M5Stack,FIRE,と,IPS,タイプのLCD,ILI9342Cをレビューしてみた M5Stack

Arduino core for the ESP32 のインストール

ここでは、Arduino core for the ESP32でプログラミングした場合を説明していきます。
以下のバージョンで現在動作確認しています。
Arduino IDE は ver 1.8.10
Arcuino core for the ESP32 ver1.0.4

Arduino core for the ESP32 のインストール方法は以下の記事を参照してください。
Arduino core for the ESP32 のインストール方法

M5Stack FIREの Arduino core for the ESP32設定

M5Stack FIRE の場合Flashメモリサイズが16MBあります。
M5Stack Basicの4倍ですね。
更に、別途PSRAMもあるので、Arduino core for the ESP32のボード設定を間違えると、再起動を繰り返すような不具合が起きてしまいます。
これは特にFlashメモリサイズとPartition Scheme設定が重要なので、下図の様にボード設定を
「M5Stack-FIRE」にすることですね。
そして、PSRAMを使う場合は必ず「Enable」にすることを忘れずに。

ボード: M5Stack-FIRE
Upload Speed: 921600
Partition Scheme: Default (2 x 6.5 MB app, 3.6 MB SPIFFS)
Core Debug Level: なし
PSRAM: Enabled
シリアルポート: ※ご自分のM5StackのUSBポート
————————-
書込装置: USBasp

 

因みに、私はボード設定を ESP32 Dev Module にして、FLASHサイズを16MBにしたのに、Partition Scheme の方で4MB設定にしてコンパイルしてしまったことがありました。
そうしたら、ぜんぜん動かずにリセットを繰り返す事態に陥りました。
この辺は要注意ですね。
ボードはM5Stack-FIREにすれば間違いが少ないと思います。

PSRAMを使った Arduino IDE スケッチ(プログラミング)例

では、今度はM5Stack FIREのPSRAMを使ってみます。

PSRAM とは、Pseudo-SRAM ( 疑似SRAM ) というそうです。
M5Stack FIRE の場合、SPI通信で接続されています。
40MHzで駆動しているそうなので、ESP32内蔵のSRAMよりも遙かに読み書き速度は遅いです。
体感的には、SPIFFSと同じくらいかと思われます。
Twitterでいつもお世話になっている、Kenta IDAさんによれば、約48倍違うらしいです。
でも、さすがに画像データでSRAMを占有されるわけにはいかないので、PSRAMを使わざるを得ないと思います。

ということで、簡単なスケッチ例を紹介します。
私の場合は画像を扱う事が多いので、2次元配列を扱いたいわけです。
PSRAMで2次元配列を扱うには、ポインタへのポインタで、ヒープ領域を確保するみたいです。
厳密には配列とは異なりますが、ポインタでも配列として扱えるので、以下のようなプログラムを作ってみました。
因みに私は素人なので、間違えていたらコメント投稿等でご連絡いただけると助かります。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

char *c_buf1, **c_buf2;
uint16_t size1 = 10, size2 = 5;

void setup() {
  Serial.begin(115200);
  delay(1000);

  c_buf1 = (char *)ps_malloc(sizeof(char *) * size1 * size2);
  c_buf2 = (char **)ps_malloc(sizeof(char *)* size2);

  //c_buf1 = (char *)heap_caps_malloc(sizeof(char *) * size1 * size2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
  //c_buf2 = (char **)heap_caps_malloc(sizeof(char *) * size2, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);

  for(int i = 0; i < size2; i++){
    c_buf2[i] = c_buf1 + i * size1;
  }

  multi_heap_info_t info;
  heap_caps_get_info(&info, MALLOC_CAP_SPIRAM);

  Serial.printf("PSRAM total size = %d\r\n", info.total_free_bytes);
  Serial.printf("PSRAM data size = %d\r\n", info.total_allocated_bytes);

  memcpy(c_buf2[0], "ABCDEFGHI", size1);
  memcpy(c_buf2[1], "abcdefghi", size1);
  memcpy(c_buf2[2], "Hello!!", size1);
  memcpy(c_buf2[3], "World", size1);
  memcpy(c_buf2[4], "Fire!!!", size1);

  for(int i = 0; i < size2; i++){
    Serial.println(c_buf2[i]);
  }
  Serial.println(c_buf2[3][2]);
}

void loop() {
}

この例では、PSRAMにヒープメモリを確保する関数として、ps_mallocを使っていますが、一般的に知られている関数は、
heap_caps_malloc
という関数です。
これは、11-12行目でコメントアウトしてあるものです。
この関数の方が細かい設定ができるようです。
私の場合はps_mallocの方がmalloc関数の扱いに近いので、ps_mallocの方を使いました。
どちらでも使えると思います。

コンパイルする前に、Arduino core ESP32のボード設定のPSRAMを「Enable」にすることを忘れないでください

コンパイル書き込み実行した、シリアルモニターの結果はこんな感じです。

無事、ほぼ、自分の意図した結果になりました。
ただ、PSRAMのデータサイズだけが計算と合わないのですが、私が何か間違えているんですかね?
それともこういうものなんですかね?
4byte単位で書き込むような情報をどこかで見た記憶があったので、それならこの220byteという値はOKなんですが、よく分かりません。
とりあえず、これでうまく行ったのでヨシとします。

M5Camera と M5Stack FIRE で、WiFi UDP で動画表示するスケッチ

では、前々回記事で紹介したM5CameraとM5StackとのWiFi UDPの動画転送プログラミングを改変して、PSRAMを使って、M5Stack FIREで表示させてみます。

私の自作ライブラリもM5Stack FIREが使えるように改変しました。
GitHubの以下の所に最新版を置いてあるので、beta ver 1.0.70 をArduino IDEにインストールして下さい。
https://github.com/mgo-tec/ESP32_mgo_tec

その他、フォントファイルのインストール等は以下の記事を参照してください。
M5Cameraの動画をM5StackへWiFi, UDPで送信する実験

画像を送信する側のM5Cameraは、ESP32 WROVERで PSRAM がありますが、M5Camera側はスピード重視なので、PSRAMは使わず、前々回記事のままで行います。

ということで、受信側 M5Stack FIRE用のスケッチです。

【ソースコード】 (※無保証 ※PCの場合、ダブルクリックすればコード全体を選択できます)

#define MGO_TEC_BV1_M5STACK_SD_SKETCH
#include <mgo_tec_bv1_m5stack_sd_simple1.h> //ESP32_mgo_tec library. beta ver 1.0.70

#include <WiFi.h>
#include <WiFiUdp.h>

const char* utf8sjis_file = "/font/Utf8Sjis.tbl"; //UTF8 Shift_JIS 変換テーブルファイル名を記載しておく
const char* shino_half_font_file = "/font/shnm8x16.bdf"; //半角フォントファイル名を定義
const char* shino_full_font_file = "/font/shnmk16.bdf"; //全角東雲フォントファイル

const char * to_udp_address = "192.168.4.1";
const int to_server_udp_port = 55555; //送信相手のポート番号
const int my_server_udp_port = 55556; //開放する自ポート

const char* ssid = "xxxxxxxxx"; //9文字以上。ご自分好きなSSIDに書き換えてください
const char* password = "xxxxxxxxx"; //9文字以上。ご自分好きなパスワードに書き換えてください

WiFiUDP udp;
boolean connected = false;

const uint16_t disp_w_pix = 200;
const uint16_t disp_h_pix = 148;
const uint8_t fix_div = 3;
const uint8_t div_h = disp_h_pix / fix_div;
uint16_t udp_line = 0;
boolean isDisp_ok = false;
const uint16_t line_max_bytes = disp_w_pix * 2;
unsigned char line_num_buf[fix_div] = {};
unsigned char **pix_buf, *pix_buf_base;

TaskHandle_t task_handl;

enum UdpSendStatus {
  udp_stop = 0,
  udp_start = 1
} UdpSendState = udp_stop;

enum UdpSendAwb {
  awb_off = 0,
  awb_on = 1
} UdpSendAWB = awb_off;

enum UdpSendAec {
  aec_off = 0,
  aec_on = 1
} UdpSendAEC = aec_on;

uint8_t udp_send_reset = 0;

int16_t exposure[4] = {0, 192, 256, 8192};
int8_t exposure_count = 0;
String aec_str[4] = {"Auto ",
                     " - 1 ",
                     " + 1 ",
                     " + 2 "};
String stream_str[3] = {"Stop ", "Start", "RESET"};
String awb_str[2] = {" OFF ", " O N "};

uint8_t send_auto_white_balance = 0;
uint8_t send_auto_exposure_control = 0;
uint8_t send_data[6] = {};
uint8_t offset = 1;
boolean isSend_ok = false;

uint16_t btn1_x0 = 0, btn1_x1 = 107;
uint16_t btn2_x0 = 107, btn2_x1 = 212;
uint16_t btn3_x0 = 212, btn3_x1 = 319;
uint16_t btn_y0 = 197, btn_y1 = 233;

//-------------------
int16_t check_sum = 0;
uint32_t calc_heap_time = 0;
boolean isCooling_cpu = false;
uint8_t cpu_temp = 0;
boolean isDisp_cpu_temp = false;
boolean isDisp_first_cpu_temp = true;
boolean onButton_A = false;
boolean isDisp_cooling = false;
IPAddress myIP;
//-------------------------
uint8_t receive_temp_buf = 0;
uint8_t old_recv_temp = 0;
uint16_t y;

//********CPU core 1 task********************
void setup(){
  Serial.begin(115200);
  delay(1000);
  //-------------------------
  pix_buf = (unsigned char **)ps_malloc(sizeof(uint8_t *) * disp_h_pix);
  pix_buf_base = (unsigned char *)ps_malloc(sizeof(uint8_t *) * disp_h_pix * line_max_bytes);
  for(int i = 0; i < disp_h_pix; i++) {
    pix_buf[i] = pix_buf_base + i * line_max_bytes;
  }
  Serial.print("psramsize = ");
  multi_heap_info_t info;
  heap_caps_get_info(&info, MALLOC_CAP_SPIRAM);
  Serial.printf("%d, %d\r\n", info.total_free_bytes ,info.total_allocated_bytes);
  //-------------------------
  initDisplay();
  send_data[2] = (uint8_t)UdpSendAEC;
  xTaskCreatePinnedToCore(&taskUDP, "taskUDP", 8192, NULL, 24, &task_handl, 0);
  while(!connected){
    Serial.print('.');
    delay(500);
  }
  connectedWifiDisp();
}

void loop(){
  if(!isCooling_cpu){
    if(isDisp_ok){
      for(int k = 0; k < disp_h_pix; k++){
        y = k + offset;
        LCD.drawPixel65kColRGB565Bytes(offset, y, disp_w_pix + offset, y, (uint8_t*)&pix_buf[k][0], line_max_bytes);
      }
      isDisp_ok = false;
    }
  }
  actionButton(); //ボタン操作
  decideUdpStarStop();

  if(isDisp_cpu_temp){
    mM5.disp_fnt[5].dispText( mM5.font[5], String(cpu_temp) );
    isDisp_cpu_temp = false;
  }
  if(receive_temp_buf != old_recv_temp){
    mM5.disp_fnt[4].dispText( mM5.font[4], String(receive_temp_buf) );
    old_recv_temp = receive_temp_buf;
  }

  if(isDisp_cooling){
    mM5.disp_fnt[6].dispText( mM5.font[6], "CPU Cooling!" );
    isDisp_cooling = false;
  }
}
//***********************************
void taskUDP(void *pvParameters){
  connectToWiFi();
  while(!connected){
    delay(1);
  }
  while(true){
    receiveUDP();
    sendUDP(send_data);
    if(isDisp_first_cpu_temp || millis() - calc_heap_time > 10000){
      cpu_temp = (uint8_t)floor(temperatureRead());
      isDisp_cpu_temp = true;
      //Serial.printf("Free Heap Size = %d\r\n", esp_get_free_heap_size());
      //Serial.printf("CPU temp = %d\r\n", cpu_temp);
      if(!isCooling_cpu){
        if(UdpSendState == udp_start && cpu_temp > 64.0){
          onButton_A = true;
          isCooling_cpu = true;
          isDisp_cooling = true;
          //udp.stop();
        }
      }else{
        if(cpu_temp < 58.0){
          //udp.begin(myIP, my_server_udp_port);
          onButton_A = true;
          isCooling_cpu = false;
          isDisp_cooling = false;
        }
      }

      calc_heap_time = millis();
      isDisp_first_cpu_temp = false;
    }
    delay(1); //ウォッチドッグタイマ動作のために必ず必要
  }
}

void receiveUDP(){
  if(!isCooling_cpu){
    if(!isDisp_ok){
      if(connected){
        int packetSize = udp.parsePacket();
        if(packetSize == 2){
          if(udp.read() == 0xff){
            receive_temp_buf = udp.read();
          }
        }else if(packetSize > 2){
          for(int i = 0; i < fix_div; i++){
            udp.read(&line_num_buf[i], 1);
            udp.read(&pix_buf[line_num_buf[i]][0], line_max_bytes);
          }
          if((line_num_buf[0] == 0) || (line_num_buf[0] == (check_sum + 1))) {
            udp_line = (uint16_t)floor((double)line_num_buf[0] / (double)fix_div);
            if(udp_line == (div_h - 1)) isDisp_ok = true;
            check_sum = line_num_buf[fix_div - 1];
          }
        }
        udp.flush();
      }
    }
  }
}

void sendUDP(uint8_t send_data[5]){
  if(isSend_ok){
    udp.beginPacket(to_udp_address, to_server_udp_port);
    udp.write(send_data, 6);
    udp.endPacket();
    isSend_ok = false;
  }
}

void initDisplay(){
  mM5.init( utf8sjis_file, shino_half_font_file, shino_full_font_file );
  LCD.ILI9341init();
  LCD.brightness( 255 );  
  LCD.displayClear();
  LCD.drawRectangleLine(0, 0, disp_w_pix + offset, disp_h_pix + offset, 31, 63, 31);
  LCD.drawRectangleLine(btn1_x0, btn_y0, btn1_x1, btn_y1, "#ffffff");
  LCD.drawRectangleLine(btn2_x0, btn_y0, btn2_x1, btn_y1, "#ffffff");
  LCD.drawRectangleLine(btn3_x0, btn_y0, btn3_x1, btn_y1, "#ffffff");

  mM5.font[0].x0 = 10; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 1, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], "  Stream" );
  mM5.font[0].x0 = 106; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 1, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], "Auto WhiteB" );
  mM5.font[0].x0 = 212; mM5.font[0].y0 = 160;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 2, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], " 露出 " );

  mM5.font[1].x0 = 10; mM5.font[1].y0 = 200;
  mM5.font[1].htmlColorCode( "red" );
  mM5.font[1].Xsize = 2, mM5.font[1].Ysize = 2;
  mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[0] );
  mM5.font[2].x0 = 120; mM5.font[2].y0 = 200;
  mM5.font[2].htmlColorCode( "blue" );
  mM5.font[2].Xsize = 2, mM5.font[2].Ysize = 2;
  mM5.disp_fnt[2].dispText( mM5.font[2], awb_str[0] );
  mM5.font[3].x0 = 230; mM5.font[3].y0 = 200;
  mM5.font[3].htmlColorCode( "#ff00ff" );
  mM5.font[3].Xsize = 2, mM5.font[3].Ysize = 2;
  mM5.disp_fnt[3].dispText( mM5.font[3], aec_str[0] );
}

void connectedWifiDisp(){
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 0;
  mM5.font[0].htmlColorCode( "#FFFFFF" );
  mM5.font[0].Xsize = 1, mM5.font[0].Ysize = 2;
  mM5.disp_fnt[0].dispText( mM5.font[0], "   Wi-Fi UDP" );
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 32;
  mM5.disp_fnt[0].dispText( mM5.font[0], "     Video" );
  mM5.font[0].x0 = 205; mM5.font[0].y0 = 64;
  mM5.disp_fnt[0].dispText( mM5.font[0], "    Receive" );

  mM5.font[4].x0 = 205; mM5.font[4].y0 = 96;
  mM5.font[4].htmlColorCode( "#00ff00" );
  mM5.font[4].Xsize = 1, mM5.font[4].Ysize = 2;
  String cam_temp_str = "Camera: -- ℃";
  mM5.disp_fnt[4].dispText( mM5.font[4], cam_temp_str );
  mM5.font[4].x0 = 269;

  mM5.font[5].x0 = 205; mM5.font[5].y0 = 128;
  mM5.font[5].htmlColorCode( "#ffff00" );
  mM5.font[5].Xsize = 1, mM5.font[5].Ysize = 2;
  String cpu_temp_str = "C P U: " + String(cpu_temp) + " ℃";
  mM5.disp_fnt[5].dispText( mM5.font[5], cpu_temp_str );
  mM5.font[5].x0 = 261;

  mM5.font[6].x0 = 5; mM5.font[6].y0 = 50;
  mM5.font[6].htmlColorCode( "#ff00ff" );
  mM5.font[6].Xsize = 2, mM5.font[6].Ysize = 2;
}
//*********************************************
void connectToWiFi(){
  Serial.println("Connecting to WiFi network: " + String(ssid));
  WiFi.disconnect(true, true);
  delay(1000);
  WiFi.onEvent(WiFiEvent);
  WiFi.begin(ssid, password);
  Serial.println("Waiting for WIFI connection...");
}

void calcExposure(boolean isIncrease){
  if(isIncrease){
    exposure_count++;
  }else{
    exposure_count--;
  }
  if(exposure_count > 3) exposure_count = 3;
  if(exposure_count < 0) exposure_count = 0;
  if(exposure_count == 0) {
    UdpSendAEC = aec_on;
    mM5.font[3].htmlColorCode( "#ff00ff" );
    Serial.println("exposure=AUTO");
  }else{
    UdpSendAEC = aec_off;
    mM5.font[3].htmlColorCode( "yellow" );
    Serial.printf("exposure=%d\r\n", exposure[exposure_count]);
  }
  mM5.disp_fnt[3].dispText( mM5.font[3], aec_str[exposure_count] );
  send_data[2] = (uint8_t)UdpSendAEC;
  send_data[3] = (uint8_t)(exposure[exposure_count] >> 8);
  send_data[4] = (uint8_t)(exposure[exposure_count] & 0x00ff);
}

void WiFiEvent(WiFiEvent_t event){
  myIP = WiFi.localIP();
  switch(event) {
    case SYSTEM_EVENT_STA_GOT_IP:
      Serial.println("WiFi connected!");
      Serial.print("My IP address: ");
      Serial.println(myIP);
      //udp.begin関数は自サーバーの待ち受けポート開放する関数である
      udp.begin(myIP, my_server_udp_port);
      delay(1000);
      connected = true;
      break;
    case SYSTEM_EVENT_STA_DISCONNECTED:
      Serial.println("WiFi lost connection");
      connected = false;
      break;
    default:
      break;
  }
}

void decideUdpStarStop(){
  if(onButton_A){
    if(UdpSendState == udp_start){
      UdpSendState = udp_stop;
      mM5.font[1].htmlColorCode( "red" );
    }else{
      UdpSendState = udp_start;
      mM5.font[1].htmlColorCode( "green" );
    }
    send_data[0] = (uint8_t)UdpSendState;
    isSend_ok = true;
    Serial.printf("UdpSendState=%d\r\n", (uint8_t)UdpSendState);
    mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[(int)UdpSendState] );
    onButton_A = false;
  }
}

void actionButton(){
  mM5.btnA.buttonAction();
  switch( mM5.btnA.ButtonStatus ){
    case mM5.btnA.MomentPress:
      Serial.println("\r\nButton A Moment Press");
      onButton_A = true;
      break;
    case mM5.btnA.ContPress:
      Serial.println("\r\n-------------Button A Cont Press");
      udp_send_reset = 1;
      send_data[5] = udp_send_reset;
      isSend_ok = true;
      mM5.font[1].htmlColorCode( "white" );
      mM5.font[1].htmlBgColorCode( "red" );
      LCD.drawRectangleFill(btn1_x0+1, btn_y0+1, btn1_x1-1, btn_y1-1, "red");
      mM5.disp_fnt[1].dispText( mM5.font[1], stream_str[2] );
      Serial.println("Send [Reset]!!!");
      break;
    default:
      break;
  }

  mM5.btnB.buttonAction();
  switch( mM5.btnB.ButtonStatus ){
    case mM5.btnB.MomentPress:
      Serial.println("\r\nButton B Moment Press");
      if(UdpSendAWB == awb_off){
        UdpSendAWB = awb_on;
        mM5.font[2].htmlColorCode( "white" );
      }else{
        UdpSendAWB = awb_off;
        mM5.font[2].htmlColorCode( "blue" );
      }
      send_data[1] = (uint8_t)UdpSendAWB;
      isSend_ok = true;
      Serial.printf("AWB=%d\r\n", (uint8_t)UdpSendAWB);
      mM5.disp_fnt[2].dispText( mM5.font[2], awb_str[(int)UdpSendAWB] );
      break;
    case mM5.btnB.ContPress:
      Serial.println("\r\n-------------Button B Cont Press");
      break;
    default:
      break;
  }

  mM5.btnC.buttonAction();
  switch( mM5.btnC.ButtonStatus ){
    case mM5.btnC.MomentPress:
      Serial.println("\r\nButton C Moment Press");
      calcExposure(true);
      isSend_ok = true;
      break;
    case mM5.btnC.ContPress:
      Serial.println("\r\n-------------Button C Cont Press");
      calcExposure(false);
      isSend_ok = true;
      break;
    default:
      break;
  }
}

では、これをコンパイル書き込み実行してみてください。
書込みした後、「Stream」ボタンを押して、画像が表示されればOKです。

PSRAM と SRAM の描画速度の違い

では、M5Stack Basic のSRAMにM5Cameraの画像を1フレーム毎に保存して描画させた場合と、M5Stack FIREのPSRAMに1フレーム毎に保存して描画させた場合の速度の違いを見てみましょう。
下図の様になります。
これは、以前のこちらの記事のM5StackをM5Cameraで撮って、WiFi UDPで送信した画像を受信したものです。
Yahooニュース電光掲示板のスクロール速度をじっくり見てください。

どうでしょうか。
やっぱり、断然BasicのSRAMがスムースですね。
FIREのPSRAMの方は動作がカクカクしています。

でも、SRAMを占有されるよりか、SRAMには余裕があった方が良いので、PSRAMの描画速度でも個人的にはOKだと思っています。
監視カメラ程度には充分使えますし、それ以外の用途は思い付かないですしね。

もっと早い描画性能を求めるなら、もっと上級マイコンモジュールを使うしかないですね。

他に注目する点として、M5Stack FIREのCPU温度ですね。
Basicよりも格段に低い温度ですね。

新型M5Stack FIREのWiFi受信が旧型Basicより悪いかも?

では、動画を表示させながら、私の手持ちのM5CameraとM5Stack FIREを徐々に距離を離していくと、旧型M5Stack Basicよりも受信状態が明らかに悪かったです。
1m以上離すと、明らかにM5Stack FIREの受信状態が悪くなりました。
WiFi関連のパーツが何か変わったんでしょうか?
それとも、ESP32が省電力モードになっているんでしょうか?

全く同じスケッチを書き込んでもM5Stack FIREの受信状態が悪かったです。
これは現在謎です。
今後判明したら、ブログ又はTwitterでお知らせします。

【2020/04/09追記】
Twitter で三太さんから情報をいただき、私の方でも実験したところ、M5Stack FIRE のバッテリー入りBottomケースを外して、モバイルバッテリーなどでWiFi通信すると、M5Stack Basicと同程度の通信状態になることが分かりました。
三太さん、情報ありがとうございました。
m(_ _)m

Bottomケースが不具合の原因かも知れません。電磁ノイズが出ているんでしょうかねぇ???
ただ、Bottomケースを外してしまうと、コンパイル書き込み時にUSBシリアルの伝送不良が起こって、書き込みでき成るなる可能性もあるので、書き込み時にBottomケースを装着して、WiFi通信時にBottomケースを外すという運用も有りかもしれません。

 

送信側M5Camera CPUの発熱がひどくなるのは、M5Stack FIREの受信状態が悪くなった時

前々回記事では、送信側のWiFiをストップしても、受信側のCPU温度が上昇するという報告をしました。
今回分かったことは、その逆です。

新型M5Stack FIREの受信状態が悪いと、送信側のM5CameraのCPU ESP32の発熱がとんでもないことになります。
時には70℃を超えて、75℃とかになったりします。

この状態が長い間続くと、パーツが故障するレベルだと思います。
この状態では、前々回記事のように冷却ファンをつけても70℃をこえてしまうという、えげつない状態に陥ります。

一方、受信状態が良くなると、途端にM5CameraのCPU温度が下がります。
これは、Twitterで@Seg_Faulさんから教えて頂いたのですが、Arduinoプログラミングとは別に裏で動いている無線関連プログラムで、MACレイヤー層の再送信が行われているそうです。
それについての資料は、以下のリンクにあることを教えて頂きました。

https://fielddesign.jp/technology/wlan/ieee802_frame/
http://www.kyoei-ele.com/faq/air_faq/82.htm

@Seg_Faulさん、いつもありがとうございます。
m(_ _)m

このことから、WiFi受信状態が悪いと、自分の意図したArduinoプログラミングとは別に、MAC層でデータの再送信を行ったり、WiFiの電波強度を上げたりしている可能性があるので、送信側のESP32の温度が上昇すると考えられますね。
要するに、WiFiアクセスポイントが無いところで、スマホのWiFiをONにしていると、電池の消耗が激しいということと同じ状況だということです。

新型M5Stack FIREの方がCPU発熱少ないかも?

一方、受信側の新型M5Stack FIREはCPUの発熱がかなり抑えられていることが分かりました。

前々回記事では、あまりにも旧型M5Stackの発熱が酷かったので、冷却ファンを付けました。

でも、今回の新型M5Stack FIREでは、CPU温度が50℃を超えることがありませんでした。
冷却ファンが不要なレベルです。

先に述べたように、もしかしたら、WiFi受信状態が悪いことと関係あるかも知れません。
無線機能のパワーを落としてあるため、WiFi受信状態が悪いのではないかと想像しました。
これはあくまで個人的想像なので、間違えていたらゴメンナサイ。。。

あるいは、IPSディスプレイILI9342Cに替わったので、省電力化されて発熱が減ったのかも知れません。(その場合は電源レギュレーターの発熱の問題ですが。。。)

あるいは、Li-Poバッテリーの発熱が減ったり、放熱がうまく行っているのかも知れません。

あと、考えられるのは、temperatureRead()という関数が機能しなくなっていて、誤った数値が返されているのかもしれません。
ですが、試しにBottomユニットを外し、バッテリーと分離させて動かしてみると、本体ボディー表面のディスプレイ側は暖かくなっていますが、裏のESP32ケースを触ってみると、それほど発熱していませんでした。
やっぱりESP32自体の発熱は抑えられていると思います。

いずれにしても、ハッキリとした原因は不明ですが、新型M5Stack FIREは省電力化されていると見受けられました。

編集後記

以上、かなり遅ればせながらですが、M5Stack FIRE のレビューでした。
短くサラッと終わらせる予定でしたが、意外に書くことがあり、長ったらしい記事になってしまいました。

WiFiの能力や、PSRAM認識の不満はありますが、IPSディスプレイは確実に良いですね。
その他、ボディーがしっかりしているので、安心感があります。

かなり高価な買い物ですが、FLASHサイズが大きいのと、PSRAM付属というのは魅力ですね。
これを使えば、M5Stackでディープラーニングということもできそうな気がしてきました。

ということで、今回はここまでです。

本年もどうぞよろしくお願いします。
今年こそは何かぶっ飛びたいですね!

コメント

  1. サーボ より:

    はじめまして。サーボ と申します。
    ついこの前に M5StackFire と M5Camera を購入し、いろいろ勉強しているところです。
    mgo-tec さんの記事を見つけ、参考にさせていただいおり大変ありがたいです。
    お忙しいところお疲れさまです。

    「PSRAMを使った Arduino IDE スケッチ(プログラミング)例」で、
    PSRAM data size が 220 となっている理由につきまして、ちょっと心当たりがあります。

    c_buf1 には、ヌル文字を含む10文字の配列を5個分だけ格納する領域へのポインタを設定され、
    c_buf2 には、上記領域を5分割してそれぞれの領域の先頭へのポインタを設定されているのかなと思います。

    ps_malloc() で c_buf1 の領域を確保する処理につきまして、sizeof(char *) の代わりに
    sizeof(char) で領域を確保してはいかがでしょうか。”char *” は M5StackFire では 4 バイトの変数なので、
    確保したいメモリ量の 4 倍の量を確保してしまいます。
    sizeof(char) に変更した場合の領域の量は、10 * 5 + 5 * 4 = 70 bytes かと思います。

    修正して動作を確認すると、PSRAM data size は 72 となっておりました。
    70 となっていないのは、mgo-tec さんのおっしゃる「4byte単位で」読み書きするため、4 バイト単位で
    領域が確保されるのかなと思います。

    少しでも参考になればと、コメントさせていただきました。

    • mgo-tec mgo-tec より:

      サーボさん

      記事をご覧いただき、ありがとうございます。
      おー!
      そう言えば、sizeof(char *)はESP32では4byteでした。
      すっかり忘れていました。
      というか、2次元配列のps_malloc()確保は、ポインタ型でしか初期化できないと思い込んでいました。
      今取組中の課題が終わったら試してみようと思います。
      ありがとうございました。
      m(_ _)m

  2. サーボ より:

    mgo-tec さん

    お役に立てて何よりです!
    お体に気を付けながら、時間があるときにでも試してみてくださいませ (^^

タイトルとURLをコピーしました