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

Where did you see 632K sample rate?

I think 5uS is about as fast as it gets, don't recall if doing dual reads is any faster.

edit.

Going back to some of my old posts I reported 4.3uS at 72Mhz while using synchronized reads, I wasn't using the fastest speed settings either so you may be able to trim it more but what do you plan to do if your running at such a high sample rate?
 
Last edited:
Somewhere above is my DMA code for 2.7usec/sample. I'd look into using both ADCs in continuous DMA mode and then write code to process data as fast as possible from the two DMA buffers. Need even more speed? Then I'd use a faster CPU (like a rasberry pi zero) with an external parallel ADC.
 
Somewhere above is my DMA code for 2.7usec/sample. I'd look into using both ADCs in continuous DMA mode and then write code to process data as fast as possible from the two DMA buffers. Need even more speed? Then I'd use a faster CPU (like a rasberry pi zero) with an external parallel ADC.

Zero is $5 :O
 
If you want to do an average of samples then it should be a bit faster if you set the averages to max and take fewer samples. For example if you want to take 100 samples then setAveraging(32) and take 3 or 4 samples. There's a small startup time that only happens once when you average. In your code you have that startup time every sample. The actual time is somewhere in the manual.
 
First,thanks for your help!
Pedvide, here is what you said about the attainable read times back in post #141(Which amounts to a whopping 1.265Msps!):

The absolute fastest measurement time is 0.79 us, with no averaging, 8 bits and both sampling and conversion speeds at ADC_VERY_HIGH_SPEED. This speed is actually out of specs (fastest measurement time within specs is 1.58 us), so 8 bit precision is not even guaranteed!
Those times were obtained with the continuous mode, because single measurements have a slight longer conversion time. That means that you should use the continuous mode. This means that you convert values at that speed and you read one when you need it. The problem is that last measurement may have been started up to 0.79us ago (no longer than that, otherwise you'll get a fresh value), and because you can't synchronize the conversions (or maybe you can?) that delay can drift from 0 to 0.79us.

And this is what paul said at post #123:

Well, there's 2 parts to that statement.

Part 1: "632 KHz is the fastest we can do within the specs"

This means 632000 samples per second is the maximum speed. That seems pretty simple, doesn't it?

Maybe you're seeing mention of other frequencies like 18 MHz. Those are internal settings. Perhaps a more beginner oriented explanation would omit those finer details of stuff going on internally.

Ultimately, the sample answer is 632 kHz is the maximum speed. You may wish for it to be faster, but wishful thinking will not make it so. Misunderstanding related specs for internal timing will not change the speed.


Part 2: "only if the sample's impedance is low enough"

Just because Teensy measures the voltage 632000 times per second doesn't necessarily mean your signal is that good.

Even though 300 kHz of signal bandwitdh (Nyquist sampling theory says your signal can be up to half the sample rate... any more causes terrible problems called "aliasing") doesn't seem like a large number compared to digital clock speeds of modern processors, in fact delivering a 300 kHz bandwidth analog signal isn't always easy. One of the big challenges involves the strength (or source impedance) of your signal. If your signals goes through resistors or inductors, or if the source of your signal just isn't very strong due to the type of sensor, you'll almost certainly need a high quality amplifier to deliver a strong (low impedance) signal to Teensy.

Some of the optional settings allow for weaker (higher impedance) signals, but at reduced speed. Such settings could be really useful for other people, who might prefer slower speed instead of the cost and complexity of adding a special amplifier circuit. But if you want to run as fast as possible, the highest speed settings only work with very strong signals.

Sooo... I think Im missing something here..
 
Last edited:
I haven't tested your code, but I think the problem is that you're calling analogRead every loop instead of calling startContinuous in the setup and read values when it's ready (see examples). The continuous mode is faster because there's no software or hardware delay setting up all registers again as with every call to analogRead. The manual says that 632 KHz is the fastest you can do WITHIN SPECS, if you use the fastest settings then you can go to 1.265MHz, but the accuracy is not guarantied anymore and the manual isn't clear whether hardware damage can occur (I doubt it, but I can't say). See the examples for the continuous measurements.

What I'd do to get 100 samples as fast as possible is setup the continuous mode, with 32 averages. In the loop you wait until the measurement is done (or use an interrupt) and increase a counter. When you get 3 or 4 measurements (96 to 128 actual samples) you stop the continuous mode.
 
