High sampling rate measurement for weak electrish fish

Status
Not open for further replies.
Error #1: never solder a Teensy if you can use a breadboard. (OK this is void now)

Upsi! Ok i dont need DMA. With the standard :
Code:
void setup() {

  pinMode(adc_pin, INPUT);
  Serial.begin(9600);

  adc.setAveraging(1); 
  adc.setResolution(12); 
  adc.setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); 
  adc.setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); 
  adc.adc0->analogRead(adc_pin)

i get about 200kHz for sampling. Now the hard part with the Logger ... i looked at a lot of examples but i'm still a bit confused :/
I guess the best approach i have so sofar is:

Code:
#include <ADC.h>
#include "SdFat.h"



const uint16_t adc_pin = A9;
ADC adc;
const size_t BUF_DIM = 32768;




void setup() {

  pinMode(adc_pin, INPUT);
  Serial.begin(9600);

  adc.setAveraging(1); // set number of averages
  adc.setResolution(12); // set bits of resolution
  adc.setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
  adc.setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

  adc.adc0->analogRead(adc_pin); // performs various ADC setup stuff
}
void loop() {
  /*
    write the buffer(with values) on the SD via .bin file.
    while the SD is busy do stuff in yield
  */
}

void yield(){
 /*
  Data acquisition via adc.adc0->analogRead(adc_pin) onto a buffer
  */
}

I tried to understand a bit about buffers etc via https://forum.pjrc.com/threads/43621-Low-latency-data-logger and the teensydemo in the sdfat examples.
The problem is that i still dont understand how to handle buffers xD i assign a part of the memory wich is 32768 bytes big as a buffer. Wich means with
a rate of 200kHz and 12bit resolution i can record about 110ms of data? I dont really get how to place my analoginput into the buffer and the buffer then on the SD.
Can someone point me to a good example ? Thank you in advance!
 
Hello Solidhfm,

I empathize with your difficulties. (I do not work on fish but mice...)

I see that you expect some kind of "magic" from the words you found here and there... Dma(), buffer(), adc(), set.this(), set.that(), and so.on()...:)

If it was so easy... One day may be, in the future...

You ask for a good example. I have not enough expertise to be able to write one to you, but the best link I found on the forum would be :

- for write a buffer on the SD :
https://github.com/tni/teensy-samples/blob/master/SdFatSDIO_low_latency_logger.ino
https://github.com/greiman/SdFat/blob/master/examples/TeensySdioDemo/TeensySdioDemo.ino

- for write adc in a buffer :
https://forum.pjrc.com/threads/43708-Teensy-3-6-Datalogging-at-10kHz

I am currently myself trying to understand these examples as well as possible to become able to mixte them in order to "write adc on the SD"...

Your case seems more simple than mine, (I need at least 8 channels) ; Have you considered to use Paul's audio card (Only 6 inputs) and the audio library?

Yannick.
 
Last edited:
I see that you expect some kind of "magic" from the words you found here and there... Dma(), buffer(), adc(), set.this(), set.that(), and son.on()...:)

There is no magic. It's basically like driving a car. You have to learn it first, and then to get a drivers license. Thus, for realizing projects with embedded processors, wouldn't it be normal to learn coding and understanding mcu architecture first?
 
Unless you (Solidhfm) really want to have your driver's licence, you could be inspired by this article :

How to record EODs http://mormyrids.myspecies.info/en/node/484 :

" ... A cheaper and more compact solution is to record EODs using consumer audio interfaces (sometimes called "sound cards") most of which now connect to a computer by USB 2.0 or 3.0. These have microphone preamps built into them which while less powerful than a BMA-200 are adequate for amplifying the EOD of a mormyrid close to the electrodes. While made to digitize signals from microphones or musical instruments, they work well for electric fish, although you're limited to AC-coupled recordings that filter out frequencies below 20 Hz. Because short EODs are just fractions of a millisecond, an audio interface with a fast digitization rate is preferable. The fastest of these devices can capture at 192 kHz (at 24 bit resolution). Not all are capable of running this fast, so be sure to get one that is. One such that is very reasonably priced ($100) is the Focusrite Scarlett Solo. I haven't tried this product, but think it's likely to work pretty well. Its preamplifiers give you 50dB of gain. There are pricier products out there with more powerful preamplifiers, like this one. ...."

Yannick.
 
I see that you expect some kind of "magic" from the words you found here and there... Dma(), buffer(), adc(), set.this(), set.that(), and so.on()...:)

First of all i'm really sorry if i made that impression :/ i'm that type of guy that wants to fully understand what he is doing, not just copy and paste.
The only experience i've made sofar was programming neuralsystems in python, this on the other hand is way more hardware oriented, which i've never
done before. Problem two is that i have to squeeze my research completly into my free time(which is not alot) so i only do babysteps.
The idea behind this whole project was to create a system which functions as well as an national instrument box(1000€) but only for <100€ and we all
agree that it is possible. In the end we would like to present our own measuretechnic in our paper.

So that aside the Focusrite Scarlett Solo could be a really good alternative and is already on our list of things to check.

For the code:

Code:
#include <ADC.h>
#include <array>
#include <SD.h>
#include <SPI.h>

int c=0;
const uint16_t adc_pin = A9;
ADC adc;
std::array<volatile uint16_t, 4096> buffer;
volatile size_t write_pos = 0;
const int ledPin = 13;
volatile uint16_t adc_val = 0;
const int chipSelect = BUILTIN_SDCARD;

