RSS

WaterFall Spectrogram.

Playing with my new toy (TFT ILI9486) I discovered, that its support vertical scrolling feature.  And my fft library needs a demo-video, so idea for new project was born. Waterfall spectrogram, real-time, audio 20 – 20 000 range. Main board is arduino DUE.  Plus capacitors and couple resistors to bias analog input at V / 2. Rectangular pulse from my DSO forms wide harmonics content, excellent source to demonstrate aliasing -);.

 

vsc_setup() sends 0x33 command to tft, setting vertical scroll area. and vScroll() 0x37 rotates image.

#include <SplitRadixReal.h>
#include <UTFT.h>

#define   SMP_RATE          44100UL 
#define   CLK_MAIN       84000000UL
#define   TMR_CNTR       CLK_MAIN / (2 *SMP_RATE)

// FFT_SIZE IS DEFINED in Header file Radix4.h 
// #define   FFT_SIZE           2048

#define   MIRROR         FFT_SIZE / 2
#define   INP_BUFF       FFT_SIZE 
         
volatile   uint16_t   sptr              =   0 ;
volatile    int16_t   flag              =   0 ;

           uint16_t  inp[2][INP_BUFF]   = { 0};     // DMA likes ping-pongs buffer

            int         f_r[FFT_SIZE]   = { 0};
            int         magn[MIRROR]    = { 0};     // Magnitudes
const       int         dc_offset       = 2047; 

            uint8_t          print_inp  =    0;     // print switch

            SplitRadixReal     radix;


#define     SIZE_X                          320
#define     SIZE_Y                          480
#define     BORDER                            5
#define     BFA                          BORDER  // Bottom Fixed Area
#define     TFA                      (SIZE_Y /2)  // Top Fixed Area 240
#define     VSH             (SIZE_Y - BFA - TFA) // Vertical Scroll Area 235
#define     CSEL                             40
#define     WFALL           (SIZE_X -(2* BORDER) -2)// Width-306
#define     MAPCF                             3  // Re-map coefficient, 1024 : 320

            UTFT        myGLCD( ILI9486, 38, 39, CSEL, 41);
extern      uint8_t     BigFont[];

            int         vsp                 = 0;

            int         imag[WFALL]      = { 0};     // Image-1
            int         blgo[WFALL]      = { 0};     // Image-2
            uint16_t    color_map[1024]  = { 0}; 
            uint16_t    log10_map[1024]  = { 0}; 

void setup()
{
  Serial.begin (115200) ; 
  adc_setup ();         
  tmr_setup ();         
//
  myGLCD.InitLCD(PORTRAIT);
  myGLCD.clrScr();

  vsc_setup();
  set_lake();  
  gen_cmap();  // color wheel map (LUT)
  gen_lmap();  // log-10 map (LUT)
  
  pinMode( 44, OUTPUT); // fft
  pinMode( 45, OUTPUT); // re-map
  pinMode( 46, OUTPUT); // pour_water
  pinMode( 47, OUTPUT); // belagio

  pinMode( 55, OUTPUT); // MIC Power
  digitalWrite( 55, HIGH);
}

inline int mult_shft12( int a, int b)  
{
  return (( a  *  b )  >> 12);      
}

void loop() 
{
  if ( flag )
  {   
   uint16_t indx_a = flag -1;
   uint16_t indx_b = 0;

digitalWrite( 44, HIGH);
   for ( uint16_t i = 0, k = (NWAVE / FFT_SIZE); i < FFT_SIZE; i++ ) 
   {  
      uint16_t windw = Hamming[i * k];
      f_r[i] = mult_shft12((inp[indx_a][indx_b++] - dc_offset), windw);
   }

   if( print_inp ){
     Serial.print("\n\tBuffer: ");    
     Serial.print(indx_a, DEC);       
     prnt_out2( f_r, FFT_SIZE);
     print_inp =  0;
     }

   radix.rev_bin( f_r, FFT_SIZE);
   radix.fft_split_radix_real( f_r, LOG2_FFT);
   radix.gain_Reset( f_r, LOG2_FFT -1); 
   radix.get_Magnit2( f_r, magn);

digitalWrite( 44,  LOW);
digitalWrite( 45, HIGH);

   re_map();
digitalWrite( 45,  LOW);
digitalWrite( 46, HIGH);

   pour_water();
digitalWrite( 46,  LOW);
digitalWrite( 47, HIGH);

   belagio();
digitalWrite( 47,  LOW);

   while (Serial.available()) {
        uint8_t input = Serial.read();
        switch(input) {
        case '\r':
            break;           
        case 'x':
            print_inp = 1;
            break;
        case 'f':
            Serial.print("\n\tReal: ");    
            prnt_out2( f_r, MIRROR);
            break;
        case 'o':
            Serial.print("\n\tMagnitudes: ");    
            prnt_out2( magn, MIRROR);
            break;
        case '?':
        case 'h':
            cmd_print_help();
            break;
        default: // -------------------------------
            Serial.print("Unexpected: ");
            Serial.print((char)input);
            cmd_print_help();
        }
    Serial.print("> ");
    }
   flag = 0;
   }   
}

void prnt_out2( int *array, int dlina) 
{
  Serial.print("\n\t");      
     for ( uint32_t i = 0; i < dlina; i++)
     {
       Serial.print(array[i]);       
       Serial.print("\t");    
       if ((i+1)%16 == 0) Serial.print("\n\t");
     }
  Serial.println("\n\t");
}

void cmd_print_help(void) 
{
    Serial.println("\n  Listing of all available CLI Commands\n");
    Serial.println("\t\"?\" or \"h\": print this menu");
    Serial.println("\t\"x\": print out adc array");
    Serial.println("\t\"f\": print out fft array");
    Serial.println("\t\"o\": print out magnitude array");
}

 

void vsc_setup(void)
{
  digitalWrite( CSEL, LOW);
  myGLCD.LCD_Write_COM( 0x33 );

  char BH0 = (TFA  >>   8);
  char BL0 = (TFA  & 0xFF);
  myGLCD.LCD_Write_DATA( BH0);
  myGLCD.LCD_Write_DATA( BL0);

  char BH1 = (VSH  >>   8);
  char BL1 = (VSH  & 0xFF);
  myGLCD.LCD_Write_DATA( BH1);
  myGLCD.LCD_Write_DATA( BL1);

  char BH2 = (BFA  >>   8);
  char BL2 = (BFA  & 0xFF);
  myGLCD.LCD_Write_DATA( BH2);
  myGLCD.LCD_Write_DATA( BL2);

  digitalWrite( CSEL, HIGH);
}

void set_lake(void)
{
  myGLCD.setFont(BigFont);
  myGLCD.setBackColor( 255, 255, 255);
  myGLCD.setColor(255, 255, 255);
//    myGLCD.setColor( 150, 255, 255);
  int dispx = SIZE_X - 1;
  int dispy = SIZE_Y - 1;

  myGLCD.fillRect( 0, 0, dispx, dispy);
  
  myGLCD.setColor(   0,   0, 255);
  int x1 = BORDER -2;
  int x2 = SIZE_X - x1;//BORDER -2;
  int y1 = BORDER -2;
  int y2 = SIZE_Y - y1;//BORDER -2;
//  myGLCD.drawRect(x1, y1, x2, y2); 
  myGLCD.fillRect(    0,   0,    x1, dispy); // left
  myGLCD.fillRect(   x2,   0, dispx, dispy); // right
  myGLCD.fillRect(    0,   0, dispx,    y1); // top
  myGLCD.fillRect(    0,  y2, dispx, dispy); // bot
//
  int strip = VSH / 2;
  myGLCD.setColor( 255,   0, 255);
  myGLCD.fillRect(  0, (y2 -2),    x1, (y2 -2 -strip)); 
  myGLCD.fillRect( x2, (y2 -2), dispx, (y2 -2 -strip));  
// riski
  myGLCD.setColor( 0, 255, 255);

  myGLCD.drawRect( x1, 50, x2, 53);    // Draw a Scale

  for ( int i = x2, cont = 0; i > x1; i -= 15, cont++ ) {
  
    myGLCD.drawRect( i, 50, i+2, 45);
    if(((cont %5) ==0) && (cont != 0)) {
      myGLCD.fillRect( i, 50, i+2, 40);
    }
  }
  myGLCD.print("0   5   10   15   20", x2, 30, 180);
}


void pour_water(void)
{
  int y = vsp + TFA;      //y = bottom line of scroll area 
  myGLCD.vScroll( y );    //roll frame.

  int x = SIZE_X - BORDER;
  for( int i = 0; i <  WFALL; i++, x--) { 
      int temp = imag[i];
      myGLCD.setColor( temp ); 
      myGLCD.drawPixel( x, y );
    }
  if(++vsp >= VSH) vsp = 1;
}

void belagio(void)
{
  int st_x = SIZE_X - BORDER; 
  int st_y = BORDER + 50; //dlya risok

// 20 ms
  myGLCD.setColor( 255, 255, 255);
  myGLCD.fillRect( st_x, st_y, st_x - WFALL, VSH);
  //  myGLCD.setColor(VGA_SILVER);

  myGLCD.setColor(  0,  0, 255);

  for( int i = 0; i <  WFALL; i++, st_x--) { 
      int temp = ((blgo[i] * 3)/ 4); // kompensate 255 : 185 (235-50)
      int end_y = st_y + temp;
      
      if(end_y > VSH) end_y = VSH; 
      
      if(temp > 0) myGLCD.drawLine( st_x, st_y, st_x, end_y );
    }
}

 

void re_map(void)
{
  for( int i = 0; i <  WFALL; i++) {
    imag[i] = 0; 
    }
 
  for( int j = 1; j < MIRROR; j++) { // DC off
    int indx = j / MAPCF;
       if( indx >= WFALL ) break; 
       imag[indx] += magn[j]; 
     }

  for( int i = 0; i <  WFALL; i++) { 
     int temp = imag[i];
       if( temp > 1023 ) temp = 1023;       
       temp = log10_map[temp];

       blgo[i] = (temp >> 2);
       imag[i] = color_map[temp];
     }
}