Here is a a post from Pedvide way back last year, but is helpful in using Continuous reads to the max.
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).
 
Pedvide, I tried switching to continues as in the examples. no change.. a loop of 100 times "val = (uint16_t)adc->analogReadContinuous();" still took 520us.
Any ideas? maybe my registers are not configured correctly(I manipulated them in previous sketches)?
How do i restore the teensy registers back to default?
Thanks again

P.S
Donziboy2, I need the fast sampling because Im using it in a high speed shaft balancer
 
OK figured it out.. just used the wrong data type for the accumulated sum for averaging.. now i get 1.7us per sample, which is good enough
Thanks all!
 
OK, Here goes:

Code:
#include <ADC.h>


ADC *adc = new ADC(); 
unsigned int rate=0, i=0, samples=100, val=0;
unsigned long microslast=0, g=0;

void setup() {

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


    adc->setAveraging(0); 
    adc->setResolution(8); 

    
    adc->setConversionSpeed(ADC_VERY_HIGH_SPEED); 

    adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED); 

    adc->startContinuous(A9);

    delay(500);
}

void loop() {

  i=0;
  microslast=micros();
  while(i<samples){
    if( adc->isComplete()){
     val = (uint8_t)adc->analogReadContinuous(A9);
    g=g+val;
    i++;
    }
  }
  rate=micros()-microslast;
  g=g/samples;
  Serial.print(rate);
  Serial.print("  ");
  Serial.println(g);
  
}

The output is 168-170us per 100 samples..
 
guyst,

Indeed setting the averages to 32 and samples=3 you get 96 real samples in 58 us, that is 0.604 us per sample or 1655 ksps!
You also had a small bug in your code. You have to reset g to zero every loop, otherwise you're doing a rolling average and have to divide by samples+1 (samples you make in the while{...} plus the sample you carry from the loop before).

The results of the tests are clear now:
  • If you want to do a SINGLE measurement as fast as possible then set averages to 0, resolution to 8. Use the continuous mode if possible.
  • If you want some sort of average then it's better to use the hardware average to the maximum extent and take fewer software samples (use the continuous mode as well).


Code:
#include <ADC.h>


ADC *adc = new ADC();
unsigned int rate=0, i=0, samples=3, val=0;
unsigned long microslast=0, g=0;

void setup() {

    pinMode(A9, INPUT);

    Serial.begin(9600);


    adc->setAveraging(32);
    adc->setResolution(8);


    adc->setConversionSpeed(ADC_VERY_HIGH_SPEED);

    adc->setSamplingSpeed(ADC_VERY_HIGH_SPEED);

    adc->startContinuous(A9);

    delay(500);
}

void loop() {

  i=0;
  microslast=micros();
  g=0;
  while(i<samples){
    if( adc->isComplete()){
     val = (uint8_t)adc->analogReadContinuous(A9);
     g=g+val;
     i++;
    }
  }
  rate=micros()-microslast;
  g=g/samples;
  Serial.print(rate);
  Serial.print("  ");
  Serial.println(g);

}
 
Set RingBuffer size when declaring it

In my project I am using the RingBuffer provided in the ADC library. However I wanted a value other than the default ring buffer size. So I asked a friend to help me and he came up with a modification. The only change to the code calling this function is that when the RingBuffer is declared in your code the size must be included within the parenthesis.

Attached you will find a zip file that includes the modified code for running on a PC and test code with the RingBuffer code for the original and modified versions.

Unfortunately the modified code no longer uses the default ringBuffer size if there is no size parameter included ie the parenthesis are empty.

In addition the code does not check to ensure that the size is a power of two. There is a line of code below that shows how this can be done.

Both of these should be easy to added by a knowledgable C++ programmer, which I am not. I hope this code in current or modified form will migrate into the ADC library, improving an already excellent product.

My test code is:

Code:
#include "RingBuffer.h"

RingBuffer *ringBuffer = new RingBuffer(16);
unsigned int i;

void setup() {
  // put your setup code here, to run once:
  Serial.begin(9600);
  delay(2000);
  Serial.println("Starting....");

  for (i = 0; !ringBuffer->isFull(); i++) {
    ringBuffer->write(i);
    Serial.println(i);
  }

  Serial.println("\nPrint Buffer is filled\n");

  for (i = 0; !ringBuffer->isEmpty(); i++)
    Serial.println(ringBuffer->read());
}

