macros

David Mellis said earlier this week that he thought the time was near to add the digitalWriteFast stuff to the Arduino core. During the last few days I’ve been seeing if I can get that to work, and indeed have something that’s passed the tests I built for each pin on an uno and mega1280. The thing that’s driving me nuts is that if I try to run a sketch that includes the digitalWriteFast library, I get an error on every single line that uses any of the six functions there. I’ve been working on revising digitalWriteFast so that won’t happen and having no success.

Most of the new stuff for the core is in ‘wiring.h’:

int analogRead(uint8_t);
void analogReference(uint8_t mode);
void analogWrite(uint8_t, int);

void __pinMode(uint8_t, uint8_t);
void __digitalWrite(uint8_t, uint8_t);
int __digitalRead(uint8_t);
//===============================
#if !defined(digitalPinToPortReg)
#if !(defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) )
      
      
      // Standard Arduino Pins
#define digitalPinToPortReg(P) \
(((P) >= 0 && (P) <= 7) ? &PORTD : (((P) >= 8 && (P) <= 13) ? &PORTB : &PORTC))
#define digitalPinToDDRReg(P) \
(((P) >= 0 && (P) <= 7) ? &DDRD : (((P) >= 8 && (P) <= 13) ? &DDRB : &DDRC))
#define digitalPinToPINReg(P) \
(((P) >= 0 && (P) <= 7) ? &PIND : (((P) >= 8 && (P) <= 13) ? &PINB : &PINC))
#define __digitalPinToBit(P) \
(((P) >= 0 && (P) <= 7) ? (P) : (((P) >= 8 && (P) <= 13) ? (P) - 8 : (P) - 14))
      
#if defined(__AVR_ATmega8__)
      
      // 3 PWM
#define __digitalPinToTimer(P) \
(((P) ==  9 || (P) == 10) ? &TCCR1A : (((P) == 11) ? &TCCR2 : 0))
#define __digitalPinToTimerBit(P) \
(((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : COM21))
#else  //168,328
      
      // 6 PWM
#define __digitalPinToTimer(P) \
(((P) ==  6 || (P) ==  5) ? &TCCR0A : \
(((P) ==  9 || (P) == 10) ? &TCCR1A : \
(((P) == 11 || (P) ==  3) ? &TCCR2A : 0)))
#define __digitalPinToTimerBit(P) \
(((P) ==  6) ? COM0A1 : (((P) ==  5) ? COM0B1 : \
(((P) ==  9) ? COM1A1 : (((P) == 10) ? COM1B1 : \
(((P) == 11) ? COM2A1 : COM2B1)))))
#endif //defined(__AVR_ATmega8__)
      
#else  //Mega 1280,2560
      // Arduino Mega Pins
#define digitalPinToPortReg(P) \
(((P) >= 22 && (P) <= 29) ? &PORTA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PORTB : \
(((P) >= 30 && (P) <= 37) ? &PORTC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PORTD : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PORTE : \
(((P) >= 54 && (P) <= 61) ? &PORTF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PORTG : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PORTH : \
(((P) == 14 || (P) == 15) ? &PORTJ : \
(((P) >= 62 && (P) <= 69) ? &PORTK : &PORTL))))))))))
      
#define digitalPinToDDRReg(P) \
(((P) >= 22 && (P) <= 29) ? &DDRA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &DDRB : \
(((P) >= 30 && (P) <= 37) ? &DDRC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &DDRD : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &DDRE : \
(((P) >= 54 && (P) <= 61) ? &DDRF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &DDRG : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &DDRH : \
(((P) == 14 || (P) == 15) ? &DDRJ : \
(((P) >= 62 && (P) <= 69) ? &DDRK : &DDRL))))))))))
      
#define digitalPinToPINReg(P) \
(((P) >= 22 && (P) <= 29) ? &PINA : \
((((P) >= 10 && (P) <= 13) || ((P) >= 50 && (P) <= 53)) ? &PINB : \
(((P) >= 30 && (P) <= 37) ? &PINC : \
((((P) >= 18 && (P) <= 21) || (P) == 38) ? &PIND : \
((((P) >= 0 && (P) <= 3) || (P) == 5) ? &PINE : \
(((P) >= 54 && (P) <= 61) ? &PINF : \
((((P) >= 39 && (P) <= 41) || (P) == 4) ? &PING : \
((((P) >= 6 && (P) <= 9) || (P) == 16 || (P) == 17) ? &PINH : \
(((P) == 14 || (P) == 15) ? &PINJ : \
(((P) >= 62 && (P) <= 69) ? &PINK : &PINL))))))))))
      
#define __digitalPinToBit(P) \
(((P) >=  7 && (P) <=  9) ? (P) - 3 : \
(((P) >= 10 && (P) <= 13) ? (P) - 6 : \
(((P) >= 22 && (P) <= 29) ? (P) - 22 : \
(((P) >= 30 && (P) <= 37) ? 37 - (P) : \
(((P) >= 39 && (P) <= 41) ? 41 - (P) : \
(((P) >= 42 && (P) <= 49) ? 49 - (P) : \
(((P) >= 50 && (P) <= 53) ? 53 - (P) : \
(((P) >= 54 && (P) <= 61) ? (P) - 54 : \
(((P) >= 62 && (P) <= 69) ? (P) - 62 : \
(((P) == 0 || (P) == 15 || (P) == 17 || (P) == 21) ? 0 : \
(((P) == 1 || (P) == 14 || (P) == 16 || (P) == 20) ? 1 : \
(((P) == 19) ? 2 : \
(((P) == 5 || (P) == 6 || (P) == 18) ? 3 : \
(((P) == 2) ? 4 : \
(((P) == 3 || (P) == 4) ? 5 : 7)))))))))))))))
      
      // 15 PWM
#define __digitalPinToTimer(P) \
(((P) == 13 || (P) ==  4) ? &TCCR0A : \
(((P) == 11 || (P) == 12) ? &TCCR1A : \
(((P) == 10 || (P) ==  9) ? &TCCR2A : \
(((P) ==  5 || (P) ==  2 || (P) ==  3) ? &TCCR3A : \
(((P) ==  6 || (P) ==  7 || (P) ==  8) ? &TCCR4A : \
(((P) == 46 || (P) == 45 || (P) == 44) ? &TCCR5A : 0))))))
#define __digitalPinToTimerBit(P) \
(((P) == 13) ? COM0A1 : (((P) ==  4) ? COM0B1 : \
(((P) == 11) ? COM1A1 : (((P) == 12) ? COM1B1 : \
(((P) == 10) ? COM2A1 : (((P) ==  9) ? COM2B1 : \
(((P) ==  5) ? COM3A1 : (((P) ==  2) ? COM3B1 : (((P) ==  3) ? COM3C1 : \
(((P) ==  6) ? COM4A1 : (((P) ==  7) ? COM4B1 : (((P) ==  8) ? COM4C1 : \
(((P) == 46) ? COM5A1 : (((P) == 45) ? COM5B1 : COM5C1))))))))))))))
      
#endif  //!(defined(__AVR_ATmega1280__) || defined(__AVR_ATmega2560__) || defined(__AVR_ATmega2561__) )
#endif  //!defined(digitalPinToPortReg)
      

static inline void turnOffPWM(uint8_t timer) __attribute__ ((always_inline));  //preexisting in wiring_digital.c


#define digitalWrite(P, V) \
do {if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
bitWrite(*digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \
} else { \
__digitalWrite((P), (V)); \
}} while (0)

      

#define pinMode(P, V) \
do {if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
   bitWrite(*digitalPinToDDRReg(P),__digitalPinToBit(P), (V)); \
  } else {  \
   __pinMode((P), (V)); \
  }} while (0)

      
#if !defined(stopAnalogWrite)
#define stopAnalogWrite(P) \
if (__builtin_constant_p(P) ) { \
if (__digitalPinToTimer(P)) \
bitClear(*__digitalPinToTimer(P), __digitalPinToTimerBit(P)); \
} else {  \
turnOffPWM((P)); \
}
#endif            
      

#define digitalRead(P) ( (int) __digitalReadFast2__((P)) )
#define __digitalReadFast2__(P ) \
(__builtin_constant_p(P) ) ? ( \
( bitRead(*digitalPinToPINReg(P), __digitalPinToBit(P))) ) : \
__digitalRead((P)) 

//=====================================
      
unsigned long millis(void);
unsigned long micros(void);

as you can surmise, in ‘wiring_digital.c’ what was formerly digitalWrite is now called __digitalWrite.

There is, of course, great similarity with code in digitalWriteFast. Indeed the entire block controlled by #if !defined(digitalPinToPortReg) has been copy/pasted. Note that __digitalPinToBit is conditionally defined depending on your MCU.

The errors I get look like this

digitalWriteFastTestMega2560x7:20733: error: '__digitalPinToBit' was not declared in this scope
digitalWriteFastTestMega2560x7:20734: error: '__digitalPinToBit' was not declared in this scope
digitalWriteFastTestMega2560x7:20735: error: '__digitalPinToBit' was not declared in this scope
digitalWriteFastTestMega2560x7:20736: error: '__digitalPinToBit' was not declared in this scope
digitalWriteFastTestMega2560x7:20738: error: '__digitalPinToBit' was not declared in this scope

