PlatformIO package of the Teensy core framework compatible with GCC 10 & C++20
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

internal_reference.ino 6.5KB

3 years ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. /* Example for the use of the internal voltage reference VREF
  2. * VREF can vary between 1.173 V and 1.225 V, so it can be adjusted by software.
  3. * This example shows how to find the optimum value for this adjustment (trim).
  4. * You need a good multimeter to measure the real voltage difference between AGND and 3.3V,
  5. * change voltage_target accordingly and run the program. The optimum trim value is printed at the end.
  6. * The bisection algorithm prints some diagnostics while running.
  7. * Additionally, the bandgap voltage is printed too, it should lie between 0.97 V and 1.03 V, with 1.00 V being the typical value.
  8. * Because of noise, the trim value is not accurate to 1 step, it fluctuates +- 1 or 2 steps.
  9. */
  10. #ifdef ADC_USE_INTERNAL_VREF
  11. #include <ADC.h>
  12. #include <VREF.h>
  13. ADC* adc = new ADC();
  14. //! change this value to your real input value, measured between AGND and 3.3V
  15. float voltage_target = 3.29;
  16. //! change the TOL (tolerance, minimum difference in mV between voltage and target that matters)
  17. //! to refine even more, but not to less than 0.5 mV
  18. const float TOL = 2.0;
  19. // Maximum iterations of the algorithm, no need to change it.
  20. const uint8_t MAX_ITER = 100;
  21. float average = 0;
  22. const int NUM_AVGS = 100;
  23. // Get the voltage of VREF using the trim value
  24. float get_voltage(uint8_t trim) {
  25. average = 0;
  26. VREF::trim(trim);
  27. VREF::waitUntilStable();
  28. delay(50);
  29. for(int i=0; i<NUM_AVGS; i++) {
  30. average += 1.195/adc->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT)*(adc->adc0->getMaxValue());
  31. }
  32. return average/NUM_AVGS;
  33. }
  34. // Simple bisection method to get the optimum trim value
  35. // This method is not the bests for noisy functions...
  36. // Electrical datasheet: "The Voltage Reference (VREF) is intended to supply an accurate voltage output
  37. // that can be trimmed in 0.5 mV steps."
  38. int8_t optimumTrimValue(float voltage_target) {
  39. // https://en.wikipedia.org/wiki/Bisection_method#Algorithm
  40. // INPUT: Function f, endpoint values a, b, tolerance TOL, maximum iterations NMAX
  41. // CONDITIONS: a < b, either f(a) < 0 and f(b) > 0 or f(a) > 0 and f(b) < 0
  42. // OUTPUT: value which differs from a root of f(x)=0 by less than TOL
  43. //
  44. // N ← 1
  45. // While N ≤ NMAX # limit iterations to prevent infinite loop
  46. // c ← (a + b)/2 # new midpoint
  47. // If f(c) = 0 or (b – a)/2 < TOL then # solution found
  48. // Output(c)
  49. // Stop
  50. // EndIf
  51. // N ← N + 1 # increment step counter
  52. // If sign(f(c)) = sign(f(a)) then a ← c else b ← c # new interval
  53. // EndWhile
  54. // Output("Method failed.") # max number of steps exceeded
  55. const uint8_t MAX_VREF_trim = 63;
  56. const uint8_t MIN_VREF_trim = 0;
  57. uint8_t niter = 0;
  58. uint8_t a = MIN_VREF_trim, b = MAX_VREF_trim, midpoint = (a + b)/2;
  59. float cur_diff, diff_a;
  60. // start VREF
  61. VREF::start(VREF_SC_MODE_LV_HIGHPOWERBUF, midpoint);
  62. Serial.println("niter,\ta,\tb,\tmidpoint,\tdiff (mV)");
  63. while (niter < MAX_ITER) {
  64. midpoint = (a + b)/2;
  65. cur_diff = (get_voltage(midpoint) - voltage_target)*1000;
  66. Serial.print(niter, DEC); Serial.print(",\t");
  67. Serial.print(a, DEC); Serial.print(",\t");
  68. Serial.print(b, DEC); Serial.print(",\t");
  69. Serial.print(midpoint, DEC); Serial.print(",\t\t");
  70. Serial.println(cur_diff, 2);
  71. if (abs(cur_diff) <= TOL || b-a < 1) {
  72. return midpoint;
  73. }
  74. niter++;
  75. diff_a = get_voltage(a) - voltage_target;
  76. if ((cur_diff < 0 && diff_a < 0) || (cur_diff > 0 && diff_a > 0)) {
  77. a = midpoint;
  78. }
  79. else {
  80. b = midpoint;
  81. }
  82. }
  83. return -1;
  84. }
  85. void setup() {
  86. Serial.begin(9600);
  87. // Best measurement conditions
  88. adc->adc0->setAveraging(32);
  89. adc->adc0->setResolution(16);
  90. adc->adc0->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);
  91. adc->adc0->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_LOW_SPEED);
  92. #ifdef ADC_DUAL_ADCS
  93. adc->adc1->setAveraging(32);
  94. adc->adc1->setResolution(16);
  95. adc->adc1->setConversionSpeed(ADC_CONVERSION_SPEED::VERY_LOW_SPEED);
  96. adc->adc1->setSamplingSpeed(ADC_SAMPLING_SPEED::VERY_LOW_SPEED);
  97. #endif // ADC_DUAL_ADCS
  98. delay(2000);
  99. int8_t VREF_trim = optimumTrimValue(voltage_target);
  100. Serial.print("Optimal trim value: "); Serial.println(VREF_trim);
  101. VREF::start(VREF_SC_MODE_LV_HIGHPOWERBUF, VREF_trim);
  102. VREF::waitUntilStable();
  103. Serial.print("3.3V pin value: ");
  104. Serial.print(1.195/adc->adc0->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT)*adc->adc0->getMaxValue(), 5);
  105. Serial.println(" V.");
  106. Serial.print("Bandgap value: ");
  107. //Serial.print(3.3*adc->adc0->analogRead(ADC_INTERNAL_SOURCE::BANDGAP)/adc->adc0->getMaxValue(), 5);
  108. adc->adc0->analogRead(ADC_INTERNAL_SOURCE::BANDGAP);
  109. adc->adc0->differentialMode(); // Use differential mode for better precision.
  110. Serial.print(voltage_target*adc->adc0->readSingle()/adc->adc0->getMaxValue(), 5);
  111. Serial.println(" V. (Should be between 0.97 and 1.03 V.)");
  112. // VREF::stop(); // you can stop it to save power.
  113. }
  114. void loop() {
  115. // If you now run your teensy from a battery you can check the charge by looking at the 3.3V pin voltage
  116. // it should be 3.3V, but it will fall as the battery discharges.
  117. Serial.print("3.3V pin value: ");
  118. Serial.print(1.195/adc->adc0->analogRead(ADC_INTERNAL_SOURCE::VREF_OUT)*adc->adc0->getMaxValue(), 5);
  119. Serial.println(" V.");
  120. delay(3000);
  121. }
  122. /*
  123. My output with a Teensy 3.6 connected to my laptop's USB.
  124. With a cheap multimeter I measured voltage_target = 3.29 V, the results are:
  125. niter, a, b, midpoint, diff (mV)
  126. 0, 0, 63, 31, 24.26
  127. 1, 31, 63, 47, 2.54
  128. 2, 47, 63, 55, -7.90
  129. 3, 47, 55, 51, -2.58
  130. 4, 47, 51, 49, 0.03
  131. Optimal trim value: 49
  132. VREF value: 3.29024 V.
  133. Bandgap value: 0.99948 V.
  134. For Teensy 3.5 connected to my laptop's USB.
  135. voltage_target = 3.28 V, the results are:
  136. niter, a, b, midpoint, diff (mV)
  137. 0, 0, 63, 31, 21.97
  138. 1, 31, 63, 47, 0.27
  139. Optimal trim value: 47
  140. VREF value: 3.28154 V.
  141. Bandgap value: 1.00387 V.
  142. My output with a (quite old) Teensy 3.0 connected to my laptop's USB.
  143. voltage_target = 3.22 V, the results are:
  144. niter, a, b, midpoint, diff (mV)
  145. 0, 0, 63, 31, 37.34
  146. 1, 31, 63, 47, 16.56
  147. 2, 47, 63, 55, 5.54
  148. 3, 55, 63, 59, -0.00
  149. Optimal trim value: 59
  150. VREF value: 3.21947 V.
  151. Bandgap value: 1.01978 V.
  152. */
  153. #else // make sure the example can run for any boards (automated testing)
  154. void setup() {}
  155. void loop() {}
  156. #endif // ADC_USE_INTERNAL_VREF