void loop() {
  // put your main code here, to run repeatedly:
  delay(1000);
}

And the file RingBuffer.h is:

Code:
/* Teensy 3.x, LC ADC library
 * https://github.com/pedvide/ADC
 * Copyright (c) 2015 Pedro Villanueva
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#ifndef RINGBUFFER_H
#define RINGBUFFER_H

// include new and delete
//#include <Arduino.h>

// THE SIZE MUST BE A POWER OF 2!!
#define RING_BUFFER_DEFAULT_BUFFER_SIZE 8



/** Class RingBuffer implements a circular buffer of fixed size (must be power of 2)
*   Code adapted from http://en.wikipedia.org/wiki/Circular_buffer#Mirroring
*/
class RingBuffer
{
    public:
        //! Default constructor, buffer has a size DEFAULT_BUFFER_SIZE
//        RingBuffer();

		// constructor for custom size
        RingBuffer(int size);

        /** Default destructor */
        virtual ~RingBuffer();

        //! Returns 1 (true) if the buffer is full
        int isFull();

        //! Returns 1 (true) if the buffer is empty
        int isEmpty();

        //! Write a value into the buffer
        void write(int value);

        //! Read a value from the buffer
        int read();

    protected:
    private:

        int increase(int p);

		int b_size = 0;//RING_BUFFER_DEFAULT_BUFFER_SIZE;
        int b_start = 0;
        int b_end = 0;
        int *elems;
//		int elems[];//[RING_BUFFER_DEFAULT_BUFFER_SIZE];
};


#endif // RINGBUFFER_H

And the file RingBuffer.cpp is:

Code:
/* Teensy 3.x, LC ADC library
 * https://github.com/pedvide/ADC
 * Copyright (c) 2015 Pedro Villanueva
 *
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS
 * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
 * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

#include "RingBuffer.h"

RingBuffer::RingBuffer(int size)
{
	if (b_size != 0) { delete []elems; b_size = 0; } 

	// check for 
	int tmp = size; 
	while ((tmp != 0) && ((tmp & 0x1) == 0)) tmp >>= 1;
	if (tmp != 1) return;
	
	b_size = size;
    b_start = 0;
    b_end = 0;
	elems = new int[size];
}

RingBuffer::~RingBuffer()
{
    // dtor
	if (b_size != 0) delete []elems; 
}

int RingBuffer::isFull() {
    return (b_end == (b_start ^ b_size));
}

int RingBuffer::isEmpty() {
    return (b_end == b_start);
}

void RingBuffer::write(int value) {
    elems[b_end&(b_size-1)] = value;
    if (isFull()) { /* full, overwrite moves start pointer */
        b_start = increase(b_start);
    }
    b_end = increase(b_end);
}

int RingBuffer::read() {
    int result = elems[b_start&(b_size-1)];
    b_start = increase(b_start);
    return result;
}

// increases the pointer modulo 2*size-1
int RingBuffer::increase(int p) {
    return (p + 1)&(2*b_size-1);
}

Force the Ring Buffer size to be a power of 2 and limit its size

Code:
unsigned int adcRingBufferSize = pow(2, ceil(log(adcRingBufferSizeRequested) / log(2)));
adcRingBufferSize = (adcRingBufferSize < 8) ? 8 : adcRingBufferSize;
adcRingBufferSize = (adcRingBufferSize > 8192) ? 8192 : adcRingBufferSize;
 

Attachments

  • RingBuffer.zip.zip
    9.7 KB · Views: 130
Last edited:
This looks like a very nice library. Thank you for your hard work!
I read thru the examples and some of the code, perhaps I missed it, but have you addressed cycling the mux thru multiple channels in ContinousRead mode?
I am starting a project where I need to read 4 current sensors as quickly as possible, and voltage and temp occasionally. I've written similar code in the past for pic32, I know how, but don't have the time in this instance.
Thanks in advance
Bart
 
After reading the device manual for some time it appears that the ADC in the k20 is somewhat different than I expected. There is no notion of automatically clocking the input channels to take readings on a set of inputs as a group, auto triggering thru the lot. So, I recognized that I will need to respond to the conversion complete interrupt and switch input channels programmatically. Hopefully this will not impact the sample rate to badly, Any Ideas on this??

Also I notice that the ADC has a temp sensor available internally, and that Pedro included it in his source files. But it also appears that the checkPin call will flag it as illegal. Can anyone confirm or deny, or better yet give a work-around. There is a ADC_TEMP_SENSOR #define so hopefully this is accessible / usable.
 
