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.

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; }
    }
}

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 216 217 218
#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);
    }
}

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 214
#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);
    }
}

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