Arduino FHT on Atmega328 3.3v

Hi,

I've been working on a radio project for quite some time now. The project is almost finished, and as I wanted to reduce my dependency on components, I wanted to switch from using a MSGEQ7 to Arduino FHT (ArduinoFHT - Open Music Labs Wiki).

Unfortunately, I can't get it to work. My situation is a little bit different, and therefore I can't simply copy all solutions I could find.

The situtation:

  • Barebone Atmega328 running on 3.3v / 8 Mhz
  • Powered by LM317T from 12V
  • 2 1.8" TFTs attached to SPI
  • TEA5767 connected through I2C
  • (TDA7375 on the board as well, works without problems)
  • Custom made, high quality PCB

Problem:
The spectrum looks incorrect.

Wiring:
As my system is running on 3.3v, the analog pins are as well. I therefore reduce the max amplitude to 3.3v, and then apply a DC offset of 1.65v.
See attachment.

Remarks:

  • There is a logarithmic potentiometer before the 'TEA5767 out or headphone' (see wiring).
  • The spectrum 'height' increase when the potentiometer is in its last ~5-10%. So from 0% to about 90% the spectrum doesn't change in height, but 'vibrates' (1-2 blocks per row go on and off).
  • The spectrum doesn't change when I disconnect the audio in.
  • I do receive some kind of signal, see attachment (those are the 32 bins of a FHT analysis with FHT_N = 32).
  • Is the wiring correct? My electrical engineering skills are limited, but I can follow almost all explanations.
  • Are the (time consuming) TFTs interfering with the FHT?
  • The first display is almost never redrawn, the spectrum analyzer gets redrawn as often as possible.

I hope you can help me out, as although I've tried a lot of changes, I assume the solution is not so difficult.

schematics.png

I tried that same library and also got incorrect results, may I recommend using the same author's FFT which works fine for me.

#include <FFT.h>

//defines
#define WINDOW 0
#define LIN_OUT 1
#define FFT_N 128 // set to x point fft

fft_input[i << 1] = ...; //This is the input for real
fft_input[(i << 1) + 1] = ...; //This is the input for imaginary, set this to 0 if recording sound

//these 2 functions take 2.8ms to complete with FFT_N = 128
fft_reorder(); // reorder the data before doing the fft
fft_run(); // process the data in the fft

//fft_input now contains the output in the order of real,imaginary,real,imaginary,...
//sqrt(real*real + imaginary*imaginary) will give the correct bin value

Not much to go on. Here's what I see:

I do receive some kind of signal, see attachment (those are the 32 bins of a FHT analysis with FHT_N = 32).

If N = 32, then only the first [Edit: fix this] 15 16 bins have meaning. The 1617th bin shows a frequency that a 32-point FFT can't resolve. The other bins might have anything in them, as they appear to be what's left over after the magnitude function runs are outside the output array's bounds. My guess is that they're linear terms, and the lower bins have been converted to logarithmic. Whatever they are, they weren't intended as output.

What's the sampling rate? If it's the default rate, it'll be about 38 kHz. At 32 samples, the bins are a kHz wide. That might suit you.

The first 15 bins - the ones that mean anything - look suspiciously like the Hann-windowed FFT magnitudes of a sample that's all at some constant level, maybe half-scale. I don't think you're getting an AC signal through, and I don't think the DC level is where you think it is.

You have a fairly complicated system here for someone who says,

My electrical engineering skills are limited

I'll recommend that you simplify it, get a simple thing working, and, once a portion is complete and working solidly, build another feature onto that. It's hubris to think that you're going to assemble and program it, and it'll just work. It's folly to think that you'll successfully troubleshoot it by looking at the TFT.

Recommendation: Step back. First, read the usage information for the Open Music Labs FHT. It's not as trivial as you think. If you don't understand something, look it up. Read about aliasing. Then, disconnect, or ignore, the TFT, and run the FHT program. Modify the code to calculate, or fetch from memory, some predetermined data - something like a sine wave. That'll take analog acquisition out of the picture, and you'll be able to tell what's really going on. Then,

  • Print the acquired data without transforming it. Was it what you expected? If not, troubleshoot and test until it works.
  • Run the transform on calculated data. If it's a pure sine wave, you'll find a sharp peak at the wave frequency. Did you get what you expected? Troubleshoot until it works.

