How measure battery voltage?

Status
Not open for further replies.

mm7

Active member
Hi,

I'd like to show battery charge status on screen of a device.

I have Teensy 3.5.

I found this blog for Arduino
http://jeelabs.org/2012/05/04/measuring-vcc-via-the-bandgap/index.html
http://jeelabs.org/2012/05/12/improved-vcc-measurement/

I like the idea to use internal reference.

I am trying to repeat it for Teensy, but it does not compile.
symbols ADMUX,ADCSRA, ADSC, ADC are not defined.

Example from here https://www.pjrc.com/teensy/adc.html
also does not work.

which file needs to be included for ADMUX,ADCSRA, ADSC, ADC symbos defined?

including these
#include <avr/io.h>
#include <avr/pgmspace.h>
does not help.

I've connected LiIo battery 4.2V to pin A0. analogRead(0) shows 1023.
I try to change AD reference to INTERNAL. But it always shows 1023
Code:
void setup()
{                
  Serial.begin(38400);
  //analogReference(DEFAULT);
  analogReference(INTERNAL);
  //analogReference(EXTERNAL);
}

int val;
void loop()                     
{
  val = analogRead(0);
  Serial.print("analog 0 is: ");
  Serial.println(val);
  delay(250);
}

what is wrong?
Any ideas?
Thanks!
 
Oh man, the analog pins of the Teensy 3.x processors are all only 3.3V tolerant (read the fu...ing reference manual). That means that all voltages of 3.3V or above (the latter risk to kill the Teensy) will read 1023 with the 10 bit default resolution. Thus, you'll have to connect your LiIo battery through a voltage divider and then do a little arithmetic in your code to do the correct scaling.

And you'll have to understand that the Teensy 3.x processors are NOT 8bit AVR processors, but 32bit ARM processors, thus, most Arduino code which directly addresses AVR hardware registers can not work on a Teensy 3.x since these have different hardware registers. Reading manuals broadens the mind...
 
Last edited:
Oh man, the analog pins of the Teensy 3.x processors are all only 3.3V tolerant (read the fu...ing reference manual).
Of course I read.
https://www.pjrc.com/store/teensy35.html - All digital pins are 5 volt tolerant.

That means that all voltages of 3.3V or above (the latter risk to kill the Teensy) will read 1023 with the 10 bit default resolution. Thus, you'll have to connect your LiIo battery through a voltage divider and then do a little arithmetic in your code to do the correct scaling.
That is what I try to avoid. I would not want that the divider works all time. It will draw power.
So I will need to add MOSFET that turns on this circuit only for couple of microseconds for measuring.

If it is possible to get V from analog pin that is connected with battery, this would simplify the thing.

And you'll have to understand that the Teensy 3.x processors are NOT 8bit AVR processors, but 32bit ARM processors, thus, most Arduino code which directly addresses AVR hardware registers can not work on a Teensy 3.x since these have different hardware registers. Reading manuals broadens the mind...
This ARM Cortex M4 has: Dual 1Msps, 16-bit ADCs of up to 24 channels with analog front end, offering offset error correction and gain control. Also includes 2-channel, 1Msps, 12-bit DAC
Not 32-bit ADCs.
Anyway I tried to set analogReadRes(12); and to 16. It does not really help.

If you could point me to Teensy manuals where it is described how to reference vs internal, I'd be happy.
:)
 
Last edited:
1. Read the sage advice (again) of Mr. Theremingenieur. Pins configured for analog input should NOT exceed the ADC's reference V, or the Vmax for the particular model.

2. At best, is seems that 11.4 ENOB is to expected for the T3.x ADCs where not doing differential stuff. So configure for 12b unless you are running windowed averages of many samples of filtered data.

3. The example at ../teensy/adc.html is for an AVR chip. Use the wonderous lib by Pedvide, as discussed in this thread:
forum.pjrc.com/threads/25532-ADC-library-update-now-with-support-for-Teensy-3-1

4. Have some T3.5s myself; one of which runs my hearing aid. Vbat is monitored by a R divider that is buffered by a lo-pwr op amp with an enable pin.
 
Yup. The add library makes life easier, Paul has also published some examples how to read analog values.

It might be as simple as a voltage divider and capacitor from analog input to agnd. Something in the 0.01uF range as recommended by OEM or higher.

Check out the voltage divider calculator over at Rarltron among other places.
 
