Xiao MG24 Mic library - no samples captured at all

Hellp, I’m trying to run in Arduino Studio the Seed Arduino mic→ mic serial recording example, with minor modifications, together with the supplied python script, to try to capture 3 seconds of audio at 8kHz. The sketch runs, the python script captures the output, but the issue is, the script seemingly does not respect the timing specified in the mic_config_t mic_config struct.

As it is, my main buffer is 24000 bytes long:

#define SAMPLES 8000*3
int16_t recording_buf[SAMPLES];

and the mic_config_t struct is like that:

mic_config_t mic_config{
  .channel_cnt = 1,
  .sampling_rate = 8000,
  .buf_size = 1600,
  .debug_pin = LED_BUILTIN                
};

As far I understand, with these settings the ISR routine that transfer the captured samples from the internal buffer to my main buffer (which I will output to serial after capturing all samples) should take ~3 seconds to capture everything: for 8kHz, the ISR should be called 15 times: ((24000/8000) 1600), and each call should happen every 200ms ((1 sec / 8kHz) * 1600).

But it just finishes very quickly, in less than a second, and doesn’t capture all samples. It’s like its not respecting the interval to call the ISR (200ms between calls), filling the buffer almost instantly with noise.

Can someone please help to check the code (which is mainly Seeed’s code anyway), or have any experience with the <mic.h> library to see if there’s anything missing in the setup?

Thank you very much,

Luis.

Arduino sketch and python code supplied below.

Arduino:

#include <mic.h>
// Settings
#define DEBUG 1                 // Enable pin pulse during ISR  
#define SAMPLES 8000*3          // 3 seconds of audio @ 8kHz

// Define the DC Offset based on the ADC range (0 to 4000)
#define DC_OFFSET 1900

mic_config_t mic_config{
  .channel_cnt = 1,
  .sampling_rate = 8000,
  .buf_size = 1600,
  .debug_pin = LED_BUILTIN                
};

MG24_ADC_Class Mic(&mic_config);

int16_t recording_buf[SAMPLES];
volatile static bool record_ready = false;
volatile static bool recording    = false;
volatile static uint32_t idx = 0;
volatile static uint32_t vezes = 0;

volatile static unsigned long initialTime = 0;
volatile static unsigned long finalTime;

void setup() {
  Serial.begin(115200);
  while (!Serial) {delay(10);}

  Mic.set_callback(audio_rec_callback);

  if (!Mic.begin()) {
    Serial.println("init_fail");
    while (1);
  }
  Serial.println("init_ok");
}

void loop() { 
  // Wait for "init" command
  while(Serial.readString() != "init\n") {
  }
  Serial.println("init_ok");  


  // Wait for "rec" command
  while(Serial.readString() != "rec\n") {
  }

  idx = 0;             // Reset index
  recording = true;    // Start the recording process
  vezes = 0;

  // Wait for the callback to finish filling the buffer
  while(!record_ready) {
  }

  //Serial.print("Passed through record_ready=true "); Serial.print(vezes);       Serial.print(" times in ");
  //Serial.print(finalTime - initialTime); Serial.println(" ms.");

  // Data transfer section
  if (record_ready) {
    Serial.println("rec_ok");
    for (int i = 0; i < SAMPLES; i++) {
      Serial.println(recording_buf[i]);
    }
  Serial.println("fi");
  record_ready = false; 
  }
}

static void audio_rec_callback(uint16_t *buf, uint32_t buf_len) {
  if (recording) {
    vezes++;
    if (initialTime == 0) { initialTime=millis(); }
    for (uint32_t i = 0; i < buf_len; i++) {     
      recording_buf[idx] = (int16_t)((buf[i] - DC_OFFSET) * 16);
      idx++;
      if (idx >= SAMPLES) { 
        finalTime = millis();
        initialTime = 0;
        idx = 0; 
        record_ready = true;
        recording = false;
        break;
      } 
    }
  }
}

Python code:

import os, sys
import serial
import wave, struct
import logging
import argparse

logging.basicConfig(level=logging.INFO)

commands = [b"“, b"rec_ok”, b"init_ok", b"fi"]

sampleRate = 8000 # hertz
duration = 3 # seconds

def write_wav_data(raw_sound, filename):
logging.debug(raw_sound)

obj = wave.open(filename, 'w')
obj.setnchannels(1) # mono
obj.setsampwidth(2)
obj.setframerate(sampleRate)

for value in raw_sound:
    data = struct.pack('<h', value)
    obj.writeframesraw(data)
obj.close()

