Wio Terminal DMA access for microphone readings

Hello.

I’m trying to write a library in order to read the analog values of the microphone using direct memory access.

The project is building but the values are not responding to sound.

I can’t find the source of the problem, maybe a couple more eyes can find what’s missing.

thanks

I’m checking it for you.

1 Like

Hi @nicolas-f:
https://github.com/nicolas-f/wioterminal-microphone/blob/dmatest/MicrophoneReadings/MicrophoneReadings.ino#L55 maybe you can modify ADC0 to ADC1 to resolve this problem.

1 Like

Thank you ! This is the solution. It works like a charm now. Now I can write a library to get audio from Wio Terminal with high frequency with low cpu usage.

@nicolas-f Looking forward to your update.

Apologies for responding to an old thread, but I wanted to add a possible solution in case anyone is reading this.

As per @Baozhu 's post here (Using Wio Terminal microphone with Edge Impulse), the internal mic is only configured (in hardware) to measure sound levels rather than actually record audio. To get around this, I was able to connect an electret microphone (with amplifier) to pin 13 (ADC0, PB08).

Here is an example that will read 1 second of audio and print it to the console in comma-separated value format (you can copy and paste these values into Python code to graph with Matplotlib or play the sound). It will only do this once on reboot.

// Use SAMD51's DMAC to read from microphone connected to PB08 (ADC0/AIN2)
// From: https://forum.arduino.cc/index.php?topic=685347.0
// and https://forum.arduino.cc/index.php?topic=709104.0

// Settings
#define DEBUG 1
enum { ADC_BUF_LEN = 1600 };
enum { AUDIO_BUF_LEN = ADC_BUF_LEN * 10 };
#if DEBUG
static const int debug_pin = 1;           // Toggles each DAC ISR
#endif

// Structs
typedef struct           // DMAC descriptor structure
{
  uint16_t btctrl;
  uint16_t btcnt;
  uint32_t srcaddr;
  uint32_t dstaddr;
  uint32_t descaddr;
} dmacdescriptor ;

// Globals
volatile uint8_t recording = 0;
volatile boolean results0Ready = false;
volatile boolean results1Ready = false;
uint16_t adc_buf_0[ADC_BUF_LEN];    // ADC results array 0
uint16_t adc_buf_1[ADC_BUF_LEN];    // ADC results array 1
int16_t audio_buf[AUDIO_BUF_LEN];   // Big buffer for testing

volatile dmacdescriptor wrb[DMAC_CH_NUM] __attribute__ ((aligned (16)));          // Write-back DMAC descriptors
dmacdescriptor descriptor_section[DMAC_CH_NUM] __attribute__ ((aligned (16)));    // DMAC channel descriptors
dmacdescriptor descriptor __attribute__ ((aligned (16)));                         // Place holder descriptor

/*******************************************************************************
 * Interrupt Service Routines (ISRs)
 */

// Called from ISR to copy double buffer contents
static void audio_rec_callback(uint16_t *buf, uint32_t buf_len) {

  static uint32_t idx = 0;

  // If recording flag is set, copy contents of ADC buffer to audio buffer
  if (recording) {
    for (int i = 0; i < buf_len; i++) {

      // Convert 12-bit unsigned ADC value to 16-bit PCM (signed) audio value
      audio_buf[idx] = ((int16_t)buf[i] - 2048) * 16;

      // Increment big buffer pointer. If it reaches max, stop recording
      idx++;
      if (idx >= AUDIO_BUF_LEN) {
        recording = 0;
        idx = 0;
      }
    }
  }
}

// Interrupt handler for DMAC channel 0
void DMAC_0_Handler() {

  static uint8_t count = 0;
  static uint16_t idx = 0;

  // Check if DMAC channel 0 has been suspended (SUSP)
  if (DMAC->Channel[0].CHINTFLAG.bit.SUSP) {

     // Debug: make pin high before copying buffer
#if DEBUG
    digitalWrite(debug_pin, HIGH);
#endif

    // Restart DMAC on channel 0 and clear SUSP interrupt flag
    DMAC->Channel[0].CHCTRLB.reg = DMAC_CHCTRLB_CMD_RESUME;
    DMAC->Channel[0].CHINTFLAG.bit.SUSP = 1;

    // See which buffer has filled up, and dump results into large buffer
    if (count) {
      audio_rec_callback(adc_buf_0, ADC_BUF_LEN);
    } else {
      audio_rec_callback(adc_buf_1, ADC_BUF_LEN);
    }

    // Flip to next buffer
    count = (count + 1) % 2;

    // Debug: make pin low after copying buffer
#if DEBUG
    digitalWrite(debug_pin, LOW);
#endif
  }
}

/*******************************************************************************
 * Functions
 */

