Solution for session freezing / overruns @ 2kHz sample rate (with SD only)

yjyj France , Bordeaux
edited November 2015 in Cyton
Hello,

Freezing during long sessions, low 250Hz sample rate, 150mS (and more) overruns, all that bothered me.

Then I started to play with a version of the soft proposed by @biomurph : https://github.com/OpenBCI/OpenBCI_32-write-to-DS-only

To only write on the SD Card, not using the RfArduino, suppressed the freezing problem...

But overruns were always there.

Since these overruns seemed to be due to a "wait SD not busy" function, I modified it to continue the pooling of the A/D converter.during the waiting time...

Of coarse we need a circular buffer and so on... Details are in the few lines of code below.

Pooling during the waiting time was not enough , I had to add pooling at some critical point in the main loop...

To verify the result I "time stamped" each sample using the "input capture" of a timer  value on the occurence of the Data Ready falling edge. I have written this value after the 8 channels on the SD card file. I have also written a "tag" to know when or where from the code the pooling pushed datas in the buffer. To understand what was going on I added the values of the writing and reading indexes when pushing and pulling. And finally I wrote a short matlab script to look at the OBCI_nn.txt file.

image
I did this with the 32bit card and a low cost SD card : SD 16Gb card from Transcend ( TS16GUSDHC10 ) Class 10 , 30MB/s, 200x.
The results probably depend on the type of SD Card...

I launch the programme with the serial terminal of the Arduino IDE, as Biomurph recommended to me.
I write : "+0++A++N+" (with out the "+" it does not work...)

At the end we still receive the message :

Max write time: 51716 uS
Min write time: 404 uS
Overruns: 5
fileBlock,micros
1,8771
608,12767
6720,12811
7744,7766
8800,51716

In fact it is when the SD waiting time occures and how long time... It does not mean that some data were lost. To know if, look at the "time stamp" in the file.

With my hardware I tested up to 2kHz and found no data lost. At 2kHz the game of read and write  indexes shows that the swallowing of the buffered data becomes difficult but it goes.

@biomurph : How to send you the full code? (if you want it).

There under some details of the code and a matlab script.

  Have fun,
  Yannick.

   
//==========================================================================   
// In File   OpenBCI_32_SD_Only.ino  

void loop() //   +0++a++N+  +0++a++N+ 
{

  if(is_running ) 
  {
      while(!( IC4CON & 0x0008)){} ;                                         // while(!(OBCI.isDataAvailable())){}   // wait for DRDY pin2.D9..PGED3/VREF+/CVREF+/AN0/C3INC/RPA0/CTED1/PMD7/RA0

                                 OBCI.ere |=0x100 ;     OBCI.push();         // reads A/D and push data , time stamp, ere, accel, aux...
                                                                              
       
      while ( OBCI.read_ix != OBCI.write_ix )
      {
         OBCI.updateChannelData();                                           // get from the FIFO ADS data , time stamp, ere, accel, aux...   
         if( IC4CON & 0x0008 ) { OBCI.ere |=0x200 ;     OBCI.push(); } ;     // reads A/D and push data , time stamp, ere, accel, aux...
       
         if(SDfileOpen)
            {
                writeDataToSDcard ( SDsampleCounter++ );   
                //if(SDsampleCounter == 0 && writeToSDonly ) { Serial0.print("SD BLOCKS written: "); Serial0.println(blockCounter);  }     // verbosity
            }
         if( IC4CON & 0x0008 ) { OBCI.ere |=0x400 ;     OBCI.push(); } ;     // reads A/D and push data , time stamp, ere, accel, aux...
         
         if(!writeToSDonly)  OBCI.sendChannelData();
      }         
     
      if ( SDsampleCounter == 0 )   { digitalWrite(LED, HIGH);  }               // yj ****     digitalWrite(18, HIGH); Serial0.write(0x0a);
      if ( SDsampleCounter == 3 )   { digitalWrite(LED,  LOW);  }               // yj ****     digitalWrite(18, LOW );
  }
   eventSerial();
}

   
   
   
//=================================================================================   
//  In File     Definitions_32.h