And so on. After that, if you have a way to input a clean sine signal, you can add analog acquisition. Then, you can try your radio input. If I were you, I'd be prepared to back up a couple of times, and revert to a clean input, or calculated data.

I'll suggest that you write a simple program to run the FHT on calculated or stored data. Keep it simple, and then, if you have trouble, when you post all the code to this forum, readers will be willing to look closely at it. If you post code that uses hardware that we don't have, or is unduly long, you'll get fewer and poorer responses.

Frankly, it looks to me that the input signal has a substantial DC component, that the windowing function is turned on in the FHT program, and that there's little or no AC component. But, it looks so much like that that I suspect that the TFT is doing what it should.

So, after all that, I finally looked closely at the schematic. Here's what I see: The voltage divider, used for level shifting, is on the wrong side of the coupling capacitor. It should be on the Arduino side, where it can set the DC level of the analog input.

As it stands, the signal to the analog input is AC-coupled - the analog pin will see only an AC voltage. You want AC coupling in the signal chain, to remove the existing DC level, whatever it might be. But, then you want to replace it with your own DC level, namely, half-scale for the analog input. That's what the two 10K resistors are for.

So, I'd recommend: Disconnect the two 10K resistors. Connect one of them to ground, and its other end to A0. Connect one end of the remaining resistor to 3.3V, and the other end to A0.

Here's what I think is going on: With no DC connected to A0, the input drifts to ground. When you try to input a signal, the ADC can only see the positive portion of the signal, so the FHT is garbled. At the same time, the FHT example code subracts 512 - analog half-scale - from the ADC reading, and then multiplies by 64 to frame the ADC data as a 16-bit reading. The readings are all somewhere around negative full scale, -32768. If you disabled the windowing function in the example code, I think that you'd see a single peak in bin 0, a near flat line in bins 1 through 15, and irrelevant junk data in the remaining bins.

You don't see a single sharp peak in bin 0 because the windowing function has modified the input data before the FHT is run. The windowing function will tend to spread peaks over a few bins; its advantage is that it reduces unwanted effects of an input signal frequency that doesn't fit neatly into the sampling window.

Note that you won't see bin 0 go all the way to zero, unless your voltage divider is perfectly matched. The magnitude-log function in the FHT example code will emphasize small voltages, so you may see a bigger peak in bin 0 than you expect. But, it will be much reduced from what you see now.

I recommend that you observe this effect for yourself. After the sketch acquires the data, and before it windows, reorders, or transforms it, print the data to the serial port. See if it's what you expected. I think it'll all be around -32768.

tmd3:
It's folly to think that you'll successfully troubleshoot it by looking at the TFT.

I may have been wrong about that. I hope so.

Thanks for the extensive replies and second look.

To start off, it's unfortunately very difficult for me to 'simply' output serial data, as the Atmega328 is standalone and sketches are uploaded through the AVR ISP MK2.

I therefore decreased the amount of data drawn on the spectrum TFT, to minimalize communication time.

I changed FHT_N from 32 to 64 and only display bins 0-31 from now on, to get more detailed data.

What's the sampling rate? If it's the default rate, it'll be about 38 kHz. At 32 samples, the bins are a kHz wide. That might suit you.

The Arduino is running at 8 MHz (all other settings are standard), I believe the sampling rate equals: 8 Mhz/32/13 = ~19kHz.
As stated above, I changed FHT_N to 64, so the bin size should be ~300 Hz.

The voltage divider, used for level shifting, is on the wrong side of the coupling capacitor. It should be on the Arduino side, where it can set the DC level of the analog input.

Changed it, makes sense.

So, I'd recommend: Disconnect the two 10K resistors. Connect one of them to ground, and its other end to A0. Connect one end of the remaining resistor to 3.3V, and the other end to A0.

Did that, basically moved the DC offset circuit part to after the 10uF capacitor and let the + side of the capacitor 'face' A0.

If you disabled the windowing function in the example code, I think that you'd see a single peak in bin 0, a near flat line in bins 1 through 15, and irrelevant junk data in the remaining bins.

