In this tutorial, the main control device used is Core2 v1.1, paired with Unit Mini PDM. This unit communicates via I2S. Modify the pin definitions in the program according to the actual circuit connections. After the device is connected, the corresponding I2S pins are G33 (CLK)
and G32 (DATA)
.
#include <M5Unified.h>
#define PIN_CLK 33
#define PIN_DATA 32
// Audio recording configuration constants
static constexpr const size_t record_number = 256; // Number of recording buffers
static constexpr const size_t record_length = 320; // Samples per buffer (recommended to match LCD width)
static constexpr const size_t record_size = record_number * record_length; // Total recording size
static constexpr const size_t record_samplerate = 16000; // Recording sample rate in Hz
// Variables for waveform drawing
static int16_t prev_y[record_length]; // Previous Y positions for waveform clearing
static int16_t prev_h[record_length]; // Previous heights for waveform clearing
static size_t rec_record_idx = 2; // Current recording buffer index
static size_t draw_record_idx = 0; // Current buffer index for drawing
static int16_t *rec_data; // Pointer to recording data buffer
static int32_t w; // Display width
void setup(void) {
// Initialize M5 device with default configuration
auto cfg = M5.config();
M5.begin(cfg);
// Configure display
M5.Display.setRotation(1); // Set display rotation
w = M5.Display.width(); // Get display width
M5.Display.startWrite(); // Start display write transaction
M5.Display.setTextDatum(top_center);// Set text alignment
M5.Display.setTextColor(TFT_WHITE); // Set text color
M5.Display.setFont(&fonts::FreeSansBoldOblique9pt7b); // Set display font
// Configure microphone settings
m5::mic_config_t mic_cfg = {
.pin_data_in = PIN_DATA, // Microphone data input pin
.pin_bck = I2S_PIN_NO_CHANGE, // No change for I2S bit clock
.pin_mck = I2S_PIN_NO_CHANGE, // No change for I2S master clock
.pin_ws = PIN_CLK, // Microphone word select (clock) pin
.sample_rate = record_samplerate, // Microphone sample rate in Hz
.dma_buf_len = 128, // DMA buffer length
.dma_buf_count = 2, // Number of DMA buffers
.i2s_port = i2s_port_t::I2S_NUM_0 // I2S port number
};
// Apply microphone configuration
M5.Mic.config(mic_cfg);
// Allocate memory for recording buffer
rec_data = (typeof(rec_data))heap_caps_malloc(record_size * sizeof(int16_t), MALLOC_CAP_8BIT);
memset(rec_data, 0, record_size * sizeof(int16_t)); // Initialize buffer to zero
// Start microphone
M5.Mic.begin();
// Display recording status indicators
M5.Display.fillCircle(200, 28, 8, RED);
M5.Display.drawString("REC", w / 2, 18);
}
void loop(void) {
// Update M5 device state
M5.update();
// Check if microphone is enabled
if (M5.Mic.isEnabled()) {
static constexpr int shift = 6; // Shift value for amplitude scaling
auto data = &rec_data[rec_record_idx * record_length]; // Get current recording buffer
// Record audio data into buffer
if (M5.Mic.record(data, record_length)) {
data = &rec_data[draw_record_idx * record_length]; // Get buffer for drawing
// Ensure display width doesn't exceed buffer size
if (w > record_length - 1) {
w = record_length - 1;
}
// Draw audio waveform
for (int32_t x = 0; x < w; ++x) {
// Clear previous waveform at this X position
M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK);
// Calculate waveform points (scaled by shift)
int32_t y1 = (data[x] >> shift);
int32_t y2 = (data[x + 1] >> shift);
// Ensure y1 is the lower value
if (y1 > y2) {
int32_t tmp = y1;
y1 = y2;
y2 = tmp;
}
// Calculate display coordinates
int32_t y = ((M5.Display.height()) >> 1) + y1; // Base Y position
int32_t h = ((M5.Display.height()) >> 1) + y2 + 1 - y; // Waveform height
// Store current values for next frame's clearing
prev_y[x] = y;
prev_h[x] = h;
// Draw current waveform segment
M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], WHITE);
}
// Update display and maintain recording indicators
M5.Display.display();
M5.Display.fillCircle(200, 28, 8, RED); // Red circle for recording status
M5.Display.drawString("REC", w / 2, 18); // "REC" text indicator
// Update buffer indices (wrap around when reaching end)
if (++draw_record_idx >= record_number) { draw_record_idx = 0; }
if (++rec_record_idx >= record_number) { rec_record_idx = 0; }
}
}
}
#include "driver/i2s_pdm.h"
#include <M5Unified.h>
#include <M5GFX.h>
// Define microphone pins
#define PIN_CLK GPIO_NUM_33
#define PIN_DATA GPIO_NUM_32
// Audio buffer and recording settings
static constexpr const size_t record_number = 2; // Total number of recording buffers
static constexpr const size_t record_length = 320; // Samples per buffer (should match display width)
static constexpr const size_t record_size = record_number * record_length; // Total number of samples
static constexpr const size_t record_samplerate = 16000; // Sample rate in Hz
// Arrays used for clearing previous waveform lines
static int16_t prev_y[record_length]; // Y-coordinates of previous frame lines
static int16_t prev_h[record_length]; // Heights of previous frame lines
// Indices for recording/drawing buffers
static size_t rec_record_idx = 2; // Index to current input buffer
static size_t draw_record_idx = 0; // Index to current drawing buffer
static int16_t *rec_data; // Pointer to audio buffer
static int32_t w; // Display width
// I2S hardware handle for ESP32
i2s_chan_handle_t rx_handle = nullptr;
#define I2S_PORT I2S_NUM_0
void setup(void) {
// Initialize M5 hardware (screen, etc.)
auto cfg = M5.config();
M5.begin(cfg);
// Initialize display parameters
M5.Display.setRotation(1);
w = M5.Display.width();
M5.Display.startWrite();
M5.Display.setTextDatum(top_center);
M5.Display.setTextColor(TFT_WHITE);
M5.Display.setFont(&fonts::FreeSansBoldOblique9pt7b);
// Create I2S channel
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_PORT, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
// Configure PDM microphone parameters (clock, slots, gpio)
i2s_pdm_rx_config_t pdm_rx_cfg = {
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(record_samplerate),
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.clk = PIN_CLK,
.din = PIN_DATA,
.invert_flags = {.clk_inv = false },
}
};
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
// Allocate memory for audio buffer and clear it
rec_data = (typeof(rec_data))heap_caps_malloc(record_size * sizeof(int16_t), MALLOC_CAP_8BIT);
memset(rec_data, 0, record_size * sizeof(int16_t));
// Draw recording status indicators
M5.Display.fillCircle(200, 28, 8, RED);
M5.Display.drawString("REC", w / 2, 18);
}
// Read one frame of audio from microphone
bool mic_record(int16_t* buf, size_t samples) {
size_t bytes_to_read = samples * sizeof(int16_t);
size_t bytes_read = 0;
esp_err_t ret = i2s_channel_read(rx_handle, (void*)buf, bytes_to_read, &bytes_read, 50 / portTICK_PERIOD_MS);
return (ret == ESP_OK && bytes_read == bytes_to_read);
}
void loop(void) {
M5.update(); // Update button/status of M5
static constexpr int shift = 6; // Used for amplitude scaling (bit shift)
auto data = &rec_data[rec_record_idx * record_length]; // Pointer to current record buffer
// Record new audio data into buffer
if (mic_record(data, record_length)) {
data = &rec_data[draw_record_idx * record_length]; // Set pointer to buffer for drawing
if (w > record_length - 1) {
w = record_length - 1;
}
// Draw waveform on display buffer
for (int32_t x = 0; x < w; ++x) {
// Erase previous waveform line at position x
M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_BLACK);
// Get two consecutive samples, scale down via shift
int32_t y1 = (data[x] >> shift);
int32_t y2 = (data[x + 1] >> shift);
// Ensure y1<=y2 for drawing from low to high
if (y1 > y2) {
int32_t tmp = y1;
y1 = y2;
y2 = tmp;
}
// Calculate line's start vertical position and height
int32_t y = ((M5.Display.height()) >> 1) + y1; // Vertical origin is center
int32_t h = ((M5.Display.height()) >> 1) + y2 + 1 - y;
// Store for later clearing
prev_y[x] = y;
prev_h[x] = h;
// Draw current waveform line in white
M5.Display.writeFastVLine(x, prev_y[x], prev_h[x], TFT_WHITE);
}
// Commit display changes and redraw recording status
M5.Display.display();
M5.Display.fillCircle(200, 28, 8, RED);
M5.Display.drawString("REC", w / 2, 18);
// Move indices to next buffer, wrap around if necessary
if (++draw_record_idx >= record_number) { draw_record_idx = 0; }
if (++rec_record_idx >= record_number) { rec_record_idx = 0; }
}
}
#include <SPI.h>
#include <SD.h>
#include <M5Unified.h>
// Pin definitions for SD card SPI communication
#define SD_SPI_CS_PIN 4
#define SD_SPI_SCK_PIN 18
#define SD_SPI_MISO_PIN 38
#define SD_SPI_MOSI_PIN 23
// Pin definitions for microphone I2S communication
#define MIC_I2S_PIN_CLK 33
#define MIC_I2S_PIN_DATA 32
// Audio recording configuration parameters
static constexpr size_t record_number = 16; // Number of recording buffers
static constexpr size_t record_length = 512; // Sample length per recording
static constexpr size_t record_samplerate = 16000; // Recording sample rate in Hz
static constexpr uint8_t bitsPerSample = 16; // Bits per audio sample
static constexpr uint8_t numChannels = 1; // Number of audio channels (1 = mono)
// Global variables for recording
static int16_t *rec_data; // Buffer to store recorded audio data
static bool isFirstWrite = true; // Flag for first write operation
static bool isRecording = false; // Flag indicating recording state
static size_t totalSamples = 0; // Total number of recorded samples
unsigned long record_start_ms = 0; // Timestamp when recording started
static int32_t w; // Display width
// Structure defining WAV file header format
struct WavHeader {
char riff[4]; // "RIFF" identifier
uint32_t fileSize; // Total file size minus 8
char wave[4]; // "WAVE" identifier
char fmt[4]; // "fmt " identifier
uint32_t fmtSize; // Size of the format chunk
uint16_t format; // Audio format (1 = PCM)
uint16_t channels; // Number of audio channels
uint32_t sampleRate; // Audio sample rate
uint32_t byteRate; // Byte rate (sampleRate * channels * bitsPerSample/8)
uint16_t blockAlign; // Block alignment (channels * bitsPerSample/8)
uint16_t bitsPerSample;// Bits per sample
char data[4]; // "data" identifier
uint32_t dataSize; // Size of the audio data
};
/**
* Writes the WAV file header to the specified file
* @param file File object to write header to
* @param pcmBytes Total size of PCM audio data in bytes
*/
void writeWavHeader(File &file, uint32_t pcmBytes) {
WavHeader header;
memcpy(header.riff, "RIFF", 4);
header.fileSize = 36 + pcmBytes; // Calculate total file size
memcpy(header.wave, "WAVE", 4);
memcpy(header.fmt, "fmt ", 4);
header.fmtSize = 16; // PCM format chunk size
header.format = 1; // PCM format
header.channels = numChannels;
header.sampleRate = record_samplerate;
header.byteRate = record_samplerate * numChannels * (bitsPerSample / 8);
header.blockAlign = numChannels * (bitsPerSample / 8);
header.bitsPerSample = bitsPerSample;
memcpy(header.data, "data", 4);
header.dataSize = pcmBytes;
file.seek(0); // Move to start of file
file.write((const uint8_t*)&header, sizeof(WavHeader)); // Write header
}
File wavFile; // File object for WAV recording
void setup(void) {
auto cfg = M5.config(); // Get default M5 configuration
M5.begin(cfg); // Initialize M5Stack device
M5.Display.setRotation(1); // Set display rotation
w = M5.Display.width(); // Get display width
M5.Display.setTextDatum(top_center); // Set text alignment
M5.Display.setTextColor(TFT_BLACK, TFT_WHITE); // Set text colors
M5.Display.setFont(&fonts::FreeSansBoldOblique9pt7b); // Set font
// Initialize SD card
SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN, SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
M5.Display.drawCenterString("SD Initializing...", w/2, 0);
if (!SD.begin(SD_SPI_CS_PIN, SPI, 25000000)) { // Attempt to initialize SD card
M5.Display.drawCenterString("SD Init Error!", w/2, 50);
while (1); // Halt if SD card initialization fails
} else {
M5.Display.drawCenterString("SD Ready", w/2, 30);
}
// Configure microphone
m5::mic_config_t mic_cfg = {
.pin_data_in = MIC_I2S_PIN_DATA,
.pin_bck = I2S_PIN_NO_CHANGE,
.pin_mck = I2S_PIN_NO_CHANGE,
.pin_ws = MIC_I2S_PIN_CLK,
.sample_rate = record_samplerate,
.dma_buf_len = 128,
.dma_buf_count = 8,
.i2s_port = I2S_NUM_0
};
M5.Mic.config(mic_cfg); // Apply microphone configuration
M5.Mic.begin(); // Initialize microphone
if (M5.Mic.isEnabled()) { // Check if microphone initialized successfully
M5.Display.drawCenterString("Mic Ready", w/2, 50);
}
M5.Mic.end(); // Temporary stop microphone
delay(500);
// Allocate memory for audio recording buffer
rec_data = (int16_t*)heap_caps_malloc(
record_length * sizeof(int16_t),
MALLOC_CAP_8BIT | MALLOC_CAP_32BIT
);
if (!rec_data) { // Check if memory allocation failed
M5.Display.drawCenterString("Memory Alloc Error", w/2, 60);
while (1); // Halt if memory allocation fails
}
memset(rec_data, 0, record_length * sizeof(int16_t)); // Initialize buffer to zero
M5.Display.clear(TFT_WHITE);
// Display usage instructions
M5.Display.drawCenterString("Mic Recording Example", w/2, 10);
M5.Display.drawCenterString("Click Btn to Start/Stop", w/2, 45);
M5.Display.drawCenterString("Start Stop", w/2, 210);
}
void loop(void) {
M5.update(); // Update M5Stack device state (buttons, sensors, etc.)
//----------------- Start recording when Button A is pressed -----------------
if (M5.BtnA.wasClicked() && !isRecording) {
M5.Display.fillRect(0, 110, w, 100, TFT_WHITE); // Clear previous status
isRecording = true;
isFirstWrite = true;
totalSamples = 0;
// Remove existing file if it exists
if (SD.exists("/recording.wav")) {
SD.remove("/recording.wav");
M5.Display.drawCenterString("Old File Deleted", w/2, 80);
}
// Open new file for writing
wavFile = SD.open("/recording.wav", FILE_WRITE);
if (!wavFile) { // Check if file opened successfully
M5.Display.drawCenterString("Create WAV File Error!", w/2, 100);
isRecording = false;
return;
}
// Write temporary header (will be updated at end of recording)
writeWavHeader(wavFile, 0);
// Start microphone and begin recording
if (M5.Mic.begin()) {
record_start_ms = millis();
M5.Display.drawCenterString("Recording...", w/2, 160);
} else {
M5.Display.drawCenterString("Mic Start Error!", w/2, 160);
isRecording = false;
wavFile.close();
}
delay(15);
}
//----------------- Stop recording when Button C is pressed -----------------
if (M5.BtnC.wasClicked() && isRecording) {
isRecording = false;
}
//----------------- Recording process (synchronous task) -----------------
if (isRecording && M5.Mic.isEnabled() && wavFile) {
// Submit recording block task
bool submitted = M5.Mic.record(rec_data, record_length);
if (submitted) {
// Wait for recording to complete
while (M5.Mic.isRecording()) {
vTaskDelay(2); // Short delay to reduce CPU usage
}
// Write recorded data to file
size_t written = wavFile.write((const uint8_t*)rec_data, record_length * sizeof(int16_t));
totalSamples += record_length;
// Display recording status: time and file size
float sec = ((float)totalSamples) / record_samplerate;
uint32_t pcmBytes = totalSamples * sizeof(int16_t);
char info[64];
sprintf(info, "Time: %.2fs Size:%dKB", sec, pcmBytes/1024);
M5.Display.drawCenterString(info, w/2, 110);
} else {
M5.Display.drawCenterString("Mic Record Fail!", w/2, 110);
vTaskDelay(10);
}
// Short delay between recording blocks to reduce SD card stress
vTaskDelay(5);
}
//------------------- Finalize recording and update file header ------------------
if (!isRecording && wavFile) {
M5.Mic.end(); // Stop microphone
// Calculate total audio data size and update WAV header
uint32_t pcmBytes = totalSamples * sizeof(int16_t);
writeWavHeader(wavFile, pcmBytes);
wavFile.close();
wavFile = File(); // Clear file handle
// Display recording summary
float sec = ((float)totalSamples) / record_samplerate;
char info[64];
sprintf(info, "New WAV Saved: %.2fs, %dKB", sec, pcmBytes/1024);
M5.Display.drawCenterString(info, w/2, 160);
delay(35);
}
}
#include <SPI.h>
#include <SD.h>
#include <driver/i2s_pdm.h>
#include <M5Unified.h>
// Pin definitions for SD card SPI communication
#define SD_SPI_CS_PIN 4
#define SD_SPI_SCK_PIN 18
#define SD_SPI_MISO_PIN 38
#define SD_SPI_MOSI_PIN 23
// Pin definitions for microphone I2S communication
#define MIC_I2S_PIN_CLK GPIO_NUM_33
#define MIC_I2S_PIN_DATA GPIO_NUM_32
// Audio recording configuration parameters
static constexpr size_t record_number = 16; // Number of recording buffers
static constexpr size_t record_length = 512; // Sample length per recording
static constexpr size_t record_samplerate = 16000; // Recording sample rate in Hz
static constexpr uint8_t bitsPerSample = 16; // Bits per audio sample
static constexpr uint8_t numChannels = 1; // Number of audio channels (1 = mono)
// Global variables for recording
static int16_t *rec_data; // Buffer to store recorded audio data
static bool isRecording = false; // Flag indicating recording state
static size_t totalSamples = 0; // Total number of recorded samples
unsigned long record_start_ms = 0; // Timestamp when recording started
static int32_t w; // Display width
// I2S hardware handle for ESP32
i2s_chan_handle_t rx_handle = nullptr;
#define I2S_PORT I2S_NUM_0
// Structure defining WAV file header format
struct WavHeader {
char riff[4]; // "RIFF" identifier
uint32_t fileSize; // Total file size minus 8
char wave[4]; // "WAVE" identifier
char fmt[4]; // "fmt " identifier
uint32_t fmtSize; // Size of the format chunk
uint16_t format; // Audio format (1 = PCM)
uint16_t channels; // Number of audio channels
uint32_t sampleRate; // Audio sample rate
uint32_t byteRate; // Byte rate (sampleRate * channels * bitsPerSample/8)
uint16_t blockAlign; // Block alignment (channels * bitsPerSample/8)
uint16_t bitsPerSample;// Bits per sample
char data[4]; // "data" identifier
uint32_t dataSize; // Size of the audio data
};
/**
* Writes the WAV file header to the specified file
* @param file File object to write header to
* @param pcmBytes Total size of PCM audio data in bytes
*/
void writeWavHeader(File &file, uint32_t pcmBytes) {
WavHeader header;
memcpy(header.riff, "RIFF", 4);
header.fileSize = 36 + pcmBytes; // Calculate total file size
memcpy(header.wave, "WAVE", 4);
memcpy(header.fmt, "fmt ", 4);
header.fmtSize = 16; // PCM format chunk size
header.format = 1; // PCM format
header.channels = numChannels;
header.sampleRate = record_samplerate;
header.byteRate = record_samplerate * numChannels * (bitsPerSample / 8);
header.blockAlign = numChannels * (bitsPerSample / 8);
header.bitsPerSample = bitsPerSample;
memcpy(header.data, "data", 4);
header.dataSize = pcmBytes;
file.seek(0); // Move to start of file
file.write((const uint8_t*)&header, sizeof(WavHeader)); // Write header
}
File wavFile; // File object for WAV recording
void setup(void) {
auto cfg = M5.config(); // Get default M5 configuration
M5.begin(cfg); // Initialize M5Stack device
M5.Display.setRotation(1); // Set display rotation
w = M5.Display.width(); // Get display width
M5.Display.setTextDatum(top_center); // Set text alignment
M5.Display.setTextColor(TFT_BLACK, TFT_WHITE); // Set text colors
M5.Display.setFont(&fonts::FreeSansBoldOblique9pt7b); // Set font
// Initialize SD card
SPI.begin(SD_SPI_SCK_PIN, SD_SPI_MISO_PIN, SD_SPI_MOSI_PIN, SD_SPI_CS_PIN);
M5.Display.drawCenterString("SD Initializing...", w/2, 0);
if (!SD.begin(SD_SPI_CS_PIN, SPI, 25000000)) { // Attempt to initialize SD card
M5.Display.drawCenterString("SD Init Error!", w/2, 50);
while (1); // Halt if SD card initialization fails
} else {
M5.Display.drawCenterString("SD Ready", w/2, 30);
}
// Create I2S channel
i2s_chan_config_t chan_cfg = I2S_CHANNEL_DEFAULT_CONFIG(I2S_PORT, I2S_ROLE_MASTER);
ESP_ERROR_CHECK(i2s_new_channel(&chan_cfg, NULL, &rx_handle));
// Configure PDM microphone parameters (clock, slots, gpio)
i2s_pdm_rx_config_t pdm_rx_cfg = {
.clk_cfg = I2S_PDM_RX_CLK_DEFAULT_CONFIG(record_samplerate),
.slot_cfg = I2S_PDM_RX_SLOT_DEFAULT_CONFIG(I2S_DATA_BIT_WIDTH_16BIT, I2S_SLOT_MODE_MONO),
.gpio_cfg = {
.clk = MIC_I2S_PIN_CLK,
.din = MIC_I2S_PIN_DATA,
.invert_flags = {.clk_inv = false },
}
};
ESP_ERROR_CHECK(i2s_channel_init_pdm_rx_mode(rx_handle, &pdm_rx_cfg));
ESP_ERROR_CHECK(i2s_channel_enable(rx_handle));
// Allocate memory for audio recording buffer
rec_data = (int16_t*)heap_caps_malloc(
record_length * sizeof(int16_t),
MALLOC_CAP_8BIT | MALLOC_CAP_32BIT
);
if (!rec_data) { // Check if memory allocation failed
M5.Display.drawCenterString("Memory Alloc Error", w/2, 60);
while (1); // Halt if memory allocation fails
}
memset(rec_data, 0, record_length * sizeof(int16_t)); // Initialize buffer to zero
M5.Display.clear(TFT_WHITE);
// Display usage instructions
M5.Display.drawCenterString("Mic Recording Example", w/2, 10);
M5.Display.drawCenterString("Click Btn to Start/Stop", w/2, 45);
M5.Display.drawCenterString("Start Stop", w/2, 210);
}
// Read one frame of audio from microphone
bool mic_record(int16_t* buf, size_t samples) {
size_t bytes_to_read = samples * sizeof(int16_t);
size_t bytes_read = 0;
esp_err_t ret = i2s_channel_read(rx_handle, (void*)buf, bytes_to_read, &bytes_read, 50 / portTICK_PERIOD_MS);
return (ret == ESP_OK && bytes_read == bytes_to_read);
}
void loop(void) {
M5.update(); // Update M5Stack device state (buttons, sensors, etc.)
//----------------- Start recording when Button A is pressed -----------------
if (M5.BtnA.wasClicked() && !isRecording) {
M5.Display.fillRect(0, 110, w, 100, TFT_WHITE); // Clear previous status
isRecording = true;
totalSamples = 0;
// Remove existing file if it exists
if (SD.exists("/recording.wav")) {
SD.remove("/recording.wav");
M5.Display.drawCenterString("Old File Deleted", w/2, 80);
}
// Open new file for writing
wavFile = SD.open("/recording.wav", FILE_WRITE);
if (!wavFile) { // Check if file opened successfully
M5.Display.drawCenterString("Create WAV File Error!", w/2, 100);
isRecording = false;
return;
}
// Write temporary header (will be updated at end of recording)
writeWavHeader(wavFile, 0);
record_start_ms = millis();
M5.Display.drawCenterString("Recording...", w/2, 160);
delay(15);
}
//----------------- Stop recording when Button C is pressed -----------------
if (M5.BtnC.wasClicked() && isRecording) {
isRecording = false;
}
//----------------- Recording process (synchronous task) -----------------
if (isRecording && wavFile) {
// Submit recording block task
bool submitted = mic_record(rec_data, record_length);
if (submitted) {
// Write recorded data to file
size_t written = wavFile.write((const uint8_t*)rec_data, record_length * sizeof(int16_t));
totalSamples += record_length;
// Display recording status: time and file size
float sec = ((float)totalSamples) / record_samplerate;
uint32_t pcmBytes = totalSamples * sizeof(int16_t);
char info[64];
sprintf(info, "Time: %.2fs Size:%dKB", sec, pcmBytes/1024);
M5.Display.drawCenterString(info, w/2, 110);
} else {
M5.Display.drawCenterString("Mic Record Fail!", w/2, 110);
vTaskDelay(10);
}
// Short delay between recording blocks to reduce SD card stress
vTaskDelay(5);
}
//------------------- Finalize recording and update file header ------------------
if (!isRecording && wavFile) {
M5.Mic.end(); // Stop microphone
// Calculate total audio data size and update WAV header
uint32_t pcmBytes = totalSamples * sizeof(int16_t);
writeWavHeader(wavFile, pcmBytes);
wavFile.close();
wavFile = File(); // Clear file handle
// Display recording summary
float sec = ((float)totalSamples) / record_samplerate;
char info[64];
sprintf(info, "New WAV Saved: %.2fs, %dKB", sec, pcmBytes/1024);
M5.Display.drawCenterString(info, w/2, 160);
delay(35);
}
}
The effect of this example is to draw the audio waveform in real time, the main control device display is shown in the figure below.
Press button A to start recording, and then press button C to stop recording. The recorded audio will be saved as a WAV file. During recording, the main control screen will display the recording time and WAV file size in real time. After successfully completing a recording and saving the file, the screen display is shown in the figure below.