Vol.941 24.Apr.2026

15・エンハンサー M5A)_SDカードを使う

A フィフティーン・エンハンサー 〜15KEY's ENHANCER

by fjk

 SD-PAD15(エアリア、@5,980)は「ソフト起動」、「定型文入力」、「機能呼出」などを専用ソフトでボタン(15×10レィヤー)に登録することにより、ワンボタンで様々な作業の効率化がはかれます。なお、登録はキー操作のみでマウス操作は利用出来ない。
主な特徴・機能

  1. 簡単ワンボタンで作業を自動化(Hotkey)
  2. 100種以上の機能を保存可能(専用アプリ)
  3. ワンボタンでレイヤー切り替え(本体ボタン)
  4. アプリ起動もワンボタンで(open)
  5. 指定した音楽ファイルをボタンひとつで瞬時に再生(Sound)
  6. 開きたいWebサイトもワンボタン(Website)
  7. 定型文も構文スニペットもワンボタンで入力(Text)
  8. 複雑な操作もマクロで自動化(Macro)
  9. 便利なUSBハブポートを搭載(TypeAx2、TypeCx1)
  10. 押しやすく深さのあるラバーボタンを採用
  11. LEDライトで操作を視覚化(4種類)
  12. 持ち運びに便利なコンパクト設計 (117×90×23.5mm、123g)
  13. PC接続:TypeC又はUSB(付属変換器使用)

SD-PAD15

SD-PAD15のLED点灯例

 早速アプリをインストールし、「長いパスワード」、よく使う「キーワード」や「長いコマンド」などを登録した。画面上に現在の設定データリスト(ワンボタンで表示のON/OFFが可能)が表示されるので、登録状態が確認できる。登録データは、Export、Inportでファイルとして保存・読出しができます。
 登録したボタンを押すと、若干のタイムラグがあるが、VSCやEdge、Word/Excel、一太郎など、ほとんどのソフトでパスワードやコマンドなどが、ワンボタンで入力できた。しかし、「秀丸エディッタ」では、「キー入力」はOKだが「テキスト入力」ができず、アプリが終了した。
 なお、パソコンをOFFにしてもLED点灯が消えないので、願わくは「電源ボタン」が欲しい。


画面表示メニュ−例(Windows)

キーデーター設定画面

 そこで、「USBスイッチ(L 型ケーブル付)」(@700)と「USBアダプター(AーC)」(@799/3)を入手した(共にデータ通信も可)。


USBスイッチ/アダプタ

SD-PAD15+USBスイッチ&アダプタ


M M5_Atomシリーズ(7) 〜AtomS3でSDカードを使う

by fjk

 AtomS3でSDカードを使うには、以下のAtomic_TFCard_BaseやTFCardチュートリアルが詳しいが、機能が盛り込まれすぎているので、ファイルアクセスの要点だけに絞ったものを作製した。

【ハード】
 TFCardBaseはSPI通信方式を採用しておリ、使用するSPIピンは、G7(SCK)、G8(MISO)、G6(MOST)で、CSは常にGND。
 なお、microSDカードは最大32GB容量をサポート。

【テスト】
 当初、上記のチュートリアルを使って動作テストしたところ、「ATOMIC_TFカードリーダーを使う」に示されたGPIOエラーが発生。対策として「SD.begin()で未使用のGPIOを使えば良い」とあり、CS=5(未使用)を指定した(CS=-1やCS=22でもSDアクセスは可能だが、GPIOエラー[警告?]が残ったまま)。


  String rcv = file.readStringUntil('\n');

  M5.Display.println(rcv);


  if(!s.equals(F("System Volume Information"))) {

     M5.Display.println(s);  }

 ここまで、SDカード(〜32GB)の読み書きが正常にできることを確認した

【ソフト】 [ ]はオプション、イタリックはパラメータ・データ
 シリアルターミナルからは以下のコマンド(大文字)が使えます。

AtomS3画面の消去(カーソルは画面左上に移動)
SD情報(type、使用済量/容量)の表示(起動画面参照)
[filName ]
ファイル名の設定(filName無しは現在filName名を表示)
str
filNameファイルに文字列strを書き込む(上書き・なければ新規)
[str ]
filNameファイルに文字列strを追記(str無しは改行[\n]を追記)
[filName ]
ファイルのデータを読込・表示(filName無しは現在filName使用)
[VfilName ]
画像ファイル(.bmp/.jpg/.png)を読込・表示(VfilName無しは現在VfilName使用)
[dirName ]
ファイルリストの表示(dirName無しは現在dirName使用、初期値は‘/’[root])
filName
filNameファイルを削除(filNameは省略不可)
filName
現在ファイル名をfilNameに変更(filNameは省略不可)
dirName
新ディレクトリーdirNameを作製(dirNameは省略不可)
dirName
dirNameディレクトリーを削除(dirNameディレクトリーは空であること)