In the current situation (with all changes made), I can't see a difference when fht_window() is not called.

Extra remarks:

  • When using mag_lin8_out, I get 0s and (almost) maximum in bin 32 (i = 31).
  • I assume I can solve the problem regarding the output range from the potentiometer by changing to a linear pot.
  • The spectrum does get higher when I increase the volume, this looks very right.
  • When using a sine generator, all bins are almost equally high (except the first 3), but the height does change when I increase the 'volume' of the sine wave.
  • The bins do not get above 50%, I therefore added the corresponding part of the sketch.

Sketch:
Note that my aim is stereo, therefore spectrum = int[2][32]. However, I'm only using spectrum[0][ x ], because I only changed the circuit for A0.

void setup() {
  ...
  TIMSK0 = 0; //Turn off timer0 for lower jitter
  ADCSRA = 0xe5; //Set the ADC to free running mode
  ADMUX = 0x40; //Use ADC0
  DIDR0 = 0x01; //Turn off the digital input for ADC0
  ...
}
void loop() {
  ...
  for (int i = 0; i < 1; i ++) {
    ADMUX = 0x40;
    DIDR0 = 0x01;
    for (int j = 0; j < FHT_N/2; j ++) {
      //Wait for the ADC to be ready
      while (!(ADCSRA & 0x10)) {
      }
      ADCSRA = 0xf5; //Restart the ADC
      byte m = ADCL; //Fetch ADC data
      byte n = ADCH;
      int k = (n << 8) | m; //Form into an int
      k -= 0x0200; //Form into a signed int
      k <<= 6; //Form into a 16b signed int
      fht_input[j] = k; //Put real data into even bins
    }
    fht_window();
    fht_reorder();
    fht_run();
    fht_mag_log();
    for (int j = 0; j < FHT_N/2; j ++) {
      spectrum[i][j] = map(fht_log_out[bins[j]], 0, 255, 0, 14);
    }
    
    for (int j = 0; j < FHT_N/2; j ++) {
      if (spectrum[i][j] > spectrum_previous[i][j]) {
        for (int k = 0; k < spectrum[i][j]; k ++) {
          tftspctrm.fillRect(4 + 70*i + 4*j, 127 - 5 - 8*(k + 1), 1, 5, colors_spectrum[k]);
        }
        spectrum_previous[i][j] = spectrum[i][j];
      }
    }
  }
  ...
  //Difference between spectrum_previous and spectrum gets removed from TFT here
}

Ragoune:
very difficult for me to 'simply' output serial data, as the Atmega328 is standalone and sketches are uploaded through the AVR ISP MK2.

I think that you'll want a way to see what's going on in the sketch, beyond what you can see on the TFT. You might consider using a standard Arduino for development.

I therefore decreased the amount of data drawn on the spectrum TFT, to minimalize communication time. I changed FHT_N from 32 to 64 and only display bins 0-31 from now on, to get more detailed data.

OK. A further look at the Open Music Labs FHT code shows that the magnitude array has N/2 elements, so accessing beyond that will yield unknown data. See the description of fht_mag_log(), here - FHTFunctions - Open Music Labs Wiki.

Your calculation of the sample rate looks right to me. It looks like these changes have improved things. There's no longer an unexpected huge peak at DC.

Looking at the TFT display, in bin 0, I see seven ticks. That corresponds to an input value to map() of about 128. Working backward through the formula shown for fht_mag_log() at the referenced url, above, that means that log2 of the magnitude is 8, so the absolute magnitude is 28, or 256. Remembering that the code multiplies the analog data by 64, the DC level, referenced to ADC midscale, is 4. [Edit: fix this] $ 4 ticks on the ADC represents about 20 millivolts. So, that looks pretty good. Bin 0 has the highest magnitude, so all the other components are at less than 4 ADC counts - not much. That would explain why there's no visible data when using the linear scaled output - it's just not big enough to show up at the granularity of the diplay. Is the input signal turned off or disconnected for these tests?