uint16_t setnColor( int layer ) {
    int  center = 128; // DC offset
    int  width 	= 127; // Amplitude  	    

    float frequency = (float) (0.001 * 2 * M_PI * layer); //0.0014 -bardovui

    float phi090 = (float) (M_PI * 1 /2 ); // 4/3=240
    float phi240 = (float) (M_PI * 4 /3 ); // 4/3=240
    float phi360 = (float) (M_PI * 2 /1 ); // 4/3=240

      int red = (int) (sin(frequency + phi360) * width + center);
      int grn = (int) (sin(frequency + phi090) * width + center);
      int blu = (int) (sin(frequency + phi240) * width + center);
        
    //  uint16_t color = yrk << 24 | red << 16 | grn << 8 | blu;
    uint16_t  color = ((red & 248) << 8 | (grn & 252) << 3 | (blu & 248) >> 3);

  return color;
}

void gen_cmap(void) 
{
  for( int bc = 0; bc < 1024; bc++) { color_map[bc] = setnColor( bc ); }	
}

void gen_lmap(void) 
{
  for( int bc = 0; bc < 1024; bc++) {
     log10_map[bc] = (int)((373.0 * log10(bc + 1.0)) - 100.0);
   }
  log10_map[0] = 0; 	
}


 

void tmr_setup ()
{
  pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0); // clock the TC0 channel 0

  TcChannel * t = &(TC0->TC_CHANNEL)[0] ;            // pointer to TC0 registers for its channel 0
  t->TC_CCR = TC_CCR_CLKDIS ;                        // disable internal clocking while setup regs
  t->TC_IDR = 0xFFFFFFFF ;                           // disable interrupts
  t->TC_SR ;                                         // read int status reg to clear pending
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |           // use TCLK1 (prescale by 2, = 42MHz)
              TC_CMR_WAVE |                          // waveform mode
              TC_CMR_WAVSEL_UP_RC |                  // count-up PWM using RC as threshold
              TC_CMR_EEVT_XC0 |     // Set external events from XC0 (this setup TIOB as output)
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
  
  t->TC_RC = TMR_CNTR;              // counter resets on RC, so sets period in terms of 42MHz clock
  t->TC_RA = TMR_CNTR /2;           // roughly square wave
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from RA and RC compares
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch to hardware trigger source.  
}

void adc_setup ()
{
  pmc_enable_periph_clk(ID_ADC);
  adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
  NVIC_EnableIRQ (ADC_IRQn);               // enable ADC interrupt vector

  adc_disable_all_channel(ADC);
  adc_enable_interrupt(ADC, ADC_IER_RXBUFF);

  ADC->ADC_RPR  =  (uint32_t)  inp[0];      // DMA buffer
  ADC->ADC_RCR  =  INP_BUFF;
  ADC->ADC_RNPR =  (uint32_t)  inp[1];      // next DMA buffer
  ADC->ADC_RNCR =  INP_BUFF;
  ADC->ADC_PTCR =  1;

  adc_set_bias_current(ADC, 0x01); 
  //  adc_enable_tag(ADC);
  adc_enable_channel(ADC, ADC_CHANNEL_7);  // AN0
  adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0);
  adc_start(ADC); 
}

void ADC_Handler (void)
{
  if((adc_get_status(ADC) & ADC_ISR_RXBUFF) ==	ADC_ISR_RXBUFF) {
    flag = ++sptr; 
    sptr &=  0x01;
    ADC->ADC_RNPR  =  (uint32_t)  inp[sptr];
    ADC->ADC_RNCR  =  INP_BUFF;
    }
}

And last touch, you need to add this function, inside UTFT or in main sketch:


void UTFT::vScroll( int vsp )
{
cbi(P_CS, B_CS);
LCD_Write_COM( 0x37 );

char BH0 = ( vsp >> 8);
char BL0 = ( vsp & 0xFF);
// LCD_Write_DATA( BH0, BL0);
LCD_Write_DATA( BH0);
LCD_Write_DATA( BL0);
sbi(P_CS, B_CS);
}

 


 
6 Comments

Posted by on January 16, 2015 in Uncategorized

 

Piezo Tester -2, Arduino DUE based DDS Synthesizer.

If you wanted to build  “LCQ Meter / Piezo Tester” project but don’t have AD9850 board,  than here is solution. Arduino DUE has a DAC, and with some magic it may replace DDS synthesizer !. Well, of course not in the RF range, only up to 250 kHz in this implementation.  As a buffer for weak DAC, I decided to use audio power amplifier TDA7052. It may directly drive 4 ohm load, works starting from 3V ( important for 3.3V compatibility ) and has wide linear frequency response, even more than necessary.

 DDS, how does it work.

 Here is the sketch, to generate < 400 kHz sinewave out of arduino dac:

#define    NWAVE               80

uint16_t  Sinewave[4][2][NWAVE] = {
  {
    { // 0 - 10 kHz
     +4095,   +4093,   +4089,   +4081,   +4070,   +4056,   +4038,   +4018,   +3995,   +3968,   +3939,   +3907,   +3872,   +3834,   +3793,   +3750,
     +3704,   +3656,   +3605,   +3551,   +3495,   +3438,   +3377,   +3315,   +3251,   +3185,   +3118,   +3048,   +2977,   +2905,   +2831,   +2757,
     +2681,   +2604,   +2526,   +2447,   +2368,   +2289,   +2209,   +2128,   +2048,   +1968,   +1887,   +1807,   +1728,   +1649,   +1570,   +1492,
     +1415,   +1339,   +1265,   +1191,   +1119,   +1048,    +978,    +911,    +845,    +781,    +719,    +658,    +601,    +545,    +491,    +440,
      +392,    +346,    +303,    +262,    +224,    +189,    +157,    +128,    +101,     +78,     +58,     +40,     +26,     +15,      +7,      +3,
    },
    {
        +1,      +3,      +7,     +15,     +26,     +40,     +58,     +78,    +101,    +128,    +157,    +189,    +224,    +262,    +303,    +346,
      +392,    +440,    +491,    +545,    +601,    +658,    +719,    +781,    +845,    +911,    +978,   +1048,   +1119,   +1191,   +1265,   +1339,
     +1415,   +1492,   +1570,   +1649,   +1728,   +1807,   +1887,   +1968,   +2048,   +2128,   +2209,   +2289,   +2368,   +2447,   +2526,   +2604,
     +2681,   +2757,   +2831,   +2905,   +2977,   +3048,   +3118,   +3185,   +3251,   +3315,   +3377,   +3438,   +3495,   +3551,   +3605,   +3656,
     +3704,   +3750,   +3793,   +3834,   +3872,   +3907,   +3939,   +3968,   +3995,   +4018,   +4038,   +4056,   +4070,   +4081,   +4089,   +4093
    }
  },
  {
    {// 10 - 100 kHz
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939
    },
    {
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939,
     +4095,   +3939,   +3495,   +2831,   +2048,   +1265,    +601,    +157,      +1,    +157,    +601,   +1265,   +2048,   +2831,   +3495,   +3939
    }
  },
  {
    {// 100 - 200 kHz
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495
    },
    {
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,
     +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495,   +4095,   +3495,   +2048,    +601,      +1,    +601,   +2048,   +3495
    }
  },
  {
    {// 200 - 400 kHz
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048
    },
    {
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,
     +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048,   +4095,   +2048,      +1,   +2048
    }
  }
};

            int       fast_mode  =    0;
            int       freq_inhz  = 1000; // Default Hz
            int       freq_intc  =    0;
            int       user_intf  =    0;

volatile   uint16_t   sptr       =    0;

void setup()
{
  Serial.begin (115200);
  dac_setup();        
  freq_intc = freqToTc(freq_inhz);
  TC_setup();        
  setup_pio_TIOA0(); 
}

int tcToFreq( int tc_cntr)
{
  int freq_hz;     

  if( tc_cntr == 0 ) return 1000;

  if( fast_mode ) freq_hz = (420000000UL / tc_cntr) / (2 * NWAVE);
  else            freq_hz = ( 42000000UL / tc_cntr) / (2 * NWAVE);

  if( fast_mode == 2 ) freq_hz *= 2;
  if( fast_mode == 3 ) freq_hz *= 4;

  return freq_hz;
}

int freqToTc( int freq_hz)
{
  int tc_cntr = 0;

  if( freq_hz == 0 ) return 25;

  if( fast_mode == 0 )  tc_cntr = (  42000000UL / freq_hz) / (2 * NWAVE);
  if( fast_mode == 1 )  tc_cntr = ( 420000000UL / freq_hz) / (2 * NWAVE);
  if( fast_mode == 2 )  tc_cntr = ( 840000000UL / freq_hz) / (2 * NWAVE);
  if( fast_mode == 3 )  tc_cntr = (1680000000UL / freq_hz) / (2 * NWAVE); 

  return tc_cntr;
}

void loop()
{
  char in_Byte;
  int     temp;
          
  if (Serial.available() > 0) {
    in_Byte = Serial.read();
    // Message Format To Set  Frequency:  1000f + "Enter".
    if((in_Byte >= '0') && (in_Byte <= '9'))
    {
      user_intf = (user_intf * 10) + (in_Byte - '0');
    }
    else
    { 
      if (in_Byte == 'f') // end delimiter
      {
        if ((user_intf > 20) && (user_intf < 400000))
        {
          freq_inhz = user_intf;
            fast_mode = 0;
            if( freq_inhz >  10000 ) fast_mode = 1;
            if( freq_inhz > 100000 ) fast_mode = 2;
            if( freq_inhz > 200000 ) fast_mode = 3;
          
          freq_intc = freqToTc(freq_inhz);
          TC_setup();        
          Serial.print("Fast Mode = ");
          Serial.println(fast_mode, DEC);
          Serial.print("freq_inhz = ");
          Serial.println(freq_inhz, DEC);
          Serial.print("freq_intc = ");
          Serial.println(freq_intc, DEC);
          Serial.print("approximation = ");
          temp = tcToFreq(freq_intc);
          Serial.println(temp, DEC);
        }
      user_intf = 0; // reset to 0 ready for the next sequence of digits
      }    
    }
  }
}

