Using digitalWrite(x, !digitalRead(x)) to toggle a pin in OUTPUT mode

I lost some points on an exam because according to my professor using this method to toggle a pin isn't reliable when using the Arduino Mega 2560 (the microcontroller board we're using in our class), and they said this was officially documented. I can't find this documentation anywhere.

From my understanding when you read the value of a pin set to OUTPUT mode using digitalRead(), it will return the last value you wrote to the pin. Using this method to toggle also worked as intended on my own board.

It's a moot point, since your method

  • uses the incorrect data types for pin states, thus may not work after future updates to the IDE
  • relies on a hardware feature that isn't portable
  • is needlessly obfuscated

Edit - I acknowledge the remark that follows this, but the last two points should stand.

1 Like

Welcome to the forum

No doubt they could give you a link to where this is documented

There was an ill advised change to the values used in digitalRead() a while ago that would have broken such code but it does not seem to have been implemented after all

3 Likes

You have the source code and you can download a datasheet, if it is important enough for you to regain the mark(s).
Or you could simply use avr-objdump to see what the generated code looks like.

Much better than "some bloke on the Web told me"

1 Like

It doesn't seem reliable to me. If there is enough time between the write and read it's going to read the actual-true physical "read" state which might be high or low or floating/unknown depending on what's connected.

In any case, your software should know (or be able to know) the current output state since it's determined by the software. :wink:

Nah, it's held in an internal latch.

3 Likes

Some API's and some MCU's have an I/O toggle function. Saves a lot of overhead. :slight_smile:

I dislike that the Arduino documentation doesn't document the types. HIGH and LOW are typeless defines, and they get cast into the undocumented type of digitalRead() and the undocumented type of the argument of digitalWrite.

Or in terms of documentation, Arduino uses the !digitalRead() !HIGH and !LOW in their built in documentation examples:

        ledState = !ledState;
      }
    }
  }

  // set the LED:
  digitalWrite(ledPin, ledState);

Yeah, eh? Not much incentive to play along with that program.

While digitalRead() will return the last value HIGH or LOW that was written using digitalWrite(), your code implementation went one step further in assuming that the value of HIGH/LOW were either 1/0 or true/false and that the value could be inverted and sent back to digitalWrite() to invert the current state.

There is no such guarantee in the API documentation.
The documentation for digitalRead() and digitalWrite() only state that HIGH and LOW are used and is totally silent on what those values are or even what type they they are.
Therefore, in order to be compliant with the documentation you can make no assumptions about what the values of HIGH and LOW might be and must always use the symbols HIGH and LOW.

And if you look at the code to see how it is implemented in a particular core and happen to see that HIGH is defined to be 1 and LOW is defined to be 0 you still can't use that information as it is abusing the API since you are using information about the API implementation that was obtained outside the API definition documenation and not guaranteed to be true across all implementations that conform to the API definition in the documentation.

i.e. HIGH cold be 2 and LOW could be 3 and still be conformant to the API documentation.
or HIGH and LOW could be types that cannot be inverted using a !

So yes you should lose points for writing code that is not conforming to the API definition in the documentation.

--- bill

2 Likes

How many points should Arduino lose for this line of built-in example code?

thirdSensor = map(digitalRead(2), 0, 1, 0, 255);

Documented here:

They could have at least done:

thirdSensor = map(digitalRead(2), LOW, HIGH, 0, 255);

or maybe just:

thirdSensor = 255 * digitalRead(2);

ETA: It is also in your IDE at:

File/Examples/Communication/SerialCallResponse

IMO, All 100 for a failing grade.
Not only is the code non compliant to the digitalRead() API but it is costly.
Shame on Arduino for including an example that is abusing the API and is non compliant to the API that they themselves created.

Also, none of those alternate proposal are compliant to the API and so they are also not assured to work.
For example, HIGH and LOW not even assured to be integers by the API documentation.

A proper way would be:


thirdSensor = (digitalRead(2) == HIGH) ? 255 : 0;

Not only is this compliant to the API but it would be smaller and faster code than the other non compliant examples as well.

And for the OP, this would be a compliant way to to write the code:

digitalWrite(x, (digitalRead(x) == HIGH) ? LOW : HIGH));

--- bill

2 Likes

Hello mttr

Take a view into Arduino.h file and you will find the following definition of datatypes used.

void digitalWrite(uint8_t pin, uint8_t val);
int digitalRead(uint8_t pin);

and

#define HIGH 0x1
#define LOW  0x0

The unary prefix ! operator computes logical negation of its operand and isn´t "suitable" for the datatype uint8_t.

The correct coding as already mentioned:

digitalWrite(x, digitalRead(x)==HIGH?LOW:HIGH) ;

to toggle a pin in OUTPUT mode.

HTH

Have a nice day and enjoy coding in C++.

1 Like

You are relying on the return value of digitalRead being safely castable to a bool by the compiler. Something not guaranteed by the documentation. But a small correction gets past the mistake...

digitalRead(x) == HIGH ? LOW : HIGH

3 Likes

Thanks for your note.
Already corrected.
I wrote this before my first coffee in the morning. :coffee:

1 Like

The documentation of the API is a maze of twisty little passages, but it's there:

Likewise, the port registers PORTB and PORTD contain one bit for the most recently written value to each digital pin, HIGH (1) or LOW (0).

Calling digitalWrite(10,HIGH); followed by digitalWrite(11,HIGH); will cause pin 10 to go HIGH several microseconds before pin 11, which may confuse certain time-sensitive external digital circuits you have hooked up.

Arduino Playground - BitMath from ~ - Arduino Reference from Arduino Reference - Arduino Reference

I agree that the practice is sloppy and not guaranteed to be portable, but the Arduino published documentation itself is sloppy and does have HIGH==true==1 and LOW==false==0 equivalences in it.

1 Like

Not strictly true. digitalRead() reads the PINx register, while the last value your wrote would be in the PORTx register. They'd normally be the same, but not under all circumstances.
(For instance, if you put the pin in internal_pullup mode, by writing HIGH with digitalWrite() while the port is in INPUT mode, then it would be expected that you could read either LOW or HIGH.

The whole "HIGH may or not be 1 and may or not be !LOW" controversy is separate. :frowning:

2 Likes

Can you provide a link to API documentation from Arduino.cc that states that HIGH==true==1 and LOW==false=0

The API documentation that I've seen for digitalRead() and digitalWrite() do not ever mention a type or value for HIGH and LOW

--- bill

Stop right there.
Arduino.h and the code in wiring_digital.c is the code for the implementation.
It is not API documentation of how the API is defined.

Code that uses an API should stick to using the API the way the API documentation defines it.
If you peek at the code and take advantage of the implementation, you are abusing the API by using or taking advantage of things that the API has not defined so it is not guaranteed to be the same or work across other implementations.

--- bill

Thank you very much for this advice.

Please take the time to write a suggestion for improvement of the Arduino documentation.

1 Like