I'd recommend developing some confidence in the whole process by sending calculated data to the fht routine. A clean sine input would be easiest to identify, since it should correspond to a single high peak. You can do it like this:

  for (int j = 0; j < FHT_N; j ++) {
      //Wait for the ADC to be ready
      while (!(ADCSRA & 0x10)) {
      }
      ADCSRA = 0xf5; //Restart the ADC
      byte m = ADCL; //Fetch ADC data
      byte n = ADCH;
      int k = (n << 8) | m; //Form into an int
      k -= 0x0200; //Form into a signed int
      k <<= 6; //Form into a 16b signed int
      fht_input[j] = k; //Put real data into even bins
      fht_input[j] = 32767 * sin(2.0 * 3.14159 * j / 16.0);  // ADDED LINE
    }

That will write over the analog reading with a sine function with four cycles over the sample period. I'd predict that you'd get a high peak in bin 4. Note that the ADC reading, and the timing of the ADC interrupts, become irrelevant with this change. It seems certain that some ADC interrupts will be missed, as sin() takes a long time, but it doesn't make any difference here.

I don't see why you use FHT_N/2, here:

   DIDR0 = 0x01;
    for (int j = 0; j < FHT_N/2; j ++) {
      //Wait for the ADC to be ready

When I try that, I get data in the first half of fht_input[]; the second half is all zeroes. I don't think that's what you want. Is there a reason you did it that way?

Time to post your code. Post an entire working sketch. If there's anything you can do to shorten it, while still maintaining a compilable and working sketch, I and other readers would appreciate it. I'd prefer to see a sketch that can be executed on a regular Arduino, with output to the serial port rather than a TFT. In fact, I'd prefer not to see the TFT code at all. I'll recommend that you migrate temporarily to a standard Arduino until you are happy with your code.

I still recommend using FFT library from the same website, works perfectly fine and is still very fast. Then when you find what is different between the 2 programs, make the changes to your FHT and all should work...

My bin size and FHT_N both equaled 32. As I increased FHT_N to 64 and changed 32 to FHT_N/2, I accidentally changed one '32' too many to FHT_N/2 instead of FHT_N.

I changed the FHT_N/2 you pointed out to FHT_N, and Arduino FHT works now!

All my other code is apparently correct, and when using a signal generator, the correct bins pop out.

Unfortunately, the maximum is still at 50%. I'm assuming this has something to do with the voltage divider and/or DC offset. So:

Is my wiring really correct, and am I using the correct resistor values?

Can you think of anything you might tell us to help troubleshoot this? Here are some hints:

  • 50% measured how? On a logarithmic scale? Linear?
  • Which of the four sources are you using to get that measurement?
  • What's the output voltage of the signal generator? Of the other three sources?

I believe I've listed all my thoughts, and here are the answers to your questions:

  • It is indeed on a logarithmic scale (mag_log_out()). The returned value from Arduino FHT is mapped to a 0-14 scale using map(fht_log_out[bins[j]], 0, 255, 0, 14).
  • This is purely based on the TFT representation of the signal. Note that the bins after FHT_N/2 do reach 100% (e.g. 14).
  • The output voltages can come from: laptop headphone out (0.447(?) V), smartphone audio out (0.447(?) V), TEA5767 out (75mV from datasheet). All never reach beyond level 7 (50%) on a logarithmic scale.

From this link - FHTFunctions - Open Music Labs Wiki - you can see that the equation for calculating the logarithmic output is 16 times the logarithm, base 2, of the magnitude. An output to the TFT of 7 corresponds to an output of 7 * 255 / 14, about 128. Dividing by 16 to get the log2 of the corresponding magnitude yields 8. 28 is 256. Noting that the FHT example code multiplies each reading by 64, a magnitude of 256 comes to a variation of 4 counts on the ADC. That's quite small, and could be nothing but noise. The corresponding voltage is 4 * 5 / 1023 = about 20 millivolts. It wouldn't surprise me, though, to find that a source with a maximum of 75 millivolts, with, say, musical content, never got above 20 millivolts. If the output were 75 millivots continuously, and that measurement were RMS, you'd get no more than 9 bars. That signal source may be too small to use without an amplifier for this application.

You seem uncertain about the voltage output of the other sources, so I won't address them here. I'll ask, though: how do you verify that the input signal has any audio content?

You mention that the signal generator gave you an obvious peak. Did you get more than 7 bars? What's the output voltage of the signal generator?