I'm trying to implement register access MACRO on a C2000 microcontroller. Sadly this microcontroller has no 8-bit type so when I need to access a byte I need to call a macro
__byte(temp,0) = 0xCA
Some of my registers are bytes others are 16-bit words. However, I'd like to use the same macro to access the data.
#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1
#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0
I'd like to access the register like this.
REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;
This would be converted to a different macro like
REG_ACCESS2(REG_WIDGET_ADDR, REG_WIDGET_IS_BYTE) = 0x0A
REG_ACCESS2(REG_GADGET_ADDR, REG_GADGET_IS_BYTE) = 0xCAFE
And then the REG_ACCESS2 macro should be converted to something like __byte(*(0x1000),0) = 0xA when the register is a byte. When the register is a 16-bit word, the macro should be converted to something like (*(0x1002) = 0xCAFE
I know how to create Macros that call different macros when the number of arguments change but not how to call different macros when the argument value change. How should I proceed?
Edit :
The C2000 does not have 8-bit registers, only 16-bit registers. But I want to program an external device using a DMA. I want to set the 8-bit and 16-bit "registers" in the C2000 Ram and then transfer it to the external device via DMA.
CodePudding user response:
This code does what you seem to request:
#define REG_WIDGET_ADDR 0x1000
#define REG_WIDGET_IS_BYTE 1
#define REG_GADGET_ADDR 0x1002
#define REG_GADGET_IS_BYTE 0
// These two macros provide the requested expansions for word and byte access.
#define REG_ACCESS_T0(a) *(a)
#define REG_ACCESS_T1(a) __byte(*(a), 0)
// This macro uses argument b to select between the two macros above.
#define REG_ACCESS3(a, b) REG_ACCESS_T##b(a)
// This macro is needed to let argument b be replaced.
#define REG_ACCESS2(a, b) REG_ACCESS3(a, b)
// This macro uses x to get the corresponding address and is-byte macros.
#define REG_ACCESS(x) REG_ACCESS2(REG_##x##_ADDR, REG_##x##_IS_BYTE)
REG_ACCESS(WIDGET) = 0x0A;
REG_ACCESS(GADGET) = 0xCAFE;
The results of macro replacement are:
__byte(*(0x1000), 0) = 0x0A;
*(0x1002) = 0xCAFE;
CodePudding user response:
Part 1
The classical approaches to this preprocessing problem involve pasting tokens from fragments which then match certain defined tokens. The problem with token fragmentation is that it's hostile to your basic development environment tooling like "find where this identifier is used".
Instead we can take the approach of defining the register as a data frame, like this:
#define WIDGET 0x1000, BYTE
#define GADGET 0x1002, WORD
but: let's not quite take this unencapsulated approach. Let's make a "constructor" for this "datatype" and use that. Reason being: if we ever add another property to our registers, we can just add a parameter to the constructor-like macro, and then the compiler will find all the places where the argument is needed, so we don't forget any:
#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
Another advantage is that the "properties" now have names. In our programming editor or IDE, we can jump to the definition of REG to see what 0x1000 means in the definition of WIDGET and see that it corresponds to a parameter named ADDR.
So now, having this "data structure" defined, we can write accessors:
#define REG(ADDR, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// retrieve addr
#define ADDR(ADDR, BYTE_OR_WORD) ADDR
// retrieve BYTE or WORD
#define BYTE_OR_WORD(ADDR, BYTE_OR_WORD) BYTE_OR_WORD
So then we can write the access macro like:
#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))
So for instance
REG_ACCESS(WIDGET)
expands to
BYTE(0x1000)
Now we just need a definition of BYTE() and WORD() to do the access. Here is the complete picture in one file:
// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// Retrieve address field of type by destructuring and selecting
#define ADDR(ADDR, BOW) ADDR
// Retrieve BYTE-or-WORD field likewise
#define BYTE_OR_WORD(ADDR, BOW) BOW
// Access macro: retrieves the fields and builds expression
#define REG_ACCESS(REG) BYTE_OR_WORD(REG)(ADDR(REG))
// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
Testing with gcc -E:
$ gcc -E regaccess.c
# 1 "regaccess.c"
# 1 "<built-in>"
# 1 "<command-line>"
# 31 "<command-line>"
# 1 "/usr/include/stdc-predef.h" 1 3 4
# 32 "<command-line>" 2
# 1 "regaccess.c"
# 18 "regaccess.c"
BYTE(0x1000)
WORD(0x1002)
Note how in the solution, every identifier has a definition somewhere, and all references to that identifier use exactly that identifier: no identifier is pasted together from unsearchable pieces.
If we see REG_ACCESS(WIDGET) in the code, we can jump to the definition of REG_ACCESS or to that of WIDGET.
MOreover, the code uses nothing but ISO C 90 preprocessor features, yet has a certain relationship to functional programming: construction of tuples, destructuring. This is because the preprocessor is (or can be) treated as a pure term-rewriting system.
Part 2
If we are doing something simple, we don't need the abstraction layer with the accessors. Because they are only used in a single macro, REG_ACCESS, we can make them disappear:
// REG type constructor
#define REG(ADDRESS, BYTE_OR_WORD) ADDRESS, BYTE_OR_WORD
// Access macro's implementation
#define X_REG_ACCESS(ADDR, BOW) BOW(ADDR)
// Access macro's interface
#define REG_ACCESS(REG) X_REG_ACCESS(REG)
// construct instances of various registers
#define WIDGET REG(0x1000, BYTE)
#define GADGET REG(0x1002, WORD)
// Test access
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
X_REG_ACCESS destructures the type's two pieces, ADDR and BOW, and without using any abstract accessors, does what it needs: constructs the application of the type's byte-or-word function name to the type's address.
The core of it is simplicity itself: BOW(ADDR). Pass the ADDR to BYTE or WORD.
Part 3
But, philosophical question: why wouldn't we just do:
#define WIDGET BYTE(0x1000)
#define GADGET WORD(0x1001)
// nothing to do: WIDGET and GADGET are already access expressions:
#define REG_ACCESS(REG) REG
How do we defend the earlier verbiage against this? One reason is that the earlier approach allows us to have macro-time access to information about a register, not just to generate code to access that register. What if we just want its address? Or whether it's a BYTE or WORD.
Our system is extensible. Say we want register names too, and a Boolean 1 or 0 valued predicate whether the register is a byte data type. Complex example:
// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) IS_BYTE, ACCESS_FN
// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) ADDRESS, DATA_TYPE, NAME
#define X_REG_ACCESS(ADDR, IS_BYTE, ACCESS_FN, NAME) ACCESS_FN(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS(REG)
#define X_REG_ADDR(ADDR, IS_BYTE, ACCESS_FN, NAME) ADDR
#define REG_ADDR(REG) X_ADDR(REG)
#define X_REG_NAME(ADDR, IS_BYTE, ACCESS_FN, NAME) NAME
#define REG_NAME(REG) X_REG_NAME(REG)
#define X_REG_IS_BYTE(ADDR, IS_BYTE, ACCESS_FN, NAME) IS_BYTE
#define REG_IS_BYTE(REG) X_REG_IS_BYTE(REG)
// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)
#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")
// Debug print about register
#define PRINT_REG_INFO(REG) \
printf("register " X_REG_NAME(REG) ", @%x, is byte: %s", \
X_REG_ADDR(REG), X_REG_IS_BYTE(REG) ? "yes" : "no")
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);
Output:
get_byte(0x1000)
get_word(0x1002)
printf("register " "I2C Acme Widget" ", @%x, is byte: %s", 0x1000, 1 ? "yes" : "no");
printf("register " "SPI Mikro Gadget" ", @%x, is byte: %s", 0x1002, 0 ? "yes" : "no");
Part 4
Note here how in Part 3 DATA_TYPE here gets spliced into the REG type, which has four elements. So that is to say, the REG accessors deal with a flat structure in which the fields of the data type are unfolded. This could be undesirable. If we want to add a field to DATA_TYPE, we have to edit all of the other types which include it to add arguments to their functions.
The fix for that is to have our constructors add parentheses, like this:
#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)
The X_ expanding macros then are invoked without parentheses:
#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG
Note how X_REG_ACCESS expansion changed from ACCESS_FN(ADDR) to ACCESS_FN(DATA_TYPE)(ADDR): DATA_TYPE is now encapsulated and we must use its accessor to get the access function name.
The whole Part 3 solution reworked: REG stuff is a 3-tuple again, where the two-element DATA_TYPE is one element:
// Machine type abstraction byte or word
#define DATA_TYPE(IS_BYTE, ACCESS_FN) (IS_BYTE, ACCESS_FN)
#define X_IS_BYTE(IS_BYTE, ACCESS_FN) IS_BYTE
#define IS_BYTE(DATA_TYPE) X_IS_BYTE DATA_TYPE
#define X_ACCESS_FN(IS_BYTE, ACCESS_FN) ACCESS_FN
#define ACCESS_FN(DATA_TYPE) X_ACCESS_FN DATA_TYPE
// Register abstraction
#define REG(ADDRESS, DATA_TYPE, NAME) (ADDRESS, DATA_TYPE, NAME)
#define X_REG_ACCESS(ADDR, DATA_TYPE, NAME) ACCESS_FN(DATA_TYPE)(ADDR)
#define REG_ACCESS(REG) X_REG_ACCESS REG
#define X_REG_ADDR(ADDR, DATA_TYPE, NAME) ADDR
#define REG_ADDR(REG) X_ADDR REG
#define X_REG_NAME(ADDR, DATA_TYPE, NAME) NAME
#define REG_NAME(REG) X_REG_NAME REG
#define X_REG_IS_BYTE(ADDR, DATA_TYPE, NAME) IS_BYTE(DATA_TYPE)
#define REG_IS_BYTE(REG) X_REG_IS_BYTE REG
// Database of machine types
#define BYTE DATA_TYPE(1, get_byte)
#define WORD DATA_TYPE(0, get_word)
#define WIDGET REG(0x1000, BYTE, "I2C Acme Widget")
#define GADGET REG(0x1002, WORD, "SPI Mikro Gadget")
// Debug print about register
#define PRINT_REG_INFO(REG) \
printf("register " X_REG_NAME REG ", @%x, is byte: %s", \
X_REG_ADDR REG, X_REG_IS_BYTE REG ? "yes" : "no")
REG_ACCESS(WIDGET)
REG_ACCESS(GADGET)
PRINT_REG_INFO(WIDGET);
PRINT_REG_INFO(GADGET);
