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

Pedvide

Senior Member+
Hello everybody,

This ADC library implements most of the Teensy ADC functionality. https://github.com/pedvide/ADC

The library supports Teensy 3.0, 3.1/3.2, LC, 3.5, 3.6, and 4.

The main class is ADC.
Code:
ADC *adc = new ADC(); // adc object
This object controls the ADCs. For example, to measure the voltage at pin A9, you can do
Code:
int value = adc->analogRead(A9);
just as with the old version of this library. If you have a Teensy 3.0 it will return the value measured by the only ADC module.
If you have a Teensy with more than omne ADC module (3.1, 3.2, 3.5, 3.6 and 4), it will look whether this pin can be accessed by ADC0, ADC1 or both. If only one ADC module can access it, it will use it. If both can do it, it will assign this measurement to the ADC with less workload (for example if one is doing a continuous measurement already).
If you have a preference for one module in particular you can do:
Code:
int value = adc->analogRead(A9, ADC_0);
If the pin can't be accessed by the ADC you selected it will return ADC_ERROR_VALUE.

It's also possible to access directly the adc modules with
Code:
adc->adc0->function
adc->adc1->function


Overview of modes of conversion

All modes of conversion of the ADC are implemented in the same fashion:
Using 1 ADC
Single-shot modeSingle-shot mode Continuous mode
return valuereturn immediately
analogReadstartSingleReadstartContinuous
analogReadDifferentialstartSingleDifferentialstartContinuousDifferential
readSingle()analogReadContinuous()
stopContinuous()
Using both ADCs (Synchronous mode)
Single-shot modeSingle-shot mode Continuous mode
return valuereturn immediately
analogSyncReadstartSynchronizedSingleReadstartSynchronizedContinuous
analogSyncReadDifferentialstartSynchronizedSingleDifferentialstartSynchronizedContinuousDifferential
readSynchronizedSinglereadSynchronizedContinuous
stopSynchronizedContinuous

Programmable Gain Amplifier

PGA can be activated with enablePGA(gain), where gain=1, 2, 4, 8, 16, 32 or 64. It will amplify signals of less that 1.2V only in differential mode (see examples). Only for Teensy 3.1/3.2.


Periodic conversions

There are two ways to do periodic conversions: using an IntervalTimer or using the PDB.

It's possible (and easy) to use the IntervalTimer library with this library, see analogReadIntervalTimer.ino in the examples folder.

Teensy 3.x have the PDB, supported on both ADCs, see the adc_pdb.ino example. The same frequency is used for both ADCs, unlike using IntervalTimers.


Error detection
Use adc->adcX->fail_flag has any internal error (wrong pin, etc.). Use the functions in ADC_util.h to translate the number to text.
Use adc->resetError() to clean any errors.


Pins

All analog pins can be measured, but not by both ADCs. The following images show which ADC can access each pin.
If the text next to the pin number says ADC0_SE## then it can be measured by ADC0, if it says ADC1_SE## by ADC1. Some pins can be measured by both.

Teensy 3.0:
Teensy3_0_AnalogCard.png
Teensy 3.1/3.2:
Teensy3_1_AnalogCard.png
Teensy 3.5:
Teensy3_5_AnalogCard.jpg
Teensy 3.6:
Teensy3_6_AnalogCard.jpg
Teensy 4:
Teensy4_AnalogCard.png


Synchronous measurements

Code:
ADC::Sync_result result = adc->analogSyncRead(pin1, pin2);
It will set up both ADCs and measure pin1 with ADC0 and pin2 with ADC1. The result is stored in the struct ADC::Sync_result, with members .result_adc0 and .result_adc1 so that you can get both. ADC0 has to be able to access pin1 (same for pin2 and ADC1), to find out see in the image that next to the pin number is written ADC0_xxxx or ADC1_xxx (or both).

I've made a simple test with a wavefunction generator measuring a sine wave of 1 Hz and 2 V amplitude with both ADCs at the same time.
I've measured it synchronously in the loop(){} with a delay of 50ms (so 20Hz sampling), the results are here:
SynchronousADC.jpg
In red and black are the measurements on each pin, as you see, the difference is very small, so the system seems to work.

Voltage reference

