0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

71歳の... ESP32はDOSマシンになるか?コマンド入力で操作したい。ついでに問題点の解決も?

Last updated at Posted at 2025-06-14

 いわゆる DOS の自作なんて私には到底無理ですが、まねっこしてみました。大げさですが、これまでの様々な知識の集大成です。併せて幾つかの問題も解決できたかな?
 概要。

TFT-LCD を日本語表示、自動スクロール可能に(横約60桁、縦19+1行)
・ファイルシステムとして microSDカード を接続
ESP32 に対し、シリアル通信でコマンドとパラメーターを送信し、何か操作を行う。

環境・構成・内容は以下です。

console.jpg

1.Windows 11 Pro
2.マイコンは、ESP32-S3-DevKit-C(16MBのFlash)
    Arduino IDE 2.3.6 では「ESP32S3 Dev Module」となる。
    ツール>Partition Scheme:"Huge APP...
3.ファイルシステムとして、 microSD Card(32GB)
    コンパイルエラーが起こるので「esp32 by Espressif Systems 3.0.0」にする
4.画面表示器は、3.95" TFT LCD Shield Display Module
    8bitパラレル接続、480*320、ILI9488、SPI接続microSDスロット付き
5.TFT用ライブラリは、「LovyanGFX」(これである必要はないと...)
6.Serial.print(ln)出力とほぼ同じ内容を TFTLCD にも表示させる。
    TFTLCD は横長にて使用。横60桁程度、縦19行+プロンプト行まで表示可能。
    縦19行を越えたら自動スクロール。
7.WAVファイル再生にはPCM5102Aを使用。電源は別途用意。   
8.現在までに実装したコマンド
    ・ファイル操作関連のコマンド
    ・画像ファイルの表示
    ・グラフィックの基本コマンド
    ・PWM出力コマンド(音の出力、LEDの点灯)
    ・以下のレベルのWAVファイルの再生
        Total size :38067156
        Format section size :16
        Wave format :1
        Channels :2
        Sample Rate :44100
        Byte Rate :176400
        Block Align :4
        Bits Per Sample :16

TFTLCDBoard.jpg

コマンド文字列をシリアル通信で ESP32 側に送るのですが、送信後に文字列の編集ができません。それで、Arduino IDE 付属の「シリアルモニター」を使用します。「Tera Term」では一文字ごとに送信してしまい、使えませんでした。コマンド入力を例示します。

機能 コマンド文字列の例 備考
画面の消去 cls
WAVファイル再生 wav *.wav ルートより下の階層も可能
画像の表示 pic *.bmp *.png *.jpg
音を出す tone 440 6 周波数 時間
SDカードの情報 sd
Dir/Fileを全て表示 ls
指定のDir/fileを調査 ls dirname/filename
ファイル名の変更 rn /file0 /file1 安全のため、ルートファイルの頭には/を付ける仕様
ファイルの中身の表示 re readme.txt
ファイルの生成 cr test.txt
ファイルへの書込み wr test.c
ファイルの削除 RM test1.txt 安全のため大文字
ドローモードへ入る draw 無限ループに入る
直線の描画 li 20 10 200 160 xBegin yBegin xEnd yEnd
楕円・円の描画 el 240 160 150 100 xCenter yCenter xRadius yRadius
四角形の描画 ra 30 20 200 150 xBegin yBegin width height
ドローモードを抜ける q 無限ループから抜ける

 工夫した事、問題点、悩んだ事を挙げます。
1.SDカードルート上のファイル名には、頭に/を付ける必要があります。

2.行が多くなった場合の自動スクロールのために<vector>コンテナを使い、コードが簡潔になりました。

3.TFTLCDへの表示にdrawchar()drawstring()ではなく、print()を使ったので、日本語表示が可能になりました。昔、レコードから録音生成したWAVファイルに付けた長い日本語名も問題なく表示できています。

4.日本語フォントの問題なのかアンダーライン「_」が TFTLCD には表示できません。

5.ファイル名の変更操作はFAT部分を書換えるだけ(?)なので簡単に実装できました。ファイルのコピーは実体を伴い面倒そうなので見送り。

