Why does digitalWrite HIGH vs digitalWrite LOW take different amounts of time?

I just got an oscilloscope and just for fun, thought I’d put together a quick sketch to toggle an output as fast as the loop would run on my Uno.

#define JPIN  8

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

void loop() {
  static int i = 0;
  
  if(i++ & 1)
    digitalWrite(JPIN, LOW);
  else
    digitalWrite(JPIN, HIGH);
}

I found the HIGH time significantly longer than the LOW time. I thought maybe the processing of the “if” conditional might actually make some difference so I swapped HIGH/LOW and still the HIGH signal is about 3 times longer than the LOW signal averaging about 11.5us HIGH and 3.7us LOW with the above code regardless.

Is there something that’s inherently more difficult / time consuming to raise vs lower the signal? 3x timing difference is pretty significant. I haven’t looked into the low level programming on the UNO to see how the control registers are modified but there seems to be something wrong.

Oscilloscopes R Fun! :slight_smile:

Try:

void loop() {

digitalWrite( JPIN, !digitalRead(JPIN) );

}

Yours:
6b2c02895ef603afd54c8d52e8fed0be6e7c4536.png

6154a62d71089747f60ea41aae9fd17345b058fb.png

You must be doing something wrong, here is what I get:
2016-07-01_21-10-00.png

LarryD, is that using my sketch or your sketch and which Arduino?

Your sketch did make the HIGH and LOW signal times the same. Unfortunately, they are both now 11.7us. The goal really wasn't to make the HIGH and LOW signal times the same but determine why directly writing HIGH and LOW takes different amounts of time.

Adding the significant CPU cycle overhead of digitalRead-ing what I was tracking via the static integer wasn't what I had in mind but thanks for the cool snippet for future use.

Your sketch. UNO

It could be the cheapo UNO I have.

I grabbed another one that I was using elsewhere. Now, it is quite close to symmetrical: 7.4 to 7.6

Oops. Thanks for your help! :)

You might be interested in this thread:

http://forum.arduino.cc/index.php?topic=384253.msg2648795#msg2648795

The time between the falling edge of the first pulse, to the rising edge of the second pulse (-65ns), is the time spent in the millis() function.

I can't think of a faster way to turn pins on and off ... What does your scope meter show... Mine isn't the best to look at this? but it still looks quite interesting.

void setup() {
DDRD = DDRD | B11111100;// Pins 2-7 digital out
}

void loop() { 
// cycle the pins 2~7 on and off 4 times before looping
PORTD = B11111100;//Pins 2~7 0n
PORTD = B00000000;//Pins 2~7 0ff
PORTD = B11111100;//Pins 2~7 0n
PORTD = B00000000;//Pins 2~7 0ff
PORTD = B11111100;//Pins 2~7 0n
PORTD = B00000000;//Pins 2~7 0ff
PORTD = B11111100;//Pins 2~7 0n
PORTD = B00000000;//Pins 2~7 0ff
}

To be precise you would need to be Deactivating timer0 to prevent millis to run at the wrong time and also don't use the loop default behavior to switch because tons of invisible tests and actions happens before returning to the top of the loop. A while(true) inside the loop with 10000 PORTB pairs of commands as of the previous post will lead to no extra parasiting activities for quite many cycles enough for your scope to offer reasonable measure. I'll try on mine later today and share

So I ran your original code on my arduino UNO and it does not appear to be massively different between on and off

|500x306 (1 horizontal division is 4 micro second)

scope has hard time to sync because of jitter introduced by the looping. a full on / off cycle is about 12,8 micro seconds

Now if you use the low level controls on PORT, in a while loop, with on and off repeated thousands of time, you get this

|500x300 (1 horizontal division is 200 nano second)

in that case a full on off cycle is 280 nano seconds - This is almost 46 times faster and that gives you a sense for the cost of the abstraction code for digitalWirte....

