How do you name your macro definitions to make your code more readable? - Page 1
Forum section: electronics / general

author: cvriv
author_rank: Frequent Contributor
timestamp: July 26, 2022, 12:53:11 pm
content: I'm special. LOL. I'm completely hung up on this...I'm trying to decide how to define 0x00, 0x01, and 0xFF as these are used very regularly. Normally I would just use those values throughout my code because I know what's going on with my code, but now I'm trying to be nice programmer, who is more friendly with other programmers. I want to name everything so everything is more readable.My issue is... how readable do I want to make my code?Example:Code:#define ON 0x01#define ENABLED 0x01#define ALL_ON 0xFF#define ALL_ENABLED 0xFF#define ANALOG 0x01#define OFF 0x00#define DISABLED 0x00#define ALL_OFF 0x00#define ALL_DISABLED 0x00#define DIGITAL 0x00Is it good practice to have so many definitions all with the same few values? It's more readable this way, but at what point does it become to much?The most simplest definitions could be:Code:#define BIT_SET 0x01#define BYTE_SET 0xFF#define CLEAR 0x00This makes more sense to me, but is not as readable. I might as well just use the values themselves.How do all of the professionals name their definitions?

author: AndyC_772
author_rank: Super Contributor
timestamp: July 26, 2022, 01:12:32 pm
content: Use names that are meaningful and appropriate to the context.The examples you've listed don't really tell the reader anything new. If "ON" = 1, what is being turned 'on' and is the sense of the bit always the same? Is *everything* that could be turned 'ON' using positive logic, or might there be things that are turned on with a 0? In those specific cases, using 'ON' would appear clear and unambiguous, but would also be incorrect.With that in mind, consider using names that incorporate the context as well, eg.Code:#define LED_ON 1#define LED_OFF 0#define BUTTON_PRESSED 0#define BUTTON_RELEASED 1#define BUTTON_UP 1#define BUTTON_DOWN 2#define BUTTON_OK 4#define BUTTON_ANY (BUTTON_UP | BUTTON_DOWN | BUTTON_OK)That way, when you use the code on new hardware in a couple of years' time, and the LEDs are wired the other way round, you only have to change two definitions in a header file, and your code works without change.

author: cvriv
author_rank: Frequent Contributor
timestamp: July 26, 2022, 01:26:30 pm
content: Quote from: AndyC_772 on July 26, 2022, 01:12:32 pmWow. I forgot about that. Like I said I usually just work with the values so I never really have to think about stuff like this. Probably why it's driving me nuts. I know what I have to do now.Thanks.

author: ejeffrey
author_rank: Super Contributor
timestamp: July 26, 2022, 04:30:18 pm
content: I would resist the urge to go nuts with defines. #define ON 1 doesn't do anyone any good. Having multiple interchangeable names for the same value can be actively harmful. Like ALL_ON and ALL_ENABLED both defined as 0xFF. Defining values that just name the number BYTE_ALL are also not usually helpful.If you are going to use macros like this it should be unambiguous which ones to use in any situation. So it would be fine to have LEDS_ALL_ON and SWITCHES_ALL_ON even if the were the same value, although I would still question the utility if they were just 0xFF. But if the value was a mask for just the LEDs in a wider IO port that makes a lot of senseA couple other options are good to consider:Wrapper functions to perform GPIO tasks that are named for the purpose are a better way to guard against future changes where the polarity of a signal changes or pin assignment changes. This is potentially much more flexible than #define. A nice advantage here is that they could do runtime selection of the behavior l so if you have two PCB versions they could use the same binary and select behavior based on the hardware revision.Enums can be used as type safe alternatives to macros. Then the fact that you have different enums with the same value doesn't matter because the enum type designates where it can be used.

