Open
Description
Description
The Arduino core has a digitalWrite() and a digitalRead() function.
In many sketches there is a need to toggle a pin, either for a blinking LED or a clock pin for swSPI etc.
There are two typical ways to invert a pin - see code below
// using a state holding the value of the pin
digitalWrite(pin, state);
state = 1 - state;
// read pin, invert and write back
digitalWrite(pin, !digitalRead(pin));
The latter one is slower as it redo a lot of "pin math", so I implemented a version of digitalToggle() for AVR.
Attached test sketch shows the gain compared to the two methods.
Results test on UNO
Time us 1000 calls
Reference: 7392 // read invert write
Var: 4784 // use state var
Toggle: 3964 // use digitalToggle returning state
Toggle: 3520 // use digitalToggle returning NO state
The gain of toggle returning state is 46% resp 16%
The gain of toggle returning NO state is 52% resp 26%
Imho these gains are interesting, esp for clocking data
Implementation for AVR
Arduino.h
uint8_t digitalToggle(uint8_t pin); // returns the new state of the pin
wiring_digital.c
uint8_t digitalToggle(uint8_t pin)
{
uint8_t port = digitalPinToPort(pin);
volatile uint8_t *out;
if (port == NOT_A_PIN) return 0;
uint8_t timer = digitalPinToTimer(pin);
if (timer != NOT_ON_TIMER) turnOffPWM(timer);
uint8_t bit = digitalPinToBitMask(pin);
out = portOutputRegister(port);
uint8_t oldSREG = SREG;
cli();
*out ^= bit; // invert bit
SREG = oldSREG;
return ((*out & bit) != 0);
}
Note: a no state returning version is straightforward given the above code.
Test sketch
uint32_t start, Tref, Tref2, Tnew;
const uint8_t pin = 13;
uint8_t state = LOW;
void setup()
{
Serial.begin(115200);
Serial.println();
Serial.println(__FILE__);
pinMode(pin, OUTPUT);
digitalWrite(pin, LOW);
start = micros();
for (int i = 0; i < 1000; i++) digitalWrite(pin, !digitalRead(pin));
Tref = micros() - start;
start = micros();
for (int i = 0; i < 1000; i++)
{
digitalWrite(pin, state);
state = 1 - state;
}
Tref2 = micros() - start;
start = micros();
for (int i = 0; i < 1000; i++) digitalToggle(pin);
Tnew = micros() - start;
Serial.print("Reference:\t");
Serial.println(Tref);
Serial.print(" Var:\t");
Serial.println(Tref2);
Serial.print(" Toggle:\t");
Serial.println(Tnew);
Serial.print(" Gain:\t");
Serial.println(Tref - Tnew);
Serial.print(" Perc:\t");
Serial.println(100.0 - (100.0 * Tnew) / Tref, 1);
pinMode(13, OUTPUT);
}
void loop()
{
static int cnt = 0;
if (cnt == 60)
{
cnt = 0;
Serial.println();
}
cnt++;
// digitalToggle(pin);
int x = digitalToggle(pin);
Serial.print(x);
delay(1000);
}
Activity
matthijskooijman commentedon Dec 5, 2020
Sounds like a good additoin. I think it belongs to the ArduinoCore-API repo, though, so I'm moving it there.
As for AVR, IIRC you can also toggle a pin by writing a 1 to the
PINx
register, which would be even (slightly) faster.RobTillaart commentedon Dec 5, 2020
Implementation for ESP32
For ESP32 platform I have not yet tested code.
No gain expected here, although there is one (1 << pin) and one if () less on average
esp32-hal-gpio.h
esp32-hal-gpio.c
edgar-bonet commentedon Dec 5, 2020
Nice idea. Here is a small optimization opportunity:
The compiler should issue a “load” instruction for
*out
, in order to read the port register, although its contents is known, as it has just been written. You can save this memory access by explicitly caching that contents:I would expect this to save two CPU cycles, although I did not test. The compiler cannot perform this optimization itself because the
volatile
keyword explicitly forbids it.Perehama commentedon Mar 31, 2022
That was my first contribution here on github, as I just had the same idea others have had. I came here from the Arduino forums. My only contribution is to make it a void return so it operates otherwise like digitalWrite(). There is an AVR specific version that would write a 1 to the portInputRegister(port) as per Atmel, writing a 1 to PINx will toggle that pin, but the XOR operation is portable to other microcontrollers.
jacoblgit commentedon Mar 2, 2025
Implementation for SAMD
SAMD has a PORT register OUTTGL for single instruction toggling of an I/O pin, enabling significant performance gain over the read/modify/write approach.
Common.h
wiring_digital.c
RobTillaart commentedon Mar 3, 2025
@jacoblgit
Recently put some "missing" functions into a OUTPIN class, only the AVR optimized so far.
Besides the digitalToggle() it supports pulseHigh(), pulseLow() for short pulses and pulseOut() for longer pulses.
PulseOut was implemented to complement the existing PulseIn().
From there it was a small step to the dedicated pulseHigh() and pulseLow() finctions.
It might be an idea to extend that class with SAMD optimized versions.