I've changed battery to 3v one.
trying ADC lib. It is wonderful!
running readAllPins...
However even without V applied all pins show some voltage around 3V
Code:
[B]A0: 2.97.[/B] A1: 3.07. A2: 3.05. A3: 3.05. A4: 3.04. A5: 3.05. A6: 3.05. A7: 3.05. A8: 3.06. A9: 3.04. A10: 1.07. A11: 0.94. A12: 2.99. 
A13: 3.00. A14: 3.04. A15: 3.05. A16: 3.00. A17: 3.01. A18: 2.98. A19: 3.01. A20: 3.02. A21: 1.49. A22: 1.29. A23: 2.98. A24: 3.02. 
Differential pairs: 0: -0.16. 
Temperature sensor (approx.): : 30.73 C.
Here the battery is connected only to only A0. Why others show voltage?

My understanding that differential can measure double voltage ~7V.
This covers LI battery fully charged voltage.
What about a crazy idea to connect battery to differential (A10,A11) ?
However I do not know how it will work when battery(-) will be connected to GND,
battery(+) to Vin and A11 and battery(-) again to A10. What will go to Analog GND?
Are GND and Analog GND disconnected galvanically?
 
If the other pins are not connected they'll float to some random voltage, in your case looks like they've all floated up. If you want a sensible read, you need to give them something to read. If this is just for testing you can enable the internal pullup/pulldowns on the pins and watch them change.

See the Teensy schematic
https://www.pjrc.com/teensy/schematic.html
for your last question, and the answer is 'no'.

And pretty sure you can't get the differential input above 3.3V.

You are way overthinking this - look up resistive dividers as suggested above, put in a 1/2 or 1/3 and go to it.
 
Last edited:
Thank you guys!
I see that no embedded "magic" is possible here. Only R-divider will help.
What Rs will be good? If I will use 1.2v ref, will be 200KOhm and 1MOhm good?
Or this resistance is too high?
Also, to avoid leaking through this divider I should add a switch there, what would be better a MOSFET or opamp or something else? Better it to be as small as possible, because it will be in handheld device.
 
If you take a high R voltage divider, it will load less the battery. But it risks also to give wrong readings because little load might show better battery state than real. And the delta-sigma conversion of the analog inputs does also represent a varying load (see reference manual). Thus I'd go for a 39k / 10k divider with a 10nF capacitor in parallel with the 10k resistor since that is the recommended source impedance by the manufacturer.

A p-MOSFet will do as a switch. An OP amp draws too much current for nothing.

Thus the 39k resistor goes from the battery to the source of the p-mosfet. Another 39k resistor will go from the gate to the source of the mosfet as a discharge resistor for the gate capacitance. The gate goes to your control GPIO pin which is high in the inactive times. The drain goes to the 10k resistor, the 10nF capacitor and to the analog input. The other side of the 10k and the 10nF go to AGND.

Symbolic code is:

digitalWrite(controlPin, LOW); //switch the mosfet on
delay(20); //allow the 10nF cap to charge
int rdx = analogRead(analogPin); //read the input voltage
delay(5); // just to be sure
digitalWrite(controlPin, HIGH); //switch the mosfet off

and then do whatever you want with the rdx value, scaling, offset correction, etc.
 
Last edited:
You are measuring an extremely slow signal. A 200kOhm / 50kOhm divider with a larger cap (e.g. 0.1uF) will work well.

I'm not convinced powering down the voltage divider is worth it. How are you powering down the Teensy? E.g. with Snooze hibernate, I still have 45uA power consumption with Teensy 3.5, which is FAR more than the divider.
 
toi, the application note for the ADCs of these processor states clearly that the source impedance should be <= 10kOhm...
The application note is irrelevant for the huge 0.1uF buffer cap I mentioned. That is 10'000x the 10pF of the SAR sampling capacitor and for practical purposes, the output impedance of that buffer cap is 0.

Yes, I have practically tested this.
 
You are measuring an extremely slow signal. A 200kOhm / 50kOhm divider with a larger cap (e.g. 0.1uF) will work well.

I'm not convinced powering down the voltage divider is worth it. How are you powering down the Teensy? E.g. with Snooze hibernate, I still have 45uA power consumption with Teensy 3.5, which is FAR more than the divider.

Thanks tni!

LTSpice shows I just 17uA for 200K/50K divider with 0.1uF cap.
In this case it does not make sense to add a switch.
I'll definitely try this.
 