void DACC_Handler(void)
{
  if((dacc_get_interrupt_status(DACC) & DACC_ISR_ENDTX) == DACC_ISR_ENDTX) {
    ++sptr; 
    sptr &=  0x01;
      DACC->DACC_TNPR =  (uint32_t)  Sinewave[fast_mode][sptr];      // next DMA buffer
      DACC->DACC_TNCR =  NWAVE;
  }
}

void setup_pio_TIOA0()
{
  PIOB->PIO_PDR = PIO_PB25B_TIOA0;  
  PIOB->PIO_IDR = PIO_PB25B_TIOA0;  
  PIOB->PIO_ABSR |= PIO_PB25B_TIOA0;
}

void TC_setup ()
{
  pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0); 

  TcChannel * t = &(TC0->TC_CHANNEL)[0];            
  t->TC_CCR = TC_CCR_CLKDIS;                        
  t->TC_IDR = 0xFFFFFFFF;                           
  t->TC_SR;                                         
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |          
              TC_CMR_WAVE |                         
              TC_CMR_WAVSEL_UP_RC |                 
              TC_CMR_EEVT_XC0 |     
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR;
  
  t->TC_RC = freq_intc;
  t->TC_RA = freq_intc /2;       
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET; 
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG;    
}

void dac_setup ()
{
  pmc_enable_periph_clk (DACC_INTERFACE_ID) ; // start clocking DAC
  dacc_reset(DACC);
  dacc_set_transfer_mode(DACC, 0);
  dacc_set_power_save(DACC, 0, 1);            // sleep = 0, fastwkup = 1
  dacc_set_analog_control(DACC, DACC_ACR_IBCTLCH0(0x02) | DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01));
  dacc_set_trigger(DACC, 1);
  
//  dacc_set_channel_selection(DACC, 1);
  //dacc_enable_channel(DACC, 1);
  dacc_set_channel_selection(DACC, 0);
  dacc_enable_channel(DACC, 0);

  NVIC_DisableIRQ(DACC_IRQn);
  NVIC_ClearPendingIRQ(DACC_IRQn);
  NVIC_EnableIRQ(DACC_IRQn);
  dacc_enable_interrupt(DACC, DACC_IER_ENDTX);

  DACC->DACC_TPR  =  (uint32_t)  Sinewave[fast_mode][0];      // DMA buffer
  DACC->DACC_TCR  =  NWAVE;
  DACC->DACC_TNPR =  (uint32_t)  Sinewave[fast_mode][1];      // next DMA buffer
  DACC->DACC_TNCR =  NWAVE;
  DACC->DACC_PTCR =  0x00000100;  //TXTEN - 8, RXTEN - 1.
}

The code runs continuously, but can’t generate exact frequency, there is always
some offset, that is increasing for higher frequencies.
In piezo-tester code I change algorithm, now generator outputs “bursts” of
frequencies, with high precision, phase correct and “clipped” at both ends.

Main sketch:

#include <UTFT.h>
#include <UTouch.h>
#include <SplitRadixReal.h>

#define  SMP_RATE         480000UL 
#define  CLK_MAIN       84000000UL
#define  TMR_CNTR       CLK_MAIN / (2 *SMP_RATE)

// FFT_SIZE IS DEFINED in Header file SplitRadixReal.h 
// #define  FFT_SIZE           2048

#define  MIRROR         FFT_SIZE / 2
#define  INP_BUFF       FFT_SIZE * 2

volatile int16_t        flag              =   0 ;
         uint16_t       adcIn[INP_BUFF]   = { 0};     
          int16_t       dacOut[FFT_SIZE]  = { 0};     

         int            fr[2][FFT_SIZE]   = { 0};

         int            temp[MIRROR]      = { 0};     // Magnitudes
         int            magn[MIRROR]      = { 0};     // Magnitudes
         int            phas[MIRROR]      = { 0};     // Phases
         float          resonance         =  1.0;     // Resonance based on phi = 0.

const    int            dc_offset         = 2047;
         int            debug_osm         =    0;     // degug over serial monitor 

const    float          fix_hardw         =  0.5;     // TDA7052 gain correction coefficient.
               
         SplitRadixReal radix;
         UTFT           myGLCD( ILI9486, 38, 39, 40, 41);
         UTouch         myTouch( 6, 5, 4, 3, 2);

extern   uint8_t        BigFont[];

         int            x, y, dispx, dispy, sizeX, sizeY;
         
         int            xyButtons[5][4]   = {{ 0}};
const    int            widthBtns         =     80;
const    int            spaceBtns         =      8;
         int            in_number         =      0;

         int32_t        startScan         =     10;
         int32_t        stop_Scan         =    100;

         int            activ_set         =      0;
         int            adjus_set         =      1;
 
void setup() {
  Serial.begin(115200);
// 
  adc_setup();         
  dac_setup();        
  tmr_setup();         
//
  myGLCD.InitLCD();
  myGLCD.clrScr();
  myGLCD.setFont(BigFont);
  myGLCD.setBackColor(0, 0, 255);

  myTouch.InitTouch();
  myTouch.setPrecision(PREC_MEDIUM);

  dispx = myGLCD.getDisplayXSize();
  dispy = myGLCD.getDisplayYSize();
  sizeX = dispx - widthBtns - (2 * spaceBtns); // ~400 pix.
  sizeY = dispy - 1;

  initButtons();  

  for( int i = 0; i < MIRROR; i++) { // DEMO
    int k = 10;
    magn[i]  =  (int) round(127 * cos ( k * 2 * 3.14 * i/ INP_BUFF));
    magn[i] += 127;   
    phas[i]  =  (int) round(127 * sin ( k * 2 * 3.14 * i/ INP_BUFF));
    }  
  Refresh();
//  sendFrequency(startScan * 1e3);
  sendFrequency(startScan * 1000.0);
}

void loop() {

   if (myTouch.dataAvailable()) {
      myTouch.read();
      x = myTouch.getX();
      y = myTouch.getY();
 
      int Btn =  whatButtons( x, y);
      int holdb = 0;
      
      if(Btn >= 0) {
        holdb = presButtons( Btn);
      }
      if( Btn == 0 ) {
        if( activ_set == 0 ) {
          startScan += adjus_set;
          int32_t limit = stop_Scan - 1;
          if( startScan > limit ) startScan = limit;
          }
        if( activ_set == 1 ) {
          stop_Scan += adjus_set;
          if( stop_Scan >  1000 ) stop_Scan =  1000;
          }
        }

      if( Btn == 1 ) {
        if( activ_set == 0 ) {
          startScan -= adjus_set;
          if( startScan <     1 ) startScan =     1;
          }
        if( activ_set == 1 ) {
          stop_Scan -= adjus_set;
          int32_t limit = startScan + 1;
          if( stop_Scan < limit ) stop_Scan = limit;
          }
        }

      if( Btn == 2 ) {
          scan_fft();
        }
      
      if(((Btn == 3) || (Btn == 4)) && (holdb == 0)) {
        if( ++activ_set > 1 ) activ_set = 0;
        }

      draw_Infor();   

      if(debug_osm) {
        Serial.print("\n");
        Serial.print("\tX = ");
        Serial.print( x, DEC);
        Serial.print("\tY = ");
        Serial.print( y, DEC);
        Serial.print("\tBtn = ");
        Serial.print( Btn, DEC);
        }   
      }

  while (Serial.available()) {
    uint8_t input = Serial.read();
      switch (input) {
        case '\r':
            break;
        case '\n':
            break;
        case 'x':
            debug_osm = 1 - debug_osm;
            if (debug_osm) Serial.println(F("\n\tDebug activated"));
            else           Serial.println(F("\n\tDebug de-activ."));
            break;
        case 'q':                            // Print dacOut. 0
            prnt_out2(dacOut, FFT_SIZE);
            break;
        case 'p':                            // Print Ch. 0
            prnt_out1(adcIn, MIRROR, 0);
            break;
        case 'o':                            // Print Ch. 1
            prnt_out1(adcIn, MIRROR, 1);
            break;
        case 'd':                            // Profiling-Scanner
          for (int i = 0; i < 5; i++){
            Serial.print("\n");
            for (int j = 0; j < 4; j++){
              Serial.print("\t");
              Serial.print(xyButtons[i][j], DEC);
              }
            }
          break;
        case '?':
        case 'h':
  //        cmd_print_help();
          break;
          default: // -------------------------------
          if ((input >= '0') && (input <= '9')) {
            in_number = (in_number * 10) + (input - '0');
            }
          else {
              if (input == '.') {
//                sendFrequency(in_number * 1e3);
                sendFrequency(in_number * 1000.0);
                  Serial.print("\n\tFreq. set to: ");
                  Serial.print(in_number, DEC);
                  Serial.print(" kHz.");
                in_number = 0;
                restart_sampl();
                }
               else {
                  Serial.print("Unexpected: ");
                  Serial.println((char)input);
                  } // else, esli ne ','
               } // else, esli ne cifra
          }
     }
}

void prnt_out2( int16_t *array, int dlina)
{
  Serial.print("\n\t");
  for ( uint32_t i = 0; i < dlina; i++) {
    Serial.print(array[i], DEC);
    Serial.print("\t");
    if ((i+1) % 32 == 0) Serial.print("\n\t");
    }
}

void prnt_out1( uint16_t *array, int dlina, int channel)
{
  if ((channel < 0) || (channel > 1)) channel = 0;
  Serial.print("\n\t");
  for ( uint32_t i = channel; i < dlina; i += 2) {
    Serial.print(array[i] - dc_offset);
    Serial.print("\t");
    if ((i + 2 - channel) % 64 == 0) Serial.print("\n\t");
    }
}

DDS:

void sendFrequency(float frequency) {

  const float  smp_real = CLK_MAIN / (2 * ((int)TMR_CNTR));
  const float  smp_koef = ((float) FFT_SIZE) / smp_real;

  float freq2 = frequency * smp_koef;

  for( int i = 0; i < MIRROR; i++) {
    int temp = (int) round( 2047 *sin( i *freq2 *2 *3.1415926535 /FFT_SIZE));
        // PHASE CORRECT SINEWAVE !!!
        uint16_t indx_a = MIRROR + i;
        uint16_t indx_b = MIRROR - i;
        dacOut[indx_a] =  temp;
        dacOut[indx_b] = -temp;
      }

   for ( uint16_t i = 0, k = (NWAVE / FFT_SIZE); i < FFT_SIZE; i++ ) {
     uint16_t windw = Hamming[i * k];
     int temp = dacOut[i];
         temp = mult_shft12(temp, windw);
         temp *= fix_hardw;
         dacOut[i] = temp + 2048; 
   } 
}

