いわゆる DOS の自作なんて私には到底無理ですが、まねっこしてみました。大げさですが、これまでの様々な知識の集大成です。併せて幾つかの問題も解決できたかな?
概要。
・TFT-LCD を日本語表示、自動スクロール可能に(横約60桁、縦19+1行)
・ファイルシステムとして microSDカード を接続
・ESP32 に対し、シリアル通信でコマンドとパラメーターを送信し、何か操作を行う。
環境・構成・内容は以下です。
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
コマンド文字列をシリアル通信で 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カードの破損など、自己責任でお願いします。
#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); // 使用するパネルをセットします。
}
};
#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();
}
長年の夢(ささやかな)が少しだけ叶いました。最後まで見ていただきありがとうございました。