Hi, I just started doing research on the FastLED library in preparation for when my LED strips arrive. I read this from the FastLED documentation Github, and I have a concern regarding timing.
"Writing out WS2812 data requires some pretty tight timing. Tight enough that FastLED disables interrupts while it is writing out led data. This means that while the led data is being written out, any interrupts that happen will be delayed until all the led data is written out. How long will that be for? Depends on the number of leds. WS2812 led data takes 30µs per pixel. If you have 100 pixels, then that means interrupts will be disabled for 3000µs, or 3ms."
My project will have just under 300 LEDs. Does that mean interrupts will be disabled for... the 9ms it takes for the program to update the LEDs? How will that work in conjunction with the FFT portion of the code, from my understanding the FFT will have created multiple samples in the 9ms that the LEDs are updated. Will this result in a laggy display relative to how fast the FFT samples?
/*
fft_adc.pde
guest openmusiclabs.com 8.18.12
example sketch for testing the fft library.
it takes in data on ADC0 (Analog0) and processes them
with the fft. the data is sent out over the serial
port at 115.2kb. there is a pure data patch for
visualizing the data.
*/
// do #defines BEFORE #includes
#define LIN_OUT 1 // use the log output function
#define FFT_N 256 // set to 256 point fft
#define DC_OFFSET 511
#define SAMPLING_FREQ (16000000/(13*32))
#include <FFT.h> // include the library
int adc_val;
void setup() {
Serial.begin(9600); // use the serial port
TIMSK0 = 0; // turn off timer0 for lower jitter - delay() and millis() killed
ADCSRA = 0xe5; // set the adc to free running mode
ADMUX = 0x40; // use adc0
DIDR0 = 0x01; // turn off the digital input for adc0
}
void loop() {
while (1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while (!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= DC_OFFSET;
adc_val = k<<6;
fft_input[i] = adc_val; // put real data into even bins
fft_input[i + 1] = 0; // set odd bins to 0
}
// window data, then reorder, then run, then take output
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_lin(); // take the output of the fft
sei(); // turn interrupts back on
Serial.println("start");
for (int j = 0 ; j < 128 ; j++) {
Serial.print("Frequency: "); Serial.print(j * (SAMPLING_FREQ / FFT_N)); Serial.print(" ");
Serial.println(fft_lin_out[j]); // print out the data
}
Serial.print("\n");
}
}
Thank you for taking the time to read and reply to my post. I apologize if there are any inaccuracies in this post, as I am still a novice.
i doubt you will notice a 9ms delay. However, often in pov projects the leds are arranged so than instead of one strip writing out 300 pixels, you would use 10 pins to write out 30 pixels each
from my understanding the FFT will have created multiple samples in the 9ms that the LEDs are updated.
No the Arduino can only do one thing at a time, the FFT will not generate any samples unless the code is telling it to.
What you do with an FFT is read in the samples, calculate the FFT and then display the results.
If the reading in of the samples is under the control of an interrupt then that interrupt must be disabled during the FFT calculation and display stage.
Will this result in a laggy display relative to how fast the FFT samples?
Yes, but you won’t notice it.
Grumpy_Mike:
No the Arduino can only do one thing at a time, the FFT will not generate any samples unless the code is telling it to.
If the reading in of the samples is under the control of an interrupt then that interrupt must be disabled during the FFT calculation and display stage.
The way I understand this is I should re-enable interrupts right after sampling finishes (updated code below), and then after interrupts are re-enabled I can include the code for the FFT calculation, and then I can use those results to display on the LEDs. If I do it in this order, there should not be any issues with the timing with the FFT code and display code, correct?
while (1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while (!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= DC_OFFSET;
adc_val = k<<6;
fft_input[i] = adc_val; // put real data into even bins
fft_input[i + 1] = 0; // set odd bins to 0
}
sei(); // turn interrupts back on
// window data, then reorder, then run, then take output
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_lin(); // take the output of the fft
//LED DISPLAY CODE HERE***
The way I understand this is I should re-enable interrupts right after sampling finishes (
The way you understand it is wrong. If you keep inputting samples while you are calculating the FFT that will screw up the calculations.
Grumpy_Mike:
The way you understand it is wrong. If you keep inputting samples while you are calculating the FFT that will screw up the calculations.
Ah ok, that makes sense. I'll keep sei() where it was originally. So the code below should be ok?
while (1) { // reduces jitter
cli(); // UDRE interrupt slows this way down on arduino1.0
for (int i = 0 ; i < 512 ; i += 2) { // save 256 samples
while (!(ADCSRA & 0x10)); // wait for adc to be ready
ADCSRA = 0xf5; // restart adc
byte m = ADCL; // fetch adc data
byte j = ADCH;
int k = (j << 8) | m; // form into an int
k -= DC_OFFSET;
adc_val = k<<6;
fft_input[i] = adc_val; // put real data into even bins
fft_input[i + 1] = 0; // set odd bins to 0
}
// window data, then reorder, then run, then take output
fft_window(); // window the data for better frequency response
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft
fft_mag_lin(); // take the output of the fft
sei(); // turn interrupts back on
***LED DISPLAY CODE HERE***
No do it after you have displayed the LEDs like I said.
If you do it during the display then the turning off of the interrupts during the transfer of the data to the LEDs will stop you receiving any data and so the data will not be evenly sampled and that will screw up what the FFT gives you.
Grumpy_Mike:
No do it after you have displayed the LEDs like I said.
If you do it during the display then the turning off of the interrupts during the transfer of the data to the LEDs will stop you receiving any data and so the data will not be evenly sampled and that will screw up what the FFT gives you.
Found this code on Github, and that is basically what I'll be doing. Looks straightforward to me, what do you think?
void loop() {
showfps(); // Debug output of how many frames per second we're getting. Comment this out in production.
getFFT(); // Let's take FFT_N samples and crunch 'em.
fftDisplay(); // Let's calculate the LED display from our FFT output array.
FastLED.show(); // And then display it.
} // loop()
void getFFT() {
get_sound(); // High speed sound sampling.
fft_window(); // Window the data for better frequency response.
fft_reorder(); // Reorder the data before doing the fft.
fft_run(); // Process the data in the fft.
fft_mag_log(); // I guess we'll be converting to logarithm.
// Really, I don't know what these do to tell you the truth.
} // GetFFT()
void get_sound() { // Uses high speed polled analog sampling and NOT analogRead().
cli();
for (int i = 0 ; i < FFT_N*2 ; i+=2) { // Save 256 samples. No more, no less.
while(!(ADCSRA & 0x10)); // Wait for adc to be ready.
ADCSRA = 0xf5; // restart adc
fft_input[i] = ADC - DC_OFFSET; // Get the full 10 bit A/D conversion and center it.
fft_input[i+1] = 0;
/* Serial.print(abs(sample)); // Serial plot graph of our sampling.
Serial.print(" "); // Lowest and highest values are graphed so that the plot isn't auto-scaled.
Serial.print(0); // Lowest value
Serial.print(" ");
Serial.print(512); // Highest value
Serial.println(" ");
*/
}
sei();
} // get_sound()
void fftDisplay() {
#define hueinc 0 // A hue increment value to make it rotate a bit.
#define micmult 10 // Bin values are very low, to let's crank 'em up.
#define noiseval 32 // Increase this to reduce sensitivity.
for (int i= 0; i < NUM_LEDS; i++) { // Run through the LED array.
int tmp = qsuba(fft_log_out[2*i+2], noiseval); // Get the sample and subtract the 'quiet' normalized values, but don't go < 0.
if (tmp > (leds[i].r + leds[i].g + leds[i].b)) // Refresh an LED only when the intensity is low. By Andrew Tuline.
leds[i] = CHSV(tmp*micmult+hueinc, 255, tmp*micmult); // Note how we really cranked up the tmp value to get BRIGHT LED's. Also increment the hue for fun.
leds[i].nscale8(224); // Let's fade the whole thing over time as well.
}
} // fftDisplay()
void showfps() {
long currentMillis = 0;
static long lastMillis = 0;
static long loops = 0;
currentMillis=millis(); // Determine frames per second
loops++;
if(currentMillis - lastMillis > 1000){
Serial.println(loops);
lastMillis = currentMillis;
loops = 0;
}
} // showfps()
I can’t see how this will display the FFT on your display. You want to have the number of bins equal to the number of column you have and then how far up the column lit according to the magnitude of the number in the bin. But as you don’t show the whole code then it is hard to say what it will do.
This will involve aggregating the number of bins to give you a logarithmic representation of the frequency.
The code will however do “something”