The 10nF is all you need on your analog input, as long as you don't go crazy with your divider, the difference between the 10nF and the 100nF is about 700uV with your divider. Keep in mind that your values will always be a little lower then if you used a low resistance source like an opamp, but opamps add there own gotchas and increase part count.

Here is a SAR simulation I created for the Teensy 3 series based on the Electrical Specs. The timings are based on conjecture and details gleaned from the datasheets, I sent a message to Pedvide a while back to see if he had details on the actual sample timings I could use. I was hoping to create a guide for beginners that would make it more apparent what they can and cant do with the Teensy ADC's but I have been kept busy with work.

For some strange reason I cant attach the .zip or .asc file into the thread, instead I will just drop the contents of the .asc here and you can past it into a blank .asc file and open it with LTspice.

Code:
Version 4
SHEET 1 2312 700
WIRE -624 -1296 -800 -1296
WIRE -352 -1296 -624 -1296
WIRE -800 -1264 -800 -1296
WIRE -624 -1136 -624 -1296
WIRE -528 -1136 -624 -1136
WIRE -528 -1120 -528 -1136
WIRE -272 -1104 -400 -1104
WIRE -352 -1072 -352 -1296
WIRE -400 -1056 -400 -1104
WIRE -384 -1056 -400 -1056
WIRE -272 -1040 -272 -1104
WIRE -272 -1040 -320 -1040
WIRE -224 -1040 -272 -1040
WIRE -96 -1040 -96 -1072
WIRE -96 -1040 -144 -1040
WIRE 32 -1040 -96 -1040
WIRE 160 -1040 112 -1040
WIRE 272 -1040 240 -1040
WIRE 416 -1040 272 -1040
WIRE -528 -1024 -528 -1040
WIRE -384 -1024 -528 -1024
WIRE -96 -1024 -96 -1040
WIRE 416 -1024 416 -1040
WIRE -528 -1008 -528 -1024
WIRE 272 -1008 272 -1040
WIRE 512 -1008 464 -1008
WIRE 480 -960 464 -960
WIRE 480 -944 480 -960
WIRE 272 -912 272 -944
WIRE 416 -912 416 -944
WIRE 416 -912 272 -912
WIRE 224 -848 224 -992
WIRE 224 -848 144 -848
WIRE 256 -848 224 -848
WIRE 512 -848 512 -1008
WIRE 512 -848 320 -848
WIRE -624 -592 -624 -1136
WIRE -464 -592 -624 -592
WIRE -208 -592 -464 -592
WIRE -208 -560 -208 -592
WIRE -208 -464 -208 -480
WIRE -112 -464 -112 -496
WIRE -112 -464 -208 -464
WIRE 32 -464 -112 -464
WIRE 160 -464 112 -464
WIRE 272 -464 240 -464
WIRE 416 -464 272 -464
WIRE -208 -448 -208 -464
WIRE 416 -448 416 -464
WIRE -112 -432 -112 -464
WIRE 272 -432 272 -464
WIRE 512 -432 464 -432
WIRE 480 -384 464 -384
WIRE 480 -368 480 -384
WIRE 272 -336 272 -368
WIRE 416 -336 416 -368
WIRE 416 -336 272 -336
WIRE 144 -272 144 -848
WIRE 224 -272 224 -416
WIRE 224 -272 144 -272
WIRE 256 -272 224 -272
WIRE 512 -272 512 -432
WIRE 512 -272 320 -272
WIRE -464 16 -464 -592
WIRE -224 16 -464 16
WIRE -224 32 -224 16
WIRE -224 128 -224 112
WIRE -112 128 -112 96
WIRE -112 128 -224 128
WIRE 32 128 -112 128
WIRE 160 128 112 128
WIRE 272 128 240 128
WIRE 416 128 272 128
WIRE -224 144 -224 128
WIRE 416 144 416 128
WIRE 272 160 272 128
WIRE 512 160 464 160
WIRE 480 208 464 208
WIRE 480 224 480 208
WIRE 272 256 272 224
WIRE 416 256 416 224
WIRE 416 256 272 256
WIRE 144 320 144 -272
WIRE 144 320 96 320
WIRE 224 320 224 176
WIRE 224 320 144 320
WIRE 256 320 224 320
WIRE 512 320 512 160
WIRE 512 320 320 320
WIRE 96 384 96 320
FLAG 480 224 0
FLAG 176 176 0
FLAG 96 464 0
FLAG -800 -1184 0
FLAG 416 256 0
FLAG 480 -368 0
FLAG 176 -416 0
FLAG -112 -368 0
FLAG 416 -336 0
FLAG 480 -944 0
FLAG 176 -992 0
FLAG 416 -912 0
FLAG -352 -1008 0
FLAG -224 224 0
FLAG -208 -368 0
FLAG -528 -928 0
FLAG -96 -960 0
FLAG 272 128 A
FLAG 272 -464 B
FLAG 272 -1040 C
FLAG -112 96 AIN
FLAG -112 -496 BIN
FLAG -96 -1072 CIN
SYMBOL cap 256 160 R0
SYMATTR InstName CADINA
SYMATTR Value 10p
SYMATTR SpiceLine Rser=3
SYMBOL res 128 112 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName RADINA
SYMATTR Value 5K
SYMBOL sw 416 240 R180
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S1
SYMATTR Value mySW
SYMBOL sw 144 128 R270
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S2
SYMATTR Value mySW
SYMBOL Digital\\inv 256 256 R0
SYMATTR InstName A1
SYMBOL res -208 128 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R2
SYMATTR Value 10K
SYMBOL voltage 96 368 R0
WINDOW 123 0 0 Left 2
WINDOW 39 0 0 Left 2
SYMATTR InstName V1
SYMATTR Value PULSE(0 3.3 0.001 10n 10n 800n 250u 10)
SYMBOL voltage -800 -1280 R0
WINDOW 123 0 0 Left 2
WINDOW 39 24 124 Left 2
SYMATTR SpiceLine Rser=0
SYMATTR InstName V2
SYMATTR Value 3.3
SYMBOL cap 256 -432 R0
SYMATTR InstName CADINB
SYMATTR Value 10p
SYMATTR SpiceLine Rser=3
SYMBOL res 128 -480 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName RADINB
SYMATTR Value 5K
SYMBOL sw 416 -352 R180
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S3
SYMATTR Value mySW
SYMBOL sw 144 -464 R270
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S4
SYMATTR Value mySW
SYMBOL Digital\\inv 256 -336 R0
SYMATTR InstName A2
SYMBOL Opamps\\opamp2 -352 -1104 R0
SYMATTR InstName U2
SYMATTR Value LM324
SYMBOL cap 256 -1008 R0
SYMATTR InstName CADINC
SYMATTR Value 10p
SYMATTR SpiceLine Rser=3
SYMBOL res 128 -1056 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName RADINC
SYMATTR Value 5K
SYMBOL sw 416 -928 R180
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S5
SYMATTR Value mySW
SYMBOL sw 144 -1040 R270
WINDOW 0 24 16 Invisible 2
WINDOW 3 24 96 Invisible 2
SYMATTR InstName S6
SYMATTR Value mySW
SYMBOL Digital\\inv 256 -912 R0
SYMATTR InstName A3
SYMBOL res -128 -1056 R90
WINDOW 0 0 56 VBottom 2
WINDOW 3 32 56 VTop 2
SYMATTR InstName R6
SYMATTR Value 1.5K
SYMBOL cap -128 -432 R0
SYMATTR InstName C2
SYMATTR Value 0.01µ
SYMATTR SpiceLine V=16 Irms=647m Rser=0.0322889 Lser=0 mfg="KEMET" pn="C0201C103K4PAC" type="X5R"
SYMBOL res -208 240 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R7
SYMATTR Value 10K
SYMBOL res -192 -464 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R4
SYMATTR Value 10K
SYMBOL res -192 -352 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R8
SYMATTR Value 10K
SYMBOL res -512 -1024 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R9
SYMATTR Value 10K
SYMBOL res -512 -912 R180
WINDOW 0 36 76 Left 2
WINDOW 3 36 40 Left 2
SYMATTR InstName R10
SYMATTR Value 10K
SYMBOL cap -112 -1024 R0
SYMATTR InstName C4
SYMATTR Value 0.01µ
SYMATTR SpiceLine V=16 Irms=647m Rser=0.0322889 Lser=0 mfg="KEMET" pn="C0201C103K4PAC" type="X5R"
TEXT 112 544 Left 2 !.model mySW SW(Ron=0.1 Roff=10Meg Vt=.3)
TEXT -352 568 Left 2 !.tran 0 0.001001 0.0009999
TEXT 1736 -112 Left 1 !* LM324      QUAD LOW POWER OP AMP/BUFFERS\n* connections:   non-inverting input\n*                | inverting input\n*                | | positive power supply\n*                | | | negative power supply\n*                | | | | output\n*                | | | | |\n.subckt LM324    1 2 3 4 5\n*\n  c1   11 12 2.887E-12\n  c2    6  7 30.00E-12\n  dc    5 53 dx\n  de   54  5 dx\n  dlp  90 91 dx\n  dln  92 90 dx\n  dp    4  3 dx\n  egnd 99  0 poly(2) (3,0) (4,0) 0 .5 .5\n  fb    7 99 poly(5) vb vc ve vlp vln 0 21.22E6 -20E6 20E6 20E6 -20E6\n  ga    6  0 11 12 188.5E-6\n  gcm   0  6 10 99 59.61E-9\n  iee   3 10 dc 15.09E-6\n  hlim 90  0 vlim 1K\n  q1   11  2 13 qx\n  q2   12  1 14 qx\n  r2    6  9 100.0E3\n  rc1   4 11 5.305E3\n  rc2   4 12 5.305E3\n  re1  13 10 1.845E3\n  re2  14 10 1.845E3\n  ree  10 99 13.25E6\n  ro1   8  5 50\n  ro2   7 99 25\n  rp    3  4 9.082E3\n  vb    9  0 dc 0\n  vc    3 53 dc 1.500\n  ve   54  4 dc 0.65\n  vlim  7  8 dc 0\n  vlp  91  0 dc 40\n  vln   0 92 dc 40\n.model dx D(Is=800.0E-18 Rs=1)\n.model qx PNP(Is=800.0E-18 Bf=166.7)\n.ends\n*$
TEXT 688 216 Left 7 ;A
TEXT 680 -376 Left 7 ;B
TEXT 680 -936 Left 7 ;C
LINE Normal 0 400 0 -16 2
LINE Normal 0 400 0 400 2
LINE Normal 0 400 0 400 2
LINE Normal 0 400 0 400 2
LINE Normal 0 400 0 400 2
LINE Normal 656 496 0 496 2
LINE Normal 656 -16 656 400 2
LINE Normal 0 -16 656 -16 2
LINE Normal 0 -16 0 -16 2
LINE Normal 0 -16 0 -16 2
LINE Normal 0 496 0 400 2
LINE Normal 656 496 656 400 2
LINE Normal 0 -192 0 -608 2
LINE Normal 0 -192 0 -192 2
LINE Normal 0 -192 0 -192 2
LINE Normal 0 -192 0 -192 2
LINE Normal 0 -192 0 -192 2
LINE Normal 656 -96 0 -96 2
LINE Normal 656 -608 656 -192 2
LINE Normal 0 -608 656 -608 2
LINE Normal 0 -608 0 -608 2
LINE Normal 0 -608 0 -608 2
LINE Normal 0 -96 0 -192 2
LINE Normal 656 -96 656 -192 2
LINE Normal 0 -768 0 -1184 2
LINE Normal 0 -768 0 -768 2
LINE Normal 0 -768 0 -768 2
LINE Normal 0 -768 0 -768 2
LINE Normal 0 -768 0 -768 2
LINE Normal 656 -672 0 -672 2
LINE Normal 656 -1184 656 -768 2
LINE Normal 0 -1184 656 -1184 2
LINE Normal 0 -1184 0 -1184 2
LINE Normal 0 -1184 0 -1184 2
LINE Normal 0 -672 0 -768 2
LINE Normal 656 -672 656 -768 2
 