void dac_setup ()
{
  pmc_enable_periph_clk (DACC_INTERFACE_ID) ; // start clocking DAC
  dacc_reset(DACC);
  dacc_set_transfer_mode(DACC, 0);
  dacc_set_power_save(DACC, 0, 1);            // sleep = 0, fastwkup = 1
  dacc_set_analog_control(DACC, DACC_ACR_IBCTLCH0(0x02) | DACC_ACR_IBCTLCH1(0x02) | DACC_ACR_IBCTLDACCORE(0x01));

  DACC->DACC_MR = (DACC->DACC_MR & 0xFFFFFFFE);    //DISABLE
  dacc_set_trigger(DACC, 1); // TIMER = 1 (0,1,2).
  //  dacc_set_channel_selection(DACC, 1);
  //  dacc_enable_channel(DACC, 1);
  dacc_set_channel_selection(DACC, 0);
  dacc_enable_channel(DACC, 0);
  NVIC_DisableIRQ(DACC_IRQn);
  NVIC_ClearPendingIRQ(DACC_IRQn);
//  NVIC_EnableIRQ(DACC_IRQn);
//  dacc_enable_interrupt(DACC, DACC_IER_ENDTX);

  DACC->DACC_TPR  =  (uint32_t)  dacOut;      // DMA buffer
  DACC->DACC_TCR  =  FFT_SIZE;
//  DACC->DACC_TNPR =  (uint32_t)  Sinewave[fast_mode][1];      // next DMA buffer
//  DACC->DACC_TNCR =  NWAVE;
  DACC->DACC_PTCR =  0x00000100;  //TXTEN - 8, RXTEN - 1.
}

Display:

void   Refresh()
{
  myGLCD.clrScr();
  drawButtons();
  draw_Table();
  draw_Chart();   
  draw_Infor();   
}

void   draw_Infor()
{
    myGLCD.setColor( 0, 255, 255);
    myGLCD.print("Resonance:", 15, 288);
    myGLCD.printNumF( resonance, 3, 215, 288);
    myGLCD.print("kHz.", 330, 288);  

    myGLCD.setColor( 255, 255, 255);
    myGLCD.fillRect( 15, 12, 384, 34);

    myGLCD.setColor( 255,   0, 255);
    myGLCD.print("Start:",  15, 15);
    myGLCD.print("Stop :", 215, 15);  

    if( activ_set == 0 ) myGLCD.setBackColor( 150, 150, 150);
    else                 myGLCD.setBackColor( 255, 255, 255);
    myGLCD.printNumI( startScan, 120, 15);    

    if( activ_set == 1 ) myGLCD.setBackColor( 150, 150, 150);
    else                 myGLCD.setBackColor( 255, 255, 255);
    myGLCD.printNumI( stop_Scan, 320, 15);    

    myGLCD.setBackColor( 255, 255, 255);
    myGLCD.setColor(  0, 255, 255);
    
    if( activ_set == 0 ) {
      if(adjus_set ==  1) myGLCD.drawRect( 168, 12, 184, 34 );
      if(adjus_set == 10) myGLCD.drawRect( 152, 12, 168, 34 );
      if(adjus_set ==100) myGLCD.drawRect( 136, 12, 152, 34 );
      }
    if( activ_set == 1 ) {
      if(adjus_set ==  1) myGLCD.drawRect( 368, 12, 384, 34 );
      if(adjus_set == 10) myGLCD.drawRect( 352, 12, 368, 34 );
      if(adjus_set ==100) myGLCD.drawRect( 336, 12, 352, 34 );
      }
}  

void   draw_Chart()
{
  int offsetY1 = 36 + 15; //sizeY -15;
  int offsetY2 = sizeY / 2;
  
  myGLCD.setColor(  0,255,255);
  for (int i = 1, p = 0; i < sizeX; i++, p++)
  {
    myGLCD.drawLine( p, offsetY1 + magn[p], i, offsetY1 + magn[i]);
  }
  myGLCD.setColor(255,  0,255);
  for (int i = 1, p = 0; i < sizeX; i++, p++)
  {
    myGLCD.drawLine( p, offsetY2 + phas[p], i, offsetY2 + phas[i]);
  }
}

void  draw_Table()
{
    myGLCD.setColor( 150, 150, 150);
   
    for ( int y = 10; y < sizeY; y += 36) {
      for ( int x = 10; x < (sizeX -12); x += 6) {
        myGLCD.drawPixel( x, y);
      }
    }
    for ( int x = 10; x < sizeX; x += 36) {
      for (int y = 10; y < (sizeY - 6); y +=  6) {
        myGLCD.drawPixel( x, y);
      }
    }
}

void initButtons()
{
  int cl, cr, cd, cu;

      cl = dispx - 1 - widthBtns;
      cr = dispx - 1 - spaceBtns;
  
  int heigE = (dispy - spaceBtns) /5;
  int heigB =  heigE - spaceBtns;
  
  for (int i = 0; i < 5; i++)
  {   
    cu = ( i * heigE) + spaceBtns;
    cd = cu + heigB;
    
    xyButtons[i][0] = cl;
    xyButtons[i][1] = cr;
    xyButtons[i][2] = cu;
    xyButtons[i][3] = cd;
  }
}

void drawButtons()
{
    int crx, cry;

        crx = dispx - 1;
        cry = dispy - 1;
    
        myGLCD.setColor(255, 255, 255);
        myGLCD.fillRect( 0, 0, crx, cry);
  
  for ( int i = 0; i < 5; i++) {
    myGLCD.setColor(0, 0, 255);
    myGLCD.fillRoundRect(xyButtons[i][0], xyButtons[i][2], xyButtons[i][1], xyButtons[i][3]);
    myGLCD.setColor(255, 255, 255);
    myGLCD.drawRoundRect(xyButtons[i][0], xyButtons[i][2], xyButtons[i][1], xyButtons[i][3]);
    }

    myGLCD.setColor( 0, 255, 0);
    myGLCD.setBackColor( 0,  0, 255);
    myGLCD.print("UP", xyButtons[0][0] +22, xyButtons[0][2] +22);
    myGLCD.print("DN", xyButtons[1][0] +22, xyButtons[1][2] +22);
    myGLCD.print("OK", xyButtons[2][0] +22, xyButtons[2][2] +22);
    myGLCD.print("LF", xyButtons[3][0] +22, xyButtons[3][2] +22);
    myGLCD.print("RH", xyButtons[4][0] +22, xyButtons[4][2] +22);
}

int presButtons(int n)
{
  int hold = 0;
  myGLCD.setColor(255, 0, 0);
  myGLCD.drawRoundRect(xyButtons[n][0], xyButtons[n][2], xyButtons[n][1], xyButtons[n][3]);

  long stampA = millis();
  while (myTouch.dataAvailable())
    myTouch.read();
  long stampB = millis();
  int time = stampB - stampA;
    if((time > 500) && (n == 3)) {
      adjus_set *= 10;
      if( adjus_set > 100 ) adjus_set = 100;
      hold = 1;
    }
    if((time > 500) && (n == 4)) {
      adjus_set /= 10;
      if( adjus_set < 1 ) adjus_set = 1;
      hold = 1;
    }
  myGLCD.setColor(255, 255, 255);
  myGLCD.drawRoundRect(xyButtons[n][0], xyButtons[n][2], xyButtons[n][1], xyButtons[n][3]);

  return hold;
}

int whatButtons( int cx, int cy)
{
  int Btn = -1;

  for (int i = 0; i < 5; i++)
  {   
    if((cx > xyButtons[i][0]) && \
       (cx < xyButtons[i][1]) && \
       (cy > xyButtons[i][2]) && \
       (cy < xyButtons[i][3]))
       Btn = i; 
  }
  return Btn;
}

Scan_FFT:

void scan_fft()
{
  const  float  phas_corr = -0.850059032;
  double resol  = ((double)(stop_Scan - startScan)) / sizeX;
  double setfr  = startScan * 1000UL;
         resol *= 1000; // in kHz

  int    minim  =  900;  

  for (int i = 0; i < sizeX; i++) {
    setfr += resol;
    sendFrequency(setfr);
    //    delay(2);
    restart_sampl();

    int   prom_magn[2] = {   0};
    float prom_phas[2] = { 0.0};
    int   peak = 1;

    if(debug_osm) {
      Serial.print("\n\tFr: ");
      Serial.print( setfr, 1);
      }
    
    for( uint32_t y = 0; y < 2; y++) {
         radix.rev_bin( fr[y], FFT_SIZE);
         radix.fft_split_radix_real( fr[y], LOG2_FFT);
         //    radix.gain_Reset( fr[y], LOG2_FFT -1);
         radix.gain_Reset( fr[y], 5);
         } 

         radix.get_Magnit1( fr[0], temp);
         peak = peak_search( temp, MIRROR);
         prom_magn[0] = temp[peak];
         prom_phas[0] = get_Phase( fr[0], peak);         

         radix.get_Magnit1( fr[1], temp);
         prom_magn[1] = temp[peak];
         prom_phas[1] = get_Phase( fr[1], peak);         

    magn[i] = (prom_magn[1] - prom_magn[0]) / 4; // ~1200 amplit to 300 - screen size
    magn[i] /= 32;  // Compenstaion for less gain_reset
    magn[i] *= -1;  // Inverse for display, Attenuation.
    
    prom_phas[1] += (peak * phas_corr);    
    int tmpr = prom_phas[1] - prom_phas[0];
    if( tmpr >  1800 ) tmpr -= 3600;
    if( tmpr < -1800 ) tmpr += 3600; 

        if(debug_osm) {
           Serial.print("\tCh0: ");
           Serial.print("\tPk: ");
           Serial.print( peak, DEC);
           Serial.print("\tM: ");
           Serial.print( prom_magn[0], DEC);
           Serial.print("\tP: ");
           Serial.print( prom_phas[0], 3);
           Serial.print("\tCh1: ");
           Serial.print("\tM: ");
           Serial.print( prom_magn[1], DEC);
           Serial.print("\tP: ");
           Serial.print( prom_phas[1], 3);
           delay(20);
           Serial.print("\tD: ");
           Serial.print( tmpr, DEC);
          }
    int tmp2 = abs(tmpr);
    if(abs(tmp2) < minim) {
      resonance =  setfr / 1000;             // in kHz
      minim = tmp2;
      }
    tmpr /= 6;                               // ~1800 phase to 300 - screen size
    phas[i] = tmpr;
  }    
  Refresh();
}