The measurements are done comparing to a reference of known voltage. There are a few options depending on the board
  1. REF_3V3: All boards have the 3.3 V output of the regulator (VOUT33 in the schematics). If VIN is too low, or you are powering the Teensy directly to the 3.3V line, this value can be lower. In order to know the value of the final measurement you need to know the value of the reference in some other way. This is the default option.
  2. REF_EXT: The second option is available on the Teensy 3.x and LC boards. It is the external reference, the pin AREF. Simply connect this pin to a known voltage and it will be used as reference.
  3. REF_1V2: Teensy 3.x have an internal 1.2V reference. Use it when voltages are lower than 1.2V or when using the PGA in Teensy 3.1/3.2.
To change the reference:
Code:
adc->setReference(option, ADC_x);
where option is ADC_REFERENCE::REF_3V3, ADC_REFERENCE::REF_EXT, or ADC_REFERENCE::REF_1V2.
ADC_REFERENCE is a class enum. The advantage of class enums with respect to #defines is that the values are checked at compile time, that is, if you write something like ADC_REFERENCE::REF_5V5, the compiler will tell you that value doesn't exist. If you try to use an int (from a define, for example), the compiler will complain again. This may save you from very sneaky bugs, where you use a value that does something different than what you expect.

Sampling and conversion speed

The measurement of a voltage takes place in two steps:
  1. Sampling: Load an internal capacitor with the voltage you want to measure. The longer you let this capacitor be charged, the closest it will resemble the voltage.
  2. Conversion: Convert that voltage into a digital representation that is as close as possible to the selected resolution.
The example conversionSpeed shows for each setting of the resolution, averages, sampling and conversion speed the total measurement time and the measured voltage. Values that fall well below 1.0 (in that example) show that the sampling speed was too high.

You can select the speed at which the sampling step takes place. Usually you can increase the speed if what you measure has a low impedance. However, if the impedance is high you should decrease the speed.
Code:
adc->setSamplingSpeed(speed); // change the sampling speed
speed can be any from the ADC_SAMPLING_SPEED class enum: see the documentation for each board.
For Teensy LC and 3.x:
  • VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK). (ADCK is the ADC clock speed, see below).
  • LOW_SPEED adds +16 ADCK.
  • MED_SPEED adds +10 ADCK.
  • HIGH_SPEED adds +6 ADCK.
  • VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added).
For Teensy 4:
  • VERY_LOW_SPEED: is the lowest possible sampling speed (+22 ADCK, 24 in total).
  • LOW_SPEED adds +18 ADCK, 20 in total.
  • LOW_MED_SPEED adds +14, 16 in total.
  • MED_SPEED adds +10, 12 in total.
  • MED_HIGH_SPEED adds +6 ADCK, 8 in total.
  • HIGH_SPEED adds +4 ADCK, 6 in total.
  • HIGH_VERY_HIGH_SPEED adds +2 ADCK, 4 in total
  • VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added, 2 in total).

The conversion speed can also be changed, and depends on the bus speed:
Code:
adc->setConversionSpeed(speed); // change the conversion speed
speed can be any from the ADC_CONVERSION_SPEED class enum: see the documentation of each board.
This will change the ADC clock, ADCK. And affects all stages in the measurement. You can check what the actual frequency for the current bus speed is in the header settings_defines.h.
For Teensy 3.x, LC:
  • VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs for resolutions less than 16 bits (higher than 1 MHz), it's different from ADC_LOW_SPEED only for 24, 4 or 2 MHz.
  • LOW_SPEED is guaranteed to be the lowest possible speed within specs for all resolutions (higher than 2 MHz).
  • MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED.
  • HIGH_SPEED_16BITS is guaranteed to be the highest possible speed within specs for all resolutions (lower or eq than 12 MHz).
  • HIGH_SPEED is guaranteed to be the highest possible speed within specs for resolutions less than 16 bits (lower or eq than 18 MHz).
  • VERY_HIGH_SPEED may be out of specs, it's different from ADC_HIGH_SPEED only for 48, 40 or 24 MHz.
For Teensy 4:
  • VERY_LOW_SPEED is guaranteed to be the lowest possible speed within specs (higher than 4 MHz).
  • LOW_SPEED is equal to VERY_LOW_SPEED
  • MED_SPEED is always >= ADC_LOW_SPEED and <= ADC_HIGH_SPEED.
  • HIGH_SPEED is guaranteed to be the highest possible speed within specs (lower or eq than 40 MHz).
  • VERY_HIGH_SPEED is equal to HIGH_SPEED