// Configure DMA to sample from ADC at regular interval
// I'm sorry everything is hardcoded. I don't have time to make a library.
// Adafruit_ZeroDMA may work--I haven't tried it.
void config_dma_adc() {
  
  // Configure DMA to sample from ADC at a regular interval (triggered by timer/counter)
  DMAC->BASEADDR.reg = (uint32_t)descriptor_section;                          // Specify the location of the descriptors
  DMAC->WRBADDR.reg = (uint32_t)wrb;                                          // Specify the location of the write back descriptors
  DMAC->CTRL.reg = DMAC_CTRL_DMAENABLE | DMAC_CTRL_LVLEN(0xf);                // Enable the DMAC peripheral
  DMAC->Channel[0].CHCTRLA.reg = DMAC_CHCTRLA_TRIGSRC(TC5_DMAC_ID_OVF) |      // Set DMAC to trigger on TC5 timer overflow
                                 DMAC_CHCTRLA_TRIGACT_BURST;                  // DMAC burst transfer
  descriptor.descaddr = (uint32_t)&descriptor_section[1];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adc_buf_0 + sizeof(uint16_t) * ADC_BUF_LEN;  // Place it in the adc_buf_0 array
  descriptor.btcnt = ADC_BUF_LEN;                                             // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
  memcpy(&descriptor_section[0], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section
  descriptor.descaddr = (uint32_t)&descriptor_section[0];                     // Set up a circular descriptor
  descriptor.srcaddr = (uint32_t)&ADC0->RESULT.reg;                           // Take the result from the ADC0 RESULT register
  descriptor.dstaddr = (uint32_t)adc_buf_1 + sizeof(uint16_t) * ADC_BUF_LEN;  // Place it in the adc_buf_1 array
  descriptor.btcnt = ADC_BUF_LEN;                                             // Beat count
  descriptor.btctrl = DMAC_BTCTRL_BEATSIZE_HWORD |                            // Beat size is HWORD (16-bits)
                      DMAC_BTCTRL_DSTINC |                                    // Increment the destination address
                      DMAC_BTCTRL_VALID |                                     // Descriptor is valid
                      DMAC_BTCTRL_BLOCKACT_SUSPEND;                           // Suspend DMAC channel 0 after block transfer
  memcpy(&descriptor_section[1], &descriptor, sizeof(descriptor));            // Copy the descriptor to the descriptor section

  // Configure NVIC
  NVIC_SetPriority(DMAC_0_IRQn, 0);    // Set the Nested Vector Interrupt Controller (NVIC) priority for DMAC0 to 0 (highest)
  NVIC_EnableIRQ(DMAC_0_IRQn);         // Connect DMAC0 to Nested Vector Interrupt Controller (NVIC)

  // Activate the suspend (SUSP) interrupt on DMAC channel 0
  DMAC->Channel[0].CHINTENSET.reg = DMAC_CHINTENSET_SUSP;

  // Configure ADC
  ADC0->INPUTCTRL.bit.MUXPOS = ADC_INPUTCTRL_MUXPOS_AIN2_Val; // Set the analog input to ADC0/AIN2 (PB08 - A4 on Metro M4)
  while(ADC0->SYNCBUSY.bit.INPUTCTRL);                // Wait for synchronization
  ADC0->SAMPCTRL.bit.SAMPLEN = 0x00;                  // Set max Sampling Time Length to half divided ADC clock pulse (2.66us)
  while(ADC0->SYNCBUSY.bit.SAMPCTRL);                 // Wait for synchronization 
  ADC0->CTRLA.reg = ADC_CTRLA_PRESCALER_DIV128;       // Divide Clock ADC GCLK by 128 (48MHz/128 = 375kHz)
  ADC0->CTRLB.reg = ADC_CTRLB_RESSEL_12BIT |          // Set ADC resolution to 12 bits
                    ADC_CTRLB_FREERUN;                // Set ADC to free run mode       
  while(ADC0->SYNCBUSY.bit.CTRLB);                    // Wait for synchronization
  ADC0->CTRLA.bit.ENABLE = 1;                         // Enable the ADC
  while(ADC0->SYNCBUSY.bit.ENABLE);                   // Wait for synchronization
  ADC0->SWTRIG.bit.START = 1;                         // Initiate a software trigger to start an ADC conversion
  while(ADC0->SYNCBUSY.bit.SWTRIG);                   // Wait for synchronization

  // Enable DMA
  DMAC->Channel[0].CHCTRLA.bit.ENABLE = 1;            // Enable DMAC ADC on channel 1

  // Configure Timer/Counter 5
  GCLK->PCHCTRL[TC5_GCLK_ID].reg = GCLK_PCHCTRL_CHEN |        // Enable perhipheral channel for TC5
                                   GCLK_PCHCTRL_GEN_GCLK1;    // Connect generic clock 0 at 48MHz
   
  TC5->COUNT16.WAVE.reg = TC_WAVE_WAVEGEN_MFRQ;               // Set TC5 to Match Frequency (MFRQ) mode
  TC5->COUNT16.CC[0].reg = 3000 - 1;                          // Set the trigger to 16 kHz: (4Mhz / 16000) - 1
  while (TC5->COUNT16.SYNCBUSY.bit.CC0);                      // Wait for synchronization

  // Start Timer/Counter 5
  TC5->COUNT16.CTRLA.bit.ENABLE = 1;                          // Enable the TC5 timer
  while (TC5->COUNT16.SYNCBUSY.bit.ENABLE);                   // Wait for synchronization
}

/*******************************************************************************
 * Main
 */

void setup() {

  // Configure pin to toggle on DMA interrupt
#if DEBUG
  pinMode(debug_pin, OUTPUT);
#endif

  // Configure serial port
  Serial.begin(115200);

  // Configure DMA to sample from ADC at 16kHz
  config_dma_adc();

  // TEST: record for 1 second, print results in CSV

  // Wait a second, start recording
  while(!Serial);
  delay(1000);
  Serial.println("Recording...");
  recording = 1;

  // Wait for recording to be done, print results
  while (recording);
  for (int i = 0; i < AUDIO_BUF_LEN; i++) {
    Serial.print(audio_buf[i]);
    if (i < AUDIO_BUF_LEN - 1) {
      Serial.print(", ");
    }
  }
  Serial.println();
}

void loop()
{ 
  delay(1000);
}