Why is one of the time multiplexed analogReads of the same pin (reading noise) not centered around zero?

shookti

Member
I have a program that acquires time multiplexed analog data from a single pin (pin #27) on a Teensy 4.1.
Each measurement is a single value that's reported using Serial.print averaged over ~500 microseconds, with some values being averaged by setting analogReadAveraging(32) and the remaining by averaging by code shown below.

The problem I am facing is that - while testing the code by putting my finger on pin #27, just to see the noise readout, the noise values read by the "excitation" section jumps to around -40 (set to 12 bit resolution) and slowly veering to zero instead of being centred around 0 like how the 'isosbestic' section is.

For some context this program modulates 2 LEDs of different wavelengths that are exciting certain proteins in populations of neurons in live rat brains that fluoresce at different intensities based on the concentration of calcium ions in the population (method known as "in-vivo fibre photometry" in neuroscience).
One important feature of the program is that the fluorescence values are analog-read with baseline subtraction to account for noise in the fluorescence activity in the brain

There might be some unused / undeclared variables in the following code snippet because I have erased a lot of repetitive code to make it easier to read, and some declarations are in another header file. I don't think this code has anything to do with this problem.

I have verified that all the modulation steps are being executed correctly.

1701674607560.png


C++:
#include "config.hpp"

IntervalTimer timer;
volatile int timeCounter = 0;
int totalTime = MEASURE + LED_ON + ISO_EXC_GAP + MEASURE + LED_ON + (TIME_PERIOD * 1000);

float baselines [MAX_MEASUREMENTS] = { };
int baselineCounter = 0;

volatile bool recording = false;
volatile uint64_t timestamp = 0;
bool timestampSet = false;

void setup() {
    Serial.begin(SERIAL_BAUD);
    Serial.println("START");

    // irl540n switches in the circuit are active LOW, so start with HIGH
    pinMode(ISO_LED, OUTPUT);
    digitalWriteFast(ISO_LED, HIGH);
    pinMode(EXC_LED, OUTPUT);
    digitalWriteFast(EXC_LED, HIGH);
    pinMode(OPTO_LED, OUTPUT);
    digitalWriteFast(OPTO_LED, HIGH);

    if (BIT_DEPTH != -1) analogReadResolution(BIT_DEPTH);
    if (BUILTIN_AVG != -1) analogReadAveraging(BUILTIN_AVG);
    timer.begin(incrementTimeCounter, 1);
}

void loop() {
    if (!(recording || RECORD_ON_POWERUP)) return;

    if (RECORD_ON_POWERUP && (!timestampSet)) {
        timestamp = 0;
        timestampSet = true;
    }

    // measure iso baseline
    if (timeCounter >= isoBaselineMeasureOn && timeCounter <= isoBaselineMeasureOff) {
        baselines[baselineCounter] = measure(MEASURE_PIN, NOT_BUILTIN_AVG);
        baselineCounter++;
    }

    // turn on iso LED
    if (timeCounter >= isoLedOn and timeCounter <= isoLedOff) {
        digitalWriteFast(ISO_LED, LOW);
    }

    // measure iso emission
    if (timeCounter >= isoEmmMeasureOn && timeCounter <= isoEmmMeasureOff) {
        float val = 0.0;

        for (int i = 0; i < baselineCounter; i++) {
            val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG) - baselines[i];
            baselines[i] = 0;
        }

        val = val / baselineCounter;

        if (val == val) {
            // sometimes the values are nans, unknown reason
            Serial.print("i,");
            Serial.print(timestamp);
            Serial.print(',');
            Serial.println(val / baselineCounter, 4);
        }

        baselineCounter = 0;
    }

    // turn off iso LED
    if (timeCounter >= isoLedOff) {
        digitalWriteFast(ISO_LED, HIGH);
    }

    // measure exc baseline
    if (timeCounter >= excBaselineMeasureOn && timeCounter <= excBaselineMeasureOff) {
        baselines[baselineCounter] = measure(MEASURE_PIN, NOT_BUILTIN_AVG);
        baselineCounter++;
    }

    // turn on exc LED
    if (timeCounter >= excLedOn && timeCounter <= excLedOff) {
        digitalWriteFast(EXC_LED, LOW);
    }

    // measure exc emission
    if (timeCounter >= excEmmMeasureOn && timeCounter <= excEmmMeasureOff) {
        float val = 0.0;

        for (int i = 0; i < baselineCounter; i++) {
            val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG) - baselines[i];
            baselines[i] = 0;
        }

        val = val / baselineCounter;

        if (val == val) {
            // sometimes the values are nans, unknown reason
            Serial.print("e,");
            Serial.print(timestamp);
            Serial.print(',');
            Serial.println(val / baselineCounter, 4);
        }

        baselineCounter = 0;
    }

    // turn off exc LED
    if (timeCounter >= excLedOff) {
        digitalWriteFast(EXC_LED, HIGH);
    }
}