There's another option for the ADC clock, the asynchronous ADC clock, ADACK. It's independent on the bus frequency.
For Teensy 3.x, LC:
  • ADACK_2_4: 2.4 MHz
  • ADACK_4_0: 4 MHz
  • ADACK_5_2: 5.2 MHz
  • ADACK_6_2: 6.2 MHz
For Teensy 4:
  • ADACK_10: 10 MHz
  • ADACK_20: 20 MHz

Both the sampling and the conversion speed affect the time it takes to measure, there are some tests in the continuous conversion examples.

Other conversion sources

All boards have a few additional internal conversion sources. The possible options are in the ADC_INTERNAL_SOURCE class enum.
For Teensy 3.x, LC:
  • TEMP_SENSOR
  • VREF_OUT: The 1.2 V reference (not for Teensy LC)
  • BANDGAP: 1 V bandgap, see VREF.h how to enable it.
  • VREFH
  • VREFL
To convert from the temperature sensor voltage to ºC you can use:
Code:
value = adc->analogRead(ADC_INTERNAL_SOURCE::TEMP_SENSOR);
float volts = value*3.3/adc->getMaxValue(ADC_0);
25-(volts-0.72)/1.7*1000
The constants 1.7 mV/ºC and 0.72 mV depend somewhat on the specific board, so a calibration is recommended.

For Teensy 4 the only extra source is VREFSH, which is connected to VDD.

Library size
The library uses both program and RAM space. The exact amount depends on the Teensy used and whether optimizations were used.
The results in bytes are the respective progmem or ram of the analogRead.ino example minus that of the blinky example, the percentages are the memory used out of the total:

Teensy 3.0, 96 MHz, Serial: 5744 B (11%) program storage space and 132 B (14%) dynamic memory space used.
Teensy 3.1, 96 MHz, Serial, no optimizations: 6176 B (6%) program storage space and 136 B (3%) dynamic memory space used.
Teensy 3.1, 96 MHz, Serial, with optimizations (default): 9244 B (8%) program storage space and 1116 B (7%) dynamic memory space used.
Teensy LC, 48 MHz, Serial, no optimizations (default): 11120 B (33%) program storage space and 128 B (28%) dynamic memory space used.
Teensy LC, 48 MHz, Serial, with optimizations: 14212 B (41%) program storage space and 1112 B (53%) dynamic memory space used.

The progmem usage for Teensy LC is higher due to the use of floats (multiplying by 3.3). If possible, don't use floats at all in Teensy LC as they use about 6 kB of space (2 kB for Teenst 3.x).

Updates
Now it works with the Audio library:
Teensy 3.0 is compatible, except if you try to use the Audio library ADC input (AudioInputAnalog), because there's only one ADC module so you can't use it for two things.
Teensy 3.1 is compatible. If you want to use AudioInputAnalog and the ADC you have to use the ADC_1 module, the Audio library uses ADC_0. Any calls to functions that use ADC_0 will most likely crash the program. Note: make sure that you're using pins that ADC_1 can read (see pictures above)! Otherwise the library will try to use ADC_0 and it won't work!!

ADC library now supports Teensy LC!! Finally!

PDB is supported on both ADCs

Support for Teensy 3.5 and 3.6

New format for setReference, setSamplingSpeed, and setConversionSpeed. The information above has been updated.

Added support for pins A25, A26 in Teensy 3.5

Support for Teensy 4.

Still TODO:

See issues in GitHub.
 
Last edited:
Thanks, pedvide! great job.
Some questions:
-So all of the 6 available pins can be paired with adc->analogSyncRead(pin1, pin2) ?
-And the singereads are fully independed on the two adc's? (so you can start another adc while the other one is working)
-interrupts for second adc like this: void adc1_isr(void) {} ?
 
