/* File/Sketch Name: AudioFrequencyDetector Version No.: v1.0 Created 12 December, 2019 Original Author: Clyde A. Lettsome, PhD, PE, MEM Description: This code/sketch makes displays the approximate frequency of the loudest sound detected by a sound detection module. For this project, the analog output from the sound module detector sends the analog audio signal detected to A0 of the Arduino Uno. The analog signal is sampled and quantized (digitized). A Fast Fourier Transform (FFT) is then performed on the digitized data. The FFT converts the digital data from the approximate discrete-time domain result. The maximum frequency of the approximate discrete-time domain result is then determined and displayed via the Arduino IDE Serial Monitor. Note: The arduinoFFT.h library needs to be added to the Arduino IDE before compiling and uploading this script/sketch to an Arduino. License: This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License (GPL) version 3, or any later version of your choice, as published by the Free Software Foundation. Notes: Copyright (c) 2019 by C. A. Lettsome Services, LLC For more information visit https://clydelettsome.com/blog/2019/12/18/my-weekend-project-audio-frequency-detector-using-an-arduino/ */ #include "arduinoFFT.h" //MAKE SURE TO INSTALL V1.5 of the arduinoFFT lib #define SAMPLES 512 //SAMPLES-pt FFT. Must be a base 2 number. Max 128 for Arduino Uno. #define SAMPLING_FREQUENCY 2048 //Ts = Based on Nyquist, must be 2 times the highest expected frequency. #define MIC 10 arduinoFFT FFT = arduinoFFT(); unsigned int samplingPeriod; unsigned long microSeconds; int threshold = 200; double vReal[SAMPLES]; //create vector of size SAMPLES to hold real values double vImag[SAMPLES]; //create vector of size SAMPLES to hold imaginary values void setup() { SerialUSB.begin(115200); //Baud rate for the Serial Monitor //ADC Config /* Enable the APB clock for the ADC. */ PM->APBCMASK.reg |= PM_APBCMASK_ADC; /* Enable GCLK1 for the ADC */ GCLK->CLKCTRL.reg = GCLK_CLKCTRL_CLKEN | GCLK_CLKCTRL_GEN_GCLK4 | GCLK_CLKCTRL_ID_ADC; /* Wait for bus synchronization. */ while (GCLK->STATUS.bit.SYNCBUSY) {}; uint32_t bias = (*((uint32_t *) ADC_FUSES_BIASCAL_ADDR) & ADC_FUSES_BIASCAL_Msk) >> ADC_FUSES_BIASCAL_Pos; uint32_t linearity = (*((uint32_t *) ADC_FUSES_LINEARITY_0_ADDR) & ADC_FUSES_LINEARITY_0_Msk) >> ADC_FUSES_LINEARITY_0_Pos; linearity |= ((*((uint32_t *) ADC_FUSES_LINEARITY_1_ADDR) & ADC_FUSES_LINEARITY_1_Msk) >> ADC_FUSES_LINEARITY_1_Pos) << 5; /* Wait for bus synchronization. */ while (ADC->STATUS.bit.SYNCBUSY) {}; /* Write the calibration data. */ ADC->CALIB.reg = ADC_CALIB_BIAS_CAL(bias) | ADC_CALIB_LINEARITY_CAL(linearity); /* Wait for bus synchronization. */ while (ADC->STATUS.bit.SYNCBUSY) {}; /* Use the internal VCC reference. This is 1/2 of what's on VCCA. since VCCA is typically 3.3v, this is 1.65v. */ ADC->REFCTRL.reg = ADC_REFCTRL_REFSEL_INTVCC1; /* Only capture one sample. The ADC can actually capture and average multiple samples for better accuracy, but there's no need to do that for this example. */ ADC->AVGCTRL.reg = ADC_AVGCTRL_SAMPLENUM_1; /* Set the clock prescaler to 512, which will run the ADC at 8 Mhz / 512 = 31.25 kHz. Set the resolution to 12bit. */ ADC->CTRLB.reg = ADC_CTRLB_PRESCALER_DIV16 | ADC_CTRLB_RESSEL_12BIT; /* Configure the input parameters. - GAIN_DIV2 means that the input voltage is halved. This is important because the voltage reference is 1/2 of VCCA. So if you want to measure 0-3.3v, you need to halve the input as well. - MUXNEG_GND means that the ADC should compare the input value to GND. - MUXPOST_PIN3 means that the ADC should read from AIN3, or PB09. This is A2 on the Feather M0 board. */ ADC->INPUTCTRL.reg = ADC_INPUTCTRL_GAIN_DIV2 | ADC_INPUTCTRL_MUXNEG_GND | ADC_INPUTCTRL_MUXPOS_PIN18; /* Set PB09 as an input pin. */ PORT->Group[1].DIRCLR.reg = PORT_PA10; /* Enable the peripheral multiplexer for PB09. */ PORT->Group[1].PINCFG[10].reg |= PORT_PINCFG_PMUXEN; /* Set PB09 to function B which is analog input. */ PORT->Group[1].PMUX[4].reg = PORT_PMUX_PMUXO_B; /* Wait for bus synchronization. */ while (ADC->STATUS.bit.SYNCBUSY) {}; /* Enable the ADC. */ ADC->CTRLA.bit.ENABLE = true; samplingPeriod = round(1000000*(1.0/SAMPLING_FREQUENCY)); //Period in microseconds } void loop() { /*Sample SAMPLES times*/ //Wait for bus synchronization. while (ADC->STATUS.bit.SYNCBUSY) {}; // Start the ADC using a software trigger. ADC->SWTRIG.bit.START = true; // Wait for the result ready flag to be set. while (ADC->INTFLAG.bit.RESRDY == 0); // Clear the flag. ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; uint32_t thresholdCheck = ADC->RESULT.reg; if (thresholdCheck > threshold) { for(int i=0; i< SAMPLES; i++) { //Wait for bus synchronization. while (ADC->STATUS.bit.SYNCBUSY) {}; // Start the ADC using a software trigger. ADC->SWTRIG.bit.START = true; // Wait for the result ready flag to be set. while (ADC->INTFLAG.bit.RESRDY == 0); // Clear the flag. ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; //Read the value. uint32_t result = ADC->RESULT.reg; microSeconds = micros(); //Returns the number of microseconds since the Arduino board began running the current script. vReal[i] = result; //Reads the value from analog pin 0 (A0), quantize it and save it as a real term. vImag[i] = 0; //Makes imaginary term 0 always //SerialUSB.println(result); //remaining wait time between samples if necessary while(micros() < (microSeconds + samplingPeriod)) { //do nothing } } //Wait for bus synchronization. while (ADC->STATUS.bit.SYNCBUSY) {}; // Start the ADC using a software trigger. ADC->SWTRIG.bit.START = true; // Wait for the result ready flag to be set. while (ADC->INTFLAG.bit.RESRDY == 0); // Clear the flag. ADC->INTFLAG.reg = ADC_INTFLAG_RESRDY; /*Perform FFT on samples*/ FFT.Windowing(vReal, SAMPLES, FFT_WIN_TYP_HAMMING, FFT_FORWARD); FFT.Compute(vReal, vImag, SAMPLES, FFT_FORWARD); FFT.ComplexToMagnitude(vReal, vImag, SAMPLES); /*Find peak frequency and print peak*/ double peak = FFT.MajorPeak(vReal, SAMPLES, SAMPLING_FREQUENCY); SerialUSB.println(peak); //Print out the most dominant frequency. //delay(2000); /*Script stops here. Hardware reset required.*/ //while (1); //do one time } }