ADC library, with support for Teensy 4, 3.x, and LC

A few other comments:

ADC0_CFG1 = 0b00000000;
This means you are running the ADC with a speed of 48 MHz, the specs limit for resolutions less than 16 bits is 18 MHz, so you are running at 3 times the maximum speed! The manual doesn't say if this can lead to actual physical damage of the ADC, or only loss of precision. It's true that you're using the ADHSC bit though, but adding 2 ADCK at 48 MHz (0.04 us!) won't probably be enough (I think min time for a conversion is around 1 us). If it works for you then it's fine, but I mention it so you know that maybe the ADC fails under the stress sooner than it should.

Waiting for calibration to finish. The documentation is confused as to what flag to be waiting for (SC3[CAL] on page 663 and SC1n[COCO] on page 687+688)
When the calibration finishes it sets SC3[CAL] AND SC1n[COCO], so you could check either, but it makes more sense to check SC3[CAL] (and SC3[CALF] to make sure the calibration was successful), as you do.

An idea to make it run even faster (I haven't really thought about this in detail):
Start a continuous measurement on both ADCs using pins A2, and A3 (for example) in the setup already.
In the loop:
- store the values value1,value2.
- start a normal measurement on pins A11, A10 and get the value (like you do now).
- start a continuous measurement again on pins A2, A3.
- process data.

This algorithm is faster if the time that it takes to start the continuous measurement is less than the total time of highSpeed8bitAnalogReadMacro(...), which I think is the case. You'd be measuring the new values of A2, A3 while doing you processing!
To start the continuous thingy you set SC3[ADCO] and write to SC1A.


I read a little bit about your project and you said that you wrote some java program to log the values from the Teensy, I'm interested in something like that to test the ADC in a more systematic way. Can you give me a copy and some instructions on how to use it? (I've never used java).


I'm preparing a big update for the library adding support for different conversion and sampling speeds, adding more synchronous methods (single-ended, differential, both single-shot and continuous), and using bitband for atomic access to the registers so everything is interrupt safe. I won't include methods for analog timers, but there will be examples on how to use IntervalTimer.
 
pedvide,

This is a very useful library for the Teensy 3.1. I would like to use it for some projects that I am doing. However, there is no license information in the code that would allow others to use it, whether that be MIT or LGPL or freely released with needed attribution like much of Paul's code. Could you clarify the license for this library and perhaps add it to the code?

Thanks!
 
drbm:

I hadn't thought about that. I've been reading about the different licenses and I'm thinking of going with LGPL. I understand that this license allows anybody to use the library, even for commercial stuff but if you modify the library itself then your modified copy is also free software (so you have to release the code with all changes).
 
I usually use MIT license for code that gets compiled into firmware. Sometimes I add an extra attribution request or requirement, but still basically MIT.
 
Big update!

Finally I've pushed to github a big update with the following new features:

- Conversion and sampling speed select.
- More synchronized methods.
- Better initial calibration.
- Bitband access, so all functions are interrupt safe

The analog timers aren't supported anymore, they add a lot of complexity to the code. The example analogReadIntervalTimer shows how to use timers with this library.

I've updated the 1st post with all the new information, make sure to check it out.

The license for the library is the MIT.
 
Last edited:
Thanks for the update, can't wait to try it out. Just wondering, for PGA, do we now use 2,4,8 etc...instead of 0-6 like before? Also, was wondering about calling compare. For my measurements, I only care about the raw data and not interested in the actual voltage. If this is the case, do I need to worry about calling these functions?
 
Last edited:
turtle9er:

Yes, originally I programmed the PGA thing very quickly, but now I realized that it was a bit confusing that PGA 1 means 2x, 2=4x, etc, so now PGA 2 means 2x, 4=4x, etc.

The compare functions are useful when you only need to "control" some kind of sensor, or battery, so you are interested in voltages above or below some value, but not the rest.
If you want to know the voltage at all times you don't need them! It just saves a comparison in software.
 
Hi Pedvide,

I finally got to testing the syncro function and now it works. However I have attached the output I had from reading a voltage divider on two pins A2 and A3. So, the same voltage should of been seen at both pins, however I was not getting similar results. Thanks again for all your hard work in this library, it has made my life easier, that is for sure.

View attachment Syncro_ADC_test.txt
 
Please post the final code with which you got that result and also the schematics of your system! (a picture is probably fine).
What's the value of the resistors in your voltage divider?
 