def main(args):
i = 1
ser = serial.Serial(args.port, args.baud_rate, timeout=1)
logging.info(‘Awaiting response from device’)

while True:
    ser.write(b"init\n")
    recv = ser.readline().rstrip()
    print(recv)
    if recv == b'init_ok':
        logging.info('Device init successful')            
        break
    if recv == b'init_fail':
        logging.error('Device init failed')
        sys.exit(0)

while True: 
    try:
        logging.info('READY') 

        input("Press Enter to continue...")
        ser.write(b"rec\n")
        logging.info('RECORDING')  
        recv = ""
        raw_sound = []
        while True:
            recv = ser.readline().rstrip()
            if recv == b"rec_ok":
                logging.info('RECORDING FINISHED') 
            if recv == b"fi":
                logging.info('TRANSFER FINISHED')
                break 
            if not recv in commands:
                raw_sound.append(int(recv))
                #print(int(recv))
            #logging.debug(recv)

        filename = args.filename + str(i) + ".wav"
        write_wav_data(raw_sound, filename)
        i += 1

    except KeyboardInterrupt:
        logging.info('Exiting script')            
        break

if name == ‘main’:

argparser = argparse.ArgumentParser(
    description='Record and save sound from device')

argparser.add_argument(
    '-p',
    '--port',
    help='port for connection to the device')

argparser.add_argument(
    '-b',
    '--baud_rate',
    default=115200,
    help='Connection baud rate')

argparser.add_argument(
    '-n',
    '--filename',
    default='sound',        
    help='Prefix for sound files')

args = argparser.parse_args()

main(args)

Hi there,

So , Some may get there painties in a twist..but
I see you math is correct, the sampling would be if the ADC actually ran at 8 kHz.
Remeber this is Marketing from some,
But on MG24, the mic example and docs only ever show sampling_rate = 16000. There is a very good chance that:

  • The driver ignores 8000 and snaps to a higher fixed rate (e.g. 16 kHz or 48 kHz), or
  • The current Arduino wrapper doesn’t yet support 8 kHz and just runs at a default of 16 kHz.
    Do this to check;
if (idx >= SAMPLES) { 
  finalTime = millis();
  Serial.print("Capture time (ms): ");
  Serial.println(finalTime - initialTime);
  ...
}

Then compute what you get…

effective_rate = SAMPLES / ((finalTime - initialTime) / 1000.0)

Just switch to a supported rate and admit it :grin:

mic_config_t mic_config{
  .channel_cnt   = 1,
  .sampling_rate = 16000,  // use 16k as in Seeed’s demo
  .buf_size      = 1600,
  .debug_pin     = LED_BUILTIN
};

#define SAMPLES 16000*3

and in Python:

sampleRate = 16000
duration = 3

If you really need 3 seconds, then just set it;
"sampleRate = effective_rate # what you measured "
That way the WAV header matches :+1:

the only other would be make sure your "record_ready " is reset before each capture.

HTH
GL :santa_claus: PJ :v:

Hi PJ_Glasso,

I was just about to write a reply to myself when I got your answer. The fact is…the library was incomplete. After looking at the code, in short, the ADC is programmed to use a 10Mhz clock… and was let run at this speed! In no place on the code it was programmed to respect the sampling rate specified on the struct.

I’ve made several changes to the code to:

  1. Program a timer (I’m using TIMER3) at the specified frequency (right now I have changed it to 16kHz);

  2. Plug the timer output as the input gate for the ADC sampling;

  3. Instead of letting it run continuosly, trigger a single sample at the rise edge of the gate signal (the TIMER output)

And now it works! It captures 3 seconds of samples and I can play the .wav file on Audacity.

Question is, would you like to have this code and update the library? It is a bit disappointing to buy a neat device like the MG24 and discover that the code was kind of abandoned (last revision 2021?). I don’t care about having a github account and publish it in my name, but I think it would be nice for other devs (specially those not experienced enough to modify libraries) to just have the right library at start. And, of course, I may (probably!) be doing something wrong or incomplete, so your team could finish it and ship the library as ready.

Thanks!

1 Like

Hi there,

Excellent, You are preaching to the Quire on the Abandonment..Seems to be a thing lately , everyone has left the Chat for the New :grin: shinny AI object..

If we get lucky someone from seeed or even a MOLE from SIL maybe and it will happen :crossed_fingers:

Glad it’s working and please do us a solid and mark it as the solution, so others can find it fast. :+1:

Season’s Greetings :christmas_tree:
GL :santa_claus: PJ :v: