Audio Spectrum analyser - FFT using mbed-dsp - driving RGB(W) LED or PC python script.
Dependencies: HSI2RGBW_PWM NVIC_set_all_priorities mbed-dsp mbed FastAnalogIn
Spectrum Analyzer
Code ported from Tony DiCola at AdaFruit FFT: Fun with Fourier Transforms.
Modifications were made to allow the use of the KL25Z on-board RGB LED or an external RGBW power LED.
See items marked with * in the next sections.
Main features
- Uses mbed-dsp library.
- Uses FastAnalogIn to allow a sample rate of 40kHz.
- Display the audio spectrum on a single RGB(W) LED*.
- Display the audio spectrum on your computer using an audio spectrogram tool (python script).
- Change parameters using a terminal connection : Sample rate, min/max db, slowdown*.
Information
Detailed information and download of the python scripts is available here.
KL25Z wiring
Audio inputs
The software samples a single audio channel at 40kHz and applies a Fourier transform to return the frequency spectrum.
Analog input : PTC2
.
A DC offset, and possibly some amplification or attenuation, is needed before the signal is fed into the analog inputs.
The opamp choice is not critical, just make sure it supports single supply operation.
Schematic
Currently, only one channel is used when the KL25Z is sampling at 40kHz.
External PWM outputs
Pin | Color |
PTD4 | Red |
PTA12 | Green |
PTA4 | Blue |
PTA5 | White |
If you want to use a RGB LED, remove the last pin declaration (PTA5).
The conversion routine automatically switches from HSI/RGBW to HSI/RGB.
To use the external RGBW LED, enable following line in the code:
#define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED.
Commands
Parameters can be altered through the serial port Using a terminal program (eg : TeraTerm).
Communication settings : 38400 baud + local echo
.
Each command needs to be terminated with a semicolon.
Use GET <command>;
to read a parameter.
Use SET <command> <value>;
to set a parameter.
Command | Description |
GET MAGNITUDES; | Reads back the FFT magnitudes. Number of magnitudes = PIXEL_COUNT. |
GET SAMPLES; | Reads back the current samples. Number of samples = PIXEL_COUNT. |
GET FFT_SIZE; | The size of the FFT. |
GET SAMPLE_RATE_HZ; | Audio sample rate (Hz). |
SET SAMPLE_RATE_HZ <value>; | Change the audio sample rate (see also 'Limitations' below). |
GET LEDS_ENABLED; | LEDs enabled status. |
SET LEDS_ENABLED <value>; | Control if the LED's should display the spectrum or not. 1 is true, 0 is false. |
GET SPECTRUM_MIN_DB; | Audio intensity (in decibels) that maps to low LED brightness. |
SET SPECTRUM_MIN_DB <value>; | Change low sensitivity (0...100dB). |
GET SPECTRUM_MAX_DB; | Audio intensity (in decibels) that maps to high LED brightness. |
SET SPECTRUM_MAX_DB <value>; | Change high sensitivity (0...100dB). |
GET SLOWDOWN; | LED visualisation delay. |
SET SLOWDOWN <value>; | * Useful to visualize the spectrum using a single RGB(W) LED. Without this command, the color values are shown too fast for the human eye. This allows you to slow down the visualization without interfering in the FFT conversion. Each frequency window is shown a little longer. The number of frequency windows depends on the value of the PIXEL_COUNT variable.[0...999] The larger the value, the longer each frequency window is shown - a good value is 4 when PIXEL_COUNT = 32 (choose a higher SLOWDOWN value when PIXEL_COUNT is lowered). [1000...1000 + PIXEL_COUNT] Selecting a value within this range allows us to lock to a specific frequency window. |
NOTE : PIXEL_COUNT is declared at compile time and determines the number of frequency windows (aka LED colors).
Limitations
The original code was written for a cortex-M4 processor.
For a cortex-M0 processor, following limitations apply:
SAMPLE_RATE_HZ | FFT_SIZE |
1...40000 | max 64 |
Demo Videos
Parameter settings
SLOWDOWN | 16 (initial value - changed to 1000+ during the video to demonstrate the lock option). |
SAMPLE_RATE_HZ | 40000 |
SPECTRUM_MIN_DB | 40.0 |
SPECTRUM_MAX_DB | 80.0 |
FFT_SIZE | 64 |
PIXEL_COUNT | 32 |
Using the on-board RGB LED
SLOWDOWN is set to different values (normal mode and locked mode).
Using an external 10W RGBW LED
SLOWDOWN is set to different values (normal mode and locked mode).
Spectrogram on Computer screen (serial input from KL25Z board
main.cpp@2:035d551759a5, 2014-03-08 (annotated)
- Committer:
- frankvnk
- Date:
- Sat Mar 08 19:30:20 2014 +0000
- Revision:
- 2:035d551759a5
- Parent:
- 1:736b34e0f484
Replaced deprecated FFT calls with new ones.
Who changed what in which revision?
User | Revision | Line number | New contents of line |
---|---|---|---|
frankvnk | 0:0c037aff5039 | 1 | // Audio Spectrum Display |
frankvnk | 0:0c037aff5039 | 2 | // Copyright 2013 Tony DiCola (tony@tonydicola.com) |
frankvnk | 0:0c037aff5039 | 3 | // Code ported from the guide at http://learn.adafruit.com/fft-fun-with-fourier-transforms?view=all |
frankvnk | 0:0c037aff5039 | 4 | |
frankvnk | 0:0c037aff5039 | 5 | #include "mbed.h" |
frankvnk | 0:0c037aff5039 | 6 | #include "NVIC_set_all_priorities.h" |
frankvnk | 0:0c037aff5039 | 7 | #include <ctype.h> |
frankvnk | 0:0c037aff5039 | 8 | #include "arm_math.h" |
frankvnk | 2:035d551759a5 | 9 | #include "arm_const_structs.h" |
frankvnk | 0:0c037aff5039 | 10 | #include "hsi2rgbw_pwm.h" |
frankvnk | 1:736b34e0f484 | 11 | #include "FastAnalogIn.h" |
frankvnk | 0:0c037aff5039 | 12 | |
frankvnk | 0:0c037aff5039 | 13 | Serial pc(USBTX, USBRX); |
frankvnk | 0:0c037aff5039 | 14 | |
frankvnk | 1:736b34e0f484 | 15 | FastAnalogIn Audio(PTC2); |
frankvnk | 0:0c037aff5039 | 16 | |
frankvnk | 0:0c037aff5039 | 17 | //#define RGBW_ext // Disable this line when you want to use the KL25Z on-board RGB LED. |
frankvnk | 0:0c037aff5039 | 18 | |
frankvnk | 0:0c037aff5039 | 19 | #ifndef RGBW_ext |
frankvnk | 0:0c037aff5039 | 20 | // HSI to RGB conversion with direct output to PWM channels - on-board RGB LED |
frankvnk | 0:0c037aff5039 | 21 | hsi2rgbw_pwm led(LED_RED, LED_GREEN, LED_BLUE); |
frankvnk | 0:0c037aff5039 | 22 | #else |
frankvnk | 0:0c037aff5039 | 23 | // HSI to RGBW conversion with direct output to external PWM channels - RGBW LED |
frankvnk | 0:0c037aff5039 | 24 | hsi2rgbw_pwm led(PTD4, PTA12, PTA4, PTA5); //Red, Green, Blue, White |
frankvnk | 0:0c037aff5039 | 25 | #endif |
frankvnk | 0:0c037aff5039 | 26 | |
frankvnk | 0:0c037aff5039 | 27 | // Dummy ISR for disabling NMI on PTA4 - !! DO NOT REMOVE THIS !! |
frankvnk | 0:0c037aff5039 | 28 | // More info at https://mbed.org/questions/1387/How-can-I-access-the-FTFA_FOPT-register-/ |
frankvnk | 0:0c037aff5039 | 29 | extern "C" void NMI_Handler() { |
frankvnk | 0:0c037aff5039 | 30 | DigitalIn test(PTA4); |
frankvnk | 0:0c037aff5039 | 31 | } |
frankvnk | 0:0c037aff5039 | 32 | |
frankvnk | 0:0c037aff5039 | 33 | |
frankvnk | 0:0c037aff5039 | 34 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 35 | // CONFIGURATION |
frankvnk | 0:0c037aff5039 | 36 | // These values can be changed to alter the behavior of the spectrum display. |
frankvnk | 0:0c037aff5039 | 37 | // KL25Z limitations |
frankvnk | 0:0c037aff5039 | 38 | // ----------------- |
frankvnk | 0:0c037aff5039 | 39 | // - When used with the Spectrogram python script : |
frankvnk | 0:0c037aff5039 | 40 | // There is a substantial time lag between the music and the screen output. |
frankvnk | 2:035d551759a5 | 41 | // Max allowed SAMPLE_RATE_HZ is 40000 |
frankvnk | 2:035d551759a5 | 42 | // Max allowed FFT_SIZE is 64 |
frankvnk | 0:0c037aff5039 | 43 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 44 | |
frankvnk | 2:035d551759a5 | 45 | int SLOWDOWN = 4; // Create an optical delay in spectrumLoop - useful when only one RGB led is used. |
frankvnk | 0:0c037aff5039 | 46 | // Only active when nonzero. |
frankvnk | 0:0c037aff5039 | 47 | // A value >= 1000 and <= 1000 + PIXEL_COUNT fixes the output to a single frequency |
frankvnk | 0:0c037aff5039 | 48 | // window = a single color. |
frankvnk | 0:0c037aff5039 | 49 | int SAMPLE_RATE_HZ = 40000; // Sample rate of the audio in hertz. |
frankvnk | 2:035d551759a5 | 50 | float SPECTRUM_MIN_DB = 30.0; // Audio intensity (in decibels) that maps to low LED brightness. |
frankvnk | 1:736b34e0f484 | 51 | float SPECTRUM_MAX_DB = 80.0; // Audio intensity (in decibels) that maps to high LED brightness. |
frankvnk | 0:0c037aff5039 | 52 | int LEDS_ENABLED = 1; // Control if the LED's should display the spectrum or not. 1 is true, 0 is false. |
frankvnk | 0:0c037aff5039 | 53 | // Useful for turning the LED display on and off with commands from the serial port. |
frankvnk | 1:736b34e0f484 | 54 | const int FFT_SIZE = 64; // Size of the FFT. |
frankvnk | 1:736b34e0f484 | 55 | const int PIXEL_COUNT = 32; // Number of pixels. You should be able to increase this without |
frankvnk | 0:0c037aff5039 | 56 | // any other changes to the program. |
frankvnk | 0:0c037aff5039 | 57 | const int MAX_CHARS = 65; // Max size of the input command buffer |
frankvnk | 0:0c037aff5039 | 58 | |
frankvnk | 0:0c037aff5039 | 59 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 60 | // INTERNAL STATE |
frankvnk | 0:0c037aff5039 | 61 | // These shouldn't be modified unless you know what you're doing. |
frankvnk | 0:0c037aff5039 | 62 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 2:035d551759a5 | 63 | const static arm_cfft_instance_f32 *S; |
frankvnk | 0:0c037aff5039 | 64 | Ticker samplingTimer; |
frankvnk | 0:0c037aff5039 | 65 | float samples[FFT_SIZE*2]; |
frankvnk | 0:0c037aff5039 | 66 | float magnitudes[FFT_SIZE]; |
frankvnk | 0:0c037aff5039 | 67 | int sampleCounter = 0; |
frankvnk | 0:0c037aff5039 | 68 | char commandBuffer[MAX_CHARS]; |
frankvnk | 0:0c037aff5039 | 69 | float frequencyWindow[PIXEL_COUNT+1]; |
frankvnk | 0:0c037aff5039 | 70 | float hues[PIXEL_COUNT]; |
frankvnk | 0:0c037aff5039 | 71 | bool commandRecv = 0; |
frankvnk | 0:0c037aff5039 | 72 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 73 | // UTILITY FUNCTIONS |
frankvnk | 0:0c037aff5039 | 74 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 75 | |
frankvnk | 0:0c037aff5039 | 76 | void rxisr() { |
frankvnk | 0:0c037aff5039 | 77 | char c = pc.getc(); |
frankvnk | 0:0c037aff5039 | 78 | // Add any characters that aren't the end of a command (semicolon) to the input buffer. |
frankvnk | 0:0c037aff5039 | 79 | if (c != ';') { |
frankvnk | 0:0c037aff5039 | 80 | c = toupper(c); |
frankvnk | 0:0c037aff5039 | 81 | strncat(commandBuffer, &c, 1); |
frankvnk | 0:0c037aff5039 | 82 | } else { |
frankvnk | 0:0c037aff5039 | 83 | // Parse the command because an end of command token was encountered. |
frankvnk | 0:0c037aff5039 | 84 | commandRecv = 1; |
frankvnk | 0:0c037aff5039 | 85 | } |
frankvnk | 0:0c037aff5039 | 86 | } |
frankvnk | 0:0c037aff5039 | 87 | |
frankvnk | 0:0c037aff5039 | 88 | // Compute the average magnitude of a target frequency window vs. all other frequencies. |
frankvnk | 0:0c037aff5039 | 89 | void windowMean(float* magnitudes, int lowBin, int highBin, float* windowMean, float* otherMean) |
frankvnk | 0:0c037aff5039 | 90 | { |
frankvnk | 0:0c037aff5039 | 91 | *windowMean = 0; |
frankvnk | 0:0c037aff5039 | 92 | *otherMean = 0; |
frankvnk | 0:0c037aff5039 | 93 | // Notice the first magnitude bin is skipped because it represents the |
frankvnk | 0:0c037aff5039 | 94 | // average power of the signal. |
frankvnk | 0:0c037aff5039 | 95 | for (int i = 1; i < FFT_SIZE/2; ++i) { |
frankvnk | 0:0c037aff5039 | 96 | if (i >= lowBin && i <= highBin) { |
frankvnk | 0:0c037aff5039 | 97 | *windowMean += magnitudes[i]; |
frankvnk | 0:0c037aff5039 | 98 | } else { |
frankvnk | 0:0c037aff5039 | 99 | *otherMean += magnitudes[i]; |
frankvnk | 0:0c037aff5039 | 100 | } |
frankvnk | 0:0c037aff5039 | 101 | } |
frankvnk | 0:0c037aff5039 | 102 | *windowMean /= (highBin - lowBin) + 1; |
frankvnk | 0:0c037aff5039 | 103 | *otherMean /= (FFT_SIZE / 2 - (highBin - lowBin)); |
frankvnk | 0:0c037aff5039 | 104 | } |
frankvnk | 0:0c037aff5039 | 105 | |
frankvnk | 0:0c037aff5039 | 106 | // Convert a frequency to the appropriate FFT bin it will fall within. |
frankvnk | 0:0c037aff5039 | 107 | int frequencyToBin(float frequency) |
frankvnk | 0:0c037aff5039 | 108 | { |
frankvnk | 0:0c037aff5039 | 109 | float binFrequency = float(SAMPLE_RATE_HZ) / float(FFT_SIZE); |
frankvnk | 0:0c037aff5039 | 110 | return int(frequency / binFrequency); |
frankvnk | 0:0c037aff5039 | 111 | } |
frankvnk | 0:0c037aff5039 | 112 | |
frankvnk | 0:0c037aff5039 | 113 | |
frankvnk | 0:0c037aff5039 | 114 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 115 | // SPECTRUM DISPLAY FUNCTIONS |
frankvnk | 0:0c037aff5039 | 116 | /////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 117 | |
frankvnk | 0:0c037aff5039 | 118 | void spectrumSetup() |
frankvnk | 0:0c037aff5039 | 119 | { |
frankvnk | 0:0c037aff5039 | 120 | // Set the frequency window values by evenly dividing the possible frequency |
frankvnk | 0:0c037aff5039 | 121 | // spectrum across the number of neo pixels. |
frankvnk | 0:0c037aff5039 | 122 | float windowSize = (SAMPLE_RATE_HZ / 2.0) / float(PIXEL_COUNT); |
frankvnk | 0:0c037aff5039 | 123 | for (int i = 0; i < PIXEL_COUNT+1; ++i) { |
frankvnk | 0:0c037aff5039 | 124 | frequencyWindow[i] = i*windowSize; |
frankvnk | 0:0c037aff5039 | 125 | } |
frankvnk | 0:0c037aff5039 | 126 | // Evenly spread hues across all pixels. |
frankvnk | 0:0c037aff5039 | 127 | for (int i = 0; i < PIXEL_COUNT; ++i) { |
frankvnk | 0:0c037aff5039 | 128 | hues[i] = 360.0*(float(i)/float(PIXEL_COUNT-1)); |
frankvnk | 0:0c037aff5039 | 129 | } |
frankvnk | 0:0c037aff5039 | 130 | } |
frankvnk | 0:0c037aff5039 | 131 | |
frankvnk | 0:0c037aff5039 | 132 | void spectrumLoop() |
frankvnk | 0:0c037aff5039 | 133 | { |
frankvnk | 0:0c037aff5039 | 134 | // Update each LED based on the intensity of the audio |
frankvnk | 0:0c037aff5039 | 135 | // in the associated frequency window. |
frankvnk | 0:0c037aff5039 | 136 | static int SLrpt = 0, SLpixcnt = 0; |
frankvnk | 0:0c037aff5039 | 137 | int SLpixend = 0; |
frankvnk | 0:0c037aff5039 | 138 | float intensity, otherMean; |
frankvnk | 0:0c037aff5039 | 139 | if(SLOWDOWN != 0) |
frankvnk | 0:0c037aff5039 | 140 | { |
frankvnk | 0:0c037aff5039 | 141 | if(SLOWDOWN >= 1000) |
frankvnk | 0:0c037aff5039 | 142 | { |
frankvnk | 0:0c037aff5039 | 143 | if(SLOWDOWN <= (1000 + PIXEL_COUNT-1)) |
frankvnk | 0:0c037aff5039 | 144 | { |
frankvnk | 0:0c037aff5039 | 145 | SLpixcnt = SLOWDOWN - 1000; |
frankvnk | 0:0c037aff5039 | 146 | SLrpt = 0; |
frankvnk | 0:0c037aff5039 | 147 | SLpixend = SLpixcnt + 1; |
frankvnk | 0:0c037aff5039 | 148 | } |
frankvnk | 0:0c037aff5039 | 149 | else |
frankvnk | 0:0c037aff5039 | 150 | SLOWDOWN = 0; |
frankvnk | 0:0c037aff5039 | 151 | } |
frankvnk | 0:0c037aff5039 | 152 | else |
frankvnk | 0:0c037aff5039 | 153 | { |
frankvnk | 0:0c037aff5039 | 154 | SLrpt++; |
frankvnk | 0:0c037aff5039 | 155 | if (SLrpt >= SLOWDOWN) |
frankvnk | 0:0c037aff5039 | 156 | { |
frankvnk | 0:0c037aff5039 | 157 | SLrpt = 0; |
frankvnk | 0:0c037aff5039 | 158 | SLpixcnt = SLpixcnt < PIXEL_COUNT-1 ? ++SLpixcnt : 0; |
frankvnk | 0:0c037aff5039 | 159 | } |
frankvnk | 0:0c037aff5039 | 160 | SLpixend = SLpixcnt + 1; |
frankvnk | 0:0c037aff5039 | 161 | } |
frankvnk | 0:0c037aff5039 | 162 | } |
frankvnk | 0:0c037aff5039 | 163 | else |
frankvnk | 0:0c037aff5039 | 164 | { |
frankvnk | 0:0c037aff5039 | 165 | SLpixcnt = 0; |
frankvnk | 0:0c037aff5039 | 166 | SLrpt = 0; |
frankvnk | 0:0c037aff5039 | 167 | SLpixend = PIXEL_COUNT; |
frankvnk | 0:0c037aff5039 | 168 | } |
frankvnk | 0:0c037aff5039 | 169 | for (int i = SLpixcnt; i < SLpixend; ++i) { |
frankvnk | 0:0c037aff5039 | 170 | windowMean(magnitudes, |
frankvnk | 0:0c037aff5039 | 171 | frequencyToBin(frequencyWindow[i]), |
frankvnk | 0:0c037aff5039 | 172 | frequencyToBin(frequencyWindow[i+1]), |
frankvnk | 0:0c037aff5039 | 173 | &intensity, |
frankvnk | 0:0c037aff5039 | 174 | &otherMean); |
frankvnk | 0:0c037aff5039 | 175 | // Convert intensity to decibels. |
frankvnk | 0:0c037aff5039 | 176 | intensity = 20.0*log10(intensity); |
frankvnk | 0:0c037aff5039 | 177 | // Scale the intensity and clamp between 0 and 1.0. |
frankvnk | 0:0c037aff5039 | 178 | intensity -= SPECTRUM_MIN_DB; |
frankvnk | 0:0c037aff5039 | 179 | intensity = intensity < 0.0 ? 0.0 : intensity; |
frankvnk | 0:0c037aff5039 | 180 | intensity /= (SPECTRUM_MAX_DB-SPECTRUM_MIN_DB); |
frankvnk | 0:0c037aff5039 | 181 | intensity = intensity > 1.0 ? 1.0 : intensity; |
frankvnk | 0:0c037aff5039 | 182 | led.hsi2rgbw(hues[i], 1.0, intensity); |
frankvnk | 0:0c037aff5039 | 183 | } |
frankvnk | 0:0c037aff5039 | 184 | } |
frankvnk | 0:0c037aff5039 | 185 | |
frankvnk | 0:0c037aff5039 | 186 | |
frankvnk | 0:0c037aff5039 | 187 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 188 | // SAMPLING FUNCTIONS |
frankvnk | 0:0c037aff5039 | 189 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 190 | |
frankvnk | 0:0c037aff5039 | 191 | void samplingCallback() |
frankvnk | 0:0c037aff5039 | 192 | { |
frankvnk | 0:0c037aff5039 | 193 | // Read from the ADC and store the sample data |
frankvnk | 1:736b34e0f484 | 194 | samples[sampleCounter] = (1023 * Audio) - 511.0f; |
frankvnk | 0:0c037aff5039 | 195 | // Complex FFT functions require a coefficient for the imaginary part of the input. |
frankvnk | 0:0c037aff5039 | 196 | // Since we only have real data, set this coefficient to zero. |
frankvnk | 0:0c037aff5039 | 197 | samples[sampleCounter+1] = 0.0; |
frankvnk | 0:0c037aff5039 | 198 | // Update sample buffer position and stop after the buffer is filled |
frankvnk | 0:0c037aff5039 | 199 | sampleCounter += 2; |
frankvnk | 0:0c037aff5039 | 200 | if (sampleCounter >= FFT_SIZE*2) { |
frankvnk | 0:0c037aff5039 | 201 | samplingTimer.detach(); |
frankvnk | 0:0c037aff5039 | 202 | } |
frankvnk | 0:0c037aff5039 | 203 | } |
frankvnk | 0:0c037aff5039 | 204 | |
frankvnk | 0:0c037aff5039 | 205 | void samplingBegin() |
frankvnk | 0:0c037aff5039 | 206 | { |
frankvnk | 0:0c037aff5039 | 207 | // Reset sample buffer position and start callback at necessary rate. |
frankvnk | 0:0c037aff5039 | 208 | sampleCounter = 0; |
frankvnk | 0:0c037aff5039 | 209 | samplingTimer.attach_us(&samplingCallback, 1000000/SAMPLE_RATE_HZ); |
frankvnk | 0:0c037aff5039 | 210 | } |
frankvnk | 0:0c037aff5039 | 211 | |
frankvnk | 0:0c037aff5039 | 212 | bool samplingIsDone() |
frankvnk | 0:0c037aff5039 | 213 | { |
frankvnk | 0:0c037aff5039 | 214 | return sampleCounter >= FFT_SIZE*2; |
frankvnk | 0:0c037aff5039 | 215 | } |
frankvnk | 0:0c037aff5039 | 216 | |
frankvnk | 0:0c037aff5039 | 217 | |
frankvnk | 0:0c037aff5039 | 218 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 219 | // COMMAND PARSING FUNCTIONS |
frankvnk | 0:0c037aff5039 | 220 | // These functions allow parsing simple commands input on the serial port. |
frankvnk | 0:0c037aff5039 | 221 | // Commands allow reading and writing variables that control the device. |
frankvnk | 0:0c037aff5039 | 222 | // |
frankvnk | 0:0c037aff5039 | 223 | // All commands must end with a semicolon character. |
frankvnk | 0:0c037aff5039 | 224 | // |
frankvnk | 0:0c037aff5039 | 225 | // Example commands are: |
frankvnk | 0:0c037aff5039 | 226 | // GET SAMPLE_RATE_HZ; |
frankvnk | 0:0c037aff5039 | 227 | // - Get the sample rate of the device. |
frankvnk | 0:0c037aff5039 | 228 | // SET SAMPLE_RATE_HZ 400; |
frankvnk | 0:0c037aff5039 | 229 | // - Set the sample rate of the device to 400 hertz. |
frankvnk | 0:0c037aff5039 | 230 | // |
frankvnk | 0:0c037aff5039 | 231 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 232 | |
frankvnk | 0:0c037aff5039 | 233 | void parseCommand(char* command) |
frankvnk | 0:0c037aff5039 | 234 | { |
frankvnk | 0:0c037aff5039 | 235 | if (strcmp(command, "GET MAGNITUDES") == 0) { |
frankvnk | 0:0c037aff5039 | 236 | for (int i = 0; i < FFT_SIZE; ++i) { |
frankvnk | 0:0c037aff5039 | 237 | printf("%f\r\n", magnitudes[i]); |
frankvnk | 0:0c037aff5039 | 238 | } |
frankvnk | 0:0c037aff5039 | 239 | } else if (strcmp(command, "GET SAMPLES") == 0) { |
frankvnk | 0:0c037aff5039 | 240 | for (int i = 0; i < FFT_SIZE*2; i+=2) { |
frankvnk | 0:0c037aff5039 | 241 | printf("%f\r\n", samples[i]); |
frankvnk | 0:0c037aff5039 | 242 | } |
frankvnk | 0:0c037aff5039 | 243 | } else if (strcmp(command, "GET FFT_SIZE") == 0) { |
frankvnk | 0:0c037aff5039 | 244 | printf("%d\r\n", FFT_SIZE); |
frankvnk | 0:0c037aff5039 | 245 | } else if (strcmp(command, "GET SAMPLE_RATE_HZ") == 0) { |
frankvnk | 0:0c037aff5039 | 246 | printf("%d\r\n", SAMPLE_RATE_HZ); |
frankvnk | 0:0c037aff5039 | 247 | } else if (strstr(command, "SET SAMPLE_RATE_HZ") != NULL) { |
frankvnk | 0:0c037aff5039 | 248 | SAMPLE_RATE_HZ = (typeof(SAMPLE_RATE_HZ)) atof(command+(sizeof("SET SAMPLE_RATE_HZ")-1)); |
frankvnk | 0:0c037aff5039 | 249 | } else if (strcmp(command, "GET LEDS_ENABLED") == 0) { |
frankvnk | 0:0c037aff5039 | 250 | printf("%d\r\n", LEDS_ENABLED); |
frankvnk | 0:0c037aff5039 | 251 | } else if (strstr(command, "SET LEDS_ENABLED") != NULL) { |
frankvnk | 0:0c037aff5039 | 252 | LEDS_ENABLED = (typeof(LEDS_ENABLED)) atof(command+(sizeof("SET LEDS_ENABLED")-1)); |
frankvnk | 0:0c037aff5039 | 253 | } else if (strcmp(command, "GET SPECTRUM_MIN_DB") == 0) { |
frankvnk | 0:0c037aff5039 | 254 | printf("%f\r\n", SPECTRUM_MIN_DB); |
frankvnk | 0:0c037aff5039 | 255 | } else if (strstr(command, "SET SPECTRUM_MIN_DB") != NULL) { |
frankvnk | 0:0c037aff5039 | 256 | SPECTRUM_MIN_DB = (typeof(SPECTRUM_MIN_DB)) atof(command+(sizeof("SET SPECTRUM_MIN_DB")-1)); |
frankvnk | 0:0c037aff5039 | 257 | } else if (strcmp(command, "GET SPECTRUM_MAX_DB") == 0) { |
frankvnk | 0:0c037aff5039 | 258 | printf("%f\r\n", SPECTRUM_MAX_DB); |
frankvnk | 0:0c037aff5039 | 259 | } else if (strstr(command, "SET SPECTRUM_MAX_DB") != NULL) { |
frankvnk | 0:0c037aff5039 | 260 | SPECTRUM_MAX_DB = (typeof(SPECTRUM_MAX_DB)) atof(command+(sizeof("SET SPECTRUM_MAX_DB")-1)); |
frankvnk | 0:0c037aff5039 | 261 | } else if (strcmp(command, "GET SLOWDOWN") == 0) { |
frankvnk | 0:0c037aff5039 | 262 | printf("%d\r\n", SLOWDOWN); |
frankvnk | 0:0c037aff5039 | 263 | } else if (strstr(command, "SET SLOWDOWN") != NULL) { |
frankvnk | 0:0c037aff5039 | 264 | SLOWDOWN = (typeof(SLOWDOWN)) atoi(command+(sizeof("SET SLOWDOWN")-1)); |
frankvnk | 0:0c037aff5039 | 265 | } |
frankvnk | 0:0c037aff5039 | 266 | |
frankvnk | 0:0c037aff5039 | 267 | // Update spectrum display values if sample rate was changed. |
frankvnk | 0:0c037aff5039 | 268 | if (strstr(command, "SET SAMPLE_RATE_HZ ") != NULL) { |
frankvnk | 0:0c037aff5039 | 269 | spectrumSetup(); |
frankvnk | 0:0c037aff5039 | 270 | } |
frankvnk | 0:0c037aff5039 | 271 | |
frankvnk | 0:0c037aff5039 | 272 | // Turn off the LEDs if the state changed. |
frankvnk | 0:0c037aff5039 | 273 | if (LEDS_ENABLED == 0) { |
frankvnk | 0:0c037aff5039 | 274 | } |
frankvnk | 0:0c037aff5039 | 275 | } |
frankvnk | 0:0c037aff5039 | 276 | |
frankvnk | 0:0c037aff5039 | 277 | void parserLoop() |
frankvnk | 0:0c037aff5039 | 278 | { |
frankvnk | 0:0c037aff5039 | 279 | // Process any incoming characters from the serial port |
frankvnk | 0:0c037aff5039 | 280 | while (pc.readable()) { |
frankvnk | 0:0c037aff5039 | 281 | char c = pc.getc(); |
frankvnk | 0:0c037aff5039 | 282 | // Add any characters that aren't the end of a command (semicolon) to the input buffer. |
frankvnk | 0:0c037aff5039 | 283 | if (c != ';') { |
frankvnk | 0:0c037aff5039 | 284 | c = toupper(c); |
frankvnk | 0:0c037aff5039 | 285 | strncat(commandBuffer, &c, 1); |
frankvnk | 0:0c037aff5039 | 286 | } else { |
frankvnk | 0:0c037aff5039 | 287 | // Parse the command because an end of command token was encountered. |
frankvnk | 0:0c037aff5039 | 288 | parseCommand(commandBuffer); |
frankvnk | 0:0c037aff5039 | 289 | // Clear the input buffer |
frankvnk | 0:0c037aff5039 | 290 | memset(commandBuffer, 0, sizeof(commandBuffer)); |
frankvnk | 0:0c037aff5039 | 291 | } |
frankvnk | 0:0c037aff5039 | 292 | } |
frankvnk | 0:0c037aff5039 | 293 | } |
frankvnk | 0:0c037aff5039 | 294 | |
frankvnk | 0:0c037aff5039 | 295 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 296 | // MAIN FUNCTION |
frankvnk | 0:0c037aff5039 | 297 | //////////////////////////////////////////////////////////////////////////////// |
frankvnk | 0:0c037aff5039 | 298 | |
frankvnk | 0:0c037aff5039 | 299 | int main() |
frankvnk | 0:0c037aff5039 | 300 | { |
frankvnk | 0:0c037aff5039 | 301 | NVIC_set_all_irq_priorities(1); |
frankvnk | 0:0c037aff5039 | 302 | NVIC_SetPriority(UART0_IRQn, 0); |
frankvnk | 0:0c037aff5039 | 303 | // Set up serial port. |
frankvnk | 0:0c037aff5039 | 304 | pc.baud (38400); |
frankvnk | 0:0c037aff5039 | 305 | pc.attach(&rxisr); |
frankvnk | 0:0c037aff5039 | 306 | #ifndef RGBW_ext |
frankvnk | 0:0c037aff5039 | 307 | led.invertpwm(1); //On-board KL25Z RGB LED uses common anode. |
frankvnk | 0:0c037aff5039 | 308 | #endif |
frankvnk | 0:0c037aff5039 | 309 | // Clear the input command buffer |
frankvnk | 0:0c037aff5039 | 310 | memset(commandBuffer, 0, sizeof(commandBuffer)); |
frankvnk | 0:0c037aff5039 | 311 | |
frankvnk | 0:0c037aff5039 | 312 | // Initialize spectrum display |
frankvnk | 0:0c037aff5039 | 313 | spectrumSetup(); |
frankvnk | 0:0c037aff5039 | 314 | |
frankvnk | 0:0c037aff5039 | 315 | // Begin sampling audio |
frankvnk | 0:0c037aff5039 | 316 | samplingBegin(); |
frankvnk | 0:0c037aff5039 | 317 | |
frankvnk | 2:035d551759a5 | 318 | // Init arm_ccft_32 |
frankvnk | 2:035d551759a5 | 319 | switch (FFT_SIZE) |
frankvnk | 2:035d551759a5 | 320 | { |
frankvnk | 2:035d551759a5 | 321 | case 16: |
frankvnk | 2:035d551759a5 | 322 | S = & arm_cfft_sR_f32_len16; |
frankvnk | 2:035d551759a5 | 323 | break; |
frankvnk | 2:035d551759a5 | 324 | case 32: |
frankvnk | 2:035d551759a5 | 325 | S = & arm_cfft_sR_f32_len32; |
frankvnk | 2:035d551759a5 | 326 | break; |
frankvnk | 2:035d551759a5 | 327 | case 64: |
frankvnk | 2:035d551759a5 | 328 | S = & arm_cfft_sR_f32_len64; |
frankvnk | 2:035d551759a5 | 329 | break; |
frankvnk | 2:035d551759a5 | 330 | case 128: |
frankvnk | 2:035d551759a5 | 331 | S = & arm_cfft_sR_f32_len128; |
frankvnk | 2:035d551759a5 | 332 | break; |
frankvnk | 2:035d551759a5 | 333 | case 256: |
frankvnk | 2:035d551759a5 | 334 | S = & arm_cfft_sR_f32_len256; |
frankvnk | 2:035d551759a5 | 335 | break; |
frankvnk | 2:035d551759a5 | 336 | case 512: |
frankvnk | 2:035d551759a5 | 337 | S = & arm_cfft_sR_f32_len512; |
frankvnk | 2:035d551759a5 | 338 | break; |
frankvnk | 2:035d551759a5 | 339 | case 1024: |
frankvnk | 2:035d551759a5 | 340 | S = & arm_cfft_sR_f32_len1024; |
frankvnk | 2:035d551759a5 | 341 | break; |
frankvnk | 2:035d551759a5 | 342 | case 2048: |
frankvnk | 2:035d551759a5 | 343 | S = & arm_cfft_sR_f32_len2048; |
frankvnk | 2:035d551759a5 | 344 | break; |
frankvnk | 2:035d551759a5 | 345 | case 4096: |
frankvnk | 2:035d551759a5 | 346 | S = & arm_cfft_sR_f32_len4096; |
frankvnk | 2:035d551759a5 | 347 | break; |
frankvnk | 2:035d551759a5 | 348 | } |
frankvnk | 2:035d551759a5 | 349 | |
frankvnk | 0:0c037aff5039 | 350 | while(1) { |
frankvnk | 0:0c037aff5039 | 351 | // Calculate FFT if a full sample is available. |
frankvnk | 0:0c037aff5039 | 352 | if (samplingIsDone()) { |
frankvnk | 0:0c037aff5039 | 353 | // Run FFT on sample data. |
frankvnk | 2:035d551759a5 | 354 | // Run FFT on sample data. |
frankvnk | 2:035d551759a5 | 355 | arm_cfft_f32(S, samples, 0, 1); |
frankvnk | 0:0c037aff5039 | 356 | // Calculate magnitude of complex numbers output by the FFT. |
frankvnk | 0:0c037aff5039 | 357 | arm_cmplx_mag_f32(samples, magnitudes, FFT_SIZE); |
frankvnk | 0:0c037aff5039 | 358 | |
frankvnk | 0:0c037aff5039 | 359 | if (LEDS_ENABLED == 1) { |
frankvnk | 0:0c037aff5039 | 360 | spectrumLoop(); |
frankvnk | 0:0c037aff5039 | 361 | } |
frankvnk | 0:0c037aff5039 | 362 | |
frankvnk | 0:0c037aff5039 | 363 | // Restart audio sampling. |
frankvnk | 0:0c037aff5039 | 364 | samplingBegin(); |
frankvnk | 0:0c037aff5039 | 365 | } |
frankvnk | 0:0c037aff5039 | 366 | |
frankvnk | 0:0c037aff5039 | 367 | // Parse any pending commands. |
frankvnk | 0:0c037aff5039 | 368 | if(commandRecv) { |
frankvnk | 0:0c037aff5039 | 369 | // pc.attach(NULL); |
frankvnk | 0:0c037aff5039 | 370 | parseCommand(commandBuffer); |
frankvnk | 0:0c037aff5039 | 371 | commandRecv = 0; |
frankvnk | 0:0c037aff5039 | 372 | // Clear the input buffer |
frankvnk | 0:0c037aff5039 | 373 | memset(commandBuffer, 0, sizeof(commandBuffer)); |
frankvnk | 0:0c037aff5039 | 374 | // pc.attach(&rxisr); |
frankvnk | 0:0c037aff5039 | 375 | } |
frankvnk | 0:0c037aff5039 | 376 | } |
frankvnk | 0:0c037aff5039 | 377 | } |