// Analog input test for Teensy 4.X Oct 4 2012 J.Beale
// Hacked on by JonR (probably the source of any problems)
// Setup: https://picasaweb.google.com/109928236040342205185/Electronics#5795546092126071650
// Posted to PJRC form 8/20/20
// Modified by mborgerson 8/20/20
// Changed for T3.6 and 16-bit input 8/21/2020
// Switched to collecting samples and after-the-fact statistics
// Added histogram display of samples and raw data
#define VREF (3.2444) // ADC reference voltage (= power supply)
#define VINPUT (1.04020) // ADC input voltage from resistive divider to VREF
#define ADCMAX (65536) // maximum possible reading from ADC
#define EXPECTED (ADCMAX*(VINPUT/VREF)) // expected ADC reading
#define OFFSET 0 // -149.76 // calibrate out a fixed offset
#define num 1 // ADC internal oversampling
const int analogInPin = A9; // Analog input pin
const int ledpin = 13;
#define MAXBINS 1000
uint32_t histobins[MAXBINS];
#define OSMAX 1024
/* for later implementation of running mean
uint16_t rmdat[MAXOS];
uint16_t rmidx = 0;
uint32_t rmsum;
*********************************************/
float simStd = 8.0;
#define SAMPLES 2000 // how many samples to combine for pp, std.dev statistics
#define RAWSAMPLEMAX 64000
uint16_t OS = 32;
float mnsamples[SAMPLES], mdsamples[SAMPLES];
uint16_t rawsamples[RAWSAMPLEMAX];
uint16_t rawmin,rawmax;
#define SAMPLERATE 500
bool histoflag = false;
bool excelformat = false;
bool useanalog = false;
elapsedMicros sampletimer;
#define LEDON digitalWriteFast(ledpin, HIGH);
#define LEDOFF digitalWriteFast(ledpin, LOW);
void setup() { // ==============================================================
pinMode(analogInPin, INPUT_DISABLE); // because I don't know default at startup
pinMode(ledpin, OUTPUT);
analogReadAveraging(4);
analogReadRes(16); // Teensy 3.6: set ADC resolution to this many bits
// on T4.X, Default Arduino AnalogRead(pin) takes about 6uSec---the slowest mode!
Serial.begin(115200); // baud rate is ignored with Teensy USB ACM i/o
delay(1000);
Serial.printf("# Teensy 3.6 16-bit ADC test start: %dx%d oversampling \n\n", OS, num);
delay(1000);
srand(micros()); // use unix time as random seed
Serial.println();
ARM_DEMCR |= ARM_DEMCR_TRCENA;
ARM_DWT_CTRL |= ARM_DWT_CTRL_CYCCNTENA;
} // ==== end setup() ===========
// for T3.6 with 16-bit ADC, samples are float because mean and running mean return values with fractional part
int sort_descD(const void *cmp1, const void *cmp2)
{
// Need to cast the void * to uint16_t
float a = *((float *)cmp1);
float b = *((float *)cmp2);
// The comparison
return a > b ? -1 : (a < b ? 1 : 0);
}
// Simple user interface to switch between mean and median of 32 samples
// and to turn dithering on and off
void CheckUserInput(void) {
char ch;
if (Serial.available()) {
ch = Serial.read();
if (ch == 'e') excelformat = !excelformat;
if (ch == 'a') useanalog = !useanalog;
if (ch == 'h') histoflag = true;
if (ch == '1') OS = 32;
if (ch == '2') OS = 128;
if (ch == '3') OS = 1024;
if (ch == '4') simStd = 4.0;
if (ch == '5') simStd = 8.0;
if (ch == '6') simStd = 16.0;
if (ch == '7') simStd = 32.0;
srand(micros()); // reseed the RNG
}
}
float medarray[OSMAX]; // used to qsort oversampled adc values
// uint32_t dwt;
// dwt = ARM_DWT_CYCCNT;
// Added fixed timing for oversampled acquisition
void loop() { // ================================================================
float datAvg1, datAvg2, pkpk1, pkpk2,std1,std2;
float smin1,smax1, smin2,smax2;
float ctimesum;
uint32_t startctime;
uint16_t adval;
char ch;
float x1,x2, ovsum;
uint16_t rawidx, rawmed;
ctimesum = 0; // for accumulating sum of conversion and calculation times
sampletimer = 0; // reset the sampling timer
rawidx = 0; rawmin = 65535; rawmax = 0;
for (int i = 0; i < SAMPLES; i++) {
// wait until time for next sample
while (sampletimer < (1000000 / SAMPLERATE));
sampletimer = 0; // reset the sampling timer
startctime = micros();
ovsum = 0;
for (int j = 0; j < OS; ++j) {
if(useanalog){
adval = analogRead(analogInPin);
} else {
adval = SimAnalog();a
}
if(rawidx < RAWSAMPLEMAX){
rawsamples[rawidx] = adval;
rawidx++;
}
if(adval > rawmax) rawmax = adval;
if(adval < rawmin) rawmin = adval;
ovsum += adval; // for mean(32) calculation
medarray[j] = (float)adval; // for median(32) calculation
} // end of oversampling
x1 = (ovsum / OS); // This does the mean(32) calculation
qsort(medarray, OS, sizeof(medarray[0]), sort_descD);
x2 = medarray[OS / 2]; // This is the median(32) calculation
mnsamples[i] = x1;
mdsamples[i] = x2;
ctimesum += micros() - startctime;
}// end of sampling for loop
MeanEtc(mnsamples, SAMPLES, &datAvg1,&std1,&smin1, &smax1);
pkpk1 = smax1-smin1;
MeanEtc(mdsamples, SAMPLES, &datAvg2,&std2,&smin2, &smax2);
pkpk2 = smax2-smin2;
// Serial.printf("\n Avg. Conversion time: %6.2f uSec\n", ctimesum / SAMPLES);
// Serial.printf(" Raw min %u Raw max %u\n", rawmin, rawmax);
if (excelformat) { // print tab separated for direct paste to Excel
Serial.printf("%8.2f \t%8.2f \t", datAvg1, datAvg2);
Serial.printf("% 8.2f \t% 8.2f \t", pkpk1, pkpk2);
Serial.printf("%6.3f\t %6.3f\t ", std1, std2);
if(useanalog) Serial.print("A"); else Serial.print("S");
Serial.println();
} else { //verbose format on two lines with labels
Serial.printf(" Avg: %8.2f ", datAvg1);
Serial.printf(" P-P :%8.2f", pkpk1);
Serial.printf(" St.Dev: %7.3f Mean(%u) ", std1, OS);
if(useanalog) Serial.println("A"); else Serial.println("S");
Serial.printf(" Avg: %8.2f ", datAvg2);
Serial.printf(" P-P :%8.2f", pkpk2);
Serial.printf(" St.Dev: %7.3f Median(%u)", std2, OS);
Serial.printf(" ctime: %5.1f\n\n", (float)ctimesum/SAMPLES);
}
if(histoflag){
binMedianF(mnsamples, SAMPLES, histobins, MAXBINS, (uint16_t)smin1, (uint16_t)smax1);
Serial.printf("Mean(%u) method ", OS);
ShowHisto(histobins, (uint16_t)smin1, (uint16_t)smax1);
binMedianF(mdsamples, SAMPLES, histobins, MAXBINS, (uint16_t)smin2, (uint16_t)smax2);
Serial.printf("Median(%u) method ", OS);
ShowHisto(histobins, (uint16_t)smin2, (uint16_t)smax2);
rawmed = binMedianU(rawsamples, RAWSAMPLEMAX, histobins, MAXBINS, (uint16_t)rawmin, (uint16_t)rawmax);
Serial.printf("Median of Raw values: %u ",rawmed);
ShowHisto(histobins, (uint16_t)rawmin, (uint16_t)rawmax);
histoflag = false;
Serial.println("\nPress any key to continue.");
while(!Serial.available());
ch = Serial.read();
}
CheckUserInput();
} // end loop() =====================================================
#define SIMMEAN 20000.0
uint16_t SimAnalog(void){
bool badresult;
uint16_t ival;
float x;
do {
badresult = false;
x = fastrnorm(SIMMEAN, simStd);
ival = floor(x+0.5); // round the result
if (ival > 65535) badresult = true;
if (ival > (SIMMEAN + 6 * simStd)) badresult = true;
if (ival < (SIMMEAN - 6 * simStd)) badresult = true;
} while (badresult); // fall through when result in bounds
return ival;
}
// do the easy stats mean, median, mode, max, min
// converted input array to float because oversampled mean has fractional part
void MeanEtc(float dat[], uint32_t nvals, float *mnptr, float *stptr, float *minp, float *maxp) {
uint32_t i;
float_t imin, imax, ival;
float var, fv, s1, s2, mean;
double sum, sumsq; // with 4 million samples, even 32-bit floats have rounding problems!
// NOTE SUMSQ gets LARGE: suppose the values are ~4000 and you sum 100,000 of them then the sumsq
// is about (4x10^3) * (4x10^3) * (1x10^5) or 1.6x10^12 which is way too big for a uint32_t
sum = 0.0;
sumsq = 0.0;
imin = 65535;
imax = 0;
// first get sum and calculate mean
for (i = 0; i < nvals; i++) {
ival = dat[i];
if (ival < imin) imin = ival;
if (ival > imax) imax = ival;
sum += (float)ival;
sumsq += (float)ival * (float)ival;
}
mean = sum / nvals;
// now rcalculate variance in tw-pass algorithm to minimize roundoff error
s1 = 0; s2 = 0;
for (i = 0; i < nvals; i++) {
fv = dat[i] - mean;
s1 += (fv * fv );
s2 += fv;
}
var = (s1 - s2 * s2 / nvals) / (nvals - 1);
*mnptr = mean;
//Serial.printf("Variance: %8.3ef s1: %8.3e s2^2/n: %8.3e\n", var, s1, s2*s2/nvals);
*stptr = sqrt(var);
*minp = imin;
*maxp = imax;
}
// Accumulate bin counts for histogram data should be in range 0..65535
// takes about 140mSec to find median of 100,000 records
// this version for uint16_t dat
uint16_t binMedianU(uint16_t dat[], uint32_t numsamp, uint32_t bins[], uint16_t numbins, uint16_t imin, uint16_t imax) {
uint32_t i, bsum, maxbinval;
uint16_t val;
if((imax-imin) > numbins){
Serial.printf("Data range too high for %u bins\n", MAXBINS);
return 0;
}
if(imax<=imin){
Serial.printf("Max <= Min\n");
return 0;
}
Serial.printf("min: %u and max %u \n",imin, imax);
// empty the bins--they could be on the stack and have non-zero values
memset(bins, 0, numbins * sizeof(uint32_t));
// Serial.printf("In binMedian, address of dat is % p\n", &dat);
// delay(5);
//maxbinval = 0;
for (i = 0; i < numsamp; i++) {
val = dat[i];
if ((val-imin) < numbins) { // prevent writing past end of bins array
bins[val-imin]++;
} else printf("Out of Bounds with %u and min %u\n", val, imin);
} // bins are filled
i = 0; bsum = 0;
do {
bsum += bins[i];
i++;
} while (bsum < numsamp / 2);
return imin+ i - 1;
}
// Accumulate bin counts for histogram data should be in range 0..65535
// takes about 140mSec to find median of 100,000 records
// this version for floating point dat
uint16_t binMedianF(float dat[], uint32_t numsamp, uint32_t bins[], uint16_t numbins, uint16_t imin, uint16_t imax) {
uint32_t i, bsum, maxbinval;
uint16_t val;
if((imax-imin) > numbins){
Serial.printf("Data range too high for %u bins\n", MAXBINS);
return 0;
}
if(imax<=imin){
Serial.printf("Max <= Min\n");
return 0;
}
Serial.printf("min: %u and max %u \n",imin, imax);
// empty the bins--they could be on the stack and have non-zero values
memset(bins, 0, numbins * sizeof(uint32_t));
// Serial.printf("In binMedian, address of dat is % p\n", &dat);
// delay(5);
//maxbinval = 0;
for (i = 0; i < numsamp; i++) {
val = (uint16_t)dat[i]; // truncate data to integer
// if(val == imin) Serial.printf( "min of %u at %u\n", val, i);
// if(val == imax-1) Serial.printf( "max of %u at %u\n", val, i);
if ((val-imin) < numbins) { // prevent writing past end of bins array
bins[val-imin]++;
} else printf("Out of Bounds with %u and min %u\n", val, imin);
} // bins are filled
i = 0; bsum = 0;
do {
bsum += bins[i];
i++;
} while (bsum < numsamp / 2);
return imin+i - 1;
}
// with new binMedian algorithm, lowest sample value is in bin[bins]
void ShowHisto(uint32_t bins[], uint16_t imin, uint16_t imax) {
uint16_t i, lnum;
lnum = 0;
Serial.print("Histogram Data");
for (i = 0; i < (imax-imin+1); i++) {
if ((lnum % 10) == 0) Serial.printf("\n % 4d: ", i+imin);
lnum++;
Serial.printf(" % 5lu ", bins[i]);
}
Serial.println();
}
// returns a random floating point value between 0.0 and 1.0
// we use the built-in rand() function for speed as we are
// not particularly picky about the characteristics of the number
float randf(void) {
float val;
val = (float)rand() / ((float)(RAND_MAX) - 0.5);
return val;
}
// convert floating rnd to normal curve
float fastrnorm(float mean, float stdDev)
{
static float spare;
static float u1;
static float u2;
static float s;
static bool isSpareReady = false;
if (isSpareReady)
{
isSpareReady = false;
return ((spare * stdDev) + mean);
} else {
do {
u1 = (randf() * 2) - 1;
u2 = (randf() * 2) - 1;
s = (u1 * u1) + (u2 * u2);
} while (s >= 1.0);
s = sqrtf(-2.0 * logf(s) / s);
spare = u2 * s;
isSpareReady = true;
return (mean + (stdDev * u1 * s));
}
}