inline int mult_shft12( int a, int b)
{
  return (( a  *  b )  >> 12);
}

void restart_sampl( void )
{
   flag = 1;
   adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0);

   dacc_set_trigger(DACC, 1); // TIMER = 1 (0,1,2).
   DACC->DACC_TPR  =  (uint32_t)  dacOut;      // DMA buffer
   DACC->DACC_TCR  =  FFT_SIZE;
   DACC->DACC_PTCR =  0x00000100;  // Must BE
    
   while ( flag ) {} // wait new pull

   uint16_t indx_b = 0;
   for( uint16_t i = 0; i < FFT_SIZE; i++ ) {
       fr[0][i] = adcIn[indx_b++] - dc_offset;
       fr[1][i] = adcIn[indx_b++] - dc_offset;
     }
}

void tmr_setup ()
{
  pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0); // clock the TC0 channel 0

  TcChannel * t = &(TC0->TC_CHANNEL)[0] ;            // pointer to TC0 registers for its channel 0
  t->TC_CCR = TC_CCR_CLKDIS ;                        // disable internal clocking while setup regs
  t->TC_IDR = 0xFFFFFFFF ;                           // disable interrupts
  t->TC_SR ;                                         // read int status reg to clear pending
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |           // use TCLK1 (prescale by 2, = 42MHz)
              TC_CMR_WAVE |                          // waveform mode
              TC_CMR_WAVSEL_UP_RC |                  // count-up PWM using RC as threshold
              TC_CMR_EEVT_XC0 |     // Set external events from XC0 (this setup TIOB as output)
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
  
  t->TC_RC = TMR_CNTR;              // counter resets on RC, so sets period in terms of 42MHz clock
  t->TC_RA = TMR_CNTR /2;           // roughly square wave
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from RA and RC compares
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch to hardware trigger source.
}

void adc_setup ()
{
  pmc_enable_periph_clk(ID_ADC);
  //  adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
  adc_init(ADC, SystemCoreClock, 21000000UL, ADC_STARTUP_FAST);
  NVIC_EnableIRQ (ADC_IRQn);                   // enable ADC interrupt vector
  adc_disable_all_channel(ADC);
  adc_enable_interrupt(ADC, ADC_IER_RXBUFF);
//  ADC->ADC_RPR  =  (uint32_t)  inp;            // DMA buffer
  //ADC->ADC_RCR  =  INP_BUFF;
  ADC->ADC_RPR  =  (uint32_t)  adcIn;      // DMA buffer
  ADC->ADC_RCR  =  INP_BUFF;

  ADC->ADC_PTCR =  1;
  adc_set_bias_current(ADC, 0x01);
  //  adc_enable_tag(ADC);
  adc_enable_channel(ADC, ADC_CHANNEL_7);        // AN0
  adc_enable_channel(ADC, ADC_CHANNEL_6);        // AN1
//  adc_enable_channel(ADC, ADC_CHANNEL_5);      // AN2
  //adc_enable_channel(ADC, ADC_CHANNEL_4);      // AN3
  ADC->ADC_MR = (ADC->ADC_MR & 0xFFFFFFFE);      //DISABLE
  adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0);
  //  adc_start(ADC);
}

void ADC_Handler (void)
{
  if ((adc_get_status(ADC) & ADC_ISR_RXBUFF) ==	ADC_ISR_RXBUFF) {
    ADC->ADC_MR = (ADC->ADC_MR & 0xFFFFFFFE);    // TRGEN - stop trigger
    ADC->ADC_RPR  =  (uint32_t)  adcIn;          // DMA buffer
    ADC->ADC_RCR  =  INP_BUFF;

    flag = 0;

    DACC->DACC_MR   = (DACC->DACC_MR & 0xFFFFFFFE);  // DISABLE
    }
}

Supplementary:

int peak_search( int *inTemp, int16_t inSize)
{
  int maxim = 0;
  int index = 1;
  
  for( uint16_t i = 1; i < inSize; i++ ) { // DC-off
    int temp = inTemp[i];
    if( temp > maxim ) {
      maxim = temp;
      index = i;
    }
  }
  return index;
}

float get_Phase(int *fr, int n_bin)
{ // Shkala x10, t.e. +-180 = +- 1800, resolution 0.1 degree
        int real = fr[n_bin];
        int imag = fr[FFT_SIZE -n_bin];
        int Phase = 10  * RAD_CONV * atan2((float) imag, (float) real);
        return Phase;
} 

Finished…



		
 
Leave a comment

Posted by on December 26, 2014 in Uncategorized

 

LCQ Meter / Amplitude-Phase Characterizer / Piezo tester.

After completing “Ultrasonic Radar” project, I was wander how common ultrasonic sensor reacts to “out of band” signals, that are not equal to default 40 kHz. Thinking for awhile, I came up to idea create a project, which could measure a frequency response of the sensor in wide frequency range, and plot two charts, amplitude and phase. In short – “Piezo tester”. Of course, device could measure amplitude – phase characteristic of anything, in particular, audio filters, speakers, RF circuits etc. And connecting inductor or capacitor with known “calibrated” values in series with DUT, you can easily calculate LCQ parameters. Sure, mr. Arduino can to do it for you,  so this part of the code isn’t written yet.

 Discoveries I’ve made so far:

1. Regular piezo sensor (extracted from cheap ultrasonic range meter) has  one more resonance about 54-58 kHz, it’s x1.3 times higher than “default” 38-42 kHz commonly used frequency. Test was run from 30 to 70 kHz.

2. One out of 4 sensors I tested, has “abnormal” two-peaks shape resonance at that frequency. Look at the pictures.

 Part list, main components:

  1. Arduino DUE
  2. AD9850 DDS module
  3. 3.5′  TFT shield
  4. MCP6022
  5. Resistors, capacitors according to drawings.

Piezo_tester I don’t think schematic requires any comments, OPA is just a buffer, both outputs preset to 1.65V and about x3 gain. MCP6022 has 10 MHz cut-off, and rail-to-rail capability, this has to be taken into account in case of replacement.

Software.

 New FFT library  calculates data,  and keeps track on all data flow starting from ADC.  UTFT and UTouch libraries serve as hardware interface to the TFT display module, plus HMI for operator / user.  I also borrow small piece of code to communicate with AD9850.  Here is main sketch:

#include <UTFT.h>
#include <UTouch.h>
#include <SplitRadixReal.h>

#define  W_CLK  8       // Pin  8 - connect to AD9850 module word load clock pin (CLK)
#define  FQ_UD  9       // Pin  9 - connect to freq update pin (FQ)
#define  DATAS 10       // Pin 10 - connect to serial DATAS load pin (DATAS)
#define  RESET 11       // Pin 11 - connect to reset pin (RST).

#define  pulseHigh(pin) {digitalWrite(pin, HIGH); digitalWrite(pin, LOW); }

#define  SMP_RATE         480000UL 
#define  CLK_MAIN       84000000UL
#define  TMR_CNTR       CLK_MAIN / (2 *SMP_RATE)

// FFT_SIZE IS DEFINED in Header file SplitRadixReal.h 
// #define  FFT_SIZE           2048

#define  MIRROR         FFT_SIZE / 2
#define  INP_BUFF       FFT_SIZE * 2

volatile int16_t        flag              =   0 ;
         uint16_t       adcIn[INP_BUFF]   = { 0};     

         int            fr[2][FFT_SIZE]   = { 0};

         int            temp[MIRROR]      = { 0};     // Magnitudes
         int            magn[MIRROR]      = { 0};     // Magnitudes
         int            phas[MIRROR]      = { 0};     // Phases
         float          resonance         =  1.0;     // Resonance based on phi = 0.

const    int            dc_offset         = 2047; 

         int            debug_osm         =    0;     // degug over serial monitor
               
         SplitRadixReal radix;

UTFT     myGLCD( ILI9486, 38, 39, 40, 41);
UTouch   myTouch( 6, 5, 4, 3, 2);

extern   uint8_t  BigFont[];

         int      x, y, dispx, dispy, sizeX, sizeY;
         
         int            xyButtons[5][4]   = {{ 0}};
const    int            widthBtns         =     80;
const    int            spaceBtns         =      8;
         int            in_number         =      0;

         int32_t        startScan         =     10;
         int32_t        stop_Scan         =    100;

         int            activ_set         =      0;
         int            adjus_set         =      1;
 
void setup() {
  Serial.begin(115200);
// 
  adc_setup ();         
  tmr_setup ();         
//  pinMode( 2, INPUT); 
//
  pinMode(FQ_UD, OUTPUT);
  pinMode(W_CLK, OUTPUT);
  pinMode(DATAS, OUTPUT);
  pinMode(RESET, OUTPUT);
   
  pulseHigh(RESET);
  pulseHigh(W_CLK);
  pulseHigh(FQ_UD);  // this pulse enables serial mode - DATASsheet page 12 figure 10

  sendFrequency(startScan * 1e3);
//  
  myGLCD.InitLCD();
  myGLCD.clrScr();
  myGLCD.setFont(BigFont);
  myGLCD.setBackColor(0, 0, 255);

  myTouch.InitTouch();
  myTouch.setPrecision(PREC_MEDIUM);

  dispx = myGLCD.getDisplayXSize();
  dispy = myGLCD.getDisplayYSize();
  sizeX = dispx - widthBtns - (2 * spaceBtns); // ~400 pix.
  sizeY = dispy - 1;

  initButtons();  

  for( int i = 0; i < MIRROR; i++) { // DEMO
    int k = 10;
    magn[i]  =  (int) round(127 * cos ( k * 2 * 3.14 * i/ INP_BUFF));
    magn[i] += 127;   
    phas[i]  =  (int) round(127 * sin ( k * 2 * 3.14 * i/ INP_BUFF));
    }  
  Refresh();
}