6.ファイルの書込みは一行ずつ行います。日本語の入力については、シリアルモニターの一行窓内で日本語に変換すると動作がおかしくなります。でも、別のところからコピー&ペーストで持ってくれば日本語も入力可能のようです。

7.次のエラーメッセージが出て、jpgなどの画像ファイル表示ができなくなっていました。解決できました。少し方法が変わったようです。

 error: cannot declare variable 'file' to be of abstract type 'lgfx::v1::DataWrapperT<const char [7]>'
   DataWrapperT<T> file ( &fs ); \
                   ^~~~

8.このエラーに悩みました。

 Guru Meditation Error: Core 1 panic'ed (LoadProhibited). Exception was unhandled.

 さっきまで動いてたのに突然発生するなどの実行時エラーです。ネット上では様々な意見があります。私の場合は、画面表示メモリ(以後VRAM)外への書込みが発生したと思われます。テキストラップを無視していたので、極端に長い文字列やVRAM終端近くでの文字列書込みによるもののようです。t.setTextWrap(false, false)と設定するとともに、画面最終の20行目に、>以外は書込まないように気を付け解決できています。


専用のLovyanGFX.hpp(スケッチと同じフォルダーに置く)とスケッチです。長くてすいません。できることが多く、詰込んじゃいました。
利用される場合は、SDカードの破損など、自己責任でお願いします。

myLovyanGFX.hpp
#pragma once

#define LGFX_USE_V1

#include <LovyanGFX.hpp>

class LGFX : public lgfx::LGFX_Device 
{
  lgfx::Panel_ILI9488   _panel_instance;
  lgfx::Bus_Parallel8   _bus_instance;  // 8ビットパラレルバスのインスタンス (ESP32のみ)
  
public:
  LGFX(void) {   
    {                                   // バス制御の設定を行います。
      auto cfg = _bus_instance.config();// バス設定用の構造体を取得します。
      // 8ビットパラレルバスの設定
      cfg.freq_write = 20000000;  // 送信クロック (最大20MHz, 80MHzを整数で割った値に丸められます)
      cfg.pin_wr = 6;//11;            // WR を接続しているピン番号
      cfg.pin_rd = 7;//12; // must    // RD を接続しているピン番号
      cfg.pin_rs = 5;//10;            // RS(D/C)を接続しているピン番号
      cfg.pin_d0 = 18;            // D0を接続しているピン番号
      cfg.pin_d1 = 39;//37;            // D1を接続しているピン番号
      cfg.pin_d2 = 17;            // D2を接続しているピン番号
      cfg.pin_d3 = 40;//38;            // D3を接続しているピン番号
      cfg.pin_d4 = 16;            // D4を接続しているピン番号
      cfg.pin_d5 = 41;//39;            // D5を接続しているピン番号
      cfg.pin_d6 = 15;            // D6を接続しているピン番号
      cfg.pin_d7 = 42;//40;            // D7を接続しているピン番号
      _bus_instance.config(cfg);               // 設定値をバスに反映します。
      _panel_instance.setBus(&_bus_instance);  // バスをパネルにセットします。
    }

    {                                       // 表示パネル制御の設定を行います。
      auto cfg = _panel_instance.config();  // 表示パネル設定用の構造体を取得します。
      cfg.pin_cs  =  4;//9;    // 27 CSが接続されているピン番号 (-1 = disable)
      cfg.pin_rst =  -1;//8;   // 33 RSTが接続されているピン番号  (-1 = disable)
      cfg.pin_busy = -1;  // BUSYが接続されているピン番号 (-1 = disable)
      cfg.memory_width = 320;                // ドライバICがサポートしている最大の幅
      cfg.memory_height = 480;               // ドライバICがサポートしている最大の高さ
      cfg.panel_width = 320;     // 実際に表示可能な幅
      cfg.panel_height = 480;    // 実際に表示可能な高さ
      cfg.offset_x = 0;          // パネルのX方向オフセット量
      cfg.offset_y = 0;          // パネルのY方向オフセット量
      cfg.offset_rotation = 0;   // 回転方向の値のオフセット 0~7 (4~7は上下反転)
      cfg.dummy_read_pixel = 8;  // ピクセル読出し前のダミーリードのビット数
      cfg.dummy_read_bits = 1;   // ピクセル以外のデータ読出し前のダミーリードのビット数
      cfg.readable = false;       // データ読出しが可能な場合 trueに設定
      cfg.invert = false;        // パネルの明暗が反転してしまう場合 trueに設定
      cfg.rgb_order = false;     // パネルの赤と青が入れ替わってしまう場合 trueに設定
      cfg.dlen_16bit = false;    // 16bitパラレルやSPIでデータ長を16bit単位で送信するパネルの場合 trueに設定
      cfg.bus_shared = false;    // SDカードとバスを共有している場合 trueに設定(drawJpgFile等でバス制御を行います)
      _panel_instance.config(cfg);
    }

    setPanel(&_panel_instance);  // 使用するパネルをセットします。
  }

};
Prompt_Command_SD.ino
#include "myLovyanGFX.hpp"
#include "SD.h"
#include "driver/i2s.h"  // Library of I2S routines, comes with ESP32 standard install
#include <string>
#include <vector>
using namespace std;

