pdf-icon

Arduino Quick Start

2. Devices & Examples

6. Applications

M5GO Base Analog Microphone

Example program for M5GO Base Analog Microphone.

Example

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
#include <SPI.h>       
#include <SD.h>       
#include <M5Unified.h> 

// Pin definitions
#define AIN_PIN 34            // Analog input pin for microphone signal
#define SD_SPI_CS_PIN     4   // Chip Select pin for SD card SPI communication
#define SD_SPI_SCK_PIN    18  // SPI Clock pin for SD card
#define SD_SPI_MISO_PIN   19  // SPI MISO (Master In Slave Out) pin for SD card
#define SD_SPI_MOSI_PIN   23  // SPI MOSI (Master Out Slave In) pin for SD card

// Recording parameters
constexpr size_t record_length     = 512;    // Number of samples per recording loop
constexpr size_t record_samplerate = 12000;  // Sampling frequency in Hertz (Hz)
constexpr uint8_t bitsPerSample    = 16;     // Bit depth per audio sample
constexpr uint8_t numChannels      = 1;      // Number of audio channels (1 = mono)

static int16_t *rec_data;                  // Buffer to store current batch of samples
static volatile bool isRecording = false;  // Flag indicating recording state
static size_t totalSamples = 0;            // Total number of samples recorded
unsigned long record_start_ms = 0;         // Timestamp when recording started
static int32_t w;                          // Display width (for UI positioning)

// Structure defining the WAV file header format
struct WavHeader {
    char riff[4];         // "RIFF" identifier (Resource Interchange File Format)
    uint32_t fileSize;    // Total file size minus 8 bytes (size of RIFF chunk)
    char wave[4];         // "WAVE" identifier (specifies WAV format)
    char fmt[4];          // "fmt " identifier (format subchunk)
    uint32_t fmtSize;     // Size of the format subchunk (16 for PCM)
    uint16_t format;      // Audio format (1 = PCM, linear quantization)
    uint16_t channels;    // Number of audio channels
    uint32_t sampleRate;  // Sampling frequency in Hz
    uint32_t byteRate;    // Byte rate (sampleRate * channels * bitsPerSample/8)
    uint16_t blockAlign;  // Block alignment (channels * bitsPerSample/8)
    uint16_t bitsPerSample;// Number of bits per sample
    char data[4];         // "data" identifier (data subchunk)
    uint32_t dataSize;    // Size of the audio data in bytes
};

/**
 * Writes the WAV file header to a file
 * @param file Reference to the open WAV file
 * @param pcmBytes Total size of the audio data in bytes
 */
void writeWavHeader(File &file, uint32_t pcmBytes) {
    WavHeader header;
    memcpy(header.riff, "RIFF", 4);               // Set RIFF identifier
    header.fileSize      = 36 + pcmBytes;          // Calculate total file size
    memcpy(header.wave, "WAVE", 4);               // Set WAVE identifier
    memcpy(header.fmt, "fmt ", 4);                // Set format subchunk identifier
    header.fmtSize       = 16;                     // PCM format subchunk size
    header.format        = 1;                      // PCM format
    header.channels      = numChannels;            // Set number of channels
    header.sampleRate    = record_samplerate;      // Set sampling rate
    header.byteRate      = record_samplerate * numChannels * (bitsPerSample / 8);  // Calculate byte rate
    header.blockAlign    = numChannels * (bitsPerSample / 8);  // Calculate block alignment
    header.bitsPerSample = bitsPerSample;          // Set bit depth
    memcpy(header.data, "data", 4);                // Set data subchunk identifier
    header.dataSize      = pcmBytes;               // Set audio data size
    file.seek(0);                                  // Move to start of file
    file.write((const uint8_t*)&header, sizeof(WavHeader));  // Write header data
}

File wavFile;  // File object for the WAV recording