You may not need a cap, when your frequency is low. If your resolution is 12 BIT or lower you can set the ADC clock to 1MHZ. I figued this out with a 100K/100K (R1/R2) voltage divider. When everything is fine I would measure a resistance of 100K. As you can see with a 100K the resluts for high frequencies are far away from reality. Voltage is underestimated because of incomplete loading of internal cap. For lower frequencies (and therefore longer sampling times) they come closer to the expected value.


Plot1b.png

In the first case there was only a small sample time selected (6 ADC clock cycles) . But sample time can be increaed to a maximum value of 24 ADC clock cycles. This is shown in the second plot. For a sampling time of 24 ADC clock cycles and a ADC clock frequency of 1.5 MHZ (CPU clock @ 24 MHZ) the mean measured value of R2 is 99901 which is very close to 100K.




Plot2.png
 
Last edited:
That's about a 16uS sample time.
Oddly enough just looking at my LTspice model it should realistically only take about 8uS with a 100K/100K divider to basically reach the max value based on total system resistance.
 
currently not having a 0.1uF cap, I've just inserted 47K/100K divider into my breadboard.
All works. Voltage of 3.14V battery shows up as 0.99V, USB voltage (5V) shows as 1.98V.

If it works without a cap, what cap gives? Smoothing? But if port is averaging is smoothing required?
 