File file;
int value = 0;
int starttime = 0;
int endtime = 0;

void setup() {

  pinMode(adc_pin, INPUT);
  pinMode(ledPin, OUTPUT);
  Serial.begin(9600);
  adc.setAveraging(1); // set number of averages
  adc.setResolution(12); // set bits of resolution
  adc.setConversionSpeed(ADC_CONVERSION_SPEED::MED_SPEED); // change the conversion speed
  adc.setSamplingSpeed(ADC_SAMPLING_SPEED::HIGH_SPEED); // change the sampling speed

  adc.adc0->analogRead(adc_pin); // performs various ADC setup stuff


  adc.adc0->stopPDB();
  const uint32_t pdb_trigger_frequency = 200000;
  adc.adc0->startPDB(pdb_trigger_frequency);

  if (!SD.begin(chipSelect)) {
    Serial.println("initialization failed!");
    return;
     }
  file = SD.open("test7.txt", FILE_WRITE);
  starttime = micros();
  for(int i = 0; i<9999;i++){
   value = adc.adc0->readSingle();
   file.println(value);
  }
  endtime=micros();
  file.println(endtime-starttime);
  file.close();
  Serial.println("complete");
  for(int i = 0; i<9;i++){
       digitalWrite(ledPin, HIGH);
    delay(200);
    digitalWrite(ledPin, LOW);
    delay(200);
  }
  
}


void loop() {


}


void adc0_isr() {
    size_t write_pos_ = write_pos;
    buffer[write_pos_] = adc.adc0->readSingle();
    write_pos_++;
    if(write_pos_ >= buffer.size()) write_pos_ = 0;
    write_pos = write_pos_;
}
void pdb_isr(void) {
    PDB0_SC &=~PDB_SC_PDBIF; // clear interrupt
}

this reallyyyy simple code works with 42kHZ what i think is not even that bad but i need it to be at least 2.5 times faster but
that is more simple software oriented programming. Sooo yeah need to get trough all the examples again and again to understand
what people did there and then use it as i want and understand.
 
@ Soldihfm,

another rather simple code...

It is a mixture from Tni's & Greiman's examples, and my personal contribution (I am very proud of that :)), for the "big buffer" to not to be annoyed by SD card freezing...

1 channel but it should be possible to add at least one...

I tried and tested 200kHz as you wish, it runs...

If you use matlab , you can read the file and plot the beginning by :
" fid = fopen('e:\Demo.BIN','r'); m = fread(fid,[1 10*1024 ] , 'uint16'); fclose(fid); figure(1); plot(m) "

My code is dirty, sorry... If you have any questions... don't hesitate.

View attachment 12311

the code :

Code:
//*************************************************************************
//*      Hybrid program from      Tni and Greiman   plus a few inputs      *
//*************************************************************************

#include <ADC.h>          
#include <DMAChannel.h> 
#include <array>
#include "SdFat.h"

// SdFatSdio   sd ;
   SdFatSdioEX sd ;

uint32_t      BUF_DIM         = 32768 ;
uint32_t      FILE_SIZE       = 128UL*BUF_DIM ,             last = 0; 
File file;   


/********************************************************/


const uint8_t adc_pin = A9;   // digital pin 23
const uint8_t out_pin = 2;    // connect out_pin to adc_pin, PWM output on out_pin will be measured  via adc_pin.

ADC adc;
DMAChannel dma;     // chap 23 page 473
std::array<volatile uint16_t, (uint32_t)128*512> buffer __attribute__ ((aligned (32*1024)));  
char str[128] ;
typeof(*dma.TCD)  tcd_mem[4] __attribute__ ((aligned (32))) ;
                                                                           

volatile size_t write_pos = 0;

volatile uint16_t adc_val = 0;

void setup() 
{
    Serial.begin(115200);     delay(2000);     Serial.println("Begin Setup\n");  
   pinMode(LED_BUILTIN, OUTPUT);
   if (!  sd.begin()) { sd.initErrorHalt("SdFatSdio   begin() failed"); }    sd.chvol();
   if (!file.open("Demo.bin", O_RDWR | O_CREAT)) {  sd.errorHalt("open failed");  } 


    pinMode                (                 adc_pin, INPUT  );  
    adc.setAveraging       (                              1  );
    adc.setResolution      (                           12,0  );
    adc.setConversionSpeed ( ADC_CONVERSION_SPEED::MED_SPEED );      
    adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::HIGH_SPEED  );
    adc.adc0->analogRead   (                        adc_pin  );           
    if(adc.adc0->fail_flag)   {  Serial.print("ADC error: ");    Serial.println(adc.adc0->fail_flag, HEX);  }
    

    dma.source                 (           ADC0_RA);   
    dma.transferSize           (                 2);
    dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC0); 
	
