-1

In the (official) file wiring_shift.c I found the following code for shiftOut:

void shiftOut(uint8_t dataPin, uint8_t clockPin, uint8_t bitOrder, uint8_t val)
{
    uint8_t i;

    for (i = 0; i < 8; i++)  {
        if (bitOrder == LSBFIRST)
            digitalWrite(dataPin, !!(val & (1 << i)));
        else    
            digitalWrite(dataPin, !!(val & (1 << (7 - i))));

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

However, the last two digitalWrite commands occur directly after each other. I wouldn't hope that digitalWrite depends on some time or delay, so how can it be assumed that the slave device sees the clockPin is HIGH if the Arduino immediately sets the clockPin to LOW again?

(My plan is to convert the shiftIn/shiftOut code to STM32 using HAL/CubeIDE).

1 Answer 1

3

Some time ago, I timed digitalWrite() and direct port write commands by looping over 10,000 executions of each and timing them with millis(). I made both tests on a 16MHz Atmega 328p at 16MHz.

For digitalWrite(13, LOW), I got 2.7usec / call.
For PORTB &= 0x20;, I got 0.15usec / statement. (The direct port access was executed 10 times within the loop to further pad it out for the millis() result to have any significance.)

[In my notes I wrote that I timed PORTB &= 0x20;, but the correct equivalent to the digitalWrite() version is PORTB &= ~0x20;. That is probably what I actually did, but note that the bit-mask would be generated at compile time, so either way should have produced the same result.]

how can it be assumed that the slave device sees the clockPin is HIGH

It would depend on the specifics of the slave, but it is quite likely triggered by a pulse, rather than polling its input until it finds a high-level.

Update: To confirm my timing data, I redid my 2.5-years-ago experiment. I put 10 statements in each of 2 loops of 10,000 iterations, either:

digitalWrite(13, LOW);

or

PORTB &= ~0x20;

And got these total times in msec for the 100,000 executions:

digitalWrite(): 305 // digitalWrite(13, LOW); PORTB (correct): 15 // PORTB &= ~0x20;

These translate to single execution times in 10s of nanoseconds:
3050 nsec == 3.05 usec for the function call,
150 nsec for direct port I/O, making port I/O about 20x faster.

3
  • 1
    Re “either way should have produced the same result”: Not really. The compiler can see that PORTB &= ~0x20; affects a single bit, and compiles that to a single cbi instruction that executes in 2 CPU cycles (0.125 µs @ 16 MHz). PORTB &= 0x20; is likely going to give you a 3-cycle read-modify-write sequence (0.1875 µs). Commented Jan 26, 2020 at 17:26
  • Back to the drawing board... :)
    – JRobert
    Commented Jan 26, 2020 at 18:21
  • @JRobert thanks for the explanation, the first part I knew (kind of), but the second really answers my question exactly. I know that for timing mostly an IC checks the middle of a fixed state (either high or low), but if it triggers for an edge, yes, than it doesn't matter (as long as of course the IC has the time to get fully HIGH for a rising edge or fully DOWN for a falling edge, but I'm sure that's in the ns scale). Commented Jan 26, 2020 at 21:00

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.