void incrementTimeCounter() {
    timestamp++;

    if (timeCounter != totalTime) {
        timeCounter++;
    } else {
        timeCounter = 0;
    }
}

int measure(int pin, int avgx) {
    if (avgx == 1 || avgx == -1) {
        return analogRead(pin);
    }

    float val = 0;

    for (int i = 0; i < avgx; i++) {
        val += analogRead(pin);
    }

    return (val / avgx);
}

What could be the reason for this behaviour where noise created by my finger is not centred around Zero for one type of analogReads?

Another issue I am facing is that sometimes (randomly) the values reported are NaN, hence the reason for the "val == val" checks, which only NaNs fail.
 
Last edited:
Missing config.hpp -

Created code that may follow desired text in a simple fashion?
Code:
void setup() {
    analogReadResolution(12);
    analogReadAveraging(32);
}

elapsedMillis aTime;
void loop() {
  static int aSum=0;
  static int aCnt=0;
  if ( aTime > 1000 ) {
    if (aCnt) Serial.println( aSum/aCnt );
    aSum=0;
    aCnt=0;
    aTime-=1000;
  }
  aCnt++;
  aSum += analogRead(27);
}

Wire in a header does something odd at some points the read goes and stays high until grounded?
Floats at 33 rises with finger then proximity to something (?) or (?) and it goes over 4K and stays with or with finger - even when finger on GND?
74
65
78
79
79
80
79
79
79
75
3164
4078
4078
4078
4078
4078
4078
4078
4078
4078
4078
4069
4034
4042
3974
3973
3871
3806
3778
3804
3785
3849
1225
0
15
55
38
32
32
32
32
 
Floats at 33 rises with finger then proximity to something (?) or (?) and it goes over 4K and stays with or with finger - even when finger on GND?
Pads default to having their keeper enabled unless you use pinMode() to reconfigure them, which causes problems with ADC (page 3407 of the reference manual mentions this).
 
Missing config.hpp -
It does not #include anything else. It only contains variable definitions like the numbers that decide the LED modulation and measurement timing. Do you still need to see the code?

Floats at 33 rises with finger then proximity to something (?) or (?) and it goes over 4K and stays with or with finger - even when finger on GND?
I'm sorry, I did not understand this. What do you mean by "proximity to something (?) or (?)"

I ran the code that you've used to test this behaviour and this was the output, it does not show the behaviour you saw:
24
24
24
24
52
97
87
84
81
79
41
24
24
24
24
24
25
When it jumps from 24 to 52 is when I touched the pin with my finger
 
Last edited:
Pads default to having their keeper enabled unless you use pinMode() to reconfigure them, which causes problems with ADC (page 3407 of the reference manual mentions this).
Are you saying that after every analogRead, the pin should be reconfigured using pinMode?
Since the pin is being used to read analog values from, wouldn't using pinMode set the pin to INPUT_PULLUP or INPUT_PULLDOWN which would render the pin unusable for analog input?

page 3407 of the reference manual mentions this
I could not find the document you are referring to, could you post a link to it? The largest document I found was 145 pages long :p
 
