I want to write a driver that can use either timer0 or timer2, based on a choice made at compile time.
So I want all references to timer registers to be abstracted, e.g. OCRA for OCR0A/OCR2A, etc.
I could do it with plain macros, like so:
#define _CONCAT3(a,b,c) a##b##c
#define CONCAT3(a,b) _CONCAT3(a,b,c)
// set timer number globally
#define MY_TIMER 2
// define an alias for each control register
#define OCRA CONCAT3(OCR, MY_TIMER, A)
but for some reason I don't want to use macros there.
So I came up with this scheme:
typedef volatile uint8_t& tRegister;
constexpr volatile uint8_t& timer_register (volatile uint8_t& a, volatile uint8_t& b)
{ return MY_TIMER==0 ? a : b; }
tRegister OCRA = timer_register (OCR0A, OCR2A);
It works pretty well for all its awkwardness, but when I try to use this definition in an inline assembly directive, I get strange results:
asm volatile("lds r24, %0" : : "i"(&OCRA));
will get an error: impossible constraint in 'asm'
Now this:
asm volatile("lds r24, %0" : : "i"(&timer_register (OCR0A , OCR2A )));
will compile (and produce lds r24, 0x00B3
as it should).
By definition a reference to a static variable is a constant address, known at compile time, so I can't see why it can't be used in this context.
So my questions are:
- what is going on there?
- how to alias these registers (without macros) to please the inline assembler?
1 Answer 1
You could use pointers instead of references. With pointers, you have
the separate notions of "constant pointer" and "pointer to constant". A
reference is a kind of pointer coated with syntactic sugar. However,
whereas you can have a "constant reference" (equivalent to pointer to
constant), I do not know how to declare a "reference with constant
address" (which would be the equivalent of a constant pointer). And
this is your issue: the "i"
constraint in the asm
statement requires
a constant.
Here is my take with pointers:
typedef volatile uint8_t * tRegister;
constexpr tRegister timer_register(tRegister a, tRegister b)
{ return MY_TIMER==0 ? a : b; }
// The `const' below is essential.
const tRegister OCRA = timer_register(&OCR0A, &OCR2A);
asm volatile("lds r24, %0" : : "i"(OCRA));
Of course, you then have to change all your OCRA = ...;
statements
with *OCRA = ...;
, which is awkward. But note that the avr-libc
defines the SFRs with macros equivalent to
#define OCR0A (*address_of_OCR0A)
If you want this convenience, you may have to resort to using similar macros yourself.
-
1Well thanks, but that's a workaround. I actually did something similar, but limited to the asm directives, so that I could at least use my reference aliases in the C++ code. I guess this fine docrine point about the difference between a reference and a constant pointer is just another small hole in the atrociously convoluted specs of C++ the poor guys who implemented the inline assembler fell into :D.kuroi neko– kuroi neko2017年08月10日 20:00:32 +00:00Commented Aug 10, 2017 at 20:00