void loop() {

   if (myTouch.dataAvailable()) {
      myTouch.read();
      x = myTouch.getX();
      y = myTouch.getY();
 
      int Btn =  whatButtons( x, y);
      int holdb = 0;
      
      if(Btn >= 0) {
        holdb = presButtons( Btn);
      }
      if( Btn == 0 ) {
        if( activ_set == 0 ) {
          startScan += adjus_set;
          int32_t limit = stop_Scan - 1;
          if( startScan > limit ) startScan = limit;
          }
        if( activ_set == 1 ) {
          stop_Scan += adjus_set;
          if( stop_Scan >  1000 ) stop_Scan =  1000;
          }
        }

      if( Btn == 1 ) {
        if( activ_set == 0 ) {
          startScan -= adjus_set;
          if( startScan <     1 ) startScan =     1;
          }
        if( activ_set == 1 ) {
          stop_Scan -= adjus_set;
          int32_t limit = startScan + 1;
          if( stop_Scan < limit ) stop_Scan = limit;
          }
        }

      if( Btn == 2 ) {
          scan_fft();
        }
      
      if(((Btn == 3) || (Btn == 4)) && (holdb == 0)) {
        if( ++activ_set > 1 ) activ_set = 0;
        }
      //  Refresh();  - vspuxivaet ekran
      draw_Infor();   

      if(debug_osm) {
        Serial.print("\n");
        Serial.print("\tX = ");
        Serial.print( x, DEC);
        Serial.print("\tY = ");
        Serial.print( y, DEC);
        Serial.print("\tBtn = ");
        Serial.print( Btn, DEC);
        }   
      }

  while (Serial.available()) {
    uint8_t input = Serial.read();
      switch (input) {
        case '\r':
            break;
        case '\n':
            break;
        case 'x':
            debug_osm = 1 - debug_osm;
            if (debug_osm) Serial.println(F("\n\tDebug activated"));
            else           Serial.println(F("\n\tDebug de-activ."));
            break;
        case 'p':                            // Print Ch. 0
            prnt_out1(adcIn, MIRROR, 0);
            break;
        case 'o':                            // Print Ch. 1
            prnt_out1(adcIn, MIRROR, 1);
            break;
        case 'd':                            // Profiling-Scanner
          for (int i = 0; i < 5; i++){
            Serial.print("\n");
            for (int j = 0; j < 4; j++){
              Serial.print("\t");
              Serial.print(xyButtons[i][j], DEC);
              }
            }
          break;
        case '?':
        case 'h':
  //        cmd_print_help();
          break;
          default: // -------------------------------
          if ((input >= '0') && (input <= '9')) {
            in_number = (in_number * 10) + (input - '0');
            }
          else {
              if (input == '.') {
                sendFrequency(in_number * 1e3);
                  Serial.print("\n\tFreq. set to: ");
                  Serial.print(in_number, DEC);
                  Serial.print(" kHz.");
                in_number = 0;
                }
               else {
                  Serial.print("Unexpected: ");
                  Serial.println((char)input);
                  } // else, esli ne ','
               } // else, esli ne cifra
          }
     }
}

void prnt_out1( uint16_t *array, int dlina, int channel)
{
  if ((channel < 0) || (channel > 1)) channel = 0;
  Serial.print("\n\t");
  for ( uint32_t i = channel; i < dlina; i += 2) {
    Serial.print(array[i] - dc_offset);
    Serial.print("\t");
    if ((i + 2 - channel) % 64 == 0) Serial.print("\n\t");
    }
}

DDS Module AD9850:

void tfr_byte(byte inpd)
{
  for( int i = 0; i < 8; i++, inpd >>= 1) {
    digitalWrite(DATAS, inpd & 0x01);
    pulseHigh(W_CLK);   //after each bit sent, CLK is pulsed high
  }
}
// frequency calc from DATASsheet page 8 = <sys clock> * <frequency tuning word>/2^32
void sendFrequency(double frequency) {
  int32_t freq = frequency * 4294967295 /125000000;  // note 125 MHz clock on 9850
  for( int b = 0; b < 4; b++, freq >>= 8) {
    tfr_byte(freq & 0xFF);
    }
  tfr_byte(0x000);   // Final control byte, all 0 for 9850 chip
  pulseHigh(FQ_UD);  // Done!  Should see output
}

TFT display part:

void   Refresh()
{
  myGLCD.clrScr();
  drawButtons();
  draw_Table();
  draw_Chart();   
  draw_Infor();   
}

void   draw_Infor()
{
    myGLCD.setColor( 0, 255, 255);
    myGLCD.print("Resonance:", 15, 288);
    myGLCD.printNumF( resonance, 3, 215, 288);
    myGLCD.print("kHz.", 330, 288);  

    myGLCD.setColor( 255, 255, 255);
    myGLCD.fillRect( 15, 12, 384, 34);

    myGLCD.setColor( 255,   0, 255);
    myGLCD.print("Start:",  15, 15);
    myGLCD.print("Stop :", 215, 15);  

    if( activ_set == 0 ) myGLCD.setBackColor( 150, 150, 150);
    else                 myGLCD.setBackColor( 255, 255, 255);
    myGLCD.printNumI( startScan, 120, 15);    

//    myGLCD.setColor( 255,   0, 255);
    if( activ_set == 1 ) myGLCD.setBackColor( 150, 150, 150);
    else                 myGLCD.setBackColor( 255, 255, 255);
    myGLCD.printNumI( stop_Scan, 320, 15);    

    myGLCD.setBackColor( 255, 255, 255);
    myGLCD.setColor(  0, 255, 255);
    
    if( activ_set == 0 ) {
      if(adjus_set ==  1) myGLCD.drawRect( 168, 12, 184, 34 );
      if(adjus_set == 10) myGLCD.drawRect( 152, 12, 168, 34 );
      if(adjus_set ==100) myGLCD.drawRect( 136, 12, 152, 34 );
      }
    if( activ_set == 1 ) {
      if(adjus_set ==  1) myGLCD.drawRect( 368, 12, 384, 34 );
      if(adjus_set == 10) myGLCD.drawRect( 352, 12, 368, 34 );
      if(adjus_set ==100) myGLCD.drawRect( 336, 12, 352, 34 );
      }
}  

void   draw_Chart()
{
  int offsetY1 = 36 + 15; //sizeY -15;
  int offsetY2 = sizeY / 2;
  
  myGLCD.setColor(  0,255,255);
  for (int i = 1, p = 0; i < sizeX; i++, p++)
  {
//    myGLCD.drawPixel( i, offsetY + inputData[0][i]);
//    myGLCD.drawLine( p, offsetY1 - magn[p], i, offsetY1 - magn[i]);
    myGLCD.drawLine( p, offsetY1 + magn[p], i, offsetY1 + magn[i]);
  }
  myGLCD.setColor(255,  0,255);
  for (int i = 1, p = 0; i < sizeX; i++, p++)
  {
 //   myGLCD.drawPixel( i, offsetY + inputData[1][i]);
    myGLCD.drawLine( p, offsetY2 + phas[p], i, offsetY2 + phas[i]);
  }
}

void  draw_Table()
{
    myGLCD.setColor( 150, 150, 150);
   
    for ( int y = 10; y < sizeY; y += 36) {
      for ( int x = 10; x < (sizeX -12); x += 6) {
        myGLCD.drawPixel( x, y);
      }
    }
    for ( int x = 10; x < sizeX; x += 36) {
      for (int y = 10; y < (sizeY - 6); y +=  6) {
        myGLCD.drawPixel( x, y);
      }
    }
}

void initButtons()
{
  int cl, cr, cd, cu;

      cl = dispx - 1 - widthBtns;
      cr = dispx - 1 - spaceBtns;
  
  int heigE = (dispy - spaceBtns) /5;
  int heigB =  heigE - spaceBtns;
  
  for (int i = 0; i < 5; i++)
  {   
    cu = ( i * heigE) + spaceBtns;
    cd = cu + heigB;
    
    xyButtons[i][0] = cl;
    xyButtons[i][1] = cr;
    xyButtons[i][2] = cu;
    xyButtons[i][3] = cd;
  }
}

void drawButtons()
{
    int crx, cry;

        crx = dispx - 1;
        cry = dispy - 1;
    
        myGLCD.setColor(255, 255, 255);
        myGLCD.fillRect( 0, 0, crx, cry);
  
  for ( int i = 0; i < 5; i++) {
    myGLCD.setColor(0, 0, 255);
    myGLCD.fillRoundRect(xyButtons[i][0], xyButtons[i][2], xyButtons[i][1], xyButtons[i][3]);
    myGLCD.setColor(255, 255, 255);
    myGLCD.drawRoundRect(xyButtons[i][0], xyButtons[i][2], xyButtons[i][1], xyButtons[i][3]);
    }

    myGLCD.setColor( 0, 255, 0);
    myGLCD.setBackColor( 0,  0, 255);
    myGLCD.print("UP", xyButtons[0][0] +22, xyButtons[0][2] +22);
    myGLCD.print("DN", xyButtons[1][0] +22, xyButtons[1][2] +22);
    myGLCD.print("OK", xyButtons[2][0] +22, xyButtons[2][2] +22);
    myGLCD.print("LF", xyButtons[3][0] +22, xyButtons[3][2] +22);
    myGLCD.print("RH", xyButtons[4][0] +22, xyButtons[4][2] +22);
}

int presButtons(int n)
{
  int hold = 0;
  myGLCD.setColor(255, 0, 0);
  myGLCD.drawRoundRect(xyButtons[n][0], xyButtons[n][2], xyButtons[n][1], xyButtons[n][3]);

  long stampA = millis();
  while (myTouch.dataAvailable())
    myTouch.read();
  long stampB = millis();
  int time = stampB - stampA;
//  if( time > 2000 ) {
//    adjus_set /= 10;
//    if( adjus_set < 1 ) adjus_set = 1;
//    }
//  else {  
    if((time > 500) && (n == 3)) {
      adjus_set *= 10;
      if( adjus_set > 100 ) adjus_set = 100;
      hold = 1;
    }
    if((time > 500) && (n == 4)) {
      adjus_set /= 10;
      if( adjus_set < 1 ) adjus_set = 1;
      hold = 1;
    }
//  }
  myGLCD.setColor(255, 255, 255);
  myGLCD.drawRoundRect(xyButtons[n][0], xyButtons[n][2], xyButtons[n][1], xyButtons[n][3]);

  return hold;
}

