What’s cooler (than being cool, ice cold? no…) than having the ability to view the spectrum of audio?! A few things I hear you say, to which I also agree. On the other hand, viewing the spectrum of your music can be a fun and “wow factor” way to show off how electronics can do cool things to non-electronically minded people. Who doesn’t want to see the bass drop in a “phat” dubstep song, right?
So! After previously designing a relatively inefficient 8 band spectrum analyzer around the MAX7219 8×8 LED matrix driver, I decided to drastically improve that idea and design around a PCD8544 LCD, commonly found in old Nokia mobile phones – ahem, Nokia 3310. The screen in itself has been used in a variety of my projects, starting from the Eon smartwatches 1 and 2 and even making a minor appearance on the Phobass scene; needless to say, I have a fair few lying around and interfacing them is pretty easy. They feature relatively fast update rates, theroetically up to about 900fps though thats obviously theoretical as LCDs do not have the ability to change at that speed (sending 504 bytes over a 4MHz SPI channel), along with a resolution of 84×48 pixels.
Creating a spectrum analyzer is actually a relatively simple task and consists of gathering a buffer of data, applying the FFT and plotting the resultant bins on the screen. The FFT is an absolutely magical transform (Fast Fourier Transform to be exact) that converts time domain samples into their frequency domain components. This can therefore be used to display the frequency content of a set of samples, with a set of samples in this case being your audio!
The STM32F0 is a really versatile little chip offering loads of features but unlike its bigger brother, the STM32F4 (and potentially the STM32F3/2?), it doesn’t feature dedicated fixed point routines to execute the FFT. This poses a slight problem as I could either code up a really slow DFT, as with the 8×8 LED version, or I could search elsewhere on the internet to find a fixed point implementation of the FFT, bearing in mind here that the STM32F0 also doesn’t have native floating point support! Obviously, this was the better option and I came across a pretty old implementation of a fixed point FFT routine named “fix_fft” found here. Turns out that this fixed implementation of the FFT was originally written in 1989 – before I was even born, with modifications made 1994 and 2006. Regardless, something as staple as the FFT doesn’t change that much and this implementation works just fine.
The FFT does however produce a real and imaginary output. Both of these arrays contain useful data and are required to display the spectrum on the screen. As it turns out, it is actually the magnitude of the real and imaginary parts that are displayed on the screen. For those who don’t know, a real and imaginary number can be represented together as a position vector in the complex plane. The magnitude of this can therefore be calculated using pythagoras’ theorem: Mag*Mag = Real*Real + Imag*Imag (ignoring the i section of the imaginary number). Further simplifying this, the magnitude can be found as: Mag = sqrt(R*R + I*I). This posed a second problem. I could either use the math.h library for a floating point square root – on integers, or I could search the internet for an integer square root routine. In my foundation year math classes, I did actually get taught a way, known as the Newton Raphson method so this actually helped in my understanding of integer square root routines. I eventually settled on a short nugget of code I found off stack overflow posted by the user gutskalk.
These two nuggets of code equiped me with all I needed to implement my graphics analyzer. For those who want to see it in action, I made a short video! Sorry for the cut off at the end, it seems my camera must’ve got fed up of my voice.
Obviously, the STM32F0 needs to sample the audio at set points to give an accurate representation in samples of the audio. This means interfacing a relatively fast running ADC with the audio. The problem here lies with the fact that audio sources can have a very variable output impedance, ranging from the low 1’s of ohms (for headphone outputs), up to 10k+ for some phono outputs. This problem can be mitigated by introducing an amplifier between the audio and the ADC input. As the ADC input will present a relatively low impedance input, the amplifier can match the two varying impedances. In this instance, I’ve designed a pretty poor common collector amplifier which couples the audio to the input, along with low pass filtering it. A low pass filter is required to avoid aliasing, a pretty bad problem when it comes to sampling. A theoretically perfect anti aliasing filter would have a completely flat response of 1 up to half the sample rate, then have a completely zero’d response after this point. This however is hard to realise with analog filters and other problems such as non linear phase become apparent with high order filters, along with complexity. I’ve completely scamped out on a good anti aliasing filter and have included a really shoddy 1 pole low pass filter (with the 3dB point well above the FS/2 point), poor I know! I have however, included the ability to oversample and digitally filter within the code so hopefully that somewhat makes up for it…
Simulating the above circuit in LTspice tells us that the -3dB point is at 44kHz, hardly a very good anti aliasing filter! Regardless, anti aliasing in hardware and oversampling, with anti aliasing in software helps too.
For those interested, the code can be found on my Github!
Edit: Turns out, the aliases you can see in the video are solved by my oversampling anti aliasing technique!