※(V)filNameおよびdirNameは、先頭に‘/’を付けたフルパス・ネームで指定すること。
※Vコマンドのファイル名(VfilName)は、F/R/W/Aコマンドのファイル名(filName)とは別です。


起動時(I)

dirリスト(L)

データ読込み(R)

rose.bmp読込み(V)

Atomc-TFcard.cpp(zip)

/*************************************(Atomic-TFcard.cpp)****
 *	Atomic TF-Card Base test 
 *************************************************************/
#include <SPI.h>
#include <SD.h>
#include <M5Unified.h>
#include <M5GFX.h>

// ------------------- Pin Definitions -------------------
#define SD_SPI_CS_PIN     5             // Chip Select pin(no Use) 
#define SD_SPI_SCK_PIN    7             // SPI Clock pin 
#define SD_SPI_MISO_PIN   8             // SPI MISO pin 
#define SD_SPI_MOSI_PIN   6    	        // SPI MOSI pin 

#define SRBFSZ 32                       // シリアルバッファーサイズ
#define FLNMSZ 32                       // ファイル名サイズ

//----  グローバル変数 ------------------------------------------
char RcvBf[SRBFSZ];                     // シリアル受信データバッファ
char DirNm[FLNMSZ] = "/";               // ディレクトリー名
char FilNm[FLNMSZ] = "/hello.txt";      // ファイル名
char VFlNm[FLNMSZ] = "/rose.bmp";       // 画像ファイル名

//----  文字列シリアル受信 --------------------------------------
int getSerialLine(void){                // シリアル文字列取得
  static int  sz = 0;                   // 受信データポインタ
  if (Serial.available()) {
    char ch = Serial.read(); 
    if ((ch=='\n')|(ch=='\r')){
//   if (ch=='\n'){                     // \rが残る場合の処理ケース2
      RcvBf[sz] = '\0';
      int i = sz + 1;
      sz = 0;
      Serial.print('\n');
      return i;
    }else if((ch == '\b')&&(sz > 0)){
      Serial.print('\b');
      sz--;
//   }else if(ch == '\r'){              // \rが残る場合の処理ケース2
//     RcvBf[sz]='\0';                  // \rが残る場合の処理ケース2
    }else{
      if(sz < SRBFSZ - 1){
        RcvBf[sz++] = ch;
        Serial.print(ch);
      }
    }
  }
  return 0;
}

//------------ 画面消去 ---------------------------------
void clrDisplay(void){
  M5.Display.clear();                   // 表示クリア    
  M5.Display.setCursor(0, 0);           // 表示開始位置左上角(X,Y)
}

//********** SD関連関数 *********************************/
//----------- ファイル名の設定 -------------------------------
// str:     設定したいファイル名(空なら現在の名前を表示)
void setFilNam(const char *path){
  if(strlen(path) > 0){
    strcpy(FilNm, path);
  }
  Serial.println(FilNm);
  M5.Display.println(FilNm);
}

// ---- ディレクトリー・ファイルリスト---------------------
// fs:      ファイルシステムオブジェクト
// dirname: リストするディレクトリのパス
// levels:  リストする再帰レベルの数 (0 = only current directory)
void listDir(fs::FS &fs, const char * dirname, uint8_t levels){
  if(strlen(dirname) > 0){
    strcpy(DirNm, dirname);
  }
  Serial.printf("Listing directory: %s\n", DirNm);
  File root = fs.open(DirNm);
  if(!root){
    Serial.println("Failed to open directory");
    return;
  }
  if(!root.isDirectory()){              // ディレクトリ?
    Serial.println("Not a directory");
    return;
  }
  clrDisplay();
  M5.Display.setTextColor(TFT_YELLOW);
  M5.Display.println(DirNm);
  M5.Display.setTextColor(TFT_CYAN);
  File file = root.openNextFile();      // 最初のファイルを開く
  while(file){                          // 以後、反復処理
    if(file.isDirectory()){
      Serial.print("  DIR : ");
      String s = file.name();
      Serial.println(s);
      if(!s.equals(F("System Volume Information"))){
        M5.Display.println(s);
      }
     ト
      if(levels){                       // レベル>0なら再帰的にリスト
        listDir(fs, file.name(), levels - 1);
      }
    } else {
      Serial.print("  FILE: ");
      Serial.print(file.name());
      Serial.print("  SIZE: ");
      Serial.println(file.size());
      M5.Display.println(file.name());
    }
    file = root.openNextFile();         // 次のファイル/ディレクトリへ移動
  }
}