// SD Card
#define SD_CS 10  // SD Card chip select
#define SPI_MOSI 11
#define SPI_MISO 13
#define SPI_SCK 12
// Display
#define WIDTH 480
#define HEIGHT 320
#define cellX 8                         // character width
#define cellY 16                        // line feed step
#define COLS (int)(WIDTH / cellX)       //48
#define ROWS (int)(HEIGHT / cellY) - 1  //16
#define NAVY 0x0002

// I2S
#define I2S_DOUT 35  // i2S Data out oin
#define I2S_BCLK 36  // Bit clock
#define I2S_LRC 37   // Left/Right clock, also known as Frame clock or word select
#define I2S_NUM 0    // i2s port number
#define byte uint8_t
// Wav File reading
#define NUM_BYTES_TO_READ_FROM_FILE 1024 * 4  // How many bytes to read from wav file at a time
//-------------------------------------------------------------------------------------------
// structures and also variables
//  I2S configuration
static const i2s_config_t i2s_config = {
  .mode = (i2s_mode_t)(I2S_MODE_MASTER | I2S_MODE_TX),
  .sample_rate = 44100,  // Note, this will be changed later
  .bits_per_sample = I2S_BITS_PER_SAMPLE_16BIT,
  .channel_format = I2S_CHANNEL_FMT_RIGHT_LEFT,
  .communication_format = (i2s_comm_format_t)(I2S_COMM_FORMAT_I2S | I2S_COMM_FORMAT_I2S_MSB),
  .intr_alloc_flags = ESP_INTR_FLAG_LEVEL1,  // high interrupt priority
  .dma_buf_count = 8,                        // 8 buffers
  .dma_buf_len = 64,                         // 64 bytes per buffer, so 8K of buffer space
  .use_apll = 0,
  .tx_desc_auto_clear = true,
  .fixed_mclk = -1
};

static const i2s_pin_config_t pin_config = {
  .bck_io_num = I2S_BCLK,           // The bit clock connectiom, goes to pin 27 of ESP32
  .ws_io_num = I2S_LRC,             // Word select, also known as word select or left right clock
  .data_out_num = I2S_DOUT,         // Data out from the ESP32, connect to DIN on 38357A
  .data_in_num = I2S_PIN_NO_CHANGE  // (-1)we are not interested in I2S data into the ESP32
};

struct WavHeader_Struct {
  //   RIFF Section
  char RIFFSectionID[4];  // Letters "RIFF"
  uint32_t Size;          // Size of entire file less 8
  char RiffFormat[4];     // Letters "WAVE"

