Constrain of analogRead leads to underflow

Hi guys.
Pretty sure I am doing something very wrong, but I think I need your help putting the finger on what, exactly.
So I have this sketch that reads an analog value from a SAMD21 (Seeed XIAO) pin. After reading it, I want to constrain it to a range smaller than 10bit and subtract the difference to have a number with a minimum value of 0.
I have encountered the following strange behaviour:

The snippet below created (seemingly) random underflows with analogRead values between 0 and 7, where fader_1 will be 65535 (always that, never a different value),

  uint16_t fader = constrain(analogRead(potPin), 4, 1023) - 4;

while this works as expected, ie, no underflows:

  uint16_t f = analogRead(potPin);
  uint16_t fader = constrain(f, 4, 1023) - 4;

I tested the cause by using print statements like this:

  Serial.println("//");
  Serial.println(analogRead(potPin));
  uint16_t fader = constrain(analogRead(potPin), 4, 1023) - 4;
  Serial.println(fader);
  Serial.println("\\\\");

and got value pairs like 6, 0 and then 6, 65535
or 6, 1 followed by 7, 65535.
No pattern visible in the pairs leading up to the underflow.
How does that even happen? If analogRead is 6, and I constrain it and subtract 4, why does that equal 1 or even 0?

I am being dumb, but I don't know HOW I'm being dumb, which is just the worst.
I suspect it has something to do with typecasting, but honestly, I'm at a loss.

If someone could explain to me exactly what is causing this behaviour, I think that would deepen my understanding of programming.

Thank you very much in advance.

Doesn't happen here. Check your wiring.

Sry, it must be something about the SAMD21.

a7

I was thinking it was some strange issue too..
but then I read the Arduino reference..
Most specifically, the Notes and Warnings section..

good luck.. ~q

1 Like

wow, RTFM I guess. Thank you for doing it for me, sorry for the inconvenience!

For anyone as stupid as me, this is how constrain() is implemented:

#define constrain(amt,low,high) ((amt)<(low)?(low):((amt)>(high)?(high):(amt)))

A #define means the compiler expands the left term to the right term before compilation, if I remember correctly.

Which in this case means

((analogRead(5))<(4)?(4):((analogRead(5))>(1023)?(1023):(analogRead(5))))

as the compiler replaces every occurence of amt in the right term with whatever you put there, which leads to multiple calls to amt if it is a function, ergo different values for amt at different points in the evaluation.

Feel free to correct me if I got anything wrong!

Again, thank you for your help and consideration, @qubits-us!

1 Like

You're welcome..
Vaguely remember doing the exact same thing..
Took a trip into the core found analogRead, saw it's returning a native int..
So then I went searching for constrain and found the docs first..
Will try to remember that one..
I didn't bother digging further..
The return type of int is probably why you're seeing the 65k, blowing over that word..
No worries, I help, I learn..

good luck.. ~q

1 Like

constrain() is a macro and doesn't have a return type, so here the type is set when the result is assigned to a unit16_t.

The third analog reading can escaped the constraint, and return something less than 4. Subtracting 4 from that gives a very large number in 16 bits.

If you make the 4 larger, you can see the constraint escape value differ, it depends on how fast the input is changing.

a7

1 Like

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.