Signal analysis and display on Adafruit shield18

I have this desktop project boiling for my students in the coming semester. I wanted to show you how I toggle the small display in order to be able to scan through a time series of 1024 samples - just the right amount for an FFT of the signal!

I hoped to place the FFT algorithm in a library - but the Arduino IDE gave me such a headache that I need your recommendation of another editor with built-in support for the .ino language - so I can upload code to the Arduino Uno/Due/Nano and have my libraries written in ANSI C or ISO C++ available in the same editor!

Here below is the whole code stored in a .ino file. For speed optimisation pointer arithmetics should be introduced in the fft( ) function, but it actually runs quite fast as it is on my small Due so I will maybe leave that as a student task!

Also I have to warn you that there is added a DC value of 3.3VDC/2 = 1.65 VDC to the software test signal. I have used a simple A*sin(omega*t) function in order to test the display, the scaling, etc.

The DC value shown in stave 0 (0kHz) is kind of disturbing the scaling of the resulting spectrum.
I will eat some good Christmas food and drink some beer too. Then I will return to the task of placing the DC calculation in its proper place - so the fft can be shown with the DC value or with the DC value discarded before the analysis.

Merry Christmas to you all at Adafriut! Nice product, the shield18 with seesaw, joystick and buttons A,B and C. I still need to figure what I can use these buttons for. DC removal perhaps?

Steen ok
Tech High School Thisted
Thy (Cold Hawaii as the surfers call it!)
Danmark

Code: Select all | TOGGLE FULL SIZE

#define DEVNAME " Shield18 FFT-analysis "
#define DEVDATE " 19dec2018 "
#define DEVTIME " 1300 "
/***************************************************
Based on an example sketch for the Adafruit 1.8" TFT shield with joystick
This example is for the Seesaw version
Check out the links above for our tutorials and wiring diagrams
These displays use SPI to communicate, 4 pins are required to
interface
One pin is also needed for the joystick, we use analog 3
****************************************************/

#include <SPI.h>
#include <SD.h>
#include <math.h>
// #include <libraries\fft_library\fft_library.h>

#if defined(__SAM3X8E__) // DUE detected
#define MODEL  2     // Due
#define ARR    analogReadResolution(12);    // 12 bits = 4096 steps
#define DMAX   4095  // Analog til digital konvertering
#define U0_mV  3300  // Spandingsforsyning Vcc
#else                  // Ellers maa det vaere en uno!
#define MODEL  1     // Uno
#define ARR    /*not used for Uno*/  // default 10 bits = 1024 steps
#define DMAX   1023
#define U0_mV  5000
#endif

#define SD_CS    4  // Chip select line for SD card on Shield
#define TFT_CS  10  // Chip select line for TFT display on Shield
#define TFT_DC   8  // Data/command line for TFT on Shield
#define TFT_RST  -1  // Reset line for TFT is handled by seesaw!
// For the frame of the 128x160 pixel 1.8" TFT display
#define X_BUF_MAX  1050          // 7 display frames x 150 pixel
#define X_DISP      150          // Available for display in frame
#define X_OFF       2            // Reserved for green frame
#define Y_OFF       2            // Reserved for green frame
#define X_MAX       158          // Pixel size of display
#define Y_MAX       126          // Pixel size of display
#define F_MEAS_HZ  1000          // Frequency of measuring signal in Hz
#define T_SAMPL_US   25          // Inter sample time T_s
#define SIGNAL_IN     0          // Wire signal to Port analog 0
#define TIME_SERIES   0          // Set display_type = 0
#define FREQ_SPECTR   1          // Set display_type = 1
#define HZ_PER_STAVE 40          // hps = f_n/ND2 = 20000/512 = 39 !!!
float vcc = 0;
float f_meas_hz;                 // Frequency of measuring signal in Hz
float T_meas_ms;                 // Period time in ms for measuring signal
float T_sampl_us;                // Sampling time in us
float y_offset_mV;               // Due: 3300mV/2 = 1650mV
float A_mV;                      // Amplitude of meas signal
float omega;                     // omega = 2*pi*f of meas sig
float yval_f[X_BUF_MAX];         // Et array af values
float yval_max_f; // Find the max between values yval_f[0]..yval_f[X_MAX] for scale purpose!
float yval_sum_f; // Find the DC value of the time series
float meas_time_ms;
float conv_factor_mV;
// long starttime;
int displ_type;
long n_displ_begin = 0;         // 0..150..300..450....1050
// defined in library long yval[X_MAX];