  //   Format Section
  char FormatSectionID[4];  // letters "fmt"
  uint32_t FormatSize;      // Size of format section less 8
  uint16_t FormatID;        // 1=uncompressed PCM
  uint16_t NumChannels;     // 1=mono,2=stereo
  uint32_t SampleRate;      // 44100, 16000, 8000 etc.
  uint32_t ByteRate;        // =SampleRate * Channels * (BitsPerSample/8)
  uint16_t BlockAlign;      // =Channels * (BitsPerSample/8)
  uint16_t BitsPerSample;   // 8,16,24 or 32

  // Data Section
  char DataSectionID[4];  // The letters "data"
  uint32_t DataSize;      // Size of the data that follows
} WavHeader;

//  Global Variables/objects
static const i2s_port_t i2s_num = I2S_NUM_0;  // i2s port number
// The file we're currently playing
File WavFile;                              // Object for root of SD card directory
static uint16_t _BytesRead = 0;
// I2S END

LGFX t;                          // LGFXのインスタンスを作成。
static LGFX_Sprite sprite1(&t);  // 画像表示用

File fp;
std::vector<String> l_buffer;
const String _rt = "/";
const int PWM_PIN = 14;

int CurY = 0, by = 0;
void tString(String s);
void pri_both(String s);
void printDirectory(File rt, int numTabs);
int split(String data, char delimiter, String* dst, int num);
void ascr();
int pn;

int pastY = 0; // 前スクロールのため

void ls_0(File fp) {
  String s;
  while (File file = fp.openNextFile()) {
    // Loop through all the files in the root directory
    Serial.print("  ");
    if (file.isDirectory()) {
      s = (String)file.name() + "/";
      Serial.print(s);
    } else {
      s = (String)file.name();
      Serial.print(s);
    }
    Serial.print("\t");
    Serial.println(file.size());  // Print the filesize
    tString("  " + s + " ;  size=" + (String)file.size() + "bytes");
    file.close();  // Close the file
  }
  Serial.println(" --- Found Dirs/Files.");
  tString(" --- Found Dirs/Files.");
}

void SD_Info() {
  uint8_t cardType = SD.cardType();  // カード情報
  String s = " - Card Type: ";
  if (cardType == CARD_MMC) s += "MMC";
  else if (cardType == CARD_SD) s += "SDSC";
  else if (cardType == CARD_SDHC) s += "SDHC";
  else s += "UNKNOWN";
  pri_both(s);

  uint64_t cardSize = SD.cardSize() / (1024 * 1024 * 1024);
  s = " - Card Size: " + (String)(cardSize) + "GB";
  pri_both(s);
}

void setup() {
  Serial.begin(115200);
  Serial.println("\n\rSerial Console start...");

  t.init();
  t.setRotation(1);
  t.setBaseColor(NAVY);
  t.clear();
  t.setFreeFont(&fonts::lgfxJapanMinchoP_12);  //&lgfx::fonts::Font2);
  t.setTextSize(1);
  t.setTextWrap(false, false);
  t.setTextColor(TFT_WHITE, NAVY);

  SD.begin(SD_CS, SPI, 24000000, "/sd");

  sprite1.createSprite(250, 250);  // 画像表示用
  ledcAttach(PWM_PIN, 24000, 8);   // for tone

  //I2S
  i2s_driver_install(i2s_num, &i2s_config, 0, NULL);
  i2s_set_pin(i2s_num, &pin_config);
}