I was just using a 100k trim pot, thing the ratio was 33/67. Is this too high of a resistor? I was worried about using low values and then it drawing more current from the batteries. I will be in the lab tomorrow, and I will try your provided code and see what results that give me. I personally am not using this method right now, it was during helping another forum member that I saw this result.

Code:
/*
ECG measurement Project:
- Sensing two ADC values,
- Analog files being logged on sd card
    Time  A-B  A  B
*/

//Including Libraries
#include <ADC.h>
#include <string.h>
#include <Time.h>  
#include <SdFat.h>
#include <SdFatUtil.h>
#include <BufferedWriter.h>
SdFat sd;
SdFile myFile;

//Teensy 3.1 has the LED on pin 13
const int ledPin = 13;
const int readPin0 = A2;  //ADC read A2
const int readPin1 = A3;  //ADC read A3
const int chipSelect = SS;
//char filename[] = "00000000.txt";
#define SD_CHIP_SELECT  10  // SD chip select pin
#define error(s) sd.errorHalt_P(PSTR(s))
#define FILE_BASE_NAME "CHOP" //Can be max size of 5 character, XXXXX000.BIN DOS 8.3 format
#define TMP_FILE_NAME "TMP_LOG.BIN"
const uint8_t BASE_NAME_SIZE = sizeof(FILE_BASE_NAME) - 1;
const uint32_t FILE_BLOCK_COUNT = 500UL; //500=250kb 1 block=512bytes
const uint32_t ERASE_SIZE = 262144L;
uint32_t bgnBlock, endBlock;
uint8_t*  pCache;
char binName[13] = FILE_BASE_NAME "000.BIN";

ADC *adc = new ADC(); // adc object
ADC::Sync_result result;
float ADCvalue_0 = ADC_ERROR_VALUE;
float ADCvalue_1 = ADC_ERROR_VALUE;
float SDBuffer [128];
int count = 0;
uint32_t bn = 1; //block number
int startTime; //to reset milli count at each recording

//Interval Timer
IntervalTimer timer0;
char c=0;

//------------------------------------------------------------------------------
/*
 * User provided date time callback function.
 * See SdFile::dateTimeCallback() for usage.
 */
void dateTime(uint16_t* date, uint16_t* time) {
  // User gets date and time from GPS or real-time
  // clock in real callback function
  time_t now();
  // return date using FAT_DATE macro to format fields
  *date = FAT_DATE(year(), month(), day());

  // return time using FAT_TIME macro to format fields
  *time = FAT_TIME(hour(), minute(), second());
}
void createFile(){

  bn=1; //reset block count
  count=0;
  // Find unused file name.
  if (BASE_NAME_SIZE > 5) {
  }
  while (sd.exists(binName)) {
    if (binName[BASE_NAME_SIZE + 2] != '9') {
      binName[BASE_NAME_SIZE + 2]++; //changed from +1 since now 0-999 instead of 0-99
    } 
    else {
      binName[BASE_NAME_SIZE + 2] = '0';
      //binName[BASE_NAME_SIZE + 1]++;
      if (binName[BASE_NAME_SIZE+1] == '9') {
        binName[BASE_NAME_SIZE + 1] = '0';
        binName[BASE_NAME_SIZE ]++;
      }
      else{
      if (binName[BASE_NAME_SIZE] == '9') ;
      binName[BASE_NAME_SIZE + 1]++;
      }
    }
 
  }
 // Serial.println(binName);
  // delete old log file
  if (sd.exists(TMP_FILE_NAME)) {
    if (!sd.remove(TMP_FILE_NAME)) ;
  }
  // create new file
  myFile.close();
  if (!myFile.createContiguous(sd.vwd(),
  TMP_FILE_NAME, 512 * FILE_BLOCK_COUNT));
  // get address of file on SD
  if (!myFile.contiguousRange(&bgnBlock, &endBlock));
  // use SdFat's internal buffer
  pCache = (uint8_t*)sd.vol()->cacheClear();
  if (pCache == 0); 
  memset(pCache, '0', 512); 
  // flash erase all data in file
  uint32_t bgnErase = bgnBlock;
  uint32_t endErase;
  while (bgnErase < endBlock) {
    endErase = bgnErase + ERASE_SIZE;
    if (endErase > endBlock) endErase = endBlock;
    if (!sd.card()->erase(bgnErase, endErase)) ;
    bgnErase = endErase + 1;
  }
}