AFAIK - noted above there is an internal cap within the Teensy that must be charged to the level of the signal to be measured. That cap it used to complete the internal multi sample measurement of the signal.

The voltage divider used to drop voltage and limit current drain will also limit the ability of that internal cap to be fully charged to the actual signal level as it is internally discharged during sampling.

The external cap being larger than the internal cap will over time match the signal level to be measured and when the Teensy starts a sample and charges the internal cap it will have that larger external cap to draw from in a way that will not affect the signal to be measured as shown in the post #16 images where rapid samples indicate a lower than true signal value.
 
Cap is there to get a 'correct' response from the ADC. As noted above the ADC isn't magic, and has a non zero current consumption during conversion and using very high value resistors (manual says max 10k) the measured result will skew, I think downward if I'm understanding the ADC design right. So you can't just use the ADC value * divisor factor to get a voltage. Adding the capacitor means that during non sampling time the capacitor charges slowly through the resistors (slowly in electronic terms anyway), then when ADC starts the cap provides the current need to accurately measure the voltage present. Note this means you can't do high speed reads since you need to wait for the cap to stabilise again.

Without the cap the results should be consistent, just not the actual voltage present. So if you don't want to add it you should be able to manually input known voltages (or use another Teensy DAC) and then map the ADC output numbers to your measured voltage and make up some form of conversion. If all you want is 'full/half/empty indicators then you can just find out what the AnalogRead for those levels are and call it done. Gets trickier if you want to actually display bat voltage somewhere.