void loop() {
  Serial.println(">");
  t.setCursor(0, CurY * cellY);
  t.print(">");
  t.setCursor(52 * cellX, 0);
  t.print("line=" + (String)by);

  while (Serial.available() == 0) { ; }

  String line = Serial.readString();
  line.trim();

  if (line.substring(0, 1) == "^") { // 前スクロール 1行ごとにリターンキー入力が必要
    if ((2 + by - ROWS + pastY) > 0) pastY--;
    ascr();
  } else {

    pastY = 0;
    pri_both(">" + line);

    if (line.substring(0, 3) == "cls") {
      t.clear();
      CurY = 0;
    }

    else if (line.substring(0, 3) == "wav") {

      WavFile = SD.open("/music/wav/Kohaku.wav");  // Open the wav file
      if (WavFile == false){
        Serial.println("Could not open 'wavfile(*.wav)'");
      }else {
        WavFile.read((byte*)&WavHeader, 44);                    // Read in the WAV header, which is first 44 bytes of the file.
                                                                // We have to typecast to bytes for the "read" function
        DumpWAVHeader(&WavHeader);                              // Dump the header data to serial, optional!
        if (ValidWavData(&WavHeader))                           // optional if your sure the WAV file will be valid.
          i2s_set_sample_rates(i2s_num, WavHeader.SampleRate);  //set sample rate
      }                                                         // Your normal code to do your task can go here
      
      while ( PlayWav() > 0){;} // 曲の終了までここでloop

    }

    else if (line.substring(0, 3) == "pic") {
      String fn = _rt + line.substring(4);  // "pic *.png" or "pic *.jpg"
      File picfile = SD.open(fn, FILE_READ, false);
      if (picfile) {
        if (line.substring(line.length() - 3) == "bmp") {
          t.drawBmp(&picfile, 300, 0);  // tft_lcd座標(300,0)に描画
        } else if (line.substring(line.length() - 3) == "png") {
          t.drawPng(&picfile, 50, 0);  // tft_lcd座標(50,0)に描画
        } else if (line.substring(line.length() - 3) == "jpg") {
          // Setted as [static LGFX_Sprite sprite1(&t);]
          sprite1.drawJpg(&picfile, 0, 0);  // スプライト座標(0,0)に描画
          sprite1.pushSprite(50, 50);       // tft_lcd座標(50,50)にスプライトを描画
          sprite1.pushRotateZoom(350, 200, 0, 0.5, 0.5, TFT_WHITE);
        } else {
          ;
        }
      } else {
        String s = " " + fn + " not exists.";
        pri_both(s);
      }
    }

    else if (line.length() > 5 && line.substring(0, 4) == "tone") {
      int n = 2;  // "tone freq duration"
      String za[n];
      int co[n];
      bool flag = true;
      if (split(line.substring(5), ' ', za, n) != n) flag = false;
      for (int i = 0; i < n; i++) {
        if (!(co[i] = za[i].toInt())) {
          flag = false;
          break;
        }
      }
      if (flag) {
        ledcWriteTone(PWM_PIN, co[0]);  // freq
        delay(co[1] * 500);             // duration
        ledcWriteTone(PWM_PIN, 0);
      }
    } else if (line.substring(0, 2) == "sd") {  // SD information
      SD_Info();

    } else if (line.substring(0, 2) == "ls") {
      if (line.length() == 2) {  // list up all dirs/files
        File root = SD.open(_rt);
        pn = 0;
        printDirectory(root, 0);
      } else {
        String s, fn = _rt + line.substring(3);  // "ls dir/file"
        if (SD.exists(fn)) {
          s = " " + fn + " exists.";
          pri_both(s);
          File fp = SD.open(fn);
          if (fp.isDirectory()) ls_0(fp);  //そのdir内のみを表示
        } else {
          s = " " + fn + " not exists.";
          pri_both(s);
        }
      }

    } else if (line.substring(0, 2) == "rn") {  // rename filename
      int n = 2;                                // "rn /before.txt /after.txt"
      String za[n];
      bool flag = true;
      if (split(line.substring(3), ' ', za, n) != n) flag = false;
      if (flag) {
        if (!SD.exists(za[0])) {
          String s = " " + za[0] + " not exists. unabel!";
          pri_both(s);
        } else if (SD.exists(za[1])) {
          String s = " " + za[1] + " already exists. Prohibited!";
          pri_both(s);
        } else {
          String s = " rename " + za[0] + " -> " + za[1] + ".";
          pri_both(s);
          if (SD.rename(za[0], za[1])) pri_both(" successed.");
          else pri_both(" failed.");
        }
      }

    } else if (line.substring(0, 2) == "re") {  // read file
      String s, fn = _rt + line.substring(3);   // "re filename"
      if (SD.exists(fn)) {
        File fp = SD.open(fn, FILE_READ);
        if (fp) {
          s = " --- " + fn + " begin ...";
          pri_both(s);
          while (fp.available()) {  // read from the file until there's nothing else in it
            String l = fp.readStringUntil('\n');
            l.trim();
            pri_both(l);
          }
          fp.close();  // close the file:
          s = " --- " + fn + " ends.";
          pri_both(s);
        } else {  // if the file didn't open, print an error
          s = " --- " + fn + " open error.";
          pri_both(s);
        }
      } else {
        s = " --- " + fn + " not exists.";
        pri_both(s);
      }

    } else if (line.length() > 3 && line.substring(0, 2) == "cr") {
      String s, fn = _rt + line.substring(3);  // "cr filename"
      if (SD.exists(fn)) {
        s = " " + fn + " already exists. Prohibited!";
        pri_both(s);
      } else {
        File fp = SD.open(fn, FILE_WRITE);
        if (fp) {
          fp.close();
          s = " " + fn + " created.";
          pri_both(s);
        } else {
          s = " " + fn + " create error.";
          pri_both(s);
        }
      }

    } else if (line.length() > 3 && line.substring(0, 2) == "wr") {
      String fn = _rt + line.substring(3);  // "wr filename"
      String s = " Write to " + fn;
      pri_both(s);
      File fp = SD.open(fn, FILE_APPEND);
      if (fp) {
        s = " file opened. Type a line or EOF.";
        pri_both(s);
        while (1) {
          if (Serial.available()) {
            String data = Serial.readString();
            data.trim();
            if (data.equals("EOF")) {
              fp.close();
              break;
            }
            fp.println(data);  // 1行ごと書き込む
            s = " Inputed a line >" + data;
            pri_both(s);
          }
        }
        s = " --- EOF Ended. file closed.";
        pri_both(s);
      } else {
        s = " file open refused.";
        pri_both(s);
      }

    } else if (line.length() > 3 && line.substring(0, 2) == "RM") {
      String s, fn = _rt + line.substring(3);
      if (SD.exists(fn)) {
        SD.remove(fn);
        s = " " + fn + " removed";
        pri_both(s);
      } else {
        s = " " + fn + " not exists.";
        pri_both(s);
      }

    } else if (line.equals("draw")) {  // Draw Graphics mode
      t.clear();
      String dr = "";  // line , ellipse or rectangle
      while (1) {
        t.drawLine(0, HEIGHT / 2, WIDTH, HEIGHT / 2, 0x39E7);
        t.drawLine(WIDTH / 2, 0, WIDTH / 2, HEIGHT, 0x39E7);
        Serial.print(">");
        t.setCursor(0, 0);
        t.print(">");

        while (Serial.available() == 0) { ; }  // ever loop
        dr = Serial.readString();
        dr.trim();

        if (dr.substring(0, 1) == "q") break;

        if (dr.length() > 3) {  // Input->"li/el/rt x0 y0 x1 y1"
          t.fillRect(0, 0, WIDTH / 2, cellY, NAVY);
          String k = dr.substring(0, 2);
          int n = 4;
          String za[n];
          int co[n];
          bool flag = true;
          if (split(dr.substring(3), ' ', za, n) != n) flag = false;
          for (int i = 0; i < n; i++) {
            if (!(co[i] = za[i].toInt())) {
              flag = false;
              break;
            }
          }
          if (flag) {
            if (k.equals("li")) t.drawLine(co[0], co[1], co[2], co[3], TFT_YELLOW);
            else if (k.equals("el")) t.drawEllipse(co[0], co[1], co[2], co[3], TFT_MAGENTA);
            else if (k.equals("ra")) t.drawRect(co[0], co[1], co[2], co[3], TFT_RED);
            else { ; }
          }
          t.setCursor(0, 0);
          t.print(">" + dr);
        }
      }
      t.clear();
    }

    else {
      pri_both("?");
    }
  }
}