// ------------------- ディレクトリ作成 -------------------
// fs:      ファイルシステムオブジェクト
// path:    作成するディレクトリーのフルパス名
void createDir(fs::FS &fs, const char * path){
  Serial.printf("Creating Dir: %s\n", path);
  if(fs.mkdir(path)){
    Serial.println("Dir created");
  } else {
    Serial.println("mkdir failed"); 
  }
}

// ------------------- ディレクトリ削除 -------------------
// fs:      ファイルシステムオブジェクト
// path :   削除するディレクトリー(空であること)のフルパス名 
void removeDir(fs::FS &fs, const char * path){
  Serial.printf("Removing Dir: %s\n", path);
  if(fs.rmdir(path)){
    Serial.println("Dir removed");
  } else {
    Serial.println("rmdir failed");     // ディレクトリーが空でない
  }
}

// ------------------- ファイル読み込み -------------------
// fs:      ファイルシステムオブジェクト
// path:    読み込むファイルのフルパス名
void readFile(fs::FS &fs, const char * path){
  if(strlen(path) > 0){
    strncpy(FilNm, path, FLNMSZ-1);
  }
  Serial.printf("Reading file: %s\n", FilNm);
  File file = fs.open(FilNm);  // ファイルを読み取りモードで開く
  if(!file){
    Serial.println("Failed to open file for reading");
    return;
  }
  Serial.print("Read from file: ");
  clrDisplay();
  M5.Display.setTextColor(TFT_YELLOW);
  M5.Display.println(FilNm);
  M5.Display.setTextColor(TFT_WHITE);
  while(file.available()){
    if (String(FilNm).indexOf(".txt") != -1) {  // テキストファイル? 
      String rcv = file.readStringUntil('\n');  // 文字列として読込   
      M5.Display.println(rcv);
      Serial.println(rcv);
    }else{                              // テキストファイル以外  
      int byt = file.read();            // バイト単位で読込
      M5.Display.printf("%2X", byt );   // 16進数で表示
      Serial.printf("%2X", byt );
    }
  }
  file.close();
}

// ------------------- ファイル書き込み -------------------
// fs:      ファイルシステムオブジェクト
// path:    書き込むファイルのフルパス名
// msg:     書き込むテキストデータ (上書きモード)
void writeFile(fs::FS &fs, const char * path, const char * msg){
  Serial.printf("Writing file: %s\n", path);
  File file = fs.open(path, FILE_WRITE);  //上書きモードでオープン
  if(!file){
    Serial.println("Failed to open file for writing");
    return;
  }
  if(file.print(msg)){
    Serial.println("File written");
  } else {
    Serial.println("Write failed");
  }
  file.close();
}

// ------------------- ファイル追加 -------------------
// fs:      ファイルシステムオブジェクト
// path:    追加書き込みするファイルのフルパス名
// msg:     追加書き込むテキストデータ(ファイルの最後に追加)
void appendFile(fs::FS &fs, const char * path, const char * msg){
  Serial.printf("Appending to file: %s\n", path);

  // Open file in append mode (追加モード)
  File file = fs.open(path, FILE_APPEND);
  if(!file){
    Serial.println("Failed to open file for appending");
    return;
  }
  if(strlen(msg) == 0){                 // msgが空なら
    file.print('\n');                   // 改行を追加
  }else{
    if(file.print(msg)){
      Serial.println("Message appended");
    } else {
      Serial.println("Append failed");
    }
  }
  file.close();
}

// ------------------- ファイル名変更 -------------------
// fs:      ファイルシステムオブジェクト
// path1:   変更したいファイル名
// path2:   変更後の新ファイル名
void renameFile(fs::FS &fs, const char * path1, const char * path2){
  Serial.printf("Renaming file %s to %s\n", path1, path2);
  if (fs.rename(path1, path2)) {
    Serial.println("File renamed");
  } else {
    Serial.println("Rename failed");    // 同名ファイルがあるとエラー
  }
}

// ------------------- ファイル削除 -------------------
// fs:      ファイルシステムオブジェクト
// path:    削除したいファイルのフルパス名
void deleteFile(fs::FS &fs, const char * path){
  Serial.printf("Deleting file: %s\n", path);
  if(fs.remove(path)){
    Serial.println("File deleted");
  } else {
    Serial.println("Delete failed");    // ファイルがないとエラー
  }
}