this is the code, for upload size reason I removed almost all the on/off lines, just copy & past the 2 lines plenty of times instead of the comment in the middle of the loop

#define JPIN  8

void setup() {
  pinMode(JPIN, OUTPUT); // PORTB on UNO = digital pin 8 to 13
}

void loop() {
  while (true) { // avoid looping cost
    PORTB |= B00000001;  // pin 8 HIGH
    PORTB &= B11111110;  // pin 8 LOW
    PORTB |= B00000001;  // pin 8 HIGH
    PORTB &= B11111110;  // pin 8 LOW

    // ... REPEAT THIS PATTERN 10000 TIMES

    PORTB |= B00000001;  // pin 8 HIGH
    PORTB &= B11111110;  // pin 8 LOW
  }
}

I played with my new Salea clone and writing to PINx, hardcoded bursts or single pulses are possible with 8MHz.

|500x170

const byte pinCha0 = 10; // PB2
const byte pinCha1 = 9;  // PB1
const byte pinCha2 = 8;  // PB0

void setup() {
  pinMode(LED_BUILTIN, OUTPUT);
  pinMode(pinCha0, OUTPUT);
  pinMode(pinCha1, OUTPUT);
  pinMode(pinCha2, OUTPUT);
  Serial.begin(115200);
  Serial.println(F("Salea DigitalWrite 1.00.000"));
}

void loop() {
  PINB = 1;
  PINB = 1;

  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;
  PINB = 4;

  digitalWrite(pinCha0, HIGH);
  digitalWrite(pinCha0, LOW);
  digitalWrite(pinCha0, HIGH);
  digitalWrite(pinCha0, LOW);
}

Thanks sailogreg for your post. I knew there was some overhead each time we looped but I didn't realize how much it added up!

This is the main() function that calls the loop function:

Simplified version full code is found in main.cpp
int main(void)
{
    init();
    initVariant();
    setup();
    for (;;) {
        loop();
        if (serialEventRun) serialEventRun();
    }
    return 0;
}

calls code in HardwareSerial.cpp every time you release for looping

Now my question is how close does it get with out releasing our loop back to the main program

#define JPIN  8

void setup() {
  pinMode(JPIN, OUTPUT); // PORTB on UNO = digital pin 8 to 13
}

void loop() {
  for (;;) {
    PINB = 1;
    PINB = 4;
  }
}

How fast is this? My scope shows a frequency of near 2MHZ but I also see a gap that appears but I i am not able analyze this :(

I also compared it to using the loop() to cycle which is about 500kHz:

void loop() {
//  for (;;) {
    PINB = 1;
    PINB = 4;
//  }

This will remove the gap (timer0) and will toggle the output at 2MHz. If you repeat the toggle command, you'll get 4MHz until the loop iterates.

void setup() {
  pinMode(8, OUTPUT);
  TIMSK0 = 0; // disable timer0 interrupts
}

void loop() {
  for (;;) {
    PINB |= _BV (0);  // toggle pin 8
  }
}

As for the question:

Why does digitalWrite HIGH vs digitalWrite LOW take different amounts of time?

The supporting code is what makes the time interval different. if you skip the serial input check and turn of the timer as I did here but still use digitalWrite() the times look to be close if not exact.

void setup() {
  pinMode(8, OUTPUT);
  TIMSK0 = 0; // disable timer0 interrupts
}

void loop() {
  bool i; // don't need to make it static 
  for (;;) {
    digitalWrite( 8,i); 
    i=!i;
  }

This ran at 95.2kHz I’m not sure of the difference in times. They look close according to my scope meter but could you check? My scope meter isn’t designed to measure this closely.

What is interesting is, if you don’t need to test for serial input (Adding if( ; ; ){your code}) and you disable timer0 ( TIMSK0 = 0 ; // disable timer0 interrupts ) you could end up with a super fast cycle time without any interrupts during execution with a max loop rate of about 2 MHz with only one direct command to pins.