So I took a look thru the processor manual and found a link to another doc
HTML:
http://cache.freescale.com/files/microcontrollers/doc/app_note/AN3031.pdf
that discusses how to transform the ADC readings from the temp sensor. Here is some code I cobbled together that appears to work.
Code:
float vtemp, temp, divi;

	value = adc->analogRead(ADC_TEMP_SENSOR); // read a new value
	vtemp = value * 0.00322; // assumes 3.3vref and 10 bit resolution :: 3.3/1024
	if (vtemp >= 0.7012)
	{
		divi = 0.001646;
	}
	else
	{
		divi = 0.001749;
	}
	temp = 25 - ((vtemp - 0.7012) / divi);

	Serial.printf(" %d, %4.1f \n", value, temp );

Hope it helps
Bart
 
I have put some code together to read the internal temperature sensor and/or an external thermister. First I read it with a non-blocking call and then with a blocking call with an interrupt. Trouble is the ISR to read the value when the non-blocking call is executed does not execute.

The code is:

Code:
#include <ADC.h>

#define  ADC_PIN  38     // choose between A4 for external thermister, or 38 for chip internal temperature


ADC *adc = new ADC();  // adc object

uint16_t tempRaw, adcMax;
float tempVolt, temp;

void setup() {
  // put your setup code here, to run once:
  pinMode(ADC_PIN, INPUT);

  adc->setReference(ADC_REF_1V2, ADC_0);  //ADC_REF_3V3, ADC_REF_1V2, ADC_REF_EXT
  adc->setAveraging(32, ADC_0);
  adc->setResolution(12, ADC_0); // see ADC.h
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_0); // see ADC.h for options
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_0); // see ADC.h for options

   adc->enableInterrupts(ADC_0);


  adcMax = (float)adc->getMaxValue(ADC_0);

  Serial.begin(9600);
  delay(2000);
  Serial.println("Starting ...");
}

void loop() {
  // put your main code here, to run repeatedly:
  tempRaw = (uint16_t)analogRead(ADC_PIN);
  tempVolt = 1.2 * (float)tempRaw / (float)adcMax;
  if (ADC_PIN == 38)
    temp = 1000.0 / 1.715 * tempVolt - 1000.0 / 1.715 * 0.676125;
  else
    temp = 1000.0 / 19.5 * tempVolt - 1000.0 / 19.5 * 0.4;

  Serial.print(temp);
  Serial.println("  from blocking ADC");
  delay(200);
//  attachInterruptVector(IRQ_ADC0, readIntTemp_isr);
  // NVIC_SET_PRIORITY(readIntTemp_isr, 0);    // 0 is highest priority
  adc->startSingleRead(ADC_PIN);
  delay(1500);
}


// Interrupt single-ended read data
void adc0_isr() { //readIntTemp_isr() {
  Serial.print("ISR read started - ");
  noInterrupts();
  tempRaw = adc->readSingle();
  tempVolt = 1.2 * (float)tempRaw / (float)adcMax;
  if (ADC_PIN == 38)
    temp = 1000.0 / 1.715 * tempVolt - 1000.0 / 1.715 * 0.676125;
  else
    temp = 1000.0 / 19.5 * tempVolt - 1000.0 / 19.5 * 0.4;
  Serial.print(temp);
  Serial.println(" from ISR ADC");
  interrupts();
}

This gives the output on the serial monitor

HTML:
Starting ...
20.29  from blocking ADC
20.12  from blocking ADC
19.94  from blocking ADC

and never output "ISR read started"

Any help would be appreciated on this.
 
I have a non-blocking (interrupt) based MUX data acquisition working here. The ADC conversion is called and when complete it calls an interrupt. When the interrupt service routine has read the value into a ringbuffer, another ADC call is made. As many of these can be daisy chained together as you want. One way to change the sampling rate is to decimate the data streams coming in. There is an example of how to handle things when the ADC has multiple calls simultaneously, but I would prefer not to go there.

The loop portion of the code is used to read the values from the ringbuffer when it is not empty. I have read the internal chip temperature here. Make sure you read the initial portion of this thread to have the right ADC read the pin you are interested in. https://forum.pjrc.com/attachment.php?attachmentid=1792&d=1396800484 I didn't initially and it caused problems.