void pri_both(String s) {
  Serial.println(s);
  tString(s);
}

void tString(String s) {
  l_buffer.push_back(s);
  t.setCursor(0, CurY * cellY);
  t.print(s);
  ++by;
  ++CurY;
  ascr();
}

void ascr() {
  if (by >= ROWS) {
    t.clear();

    for (int y = 0; y < ROWS; y++) {
      t.setCursor(0, y * cellY);
      t.print(l_buffer[2 + by - ROWS + y + pastY]);  // add +pastY
    }
    CurY = ROWS - pastY;  // add - pastY
  }
}

int split(String data, char delimiter, String* dst, int num) {
  int index = 0;
  int len = data.length();
  for (int i = 0; i < len; i++) {
    char tmp = data.charAt(i);
    if (tmp == delimiter) {
      index++;
      if (index > num - 1) return -1;
    } else {
      dst[index] += tmp;
    }
  }
  return index + 1;
}

void printDirectory(File rt, int numTabs) {
  while (File file = rt.openNextFile()) {
    Serial.printf(" #%d=", ++pn);
    for (uint8_t i = 0; i < numTabs; i++) Serial.print('\t');

    if (file.isDirectory()) {
      Serial.println((String)file.name() + "/");
      tString(" #" + (String)pn + "=" + (String)file.name() + '/');
      printDirectory(file, numTabs + 1);
    } else {  // files have sizes, directories do not
      Serial.print(file.name());
      Serial.print("\t\t");
      Serial.println(file.size(), DEC);
      tString(" #" + (String)pn + "=" + (String)file.name() + " _ " + (String)file.size());
    }
    file.close();
  }
}

