[solved] Analog pin configuration outside of setup()

I haven’t found any documentation that mentions it, but is there an issue with calling pinMode() outside of setup()? I’m having some weird issues because of the way I re-initialize the device based on external configuration changes, which are pulled by the firmware during normal operation.

Basically if pinMode( A2, INPUT ) gets called more than once, the readings start fluctuating and are not correct anymore. I’ve also noticed that calling getPinMode( A2 ) often returns AN_INPUT. Is that normal?

I’m able to reproduce this with the following test firmware:

int counter = 0;

void setup()
{      
    pinMode( A2, INPUT );
}

void loop()
{
    if ( ++counter % 10 == 0 )
    {
        Serial.println( "getPinMode( A2 ): " + String( getPinMode( A2 ) ) );
        Serial.println( "pinMode( A2, INPUT );" );

        pinMode( A2, INPUT );
    }
    
    Serial.println( analogRead( A2 ) );
    
    delay( 500 );
}

There is no problem in using pinMode() outside setup (multiple libraries rely on that).
But you need to know that analogRead() does alter the hardware pin mode in order to do the analog-digital conversion, hence the AN_INPUT.

Cool, that’s good to know, thanks. I’m still not sure why redundant calls to pinMode() seems to be impacting the readings though. Is there any activity on the wire when it’s called that could mess with the attached sensor?

It might be a timing issue.
Try a delay after pinMode().

Yes, INPUT and AN_INPUT do cause slightly different electrical characteristics on the pin (input resistance and capacitance) and the timers/interrupts are “attached” during analogRead().

But to judge if this would upset your sensor you’d need to provide more detail about the sensor and your wiring.

It’s an MH-Z14 CO2 sensor, and it outputs 0 - 2.5V, which we’re reading on A2.

http://www.thaieasyelec.com/downloads/ESEN185/MH-Z14_CO2.pdf

Would subsequent analogRead() calls be affected by the timing issue you’re proposing? We read the pin every second ongoing after calling pinMode(), and all reads seem to be impacted equally.

Once per sec should not be a problem and definetly not when not permanently changing pinMode()

I’ve got a 4-wire touch library that changes between OUTPUT and AN_INPUT every few milliseconds, so I’d guess there’s something else wrong.

How well filtered is your power source?

BTW: I was looking for an effordable good CO2 sensor a while back and saw these too, but they were quite pricy. Where did you get that one from and what did it cost?

Hey ScruffR,
I am working with polystyrene on the board.

To answer your question, the power rails have plenty of filtering on them, and all integrated circuits on the board also have their corresponding decoupling capacitors. The power rails are a wide 60 mil trace for both the CO2 sensor and the photon. All of our analog signals going to the Photon also have a 0.01uF filtering capacitor which seems to work well with the Photon’s internal ADC.

We were initially getting the CO2 sensors from Sainsmart, and have now been getting them directly from China for about 40-50 bucks. They both seem to be the same part #, so it seems Sainsmart is just reselling them? They seem to be a decent balance between performance/reliability/cost.

@Mahonroy and I just tested the voltage from the sensor on the board, and it never drops. analogRead() goes from 0.45V to 0.1V, but the volt meter never changes.

However, we switched the firmware to set the pin to AN_INPUT explicitly, instead of INPUT, and the issue went away entirely. That’s never mentioned as an option in the documentation, but is it recommended anywhere to do that instead if you’re doing analog reads?

@polystyrene, @Mahonroy, thanks for the update and good to here you got your problem handled.
The caps would have been the next thing to suggest, but you obviously know what you’re doing on the HW front :wink:

But the thing that pinMode() kept giving you headaches and setting AN_INPUT explicitly got rod of that might be of interest to Particle (@BDub - any thoughts about the background?)

Just to clarify: Do you see good readings with pinMode() only set in setup() or are the readings just as bad then?

Readings were accurate from startup, up until a second call to pinMode(). This did happen to be during setup(), but I’m not positive setup() vs loop() was at issue.

What I noticed in my test firmware was that to start I’d set pinMode() to INPUT, and readings were accurate. Immediately prior to a second call to pinMode(), getPinMode() returned AN_INPUT instead of INPUT. After that second call to pinMode(), readings were inaccurate, but getPinMode() was now returning INPUT, even after calls to analogRead().

I don’t know much of anything about the internals of the system, but it seemed to me like calling pinMode() with INPUT, after the background process had set it to AN_INPUT due to using analogRead(), messed things up.

Thanks for the clarification and your reasoning seems sound - maybe it’s something for @mdma rather than Brett after all.

Yeah, I’d be interested to hear what those guys think too. Fortunately, I feel pretty comfortable with using AN_INPUT explicitly for now. Thanks for all the help troubleshooting.

1 Like

