pinMode() in class constructor? seem not to work

Hi folks, I have a problem regarding statements in class constructor. Before I raise my question, please allow me to go through my little trouble.

I was working on a TCS3200(color sensor) module, and I was writing a class for it. The module requires me to set up a few output digital pins, so in the class constructor, I read in pin numbers and set their direction to output using pinMode. It's something like this:

//constructor, get pin number for s0-3, and LED
  TCS3200(int s0,int s1,int s2,int s3,int led){
   //assign pin number
   s_pin[0]=s0;
   s_pin[1]=s1;
   s_pin[2]=s2;
   s_pin[3]=s3;
   led_pin=led;
   //data_pin=data;
   
   for(int i=0;i<3;i++){
     pinMode(s_pin[i],OUTPUT);
   }
   pinMode(led_pin,OUTPUT);
   // update pin state(setting their state)
   updatePins();
   return;
  }

In my program, I declared an instance of TCS3200 class as global variable(at least I intend to do so) with following code

//TCS3200 class definition
//code...

TCS3200 sensor(4,5,6,7,3);

void setup(){
//code...
}
void loop(){
//mode code...
}

I ran the program and there were error in its outputting data, and I successfully fixed it after hours of desperate attempt by setting pin direction(in/out) in setup code, like this:

void setup(){
pinMode(2,INPUT);
pinMode(3,OUTPUT);
pinMode(4,OUTPUT);
//mode pinMode()...
}

and the problem seem to disappear, and in reflection I realize the error earlier was caused by failure to set the output pins to correct state, because their directions were not set to OUTPUT. So it seems that the pinMode in my class constructor did not execute properly.

So here's my problem:

  1. when is constructor executed? before, or after setup or loop?
  2. why the pinMode() in constructor failed to execute in my example?
  3. what's the right thing to do? i.e. where to put the pinMode() command?

Thank you all in advance.

The general advice is to have an init or begin function that is called from setup() after the object has been created. This allows time for the hardware to settle down to a stable state before being used.

This statement of yours implies your constructor function has 5 arguments:

TCS3200 sensor(4,5,6,7,3);

This implies it has 4 arguments:

TCS3200(int s0,int s1,int s2,int s3,int led)

This implies only the first 3 arguments will be processed:

for(int i=0;i<3;i++){

edit:

on second reading, it looks like your only problem is that you are not handling the 4th argument because of the stopping condition in your loop which should be: for(int i=0;i<4;i++)

Hi

Look at the hiden main() function in arduino cores.
There is an hiden init() function executed before setup(), for hardware purpose (don't know exactly what it does).

and the constructor is executed before main()

A secure constructor shouldn't use hardware.
This should be in a begin() method called from setup().

BUT i also use pinMode() in constructors for leds (ouput) or pushbuttons (input) without any problem...

6v6gt:
This implies it has 4 arguments:

TCS3200(int s0,int s1,int s2,int s3,int led)

Methinks you need to count again, a bit more carefully....

Regards,
Ray L.

RayLivingston:
Methinks you need to count again, a bit more carefully....

Regards,
Ray L.

Yes. You are right. He changed his system for labeling the pins and I did not notice that on a first quick glance. You may notice, though, that my correcting edit has an earlier timestamp than your post.

Set pinMode is a runtime thing. AVR pins start INPUT LOW by default.

bricoleau:
BUT i also use pinMode() in constructors for leds (ouput) or pushbuttons (input) without any problem...

I haven't, please an example sketch of how?

6v6gt:
This statement of yours implies your constructor function has 5 arguments:

TCS3200 sensor(4,5,6,7,3);

This implies it has 4 arguments:

TCS3200(int s0,int s1,int s2,int s3,int led)

This implies only the first 3 arguments will be processed:

for(int i=0;i<3;i++){

edit:

on second reading, it looks like your only problem is that you are not handling the 4th argument because of the stopping condition in your loop which should be: for(int i=0;i<4;i++)

That's exactly the problem, thank you so much for your help!
Guess I have to be more meticulous when programming~ :slight_smile:

This advice is a bit outdated. If you follow the official Arduino guide to create a library, you will end up with the constructor calling pinMode() before setup(), which is the recommended solution from Arduino in the learning guide:

Example in the Arduino guide:

Morse::Morse(int pin)
{
  pinMode(pin, OUTPUT);
  _pin = pin;
}
#include <Morse.h>

Morse morse(13);

void setup()
{
}

void loop()
{
  morse.dot(); morse.dot(); morse.dot();
  morse.dash(); morse.dash(); morse.dash();
  morse.dot(); morse.dot(); morse.dot();
  delay(3000);
}

Nope. Still good advice.

This would not be the first time nor is it unlikely to be the last time that an official anything included a mistake.

The word "recommend" is not used once on that page. Nor is it implied. "...what should happen when someone creates an instance of your class..." is the phrase of interest. In the author's opinion what should happen is the pin should be initialized. Touching any hardware, including calling pinMode, before init is called is a mistake. The author's opinion is wrong; they made a mistake.

You assume pinMode will always be safe in a constructor. What happens when your library comes in contact with a processor that requires the I/O subsystem to be first powered up? Is pinMode likely to work? Who cares. The correct choice is precisely what's described above ... provide a begin method with the expectation that it will be called in setup. That's the contract. Abide by the contract and your library will be much more likely to work with current Arduinos and future Arduinos.

I suspect a pull request correcting the mistake would be welcome.

That's the contract

Can you provide a reference?

I'm not disagreeing, but I'm also not the only person who uses that how-to guide to build Arduino libraries. It would be good to know if there is better guide for building classes for Arduino. Most libraries I use were built the way the tutorial outlines.

I understand the concern because I see the main() function Arudino uses has init() at the top, but the approach seems non-intuitive. The beauty of Arduino development is the abstraction that handles the hardware. I'm wondering, instead of changing all of the libraries to accommodate, could we instead address how Arduino supports global declarations that initialize hardware? Calls made in a constructor that invoke Arduino functions or make hardware calls like pinMode() that need the main init() could trigger init() themselves. That support would make it a much cleaner developer experience which seems to be more in the spirit of the Arduino project.

The setup() is called once, when the sketch starts. It's a good place to do setup tasks like setting pin modes or initializing libraries.

There are copious places where that is affirmed. Enough to justify my claim that it's a contract.

Bear in mind that tutorial is older than this thread and has not been subjected to peer review (until now). It needs to be updated.

There is. It has a high price.

That would be a breaking change. The overhead of checking for initialization would interfere with existing code that requires low(-ish) latency.

Serial.begin
Wire.begin
SPI.begin
Notecard.begin
LiquidCrystal_I2C .begin
WiFi.begin
The MIDI Device has a begin.
et cetera

The spirit of Arduino is to include a begin meant to be called in setup.

First of all, @Coding_Badly , thank you! I'm extremely grateful that you took the time to engage and help. :pray:

In my defense, the Library Guide is dated "LAST REVISION: 02/07/2023, 03:21 AM", and as I said, I'm not alone in referencing the tutorial to build my libraries. It sounds like others like me are still getting tripped up by this.

The beauty of the Tutorial is that it is simple to grasp. I would never look at Serial, Wire or SPI as a reference, as they are way too complex. More importantly, when you look at those and other ArduinoCore examples, they don't seem to do the right thing either. I'm sure I'm wrong but I am confused when I look at the source. I suspect I'm reading these wrong, but I saw more more cause for confusion. The "spirit" seems a bit chaotic good :joy: :

  • SoftwareSerial() runs pinMode() in the constructor (here)
  • Serial.begin() doesn't set pinMode() (here)
  • SPI.begin() sets pinMode() (here) - at least providing one good example

It seems that for my libraries (e.g. TM1637TinyDisplay) the pinMode() calls in the constructor may not even be needed (INPUT default?). Most of my projects target ATTiny (and other AVRs) or ESP so I've haven't discovered any MCU that needs init() in main() to run first for my lib to work. However it is probably already out there or will be someday.

I plan to update my libraries to honor the begin() approach (hopefully without making any breaking changes), I still hope we can get an official decision/consensus, reference and guide for the Arduino community on the right approach. It is confusing otherwise.

2 Likes

@jasonacox has submitted a correction to the tutorial:

Thanks!


There is some formal documentation of the begin function convention here:

https://docs.arduino.cc/learn/contributions/arduino-library-style-guide#:~:text=as%20the%20basis.-,Use,to%20initialize%20a%20library%20instance,-%2C%20usually%20with%20some


I'll share a minimal demo of why pinMode should not be called from the constructor:

const byte LEDPin = 2;

class Foo {
public:
  Foo();
};

Foo::Foo() {
  pinMode(LEDPin, OUTPUT);
}

Foo foo();

void setup() {}

void loop() {
  digitalWrite(LEDPin, HIGH);
  delay(1000);
  digitalWrite(LEDPin, LOW);
  delay(1000);
}

If you connect an LED to pin 2 and upload that sketch, you will likely see one of the following things:

  • The LED blinking very dimly.
    • This is because when the pin is in INPUT mode, digitalWrite activates/deactivates the internal pullup resistor, which allows a small amount of current to flow through the LED.
  • The LED doesn't blink at all.

:exclamation: I recommend against using the built-in LED on boards (e.g., Uno, Mega) that have a "buffer" circuit for that LED as the circuit complicates things when compared to driving an LED directly from a pin.

Now upload this sketch:

const byte LEDPin = 2;

class Foo {
public:
  Foo();
};

Foo::Foo() {}

Foo foo();

void setup() {
  pinMode(LEDPin, OUTPUT);
}

void loop() {
  digitalWrite(LEDPin, HIGH);
  delay(1000);
  digitalWrite(LEDPin, LOW);
  delay(1000);
}

You will now see the LED blinking brightly as expected.


I found that 100% of the boards I tried the experiment on misbehaved when running the first sketch:

  • Uno
  • Mega
  • Nano Every
  • MKR Zero
  • ESP8266
  • ESP32
3 Likes

Thanks @ptillisch ! Great practical example. I wonder if we could add a bit more to the style guide to help with this as well, but getting the tutorial updated is GOLD! As I mentioned, it was the training and template I used to start creating my own libraries, and to be fair, the reason I loaded my constructors with hardware calls.

Thanks for accepting my PR. I hope this helps provide more clarity. :pray: