What is the modern C++ way of having a constexpr const pointer to a volatile memory location for embedded applications?
Asked Answered
T

1

10

In building libraries for controlling hardware on embedded microprocessors, a common task is manipulating bits at specific memory locations for controlling hardware features.

In AVR processors, Atmel (now Microchip) provides macros that expand to something like this:

#define PORTA (*(volatile uint8_t *)(0x25))

Which enables things like:

PORTA |= 1;

Now in C++11 (and newer), it is desirable to replace almost any usage of #define with constexpr.

In older versions of the GCC C++ compiler (4.9.2), the following compiled:

#include <avr/io.h>
constexpr volatile uint8_t *const PortA = &PORTA;

In version 8.2.0, the above does not compile and gives errors:

error: 'reinterpret_cast<volatile uint8_t* {aka volatile unsigned char*}>(37)' is not a constant expression

I'm not looking for explanations of why you cannot use reinterpret_cast inside a constexpr context or why integer to pointer conversion is illegal.

What is the correct way to have a constexpr pointer to volatile memory in modern C++?

I've seen suggestions of storing the memory address of PORTA in a constexpr uintptr_t and then reinterprect_casting that to volatile uint8_t * const at runtime for bit manipulation.

For instance, this works and even compiles to a single sbi instruction in avr-gcc as expected.

#include <stdint.h>
constexpr uintptr_t PortA = 0x25;
void set() { *((volatile uint8_t *)(PortA)) |= 1; }

However it takes a decent amount of ugly boilerplate to use PortA as the pointer it is intended to be.

This also has the problem that it seem to be impossible to use the PORTA macro directly. We're instead forced to hard-code the memory address 0x25 which breaks certain desirable portability features.

It feels like I'm missing something obvious buy my searches have not yielded anything fruitful.

For instance, this feels like an "address constant expression", but that seems to relate to referring to statically allocated const values like which is not quite what I want.

const char str[] = "FooBar";
constexpr const char * x = str + 2;
Torsibility answered 2/2, 2019 at 0:33 Comment(6)
I couldn't reproduce your error message error: reinterpret_cast from integer to pointer. My compiler (g++ 7.3.0) spits out a different message - error: value ‘37’ of type ‘volatile uint8_t* {aka volatile unsigned char*}’ is not a constant expression. Which is also very strange...Waylon
Would volatile uint8_t& PortA = PORTA; do what you want?Ventose
@IgorTandetnik That does not work for a constexpr. I get the same errors a HEKTO.Torsibility
for a pointer to non-const variable to be constexpr the pointer has to be declared inside of function.Stationer
@CameronTacklind, have you checked kvasir? Take a look at this: github.com/kvasir-io/Kvasir/blob/master/Lib/Chip/CM4/Nordic/… They do the constexpr number + runtime reinterpret_cast trick you ruled out, but since they auto-generate these headers from SVD files, there isn't really a maintenance burden.Anthropolatry
@FatihBAKIR yeah, it works, but only if you're starting with a literal address. That's fine if you're using those directly but, for now at least, I'm trying to lean on the device specific Atmel provided header files. This also has option implications for other similar embedded cores.Torsibility
M
0

You can't create a constexpr pointer initialized by a non-constant expression. However, you can create a static const pointer:

static uint8_t volatile* const PortA = &PORTA;

Or, even better, a static reference:

static uint8_t volatile& PortA = PORTA;
Minard answered 16/3, 2022 at 17:51 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.