time_t getTeensy3Time()
{
  return Teensy3Clock.get();
}

void setup() {
  Serial.begin(115200);
  setSyncProvider(getTeensy3Time);
  SdFile::dateTimeCallback(dateTime);
  pinMode(ledPin, OUTPUT);
  pinMode(readPin0, INPUT);  //Read analog signal pin1
  pinMode(readPin1, INPUT);  //Read analog signal pin2
  
  //Set ADC (Averaging and Resolution)  
  adc->setAveraging(32); // set number of averages
  adc->setResolution(12); // set bits of resolution
    // it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
  //try low/med speed since load cell has high impedence
  adc->setConversionSpeed(ADC_HIGH_SPEED); // change the conversion speed
  // it can be ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED or ADC_VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_HIGH_SPEED); // change the sampling speed
 //adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_0), 0, ADC_0); // measurement will be ready if value < 1.0V
  // 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->setAveraging(32, ADC_1); // set number of averages
  adc->setResolution(12, ADC_1); // set bits of resolution
  adc->setConversionSpeed(ADC_HIGH_SPEED, ADC_1); // change the conversion speed
  adc->setSamplingSpeed(ADC_HIGH_SPEED, ADC_1); // change the sampling speed
  // always call the compare functions after changing the resolution!
   // adc->enableCompare(1.0/3.3*adc->getMaxValue(ADC_1), 0, ADC_1); // measurement will be ready if value < 1.0V
   // adc->enableCompareRange(1.0*adc->getMaxValue(ADC_1)/3.3, 2.0*adc->getMaxValue(ADC_1)/3.3, 0, 1, ADC_1); // ready if value lies out of [1.0,2.0] V

    
  // Initialize SdFat or print a detailed error message and halt
  // Use half speed like the native library.
  // change to SPI_FULL_SPEED for more performance.
  if (!sd.begin(chipSelect, SPI_FULL_SPEED)) sd.initErrorHalt();

  createFile();  
}

void loop() {
//not sure how you start/stop data collection
  //Example using serial input
    if (Serial.available()) {
	c = Serial.read();
	if(c=='r') { // Start recording
        if (!sd.card()->writeStart(bgnBlock, FILE_BLOCK_COUNT))Serial.println("Recording") ;
        //digitalWrite(LED,ledValue);//Turn LED on to indicate recording if you want
        timer0.begin(timerCallback0, 40000); //start timer 4000=250sps
        startTime=millis();
    } else if(c=='s') { // stop recording
        stopRecording();  
    } 

  }
 delay(1);

}

void stopRecording(){
        timer0.end(); //Turn off timer to stop SD card write
        if (!sd.card()->writeStop())Serial.println("Stopped");
        // Truncate file if recording stopped early.
        if (bn != FILE_BLOCK_COUNT) {    
            if (!myFile.truncate(512L * bn)) ;
        }
        binaryToCsv();
        if (!myFile.rename(sd.vwd(), binName)) ;
        createFile(); 
		}