Since the pin is being used to read analog values from, wouldn't using pinMode set the pin to INPUT_PULLUP or INPUT_PULLDOWN which would render the pin unusable for analog input?
What @jmarsh meant is to use pinMode(<pin>, INPUT_DISABLE); in void setup().
So in your case pinMode(MEASURE_PIN, INPUT_DISABLE);
More info here.

Paul
 
It does not #include anything else. It only contains variable definitions like the numbers that decide the LED modulation and measurement timing. Do you still need to see the code?
Without that the code cannot be run as posted - nor can it be seen what other pins are involved.

Pads default to having their keeper enabled unless you use pinMode() to reconfigure them, which causes problems with ADC (page 3407 of the reference manual mentions this).
Indeed, that changes the situation with test code presented p#2: pinMode(27, INPUT_DISABLE);
 
Without that the code cannot be run as posted - nor can it be seen what other pins are involved.
Here is all the code:

PhotoOpto.ino:

C++:
#include "config.hpp"

IntervalTimer timer;
volatile int timeCounter = 0;
int totalTime = MEASURE + LED_ON + ISO_EXC_GAP + MEASURE + LED_ON + (TIME_PERIOD * 1000);

// times at which isosbestic LED turns on and off, and analog data is acquired
int isoBaselineMeasureOn = 0;
int isoBaselineMeasureOff = MEASURE;
int isoLedOn = MEASURE;
int isoLedOff = isoLedOn + LED_ON;
int isoEmmMeasureOn = LED_ON;
int isoEmmMeasureOff = LED_ON + MEASURE;

// excitation LED timing + analog data acquisition
int excBaselineMeasureOn = isoEmmMeasureOff + ISO_EXC_GAP;
int excBaselineMeasureOff = excBaselineMeasureOn + MEASURE;
int excLedOn = excBaselineMeasureOff;
int excLedOff = excLedOn + LED_ON;
int excEmmMeasureOn = excBaselineMeasureOn + LED_ON;
int excEmmMeasureOff = excBaselineMeasureOn + LED_ON + MEASURE;

float baselines [MAX_MEASUREMENTS] = { };
int baselineCounter = 0;
volatile bool recording = false;
volatile uint64_t timestamp = 0;
bool timestampSet = false;
volatile int  msgNum = 0;
volatile int msgTime = 0;

void setup() {
    Serial.begin(SERIAL_BAUD);
    Serial.println("START");
  
    // irl540n switches in the circuit are active LOW, so start with HIGH
    pinMode(ISO_LED, OUTPUT);
    digitalWriteFast(ISO_LED, HIGH);
    pinMode(EXC_LED, OUTPUT);
    digitalWriteFast(EXC_LED, HIGH);
    pinMode(OPTO_LED, OUTPUT);
    digitalWriteFast(OPTO_LED, HIGH);
  
    pinMode(START_RECORDING, INPUT_PULLDOWN);
    pinMode(STOP_RECORDING, INPUT_PULLDOWN);
    pinMode(OPTO_STIM, INPUT_PULLDOWN);
    pinMode(LOG_MSG_1, INPUT_PULLDOWN);
    pinMode(LOG_MSG_2, INPUT_PULLDOWN);
    pinMode(LOG_MSG_3, INPUT_PULLDOWN);
    pinMode(LOG_MSG_4, INPUT_PULLDOWN);
    pinMode(LOG_MSG_5, INPUT_PULLDOWN);
    pinMode(LOG_MSG_6, INPUT_PULLDOWN);
  
    attachInterrupt(digitalPinToInterrupt(START_RECORDING), startRecording, RISING);
    attachInterrupt(digitalPinToInterrupt(STOP_RECORDING), stopRecording, RISING);
    attachInterrupt(digitalPinToInterrupt(OPTO_STIM), optoStim, CHANGE);
    attachInterrupt(digitalPinToInterrupt(LOG_MSG_1), logMsg1, RISING);
    attachInterrupt(digitalPinToInterrupt(LOG_MSG_2), logMsg2, RISING);
    attachInterrupt(digitalPinToInterrupt(LOG_MSG_3), logMsg3, RISING);
    attachInterrupt(digitalPinToInterrupt(LOG_MSG_4), logMsg4, RISING);
    attachInterrupt(digitalPinToInterrupt(LOG_MSG_5), logMsg5, RISING);
    // attachInterrupt(digitalPinToInterrupt(LOG_MSG_6), logMsg6, RISING);
  
    if (BIT_DEPTH != -1) analogReadResolution(BIT_DEPTH);
    if (BUILTIN_AVG != -1) analogReadAveraging(BUILTIN_AVG);
    timer.begin(incrementTimeCounter, 1);
}

