Close

Graphics

A project log for Spectrum Analyser Code

Code for a spectrum analyzer.

agpcooperagp.cooper 10/24/2016 at 00:312 Comments

Nokia LCD Display

I have a few of these lying around and I wrote some code using a DigiSpark a while back. The code is based on work by Julian Ilett (https://www.youtube.com/playlist?list=PLjzGSu1yGFjXWp5F4BPJg4ZJFbJcWeRzk).

First problem is that the display needs 5 output pins and the DigiSparks only has five IO pins so that is not going to work (i.e. no pin left for the signal input).

I could use an Arduino UNO with the Nokia LCD Display.

MicroView

I have a MicroView so I may as well use that.

So here it is listening to a 3v 1kHz square wave:

The top gradations are 0 kHz, 1.25 kHz, 2.5 kHz, 3.75 kHz and 5 kHz.

You can see the DC, 1 kHz and the harmonics but they off a little bit.

Next there is quite a bit of jitter at low frequencies, here is another shot:

The 1 kHz and DC signals have merged.

Here is the same square wave but with a 20kHz:

Improvements

Need to check the actual average sample time for each pass.

Need understand the nature of the jitter.

Is it an interrupt or is it due to the algorithm?

Need some free IO pins for an option menu (after I solve the other problems).

Here is the current code:

#include <MicroView.h>

// Audio Spectrum Analyser
#define SampleInput A0   // Name the sample input pin
#define BandWidth  500   // BandWidth
#define MaxFreq   4000   // Max analysis frequency
#define Trigger     10   // Trigger to synchronise the sampler 2vpp at 1kHz = 32

// Define various ADC prescaler
const unsigned char PS_16=(1<<ADPS2);
const unsigned char PS_32=(1<<ADPS2)|(1<<ADPS0);
const unsigned char PS_64=(1<<ADPS2)|(1<<ADPS1);
const unsigned char PS_128=(1<<ADPS2)|(1<<ADPS1)|(1<<ADPS0);

// Setup the serial port and pin 2
void setup() {
  // Setup the ADC
  pinMode(SampleInput,INPUT);
  ADCSRA&=~PS_128;  // Remove bits set by Arduino library
  // Set prescaler 
  // ADCSRA|=PS_64; // 64 prescaler (250 kHz assuming a 16MHz clock)
  // ADCSRA|=PS_32; // 32 prescaler (500 kHz assuming a 16MHz clock)
  ADCSRA|=PS_16;    // 16 prescaler (1 MHz assuming a 16MHz clock)

  uView.begin();                          // Start MicroView
  uView.clear(PAGE);                      // Clear page
  uView.println("Spectrum  Analyser");    // Project
  uView.println("0-20 kHz");              // Range
  uView.println();
  uView.println("agp.cooper@gmail.com");  // Author
  uView.display();                        // Display
  uView.clear(PAGE);                      // Clear page
  delay(2000);                            // Wait
}

void loop() {  
  static byte *samples;           // Sample array pointer
  static byte *window;            // Window array pointer
  static int N=0;                 // Number of samples for BandWidth
  static long sampleFreq;         // Sample frequency     
  long freq;                      // Frequency of interest
  float s;                        // Goertzel variables
  float s_prev;
  float s_prev2;
  float coeff;
  float magn;
  int i;

  if (N==0) {
    // Check sample frequency and set number of samples
    samples=(byte *)malloc(100);
    unsigned long ts=micros();
    for (i=0;i<100;i++) samples[i]=(byte)(analogRead(SampleInput)>>2);
    unsigned long tf=micros();
    free(samples);
    sampleFreq=100000000/(tf-ts);
    N=2*sampleFreq/BandWidth+1;
    
    uView.setCursor(0,0);                   // Set cursor to beginning
    uView.print("SI: A");                   // Sample input pin 
    uView.println(SampleInput-14);
    uView.print("SF: ");                    // Sample frequency 
    uView.println(sampleFreq);
    uView.print("MF: ");                    // Max frequency 
    uView.println(MaxFreq);
    uView.print("BW: ");                    // andWidth
    uView.println(MaxFreq);
    uView.print("SN: ");                    // Number of samples
    uView.println(N);
    uView.display();                        // Display
    uView.clear(PAGE);                      // Clear page
    delay(2000);
    
    // Create arrays
    samples=(byte *)malloc(N);
    window=(byte *)malloc(N);
 
    // Modified Hamming Window
    for (i=0;i<N;i++) window[i]=(byte)((0.54-0.46*cos(2*M_PI*i/(N-1)))*255); 

    // Generate test samples
    for (i=0;i<N;i++) {
      samples[i]=(byte)(30*(sin(2*M_PI*i*5000/sampleFreq)+sin(2*M_PI*i*2500/sampleFreq)+sin(2*M_PI*i*7500/sampleFreq)+sin(2*M_PI*i*10000/sampleFreq))+127);
    }
  }

  if (true) {
    // Sychronise the start of sampling with data slicer
    int a0,a1;
    a0=1023;
    for (i=0;i<N;i+=2) {    
      a1=analogRead(SampleInput);
      a0=(a0*13+a1*3)/16;
      if (a1>a0+3) break;
    }
    for (i=0;i<N;i++) samples[i]=(byte)(analogRead(SampleInput)>>2);
  }
  
  // Scan frequencies
  for (freq=0;freq<=MaxFreq;freq+=(MaxFreq/40)) {
    // Goertzel (https://en.wikipedia.org/wiki/Goertzel_algorithm)
    coeff=2*cos(2*M_PI*freq/sampleFreq);
    s_prev=0;
    s_prev2=0;
    for (i=0;i<N;i++) {
      s=0.0000768935*window[i]*samples[i]+s_prev*coeff-s_prev2;
      s_prev2=s_prev;
      s_prev=s;
    }
    // Get magnitude
    magn=2*sqrt(s_prev2*s_prev2+s_prev*s_prev-coeff*s_prev*s_prev2)/N;
    // Display on MicroView
    uView.line(freq*40/MaxFreq,47,freq*40/MaxFreq,10-(int)(20*log10(magn+0.0001)));
  }
  // Frequency graduations
  uView.setCursor(47,0);
  uView.print(MaxFreq/1000);
  uView.print("k");
  uView.line(0,0,0,5);
  uView.line(10,0,10,2);
  uView.line(20,0,20,5);
  uView.line(30,0,30,2);
  uView.line(40,0,40,5);
  // Voltage graduations   
  uView.line(0,40,40,40);
  uView.setCursor(47,38);
  uView.print("-30");
  uView.line(0,20,40,20);
  uView.setCursor(47,18);
  uView.print("-10");
  uView.line(0,10,40,10);
  uView.setCursor(47,8);
  uView.print("  0");
  //Display
  uView.display();
  uView.clear(PAGE);
}


Fixes

Measured the sample frequency on the fly.

Dynamically determined the sample number and allocated the memory for the arrays.

For the phase error issue I added a data slicer (with a time out) to try an synchronise the input signal (if possible).

Convert amplitude to a log scale.

So here it is testing the 1 kHz 3v square wave (using a 500 Hz bandwidth):

Quite stable - very little jitter.

The magnitude is RMS voltage.

The 4k at the top right-hand corner is the maximum frequency of the analysis.

The nominal band width is 500 Hz (actually 250 Hz before the Hamming Window).

You can see the spectrum noise below -30dB.

Project Complete

Until I find something to do with this project I am going to call it complete!

AlanX

Discussions

agp.cooper wrote 11/28/2016 at 00:29 point

Hi Trevor,

Great, enjoy.

Regards AlanX

  Are you sure? yes | no

trevor wrote 11/27/2016 at 21:20 point

Thanks for doing all this hard work. I need a GHz analyzer display, but will implement your code then use its graphics as a framework for expansion. Thanks again! :)

  Are you sure? yes | no