I would like to extend this to have the ADC setup saved in registers, so that instead of calling a function to set all the ADC parameters I would only have to load a register, but have not achieved that goal yet.

My code is ...

Once you know everything is working it is time to remove the print statements.

Code:
#include <IntervalTimer.h>
#include <ADC.h>
#include <RingBuffer.h>    // normally use this line
//  #include "RingBuffer.h"     // I have RingBuffer in local directory
#include <math.h>

// The following from ADC_Module.h
// Other things to measure with the ADC that don't use external pins
// In my Teensy I read 1.22 V for the ADC_VREF_OUT (doesn't exist in Teensy LC), random values for ADC_BANDGAP,
// 3.3 V for ADC_VREFH and 0.0 V for ADC_VREFL.
#define ADC_TEMP_SENSOR     38 // 0.719 V at 25\BAC and slope of 1.715 mV/\BAC for Teensy 3.x and 0.716 V, 1.62 mV/\BAC for Teensy LC
#define ADC_VREF_OUT        39
#define ADC_BANDGAP         41
#define ADC_VREFH           42
#define ADC_VREFL           43

#define LED_POWER   13

// adc variables
ADC *adc = new ADC();  // adc object
ADC::Sync_result adcValues;
ADC_Module::ADC_Config ADCtemperatureConfig;
void setADCdifferential(), setADCsingle(); // setup ADC
bool startADC;


RingBuffer *temperatureRingBuffer1 = new RingBuffer(), *temperatureRingBuffer2 = new RingBuffer();

const float Fs = 128;                        // force integer sampling rate and a multiple of Fw (assuming Fw <= 3 decimal places

// Timer setup and variables to call interrupt for ADC initialization
IntervalTimer samplingTimer; // interrupt timer for sampling
int timerReturnValue, timerPriorityReturnValue;  // timer return values for testing


elapsedMicros ISRtime = 0;
uint32_t ISRtime01;

uint32_t temperature1Raw, temperature2Raw;

void setup() {
  // initialize the light & pins
  pinMode(LED_POWER, OUTPUT);
  pinMode(A2, INPUT);
  pinMode(A3, INPUT);
  pinMode(A4, INPUT);
  pinMode(A5, INPUT);
  pinMode(A10, INPUT);
  pinMode(A12, INPUT);
  pinMode(38, INPUT);


  Serial.begin(9600);  // for debugging or datalogging
  delay(2000);
  Serial.println("Starting Serial Port");

  // set ADC parameters

  Serial.print("ISR Frequency  ");
  Serial.println(Fs);

  // set interrupt timer with sampling time in microseconds
  timerReturnValue = samplingTimer.begin(sampleBeginISR, 1e6 / Fs);

  // enable adc interrupts just before the main loop
  adc->enableInterrupts();  // call interrupt to read value upon completion of ADC
}


void sampleBeginISR(void) {
  noInterrupts();
  // Start ADC
  setADCsingle1();           // set ADC for single-ended measurement
  Serial.print("StartADC0 a - Successful - ");
  attachInterruptVector(IRQ_ADC0, singleReadADC0_isr1);
  startADC = adc->startSingleRead(38, 0);
  Serial.println(startADC);
  interrupts();
}

void sample2isr() {
  noInterrupts();
  setADCsingle2();           // set ADC for single-ended measurement
  Serial.print("\tStartADC0 b - Successful - ");
  attachInterruptVector(IRQ_ADC0, singleReadADC0_isr2);
  startADC = adc->startSingleRead(38, 0);
  Serial.println(startADC);
  interrupts();
}


// Single-ended read data and reset for differential measurement
void singleReadADC0_isr1() {
  // put single-ended values into a ring buffer
  noInterrupts();
  Serial.print("\tStartADC0 b - Successful - ");
  temperatureRingBuffer1->write(adc->readSingle());
  attachInterruptVector(IRQ_ADC0, singleReadADC0_isr2);
  startADC = adc->startSingleRead(38, 0);
  Serial.println(startADC);
  Serial.println("Read temperature 1 ADC subroutine executed");
  interrupts();
}

// Single-ended read data and reset for differential measurement
void singleReadADC0_isr2() {
  // put single-ended values into a ring buffer
  noInterrupts();
  temperatureRingBuffer2->write(adc->readSingle());
  interrupts();
  Serial.println("Read Temperature 2 ADC subroutine executed");
}