Yes to everything.
I haven't tested all the combinations, but there's no reason why it shouldn't work on all possible pairs. It's technically also possible to measure on pin A2, A3, A10-A13 with both ADCs at the same time, but I don't know what would happen.
I still haven't tested it, but should be possible to, say, start a continuous measurement on ADC0 and do single reads on ADC1 with no problem.
And yes, the interrupt isr is that. Make sure to enable interrupts on adc1
Code:
adc-->enableInterrupts(ADC_1);
 
I'm unclear in how to use enableDMA(int8_t).

I'm assuming that a DMA request is issued when a conversion is complete? And if so, how does one handle that to be helpful?
Please forgive my ignorance, as I've never worked with DMA before.
 
linuxgeek, that's it, I haven't worked with DMA either so I can't tell you anything else....

Also I forgot to add that the PGA is also implemented, so you can amplify up to 64 times a differential signal. See that the reference for the PGA is 1.2 V, so that's the maximum input without saturating it.
 
Also Paul, I'd like to propose some additions to mk20dx128.h:
Code:
// VREF options that don't appear on mk20dx128.h
#define VREF_TRM_CHOPEN (uint8_t)0x40 // Chop oscillator enable

#define VREF_SC_VREFEN (uint8_t)0x80 // Internal Voltage Reference enable
#define VREF_SC_REGEN (uint8_t)0x40 // Regulator enable
#define VREF_SC_ICOMPEN (uint8_t)0x20 // Second order curvature compensation enable
#define VREF_SC_VREFST (uint8_t)0x04 // Internal Voltage Reference stable flag
#define VREF_SC_MODE_LV(n) (uint8_t)(((n) & 3) << 0) // Buffer Mode selection: 0=Bandgap on only, 1=High power buffer mode,
                                                     // 2=Low-power buffer mode

And these defines already present should be changed from:
Code:
#define ADC0_PGA_PGAEN
#define ADC0_PGA_PGALPB
#define ADC0_PGA_PGAG(n)
to
Code:
#define ADC_PGA_PGAEN
#define ADC_PGA_PGALPB
#define ADC_PGA_PGAG(n)
As I think is a convention here that ADC0_xxx/ADC1_xxxx refer to registers and ADC_xxx to flags of those registers.
 
This looks great! I apologize for this sidetrack - I'm a very experienced embedded software engineer and am trying to use the Teensy 3.1 as a new product platform. I need a real IDE with USB debugging through the Teensy's own connector. I'll pay a lot for this capability! The forums have not been too helpful yet.

I'd even be willing to live with the Arduino IDE if it would break and allow me to look at the contents of my data structures. A useful reply might include how to focus searching to bypass the unnecessary data.
 
Hallo David
I dont think you can use other than "soft breaks" in Teensy programming, so you generate your own dump.
Connect an interrupt to a pin with a button, in the attached interrupthandler, send all data you need to Serial.
that way you can dump data to your computer on a button press, without code in your main program.
Since it is on interrupts, you will be able to debug hanging loops, as long as you're not disabling interrupts.. Remember that your isr can only "see" globals.
 
Thank you for the prompt reply. At the moment I am working through Qt Creator using the hints that I've found here. Having trouble figuring out if ANDROID_DIR is supplied by a Qt option or if I need to define that myself. I will spend another couple of hours on this before dropping back to safe mode and using the serial port as you've suggested.
 
I stopped messing with Qt Creator and am back to developing code through the Arduino IDE. Someday the naming conventions will become 2nd nature.
 
This looks great! I apologize for this sidetrack - I'm a very experienced embedded software engineer and am trying to use the Teensy 3.1 as a new product platform. I need a real IDE with USB debugging through the Teensy's own connector. I'll pay a lot for this capability! The forums have not been too helpful yet.

I'd even be willing to live with the Arduino IDE if it would break and allow me to look at the contents of my data structures. A useful reply might include how to focus searching to bypass the unnecessary data.

I use VisualMicro + Atmel Studio 6.1(free). Excellent IDE. Free. Works with Teensy 3. visualmicro.com
Optional low cost debugger for it - not a hardware based debugger so it can't single-step, but it is very useful.
 
Thank you for the pointer. I have both but haven't spent the time with that combination that I have with the others. I'll dig in that direction for a while.
 
Hi.
I tried to compile the examples but all of them give an error in ADC.h
The ADC lib as well as Arduino 1.0.5r2 and teensyduino 1.18 are uptodate.
Please see below.
Commenting the following line fixes it.
// Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) = {return analogSynchronizedRead(uint8_t pin0, uint8_t pin1);}
Regards.
Eduardo