void timerCallback0(){
      result = adc->analogSynchronizedRead(readPin0, readPin1);
     float ADCvalue_0 = result.result_adc0*3.3/adc->adc0->getMaxValue();
     float ADCvalue_1 = result.result_adc1*3.3/adc->adc1->getMaxValue();
  if (count < 128){ //since using float values, 128x4=512bytes
  //Conversion issue fixed, so don't need code commented out below
   // && (result.result_adc0 !=ADC_ERROR_VALUE) && (result.result_adc1 !=ADC_ERROR_VALUE) 
    SDBuffer[count] = millis()-startTime; //if you don't get a start time, then will be time since program started
    SDBuffer[count+1] = ADCvalue_0-ADCvalue_1; 
    SDBuffer[count+2] = ADCvalue_0;
    SDBuffer[count+3] = ADCvalue_1;
        Serial.print("Time: ");
        Serial.print(SDBuffer[count]);
        Serial.print("\t"); 
        Serial.print("A-B: ");
        Serial.print(SDBuffer[count+1],DEC);  
        Serial.print("\t");  
        Serial.print("A: ");  
        Serial.print(SDBuffer[count+2],DEC);
        Serial.print("\t");  
        Serial.print("B: ");
        Serial.println(SDBuffer[count+3],DEC); 
    count = count + 4;//move index 4 positions for next values
  }
 
  if (count== 128){
   noInterrupts(); 
   memcpy(pCache, &SDBuffer, 512);
       if (!sd.card()->writeData((uint8_t*)pCache)) ;
    bn++; //increment block number
    count = 0; 
    memset(pCache, '0', 512); 
      interrupts();
  }
       
 
}
void binaryToCsv() {
  uint8_t lastPct = 0;
  float buf[128]; //changed to float
  //metadata_t* pm;
  uint32_t t0 = millis();
  uint32_t syncCluster = 0;
  SdFile csvFile;
  char csvName[13];
  BufferedWriter bw;
  
  if (!myFile.isOpen()) {
    Serial.println(F("No current binary file"));
    return;
  }
  myFile.rewind();
  // create a new csvFile
  strcpy(csvName, binName);
  strcpy_P(&csvName[BASE_NAME_SIZE + 3], PSTR(".CSV"));

  if (!csvFile.open(csvName, O_WRITE | O_CREAT | O_TRUNC)) {
    error("open csvFile failed");  
  }
  Serial.println();
  Serial.print(F("Writing: "));
  Serial.print(csvName);
  Serial.println(F(" - type any character to stop"));
  //pm = (metadata_t*)&buf;
  bw.init(&csvFile);
  bw.putStr_P(PSTR("Time,")); //Write header at top of file
  bw.putStr_P(PSTR("A-B,"));
  bw.putStr_P(PSTR("A,"));
  bw.putStr_P(PSTR("B"));
  bw.putCRLF();

  uint32_t tPct = millis();
  while (!Serial.available() && myFile.read(&buf, 512) == 512) {
    uint16_t i;
	//read 512 bytes of data here ie:128 samples
    for (i = 0; i < 128; i++) {
      bw.putNum(buf[i],6); //Change 4 to what ever level of decimals you want
      if((i + 1)%4) { //since 4 data points, it put a comma between 3 points and on the 4th,send a carrige return
        bw.putChar(',');
      } else {
        bw.putCRLF();
      }
    }
    if (csvFile.curCluster() != syncCluster) {
      bw.writeBuf();
      csvFile.sync();
      syncCluster = csvFile.curCluster();
    }
	//looks like code to print out % done
    if ((millis() - tPct) > 1000) {
      uint8_t pct = myFile.curPosition()/(myFile.fileSize()/100);
      if (pct != lastPct) {
        tPct = millis();
        lastPct = pct;
        Serial.print(pct, DEC);
        Serial.println('%');
      }
    }
    if (Serial.available()) break;
  }
  bw.writeBuf();
  csvFile.close();
  Serial.print(F("Done: "));
  Serial.print(0.001*(millis() - t0));
  Serial.println(F(" Seconds"));
}
 
100K is a pretty high source impedance. It can work, but the results will be noisy compared to a lower source impedance.
 
I was just using a setup for another project where I use a 33k/68k resistor to measure battery voltage level. I am not worried if too noisy, since I take the average of 1000 samples.

For the collection I used the same divider and put it on the two pins. Just assumed they would be similar and found it odd how they both stayed consistent

Went with higher divider values since didn't want too much current draw when measuring the battery. Do you suggest I keep the resistors under 10k, or is that still too high?
 
With high impedance you should use lower sampling speeds (adc->setSamplingSpeed(...); ) and see if the issue remains.
 
Should this library be included with the Teensyduino installer?

If you feel it's stable enough, where the 2-5 month release cycle for Teensyduino isn't a huge problem, I'd really like to include it.
 
Hi Paul,

I'd be honored to have it included!
The library is almost "done", the only things left to implement is the DMA stuff (and maybe PDB).

What do you mean by "the 2-5 month release cycle for Teensyduino isn't a huge problem"? I imagine that I can still "publish" updates on github but that the Teensyduino version will only be updated every 2-5 months?
 
Just saw the answer now, sry for the late response.
The java code for the project is available on the blog.
If you need anything else feel free to drop me an email.

A few other comments:

ADC0_CFG1 = 0b00000000;
This means you are running the ADC with a speed of 48 MHz, the specs limit for resolutions less than 16 bits is 18 MHz, so you are running at 3 times the maximum speed! The manual doesn't say if this can lead to actual physical damage of the ADC, or only loss of precision. It's true that you're using the ADHSC bit though, but adding 2 ADCK at 48 MHz (0.04 us!) won't probably be enough (I think min time for a conversion is around 1 us). If it works for you then it's fine, but I mention it so you know that maybe the ADC fails under the stress sooner than it should.