int whatButtons( int cx, int cy)
{
  int Btn = -1;

  for (int i = 0; i < 5; i++)
  {   
    if((cx > xyButtons[i][0]) && \
       (cx < xyButtons[i][1]) && \
       (cy > xyButtons[i][2]) && \
       (cy < xyButtons[i][3]))
       Btn = i; 
  }
  return Btn;
}

Sampling & FFT:

void scan_fft()
{
  const  float  phas_corr = -0.850059032; // -0.803571429;
  double resol  = ((double)(stop_Scan - startScan)) / sizeX;
  double setfr  = startScan * 1000UL;
         resol *= 1000; // in kHz

  int    minim  =  900;  

  for (int i = 0; i < sizeX; i++) {
    setfr += resol;
    sendFrequency(setfr);
    //    delay(2);
    restart_sampl();

    int   prom_magn[2] = {   0};
    float prom_phas[2] = { 0.0};
    int   peak = 1;

    if(debug_osm) {
      Serial.print("\n\tFr: ");
      Serial.print( setfr, 1);
      }
    
    for( uint32_t y = 0; y < 2; y++){
         radix.rev_bin( fr[y], FFT_SIZE);
         radix.fft_split_radix_real( fr[y], LOG2_FFT);
         //    radix.gain_Reset( fr[y], LOG2_FFT -1);
         radix.gain_Reset( fr[y], 5);
         } 

         radix.get_Magnit1( fr[0], temp);
         peak = peak_search( temp, MIRROR);
         prom_magn[0] = temp[peak];
         prom_phas[0] = get_Phase( fr[0], peak);         

         radix.get_Magnit1( fr[1], temp);
//         peak = peak_search( temp, MIRROR);         
         prom_magn[1] = temp[peak];
         prom_phas[1] = get_Phase( fr[1], peak);         

    magn[i] = (prom_magn[1] - prom_magn[0]) / 4; // ~1200 amplit to 300 - screen size
    magn[i] /= 32;  // Compenstaion for less gain_reset
    magn[i] *= -1;  // Inverse for display, Attenuation.
    
    prom_phas[1] += (peak * phas_corr);    
    int tmpr = prom_phas[1] - prom_phas[0];
    if( tmpr >  1800 ) tmpr -= 3600;
    if( tmpr < -1800 ) tmpr += 3600; 

        if(debug_osm) {
           Serial.print("\tCh0: ");
           Serial.print("\tPk: ");
           Serial.print( peak, DEC);
           Serial.print("\tM: ");
           Serial.print( prom_magn[0], DEC);
           Serial.print("\tP: ");
           Serial.print( prom_phas[0], 3);
           Serial.print("\tCh1: ");
           Serial.print("\tM: ");
           Serial.print( prom_magn[1], DEC);
           Serial.print("\tP: ");
           Serial.print( prom_phas[1], 3);
           delay(20);
           Serial.print("\tD: ");
           Serial.print( tmpr, DEC);
          }
    int tmp2 = abs(tmpr);
    if(abs(tmp2) < minim) {
      resonance =  setfr / 1000;             // in kHz
      minim = tmp2;
      }
    tmpr /= 6;                               // ~1800 phase to 300 - screen size
    phas[i] = tmpr;
  }    
  Refresh();
}

inline int mult_shft12( int a, int b)
{
  return (( a  *  b )  >> 12);
}

void restart_sampl( void )
{
   flag = 1;
   adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0);
    
   while ( flag ) {} // wait new pull

   uint16_t indx_b = 0;
   for ( uint16_t i = 0, k = (NWAVE / FFT_SIZE); i < FFT_SIZE; i++ )
   {  
     uint16_t windw = Hamming[i * k];
       fr[0][i] = mult_shft12((adcIn[indx_b++] - dc_offset), windw);
       fr[1][i] = mult_shft12((adcIn[indx_b++] - dc_offset), windw);
   }
}

void tmr_setup ()
{
  pmc_enable_periph_clk(TC_INTERFACE_ID + 0 *3 + 0); // clock the TC0 channel 0

  TcChannel * t = &(TC0->TC_CHANNEL)[0] ;            // pointer to TC0 registers for its channel 0
  t->TC_CCR = TC_CCR_CLKDIS ;                        // disable internal clocking while setup regs
  t->TC_IDR = 0xFFFFFFFF ;                           // disable interrupts
  t->TC_SR ;                                         // read int status reg to clear pending
  t->TC_CMR = TC_CMR_TCCLKS_TIMER_CLOCK1 |           // use TCLK1 (prescale by 2, = 42MHz)
              TC_CMR_WAVE |                          // waveform mode
              TC_CMR_WAVSEL_UP_RC |                  // count-up PWM using RC as threshold
              TC_CMR_EEVT_XC0 |     // Set external events from XC0 (this setup TIOB as output)
              TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_CLEAR |
              TC_CMR_BCPB_CLEAR | TC_CMR_BCPC_CLEAR ;
  
  t->TC_RC = TMR_CNTR;              // counter resets on RC, so sets period in terms of 42MHz clock
  t->TC_RA = TMR_CNTR /2;           // roughly square wave
  t->TC_CMR = (t->TC_CMR & 0xFFF0FFFF) | TC_CMR_ACPA_CLEAR | TC_CMR_ACPC_SET ;  // set clear and set from RA and RC compares
  t->TC_CCR = TC_CCR_CLKEN | TC_CCR_SWTRG ;  // re-enable local clocking and switch to hardware trigger source.
}

void adc_setup ()
{
  pmc_enable_periph_clk(ID_ADC);
  //  adc_init(ADC, SystemCoreClock, ADC_FREQ_MAX, ADC_STARTUP_FAST);
  adc_init(ADC, SystemCoreClock, 21000000UL, ADC_STARTUP_FAST);
  NVIC_EnableIRQ (ADC_IRQn);                   // enable ADC interrupt vector
  adc_disable_all_channel(ADC);
  adc_enable_interrupt(ADC, ADC_IER_RXBUFF);
//  ADC->ADC_RPR  =  (uint32_t)  inp;            // DMA buffer
  //ADC->ADC_RCR  =  INP_BUFF;
  ADC->ADC_RPR  =  (uint32_t)  adcIn;      // DMA buffer
  ADC->ADC_RCR  =  INP_BUFF;

  ADC->ADC_PTCR =  1;
  adc_set_bias_current(ADC, 0x01);
  //  adc_enable_tag(ADC);
  adc_enable_channel(ADC, ADC_CHANNEL_7);      // AN0
  adc_enable_channel(ADC, ADC_CHANNEL_6);      // AN1
//  adc_enable_channel(ADC, ADC_CHANNEL_5);      // AN2
  //adc_enable_channel(ADC, ADC_CHANNEL_4);      // AN3
  ADC->ADC_MR = (ADC->ADC_MR & 0xFFFFFFFE);    //DISABLE
  adc_configure_trigger(ADC, ADC_TRIG_TIO_CH_0, 0);
  //  adc_start(ADC);
}

void ADC_Handler (void)
{
  if ((adc_get_status(ADC) & ADC_ISR_RXBUFF) ==	ADC_ISR_RXBUFF) {
    ADC->ADC_MR = (ADC->ADC_MR & 0xFFFFFFFE);  // TRGEN - stop trigger
    ADC->ADC_RPR  =  (uint32_t)  adcIn;          // DMA buffer
    ADC->ADC_RCR  =  INP_BUFF;

    flag = 0;
    }
}

Plus couple supplementary functions:

int peak_search( int *inTemp, int16_t inSize)
{
  int maxim = 0;
  int index = 1;
  
  for( uint16_t i = 1; i < inSize; i++ ) { // DC-off
    int temp = inTemp[i];
    if( temp > maxim ) {
      maxim = temp;
      index = i;
    }
  }
  return index;
}

float get_Phase(int *fr, int n_bin)
{ // Shkala x10, t.e. +-180 = +- 1800, resolution 0.1 degree
        int real = fr[n_bin];
        int imag = fr[FFT_SIZE -n_bin];
        int Phase = 10  * RAD_CONV * atan2((float) imag, (float) real);
        return Phase;
}

   // Time Offset Correction (1 ADC, xx usec delay).
   // 21 MHz, 24 clock = 1.143 usec / 512 (fft.512) x 360.0 = 0.803571429 

  To be continue...
 
8 Comments

Posted by on December 24, 2014 in Uncategorized

 

SplitRadixReal FFT Library.

It’s well known, that running complex fft subroutine in embedded system isn’t rational. Input data have no complex imaginary part, and consequently about 50% of processing power lost for nothing. Another aspect, is memory consumption, complex fft output has 2x redundancy. Solution to this problems is also known, SplitRadix algorithm.  So I did once more same trick, like with Radix-4 code. I take readily available C code  of the SplitRadix, thanks to http://www.jjj.de/  and convert it to arduino library. The most troubling part was to figure out corresponding addresses in the Sinewave LUT, that would substitute integer sine and cosine values.

Difference between SplitRadixReal and Radix4:

  •  SplitRR: 4630 usec.
  •  RDX4:   6968 usec.

New library runs 1.5 times faster. And requires  2x less memory to run.

An example of the application, where saving on memory size doubles spectral resolution (and saves uCPU clock cycles as well), is my latest project Sound Camera,  I discovered that I can’t  get desirable frequency resolution (20 Hz) with my Radix4 fft library in this application. Its simply demands too much memory when fft size 2048,  all DUE 96 kbytes, and my compiler wasn’t agree with that: “section `.bss’ is not within region `ram’ collect2: ld returned 1 exit status”. And it’s on DUE SAM3X8E with its huge memory size, compare, for example, to Maple STM32 and bunch others, similar cortex-3 derivatives.

Seen this before in your sketch:

  • int f_r[FFT_SIZE] = { 0};
    int f_i[FFT_SIZE] = { 0};

