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

Pedvide can confirm - maybe it is interrupt ADC reads only? - but requesting a new read can cancel a sample in progress? If so then too many reads might prevent completing an ongoing sample and getting current data. Would be interesting if you redid your sample rate testing waiting for isComplete().
 
Hello Pedro,
Found a small bug in the adc library, concerning the output result of differential readings.
When using the adc in differential mode, the results are 2's complement according to the manual: that means that the result has a sign!
In the adc library, however, you try to get rid of the sign, because you were shocked by the negative results.
But the negative results are correct, and very usefull with a differential input stage.
Example: when you apply Vcc/2 to both inputs, and impose a signal on 1 pin, the relative voltage between the 2 pins is positive or negative. And the common mode is rejected. that is why you need positive and negative numbers for differential sampling results.
 
Last edited:
PaulD,

Please post the code and describe the schematics that shows the error. I don't fully understand what the problem is. Differential readings do indeed return negative results!
 
PaulD,

Please post the code and describe the schematics that shows the error. I don't fully understand what the problem is. Differential readings do indeed return negative results!

There is no problem, I've read your code, and misread it, it is this part in ADC_Module.h
Now I see, that it is about single-ended being uint16_t, okay: I got it wrong! [*hammering on head*]
I'm working on a fretscanner, using differential and PGA for the first time. Lots of stuff to learn, this was a booboo in the process.. ")
thanks for your reply!
Code:
     *   If single-ended and 16 bits it's necessary to typecast it to an unsigned type (like uint16_t),
        *   otherwise values larger than 3.3/2 V are interpreted as negative!
        */
        inline int analogReadContinuous() {
            return (int16_t)(int32_t)*ADC_RA;
        }
 
@Pedvide:

I've tried to port one of my projects to Tennsy LC (from Teensy 3.1) but I've got some compiling errors (I'm using ADC library in my code):

Code:
Arduino: 1.6.3 (Linux), TD: 1.22-beta3, Board: "Teensy LC, Serial, 48 MHz, US English"

/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp: In constructor 'RingBufferDMA::RingBufferDMA(uint8_t, uint8_t)':
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:57:20: error: 'DMA_TCD0_CSR' was not declared in this scope
DMA_TCD_CSR = &DMA_TCD0_CSR + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:59:22: error: 'DMA_TCD0_SADDR' was not declared in this scope
DMA_TCD_SADDR = &DMA_TCD0_SADDR + dma_offset_32*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:60:21: error: 'DMA_TCD0_SOFF' was not declared in this scope
DMA_TCD_SOFF = &DMA_TCD0_SOFF + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:61:22: error: 'DMA_TCD0_SLAST' was not declared in this scope
DMA_TCD_SLAST = &DMA_TCD0_SLAST + dma_offset_32*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:63:22: error: 'DMA_TCD0_DADDR' was not declared in this scope
DMA_TCD_DADDR = &DMA_TCD0_DADDR + dma_offset_32*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:64:21: error: 'DMA_TCD0_DOFF' was not declared in this scope
DMA_TCD_DOFF = &DMA_TCD0_DOFF + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:65:25: error: 'DMA_TCD0_DLASTSGA' was not declared in this scope
DMA_TCD_DLASTSGA = &DMA_TCD0_DLASTSGA + dma_offset_32*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:67:21: error: 'DMA_TCD0_ATTR' was not declared in this scope
DMA_TCD_ATTR = &DMA_TCD0_ATTR + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:68:28: error: 'DMA_TCD0_NBYTES_MLNO' was not declared in this scope
DMA_TCD_NBYTES_MLNO = &DMA_TCD0_NBYTES_MLNO + dma_offset_32*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:70:30: error: 'DMA_TCD0_CITER_ELINKNO' was not declared in this scope
DMA_TCD_CITER_ELINKNO = &DMA_TCD0_CITER_ELINKNO + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:71:30: error: 'DMA_TCD0_BITER_ELINKNO' was not declared in this scope
DMA_TCD_BITER_ELINKNO = &DMA_TCD0_BITER_ELINKNO + dma_offset_16*DMA_channel;
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp: In member function 'void RingBufferDMA::start()':
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:86:5: error: 'DMA_CR' was not declared in this scope
DMA_CR = 0; // normal mode of operation
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:90:40: error: 'DMA_TCD_ATTR_SIZE_16BIT' was not declared in this scope
*DMA_TCD_ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_16BIT) |
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:90:63: error: 'DMA_TCD_ATTR_SSIZE' was not declared in this scope
*DMA_TCD_ATTR = DMA_TCD_ATTR_SSIZE(DMA_TCD_ATTR_SIZE_16BIT) |
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:91:61: error: 'DMA_TCD_ATTR_DSIZE' was not declared in this scope
DMA_TCD_ATTR_DSIZE(DMA_TCD_ATTR_SIZE_16BIT) |
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:92:38: error: 'DMA_TCD_ATTR_DMOD' was not declared in this scope
DMA_TCD_ATTR_DMOD(4); // src and dst data is 16 bit (2 bytes), buffer size 2^^4 bytes = 8 values
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:106:20: error: 'DMA_TCD_CSR_INTMAJOR' was not declared in this scope
*DMA_TCD_CSR = DMA_TCD_CSR_INTMAJOR; // Control and status: interrupt when major counter is complete
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:108:5: error: 'DMA_CERQ' was not declared in this scope
DMA_CERQ = DMA_CERQ_CERQ(DMA_channel); // clear all past request
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:108:41: error: 'DMA_CERQ_CERQ' was not declared in this scope
DMA_CERQ = DMA_CERQ_CERQ(DMA_channel); // clear all past request
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:109:5: error: 'DMA_CINT' was not declared in this scope
DMA_CINT = DMA_channel; // clear interrupts
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:117:5: error: 'DMA_SERQ' was not declared in this scope
DMA_SERQ = DMA_SERQ_SERQ(DMA_channel); // enable DMA request
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:117:41: error: 'DMA_SERQ_SERQ' was not declared in this scope
DMA_SERQ = DMA_SERQ_SERQ(DMA_channel); // enable DMA request
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp: In destructor 'RingBufferDMA::~RingBufferDMA()':
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:127:5: error: 'DMA_CERQ' was not declared in this scope
DMA_CERQ = DMA_CERQ_CERQ(DMA_channel); // stop channel 3
^
/root/arduino-1.6.3/libraries/ADC/RingBufferDMA.cpp:127:41: error: 'DMA_CERQ_CERQ' was not declared in this scope
DMA_CERQ = DMA_CERQ_CERQ(DMA_channel); // stop channel 3
^
Error compiling.

