Representing Memory-Mapped Devices As Objects: Dan Saks Saks & Associates
Representing Memory-Mapped Devices As Objects: Dan Saks Saks & Associates
Representing Memory-Mapped Devices As Objects: Dan Saks Saks & Associates
Representing Memory-Mapped
Devices as Objects
Dan Saks
Saks & Associates
www.dansaks.com
Abstract
Programmers who develop embedded systems often have to
assert direct control over hardware resources such as memory-
mapped i/o registers. The longstanding practice has been to use
concerns over performance as an excuse for writing some pretty
nasty code -- heavy in macros, casts and pointer arithmetic. Such
code is often hard to get working and hard to maintain. It need not
be so.
This talk shows you how to model memory-mapped devices as
C++ objects that are more robust, maintainable, and at times, even
more efficient than they would otherwise be.
Common Practice
The Alternative
Device Registers
Memory-Mapped Devices
interrupt vectors
physical memory (RAM, ROM, Flash)
memory-mapped registers
Either way, you can use the pointer to manipulate the register:
uint32_t &UTXBUF0
= *reinterpret_cast<uint32_t *>(0x03FFD00C);
Modeling Devices
Using Structures
struct UART {
dev_reg ULCON;
dev_reg UCON;
dev_reg USTAT;
dev_reg UTXBUF;
dev_reg URXBUF;
dev_reg UBRDIV;
};
Using Classes
class UART {
public:
void put(int c);
~~~
private:
dev_reg ULCON;
dev_reg UCON;
~~~
};
Unwelcome Optimizations
Device register accesses (reads and writes) may have side effects.
class UART {
~~~
dev_reg ULCON;
dev_reg UCON;
dev_reg volatile USTAT;
dev_reg volatile UTXBUF;
dev_reg volatile URXBUF;
dev_reg UBRDIV;
};
class UART {
public:
void put(int c);
int get();
~~~
private:
dev_reg ULCON;
dev_reg UCON;
~~~
};
UART isn’t volatile, but every non-static UART data member is.
Or here:
How can you be sure the UART class has this layout?
Standard-Layout Types
Standard-Layout Classes
Standard-Layout Classes
class timer {
public:
~~~
void enable();
virtual value_type get(); // not standard layout
~~~
};
Standard-Layout Classes
class widget {
public:
dev_reg status;
protected: // not standard layout
dev_reg control;
dev_reg data;
};
Standard-Layout Classes
struct IOP {
dev_reg IOPMOD;
dev_reg IOPCON; // standard-layout
};
No Need to Guess
#include <type_traits>
class timer {
~~~
};
static_assert(
is_standard_layout<timer>::value,
"timer isn't standard layout"
);
Layout Guarantees
Padding
Packing
#pragma pack(push, 4)
class UART {
~~~
};
#pragma pack(pop)
class UART {
~~~
};
static_assert(sizeof(UART) == 6 * sizeof(dev_reg));
class UART {
public:
enum baud_rate {
BR_9600 = 162 << 4, BR_19200 = 80 << 4, ~~~
};
~~~~
}
Using a Constructor
class UART {
public:
UART(baud_rate br = BR_9600) {
disable();
set_speed(br);
enable();
}
~~~~
};
Copyright © 2015 by Dan Saks 44
Constructors
Constructors
Constructors
The declaration locates the UART object, but doesn’t initialize it.
Class-Specific New
Class-Specific New
class UART {
public:
void *operator new(size_t) {
return reinterpret_cast<void *>(0x3FFD000);
}
~~~
};
Class-Specific New
It uses the UART’s operator new to “place” the UART object in its
memory-mapped location.
Cool.
Class-Specific New
com0.put(c);
class UART {
public:
void *operator new(size_t) {
return reinterpret_cast<void *, 0x3FFD000);
}
~~~
};
class UART {
public:
void *operator new(size_t, int n) {
return reinterpret_cast<void *>(
0x3FFD000 + n * 0x1000
);
~~~
};
Preventing Errors
Preventing Errors
class UART {
public:
enum uart_number { zero, one, two, three };
void *operator new(size_t, uart_number n) {
return reinterpret_cast<void *>(
0x3FFD00 + n * 0x1000);
}
~~~
};
Good Thing.
Summary