analogRead() does not actually require you to set the pinMode() to INPUT or even AN_INPUT explicitly. It will be set the first time you call analogRead(). If you set it as an INPUT that means you want it to be a digital input, which it will happily do as well. Now every time you call analogRead(), it determines that it is in digital INPUT mode and has to switch to AN_INPUT, take a reading, then switch back to INPUT so you can do a digitalRead() on the same pin again elsewhere in your code. This is simply a background convince multi-use of the pin, but as you can see that's causing some inaccuracy, and confusion.

Quick solution is to not set the pinMode() at all when using analogRead().

We probably need to add what I just wrote to the Docs as well :wink:

Longer term solution might be to remove this multi-use convenience so that when pinMode() is called explicitly, that's the only way in which the pin will be used. However I'm not sure what percentage of users would miss this multi-use functionality... I would guess not many though.

1 Like

Docs updated: https://docs.particle.io/reference/firmware/photon/#analogread-adc-

@BDub, since I wasn’t even aware of the switching back after analogRead() I wouldn’t miss it if it was gone, but as it presently exist, would this also be the case for any other pin modes?
How could this be stopped if it was once activated? There seems to be no way to unset pinMode().

Just to make things more complicated, users would naturally see analogRead() and analogWrite() related the same way as digitalRead() and digitalWrite(), but analogRead() obviously only works perfect without pinMode() and analogWrite() on the other hand only works with prior setting pinMode(pin, OUTPUT).

Notes in the docs are fine, but it’ll still confuse people.

I’ve often thought of just making the function call take over the pin and force it to be what it needs to be, but there also is a certain sanity to setting a pin’s mode first before operating on it. Most of the requirements for pinMode() and digital/analog/read/write come from Arduino/Wiring, so by deviating from that it will make porting code trickier.

There is one more case of saving the pin type, it might be the opposite way around. The other thing you can do is use a previously set OUTPUT as digitalRead() which I use all of the time.

setup() { pinMode(D7, OUTPUT); }
loop() { digitalWrite(D7, !digitalRead(D7)); delay(500); }

I do believe in making things intuitive, but also think it can’t be so intuitive that you won’t need any documentation (at least not if you want the system to be rich with features). The Docs should explain things precisely so you know how to use them. If there are missing details from the Docs, they definitely need to be added. READING the Docs is an entirely different problem to solve all together :wink:

1 Like

When I have the chance, I’ll try removing pinMode() altogether for that pin. The weird thing is getPinMode() returned AN_INPUT even 500ms after the last analogRead(), but only between the first and second calls to pinMode(). After the second call, it always returned INPUT.

Thanks for all the info and or updating the docs, @BDub.

I just looked at the code again:

The first time analogRead() is called for a particular pin, the ADC is initialized, it will save the current pinMode() and set the new pinMode() to AN_INPUT, take 10 samples and average them, and leave the pinMode() set to AN_INPUT. Every time it's called successively it will not attempt to set the pinMode() to AN_INPUT so it is imperative not to change the pinMode() after calling analogRead() for a particular pin.

If you called digitalRead() on this pin, because you never set the pinMode() explicitly to INPUT, digitalRead() will just return 0;

If you set pinMode() to INPUT, analogRead() will do it's same sequence but now it saves the current pinMode() as INPUT. When digitalRead() is called, it knows that you originally set the pinMode() as INPUT so it changes the pinMode() from AN_INPUT to INPUT and returns the value of the pin's input register. If you called analogRead() again on the same pin, it will not switch it back to AN_INPUT.

If you set pinMode() to OUTPUT, analogRead() will do it's same sequence but now it's saving the current pinMode() as OUTPUT. When digitalRead() is called, it knows that you originally set the pinMode() as OUTPUT so it changes the pinMode() from AN_INPUT to OUTPUT and returns the value of the pin's output register. If you called analogRead() again on the same pin, it will not switch it back to AN_INPUT.

So it's only attempts to set the analog pin back to AN_INPUT mode the first time you call analogRead for a given pin.

If you explicitly set a pin to INPUT or OUTPUT after that first analogRead() call, it will not attempt to switch it back to AN_INPUT.

This sounds like exactly what you are experiencing.

Best advice I can give is don't use pinMode() with analogRead(). EDIT: just pushed a change to the docs about this.

But then I’d see a “clean” implementation of analogRead() not checking if it was already set once and rely on it being unchanged but check for the current mode and if needed reactivate AN_INPUT if required.

like

    if (adcChannelConfigured != PIN_MAP[pin].adc_channel || PIN_MAP[pin].pin_mode != AN_INPUT)
    {
        HAL_GPIO_Save_Pin_Mode(PIN_MAP[pin].pin_mode);
        HAL_Pin_Mode(pin, AN_INPUT);
    }

And/or add and document the possibility to “repair” the pin by using pinMode(pin, AN_INPUT); after it got “damaged” by a previous other pinMode() call.

1 Like

That seems like a reasonable approach, but I’d want to make sure if users accidentally set the analog pin to an INPUT/OUTPUT constantly right before the analogRead() is called, it did not alter the readings.

1 Like