//---------------- SDカード情報の表示 ---------------
void InfoSDcard(void){
  // 挿入されているSDカードの種類を確認
  uint8_t cardType = SD.cardType();
  if(cardType == CARD_NONE){
    Serial.println("No SD card attached");
    return;                             // カードが無いとエラー
  } 
  //-------------- カードタイプのチェック -------------
  M5.Display.setTextColor(TFT_YELLOW);
  Serial.print("SD Card Type: ");
  if(cardType == CARD_MMC){
    Serial.println("MMC");
    M5.Display.println("MMC");
  } else if(cardType == CARD_SD){
    Serial.println("SDSC");             // 標準容量 SD(<=2GB)
    M5.Display.println("SDSC");
  } else if(cardType == CARD_SDHC){
    Serial.println("SDHC");             // 大容量 SD(2GB-32GB)
    M5.Display.println("SDHC");
  } else {
    Serial.println("UNKNOWN");          // 確認できない card type
    M5.Display.println("UNKNOWN");
  }
  uint64_t cardSize = SD.totalBytes() / (1024 * 1024);
  uint64_t usedSize = SD.usedBytes() / (1024 * 1024);
  Serial.printf("SD Card Size: %lluMB\n", cardSize);
  Serial.printf("SD Card Uesd Size: %lluMB\n", usedSize);
  M5.Display.printf("%llu/%lluMB\n", usedSize, cardSize);
  delay(500);
}

// ------------------- 画像ファイル読み込み -------------------
// fs:      ファイルシステムオブジェクト
// path:    画像ファイルのフルパス名(.bmp/.jpg/.png)
void videoFile(fs::FS &fs, const char * path){
  if(strlen(path) > 0){
    strncpy(VFlNm,path,FLNMSZ-1);
  }
  Serial.printf("Reading file: %s\n", VFlNm);
  String s = String(VFlNm);
  if (s.indexOf(".bmp") != -1) {
    M5.Display.drawBmpFile(fs,s,0,0);
  }else if (s.indexOf(".jpg") != -1) {
    M5.Display.drawJpgFile(fs,s,0,0);
  }else if (s.indexOf(".png") != -1) {
    M5.Display.drawPngFile(fs,s,0,0);
  }
}

//================== Setup =============================
void setup() {
  M5.begin();
  M5.Display.clear();
  M5.Display.setFont(&fonts::FreeMonoBold9pt7b);
  Serial.begin(115200);
  Serial.println("<-------Atomic TFCard Base Example------->"); 

  // ------------------- SD Card Initialization -------------------
  // 指定ピンでSPIバスを初期化 (SCK, MISO, MOSI, CS)
  SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN, SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
  Serial.println("SD Init...");
  M5.Display.drawCenterString("SD Init...", 64, 0);
 // SDカードを25MHzのSPIクロック速度で初期化を試みる
  if (!SD.begin(SD_SPI_CS_PIN, SPI, 25000000)) { // CSは未時用のGPIO5を指定
    Serial.println("SD Error!");
    M5.Display.println("SD Error!");
    while (1);                          // 無限ループ
  } else {
    Serial.println("SD Ready");
    M5.Display.println("SD Ready");
  }
  InfoSDcard();                         // SDカード情報の表示
}

//================= Main Loop =======================
void loop() { 
  char *s;
  if(getSerialLine()){
    char cmd = RcvBf[0];
    s = RcvBf + 1;
    switch(cmd){
      case '!':                           // Atom画面の消去
        clrDisplay();              break;
      case 'F':                           // ファイル名の設定
        setFilNam(s);              break;
      case 'R':                           // ファイルの読込・表示
        readFile(SD, s);           break;
      case 'I':                           // SD情報の表示
        InfoSDcard();              break;
      case 'W':                           // FilNmファイルにデータ書込
        writeFile(SD, FilNm, s);   break;
      case 'A':                           // FilNmファイルにデータ追記
        appendFile(SD, FilNm, s);  break;
      case 'L':                           // ファイルリストの表示
        listDir(SD, s, 0);         break;
      case 'V':                           // 画像ファイルの表示
        videoFile(SD, s);          break;
      case 'D':                           // ファイルを削除
        deleteFile(SD, s);         break; 
      case 'N':                           // 現在ファイル名を変更
        renameFile(SD, FilNm, s);  break;
      case 'C':                           // 新ディレクトリーを作成
        createDir(SD, s);          break; 
      case 'M':                           // ディレクトリーを削除
        removeDir(SD, s);          break;   
      default:                            // 受信データをそのまま表示
        M5.Display.println(RcvBf); break;
    }
  }
}

【参考】
 ・Arudino日本語リファレンス(武蔵野電波)


※プログラムのリストをハイライト付きのスタイルで見る場合はここをクリック


※ 本レポートの参考・利用は、あくまでも自己責任でお願いします。


15・エンハンサー M5A)_SDカードを使う