author: TomS_
author_rank: Frequent Contributor
timestamp: July 26, 2022, 06:48:54 pm
content: You could take a look at a processor header file for a Microchip PIC as an example of one way to do it.They have defines for each bit field such as its bit mask, position and length. This gives you a few options for setting, clearing and extracting the bit fields from their respective registers.And since each define relates to a specific bit field they are also descriptively named as to what they are for, so your code ends up full of descriptive references.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 26, 2022, 08:38:14 pm
content: Done and done:Code:#include <stdbool.h>When you have state variables, useenums, andtypedefit if you like. That is:Code:/** state counter for widget */typedef enum widget_state_e {WIDGET_OFF = 0,WIDGET_START = 1,WIDGET_RUN = 2,WIDGET_STOP = 3,} widget_state_t;/** current widget state */widget_state_t widget1;Note that enums are assigned sequentially, so the =1, =2, =3 are redundant, and the default starting value is 0 so that value is technically redundant as well. But you can if you like. And obviously, if you want certain magic numbers (non-sequential), you have to pick. Enums are also handy for bit masks, in which case specify 0x01, 0x02, 0x04, ..., or even a mix, say if you have enumerated states plus some bits packed on top (e.g. bits 0-3 are a count 0-15 corresponding to exclusive states, bits 4-7 are on/off flags..). This is how most systems define their parameters/states/masks; anything from hardware register values (at least, AVR for one (XMEGA+), defines registers this way) to operating systems.Enums aren't really types, they're kinda one step up from literals (so, have default type of at leastint). If you want other, just typedef accordingly and leave the enum by itself.In this way, you might have multiple definitions of 0, 1, etc., but they are semantically connected to the underlying object, rather than a pile of flat #defines. (Granted, they'll fill up namespace just as flat as defines will...)It also helps to think in terms of object orientation, I think:C helps you out exactly 0 here, but you can approximate OOP by considering a module (*.c) as a class, the headers (*.h) as public exports (defines, enums, structs/typedefs, shared fields/functions; fields usually should be declaredextern), and encapsulating all the state of the object inside a struct or whatever, which is passed to any function operating on it (behind the scenes, "this" is just an implicit 1st parameter passed to the function for you, when you call the "method" on that object (foo.getFoo()).If you have state that depends on globals or statics, then functions using/modifying those variables will not be reentrant, or strictly object-oriented. But, both in this case, and in properly OOP languages, you can still have globals defining, say, shared stuff like configuration; it doesn't all need to be instances, after all. But also don't forget the opportunity to consider whether those things should, after all, be fields of a higher level object, a program state object perhaps!I think games are a classic illustration of this; you just, go and write a game, say, and it's got some internal state, and... that's fine. But then later, you want to port it to something else, or add savegames, or multiplayer, and all that state has to be painfully refactored because it's connecting to different I/O, or needs to be stored/loaded or synchronized perfectly. Well, with all that in a common object instead, it's relatively trivial to serialize it, or abstract away the I/O access (use an interface and implement the drivers?).In C, you have full flexibility to put things wherever you like, and maybe you decide against object-ifying certain things for performance reasons, or there are other reasons that some module/functionality will only ever have one instance. Or, of course, if the module is static, not having any instances itself (e.g. helper functions like math?). That's fine, just to have the perspective is something.Anyways, in that case, enums can further be structured by associating with their respective modules. Which, is probably pretty obvious to say, but it may be useful to understand the whole paradigm, as it will also be more familiar to other readers these days.Tim

author: Benta
author_rank: Super Contributor
timestamp: July 26, 2022, 11:56:02 pm
content: Me being an 80% HW, 20% FW guy, I'm more into using 0bxxxxxxxx definitions on I/O ports and configuration registers.Sometimes even going to the point of naming the individual bits and then ORing/ANDing them as needed.The 0x notation is not really helpful in HW/FW development and bug tracing, you'll have to decode the value to binary before being able to use it anyway.But that's just me.

author: T3sl4co1l
author_rank: Super Contributor
timestamp: July 27, 2022, 03:23:45 am
content: Quote from: Benta on July 26, 2022, 11:56:02 pmI'm slightly tempted (but only slightly!) to port much of the AVR headers to parseable strings and commands, so I can name them by register and bit and configuration, straight from the console.But that would be an enormous PITA, take much more time than working on ten whole projects while referencing the docs as I work. And it might be enough it doesn't even fit on the system itself, even with compression; ioavr64da64.h is 361kB for example, much of that being formatting and #define and all that, but even so.Tim