void setup(void) {
    auto cfg = M5.config(); 
    M5.begin(cfg);       
    M5.Display.setRotation(1);  
    w = M5.Display.width();  
    M5.Display.setTextDatum(top_center);  
    M5.Display.setTextColor(TFT_BLACK, TFT_WHITE); 
    M5.Display.setFont(&fonts::FreeSansBoldOblique9pt7b); 

    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)) {
        M5.Display.drawCenterString("SD Init Error!", w/2, 50); 
        while (1);  // Halt execution if SD card fails
    } else {
        M5.Display.drawCenterString("SD Ready", w/2, 30);
    }

    // Allocate memory buffer for audio samples with specific memory capabilities
    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);  // Show error message
        while (1);  // Halt execution if memory allocation fails
    }
    memset(rec_data, 0, record_length * sizeof(int16_t));  // Initialize buffer to zero

    M5.Display.clear(TFT_WHITE);
    M5.Display.drawCenterString("ADC Mic Recording Example", w/2, 10);
    M5.Display.drawCenterString("A:Start   C:Stop", w/2, 45);
    M5.Display.drawCenterString("Start                                  Stop", w/2, 210);
}

uint32_t previous_sample_us = 0;  // Timestamp of previous sample (for rate control)
// Calculate microseconds between samples to maintain target sampling rate
const uint32_t sampling_interval_us = 1000000UL / record_samplerate;

void loop(void) {
    M5.update();

    // Start recording when Button A is pressed (and not already recording)
    if (M5.BtnA.wasClicked() && !isRecording) {
        M5.Display.fillRect(0, 110, w, 100, TFT_WHITE);
        isRecording = true;                             
        totalSamples = 0;                            

        // Delete existing recording file if it exists
        if (SD.exists("/recording.wav")) {
            SD.remove("/recording.wav");
            M5.Display.drawCenterString("Old File Deleted", w/2, 80);
        }

        // Open new WAV file for writing
        wavFile = SD.open("/recording.wav", FILE_WRITE);
        if (!wavFile) {  // Check if file creation failed
            M5.Display.drawCenterString("Create WAV File Error!", w/2, 100);
            isRecording = false;  
            return;
        }

        writeWavHeader(wavFile, 0);  // Write initial header (will be updated later)
        record_start_ms = millis();  // Record start time
        M5.Display.drawCenterString("Recording...", w/2, 160);  
        delay(15);  // Short delay to debounce button
    }

    // Stop recording when Button C is pressed (and currently recording)
    if (M5.BtnC.wasClicked() && isRecording) {
        isRecording = false;  
    }

    // Main recording process - runs while recording is active
    if (isRecording && wavFile) {
        uint32_t now = micros();  // Get current time in microseconds
        int32_t sum = 0;          // Accumulator for sample analysis (not used in output)

        // Collect record_length samples
        for (size_t i = 0; i < record_length; ++i) {
            // Wait until correct time for next sample (maintains sampling rate)
            while (micros() - now < i * sampling_interval_us) {}
            
            // Read analog value from microphone pin
            int adval = analogRead(AIN_PIN);
            
            // Convert 12-bit ADC value (0-4095) to 16-bit signed sample:
            // - Subtract 2048 to center around 0 (DC offset removal)
            // - Shift left by 4 bits to scale 12-bit range to 16-bit range
            rec_data[i] = (int16_t)((adval - 2048) << 4);
            sum += rec_data[i];  // Accumulate sample (for potential future use)
        }

        // Write collected samples to SD card
        size_t written = wavFile.write((const uint8_t*)rec_data, record_length * sizeof(int16_t));
        totalSamples += record_length;  // Update total sample count

        // Calculate and display recording status
        float sec = ((float)totalSamples) / record_samplerate;  // Calculate recording time in seconds
        uint32_t pcmBytes = totalSamples * sizeof(int16_t);     // Calculate total data size
        char info[64];
        sprintf(info, "Time: %.2fs Size:%dKB", sec, pcmBytes/1024); 
        M5.Display.drawCenterString(info, w/2, 110); 
    }

    // Finalize recording when stopped
    if (!isRecording && wavFile) {
        uint32_t pcmBytes = totalSamples * sizeof(int16_t);  // Calculate total data size
        writeWavHeader(wavFile, pcmBytes);  // Update header with final data size
        wavFile.close();               
        wavFile = File();               

        // Display final recording information
        float sec = ((float)totalSamples) / record_samplerate;  // Calculate total recording time
        char info[64];
        sprintf(info, "New WAV Saved: %.2fs, %dKB", sec, pcmBytes/1024);
        M5.Display.drawCenterString(info, w/2, 160); 
        delay(35);  // Short delay for stability
    }
}

After successfully downloading the above example, press BtnA to start recording, and press BtnC to stop recording. The recording file will be saved in the root directory of the SD card with the filename recording.wav. The recording process is as follows:

On This Page