void setADCsingle1() {
  Serial.println("setADCsingle 1 subroutine executed");
  // ADC 0
  // one of ADC_REF_3V3, ADC_REF_1V2, ADC_REF_EXT.
  //adc->setReference(ADC_REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

  adc->setReference(ADC_REF_1V2, ADC_0);  //ADC_REF_3V3, ADC_REF_1V2, ADC_REF_EXT
  //  adc->enablePGA(1, ADC_0); // options are 1, 2, 4, 8, 16, 32 or 64
  adc->setAveraging(64, ADC_0);
  adc->setResolution(ADC_MED_SPEED, ADC_0); // see ADC.h
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_0); // see ADC.h for options
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_0); // see ADC.h for options

  // save configuration
  // ADC_Module::saveConfig(*ADCtemperatureConfig);
}

void setADCsingle2() {
  Serial.println("setADCsingle 2 subroutine executed");
  // ADC 0
  // one of ADC_REF_3V3, ADC_REF_1V2, ADC_REF_EXT.
  //adc->setReference(ADC_REF_1V2, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

  adc->setReference(ADC_REF_1V2, ADC_0);  //ADC_REF_3V3, ADC_REF_1V2, ADC_REF_EXT
  //  adc->enablePGA(1, ADC_0); // options are 1, 2, 4, 8, 16, 32 or 64
  adc->setAveraging(1, ADC_0);
  adc->setResolution(ADC_VERY_HIGH_SPEED, ADC_0); // see ADC.h
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED_16BITS, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setConversionSpeed(ADC_VERY_HIGH_SPEED, ADC_0); // see ADC.h for options
  // one of ADC_VERY_LOW_SPEED, ADC_LOW_SPEED, ADC_MED_SPEED, ADC_HIGH_SPEED, ADC_VERY_HIGH_SPEED
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_0); // see ADC.h for options

  // save configuration
  // ADC_Module::saveConfig(*ADCtemperatureConfig);
}



void loop() {


  // check temperature only if the temperature ring buffer is not empty
  if (!temperatureRingBuffer2->isEmpty()) {

    // read data from ringBuffer
    noInterrupts();
    temperature1Raw = temperatureRingBuffer1->read();
    temperature2Raw = temperatureRingBuffer2->read();
    interrupts();

    Serial.print("Read Ring Buffer");
    Serial.print("\t");
    Serial.print(temperatureRingBuffer1->isEmpty());
    Serial.print("\t");
    Serial.print(temperature1Raw);
    Serial.print("\t");
    Serial.println(temperature2Raw);

  }
}
 
Jake, regarding your post #243.
Using your code on a Teensy 3.0 it works:
Code:
Starting ...
23.53  from blocking ADC
ISR read started - 23.36 from ISR ADC
23.02  from blocking ADC
ISR read started - 23.02 from ISR ADC
22.68  from blocking ADC
ISR read started - 22.68 from ISR ADC
22.51  from blocking ADC
ISR read started - 22.51 from ISR ADC
22.34  from blocking ADC
ISR read started - 22.34 from ISR ADC
22.17  from blocking ADC
ISR read started - 22.17 from ISR ADC

On a Teesny 3.1 however it doesn't:
Code:
Starting ...
23.87  from blocking ADC
23.19  from blocking ADC
23.02  from blocking ADC
22.68  from blocking ADC
22.51  from blocking ADC
22.34  from blocking ADC
21.99  from blocking ADC

I've found the "bug" to be in the way adc->startSingleRead(ADC_PIN); is executed. In Teensy 3.0 there's only one ADC, so the call is forwarded to adc0->startSingleRead(ADC_PIN) and everything works.
However in Teensy 3.1 there are two ADCs, and adc->startSingleRead(ADC_PIN) checks a few things. First it checks which ADC can read that pin, in this case both can. Then it checks which ADC has less work (if for example one is already doing a measurement) and forwards the call to the ADC with less workload. In case both had equal work (in this case both had zero), then it assigned it to ADC_1. I originally did that because I assumed that ADC_0 will always get more requests, however now I see that people usually assume that if you don't specify the ADC manually then it should default to ADC_0. I've changed the code to reflect that.

In any case the problem could be solved by calling adc->startSingleRead(ADC_PIN, ADC_0). In Teensy 3.1, calling a measurement on a pin that can be accessed by both ADCs without specifying the desired ADC is a bit ambiguous, now I made sure that ADC_0 will be selected first.
 