Is there a way to make this (great) library work with Teensy LC? I know that Teensy LC don't have fully featured DMA channels but maybe there's a workaround for this situation. Thanks in advance for any help!
 
Yeah sorry about that, I've been meaning to port the ADC library to Teensy LC, which I think is very easy. But I have been very busy. If you don't want to use and DMA stuff you can comment it out and I think it should work.
 
Thanks a lot for your quick support!

I've discovered a related function inside the ADC.cpp (ADC::disableDMA) but I guess I can't make use of it as I will still get compile errors, even the function is there to disable the DMA at runtime.

So.. could you further guide me on how to get rid of those DMA compile errors? It's all inside RingBufferDMA.cpp file to be commented out or should I modify other files too?
 
I just updated the GitHub repository.
ADC library now supports Teensy LC!
I haven't really tested it and it's late now, but it compiles and should work fine.
All DMA stuff is disabled for the Teensy LC. At some point I really have to use Paul's great DMAChannel stuff.
I also added to the first post some information about program and RAM usage of the library.
 
Thank you very much, I really appreciate your kind effort! Unfortunately, I still have some problems.

Although it compiles without errors, the sketch simply don't run. It won't even pass the setup() routine (blinking a LED).

If I only left the #include <ADC.h> statement alone, my sketch is running. But if I just declare "ADC *myADC = new ADC();" it won't work anymore.

I hope you could check this issue when you have some spare time. Thanks again!
 
Yeah, I've realized that there are more differences between Teensy 3.x and LC than I though before. I still want to support LC, but it will take me some more time.
Specifically the bitband addressing that I use for Teensy 3.x doesn't exist for LC, but it has something similar that I hope I can adapt without touching too much code...
I can't do anything this weekend, I hope I can post an update next week.
 
