Simplified version of FFT code - drives on-board LED as a "Colour Organ".
Dependencies: FastAnalogIn NVIC_set_all_priorities mbed-dsp mbed
Revision 0:b8c9dffbbe7e, committed 2014-06-17
- Comitter:
- tony1tf
- Date:
- Tue Jun 17 14:03:36 2014 +0000
- Commit message:
- Simplified Tony Dicola Code - directly drives the on-board LED. Needs constant writing to serial port in order to work.
Changed in this revision
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/FastAnalogIn.lib Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/Sissors/code/FastAnalogIn/#a9b753c25073
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/NVIC_set_all_priorities.lib Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/frankvnk/code/NVIC_set_all_priorities/#01504ecd2025
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/arm_const_structs.h Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,86 @@ +/* ---------------------------------------------------------------------- +* Copyright (C) 2010-2013 ARM Limited. All rights reserved. +* +* $Date: 17. January 2013 +* $Revision: V1.4.1 +* +* Project: CMSIS DSP Library +* Title: arm_const_structs.h +* +* Description: This file has constant structs that are initialized for +* user convenience. For example, some can be given as +* arguments to the arm_cfft_f32() function. +* +* Target Processor: Cortex-M4/Cortex-M3 +* +* Redistribution and use in source and binary forms, with or without +* modification, are permitted provided that the following conditions +* are met: +* - Redistributions of source code must retain the above copyright +* notice, this list of conditions and the following disclaimer. +* - Redistributions in binary form must reproduce the above copyright +* notice, this list of conditions and the following disclaimer in +* the documentation and/or other materials provided with the +* distribution. +* - Neither the name of ARM LIMITED nor the names of its contributors +* may be used to endorse or promote products derived from this +* software without specific prior written permission. +* +* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS +* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE +* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, +* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN +* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +* POSSIBILITY OF SUCH DAMAGE. +* -------------------------------------------------------------------- */ + +#ifndef _ARM_CONST_STRUCTS_H +#define _ARM_CONST_STRUCTS_H + +#include "arm_math.h" +#include "arm_common_tables.h" + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len16 = { + 16, twiddleCoef_16, armBitRevIndexTable16, ARMBITREVINDEXTABLE__16_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len32 = { + 32, twiddleCoef_32, armBitRevIndexTable32, ARMBITREVINDEXTABLE__32_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len64 = { + 64, twiddleCoef_64, armBitRevIndexTable64, ARMBITREVINDEXTABLE__64_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len128 = { + 128, twiddleCoef_128, armBitRevIndexTable128, ARMBITREVINDEXTABLE_128_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len256 = { + 256, twiddleCoef_256, armBitRevIndexTable256, ARMBITREVINDEXTABLE_256_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len512 = { + 512, twiddleCoef_512, armBitRevIndexTable512, ARMBITREVINDEXTABLE_512_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len1024 = { + 1024, twiddleCoef_1024, armBitRevIndexTable1024, ARMBITREVINDEXTABLE1024_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len2048 = { + 2048, twiddleCoef_2048, armBitRevIndexTable2048, ARMBITREVINDEXTABLE2048_TABLE_LENGTH + }; + + const arm_cfft_instance_f32 arm_cfft_sR_f32_len4096 = { + 4096, twiddleCoef_4096, armBitRevIndexTable4096, ARMBITREVINDEXTABLE4096_TABLE_LENGTH + }; + +#endif +
--- /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); + } + } +}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed-dsp.lib Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,1 @@ +https://mbed.org/teams/mbed-official/code/mbed-dsp/#7a284390b0ce
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/mbed.bld Tue Jun 17 14:03:36 2014 +0000 @@ -0,0 +1,1 @@ +http://mbed.org/users/mbed_official/code/mbed/builds/8e73be2a2ac1 \ No newline at end of file