If you want a starting point to do your own reading, since this will impact other things down the road
https://en.wikipedia.org/wiki/Input_impedance
https://en.wikipedia.org/wiki/Analog-to-digital_converter
 
Im sorry mm7 but your information does not make sense. Here is why. I have a Teensy 3.1 on my desk with 3.3V - 47K - Teensy - 100K - GND. Im running 96MHz(12MHz ADC Clock) with ADC sampling and conversion set to default speeds(medium), and averages set to 1. The values i'm getting are extremely close considering i'm not using a cap and i'm using DMA to consecutively read the pins. Even reducing sample time and conversion time to its lowest setting still provides a value closer then what your reporting.

You need to provide a picture of what your doing and a sample of the code that shows the issue because what your saying does not make sense.

Here is the code i'm using, it samples 8 pins using 2 DMA channels, each pin is sampled roughly 48000 times a second.

Code:
// Converted to only 1 buffer per ADC, reduced to a small example.
//Based on Example by SaileNav


#include "DMAChannel.h"
#include "ADC.h" 

#define BUF_SIZE 256

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

DMAChannel* dma0 = new DMAChannel(false);
DMAChannel* dma1 = new DMAChannel(false);
DMAChannel* dma2 = new DMAChannel(false);
DMAChannel* dma3 = new DMAChannel(false);


//ChannelsCfg order must be {CH1, CH2, CH3, CH0 }, adcbuffer output will be CH0, CH1, CH2, CH3
//Order must be {Second . . . . . . . . First} no matter the number of channels used.
const uint16_t ChannelsCfg_0 [] =  { 0x47, 0x4F, 0x44, 0x46 };  //ADC0: CH0 ad6(A6), CH1 ad7(A7), CH2 ad15(A8), CH3 ad4(A9)
const uint16_t ChannelsCfg_1 [] =  { 0x45, 0x46, 0x47, 0x44 };  //ADC1: CH0 ad4(A17), CH1 ad5(A16), CH2ad6(A18), CH3 ad7(A19)

const int ledPin = 13;

DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_0[BUF_SIZE];
DMAMEM static volatile uint16_t __attribute__((aligned(BUF_SIZE+0))) adcbuffer_1[BUF_SIZE];

volatile int d2_active;

void setup() {
  // initialize the digital pin as an output.
  pinMode(ledPin, OUTPUT);
  delay(500); 

  d2_active = 0;
  
  pinMode(2, INPUT_PULLUP);
  pinMode(4, OUTPUT);
  pinMode(6, OUTPUT);

  attachInterrupt(2, d2_isr, FALLING);
  
  Serial.begin(9600);
  
  // clear buffer
  for (int i = 0; i < BUF_SIZE; ++i){
      adcbuffer_0[i] = 50000;
      adcbuffer_1[i] = 50000;
    
  }
   
  setup_adc();
  setup_dma(); 
  
}
elapsedMillis debounce;