I just updated the GitHub repository with a new version of the ADC library that finally supports Teensy LC!
Apart from that, there's a change in the setReference function, I'll write more details tomorrow but now it accepts two (Teensy LC) or three (Teensy 3.x) options:
ADC_REF_3V3, ADC_REF_1V2 (not for Teensy LC) or ADC_REF_EXT (pin AREF).
The meaning I think is clear from the names. The change is due to the way Paul wired up the Teensy LC ADC references, now the internal "default" is actually the external pin (AREF) instead of the 3.3V as in the Teensy 3.x.
I've tried to optimize the program and RAM usage, but for the Teensy LC (48 MHz, Serial, non optimized) the analogRead example uses up already 32% of progmem and 28% of RAM (Blinky example uses 15% and 26% respectively), using the optimized version the progmem usage goes up to 41% while the RAM to 53% (19% and 39% for Blinky).
 
I'm using analogReadContinuous() in my sketch and it seems to not work properly, unfortunately.

I even tried the analogContinuousRead example and I only got the LED blinking but no serial output (printing the read values).

Actually, serial printing is ignored on my sketch too, when the program flow comes to analogReadContinuous() function.

Thanks again for your support.
 
Please post code that reproduces the problem.
The analogContinuousRead example doesn't output anything unless you press 'v', 't' or 'c', as it's written at the beginning of the file!
 
Oh, sorry for misreading that. I was just running (ADC) analogRead example before, where the output was printed automatically.

The analogContinuousRead example is running just fine and, as you manage to wake me up, I found a commented block inside my sketch thus everything it's up & running now.

Thanks a lot for your hard work!
 
It appears that the latest update removed ADC_REF_INTERNAL. Should I be using ADC_REF_ALT now? It looks like ADC_REF_INTERNAL was defined as 2, ADC_REF_ALT is defined as 1. I was using it to read the internal temp sensor on the Teensy 3.1.
 
Last edited:
No, ADC_REF_DEFAULT and ADC_REF_ALT are internal settings. What you should always use is ADC_REF_3V3, ADC_REF_1V2 or ADC_REF_EXT. In your case ADC_REF_1V2.
I've updated the first post with this information.

Edit: Sorry that I had to do this code-breaking change, but as I explained in #162, Teensy 3.x and LC are not consistent using DEFAULT or ALT. In future boards I'll make sure that ADC_REF_3V3, ADC_REF_1V2 and ADC_REF_EXT have the desired results, independent of the internal stuff.
 
Last edited:
Thanks for the clarification, the library works great!

I'm currently using ADC0 with the Audio Library while using ADC1 with your library to sample a few analog inputs slowly in a round robin fashion. This added functionality would have been totally out of my project's scope if I had been trying to do it on my own.
 
Hi Pedro, thanks for your work on the library.

I have a couple of questions (Teensy 3.1):
1.is there information available on how many clock cycles the 5 presets for sampling and conversion times consume?
2. how does the conversion time influence the result? isnt this just read from the holding capacitance and should be circuit independent?
3. when I set the resolution of a particular ADC to 16 bit, it will generate 16bit numbers for both the proper analog inputs and the GPIO inputs. is this intended?

4. is a little longer question: when using the oversampling setting in the library, the number that I receive is still only 16 bit. this means that all individual numbers are averaged before returning them as a value. Is it possible to provide an option in future that will just accumulate the oversampled numbers to increase bit depth -> aka dithering. This is an immensely helpful technique on high speed low precision ADCs. I tried to realize this as a simple multi-line sequence of "value += adc->analogRead" thus avoiding the for-loop clock cycles and it worked out well. But I think that the library based oversampling approach might be faster somehow?
 
Hello,

1. In the header ADC_Module.h you can find the actual definitions of the conversion and sampling speeds. For the sampling you can see how many ADC clock (ADCK) cycles it adds:
Code:
ADC_VERY_LOW_SPEED is the lowest possible sampling speed (+24 ADCK).
ADC_LOW_SPEED adds +16 ADCK.
ADC_MED_SPEED adds +10 ADCK.
ADC_HIGH_SPEED (or ADC_HIGH_SPEED_16BITS) adds +6 ADCK.
ADC_VERY_HIGH_SPEED is the highest possible sampling speed (0 ADCK added).