// I2S functions for playing wav file ---------------------------------------------------------------------------------------------------
uint16_t PlayWav() {
  static bool ReadingFile = true;                   // True if reading file from SD. false if filling I2S buffer
  static byte Samples[NUM_BYTES_TO_READ_FROM_FILE]; // Memory allocated to store the data read in from the wav file
                                                    // Num bytes actually read from the wav file which will either be
                                                    // NUM_BYTES_TO_READ_FROM_FILE or less than this if we are very
                                                    // near the end of the file. i.e. we can't read beyond the file.
  if (ReadingFile)  // Read next chunk of data in from file if needed
  {
    _BytesRead = ReadFile(Samples);  // Read data into our memory buffer, return num bytes read in
    if (_BytesRead == 0) return 0;
    ReadingFile = false;  // Switch to sending the buffer to the I2S
  } else
    ReadingFile = FillI2SBuffer(Samples, _BytesRead);  // We keep calling this routine until it returns true, at which point
                                                       // this will swap us back to Reading the next block of data from the file.
                                                       // Reading true means it has managed to push all the data to the I2S
                                                       // Handler, false means there still more to do and you should call this
                                                       // routine again and again until it returns true.
  return _BytesRead;
}

uint16_t ReadFile(byte* Samples) {
  static uint32_t BytesReadSoFar = 0;  // Number of bytes read from file so far
  uint16_t BytesToRead;                // Number of bytes to read from the file

  if (BytesReadSoFar + NUM_BYTES_TO_READ_FROM_FILE > WavHeader.DataSize)  // If next read will go past the end then adjust the
    BytesToRead = WavHeader.DataSize - BytesReadSoFar;                    // amount to read to whatever is remaining to read
  else
    BytesToRead = NUM_BYTES_TO_READ_FROM_FILE;  // Default to max to read

  WavFile.read(Samples, BytesToRead);  // Read in the bytes from the file
  BytesReadSoFar += BytesToRead;       // Update the total bytes red in so far

  if (BytesReadSoFar >= WavHeader.DataSize)  // Have we read in all the data?
  {
    // WavFile.seek(44); // Reset to start of wav data.(loop back)
    BytesReadSoFar = 0;  // Clear to no bytes read in so far
    BytesToRead = 0;
  }
  return BytesToRead;  // return the number of bytes read into buffer
}