void loop() {
    if (!(recording || RECORD_ON_POWERUP)) return;
  
    if (RECORD_ON_POWERUP && (!timestampSet)) {
        timestamp = 0;
        timestampSet = true;
    }
  
    // measure iso baseline
    if (timeCounter >= isoBaselineMeasureOn && timeCounter <= isoBaselineMeasureOff) {
        baselines[baselineCounter] = measure(MEASURE_PIN, NOT_BUILTIN_AVG);
        baselineCounter++;
    }
  
    // turn on iso LED
    if (timeCounter >= isoLedOn and timeCounter <= isoLedOff) {
        digitalWriteFast(ISO_LED, LOW);
    }
  
    // measure iso emission
    if (timeCounter >= isoEmmMeasureOn && timeCounter <= isoEmmMeasureOff) {
        float val = 0.0;
      
        for (int i = 0; i < baselineCounter; i++) {
            val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG) - baselines[i];
            baselines[i] = 0;
        }
      
        val = val / baselineCounter;
      
        if (val == val) {
            Serial.print("i,");
            Serial.print(timestamp);
            Serial.print(',');
            Serial.println(val / baselineCounter, 4);
        }
      
        baselineCounter = 0;
    }
  
    // turn off iso LED
    if (timeCounter >= isoLedOff) {
        digitalWriteFast(ISO_LED, HIGH);
    }
  
    // measure exc baseline
    if (timeCounter >= excBaselineMeasureOn && timeCounter <= excBaselineMeasureOff) {
        baselines[baselineCounter] = measure(MEASURE_PIN, NOT_BUILTIN_AVG);
        baselineCounter++;
    }
  
    // turn on exc LED
    if (timeCounter >= excLedOn && timeCounter <= excLedOff) {
        digitalWriteFast(EXC_LED, LOW);
    }
  
    // measure exc emission
    if (timeCounter >= excEmmMeasureOn && timeCounter <= excEmmMeasureOff) {
        float val = 0.0;
      
        for (int i = 0; i < baselineCounter; i++) {
            val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG) - baselines[i];
            baselines[i] = 0;
        }
      
        val = val / baselineCounter;
      
        if (val == val) {
            Serial.print("e,");
            Serial.print(timestamp);
            Serial.print(',');
            Serial.println(val / baselineCounter, 4);
        }
      
        baselineCounter = 0;
    }
  
    // turn off exc LED
    if (timeCounter >= excLedOff) {
        digitalWriteFast(EXC_LED, HIGH);
    }
  
    // report log message if triggered
    if (msgNum != 0) {
        Serial.print(msgTime);
        Serial.print(",");
        Serial.println(msgNum);
        msgNum = 0;
    }
}

void incrementTimeCounter() {
    timestamp++;
  
    if (timeCounter != totalTime) {
        timeCounter++;
    } else {
        timeCounter = 0;
    }
}

int measure(int pin, int avgx) {
    if (avgx == 1 || avgx == -1) {
        return analogRead(pin);
    }
  
    float val = 0;
  
    for (int i = 0; i < avgx; i++) {
        val += analogRead(pin);
    }
  
    return (val / avgx);
}

void startRecording() {
    recording = true;
    timestamp = 0;
    timestampSet = true;
}

void stopRecording() {
    recording = false;
}

void optoStim() {
    bool state = digitalRead(OPTO_STIM);
    digitalWriteFast(OPTO_LED, !state);
    Serial.print("opto,"); Serial.println(state);
}

void logMsg1() {
    // Serial.println("msg1");
    msgNum = 1;
    msgTime = timestamp;
}