void loop() {

  while(1){
    uint8_t pin = 0;
    if(d2_active) pin=1;

    if(pin>0){
      
      for (int i = 0; i < BUF_SIZE; i = i + 4){
        int a = adcbuffer_0[i];
        Serial.print(a);
        Serial.print(".");
        int b = adcbuffer_0[i+1];
        Serial.print(b);
        Serial.print(".");
        int c = adcbuffer_0[i+2];
        Serial.print(c);
        Serial.print(".");
        int d = adcbuffer_0[i+3];
        Serial.print(d);
        Serial.print("...");                
        int e = adcbuffer_1[i];
        Serial.print(e);        
        Serial.print(".");
        int f = adcbuffer_1[i+1];
        Serial.print(f);        
        Serial.print(".");
        int g = adcbuffer_1[i+2];
        Serial.print(g);        
        Serial.print(".");                        
        int h = adcbuffer_1[i+3];
        Serial.println(h);

      }

      digitalWrite(ledPin, HIGH);   // set the LED on
      delay(50);                  // wait for a second
      digitalWrite(ledPin, LOW);    // set the LED off

      d2_active = 0;     
    }
  }

  digitalWrite(ledPin, HIGH);   // set the LED on
  delay(100);                  // wait for a second
  digitalWrite(ledPin, LOW);    // set the LED off
  delay(100);   
}

void setup_dma() {
  dma0->begin(true);              // allocate the DMA channel 
  dma0->TCD->SADDR = &ADC0_RA;    // where to read from
  dma0->TCD->SOFF = 0;            // source increment each transfer
  dma0->TCD->ATTR = 0x101;
  dma0->TCD->NBYTES = 2;     // bytes per transfer
  dma0->TCD->SLAST = 0;
  dma0->TCD->DADDR = &adcbuffer_0[0];// where to write to
  dma0->TCD->DOFF = 2; 
  dma0->TCD->DLASTSGA = -2*BUF_SIZE;
  dma0->TCD->BITER = BUF_SIZE;
  dma0->TCD->CITER = BUF_SIZE;    
  dma0->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC0);
  dma0->disableOnCompletion();    // require restart in code
  dma0->interruptAtCompletion();
  dma0->attachInterrupt(dma0_isr);
  
  dma1->begin(true);              // allocate the DMA channel 
  dma1->TCD->SADDR = &ChannelsCfg_0[0];
  dma1->TCD->SOFF = 2;            // source increment each transfer (n bytes)
  dma1->TCD->ATTR = 0x101;
  dma1->TCD->SLAST = -8;          // num ADC0 samples * 2
  dma1->TCD->BITER = 4;           // num of ADC0 samples
  dma1->TCD->CITER = 4;           // num of ADC0 samples
  dma1->TCD->DADDR = &ADC0_SC1A;
  dma1->TCD->DLASTSGA = 0;
  dma1->TCD->NBYTES = 2;
  dma1->TCD->DOFF = 0;
  dma1->triggerAtTransfersOf(*dma0);
  dma1->triggerAtCompletionOf(*dma0);

  dma2->begin(true);              // allocate the DMA channel 
  dma2->TCD->SADDR = &ADC1_RA;    // where to read from
  dma2->TCD->SOFF = 0;            // source increment each transfer
  dma2->TCD->ATTR = 0x101;
  dma2->TCD->NBYTES = 2;     // bytes per transfer
  dma2->TCD->SLAST = 0;
  dma2->TCD->DADDR = &adcbuffer_1[0];// where to write to
  dma2->TCD->DOFF = 2; 
  dma2->TCD->DLASTSGA = -2*BUF_SIZE;
  dma2->TCD->BITER = BUF_SIZE;
  dma2->TCD->CITER = BUF_SIZE;    
  dma2->triggerAtHardwareEvent(DMAMUX_SOURCE_ADC1);
  dma2->disableOnCompletion();    // require restart in code
  dma2->interruptAtCompletion();
  dma2->attachInterrupt(dma2_isr);
  
  dma3->begin(true);              // allocate the DMA channel 
  dma3->TCD->SADDR = &ChannelsCfg_1[0];
  dma3->TCD->SOFF = 2;            // source increment each transfer (n bytes)
  dma3->TCD->ATTR = 0x101;
  dma3->TCD->SLAST = -8;          // num ADC1 samples * 2
  dma3->TCD->BITER = 4;           // num of ADC1 samples
  dma3->TCD->CITER = 4;           // num of ADC1 samples
  dma3->TCD->DADDR = &ADC1_SC1A;
  dma3->TCD->DLASTSGA = 0;
  dma3->TCD->NBYTES = 2;
  dma3->TCD->DOFF = 0;
  dma3->triggerAtTransfersOf(*dma2);
  dma3->triggerAtCompletionOf(*dma2);

  dma0->enable();
  dma1->enable();
  
  dma2->enable();
  dma3->enable();
  
} 

