My solution looks like this:
template < class Name = int, typename T = std::uint32_t, T* Value = nullptr > class mocked_register { public: static void set( T v ) { *address() = v; } static T get() { return *address(); } static volatile T* address() { static T internal_; return Value ? Value : &internal_; } };
Where the name should be any type that makes the instance different from other instances. When defining multiple types, you can use the previous defined types as a name:
typedef mocked_register<> START; typedef mocked_register< START > STOP; typedef mocked_register< STOP > COUNT;
For testing, the type saves the old 'semi' variable to preserve the value of the registers. For arm architecture, I have some cases where it is useful to use an array of registers. In this case, the Value parameter can be used to provide an external array:
std::uint32_t capture_regs[ 4 ]; typedef mocked_register< SHUTDOWN, std::uint32_t, capture_regs > CAPTURE;
For the working part, the template is much simpler:
template < std::uint32_t Register > struct addressed_register { static void set( std::uint32_t value ) { *reinterpret_cast< volatile std::uint32_t* >( Register ) = value; } static std::uint32_t get() { return *reinterpret_cast< volatile std::uint32_t* >( Register ); } };
In both cases (testing and production), the device abstraction takes a set of template parameters and uses them as registers:
template < class OUTSET, class OUTCLEAR, class DIRSET, class DIRCLR, class PIN_CNF, std::uint8_t Nr > struct pin { static void init_as_input() { DIRCLR::set( 1 << Nr ); } };
More registers, such as syntax, could be added if assignment and implicit conversion to T were implemented (but I'm not a big fan of this idea):
START start; COUNT count; start = 1; std::uint32_t c = count;