For me it's much easier to manipulate a header with pin declarations than diving into the files?
Wut, diving into what files? I mean, code has to be in files, where else? Just put the functions into those same files where you now put macros.
static inline exists just for this reason, the functions can be in header files, which can be included and included again in every compilation unit if you so wish, just like macros.
The only reason to use macros is when some macro-exclusive feature is actually needed, e.g. absence of type checking (allowing generic "template"-like thing), or text concatenation/stringify operations. Also great for generating compile-time arrays etc, I prefer them over separate code generation scripts.
Otherwise than that, macros require a lot of care:
1) You need to wrap them in do{} while(0) which looks ugly and is unnecessary boilerplate. If you fail to do that, macro cannot be used like a function e.g.
#define LED_ON() {GPIOA->BSRR = 1UL<<5;}
if(condition)
LED_ON(); // expands to {GPIOA->BSRR = 1UL<<5;};, which has one semicolon too much, so that the else below doesn't belong to anywhere
else
something_else();
Fixed:
#define LED_ON() do{GPIOA->BSRR = 1UL<<5;}while(0)
if(condition)
LED_ON(); // expands to do{GPIOA->BSRR = 1UL<<5;}while(0); -- just the right number of semicolons
else
something_else();
2) You lose type checking so need to be more careful everywhere in code
3) You have to split lines with trailing \, which is tedious,
4) You have to wrap all arguments inside ():
Fail:
#define MACRO(arg) (arg*2)
MACRO(5); // 5*2 = 10 -> ok
MACRO(5+1); // 5+1*2 = 7 -> fail
OK
#define MACRO(arg) ((arg)*2)
MACRO(5); // (5)*2 = 10 -> ok
MACRO(5+1); // (5+1)*2 = 12 -> ok
5) For macros that access the argument(s) more than once, you need to make a temporary variable:
Fail:
#define ENDIAN_SWAP(arg) (((arg)&0x00ff)<<8 | ((arg)&0xff00)>>8) // very common bug, looks fine, but...
ENDIAN_SWAP(i++); // fails, when it becomes: (((i++)&0x00ff)<<8 | ((i++)&0xff00)>>8), i is incremented twice, in undefined order.
Fuck this, I don't even try to fix it with a temporary variable, because macros do not have
return, maybe you could do something with the , operator no one normally uses. Instead, let's be reasonable and do this properly:
static inline uint16_t ENDIAN_SWAP(uint16_t arg) { return (arg&0x00ff)<<8 | (arg&0xff00)>>8; } // look how many unnecessary parenthesis we removed
ENDIAN_SWAP(i++); // works now as expected.
TLDR, only use macros when they offer something useful, not "just because".