// From fft_library file:

#define SIGLEN   1024   // Signal Length - no. of points
#define SIGLEND2  512   // Signal length div 2
#define MAX_P      10   // P: Power of 2: siglen = 2^P
#define MAX_PP1    11   // MAX_P + 1 ==> P in [0..MAX_P]
#define SIGNAL_IN   0

struct c{
float real;
float imag;
};

struct c U,S,T;
struct c X[SIGLEN];
int N = SIGLEN;       // int N=pow(2,M);
int NM1 = SIGLEN-1;        // NM1 = N-1;
int ND2 = SIGLEND2;        // ND2 = N/2;
float P_f;
int P;
int q = SIGLEND2;
int k,l,n;
int qm1;
int LE; // L Exponent = 2^l = 2,4,8,16...
int LE2;// L Exp/2 = 2^(l-1)= 1,2,4,08...
int np;
float A = 1.0; // Amplitude in test signal
float B = 2.0;
float mag;
float pi_f = 3.141592654;

// look up 2^P for P= 0,1,2,3,04,05,06,007,008,009,0010,0011,0012,0013,00014};   //,00015,00016
long int lu_pow2[MAX_PP1] = {1,2,4,8,16,32,64,128,256,512,1024};//,2048,4096,8192,16384};   //,32768,65536};
// look up cos(PI/LE2)
float lu_cospd[MAX_PP1];
float lu_sinpd[MAX_PP1];

float cos_pd(int p) { // cos_pi_div_2_to_l_minus_1 = cos(PI/LE2)
return cos(pi_f/(lu_pow2[p-1]));
}