Great news, you don’t need  f_i[.] anymore.

And last, re-print :

Created for Arduino DUE & like boards, word size optimized for 12-bits data input.

FFT takes as input ANY size of array 8, 16, 32, 64, 128, 256, 512, 1024, 2048.

Demands 2-x less memory to run !

(Does DSP Lib offer all this options? Please, drop me a message, if it does.)

 

 Updates on 29 Sept. 2014:

Playing with the code, I realized, that one of the previous authors did an optimization for sine function, and it’d make sense if library installed on a Linux. Arduino, where sin & cosine  are n’t calculated at run time, but coming from LUT, requires absolutely different approach for optimization. So, I switch two “for” loops in reverse, making trigonometry math the most internal cycle, and it brings substantial acceleration in speed. Digits talk for itself:

Before:

  •  fft.2048:   4630 usec.
    fft.1024    2115 usec.

After:

  •  fft.2048:  3479 usec.
  •  fft.1024:  1589 usec.

Now, the speed of new library more than twice better than Radix-4 code.

Download:  SplitRadixRealP. for DUE board.

 

UNO VERSION.

 Same code “optimized” to run on 8-bit arduino UNO ( and like ) board. Short summary:

 FFT takes as input ANY size of inputs array 8, 16, 32, 64, 128, 256, 512.
Max. size 512 defined by LUT (and uCPU memory limits).

Timing results, in usec:

  • fft.256:   4316
  • fft.512:   9572

 Download:   SplitRadixRealT  for UNO board.

 
7 Comments

Posted by on September 25, 2014 in Uncategorized

 

Sound Camera.

One more project, that shows breathtaking beauty of the FFT (Fast Fourier Transform). Once again, like in last 3D Ultrasonic Radar Project,    Arduino DUE was nominated to be Maestro, doing major part of the Digital Signal Processing in real time.  As you can see below, the hardware includes 4 “modules”:

  1. Sensor board
  2. Arduino DUE
  3. Bluetooth shield
  4. Android tablet.

Last two items aren’t strictly necessary. Alternative would be to connect TFT display directly to arduino, but I decided not to spend my time on re-inventing drawing-rendering software. Better to delegate all visualization stuff to the equipment that was  specifically design by big monsters in high tech industry.  I spend quite time digging into android graphics subject anyway, only hoping I can apply my knowledge somewhere else later on.

Sound_Camera

Sensor board holds 4 microphones from  SFE.  Plus a few decoupling components, capacitors and inductor in power line.

Software.

   Brief summary: Arduino sampling 4 analog inputs, close to 41 kHz,  x 4 = 164 ksps,  software library Radix4 posted on this blog was imported into project practically intact. DMA feature on Arduino DUE allows sampling rate up to 1 MSPS, and I already successfully tested its capability in 3D Radar project.  Having 2048 fft size, at the first processing stage  output there are 1024 bins 20 Hz each. Than, using arctangent LUT, phase of each bin is extracted.  Difference in phases two vertically position microphones gives Y component, and two horizontally spaced mic’s – X component. Sound source is localized with accuracy ~ 0.5 degree. Have to say, that on the lower frequency end, 100 Hz – 1 kHz , where wavelength is huge compare to spacing between two mic’s ( 3.4 meters at 100 Hz ), accuracy is deteriorating proportionally to wavelength.

Arduino is calculating data really fast, providing  X,  Y, and M  every 50 milliseconds. M – is for magnitude. Than, all this data stream flows to android over BT.  Everything else is obvious, watch the video.

Speaker outputs white noise, as for single tone (frequency) only one pixel would be visible on screen. Android software “colorized” picture based on a frequency, low range – starting from red, and up to violet on high end of the frequency band, through all 1024 color wheel possibilities.  You can see, that picture saturated with green and blue, and there is almost no red color. There are two things, first is a speaker, not performing well at low end. Second nuance is the fact, that low frequencies are not “grouped” so effectively, due to the localization error, what I tried to explain in a paragraph above. I created an option in the menu to select different types of colorization, based on a frequency or based on a magnitude. They are look pretty similar for white noise source, so there is only one video clip.

Have fun.

 Edited on 21 Oct. 2014:

 If you come across this page searching an old  “Localizator” project, published over 2 years ago, here is working material I was able to find:   Localizator.  Code was written for arduino Leonardo (AtMega32U4) and wouldn’t run on anything else!

 
18 Comments

Posted by on September 12, 2014 in Advanced projects., FFT series.

 

Tags: , , , , , , , , , ,

Ultrasonic 3D Radar.

This page is next level of Virtual Touch Screen project. 

First things is a distance, for virtual touch screen its less than 3 m, because the reflective area is too small. For radar (or sonar) its depends on a size, and the bigger size of object the stronger echo. Approximate range of detection the object as big as a wall, 30 meters.

Technically, there are two hardware parts were added, to fully demonstrate extraordinary sensitivity of the VTS project. First one is the BlueTooth module. And second is a tablet, running android. Device that I have, doesn’t support USB host mode (OTG), otherwise I may be fine w/o BT, just transfer a data over USB cable, as it was done in two previous demo video clips.  Have to say, it was not easy to represent 3D perspective on a flat screen, and picture below shows what I designed to complete a task:

android

I don’t think it requires a comments, the tricky part was to create an elliptical grid to show a distance. The number of circles is not limited to 2, I’d think about how to film next demo video, that ‘d show a “volume”.

Enjoy the movie:

There are two apples, and arduino measure position in 3D space both of them. X, Y, and Z coordinates plus P – power of reflected ultrasonic wave used to draw circles, with different colors. You can see movement of the red circle on screen when first apples moves.

edited on 21-08-2014

After thinking for awhile how to show a “volume” on a flat tablet screen:

DSCF0480

And video:

That;s it for now.

 
4 Comments

Posted by on August 20, 2014 in Uncategorized

 

Tags: , , ,

Virtual touch screen (3D Ultrasonic Radar).

First things: there are no servo motors. No motors or any mechanical moving parts in this project.

There are 4 HC-SR04 ultrasonic sensors, only one of them is working as transmitting – pinging module and receiver simultaneously, 3 others are just receivers. Arduino DUE, of course, is a prime violin of the whole project. Small prototype board has an additional 4-channel buffer amplifier (MCP6024).

Technical specification ( approximately ):

  • Scanning range 20 m, limited very low power and sensitivity of the HC-SR04 devices.
  • Spacial XY resolution depends on a distance, has to be determined.  Two object should be position at least 5 cm apart and not on the same spherical surface around sensor board.
  • Directivity diagram +-30 degree, or so.
  • Spacial Z – (distance) resolution 12 um. No typo error, micrometers.
  • Time to complete full scan 16 milliseconds, frame rate may vary from 60 Hz down to 10 Hz if there is strong reverberation.

Have to say, that ultrasonic units were slightly modified, to brought out an analog signal (40 kHz) before it gets quantization in the local uCPU.  After amplification, 4-channels are digitized by arduino’s ADC (12-bits 1 MSPS).

Fast Fourier Transform, not much differs from the published on this blog library. I’m not ready to disclose complete signal processing algorithm, and is not publishing a code, at this time. Nevertheless, I don’t mind to answer reasonable /meaningful questions.

Video: have to practice more -);

A few comments on a video clip. I intentionally use a pen to draw a picture, even it’s almost violate the basic of the physics, because reflective area of the pen practically equals to wave length, 8.5 mm for 40 kHz in the air. You can see, that arduino is loosing track on a few occasions. Distance ~ 1m.

Computer is running Linux with regular “mtPaint 3.40″ from official source. Software is receiving a mouse commands, as its has no idea where this commands come from. In the same way, if you draw a picture manually. To interface with a host, arduino emulates a left button and XY move mouse commands using “build-in” mouse driver, and I copy ButtonMouseControl example from IDE.

The surface of the touch screen is “virtual”, first things arduino does after I send a command over serial monitor console to start a drawing, is “search – scan” an object. Whatever it finds first, the closest one, would be “locked” and distance to this object is interpreted as “touched – untouched”. This is first try, and I was not pushing hard on the gesture pattern recognition yet. But as you can guess, there is no limits to “slide” “rotate” “scroll” etc movement discrimination, with only one exception. There is no “multi-touch”, as I mentioned in the specification section, two object has to be 5 cm apart. This limitation is introduced by two shortcomings of the current hardware design. First one, because there is no phase array, only one unit is transmitting ( in the middle on right side ), so there is no way arduino could identify two objects on the same sphere. Second, is low sampling rate of the ADC. In order to “shrink” XY spatial resolution down to wave length (8.5 mm), sampling rate has to be at least 6 MSPS or so.

Tracking update rate (scan frame rate – don’t confuse with a video)  is set to 32 fps.

Photo:

closedistant3

eddited: 14 Aug. 2014       “New technology is rising!”

Second video clip is posted, that demonstrates better tracking stability over bigger distance range.

Distance is 1.2 m, same pen. I think, that all for Virtual Touch Screen demonstration. Any improvements I could make in a code ‘d introduced only small changes in overall representativity of the project.

edited: 26 Aug. 2014   Answering the question, modification to HC-SR04 module.

There is an electrical drawings, I was able to locate on-line:

http://uglyduck.ath.cx/HC-SR04E/HC-SR04E.svgz

And photo:                  circuits_mod

As you can see, analog 40 kHz output is taken from pin 7, LM324. Conveniently,  it’s the rightest one, close to the board edge. Module – transmitter has a jumper wire over B-E of the transistor, others 3 units may not have this wire. I find out, that unit doesn’t transmit anything till it gets a response, that may never happened for echo reflected from tiny object like a pen.  It looks like on-board uCPU is waiting a transition in poling mode.  And additional amplification stage I build with MCP6024, is similar to first stage of the LM324 (U2D), with a gain x50.  In my first try, I connect output of LM324 directly to arduino DUE analog inputs, basically its not really safe, as voltage goes close to 3.6-3.7 V. But than introducing MCP6024 (rail-to-rail) I decrease power voltage of the OPA down to 3.3V,  not to worry about my DUE.

 
9 Comments

Posted by on August 10, 2014 in Uncategorized

 

Tags: , , , ,

 
Follow

Get every new post delivered to your Inbox.

Join 56 other followers