// Sample Rate   16KHz  8kHz  4kHz  2kHz  1kHz  500Hz   250Hz     Do not use
//               000    001   010    011   100   101    110         111
#define Sample_Rate  0b011
#define circ_buf_len 512
#define circ_buf_mask (circ_buf_len-1)





   
   
   
//==========================================================================================   
//  In File     OpenBCI_32_Daisy.h  *******************************
      void push(void);
   
    byte       circ_buf[32*circ_buf_len]        ;  // The FIFO First In - First Out ; N samples * 32 bytes
   
    uint32_t     time_stamp                        ;  // the value of TMR4 a 32 bit timer or TMR3 16bits red by an "Input capture event" )
    uint16_t     ere                               ;  // just for test...
    uint16_t     read_ix                             ;  // read  index pointing samples
    uint16_t     write_ix                             ;  // write index
   
    uint16_t   b_time_stamp                        ;  // From circ_buf
    uint16_t   b_ere                               ;  // From circ_buf     to debug I wrote these 3 informations in the buffer
    uint16_t   b_read_ix                             ;  // From circ_buf       "
    uint16_t   b_write_ix                             ;  // From circ_buf       "


   
   

   
   
   
//====================================================================================   
//  In File     OpenBCI_32_Daisy.cpp  **** reads and push data , time stamp, ere, accel, aux... ******************
   
void OpenBCI_32_Daisy::push(void)
{
 csHigh(SD_SS);  // release spi  ????????????????

 byte *    ptr_buf  =   &circ_buf[(circ_buf_mask & write_ix)*32]  ;


// Timer 3 value is in Input Capture 4 Buffer ; ere++ to note fifo's depth (I never got 2)
    while(IC4CON & 0x0008) { time_stamp = IC4BUF ;    ere++ ; } ;               

  csLow(BOARD_ADS);                                                      //  open SPI for AD 1
   for(int i = 0; i< 3; i++){              xfer(0x00); }                    //    unused daisy stat
   for(int i = 0; i<24; i++){ *ptr_buf++ = xfer(0x00); }            //    read 24 bits of channel data in 8*3 byte chunks
  csHigh(BOARD_ADS);                                                     //  close SPI
  
 uint16_t * u16 = (uint16_t *) ptr_buf ;
 
          * u16++ = (uint16_t) time_stamp  ;                     //     ; for test
          * u16++ =              ere       ;       ere = 0 ;            //     ; for test
          * u16++ =            write_ix    ;    write_ix++ ;        //     ; for test
          * u16   =             read_ix    ;                               //     ; for test

 // To add : read & push accelerometer data
  if(useAccel && LIS3DH_DataAvailable())      {   LIS3DH_updateAxisData();   }   // addAccelToSD = true;  fresh axis data goes into the X Y Z
// To add : read & push auxliary data
  
  if(daisyPresent)
  {  ptr_buf       = &circ_buf[(circ_buf_mask & write_ix)*32]  ; write_ix++;
 
   csLow(DAISY_ADS);                                                         //  open SPI for AD 2
   for(int i = 0; i< 3; i++){               xfer(0x00); }
   for(int i = 0; i<24; i++){ *ptr_buf++  = xfer(0x00); }                   //  read 24 bits of channel data in 8 3 byte chunks
   csHigh(DAISY_ADS);                                                        //  close SPI
   //
   // To add : read something else... alternatively or not ...
  }
 
 csLow(SD_SS);  // take spi   ?????????????    
}



   
   
   
//========================================================================   
//  In File     OpenBCI_32_Daisy.cpp 
//  Retrieve  the fresh ADS data , time stamp, ere, accel, aux... from the FIFO

void OpenBCI_32_Daisy::updateBoardData(){
 
  if (daisyPresent) for(int i=0; i<8; i++) lastBoardChannelDataInt[i] = boardChannelDataInt[i];   
   
byte *    ptr_buf = &circ_buf[(circ_buf_mask & read_ix)*32] ;   
   
  for(int i = 0; i<8; i++)   {
     for(int j=0; j<3; j++) boardChannelDataInt[i] = (boardChannelDataInt[i]<<8) | *ptr_buf++ ;   
     if(bitRead(boardChannelDataInt[i],23) == 1)  boardChannelDataInt[i] |= 0xFF000000; 
     else{ boardChannelDataInt[i] &= 0x00FFFFFF; }                         // Convert 24bit to 32bit
  }
                            b_time_stamp =  *((uint16_t*)ptr_buf) ;              // for test
        ptr_buf +=  2 ;        b_ere        =  *((uint16_t*)ptr_buf) ;              // for test
        ptr_buf +=  2 ;        b_write_ix   =  *((uint16_t*)ptr_buf) ;              // for test
        ptr_buf +=  2 ;        b_read_ix    =  *((uint16_t*)ptr_buf) ; read_ix++;     // for test
 
  if(daisyPresent ){   int byteCounter = 0 ;                                                                                             // if (daisyPresent && !firstDataPacket) ????
    for(int i=0; i<8; i++){     meanBoardChannelDataInt[i]    = (lastBoardChannelDataInt[i] + boardChannelDataInt[i])/2;        }          // take the average of this and the last sample
    for(int i=0; i<8; i++){ 
      for(  int b=2; b>=0; b--){  meanBoardDataRaw[byteCounter] = (meanBoardChannelDataInt[i] >> (b*8)) & 0xFF;   byteCounter++;  }     // place the average values in the meanRaw array
    }   
  }
   
  if(firstDataPacket == true)  firstDataPacket = false;                                                                                  // is it usefull ? when first the false mean is sent...
}


 //  these data being retrieved, writeDataToSDcard format them using write to Hex which calls  modified writeData( pCache ) .