float sin_pd(int p) { // sin_pi_div_2_to_l_minus_1 = -sin(PI/LE2)
return sin(pi_f/(lu_pow2[p-1]));

void fft(float * inbuf_p,int mode, int DC_subtract) { // &inbuf[0], mode = 0 no window function

P_f = (float)log(N)/log(2);
P= (int)P_f;

for (int p=1;p<=MAX_PP1;p++){
lu_cospd[p] = cos_pd(p);
lu_sinpd[p] = sin_pd(p);
}

for(n=0;n<SIGLEN;n++){  // Copy yval_f[ ] measurement to real part of FFT buffer
if (mode == 0) {
X[n].real = yval_f[n];
} else if (mode ==1){          // Hamming window added on signal:
X[n].real = yval_f[n] * (0.54-0.46*cos(2*pi_f*n/NM1));   //*(A*sin(n*2*pi_f/8)+B*sin(n*2*pi_f/4));
}
if (DC_subtract == 1) {
X[n].real -= yval_sum_f;
}
}
for (n=1;n<=N-2;n++){
if(n<q){                    // Ombyt X[n] og X[q]
T.real=X[q].real;         // T: Temporary
T.imag=X[q].imag;
X[q].real=X[n].real;
X[q].imag=X[n].imag;
X[n].real=T.real;
X[n].imag=T.imag;
} // if(n<=q)
k = ND2;
while(k<=q) {
q=q-k;
k=(int)k/2;
} // while(k<=q)
q = q+k;
} // for( n=1;n<NM1;n++)
for (l=1;l<=P;l++) {
LE = lu_pow2[l];           // LE = (int) pow(2,l);
LE2 = lu_pow2[l-1];        // LE2= LE/2;
U.real = 1.0;
U.imag = 0.0;
S.real = lu_cospd[l];   // lu_cospd[l];  // = cos(PI/LE2);
S.imag = -lu_sinpd[l];  // -lu_sinpd[l]; // = -sin(PI/LE2);
for (q=1;q<=LE2;q++){
qm1=q-1;
n = qm1;
while(n<=NM1) {
np = n+LE2;
T.real = X[np].real*U.real - X[np].imag*U.imag;
T.imag = X[np].real*U.imag + X[np].imag*U.real;
X[np].real = X[n].real - T.real;
X[np].imag = X[n].imag - T.imag;
X[n].real = X[n].real + T.real;
X[n].imag = X[n].imag + T.imag;
n = n+LE;
} // while(n<=NM1) // for (n=qm1;n<=NM1;n++)
T.real = U.real;
U.real = T.real*S.real - U.imag*S.imag;
U.imag = T.real*S.imag + U.imag*S.real;
} // for (q=1;q<=LE2;q++)
} // for(l=1;l<=P;l++)

yval_max_f = 0;           // For scaling of yval on display
for(n=0;n<SIGLEN;n++){
yval_f[n] = sqrt(pow(X[n].real,2)+pow(X[n].imag,2));  // copy magnitude of spectrum back to yval_f[ ] buffer
if (yval_f[n] > yval_max_f){
yval_max_f = yval_f[n];
} // if
}

}

void tftprint(float * yval_p) {
//  float * yval_p = yp;
int scaled_yval;
tft.setTextWrap(false);
tft.fillScreen(ST7735_BLACK);
tft.setRotation(1);   // 3 upside down
//tft.drawRect(startx,starty lenx,leny,color);
//tft.drawRect(2, 2, 158, 126, ST7735_GREEN);
tft.drawRect(X_OFF, Y_OFF, X_MAX-2*X_OFF, Y_MAX-2*Y_OFF, ST7735_GREEN);  // Gron ramme om display!
//  float f_max_val =(float)yval_max;       // Den hojeste vaerdi i maalingen er gemt
float scale = (float)(Y_MAX-4*Y_OFF)/yval_max_f;  // Hojeste vaerdi bestemmer scale (f.eks 0.25)
Serial.println();
Serial.print("  debug yval_max_f = ");  // Til debug: Vis lige hvad hojeste maaling er!
Serial.println(yval_max_f);             // Der anvendes float for at kunne skalere
Serial.print("  debug scale = ");       // Hvis max_val>Y_MAX vil 0<scale<1
Serial.println(scale,6);
for (int i=0;i<(X_MAX-4*X_OFF);i++){   // De X_MAX maalinger vises som lodrette streger

//     yval_f[i] = (float)round(yval_f[i]*scale);  // Men forst skal alle maalinger skaleres!
//   *(yval_p+i) = (float)round(*(yval_p+i)*scale);
scaled_yval = (int)round(*(yval_p+i)*scale);
//   tft.drawFastVLine(int16_t x, int16_t y, int16_t h,uint16_t, color)
//   tft.drawFastVLine(i+2*X_OFF,Y_MAX-2*Y_OFF-(int)yval_f[i],(int)yval_f[i],ST7735_BLUE);  // Lidt kringlet - se foto!
//   tft.drawFastVLine(i+2*X_OFF,Y_MAX-2*Y_OFF-(int)*(yval_p+i),(int)*(yval_p+i),ST7735_BLUE);
tft.drawFastVLine(i+2*X_OFF,Y_MAX-2*Y_OFF-scaled_yval,scaled_yval,ST7735_BLUE);
} // for i
} // tftprint()

void tftInitprint( void) {  // Noget at kigge paa mens der maales X_MAX gange i starten
tft.setTextWrap(false);
tft.fillScreen(ST7735_BLACK);
tft.setRotation(1);
//tft.drawRect(startx,starty lenx,leny,color);
tft.drawRect(X_OFF, Y_OFF, X_MAX-2*X_OFF, Y_MAX-2*Y_OFF, ST7735_GREEN);
tft.setTextColor(ST7735_WHITE);
tft.setTextSize(2);
tft.setCursor(10, 10);
tft.print("GRAF");
tft.setCursor(10, 30);           //
tft.print("BYGGES");    // (Det varer lige lidt inden vi starter!)
tft.setCursor(10, 50);
//  tft.setTextColor(tft.Color565(255,255,0));   // cyan (blue,green,red)
tft.print("OP!");
} // tftInitprint()

void usbPrint(){
for (int i=0;i<X_BUF_MAX;i++){        //
Serial.print("sample no = ");    // x_val
Serial.print(i);
Serial.print("\t\t t_ms[");           // x_val
Serial.print(i);
Serial.print("] =");
meas_time_ms = (float)i*T_sampl_us/1000;
Serial.print(meas_time_ms,3);
Serial.print("\t\t yval_f[i] = ");    // y_val
Serial.println(yval_f[i]);
}
}

void measurement(int source){ // source = 0: simulation  source = 1: analogread
yval_max_f = 0;
yval_sum_f = 0;
for (int i=0;i<SIGLEN;i++){        // De N maalinger
if (source==0){
meas_time_ms = (float)i*T_sampl_us/1000;
yval_f[i] = A_mV*sin(omega*meas_time_ms/1000) + y_offset_mV;
} else if (source==1) {
delayMicroseconds(18);
}
if (yval_f[i] > yval_max_f){
yval_max_f = yval_f[i];    // used for scaling
} // if
yval_sum_f += yval_f[i];     // used to find DC value of time series
} // for
} // void measurement()

void setup(void) {
Serial.begin(9600);
while (!Serial);
ARR
vcc = (float)U0_mV/1000;
conv_factor_mV = (float)U0_mV/(DMAX);
f_meas_hz = (float)F_MEAS_HZ;   // Frequency of measuring signal in Hz
T_meas_ms  = 1000.0/F_MEAS_HZ;    // Period time in ms for measuring signal
omega = 2*PI*f_meas_hz; // omega in s^-1
T_sampl_us = T_SAMPL_US;        // Sampling time in us
A_mV = (float)U0_mV/2;
y_offset_mV = (float)U0_mV/2;
displ_type = TIME_SERIES;
Serial.println();
Serial.println();
Serial.println();
Serial.print("Development name :");
Serial.println(DEVNAME);
Serial.print("Development Date :");
Serial.println(DEVDATE);
Serial.print("Development Time :");
Serial.println(DEVTIME);
Serial.println();
Serial.print("f_meas_hz = ");
Serial.print(f_meas_hz);
Serial.print("\t\t T_meas_ms = ");
Serial.print(T_meas_ms);
Serial.print("\t\t omega = ");
Serial.println(omega);
Serial.print("T_sampl_us = ");          // x_val
Serial.print(T_sampl_us);
Serial.print("\t\t y_offset_mV = ");    // x_val
Serial.print(y_offset_mV);
Serial.print("\t\t A_mV = ");           // x_val
Serial.println(A_mV);
Serial.println();

// start by disabling both SD card and TFT
pinMode(TFT_CS, OUTPUT);
digitalWrite(TFT_CS, HIGH);
pinMode(SD_CS, OUTPUT);
digitalWrite(SD_CS, HIGH);

// Start seesaw helper chip
if (!ss.begin()){
Serial.println("seesaw could not be initialized!");
while(1);
}
Serial.println("Seesaw I2C communication chip on shield started");
Serial.print("Version: ");
Serial.println(ss.getVersion(), HEX);
// Start set the backlight off
ss.setBacklight(TFTSHIELD_BACKLIGHT_OFF);
// Reset the TFT
ss.tftReset();

// Initialize 1.8" TFT
tft.initR(INITR_BLACKTAB);   // initialize a ST7735S chip, black tab

Serial.println("TFT OK!");
tft.fillScreen(ST77XX_CYAN);
delay(2000);
// Dim backlight it up
for (int32_t i=TFTSHIELD_BACKLIGHT_OFF; i<TFTSHIELD_BACKLIGHT_ON; i+=1000) {
ss.setBacklight(i);
delay(10);
}
tft.fillScreen(ST77XX_RED);
delay(1000);
tftInitprint();
measurement(0); // simulation by generating a sine wave in math
//  usbPrint();   // debug purpose
tftprint(&yval_f[n_displ_begin]);
}

void loop() {
tft.setTextColor(ST77XX_WHITE); // ST77XX_RED ST77XX_YELLOW ST77XX_GREEN ST77XX_BLUE ST77XX_MAGENTA
tft.setTextSize(1);

int displ_width = X_DISP;

if(! (buttons & TFTSHIELD_BUTTON_DOWN)){
tft.setCursor(0, 10);
tft.print("Down ");
}
if(! (buttons & TFTSHIELD_BUTTON_LEFT)){
n_displ_begin -= X_DISP;
if (n_displ_begin<0) n_displ_begin += X_BUF_MAX;
tftprint(&yval_f[n_displ_begin]);
tft.setCursor(0, Y_MAX-3*Y_OFF);
if (displ_type == TIME_SERIES) {
tft.print(n_displ_begin);
} else if (displ_type == FREQ_SPECTR) {
tft.print(n_displ_begin*HZ_PER_STAVE/1000);
tft.print(" kHz");
}
}
if(! (buttons & TFTSHIELD_BUTTON_UP)){
tft.setCursor(0, 60);
tft.print("Up");
}
if(! (buttons & TFTSHIELD_BUTTON_RIGHT)){
n_displ_begin += X_DISP;
if (n_displ_begin>X_BUF_MAX-X_DISP) n_displ_begin = 0; // 1050-150 = 900
tftprint(&yval_f[n_displ_begin]);
tft.setCursor(0, Y_MAX-3*Y_OFF);
if (displ_type == TIME_SERIES) {
tft.print(n_displ_begin);
} else if (displ_type == FREQ_SPECTR) {
tft.print(n_displ_begin*HZ_PER_STAVE/1000);
tft.print(" kHz");
}
}
if(! (buttons & TFTSHIELD_BUTTON_IN)){ // inserted 06dec2018
displ_type = FREQ_SPECTR;
fft(&yval_f[0],0,0);                // Run FFT - mode = 0 without Hamming window DC_subtract = 1
n_displ_begin = 0;
tftprint(&yval_f[n_displ_begin]);
tft.setCursor(0, Y_MAX-3*Y_OFF);
tft.print(n_displ_begin);
tft.print(" kHz");
}
if(! (buttons & TFTSHIELD_BUTTON_1)){
tft.setCursor(0, 140);
tft.print("1");
}
if(! (buttons & TFTSHIELD_BUTTON_2)){
tft.setCursor(50, 140);
tft.print("2");
}
if(! (buttons & TFTSHIELD_BUTTON_3)){
tft.setCursor(100, 140);
tft.print("3");
}
delay(500);
}

steenoluf

Posts: 29
Joined: Wed Mar 12, 2014 8:17 am

Re: Signal analysis and display on Adafruit shield18

Well. Shows the positive effect of taking a break and explaining oneself a bit.
It suddenly struck me: In order to find the DC value of a signal one uses the average: DC_val = y[0]+...y[N]/N.
I my previous posting I had forgot to finally divide with N which in the project above has been renamed to SIGLEN for historic reasons!

Here is the calculation of the DC value completed. Insert the new measurement() function and a nice display of the spectrum will occur.
Remember to switch DC_removal on in the call of the fft(pointer to inbuffer start, Hamming window on/off (called mode), DC_removal on/off)

Code: Select all | TOGGLE FULL SIZE
void measurement(int source){ // source = 0: simulation  source = 1: analogread
yval_max_f = 0;
yval_sum_f = 0;
for (int i=0;i<SIGLEN;i++){        // De N maalinger
if (source==0){
meas_time_ms = (float)i*T_sampl_us/1000;
yval_f[i] = A_mV*sin(omega*meas_time_ms/1000) + y_offset_mV;
} else if (source==1) {
delayMicroseconds(18);
}
if (yval_f[i] > yval_max_f){
yval_max_f = yval_f[i];    // used for scaling
} // if
yval_sum_f += yval_f[i];     // used to find DC value of time series
} // for
yval_sum_f = yval_sum_f/SIGLEN; // yval_sum_f now holds the DC value
} // void measurement()

steenoluf

Posts: 29
Joined: Wed Mar 12, 2014 8:17 am

Re: Signal analysis and display on Adafruit shield18

Shield18_FFT project - Frequency analysis utilising the FFT algorithm
I need a tiny font now for the scale of the frequency axis!
I have been looking into the GFX library and I have seen a font editor.
But before I let a student dive into that:
Can I use the tiny font that is used in the test file for the "latin screen-fill"? ("Ipsi Lorem...)
I look in the file

and I see a call to the function

void testdrawtext(char *text, uint16_t color) {
tft.setCursor(0, 0);
tft.setTextColor(color);
tft.setTextWrap(true);
tft.print(text);
}

so this is before setTextSize(uint8_t s), (from Adafruit_GFX.h) has been called.
I reckon the default size 0 is the smallest possible.
I need the smallest possible text size.
Maybe I should consider spending some exam project budget money and invest in some bigger tft screens.
I have one sample of the Adafruit 2.8" TFT touch screen.
Now: How much trouble would it be to transfer a project from the Shield18_seesaw and to the 2.8" TFT touch screen?
I think the GFX class is inherited in both screens. But I will have to convert all seesaw I2C calls to SPI calls ?

Maybe you can recommend a newer Adafruit screen - a bit bigger than the 1.8" TFT seesaw screen - that you are planning to run also with the seesaw and using I2C ?

Regards from Frosty Thy
Steen ok
Attachments
IMG_3324.JPG (997.71 KiB) Viewed 64 times

steenoluf

Posts: 29
Joined: Wed Mar 12, 2014 8:17 am