Arduino: 1.0.5-r2 (Windows 7), Board: "Teensy 3.0"
In file included from analogCont_analogRead.ino:11:0:
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:67: error: invalid pure specifier (only '= 0' is allowed) before 'return'
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:125: error: expected ';' after class definition
In file included from analogCont_analogRead.ino:11:0:
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:287:5: error: expected unqualified-id before 'protected'
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:361:1: error: expected declaration before '}' token
 
Last edited:
You have a Teensy3.0 and the 3.0 doesn't have 2 ADCs, so it doesn't support SynchronizedRead anyway..
Only works on 3.1!


Hi.
I tried to compile the examples but all of them give an error in ADC.h
The ADC lib as well as Arduino 1.0.5r2 and teensyduino 1.18 are uptodate.
Please see below.
Commenting the following line fixes it.
// Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) = {return analogSynchronizedRead(uint8_t pin0, uint8_t pin1);}
Regards.
Eduardo

Arduino: 1.0.5-r2 (Windows 7), Board: "Teensy 3.0"
In file included from analogCont_analogRead.ino:11:0:
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:67: error: invalid pure specifier (only '= 0' is allowed) before 'return'
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:125: error: expected ';' after class definition
In file included from analogCont_analogRead.ino:11:0:
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:287:5: error: expected unqualified-id before 'protected'
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:361:1: error: expected declaration before '}' token
 
Actually I have teensy 3.1 boards. I just had the setting for the ide on 3.0 for testing if it would compile.
For the actual project I will not need synchronous ADC.
Pedro should get the feedback. That's all.
 
Thanks, I'll look into it as soon as possible.
I didn't test the example you are using (analogCont_analogRead.ino) with the new library. I think it uses:
Code:
 ADC adc
instead of
Code:
ADC *adc = new ADC();
Maybe that's the problem?
 
Wait, actually that line (ADC.h:280) doesn't make sense at all, does it?
It should read
Code:
Sync_result analogSyncRead(uint8_t pin0, uint8_t pin1) {return analogSynchronizedRead(uint8_t pin0, uint8_t pin1);}
 
Now it says:

Arduino: 1.0.5-r2 (Windows 7), Board: "Teensy 3.1"
In file included from analogCont_analogRead.ino:11:0:
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h: In member function 'ADC::Sync_result ADC::analogSyncRead(uint8_t, uint8_t)':
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:103: error: expected primary-expression before 'pin0'
C:\Users\Eduardo de Mier\Documents\Projects\Arduino\Sketches\libraries\ADC/ADC.h:280:117: error: expected primary-expression before 'pin1'
 
I apologize if this is a noob mistake on my part, but whenever I try to include the ADC library in a sketch (including the examples), I get this error:


Arduino: 1.0.5-r2 (Windows 7), Board: "Teensy 3.1"
\Arduino\libraries\ADC\ADC_Module.cpp: In constructor 'ADC_Module::ADC_Module(uint8_t)':
\Arduino\libraries\ADC\ADC_Module.cpp:90:16: error: 'ADC0_PGA' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp: In member function 'void ADC_Module::enablePGA(uint8_t)':
\Arduino\libraries\ADC\ADC_Module.cpp:414:20: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp:414:55: error: 'ADC0_PGA_PGAG' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp: In member function 'void ADC_Module::disablePGA()':
\Arduino\libraries\ADC\ADC_Module.cpp:428:18: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp: In member function 'int ADC_Module::analogReadDifferential(uint8_t, uint8_t)':
\Arduino\libraries\ADC\ADC_Module.cpp:614:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp:638:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp: In member function 'int ADC_Module::startSingleDifferential(uint8_t, uint8_t)':
\Arduino\libraries\ADC\ADC_Module.cpp:847:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp:871:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp: In member function 'void ADC_Module::startContinuousDifferential(uint8_t, uint8_t)':
\Arduino\libraries\ADC\ADC_Module.cpp:981:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope
\Arduino\libraries\ADC\ADC_Module.cpp:1005:25: error: 'ADC0_PGA_PGAEN' was not declared in this scope


Any thoughts?
 
Back
Top