//=====================================================================
//  In File     SD_Card_Stuff.ino 
//  Call to overloaded writedata function in order to call OBCI.push() during SD busy state

void writeDataToSDcard(byte sampleNumber){

//  just for test and debug...
...
...
  convertToHex (                 OBCI.b_time_stamp        , 3,  true    );  // to verify if data lost
  convertToHex (                 OBCI.b_ere               , 3,  true    );  // to know where from push() was called
  convertToHex ( circ_buf_mask & OBCI.b_write_ix          , 3,  true    );  // indexes during writing in
  convertToHex ( circ_buf_mask & OBCI.b_read_ix           , 3,  true    ); 
  convertToHex ( circ_buf_mask & OBCI.  write_ix          , 3,  true    );  // indexes during reading out
  convertToHex ( circ_buf_mask & OBCI.  read_ix           , 3,  false   ); 
...
...
}
   
    THE END IN THE NEXT MESSAGE
   





.

Comments

  • yjyj France , Bordeaux
    edited November 2015
    //============================================================================================================   
    //  In File     SD_Card_Stuff.ino  ***** Call to overloaded writedata function in order to call OBCI.push() during SD busy state *******************

    void writeCache(){
        .....
        .....
        OBCI.csLow(SD_SS);  // take spi
           if(!card.writeData (   pCache,   &OBCI  )) {Serial0.println("block write fail");}   // write the block  //     *********   yj  ********
        OBCI.csHigh(SD_SS);  // release spi
        
        if( IC4CON & 0x0008 ) { OBCI.ere |=0x20 ;     OBCI.push(); } ;     // reads A/D and push data , time stamp, ere, accel, aux...
        .....
        .....
    }    

        

        
        
        
    //=========================================================================   
    //  In File      Sd2Card.h ************************
        
            uint8_t writeData   ( const uint8_t*   src                                 );      //   old one
            uint8_t writeData   ( const uint8_t*   src   , OpenBCI_32_Daisy*    OBCI   );      //   yj  

            uint8_t waitNotBusy ( uint16_t timeoutMillis                               );      //   old one
            uint8_t waitNotBusy ( uint16_t timeoutMillis , OpenBCI_32_Daisy*    OBCI   );      //   yj  

        
        
        
    //==========================================================================    
    //  In File     Sd2Card.cpp

    uint8_t Sd2Card::waitNotBusy  (   uint16_t timeoutMillis,    OpenBCI_32_Daisy*  OBCI    )
    {
      uint16_t         t0 = millis();  

      do
      {

         while(!( IC4CON & 0x0008)){} ;   //  Wait ADS_DRDY to synchronize , if you simply test , it is worse     
                                                   
        chipSelectHigh()            ;                          
                (*OBCI).ere |=0x10  ;                  
                (*OBCI).push()      ;                           
        chipSelectLow()             ;                        
        
        // ---- Now we have a litle bit of time to write on the SD Card , if it is ready ...                                         
        
         if (spiRec() == 0XFF) return true;
      }
      while (((uint16_t)millis() - t0) < timeoutMillis);
     
      return false;
    }




    //==========================================================================    
    // In File   OpenBCI_32_SD_Only.ino   

    void setup(void)
    {
    ...
    ...
    T2CONbits.TON    = 0 ; // Stop any 16/32-bit Timer2 operation
    T3CONbits.TON    = 0 ; // Stop any 16-bit Timer3 operation
    T2CONbits.T32    = 0 ; // =1; Enable 32-bit Timer mode
    T3CONbits.TCS    = 0 ; // Select internal instruction cycle clock
    T3CONbits.TGATE  = 0 ; // Disable Gated Timer mode
    T3CONbits.TCKPS  = 7 ; // Select 1:256 Prescaler

    TMR3 = 0x00;       // Clear 32-bit Timer       (lsw) 4 et (msw) 5
    PR3  = 0xffff; // Load 32-bit period value (lsw) 4 et (msw) 5 ffff

        IC4CON          &= 0x7fff     ;     // Disable and reset module, disable clocks, disable interrupt generation and allow SFR modifications
        IC4CONbits.ICM   =      0   ;  // 010 = capture falling edge
        IC4CONbits.C32   =      0   ;  // use 16 bit=0 ;32=1
        IC4CONbits.ICTMR =      0 ;  // use T3=0 ; t2=1
                                                                    // IC1CONbits.FEDGE = 1;  // capture rising edge first all others after ; inutile si ICM = 010 = On falling edge
        IC4CONbits.ICM   =      2 ;  // 010 = capture falling edge

       
       IC4CON |= 0x8000 ; // turn on IC4    //   IC4CONbits.ON = 1; "do not compile" // bit 15 = ON bit
     T3CONbits.TON = 1;
    ...
    ...
     }

     
     
     
     
     
                                   //=====    matlab script ======

    % ============== lecture du time stamp , flag et indexes ========
    clear  all
    close  all
    fclose all

               fichier = 'E:\OBCI_0e.txt' ;  

      fid = fopen(fichier,'r');
     
    ligne = 0 ;

    s = fgets(fid); s = fgets(fid); s = fgets(fid);

             dd = dir(fichier) ; len = length(s);
    d = zeros( round( dd.bytes / len ),6 )  ;

    while 1
        data_ligne = sscanf(s,'%2x,%6x,%6x,%6x,%6x,%6x,%6x,%6x,%6x,%4x,%4x,%4x,%4x,%4x,%4x');
        ligne = ligne+1 ;  
        d(ligne,:) =  data_ligne( 10:15)'  ;        
        s = fgets(fid);
        if(length(s)~=len) ; fprintf('ligne incomplete\n'); break; end ;
    end;

    fclose(fid);

    d(1:end-1,1)=diff(d(:,1)); m = find(d(1:end-1,1)<0); d(m,1) = d(m,1)+2^16 ;

    d=d(find(d(:,1)),:); d = d(1:end-1,:);

    d(:,1) = round(d(:,1)*256*25e-9*1000/0.5);

    plot(d(:,2));               % Value of "ere" ; shows where from push() was called
    figure(2); plot(d(:,1))     % time between samples ; If no overruns, all values should be "1"

    % look at the figure(1) the value of "x" when ere == 0x401 or 0x201 or 0x11
    % ; in the main loop, ere == 0x101 ;
    %   x=35340  ;   fprintf('%3d %4x %3x %3x %3x %3x\n',d(x:x+40,:)')

    % find( d(1:end-1,2)==17 & diff(d(:,2)) == 0)
    %f= find( d(1:end-1,2)==17 & diff(d(:,2)) == 0);
    %plot(f)

  • wjcroftwjcroft Mount Shasta, CA
    Yannick, wonderful, thank you!

    I'm sure Joel @biomurph will want to upload your code some place on our Github. I think you can do that also and then do a push to our repository.

    William
  • yjyj France , Bordeaux
    @wcroft

    Thank's William,    but "it is too early to crow victory"...

    To do what I want this code works but since it depends on the context and the type of SD Card, it should be tested carefully...

    For exemple I am not sure that 16 Channels / 2kHz would go... For that a binairy format on the SD should be used...

     Yannick.
  • yjyj France , Bordeaux
    A last modif :

    The SD only version of Biomurph uses the code "N" to start running.

    In the file   "OpenBCI_32_SD_Only.ino" you will find a case 'N'  :
    In this case I added the initialisation of write and read indexs.

          case 'N':        writeToSDonly = true;
            ...
            OBCI.read_ix  = 0 ;
            OBCI.write_ix = 0 ;
            break;

    Sorry for this oversight.
        Yannick.

  • biomurphbiomurph Brooklyn, NY
    @yj,

    I am just seeing this, and will take a closer look at it soon. Thanks!
  • Hi Yannick @yj,

    I am working on increasing the sampling rate of the board as well as writing the data to the SD card only to speed up its current writing speed. So that I am interested in your work in this discussion.

    Could I ask you if the modified firmware you posted here does work well until now? Could you share the code with me?

    If possible, it will be really great. My email is anh [dot] t4 [dot] nguyen [at] ucdenver [dot] edu

    Please let me know or we can discuss more on this topic. Thank you.
  • yjyj France , Bordeaux
    @ntlanh,

    I sent you a dropbox link.
    Tell me if you can use it...

    Yannick.
Sign In or Register to comment.