1

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?
VE7JRO
  • 2,515
  • 19
  • 27
  • 29
kuroi neko
  • 183
  • 6

1 Answers1

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.

Edgar Bonet
  • 45,094
  • 4
  • 42
  • 81