//  ********* y.j.**********

    dma.TCD->CITER    =           32*1024/2   ;      dma.TCD->BITER =  32*1024/2 ;   dma.TCD->DOFF = 2 ;    dma.TCD->CSR   =  0x10; 
    
    dma.TCD->DADDR        = (volatile void*) &buffer [ 0*512]    ;   Serial.println((uint32_t) &tcd_mem[0]);   
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     1]    ;   print_config (&dma); 
    memcpy ( &tcd_mem[0], dma.TCD , 32 ) ;   
                                                                                         
    dma.TCD->DADDR        = (volatile void*) &buffer [32*512]    ;   Serial.println((uint32_t) &tcd_mem[1]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     2]    ;	 print_config (&dma); 
    memcpy ( &tcd_mem[1], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [64*512]    ;   Serial.println((uint32_t) &tcd_mem[2]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     3]    ;	 print_config (&dma);
    memcpy ( &tcd_mem[2], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [96*512]    ;   Serial.println((uint32_t) &tcd_mem[3]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     0]    ;	 print_config (&dma);
    memcpy ( &tcd_mem[3], dma.TCD , 32 )  ;   
                                                                                     
    memcpy ( dma.TCD ,  &tcd_mem[0], 32 ) ;                          Serial.println(" tcd0 back");    print_config (&dma); 
                                                                                             
                                                                                                                                                                             
//  *************       


    dma.enable();      

    adc.enableDMA(ADC_0);     
     
    adc.adc0->stopPDB();
   
    const uint32_t pdb_trigger_frequency = 200*1000 ;           
    adc.adc0->startPDB(pdb_trigger_frequency);
    NVIC_DISABLE_IRQ(IRQ_PDB); // we don't want or need the PDB interrupt
    
    analogWriteFrequency(out_pin, 1000);    // PWM output on out_pin for testing purposes.
    analogWrite(out_pin, 127);   
}




void loop() 
{  
/*  */
	while ( ((128*1024-1) & ( (int)dma.TCD->DADDR - last )) > BUF_DIM ) 	
	{	 
	   if (BUF_DIM != (uint32_t)file.write( (char*)&buffer[((last/2)&(64*1024-1))], BUF_DIM) )  	   { sd.errorHalt("write failed");    } ; 
	   Serial.print(" "); Serial.print((128*1024-1) & ( (int)dma.TCD->DADDR - last )); last += BUF_DIM ;  
	}	
	
	if ( last >= FILE_SIZE ) 
	{ 	
	   file.close();	Serial.println ("\n Done");  
	   while(1) { digitalWriteFast(LED_BUILTIN, HIGH) ; delay(100) ; digitalWriteFast(LED_BUILTIN, LOW) ; delay(1000) ; } 
	} ;
	
    
    delay(10);  // can be decreased or suppressed (was just to play, try...)
}


void pdb_isr(void) {  PDB0_SC &=~PDB_SC_PDBIF  ;  Serial.println("."); }; 

void print_config(DMAChannel* dma) {
    Serial.print("channel \t");  Serial.println(dma->channel);
    Serial.print("SADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->SADDR); Serial.println(str);
    Serial.print("SOFF    \t");  Serial.println((uint32_t)dma->TCD->SOFF);
    Serial.print("ATTR    \t");  Serial.println((uint32_t)dma->TCD->ATTR);
    Serial.print("NBYTES  \t");  Serial.println((uint32_t)dma->TCD->NBYTES);
    Serial.print("SLAST   \t");  Serial.println((uint32_t)dma->TCD->SLAST);
    Serial.print("DADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->DADDR); Serial.println(str);  
    Serial.print("DOFF    \t");  Serial.println((uint32_t)dma->TCD->DOFF);
    Serial.print("CITER   \t");  Serial.println((uint32_t)dma->TCD->CITER);
    Serial.print("DLASTSGA\t");  Serial.println(( int32_t)dma->TCD->DLASTSGA);
    Serial.print("CSR     \t");  Serial.println((uint32_t)dma->TCD->CSR);
    Serial.print("BITER   \t");  Serial.println((uint32_t)dma->TCD->BITER);
    Serial.println();
}

Have fun, and sorry for the "magic"...
Yannick.
 
Last edited:
To go beyond 300kHz use "very_high_speed" at line 46,47

Code:
    adc.setConversionSpeed ( ADC_CONVERSION_SPEED::VERY_HIGH_SPEED );      // it is not the sampling rate, just the clock of the converter
    adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::VERY_HIGH_SPEED  );                  // it is not the sampling rate, just the sampling DURATION (it's not a "speed")

On a SD card from SanDisk 8 GB, I still have correct data at 500kHz, but the file is small ( FILE_SIZE = 128*32k bytes ) time to time a 128k buffer could be lost. To check some data lost , looking at a timer, time stamping, would help...

Every thing remains to be done but my morale raises...

y.
 
How much noise is being created on your samples when you write to the SD Card?

My experience so far with Sampling ADC's and trying to do SPI transactions at the same time have forced me to use an external ADC. It did not just create random noise either it forced all the samples in one direction meaning that even oversampling and averaging was fairly useless.

I'm currently in the process of re-laying out my CC Dummy Load with both external ADC, DAC and Isolation between them and the Teensy.
 
Hello Donziboy,

currently, in "my lab" on the photo, I simply sample a digital output... :
My_lab.jpg

Of coarse it looks nice fig 1 shows the 1kHz digital signal sampled at 500kHz.
fig_01.jpg

If I compute the " differential " and zoom on the plot I get this (fig 2)
fig_02.jpg
It is probably related to the writing activity since it occures every 16k samples (32kB)

A zoom on the last spot : fig 3
fig_03.jpg

As you see, in "my lab", I am unable to make a "breakout card" as you do, to use an external ADC...

I have no idea of the impedance of the digital signal used in this test. (Probably rather low)
I dont know if this 30 digit noise is in the signal or its measure...

In our biological signals we already have much noise , any kind of artefacts, the line, mice movements, etc...
I would say that at least 30% is noise...
I must try in the real situation to know if the teensy ADC can be used...
Let's say that up to now, this noise is a noise among others...
If Solihfm amplifies enough the fish's signals , may be he could use the teensy...

If I find a breakout card (already made) on the market, 8 channels, 20-30khz sample rate/channel, with amplifiers please... I will use it, sure...
But I dont find it...

Thank's for your advices.
y.
 
If you can handle soldering 0.5mm pitch pins and 0603 parts I can send you a blank ADS8698 breakout board and a parts list and you can build your own breakout. Provided your somewhere that USPS delivers to. I built the board solely for testing and have learned a few things from it. I may take what I learned and create one for Tindie at some point. The breakout can be configured in several ways depending on what you need the amps and ADC to do.

The breakout is compatible with ADS8664/8668(4 and 8 chan 12bit), ADS8674/8678(4 and 8 chan 14bit), ADS8684/8688(4 and 8 chan 16bit) and ADS8694/8698(4 and 8 chan 18bit). All are 500KSPS max.
The 16bit versions are probably the fastest since the transfers would be 32bits(16 clocks and 16 data bits back) even which could likely be done with DMA transfers.
 
@Yannick
I'm not sure ADC will give you good results without firm (soldered) contact.
I would solder at least a header and use good jumper wire
 
@ Donziboy2,

thank you very much for your kind and generous offer, but if I take the plunge, I would use chips designed by T.I. to record biological signals, (scalp eeg in human). ADS1298-99... Some time ago I played with a card from https://shop.openbci.com/collections/frontpage/products/cyton-biosensing-board-8-channel?variant=38958638542, the CYTON... It uses an ADS1299 limited to 16ks/S per channel. (But on the Cyton , the RAM quantity is weak and we cannot buffer a lot... this limited me to 2kHz sampling rate). Anyway the ADS1298 rises to 32kHz and some people have made breakout cards , for example : https://github.com/adamfeuer/ADS1298-breakout/blob/master/docs/ads1298_breakout_1.jpg . Unfortunately Adam Feuer does not sell it... If I solve all other problems (I also have to learn wifi to control, start-stop, the logger and parameter inputs...) then I will do the effort to work on it... This said I am curious to test the Teensy ADC, to know if it is disastrous... For Solidhfm's project, your card would be perfect... Thank's again for your proposal.

@WMXZ, I agree with that even if I have read somewhere, a long time ago, that wired contacts are less noisy than soldered ones. But it is the past... Thank's for your advice.
 
I was thinking about creating my own circuit with a smb ADC but in the next second i realized that i don't have the slightest clue about that stuff and laughed.
After i ran a bunch of single-ended tests with my field-amp and his internal ref(didn't get any useful data), i ran a test with the teensy differential reading and:

eod_v2.png

so now that i have fresh wind in my back i have a few questions...again sorry!
The code sofar is:
Code:
//*************************************************************************
//*      Hybrid program from      Tni and Greiman   plus a few inputs      *
//*************************************************************************

#include <ADC.h>          
#include <DMAChannel.h> 
#include <array>
#include "SdFat.h"

// SdFatSdio   sd ;
   SdFatSdioEX sd ;

/*PRÄ-------------------------------------------------------------*/
uint32_t      BUF_DIM         = 32768 ;
uint32_t      FILE_SIZE       = 128UL*BUF_DIM ,             last = 0; 
volatile size_t write_pos = 0;
volatile uint16_t adc_val = 0;
File file;   
/*----------------------------------------------------------------*/



/*PINS------------------------------------------------------------*/
const uint8_t adc_pin = A9;     // digital pin 23 for singleended
const uint16_t diff_pin1 = A10; // digital pin A10 for differential
const uint16_t diff_pin2 = A11; // digital pin A11 for differential
const uint8_t out_pin = 2;      // PWM output
/*----------------------------------------------------------------*/



/*DECLARATIONS----------------------------------------------------*/
ADC adc;            // used to declare the adc.### object
DMAChannel dma;     // used to declare the dma.### object
std::array<volatile uint16_t, (uint32_t)128*512> buffer __attribute__ ((aligned (32*1024)));  
char str[128] ;
typeof(*dma.TCD)  tcd_mem[4] __attribute__ ((aligned (32))) ;
/*----------------------------------------------------------------*/
                                                                           



void setup() 
{
   Serial.begin(250000);     
   delay(2000);     
   Serial.println("Begin Setup\n");  
   pinMode(LED_BUILTIN, OUTPUT);
   if (!  sd.begin()) { sd.initErrorHalt("SdFatSdio   begin() failed"); }    sd.chvol();
   if (!file.open("Diftest3.bin", O_RDWR | O_CREAT)) {  sd.errorHalt("open failed");  } 


    pinMode                (                 adc_pin, INPUT  ); // set pin
    pinMode                (               diff_pin1, INPUT  ); // set pin
    pinMode                (               diff_pin2, INPUT  ); // set pin
    adc.setAveraging       (                              1  ); // set averaging for the ADC
    adc.setResolution      (                           16,0  ); // set the resolution of the ADC(8/10/12/16)/(9/11/13/15)
    adc.setConversionSpeed ( ADC_CONVERSION_SPEED::HIGH_SPEED); // set conversion time between each conversion
    adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::HIGH_SPEED  ); // set sampling speed for sampling rate
    adc.setReference       (ADC_REFERENCE::REF_3V3, ADC_0    ); // set reference         
    
    if(adc.adc0->fail_flag)   {  //ADC ERROR check
      Serial.print("ADC error: ");    
      Serial.println(adc.adc0->fail_flag, HEX);  
      }
    

    dma.source                 (           ADC0_RA); // source for the DMA from the ADC Data Result Register   
    dma.transferSize           (                 2); //
    dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC0); //
  
//  ********* y.j.**********

    dma.TCD->CITER    =           32*1024/2   ;      dma.TCD->BITER =  32*1024/2 ;   dma.TCD->DOFF = 2 ;    dma.TCD->CSR   =  0x10; 
    
    dma.TCD->DADDR        = (volatile void*) &buffer [ 0*512]    ;   Serial.println((uint32_t) &tcd_mem[0]);   
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     1]    ;   print_config (&dma); 
    memcpy ( &tcd_mem[0], dma.TCD , 32 ) ;   
                                                                                         
    dma.TCD->DADDR        = (volatile void*) &buffer [32*512]    ;   Serial.println((uint32_t) &tcd_mem[1]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     2]    ;   print_config (&dma); 
    memcpy ( &tcd_mem[1], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [64*512]    ;   Serial.println((uint32_t) &tcd_mem[2]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     3]    ;   print_config (&dma);
    memcpy ( &tcd_mem[2], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [96*512]    ;   Serial.println((uint32_t) &tcd_mem[3]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     0]    ;   print_config (&dma);
    memcpy ( &tcd_mem[3], dma.TCD , 32 )  ;   
                                                                                     
    memcpy ( dma.TCD ,  &tcd_mem[0], 32 ) ;                          Serial.println(" tcd0 back");    print_config (&dma); 
                                                                                             
                                                                                                                                                                             
//  *************       


    dma.enable();      

    adc.enableDMA(ADC_0);     
     
    adc.adc0->stopPDB();
    adc.adc0->startSingleDifferential(diff_pin1,diff_pin2); // set for differential reading
    adc-adc0->differentialMode()
    const uint32_t pdb_trigger_frequency = 200*1000 ;       // set a sampling rate    
    adc.adc0->startPDB(pdb_trigger_frequency);              // start PDB with specific sampling rate
    NVIC_DISABLE_IRQ(IRQ_PDB);                              // we don't want or need the PDB interrupt
    
    //analogWriteFrequency(out_pin, 1000);    // PWM output on out_pin for testing purposes.
    //analogWrite(out_pin, 127);   
}




void loop() 
{  
/*  */
  while ( ((128*1024-1) & ( (int)dma.TCD->DADDR - last )) > BUF_DIM )   
  {  
     if (BUF_DIM != (uint32_t)file.write( (char*)&buffer[((last/2)&(64*1024-1))], BUF_DIM) )       { sd.errorHalt("write failed");    } ; 
     Serial.print(" "); Serial.print((128*1024-1) & ( (int)dma.TCD->DADDR - last )); last += BUF_DIM ;  
  } 
  
  if ( last >= FILE_SIZE ) 
  {   
     file.close();  Serial.println ("\n Done");  
     while(1) { digitalWriteFast(LED_BUILTIN, HIGH) ; delay(100) ; digitalWriteFast(LED_BUILTIN, LOW) ; delay(1000) ; } 
  } ;
  
    
    //delay(10);  // can be decreased or suppressed (was just to play, try...)
}

void pdb_isr(void) {  PDB0_SC &=~PDB_SC_PDBIF  ;  Serial.println("."); }; 

void print_config(DMAChannel* dma) {
    Serial.print("channel \t");  Serial.println(dma->channel);
    Serial.print("SADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->SADDR); Serial.println(str);
    Serial.print("SOFF    \t");  Serial.println((uint32_t)dma->TCD->SOFF);
    Serial.print("ATTR    \t");  Serial.println((uint32_t)dma->TCD->ATTR);
    Serial.print("NBYTES  \t");  Serial.println((uint32_t)dma->TCD->NBYTES);
    Serial.print("SLAST   \t");  Serial.println((uint32_t)dma->TCD->SLAST);
    Serial.print("DADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->DADDR); Serial.println(str);  
    Serial.print("DOFF    \t");  Serial.println((uint32_t)dma->TCD->DOFF);
    Serial.print("CITER   \t");  Serial.println((uint32_t)dma->TCD->CITER);
    Serial.print("DLASTSGA\t");  Serial.println(( int32_t)dma->TCD->DLASTSGA);
    Serial.print("CSR     \t");  Serial.println((uint32_t)dma->TCD->CSR);
    Serial.print("BITER   \t");  Serial.println((uint32_t)dma->TCD->BITER);
    Serial.println();
}

The thing is that the DMA doesn't get the right values i think. I tried the PDB differential setup in a simple code and it worked with adc.adc0->readSingle() and as far as i understand the dma.source does the "same" am i right?
I checked the values with the PWM output but i only get gibberish. In the ADC_module.h is the function differentialMode() that sets the ADC_SC1A, ADC_SC1_DIFF so the ADC is in differential.

Here is the Matlab-plot of the data:

difftest.jpg

I#m out off ideas on how to setup the differential reading with PDB and DMA ... any suggestions ?

Thank you in advance.
(left my teensy in the lab so i cant just test things now and asking instead)
 
Last edited:
Ok here is an update!

The code i'm using:

Code:
//*************************************************************************
//*      Hybrid program from:                                             *
//*       Tni, Greiman, Yannick and Leon                                  *
//*                                                                       *
//*       for analog measurements and datalogging                         *
//*************************************************************************

#include <ADC.h>          
#include <DMAChannel.h> 
#include <array>
#include "SdFat.h"
#include <TimeLib.h>



/*PRÄ-------------------------------------------------------------*/
uint32_t      BUF_DIM         = 32768 ;
uint32_t      FILE_SIZE       = 0 , last = 0; 
volatile size_t write_pos = 0;
volatile uint16_t adc_val = 0;
File file;   
uint32_t duration = 0;
uint32_t bytes = 0;
float preceil = 0;
float scale = 0;
/*----------------------------------------------------------------*/



/*PINS------------------------------------------------------------*/
const uint8_t adc_pin = A9;     // digital pin 23 for singleended
const uint8_t diff_pin1 = A10; // digital pin A10 for differential
const uint8_t diff_pin2 = A11; // digital pin A11 for differential
const uint8_t out_pin = 2;      // PWM output
/*----------------------------------------------------------------*/



/*DECLARATIONS----------------------------------------------------*/
SdFatSdioEX sd ;    // used to declare the sd.### object (Sdfat)
ADC adc;            // used to declare the adc.### object
DMAChannel dma;     // used to declare the dma.### object
std::array<volatile uint16_t, (uint32_t)128*512> buffer __attribute__ ((aligned (32*1024)));  
char str[128] ;
typeof(*dma.TCD)  tcd_mem[4] __attribute__ ((aligned (32))) ;
/*----------------------------------------------------------------*/


void setup() 
{
   Serial.begin(250000);     
   while(!Serial);

/*TimeSetup-------------------------------------------------------*/
   setSyncProvider(getTeensy3Time); 
   if (timeStatus()!= timeSet) {
    Serial.println("Unable to sync with the RTC");
   } else {
    Serial.println("Begin Setup\n");
   }
/*----------------------------------------------------------------*/

/*ModeSetup-------------------------------------------------------*/
  Serial.println("Please choose a measure-mode");
  Serial.println("Singleended(1) or differential(2)");
  delay(100);
  
  while(Serial.available() == 0){
    }
  char c = Serial.read();
  if (c != '1' && c != '2') {
    Serial.println("Invalid input");
    return;
  }
  if (c=='1'){
    Serial.println("Singleended");
    adc.adc0->analogRead(adc_pin);
  } else{
    Serial.println("Differential");
    adc.adc0->startSingleDifferential(diff_pin1,diff_pin2);
  }
  delay(100);
/*----------------------------------------------------------------*/

/*SamplingRate----------------------------------------------------*/
  Serial.println("Please choose a sampling rate[Hz]");
  delay(100);
  
  while(Serial.available() == 0){
    }
  const uint32_t pdbfreq = Serial.parseInt();
  if (pdbfreq> 300000){
    Serial.println("Sampling rate to high must be 10-300000[Hz)");
  } else {
    Serial.println("OK the sampling rate[Hz] is:");
    Serial.println(pdbfreq);
  }
  delay(100);
/*----------------------------------------------------------------*/

/*FileSetup-------------------------------------------------------*/
  Serial.println("Please choose a file name");
  delay(100);
  
  while(Serial.available() == 0){
    }
  String Date = String(year())+"."+String(month())+"."+String(day())+"-"+String(hour())+"."+String(minute());
  String Name;
  Name = Serial.readString();
  String filename = Name + "_" + Date + ".bin";
  char fname[30];
  filename.toCharArray(fname,30);

       
  if (!  sd.begin()) { sd.initErrorHalt("SdFatSdio   begin() failed"); }    sd.chvol();
  if (!file.open(fname, O_RDWR | O_CREAT)) {  sd.errorHalt("open failed");  } 
  delay(100);
/*----------------------------------------------------------------*/

/*DurationSetup---------------------------------------------------*/
  Serial.println("Please choose the duration[s] of the measurement");
  delay(100);
  
  while(Serial.available() == 0){
    }
  duration = Serial.parseInt();
  bytes = ((duration*1000000)/(1000000/pdbfreq))* 2; //(duration/(1/pdbfreq))*(resolution/8)
  preceil = bytes/BUF_DIM;
  scale = ceil(preceil);
  FILE_SIZE = (scale+2) * BUF_DIM;
  delay(100);
  

/*----------------------------------------------------------------*/


  
  Serial.println("Setup is done, enter any key to start");
  while(Serial.available() == 0){
    }
  delay(1000);
/*----------------------------------------------------------------*/

/*HardcodedSetup-could be made variable---------------------------*/
  pinMode                (             LED_BUILTIN, OUTPUT );
  pinMode                (                 adc_pin, INPUT  );
  pinMode                (               diff_pin1, INPUT  ); 
  pinMode                (               diff_pin2, INPUT  );  
  adc.setAveraging       (                              1  );
  adc.setResolution      (                           16,0  );
  adc.setConversionSpeed ( ADC_CONVERSION_SPEED::HIGH_SPEED);      
  adc.setSamplingSpeed   ( ADC_SAMPLING_SPEED::HIGH_SPEED  ); 
  //adc.enableCompare(1.0/3.3*adc.getMaxValue(ADC_0), 0, ADC_0);
  //adc.enableCompareRange(1.0*adc.getMaxValue(ADC_0)/3.3, 2.0*adc.getMaxValue(ADC_0)/3.3, 0, 1, ADC_0); // ready if value lies out of [1.0,2.0] V
  
  adc.setReference(ADC_REFERENCE::REF_3V3, ADC_0);          
  if(adc.adc0->fail_flag)   {  Serial.print("ADC error: ");    Serial.println(adc.adc0->fail_flag, HEX);  }
    

    dma.source                 (           ADC0_RA);   
    dma.transferSize           (                 2);
    dma.triggerAtHardwareEvent (DMAMUX_SOURCE_ADC0); 
/*----------------------------------------------------------------*/
  
//  ********* y.j.**********

    dma.TCD->CITER    =           32*1024/2   ;      dma.TCD->BITER =  32*1024/2 ;   dma.TCD->DOFF = 2 ;    dma.TCD->CSR   =  0x10; 
    
    dma.TCD->DADDR        = (volatile void*) &buffer [ 0*512]    ;   //Serial.println((uint32_t) &tcd_mem[0]);   
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     1]    ;   //print_config (&dma); 
    memcpy ( &tcd_mem[0], dma.TCD , 32 ) ;   
                                                                                         
    dma.TCD->DADDR        = (volatile void*) &buffer [32*512]    ;   //Serial.println((uint32_t) &tcd_mem[1]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     2]    ;   //print_config (&dma); 
    memcpy ( &tcd_mem[1], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [64*512]    ;   //Serial.println((uint32_t) &tcd_mem[2]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     3]    ;   //print_config (&dma);
    memcpy ( &tcd_mem[2], dma.TCD , 32 ) ;   
                                                                                       
    dma.TCD->DADDR        = (volatile void*) &buffer [96*512]    ;   //Serial.println((uint32_t) &tcd_mem[3]); 
    dma.TCD->DLASTSGA     = (   int32_t    ) &tcd_mem[     0]    ;   //print_config (&dma);
    memcpy ( &tcd_mem[3], dma.TCD , 32 )  ;   
                                                                                     
    memcpy ( dma.TCD ,  &tcd_mem[0], 32 ) ;                          //Serial.println(" tcd0 back");    //print_config (&dma); 
                                                                                             
                                                                                                                                                                             
//  *************       


    dma.enable();      

    adc.enableDMA(ADC_0);     
     
    adc.adc0->stopPDB();         
    adc.adc0->startPDB(pdbfreq);
    NVIC_DISABLE_IRQ(IRQ_PDB); // we don't want or need the PDB interrupt
    
//    analogWriteFrequency(out_pin, 1000);    // PWM output on out_pin for testing purposes.
//    analogWrite(out_pin, 127);   
}




void loop() 
{  

  
  while ( ((128*1024-1) & ( (int)dma.TCD->DADDR - last )) > BUF_DIM )   
  {  
     if (BUF_DIM != (uint32_t)file.write( (char*)&buffer[((last/2)&(64*1024-1))], BUF_DIM) )       { sd.errorHalt("write failed");    } ; 
     Serial.println("."); last += BUF_DIM ;  
  } 
  
  if ( last >= FILE_SIZE ) 
  {   
     file.close();  Serial.println ("\n Done");  
     while(1) { digitalWriteFast(LED_BUILTIN, HIGH) ; delay(100) ; digitalWriteFast(LED_BUILTIN, LOW) ; delay(1000) ; } 
  } ;
  
    
    delayMicroseconds(1);  // can be decreased or suppressed (was just to play, try...)
}

void pdb_isr(void) {  PDB0_SC &=~PDB_SC_PDBIF  ;  Serial.println("."); }; 

void print_config(DMAChannel* dma) {
    Serial.print("channel \t");  Serial.println(dma->channel);
    Serial.print("SADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->SADDR); Serial.println(str);
    Serial.print("SOFF    \t");  Serial.println((uint32_t)dma->TCD->SOFF);
    Serial.print("ATTR    \t");  Serial.println((uint32_t)dma->TCD->ATTR);
    Serial.print("NBYTES  \t");  Serial.println((uint32_t)dma->TCD->NBYTES);
    Serial.print("SLAST   \t");  Serial.println((uint32_t)dma->TCD->SLAST);
    Serial.print("DADDR   \t");  sprintf(str, "%8x", (unsigned int)dma->TCD->DADDR); Serial.println(str);  
    Serial.print("DOFF    \t");  Serial.println((uint32_t)dma->TCD->DOFF);
    Serial.print("CITER   \t");  Serial.println((uint32_t)dma->TCD->CITER);
    Serial.print("DLASTSGA\t");  Serial.println(( int32_t)dma->TCD->DLASTSGA);
    Serial.print("CSR     \t");  Serial.println((uint32_t)dma->TCD->CSR);
    Serial.print("BITER   \t");  Serial.println((uint32_t)dma->TCD->BITER);
    Serial.println();
}
time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

And the matlab code for plotting:

Code:
fid = fopen('FileName','r'); %choose a file
duration = 10; %set your duration in seconds
samplingrate = 200000; %set your samplingrate
samples = (duration/(1/samplingrate)); %get your sample count
m = fread(fid  ,[samples], 'int16'); %decode binary etc
fclose(fid);
x = linspace(0,duration,samples); %xaxis for scaling
c = m*(3.3/2^15); %set the analog scaling in Volt(16bit = 15bit value and 1bit sign)
plot(x,c);
ylabel('Volt');
xlabel('Seconds');

it could be done better but i dont have the knowledge for that.
Here are some images from testing it with the fish:

Petersii200kHz
petersii200.jpg
Petersii300kHz
petersii300.jpg
Marco200kHz
marco200.jpg
Singlespike
petersiispike.jpg

i think it's not that shabby but does any1 has an idea why some of the signals clip at -0.1 or -0.2V ?
And a HUGE thank you for all your help !!
 
Hello Solidhfm,

I am glad to discover your results.
It encourages me to pursue my work . I have still not started to record true signals and you did it... thank's.
Currently I look for how to record 8 channels... using DMA of coarse... I am almost there, remains to test it...
Like you I do "baby steps" and I have many things to do besides...

I was afraid that the noise is more important... It is recorded with differential a10/a11 , isn't it? ( the best case versus noise )

Yes this -0.1 ; -0.2 cliping is strange.
How have you wired things between electrodes, a10, a11, ground?
Do you have a ref and an "active" electrode in the bath?
It is difficult to have an idea without well knowing your wiring.

y.
 
i think it's not that shabby but does any1 has an idea why some of the signals clip at -0.1 or -0.2V ?
And a HUGE thank you for all your help !!

I'm wondering if the code is sometimes reading from the ADC before it has had time to complete its work. I wasn't able to figure out if this guess makes sense from a quick look at github.

Have you looked at the Ring Buffer DMA example in the ADC library? This seems pretty close to what you're doing.
 
@Pictographer

I'm wondering if the code is sometimes reading from the ADC before it has had time to complete its work.

It is unlikely that the DMA process read something else that the ADC which runs "non stop", continuously....
Depending on the SD card speed or "Busy Duration", overflow could occur. Then it would induce a one buffer data lost, but not a 1" data clipping...
Overflow is easy to detect looking at a timer which can be read by dma link between major loop completion... If I have time...

Have you looked at the Ring Buffer DMA example in the ADC library?
Thank's for the link, I have just read it. dma buffer has to be a power of 2 and aligned on its length, moreover it has to be in the dmamem. All that seems very restricting if you whish a big buffer (more than 8 like in the example).
Anyway it cannot be much more faster and the problem would still exits, don't you think?

Let's wait for Solidhfm wiring details.

Thank's for your suggestions.

y.
 
Last edited:
dma buffer has to be a power of 2 and aligned on its length, moreover it has to be in the dmamem. All that seems very restricting if you whish a big buffer (more than 8 like in the example).
DMA buffer must only be aligned properly, but it can be anywhere in the RAM. Size is somewhat arbitrary. But buffer should not be accessed while DMA is writing to, so keeping DMA buffer to say 1kB is reasonable.
Typically, DMA buffers are accessed in a ping-ping fashion. while DMA writes to upper part of buffer, lower buffer is read/written to and vice versa. (The audio-library I2S routines are good examples on DMA access)
 
@WMXZ,

DMA buffer must only be aligned properly, but it can be anywhere in the RAM. Size is somewhat arbitrary. But buffer should not be accessed while DMA is writing to, so keeping DMA buffer to say 1kB is reasonable.
Typically, DMA buffers are accessed in a ping-ping fashion. while DMA writes to upper part of buffer, lower buffer is read/written to and vice versa. (The audio-library I2S routines are good examples on DMA access)

It is true, nevertheless Paul explains clearly (see in the octows2811 library pages) the benefit which there is in the "DMAMEM" part of the memory.

keeping DMA buffer to say 1kB is reasonable.

The size depends on what for you have to buffer. In our case we must be able to record data in the buffer while the SD card writing is blocked by a "busy state" which duration can be as long as a few "100 milli seconds". If I had 1 Mbyte free, I would use it...

y.
 
The size depends on what for you have to buffer. In our case we must be able to record data in the buffer while the SD card writing is blocked by a "busy state" which duration can be as long as a few "100 milli seconds". If I had 1 Mbyte free, I would use it...
1 MByte, yes, but NOT for the Acquisition DMA buffer, but for the SD write buffer.

what I do is in principle:
acquisition ISR copies data (chunks of 256 bytes, or so) to huge 200kB-uSD buffer (on T3.6) . this runs in ISR interrupt mode and continuously.
in the loop() function that only runs when no ISR is running, I check if there are sufficient data in uSD buffer (say 32kByte) and write to uSD.

This way data are written in parallel to acquisition.
The only decision to be made is, what to do when uSD buffer overruns. This is application dependent.
 
But why does it not clip when i'm faster than 200kHz ? i mean i have more samples in less time but no clipping ? it seems there are those two borders where weird stuff can happen.

Here is my wiring:

IMG_20180112_105622.jpg
IMG_20180112_105643.jpg

so both analog electrode signals are grounded via pulldown on the teensy-gnd and go to pins A10 and A11.
 
both analog electrode signals are grounded via pulldown on the teensy-gnd and go to pins A10 and A11.

Isn't it a bit dangerous to be so close to the ground ?
Have you tried a midle point between 3.3 and ground?

But why does it not clip when i'm faster than 200kHz ?
Yes , funny... One could imagine that the sampling mechanic brings some charges and rises the potential... and avoid saturation???
y.
 
Jumping in here, rather late ....
I'm not fond of your electrode - Teensy interface. I would prefer using a "instrumentation amplifier" chip.
Also, this article (http://www.physiology.org/doi/full/10.1152/jn.00757.2011) may help in your endeavours. It shows a schematic to help with the electrode interface concept, but also adds some ideas that may not be suitable for your investigations.

Peter.
 
@Solidhfm,

I am still wondering if this clipping is a bug in the code or a default of the analogic part...

If my eyes have well followed your wires , your wiring is like on the left side of this image?
Then if you have a few more capacitors and resistors, try the right side schematic, it's quality is not better but it could bring an answer about this saturation/clipping...
Middle potential.jpg

y.
 
Could there not be a DC bias on the probes? I would agree that the right-side diagram of yannick's schematic uses AC coupling to avoid such a DC. The article I linked in a previous post (#48) also use AC coupling.
 
Status
Not open for further replies.
Back
Top