bool FillI2SBuffer(byte* Samples, uint16_t BytesInBuffer) {
  // Writes bytes to buffer, returns true if all bytes sent else false, keeps track itself of how many left
  // to write, so just keep calling this routine until returns true to know they've all been written, then
  // you can re-fill the buffer

  size_t BytesWritten;            // Returned by the I2S write routine,
  static uint16_t BufferIdx = 0;  // Current pos of buffer to output next
  uint8_t* DataPtr;               // Point to next data to send to I2S
  uint16_t BytesToSend;           // Number of bytes to send to I2S

  // To make the code eaier to understand I'm using to variables to some calculations, normally I'd write this calcs
  // directly into the line of code where they belong, but this make it easier to understand what's happening

  DataPtr = Samples + BufferIdx;                               // Set address to next byte in buffer to send out
  BytesToSend = BytesInBuffer - BufferIdx;                     // This is amount to send (total less what we've already sent)
  i2s_write(i2s_num, DataPtr, BytesToSend, &BytesWritten, 1);  // Send the bytes, wait 1 RTOS tick to complete
  BufferIdx += BytesWritten;                                   // increasue by number of bytes actually written

  if (BufferIdx >= BytesInBuffer) {
    // sent out all bytes in buffer, reset and return true to indicate this
    BufferIdx = 0;
    return true;
  } else
    return false;  // Still more data to send to I2S so return false to indicate this
}

bool ValidWavData(WavHeader_Struct* Wav) {
  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
    Serial.print("Invalid data - Not RIFF format");
    return false;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
    Serial.print("Invalid data - Not Wave file");
    return false;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
    Serial.print("Invalid data - No format section found");
    return false;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
    Serial.print("Invalid data - data section not found");
    return false;
  }
  if (Wav->FormatID != 1) {
    Serial.print("Invalid data - format Id must be 1");
    return false;
  }
  if (Wav->FormatSize != 16) {
    Serial.print("Invalid data - format section size must be 16.");
    return false;
  }
  if ((Wav->NumChannels != 1) & (Wav->NumChannels != 2)) {
    Serial.print("Invalid data - only mono or stereo permitted.");
    return false;
  }
  if (Wav->SampleRate > 48000) {
    Serial.print("Invalid data - Sample rate cannot be greater than 48000");
    return false;
  }
  if ((Wav->BitsPerSample != 8) & (Wav->BitsPerSample != 16)) {
    Serial.print("Invalid data - Only 8 or 16 bits per sample permitted.");
    return false;
  }
  return true;
}

void DumpWAVHeader(WavHeader_Struct* Wav) {
  if (memcmp(Wav->RIFFSectionID, "RIFF", 4) != 0) {
    Serial.print("Not a RIFF format file - ");
    PrintData(Wav->RIFFSectionID, 4);
    return;
  }
  if (memcmp(Wav->RiffFormat, "WAVE", 4) != 0) {
    Serial.print("Not a WAVE file - ");
    PrintData(Wav->RiffFormat, 4);
    return;
  }
  if (memcmp(Wav->FormatSectionID, "fmt", 3) != 0) {
    Serial.print("fmt ID not present - ");
    PrintData(Wav->FormatSectionID, 3);
    return;
  }
  if (memcmp(Wav->DataSectionID, "data", 4) != 0) {
    Serial.print("data ID not present - ");
    PrintData(Wav->DataSectionID, 4);
    return;
  }
  // All looks good, dump the data
  Serial.print("Total size :");
  Serial.println(Wav->Size);
  Serial.print("Format section size :");
  Serial.println(Wav->FormatSize);
  Serial.print("Wave format :");
  Serial.println(Wav->FormatID);
  Serial.print("Channels :");
  Serial.println(Wav->NumChannels);
  Serial.print("Sample Rate :");
  Serial.println(Wav->SampleRate);
  Serial.print("Byte Rate :");
  Serial.println(Wav->ByteRate);
  Serial.print("Block Align :");
  Serial.println(Wav->BlockAlign);
  Serial.print("Bits Per Sample :");
  Serial.println(Wav->BitsPerSample);
  Serial.print("Data Size :");
  Serial.println(Wav->DataSize);
}

void PrintData(const char* Data, uint8_t NumBytes) {
  for (uint8_t i = 0; i < NumBytes; i++)
    Serial.print(Data[i]);
  Serial.println();
}

 長年の夢(ささやかな)が少しだけ叶いました。最後まで見ていただきありがとうございました。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?