pdf-icon

Arduino Quick Start

2. Devices & Examples

6. Applications

Unit Mini PDM Arduino Tutorial

1. Preparation

  • Environment configuration: Refer to Arduino IDE Quick Start to complete the IDE installation, install the corresponding board management according to the development board in use, and install the required driver libraries.
  • Driver libraries used:
  • Hardware products used:

2. Notes

Pin Compatibility
Due to different pin configurations for each host device, to make it easier for users to use, M5Stack officially provides a pin compatibility chart for easy reference. Please modify the example program according to the actual pin connection situation.

3. Example Program

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).

Note
The two examples below based on different APIs have the same function, only the I2S configuration method is different. Users can choose according to their actual needs.

3.1 Basic Usage

Based on M5Unified Mic API

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
#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; } } } } 

Based on ESP32 I2S API

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127
#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; } } }

3.2 Save the Recording File (wav format) to an SD Card

Based on M5Unified Mic API

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215
#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 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("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; 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); } }

Based on ESP32 I2S API

cpp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
#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("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); } }

4. Compile and Upload

  • Copy and paste the above example code into your project code area according to your needs, select the device port (for details, please refer to Program Compilation and Upload), click the compile and upload button in the upper left corner of the Arduino IDE, and wait for the program to complete compilation and upload to the device.

5. Microphone Function Demonstration

  • 1.Basic Usage

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.

  • 2.Save the Recording File to SD Card

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.

On This Page