author: Siwastaja
author_rank: Super Contributor
timestamp: July 27, 2022, 09:34:27 am
content: Remove all that bullcrap.1 is on and 0 is off. What's wrong with 1 and 0, why do you need to rename them?In other words, if you want to set all bits in a byte as 1, just write0xffor0b11111111or-1or whatever is most appropriate in the context.Then it is totally obvious to anyone what is happening in the code.In special occasions, enums or defines are useful to give state names etc., this can actually make code more readable, but generic names for generic bit values? No way.You may want to considerstatic inline void led_on() {GPIOA->BSRR = 1UL<<(16+5);}static inline void led_off() {GPIOA->BSRR = 1UL<<(5);}to abstract whatever goes into turning LED on or off. This way, you need to modify only one place if the LED polarity or pin mapping changes. On the other hand,LED_PORT->BSRR = 1UL<<(LED_ON?16:0 + LED_PIN);or similar sprinkled all over the codebase is not readable even if it allows changing things "in one place".

author: jpanhalt
author_rank: Super Contributor
timestamp: July 27, 2022, 09:49:26 am
content: I write in Assembly and typically use a define for something in the code that may differ based on chip, layout, code version, etc. In that way, it can be changed in one place rather than having to go through the entire code searching for instances. As for "on" or "off," I would probably not use a define. Some of the English in the example below,"FLT," "DRDY," etc. are labels from the respective datasheets.Example:Code:;MAX31856#define FLT PORTD,7 ;#define DRDY PORTB,0 ;INT#define TC_CS LATD,6 ;;ST7567#define LCD_BL LATC,2 ;PORTC,2 shared with CCP1 for dimming(030722)#define LCD_CS LATD,5 ;PORTD,5 !CS for LCD#define LCD_RST LATD,4 ;PORTD,4 hardware reset#define LCD_A0 LATC,7 ;PORTD,7 A0(H)=DAT, A0(L) = CMD;General#define bufsize 64 ;bufsize = 2^n = 2,4,8,16,32,64,128#define bufstart 0x2040 ;change to 0x2020 if more GPRAM not needed; #define LED LATx,y ;not used w/ OSHPark board#define offsetY 5 ;"1"does not work well, used 10 in testing#define offsetX 5 ;use 1..5 "5" gave 6;Input Switches#define SW1 RB5#define SW2 RB4 ;RB4 is SW2 input to MCU

author: newbrain
author_rank: Super Contributor
timestamp: July 27, 2022, 10:29:11 am
content: Quote from: Siwastaja, edits by newbrainSo much so that's even incorrect (unless LED_PIN is 0).I tend to agree that defining "universal" constants (0, 1, 0xFF... etc.) is almost always quite pointless.I extensively use enum (and typedef) for groups of strictly related constants (e.g. states) rather then #define.

author: westfw
author_rank: Super Contributor
timestamp: July 31, 2022, 01:34:08 am
content: QuoteThis isn't too hard to do, actually. Most of the current .h files are actually prepared "automatically" from XML-format files that are easy to parse with, say, Python. And then you can spit them out however you want.The main problem is getting rid of the many mostly irrelevant defintions that clutter things up. I really don't want separate definitions for PORTC1, PORTD1, PINC1, DDRB1, and all the other relatively meaningless symbols whose value is (1<<1)For example (for ARM):https://github.com/WestfW/Atmel_ATDF_parserAlso, I think people have done this and produced Forth Dictionaries.https://mecrisp-stellaris-folkdoc.sourceforge.io/bluepill-diagnostics-v1.6.html(?)And it's not too bad to attack an existing .h file with some EMACS macros, like I did here:https://github.com/WestfW/Minimal-ARM/blob/master/inc/stm32f103c8.asmh#L6088