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  ----> http://www.adafruit.com/products/802  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 <Adafruit_GFX.h>#include <Adafruit_ST7735.h>#include <Adafruit_seesaw.h>#include <Adafruit_TFTShield18.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// A constructor with parameter has been added to this header:Adafruit_TFTShield18 ss = Adafruit_TFTShield18(&Wire1);#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 Hzfloat T_meas_ms;                 // Period time in ms for measuring signalfloat T_sampl_us;                // Sampling time in usfloat y_offset_mV;               // Due: 3300mV/2 = 1650mVfloat A_mV;                      // Amplitude of meas signalfloat omega;                     // omega = 2*pi*f of meas sigfloat yval_f[X_BUF_MAX];         // Et array af valuesfloat 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 seriesfloat 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];Adafruit_ST7735 tft = Adafruit_ST7735(TFT_CS, TFT_DC, TFT_RST);// 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   0struct 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 signalfloat 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,00016long 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) {//     X[n].real = analogRead(SIGNAL_IN)*conv_factor_mV;       yval_f[i] = analogRead(SIGNAL_IN)*conv_factor_mV;       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() {  uint32_t buttons = ss.readButtons();  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) {       yval_f[i] = analogRead(SIGNAL_IN)*conv_factor_mV;       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 44 times

steenoluf

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