void setup_adc() {
  //ADC0
  adc->setAveraging(1, ADC_0); // set number of averages
  adc->adc0->setResolution(12); // set bits of resolution
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_0); // change the conversion speed
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_0); // change the sampling speed
  //adc->adc0->setReference(ADC_REF_1V2);
  
  //ADC1
  adc->setAveraging(1, ADC_1); // set number of averages
  adc->adc1->setResolution(12); // set bits of resolution
  adc->setConversionSpeed(ADC_MED_SPEED, ADC_1); // change the conversion speed
  adc->setSamplingSpeed(ADC_MED_SPEED, ADC_1); // change the sampling speed
  //adc->adc1->setReference(ADC_REF_1V2);`
  
  ADC1_CFG2 |= ADC_CFG2_MUXSEL;
  
  adc->adc0->enableDMA(); //ADC0_SC2 |= ADC_SC2_DMAEN;  // using software trigger, ie writing to ADC0_SC1A
  adc->adc1->enableDMA();
  
} 

void d2_isr(void) {
  if(debounce > 200){
    d2_active = 1;
    debounce = 0;
    }
    else{return;}
}

void dma0_isr(void) {
    dma0->TCD->DADDR = &adcbuffer_0[0];
    dma0->clearInterrupt();
    dma0->enable();
    digitalWriteFast(4, HIGH);
    digitalWriteFast(4, LOW);
}

void dma2_isr(void) {
    dma2->TCD->DADDR = &adcbuffer_1[0];
    dma2->clearInterrupt();
    dma2->enable();
    digitalWriteFast(6, HIGH);
    digitalWriteFast(6, LOW);
}
 
Last edited:
currently not having a 0.1uF cap, I've just inserted 47K/100K divider into my breadboard.
All works. Voltage of 3.14V battery shows up as 0.99V, USB voltage (5V) shows as 1.98V.

If it works without a cap, what cap gives? Smoothing? But if port is averaging is smoothing required?

At what frequency are you running your teensy? And the ADC-resolution? 10 BIT?
 
Currently I'm experimenting and run Teensy 3.5 on default frequency.
However in future, I'll decrease F to go low power. May be even will change Teensy model to 3.2 or even LC.
It is battery based app - GPS logger to SD, however it supposed to log at 10Hz (10-12 messages/sec), so it is about 1-2KB/sec.
Also I am thinking to send some data via Bluetooth BLE to a smart watch. Like to show speed/odo-meters, GPS/battery status, etc...
 
Im sorry mm7 but your information does not make sense. Here is why. I have a Teensy 3.1 on my desk with 3.3V - 47K - Teensy - 100K - GND. ...
Hi Donziboy2, thanks for your advises!

Actually my circuit is 3.3V - 100K - Teensy:A0 - 47K - GND.

Your sketch does not print anything. d2_active is always 0.



GremlinWrangler, thanks for explanation. It is getting clear.
 
Last edited:
That divider explains your 3.14V reading but the 5V reading is way off(should be near 1.599V for 100K/47K).
A source like batteries will tend to be fairly clean but with a high divider it causes issues with noise, the higher the resistance used the easier noise can cause fluctuation. You can sample/average it a lot which chews up CPU time or use a capacitor to buffer the ADC input, the size of the capacitor determines how much noise can be filtered out. 0.01 - 0.1uF tends to be the sweet spot for most Teensy users.
Slower CPU Clock frequency's will increase the overall accuracy provided the FBus also goes down with it.

The sketch, yep, sorry you have to touch a jumper wire/button on pin 2 to GND to trigger the interrupt that lets it print, it only prints the 2*256 buffers then waits for the pin to trigger again. The DMA runs in the background constantly, you can put a Scope on pin 4 or 6 to see when the interrupts for the DMA's trigger.
 
Status
Not open for further replies.
Back
Top