At one point I even changed #if !defined(digitalPinToPortReg) to test if __digitalPinToBit was defined, without any change in the errors.

This morning I added to the digitalWriteFast macros so that it knows whether the function to call when the pin number is not defined at compile time is digitalWrite or _digitalWrite.

#if !defined(digitalWriteFast)
#ifdef __digitalWrite             //This is here so that when/if fast digitalWrite gets added to the core, code that uses this will not break.
#define digitalWriteFast(P, V) \
do{if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
                if (__digitalPinToTimer(P)) \
                        bitClear(*__digitalPinToTimer(P), __digitalPinToTimerBit(P)); \
                bitWrite(*digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \
        } else { \
                __digitalWrite((P), (V)); \
        }} while(0)
#else
do{if (__builtin_constant_p(P) && __builtin_constant_p(V)) { \
      if (__digitalPinToTimer(P)) \
            bitClear(*__digitalPinToTimer(P), __digitalPinToTimerBit(P)); \
            bitWrite(*digitalPinToPortReg(P), __digitalPinToBit(P), (V)); \
        } else { \
                  digitalWrite((P), (V)); \
        }} while(0)
#endif //__digitalWrite
#endif //  !defined(digitalWriteFast)

Still no success.

I recognize that the usual thing to do is to put commonality like this into a single header and #include it, wrap that header in #ifndef/#endif to not include it twice. I don’t feel that is appropriate here, given that what I’m trying to compensate for are changes in the core that are not really under my direction. It seems like what I’ve done here should be equivalent, but it sure isn’t working out for me.

The alternative would be to require everyone that has been using digitalWriteFast to pull it out, find/replace all to change everything back to digitalWrite. That was working yesterday morning, but it seems like there has to be a way around this.

This is why I REALLY hate macros. Troubleshooting obscure problems is a nightmare. If someone else doesn't help you by this evening, I'll take another look.

On a different topic... I believe the body of turnOffPWM has to be moved to the header file in order for turnOffPWM to be inlined... static inline void turnOffPWM(uint8_t timer) attribute ((always_inline)); //preexisting in wiring_digital.c

If I remember correctly, because the body is not available, the compiler treats the function as a normal / non-inlined function.

The declaration and the definition were both in wiring_digital.c before. I referred to it here and moved the declaration. I'm more used to combining them so that the declaration contains the definition. I'll move it.

Speaking of the nightmare of troubleshooting macros, I would have thought that someone had written a unix tool that would show you how they expanded. I'm not aware of such a thing and didn't succeed in googling one up this weekend. It still seems like it must have been done.

It SEEMS like the problem must be in the #if defined( 's rather than in the really complex pin by pin things. Those have been working and other than fixing a name conflict haven't changed.

Speaking of the nightmare of troubleshooting macros, I would have thought that someone had written a unix tool that would show you how they expanded

They have. It's the C Preprocessor.

You have two hurdles to overcome...

  1. Getting the command-line right. With some preprocessors you can pass the same arguments that are passed to the C compiler. The preprocessor ignores the ones it doesn't understand and uses the ones it does understand. I have no idea if the GCC preprocessor works this way.

  2. Matching the preprocessor output to source. Some preprocessors make this easy by optionally including file / line information. Some don't. Again, I have no idea if the GCC preprocessor has this option.

In any case, it's a whole lot of fiddling with a seldom used tool. Something that brings a shiver to my spine. If a horror movie about programmers was ever created, using the preprocessor to troubleshoot macro problems would certainly be included. Nooooo! The preprocessor has my girlfriend! It's replacing her keywords until ... she's turned into a platypus! Nooooo!

Sorry ... I digress.

The thing that's driving me nuts is that if I try to run a sketch that includes the digitalWriteFast library, I get an error on every single line that uses any of the six functions there

Maybe you're looking at it the wrong way around. If I had been using the digitalWriteFast library and that functionality was moved into the core, I would expect digitalWriteFast to become...

define digitalWriteFast digitalWrite

In other words, I'd expect the digitalWriteFast library to essentially disappear from the build. Or, I'd expect to have to remove the digitalWriteFast library. But I'm not using the digitalWriteFast library so my opinion doesn't count for much.

I suggest soliciting opinions from current digitalWriteFast library users.

I think that's a great idea.

I can bracket that #define with #ifdef __digitalWrite. Seems like a robust solution.