void logMsg2() {
    // Serial.println("msg2");
    msgNum = 2;
    msgTime = timestamp;
}

void logMsg3() {
    // Serial.println("msg3");
    msgNum = 3;
    msgTime = timestamp;
}

void logMsg4() {
    // Serial.println("msg4");
    msgNum = 4;
    msgTime = timestamp;
}

void logMsg5() {
    // Serial.println("msg5");
    msgNum = 5;
    msgTime = timestamp;
}

// void logMsg6() {
//     // Serial.println("msg6");
//     msgNum = 6;
//     msgTime = millis() - recordingStartTime;
// }

config.hpp:

C++:
#pragma once

// read by photoOpto.py - all consts should be in capital letters

const bool SAVE_TO_SD = false;
const bool RECORD_ON_POWERUP = true;

// milliseconds
const int TIME_PERIOD = 6;

// microseconds
const int MEASURE     = 550;
const int LED_ON      = 900;
const int ISO_EXC_GAP = 10;
const int BIT_DEPTH       = 12;  // recommended: 12, Teensy 4.1 max: 12, -1 for default
const int BUILTIN_AVG     = 32;  // recommended: 16, choose between 4(20us), 8(40us), 16(80us) or 32(160us), -1 to disable
const int NOT_BUILTIN_AVG = 2;   // recommended: 2, 20 us per sample, multiply with BUILTIN_AVG duration for total sample duration

// pin declarations
const int MEASURE_PIN = 27;
const int ISO_LED     = 8;
const int EXC_LED     = 24;
const int OPTO_LED    = 30;

const int START_RECORDING = 33;
const int STOP_RECORDING  = 34;
const int OPTO_STIM       = 35;
const int LOG_MSG_1       = 36;
const int LOG_MSG_2       = 37;
const int LOG_MSG_3       = 38;
const int LOG_MSG_4       = 39;
const int LOG_MSG_5       = 40;
const int LOG_MSG_6       = 41;

const int SERIAL_BAUD = 1000000;
const int MAX_MEASUREMENTS = 1000;
 
Last edited:
So in your case pinMode(MEASURE_PIN, INPUT_DISABLE);
I put this line in the setup function and the behaviour did change, but now it looks like this:

1701718401731.png


Neither of the 2 types of reported values are centered around Zero now.
Ran the code twice just to make sure, and both times it looks like this.

This is how it looks if baseline subtraction is commented out and the pin-mode set to INPUT_DISABLE:
ie, replacing
val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG) - baselines[i];
with
val = val + measure(MEASURE_PIN, NOT_BUILTIN_AVG);

1701718806917.png
 
Last edited:
The code in p#2 only went to 0 when input was on GND, and would go full scale when on 3.3V as expected.

Floating in between - a small amount ~33 and then rising when touched to something over double that. That values showing now are above midrange - perhaps with the INPUT_DISABLE change allowing it to float.

What is the input and expected result?

For the p#2 code if the interval of a second was made the baseline and then that was subtracted from the received values for the next second that would tend to zero when nothing influenced the input to the pin.

Once second is a long time but indeed it works to tend to 0 and then adapt (second by second) to new inputs:
Code:
void setup() {
  pinMode(27, INPUT_DISABLE);
  analogReadResolution(12);
  analogReadAveraging(32);
}

elapsedMillis aTime;
void loop() {
  static int aSum = 0;
  static int aCnt = 0;
  static int aLast = 0;
  if (aTime > 1000) {
    if (aCnt) Serial.println( aSum / aCnt - aLast);
    aLast = aSum / aCnt;
    aSum = 0;
    aCnt = 0;
    aTime -= 1000;
  }
  aCnt++;
  aSum += analogRead(27);
}

Here is the output when the wire is carelessly moved between 3.3V and GND:
Code:
0
0
-164
-193
329
28
0
414
708
0
0
0
0
0
-657
-464
-1
-2325
591
-1212
-24
0
0
946
1883
173
671
419
0
0
0
-794
-388
-2910
1029
1527
-731
-565
-1260
180
22
-180
-22
0
30
-30
0
0
836
1297
-2133
0
0
 
Back
Top