When the calibration finishes it sets SC3[CAL] AND SC1n[COCO], so you could check either, but it makes more sense to check SC3[CAL] (and SC3[CALF] to make sure the calibration was successful), as you do.

An idea to make it run even faster (I haven't really thought about this in detail):
Start a continuous measurement on both ADCs using pins A2, and A3 (for example) in the setup already.
In the loop:
- store the values value1,value2.
- start a normal measurement on pins A11, A10 and get the value (like you do now).
- start a continuous measurement again on pins A2, A3.
- process data.

This algorithm is faster if the time that it takes to start the continuous measurement is less than the total time of highSpeed8bitAnalogReadMacro(...), which I think is the case. You'd be measuring the new values of A2, A3 while doing you processing!
To start the continuous thingy you set SC3[ADCO] and write to SC1A.


I read a little bit about your project and you said that you wrote some java program to log the values from the Teensy, I'm interested in something like that to test the ADC in a more systematic way. Can you give me a copy and some instructions on how to use it? (I've never used java).


I'm preparing a big update for the library adding support for different conversion and sampling speeds, adding more synchronous methods (single-ended, differential, both single-shot and continuous), and using bitband for atomic access to the registers so everything is interrupt safe. I won't include methods for analog timers, but there will be examples on how to use IntervalTimer.
 
Sampling Audio (line/mic)

What would be a good input circuit to sample line and mic level audio using this library?
 
Sorry I can't give you any information on that, my knowledge of hardware is very limited. Maybe ask in the Audio library thread, where specialist in audio can read it.
 
Hi Pedvide,


I think the ADC in teensy3.x is quicker as described, I think about 8 Msps (8bit).

I tested teensy 3.1 with your library with Sinus-Signal between 20 kHz and about 80 kHz in 8-bit mode. I need for 239 samples ca 41 µs with one input channel, ca 70µs with 2 channels . The 8 bit conversion needs for 239 samples (on my GLCD) ca 30 µs (ca 10 µs needs the for ... loop) . It's ca 0.126 µs/samples equal ca 8.5 Msps. I tested it with function micros() (see in the code).

with these settings in setup:

// ADC_0 on Pin23 / A9
adc->setAveraging(1, ADC_0); // set number of averages
adc->setResolution(8, ADC_0); // set bits of resolution
adc->setConversionSpeed(ADC_VERY_HIGH_SPEED,ADC_0); // change the conversion speed
adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED,ADC_0); // change the sampling speed

// ADC_1 on Pin16 / A2
adc->setAveraging(1, ADC_1); // set number of averages
adc->setResolution(8, ADC_1); // set bits of resolution
adc->setConversionSpeed(ADC_VERY_HIGH_SPEED, ADC_1); // change the conversion speed
adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED, ADC_1); // change the sampling speed

adc->startContinuous(readPin0, ADC_0);
adc->startContinuous(readPin1, ADC_1);

...

and in the loop - section:


...
ms = micros();
uint8_t xpom = 240; // max x counts on the Display (2.2 ")
for(uint8_t xpo=0; xpo < xpom; xpo++) {

SampleC[xpo] = adc->analogReadContinuous(ADC_0);// read a new value, will return ADC_ERROR_VALUE if the comparison is false.

SampleD[xpo] = 20 + adc->analogReadContinuous(ADC_1); // <<<<<<<<<<<<<<<<<<<<<<<<<<
// the addition of 20 is to show the two equal-signals (I have only one signal-source) on different points on the display
}

ms = micros() - ms;


mausi_mick
 
Last edited:
analogReadContinuous will always return a value, but it doesn't mean it's a new one. I imagine you are getting a number of duplicate values. The speed you are measuring is the speed at which the loop function executes!
The best thing with continuous functions is to use interrupts, otherwise you can use bool isComplete(adc_num).
 
Thanks for your information.
But I think it's , I need it for a small display, there it is not so critical with 7 or 8 bit resolution.
I tested it with function : isComplete(), but the conversion is more than 4 times slower.


T
 
Last edited:
If it works for you then use it, of course. But don't think that you are actually measuring a new value every 0.126 us! The fastest you can measure new values is 1262 kHz, or 0.79 us, and that's using settings that are out of specs.
 
Why do the ADC read methods return signed int values?

--Edit--
Oh of course. Error codes.
 
Last edited:
Back
Top