Simplified version of FFT code - drives on-board LED as a "Colour Organ".
Dependencies: FastAnalogIn NVIC_set_all_priorities mbed-dsp mbed
Fork of KL25Z_FFT_Demo_tony by
Diff: main.cpp
- Revision:
- 0:b8c9dffbbe7e
- Child:
- 1:7421267b0777
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/main.cpp Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,384 @@ +// Audio Spectrum Display +// Copyright 2013 Tony DiCola (tony@tonydicola.com) +// Code ported from the guide at http://learn.adafruit.com/fft-fun-with-fourier-transforms?view=all +// mods by Tony Abbey to simplify code to drive tri-colour LED as a "colour organ" + +#include "mbed.h" +#include "NVIC_set_all_priorities.h" +#include <ctype.h> +#include "arm_math.h" +#include "arm_const_structs.h" +#include "FastAnalogIn.h" + +Serial pc(USBTX, USBRX); + +FastAnalogIn Audio(PTC2); + +//#define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED. + + +#ifndef RGBW_ext +// RGB direct output to PWM channels - on-board RGB LED + PwmOut gled(LED_GREEN); + PwmOut rled(LED_RED); + PwmOut bled(LED_BLUE); +#else +// HSI to RGBW conversion with direct output to external PWM channels - RGBW LED +// hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5); //Red, Green, Blue, White +#endif + +// Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !! +// More info at https://mbed.org/questions/1387/How-can-I-access-the-FTFA_FOPT-register-/ +extern "C" void NMI_Handler() { + DigitalIn test(PTA4); +} + + +//////////////////////////////////////////////////////////////////////////////// +// CONFIGURATION +// These values can be changed to alter the behavior of the spectrum display. +// KL25Z limitations +// ----------------- +// - When used with the Spectrogram python script : +// There is a substantial time lag between the music and the screen output. +// Max allowed SAMPLE_RATE_HZ is 40000 +// Max allowed FFT_SIZE is 64 +//////////////////////////////////////////////////////////////////////////////// + +int SLOWDOWN = 4; // Create an optical delay in spectrumLoop - useful when only one RGB led is used. + // Only active when nonzero. + // A value >= 1000 and <= 1000 + PIXEL_COUNT fixes the output to a single frequency + // window = a single color. +int SAMPLE_RATE_HZ = 20000; // Sample rate of the audio in hertz. +float SPECTRUM_MIN_DB = 20.0; // Audio intensity (in decibels) that maps to low LED brightness. +float SPECTRUM_MAX_DB = 80.0; // Audio intensity (in decibels) that maps to high LED brightness. +int LEDS_ENABLED = 1; // Control if the LED's should display the spectrum or not. 1 is true, 0 is false. + // Useful for turning the LED display on and off with commands from the serial port. +const int FFT_SIZE = 64; // Size of the FFT. +const int PIXEL_COUNT = 3; // Number of pixels (RGB LED). You should be able to increase this without + // any other changes to the program. +const int MAX_CHARS = 65; // Max size of the input command buffer + +//////////////////////////////////////////////////////////////////////////////// +// INTERNAL STATE +// These shouldn't be modified unless you know what you're doing. +//////////////////////////////////////////////////////////////////////////////// +const static arm_cfft_instance_f32 *S; +Ticker samplingTimer; +float samples[FFT_SIZE*2]; +float magnitudes[FFT_SIZE]; +int sampleCounter = 0; +char commandBuffer[MAX_CHARS]; +float frequencyWindow[PIXEL_COUNT+1]; +float hues[PIXEL_COUNT]; +bool commandRecv = 0; +//////////////////////////////////////////////////////////////////////////////// +// UTILITY FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +void rxisr() { + char c = pc.getc(); + // Add any characters that aren't the end of a command (semicolon) to the input buffer. + if (c != ';') { + c = toupper(c); + strncat(commandBuffer, &c, 1); + } else { + // Parse the command because an end of command token was encountered. + commandRecv = 1; + } +} + +// Compute the average magnitude of a target frequency window vs. all other frequencies. +void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) +{ + *windowMean = 0; + *otherMean = 0; + // Notice the first magnitude bin is skipped because it represents the + // average power of the signal. + for (int i = 1; i < FFT_SIZE/2; ++i) { + if (i >= lowBin && i <= highBin) { + *windowMean += magnitudes[i]; + } else { + *otherMean += magnitudes[i]; + } + } + *windowMean /= (highBin - lowBin) + 1; + *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin)); +} + +// Convert a frequency to the appropriate FFT bin it will fall within. +int frequencyToBin(float frequency) +{ + float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE); + return int(frequency / binFrequency); +} + + +//////////////////////////////////////////////////////////////////////////////// +// SPECTRUM DISPLAY FUNCTIONS +/////////////////////////////////////////////////////////////////////////////// + +void spectrumSetup() +{ + // Set the frequency window values by evenly dividing the possible frequency + // spectrum across the number of neo pixels. + float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(PIXEL_COUNT); + for (int i = 0; i < PIXEL_COUNT+1; ++i) { + frequencyWindow[i] = i*windowSize; + } + +} + +void spectrumLoop() +{ + // Update each LED based on the intensity of the audio + // in the associated frequency window. + static int SLrpt = 0, SLpixcnt = 0; + int SLpixend = 0; + float intensity, otherMean; + if(SLOWDOWN != 0) + { + if(SLOWDOWN >= 1000) + { + if(SLOWDOWN <= (1000 + PIXEL_COUNT-1)) + { + SLpixcnt = SLOWDOWN - 1000; + SLrpt = 0; + SLpixend = SLpixcnt + 1; + } + else + SLOWDOWN = 0; + } + else + { + SLrpt++; + if (SLrpt >= SLOWDOWN) + { + SLrpt = 0; + SLpixcnt = SLpixcnt < PIXEL_COUNT-1 ? ++SLpixcnt : 0; + } + SLpixend = SLpixcnt + 1; + } + } + else + { + SLpixcnt = 0; + SLrpt = 0; + SLpixend = PIXEL_COUNT; + } + for (int i = SLpixcnt; i < SLpixend; ++i) { + windowMean(magnitudes, + frequencyToBin(frequencyWindow[i]), + frequencyToBin(frequencyWindow[i+1]), + &intensity, + &otherMean); + // Convert intensity to decibels. + intensity = 20.0*log10(intensity); + // Scale the intensity and clamp between 0 and 1.0. + intensity -= SPECTRUM_MIN_DB; + intensity = intensity < 0.0 ? 0.0 : intensity; + intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB); + intensity = intensity > 1.0 ? 1.0 : intensity; + hues[i]=intensity; + } + rled=1.0-hues[0] ; // onboard LED is common anode so inversion needed + gled=1.0-hues[1]; + bled=1.0-hues[2]; +} + + +//////////////////////////////////////////////////////////////////////////////// +// SAMPLING FUNCTIONS +//////////////////////////////////////////////////////////////////////////////// + +void samplingCallback() +{ + // Read from the ADC and store the sample data + samples[sampleCounter] = (1023 * Audio) - 511.0f; + // Complex FFT functions require a coefficient for the imaginary part of the input. + // Since we only have real data, set this coefficient to zero. + samples[sampleCounter+1] = 0.0; + // Update sample buffer position and stop after the buffer is filled + sampleCounter += 2; + if (sampleCounter >= FFT_SIZE*2) { + samplingTimer.detach(); + } +} + +void samplingBegin() +{ + // Reset sample buffer position and start callback at necessary rate. + sampleCounter = 0; + samplingTimer.attach_us(&samplingCallback, 1000000/SAMPLE_RATE_HZ); +} + +bool samplingIsDone() +{ + return sampleCounter >= FFT_SIZE*2; +} + + +//////////////////////////////////////////////////////////////////////////////// +// COMMAND PARSING FUNCTIONS +// These functions allow parsing simple commands input on the serial port. +// Commands allow reading and writing variables that control the device. +// +// All commands must end with a semicolon character. +// +// Example commands are: +// GET SAMPLE_RATE_HZ; +// - Get the sample rate of the device. +// SET SAMPLE_RATE_HZ 400; +// - Set the sample rate of the device to 400 hertz. +// +//////////////////////////////////////////////////////////////////////////////// + +void parseCommand(char* command) +{ + if (strcmp(command, "GET MAGNITUDES") == 0) { + for (int i = 0; i < FFT_SIZE; ++i) { + printf("%f\r\n", magnitudes[i]); + } + } else if (strcmp(command, "GET SAMPLES") == 0) { + for (int i = 0; i < FFT_SIZE*2; i+=2) { + printf("%f\r\n", samples[i]); + } + } else if (strcmp(command, "GET FFT_SIZE") == 0) { + printf("%d\r\n", FFT_SIZE); + } else if (strcmp(command, "GET SAMPLE_RATE_HZ") == 0) { + printf("%d\r\n", SAMPLE_RATE_HZ); + } else if (strstr(command, "SET SAMPLE_RATE_HZ") != NULL) { + SAMPLE_RATE_HZ = (typeof(SAMPLE_RATE_HZ)) atof(command+(sizeof("SET SAMPLE_RATE_HZ")-1)); + } else if (strcmp(command, "GET LEDS_ENABLED") == 0) { + printf("%d\r\n", LEDS_ENABLED); + } else if (strstr(command, "SET LEDS_ENABLED") != NULL) { + LEDS_ENABLED = (typeof(LEDS_ENABLED)) atof(command+(sizeof("SET LEDS_ENABLED")-1)); + } else if (strcmp(command, "GET SPECTRUM_MIN_DB") == 0) { + printf("%f\r\n", SPECTRUM_MIN_DB); + } else if (strstr(command, "SET SPECTRUM_MIN_DB") != NULL) { + SPECTRUM_MIN_DB = (typeof(SPECTRUM_MIN_DB)) atof(command+(sizeof("SET SPECTRUM_MIN_DB")-1)); + } else if (strcmp(command, "GET SPECTRUM_MAX_DB") == 0) { + printf("%f\r\n", SPECTRUM_MAX_DB); + } else if (strstr(command, "SET SPECTRUM_MAX_DB") != NULL) { + SPECTRUM_MAX_DB = (typeof(SPECTRUM_MAX_DB)) atof(command+(sizeof("SET SPECTRUM_MAX_DB")-1)); + } else if (strcmp(command, "GET SLOWDOWN") == 0) { + printf("%d\r\n", SLOWDOWN); + } else if (strstr(command, "SET SLOWDOWN") != NULL) { + SLOWDOWN = (typeof(SLOWDOWN)) atoi(command+(sizeof("SET SLOWDOWN")-1)); + } + + // Update spectrum display values if sample rate was changed. + if (strstr(command, "SET SAMPLE_RATE_HZ ") != NULL) { + spectrumSetup(); + } else if (strcmp(command, "GET HUES") == 0) { + for (int i = 0; i < PIXEL_COUNT; ++i) { + printf("%f\r\n", hues[i]); + } + } + + + // Turn off the LEDs if the state changed. + if (LEDS_ENABLED == 0) { + } +} + +void parserLoop() +{ + // Process any incoming characters from the serial port + while (pc.readable()) { + char c = pc.getc(); + // (doesnt work!) printf("%c",c); // echo characters typed + // Add any characters that aren't the end of a command (semicolon) to the input buffer. + if (c != ';') { + c = toupper(c); + strncat(commandBuffer, &c, 1); + } else { + // Parse the command because an end of command token was encountered. + parseCommand(commandBuffer); + // Clear the input buffer + memset(commandBuffer, 0, sizeof(commandBuffer)); + } + } +} + +//////////////////////////////////////////////////////////////////////////////// +// MAIN FUNCTION +//////////////////////////////////////////////////////////////////////////////// + +int main() +{ + NVIC_set_all_irq_priorities(1); + NVIC_SetPriority(UART0_IRQn, 0); + // Set up serial port. + pc.baud (38400); + pc.attach(&rxisr); + + // Clear the input command buffer + memset(commandBuffer, 0, sizeof(commandBuffer)); + + // Initialize spectrum display + spectrumSetup(); + + // Begin sampling audio + samplingBegin(); + + // Init arm_ccft_32 + switch (FFT_SIZE) + { + case 16: + S = & arm_cfft_sR_f32_len16; + break; + case 32: + S = & arm_cfft_sR_f32_len32; + break; + case 64: + S = & arm_cfft_sR_f32_len64; + break; + case 128: + S = & arm_cfft_sR_f32_len128; + break; + case 256: + S = & arm_cfft_sR_f32_len256; + break; + case 512: + S = & arm_cfft_sR_f32_len512; + break; + case 1024: + S = & arm_cfft_sR_f32_len1024; + break; + case 2048: + S = & arm_cfft_sR_f32_len2048; + break; + case 4096: + S = & arm_cfft_sR_f32_len4096; + break; + } + + while(1) { + // Calculate FFT if a full sample is available. + if (samplingIsDone()) { + // Run FFT on sample data. + arm_cfft_f32(S, samples, 0, 1); + // Calculate magnitude of complex numbers output by the FFT. + arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE); + + if (LEDS_ENABLED == 1) { + spectrumLoop(); + } + + // Restart audio sampling. + samplingBegin(); + printf("this will make it work "); + } + + // Parse any pending commands. + if(commandRecv) { +// pc.attach(NULL); + parseCommand(commandBuffer); + commandRecv = 0; + // Clear the input buffer + memset(commandBuffer, 0, sizeof(commandBuffer)); +// pc.attach(&rxisr); + } + } +}