Jake, regarding your post #244,

The correct syntax for saving and loading all ADC config registers is:
Code:
// save configuration into ADCtemperatureConfig
  adc->adc0->saveConfig(&ADCtemperatureConfig);
// load configuration from ADCtemperatureConfig
  adc->adc0->loadConfig(&ADCtemperatureConfig);

I haven't tested it on your code, as it's very complex, but I use those functions internally with no problem.
 
bartt, regarding your post #241,

There are at least 3 ways of doing that:

1. Manually in the loop, go over all pins and measure them. Slow but simple.
2. Using interrupts. Start a measurement, when it finishes start a new one in the isr, and so on. Jake has some similar code below, but I think it's better to use a single isr function. This should be fast, but it gets complicated.
3. Using DMA. The fastest but more complicated way. When the ADC finishes a measurement, DMA will automatically copy the result into a result array, and copy a new pin into the SC1A register, so that the ADC will begin a new conversion. You need to setup two DMA channels, one for the results and one for the SC1A registers. The core DMAChannel.h functions will help immensely in this.

In your specific case if you have a Teensy 3.1/3.2 you can measure the 4 channels at the same time! Use the synchronous functions in both ADCs.
 
I am way late to this party!

I have a LiPo battery on my custom Teensy board that reads 3.81 V with the voltmeter. I have a 100 K/100K voltage divider connected to pin 15 (A1) and I am using this code:

Code:
#include <ADC.h>

ADC *adc = new ADC(); // adc object;

const uint8_t ledPin1 = 27;
const uint8_t ledPin2 = 28;
const uint8_t ledPin3 = 29;
const uint8_t VbatPin = A1;

uint16_t VBAT;

void setup() 
{

  Serial.begin(9600);
  pinMode(ledPin1, OUTPUT);
  pinMode(ledPin2, OUTPUT);
  pinMode(ledPin3, OUTPUT);
  pinMode(VbatPin, INPUT); //pin 15 (A1) single ended

    ///// ADC0 ////
    // reference can be ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT.
    adc->setReference(ADC_REF_3V3, ADC_0); // change all 3.3 to 1.2 if you change the reference to 1V2

    adc->setAveraging(4); // 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
    // see the documentation for more information
    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
}

void loop() 
{

 digitalWrite(ledPin1, LOW);
 digitalWrite(ledPin2, HIGH);
 digitalWrite(ledPin3, HIGH);
 delay(1000);
 

 digitalWrite(ledPin1, HIGH);
 digitalWrite(ledPin2, LOW);
 digitalWrite(ledPin3, HIGH);
 delay(1000);
 
 digitalWrite(ledPin1, HIGH);
 digitalWrite(ledPin2, HIGH);
 digitalWrite(ledPin3, LOW);
 delay(1000);

 VBAT = adc->analogRead(VbatPin); // read a new value, will return ADC_ERROR_VALUE if the comparison is false.
 
 Serial.print("VBAT = "); Serial.print(2.*VBAT*3.3/adc->getMaxValue(ADC_0), 2); Serial.println(" V");
}

to read the voltage. I multiply by two since I am dividing the voltage by two on the board.

With just analogRead and no ADC library I have a lot of jitter and the voltage was too high by about 10% on average. With the ADC library I can get very stable values if I choose low sampling rates, but the voltage values I read are way high. For the LOW_SPEED sampling I am getting 4.80 V and for HIGH_SPEED sampling I am getting 4.38 V.

I am sure i am doing something wrong but this is not what I expected!

Can you offer some advice for making this right?
 

Add a 0.1u cap between the gnd and the A1 pin. The Teensy has a hard time reading anything with very high resistance values(>10K).
You can avoid multiply and divide time sinks by using bit shifting. Also be aware that your 3.3V may not always be true. It may actually be 3.25 or 3.35 which will throw things off.
Code:
x << 1
I have not tried the library averaging. I store 16 values in an array and then add them up and do a bit shift.
Code:
    for (i = 0; i < 16; i++) {   //busv averaging

      n = n + busv[i];

      if (i == 15) {            //average and save on last cycle
          busvaverage = n >> 4;
          busvaverage = vadcmultiplier * busvaverage;
          busvaverage = busvaverage / 1000;         
      }
    }
Just make sure the variable is 16 bit or bigger when working with 12bit adc values (4095*16 = 65520).
 
Back
Top