westfw:
Nah, it's a little faster than that. Your sample sketch includes the overhead of calling and returning from loop(), and you'll go a it faster in a local loop, or if you unroll the loop. See Maximum pin toggle speed - Frequently-Asked Questions - Arduino Forum (which all talks about a 16MHz AVR, so divide by 2...)
Basically, there are a bunch of arrays stored in flash, indexed by the pin number, that hold the values needed to do the actual bit write. (port, bitmask, otherstuff.) The function loads the values from the arrays, does some sanity checking, and then does the actual IO with the values it's pulled from the arrays. It's faster than your code because the array lookups replace your loop, and slower than direct port IO both because of the translation, and because it can't use the single-instruction bitset/bitclear opcodes when the port and bit are variables instead of constants.
Here's the code, gathered, and with additional comments. As used here, a "handle" is an arbitrary number that refers to something else, which is somewhat subtlely different than, say, the actual address of the thing. Usually shorter.
// An array in flash that is indexed by "pin number" and returns a timer "handle" (or NOT_ON_TIMER)
const uint8_t PROGMEM digital_pin_to_timer_PGM[] = {
NOT_ON_TIMER, /* 0 - port D */
NOT_ON_TIMER,
NOT_ON_TIMER,
TIMER2B,
#endif
:
};
// And here's a macro that does the array lookup. It's somewhat strange because of the need to
// read from flash instead of a RAM array. And it's stranger than it needs to be (sigh.)
// (I would have written pgm_read_byte(&digital_pin_to_timer_PGM[P]) to make the array-ness clearer)
#define digitalPinToTimer(P) ( pgm_read_byte( digital_pin_to_timer_PGM + (P) ) )
// Similar array for converting pin number to bit withing the port. Any pin on the chip is uniquely
// identified by which port it's associated with, and which bit within that port.
const uint8_t PROGMEM digital_pin_to_bit_mask_PGM[] = {
_BV(0), /* 0, port D */
_BV(1),
_BV(2),
_BV(3),
_BV(4),
_BV(5),
:
};
// Similar macro for bitmask:
#define digitalPinToBitMask(P) ( pgm_read_byte( digital_pin_to_bit_mask_PGM + (P) ) )
// Similar array for pin number to port "handle." The constants PD, PB, PC have no inherent meaning;
// they'll later be used as an index to another array.
const uint8_t PROGMEM digital_pin_to_port_PGM[] = {
PD, /* 0 */
PD,
PD,
PD,
:
};
// Similar macro for port handle
#define digitalPinToPort(P) ( pgm_read_byte( digital_pin_to_port_PGM + (P) ) )
// Array for translating port handle to actual output port address.
// Note that generally, a port may have a different register for reading (PINx) and writing (PORTx)
const uint16_t PROGMEM port_to_output_PGM[] = {
NOT_A_PORT,
NOT_A_PORT,
(uint16_t) &PORTB,
(uint16_t) &PORTC,
(uint16_t) &PORTD,
};
// Macro for port handle to output port address. Note that the address is a word, rather than a byte.
#define portOutputRegister(P) ( (volatile uint8_t *)( pgm_read_word( port_to_output_PGM + (P))) )
void digitalWrite(uint8_t pin, uint8_t val)
{
uint8_t timer = digitalPinToTimer(pin); // Load timer used for analogWrite() (if any)
uint8_t bit = digitalPinToBitMask(pin); // Load bitmask for within the (8bit) port
uint8_t port = digitalPinToPort(pin); // Load port "handle"
volatile uint8_t *out;
// if the port handle says this is not an actual pin, the return without doing anything.
if (port == NOT_A_PIN) return;
// If the pin that support PWM output, we need to turn it off
// before doing a digital write.
if (timer != NOT_ON_TIMER) turnOffPWM(timer);
out = portOutputRegister(port); // convert the port handle into an actual port address.
uint8_t oldSREG = SREG; // Save current interrupt-enable state and turn off interrupts,
cli(); // to prevent race conditions (an ISR modifying the same port.)
if (val == LOW) {
*out &= ~bit; // For writing low, read the port, clear the bit, and re-write.
} else {
*out |= bit; // for high, read, set bit, and re-write.
}
SREG = oldSREG; // restore interrupt-enable state.
}
Thanks, this is slightly clearer, but by no means crystal clear to me. It is still very confusing that it's done with many chains and pointers, but I think I got the concept or the programming flow completely now. It basically use Port mapping and Memory addressing to do the actual DigitalWrite, very confusing but clever indeed.
The technique for disabling the interrupt is neat, but will it even work or is it really necessary? what if there is an interrupt triggered right before " cli();"?