For the conversion is a bit more difficult. When you change the conversion speed you mainly change the ADCK frequency. For example, for F_BUS = 48 Mhz (Teensy 3.x default), ADC_CFG1_MED_SPEED sets a ADCK frequency of 6 MHz. Apart from that the different speed setting change the low power (used only at low speeds) and high speed settings (used at high speed).
I also added support to use the internal asynchronous clock (ADACK). Only for the conversion speed you can select: ADC_ADACK_2_4, ADC_ADACK_4_0, ADC_ADACK_5_2 and ADC_ADACK_6_2, where the numbers are the frequency of the ADC clock (ADCK) in MHz and are independent on the bus speed.
If you wan to know more about this you should read the relevant sections of the manual (from 31.4.4.5 to 31.4.4.6.3 of the Teens 3.0 reference manual).

2. The conversion step can take longer or shorter time, use higher or lower resolution, etc.

3. I don't understand the question. The ADC can only read analog pins, not pure digital ones.
The 16 bit resolution is only actually realistic for differential inputs, single-ended inputs are only 12 bit. (see the electrical specifications datasheet for more information).

4. There's no oversampling option! setAveraging(N) only makes the ADC repeat the whole measurement N times. Oversampling would be measuring a signal with a frequency higher than Nyquist, but the ADC simply repeats the measurement as fast as it can. Because oversampling requires knowing the bandwidth of the signal you are trying to measure, it has to be the user who deals with it. If you use a timer (see the example) you can set periodic measurements and store them in a buffer.
 
I have an installation question. My Arduino IDE already has a library called ADC. Importing either of these libraries cause the same lines to be added to the code:
#include <ADC.h>
#include <ADC_Module.h>
#include <RingBuffer.h>
#include <RingBufferDMA.h>

How do I make sure that the correct library is used?

Edit: I installed using the "Add ZIP library" option.
 
You should only have one copy of the ADC library, I don't know where that option actually installs things, so you can search "ADC.h" or something like that and delete duplicates. Those #includes will look for the library in the folders that arduino IDE has. So you need to make sure that there's only one copy.
See that you usually only need to #include <ADC.h>, and if you use the RingBuffers then #include <RingBuffer.h> or #include <RingBufferDMA.h>, but #include <ADC_Module.h> isn't necessary.
 
@nickexists -remember to restart the IDE to rescan after adding/removing libraries. If you have duplicate libs, Paul got code in the IDE that should detect that. During build the used path to the code should be shown, maybe even in the duplicate lib notice.
 
I've been playing around a bit with a Teensy 3.1, ADC, and DMA. I've thrown together a variant of the RingBufferDMA class which seemed useful for my particular application (digitizing a whole "burst" of data from the ADC, as fast as possible, with minimal overhead).

It uses a lot of code ideas that were clearly implied by some commented-out code in RingBufferDMA, but has a slightly different approach to data residency. There's no intent to support application access to "only half filled in so far" buffers. Instead, you start out with an empty buffer, set up the DMA, and start it. At that point, you wait for it to be done(). When it is, you can read out the members of the buffer until it's empty... lather, rinse, repeat. Access to the data each time is strictly linear, rather than via a circular-buffer technique such as RingBuffer and RingBufferDMA use. In order to process data that has already been DMA'ed while DMA is still going on, you'd need to use two of these BurstBuffer objects via a classic "double buffering" technique. That's how I plan to use it in my application (digitizing a fast-rising pulse from a gamma ray spectrometer charge-sensitive amplifier, and computing the pulse-height distributions).

I wrote the code against a fairly recent TeensyDuino distribution. It seems to work OK on a Teensy 3.1. It doesn't have support for Teensy LC yet - I just picked up Pedvide's latest code last night and haven't yet started integrating and rewriting to support the rather different DMA structure on Teensy LC.

I've put the code in http://users.lmi.net/dplatt/teensy/ - feel free to use/adopt it if it's useful to you.
 
DavePlatt,

I rewrote the RignBufferDMA to use DMAChannel. In this way you don't have to deal with the internal DMA stuff, just simple function calls. It still doesn't work in Teensy LC, but hopefully I'll manage to do it.
Your code would probably benefit using DMAChannel as well.
 
Back
Top