Simplified version of FFT code - drives on-board LED as a "Colour Organ".
Dependencies: FastAnalogIn NVIC_set_all_priorities mbed-dsp mbed
main.cpp
00001 // Audio Spectrum Display 00002 // Copyright 2013 Tony DiCola (tony@tonydicola.com) 00003 // Code ported from the guide at http://learn.adafruit.com/fft-fun-with-fourier-transforms?view=all 00004 // mods by Tony Abbey to simplify code to drive tri-colour LED as a "colour organ" 00005 00006 #include "mbed.h" 00007 #include "NVIC_set_all_priorities.h" 00008 #include <ctype.h> 00009 #include "arm_math.h" 00010 #include "arm_const_structs.h" 00011 #include "FastAnalogIn.h" 00012 00013 Serial pc(USBTX, USBRX); 00014 00015 FastAnalogIn Audio(PTC2); 00016 00017 //#define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED. 00018 00019 00020 #ifndef RGBW_ext 00021 // RGB direct output to PWM channels - on-board RGB LED 00022 PwmOut gled(LED_GREEN); 00023 PwmOut rled(LED_RED); 00024 PwmOut bled(LED_BLUE); 00025 #else 00026 // HSI to RGBW conversion with direct output to external PWM channels - RGBW LED 00027 // hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5); //Red, Green, Blue, White 00028 #endif 00029 00030 // Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !! 00031 // More info at https://mbed.org/questions/1387/How-can-I-access-the-FTFA_FOPT-register-/ 00032 extern "C" void NMI_Handler() { 00033 DigitalIn test(PTA4); 00034 } 00035 00036 00037 //////////////////////////////////////////////////////////////////////////////// 00038 // CONFIGURATION 00039 // These values can be changed to alter the behavior of the spectrum display. 00040 // KL25Z limitations 00041 // ----------------- 00042 // - When used with the Spectrogram python script : 00043 // There is a substantial time lag between the music and the screen output. 00044 // Max allowed SAMPLE_RATE_HZ is 40000 00045 // Max allowed FFT_SIZE is 64 00046 //////////////////////////////////////////////////////////////////////////////// 00047 00048 int SLOWDOWN = 4; // Create an optical delay in spectrumLoop - useful when only one RGB led is used. 00049 // Only active when nonzero. 00050 // A value >= 1000 and <= 1000 + PIXEL_COUNT fixes the output to a single frequency 00051 // window = a single color. 00052 int SAMPLE_RATE_HZ = 20000; // Sample rate of the audio in hertz. 00053 float SPECTRUM_MIN_DB = 20.0; // Audio intensity (in decibels) that maps to low LED brightness. 00054 float SPECTRUM_MAX_DB = 80.0; // Audio intensity (in decibels) that maps to high LED brightness. 00055 int LEDS_ENABLED = 1; // Control if the LED's should display the spectrum or not. 1 is true, 0 is false. 00056 // Useful for turning the LED display on and off with commands from the serial port. 00057 const int FFT_SIZE = 64; // Size of the FFT. 00058 const int PIXEL_COUNT = 3; // Number of pixels (RGB LED). You should be able to increase this without 00059 // any other changes to the program. 00060 const int MAX_CHARS = 65; // Max size of the input command buffer 00061 00062 //////////////////////////////////////////////////////////////////////////////// 00063 // INTERNAL STATE 00064 // These shouldn't be modified unless you know what you're doing. 00065 //////////////////////////////////////////////////////////////////////////////// 00066 const static arm_cfft_instance_f32 *S; 00067 Ticker samplingTimer; 00068 float samples[FFT_SIZE*2]; 00069 float magnitudes[FFT_SIZE]; 00070 int sampleCounter = 0; 00071 char commandBuffer[MAX_CHARS]; 00072 float frequencyWindow[PIXEL_COUNT+1]; 00073 float hues[PIXEL_COUNT]; 00074 bool commandRecv = 0; 00075 //////////////////////////////////////////////////////////////////////////////// 00076 // UTILITY FUNCTIONS 00077 //////////////////////////////////////////////////////////////////////////////// 00078 00079 void rxisr() { 00080 char c = pc.getc(); 00081 // Add any characters that aren't the end of a command (semicolon) to the input buffer. 00082 if (c != ';') { 00083 c = toupper(c); 00084 strncat(commandBuffer, &c, 1); 00085 } else { 00086 // Parse the command because an end of command token was encountered. 00087 commandRecv = 1; 00088 } 00089 } 00090 00091 // Compute the average magnitude of a target frequency window vs. all other frequencies. 00092 void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) 00093 { 00094 *windowMean = 0; 00095 *otherMean = 0; 00096 // Notice the first magnitude bin is skipped because it represents the 00097 // average power of the signal. 00098 for (int i = 1; i < FFT_SIZE/2; ++i) { 00099 if (i >= lowBin && i <= highBin) { 00100 *windowMean += magnitudes[i]; 00101 } else { 00102 *otherMean += magnitudes[i]; 00103 } 00104 } 00105 *windowMean /= (highBin - lowBin) + 1; 00106 *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin)); 00107 } 00108 00109 // Convert a frequency to the appropriate FFT bin it will fall within. 00110 int frequencyToBin(float frequency) 00111 { 00112 float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE); 00113 return int(frequency / binFrequency); 00114 } 00115 00116 00117 //////////////////////////////////////////////////////////////////////////////// 00118 // SPECTRUM DISPLAY FUNCTIONS 00119 /////////////////////////////////////////////////////////////////////////////// 00120 00121 void spectrumSetup() 00122 { 00123 // Set the frequency window values by evenly dividing the possible frequency 00124 // spectrum across the number of neo pixels. 00125 float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(PIXEL_COUNT); 00126 for (int i = 0; i < PIXEL_COUNT+1; ++i) { 00127 frequencyWindow[i] = i*windowSize; 00128 } 00129 00130 } 00131 00132 void spectrumLoop() 00133 { 00134 // Update each LED based on the intensity of the audio 00135 // in the associated frequency window. 00136 static int SLrpt = 0, SLpixcnt = 0; 00137 int SLpixend = 0; 00138 float intensity, otherMean; 00139 if(SLOWDOWN != 0) 00140 { 00141 if(SLOWDOWN >= 1000) 00142 { 00143 if(SLOWDOWN <= (1000 + PIXEL_COUNT-1)) 00144 { 00145 SLpixcnt = SLOWDOWN - 1000; 00146 SLrpt = 0; 00147 SLpixend = SLpixcnt + 1; 00148 } 00149 else 00150 SLOWDOWN = 0; 00151 } 00152 else 00153 { 00154 SLrpt++; 00155 if (SLrpt >= SLOWDOWN) 00156 { 00157 SLrpt = 0; 00158 SLpixcnt = SLpixcnt < PIXEL_COUNT-1 ? ++SLpixcnt : 0; 00159 } 00160 SLpixend = SLpixcnt + 1; 00161 } 00162 } 00163 else 00164 { 00165 SLpixcnt = 0; 00166 SLrpt = 0; 00167 SLpixend = PIXEL_COUNT; 00168 } 00169 for (int i = SLpixcnt; i < SLpixend; ++i) { 00170 windowMean(magnitudes, 00171 frequencyToBin(frequencyWindow[i]), 00172 frequencyToBin(frequencyWindow[i+1]), 00173 &intensity, 00174 &otherMean); 00175 // Convert intensity to decibels. 00176 intensity = 20.0*log10(intensity); 00177 // Scale the intensity and clamp between 0 and 1.0. 00178 intensity -= SPECTRUM_MIN_DB; 00179 intensity = intensity < 0.0 ? 0.0 : intensity; 00180 intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB); 00181 intensity = intensity > 1.0 ? 1.0 : intensity; 00182 hues[i]=intensity; 00183 } 00184 rled=1.0-hues[0] ; // onboard LED is common anode so inversion needed 00185 gled=1.0-hues[1]; 00186 bled=1.0-hues[2]; 00187 } 00188 00189 00190 //////////////////////////////////////////////////////////////////////////////// 00191 // SAMPLING FUNCTIONS 00192 //////////////////////////////////////////////////////////////////////////////// 00193 00194 void samplingCallback() 00195 { 00196 // Read from the ADC and store the sample data 00197 samples[sampleCounter] = (1023 * Audio) - 511.0f; 00198 // Complex FFT functions require a coefficient for the imaginary part of the input. 00199 // Since we only have real data, set this coefficient to zero. 00200 samples[sampleCounter+1] = 0.0; 00201 // Update sample buffer position and stop after the buffer is filled 00202 sampleCounter += 2; 00203 if (sampleCounter >= FFT_SIZE*2) { 00204 samplingTimer.detach(); 00205 } 00206 } 00207 00208 void samplingBegin() 00209 { 00210 // Reset sample buffer position and start callback at necessary rate. 00211 sampleCounter = 0; 00212 samplingTimer.attach_us(&samplingCallback, 1000000/SAMPLE_RATE_HZ); 00213 } 00214 00215 bool samplingIsDone() 00216 { 00217 return sampleCounter >= FFT_SIZE*2; 00218 } 00219 00220 00221 //////////////////////////////////////////////////////////////////////////////// 00222 // COMMAND PARSING FUNCTIONS 00223 // These functions allow parsing simple commands input on the serial port. 00224 // Commands allow reading and writing variables that control the device. 00225 // 00226 // All commands must end with a semicolon character. 00227 // 00228 // Example commands are: 00229 // GET SAMPLE_RATE_HZ; 00230 // - Get the sample rate of the device. 00231 // SET SAMPLE_RATE_HZ 400; 00232 // - Set the sample rate of the device to 400 hertz. 00233 // 00234 //////////////////////////////////////////////////////////////////////////////// 00235 00236 void parseCommand(char* command) 00237 { 00238 if (strcmp(command, "GET MAGNITUDES") == 0) { 00239 for (int i = 0; i < FFT_SIZE; ++i) { 00240 printf("%f\r\n", magnitudes[i]); 00241 } 00242 } else if (strcmp(command, "GET SAMPLES") == 0) { 00243 for (int i = 0; i < FFT_SIZE*2; i+=2) { 00244 printf("%f\r\n", samples[i]); 00245 } 00246 } else if (strcmp(command, "GET FFT_SIZE") == 0) { 00247 printf("%d\r\n", FFT_SIZE); 00248 } else if (strcmp(command, "GET SAMPLE_RATE_HZ") == 0) { 00249 printf("%d\r\n", SAMPLE_RATE_HZ); 00250 } else if (strstr(command, "SET SAMPLE_RATE_HZ") != NULL) { 00251 SAMPLE_RATE_HZ = (typeof(SAMPLE_RATE_HZ)) atof(command+(sizeof("SET SAMPLE_RATE_HZ")-1)); 00252 } else if (strcmp(command, "GET LEDS_ENABLED") == 0) { 00253 printf("%d\r\n", LEDS_ENABLED); 00254 } else if (strstr(command, "SET LEDS_ENABLED") != NULL) { 00255 LEDS_ENABLED = (typeof(LEDS_ENABLED)) atof(command+(sizeof("SET LEDS_ENABLED")-1)); 00256 } else if (strcmp(command, "GET SPECTRUM_MIN_DB") == 0) { 00257 printf("%f\r\n", SPECTRUM_MIN_DB); 00258 } else if (strstr(command, "SET SPECTRUM_MIN_DB") != NULL) { 00259 SPECTRUM_MIN_DB = (typeof(SPECTRUM_MIN_DB)) atof(command+(sizeof("SET SPECTRUM_MIN_DB")-1)); 00260 } else if (strcmp(command, "GET SPECTRUM_MAX_DB") == 0) { 00261 printf("%f\r\n", SPECTRUM_MAX_DB); 00262 } else if (strstr(command, "SET SPECTRUM_MAX_DB") != NULL) { 00263 SPECTRUM_MAX_DB = (typeof(SPECTRUM_MAX_DB)) atof(command+(sizeof("SET SPECTRUM_MAX_DB")-1)); 00264 } else if (strcmp(command, "GET SLOWDOWN") == 0) { 00265 printf("%d\r\n", SLOWDOWN); 00266 } else if (strstr(command, "SET SLOWDOWN") != NULL) { 00267 SLOWDOWN = (typeof(SLOWDOWN)) atoi(command+(sizeof("SET SLOWDOWN")-1)); 00268 } 00269 00270 // Update spectrum display values if sample rate was changed. 00271 if (strstr(command, "SET SAMPLE_RATE_HZ ") != NULL) { 00272 spectrumSetup(); 00273 } else if (strcmp(command, "GET HUES") == 0) { 00274 for (int i = 0; i < PIXEL_COUNT; ++i) { 00275 printf("%f\r\n", hues[i]); 00276 } 00277 } 00278 00279 00280 // Turn off the LEDs if the state changed. 00281 if (LEDS_ENABLED == 0) { 00282 } 00283 } 00284 00285 void parserLoop() 00286 { 00287 // Process any incoming characters from the serial port 00288 while (pc.readable()) { 00289 char c = pc.getc(); 00290 // (doesnt work!) printf("%c",c); // echo characters typed 00291 // Add any characters that aren't the end of a command (semicolon) to the input buffer. 00292 if (c != ';') { 00293 c = toupper(c); 00294 strncat(commandBuffer, &c, 1); 00295 } else { 00296 // Parse the command because an end of command token was encountered. 00297 parseCommand(commandBuffer); 00298 // Clear the input buffer 00299 memset(commandBuffer, 0, sizeof(commandBuffer)); 00300 } 00301 } 00302 } 00303 00304 //////////////////////////////////////////////////////////////////////////////// 00305 // MAIN FUNCTION 00306 //////////////////////////////////////////////////////////////////////////////// 00307 00308 int main() 00309 { 00310 NVIC_set_all_irq_priorities(1); 00311 NVIC_SetPriority(UART0_IRQn, 0); 00312 // Set up serial port. 00313 pc.baud (38400); 00314 pc.attach(&rxisr); 00315 00316 // Clear the input command buffer 00317 memset(commandBuffer, 0, sizeof(commandBuffer)); 00318 00319 // Initialize spectrum display 00320 spectrumSetup(); 00321 00322 // Begin sampling audio 00323 samplingBegin(); 00324 00325 // Init arm_ccft_32 00326 switch (FFT_SIZE) 00327 { 00328 case 16: 00329 S = & arm_cfft_sR_f32_len16; 00330 break; 00331 case 32: 00332 S = & arm_cfft_sR_f32_len32; 00333 break; 00334 case 64: 00335 S = & arm_cfft_sR_f32_len64; 00336 break; 00337 case 128: 00338 S = & arm_cfft_sR_f32_len128; 00339 break; 00340 case 256: 00341 S = & arm_cfft_sR_f32_len256; 00342 break; 00343 case 512: 00344 S = & arm_cfft_sR_f32_len512; 00345 break; 00346 case 1024: 00347 S = & arm_cfft_sR_f32_len1024; 00348 break; 00349 case 2048: 00350 S = & arm_cfft_sR_f32_len2048; 00351 break; 00352 case 4096: 00353 S = & arm_cfft_sR_f32_len4096; 00354 break; 00355 } 00356 00357 while(1) { 00358 // Calculate FFT if a full sample is available. 00359 if (samplingIsDone()) { 00360 // Run FFT on sample data. 00361 arm_cfft_f32(S, samples, 0, 1); 00362 // Calculate magnitude of complex numbers output by the FFT. 00363 arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE); 00364 00365 if (LEDS_ENABLED == 1) { 00366 spectrumLoop(); 00367 } 00368 00369 // Restart audio sampling. 00370 samplingBegin(); 00371 printf("this will make it work "); 00372 } 00373 00374 // Parse any pending commands. 00375 if(commandRecv) { 00376 // pc.attach(NULL); 00377 parseCommand(commandBuffer); 00378 commandRecv = 0; 00379 // Clear the input buffer 00380 memset(commandBuffer, 0, sizeof(commandBuffer)); 00381 // pc.attach(&rxisr); 00382 } 00383 } 00384 }
Generated on Wed Jul 13 2022 10:40:14 by 1.7.2