[Continued from Writing elaborate declarations is worth the effort (Part 1)]
An extra line of defense
A few readers have observed—rightfully so—that compilers might pack or pad structure members so that the members don't overlay the device registers as expected. As I've noted in some previous columns, most of my examples used the simplifying assumption that the device registers are full four-byte (32-bit) words aligned to an address that's a multiple of four. Alignment issues aren't relevant to most of the issues I've discussed this past year or so, and I didn't want to get distracted.
Although member alignment is a real concern when using structures, it's hardly a show-stopper. Nearly all C and C++ compilers provide facilities, albeit non-standard ones, to control structure packing. For example, with some compilers you can use a pragma directive such as:
#pragma pack(push, 4)
struct timer_type
{
~~~
};
With other compilers, you might use something like:
struct timer_type
{
~~~
} __attribute__ ((__packed__));
Whatever notation you use, you might still be uncertain that you've aligned your registers correctly. As I explained some years ago, you can use compile-time assertions to verify that your structure or class members have the necessary offsets.5 A compile-time assertion is a statement such as:
compile_time_assert(condition);
which does nothing if condition is true and generates a compile error if condition is false. In either event, it generates no code.
The new 2011 C++ Standard introduces a built-in form of compile-time assertion, but if your compiler doesn't support it, you can use the one from the open-source Boost library (www.boost.org) or roll you own using a macro as I showed in that earlier column.
Using my compile_time_assert macro, you can verify the offset of each timer register as follows:
typedef struct timer_type timer_type;
struct timer_type
{
device_register TMOD;
device_register TDATA;
device_register TCNT;
};
compile_time_assert(offsetof(timer_type,TDATA) == 0x04);
compile_time_assert(offsetof(timer_type,TCNT) == 0x08);
compile_time_assert(sizeof(timer_type) == 0x12);
You can use the same assertions with a class as well, as long as the class is a standard-layout class, something I plan to discuss in an upcoming column. No matter how you fiddle with the structure or class definition to pack or pad the members, the assertions prevent the code from compiling unless the offsets are correct.
Writing the assertions in addition to a structure or class definition is clearly more work than writing the original bare-bones macro definitions:
// timer registers
#define TMOD ((uint32_t volatile *)0xFFFF6000)
#define TDATA ((uint32_t volatile *)0xFFFF6004)
#define TCNT ((uint32_t volatile *)0xFFFF6008)
So why bother with the structure or class? The structure yields a better interface—one that's easier to use correctly. The class is even better because it's harder to use incorrectly. For each device, you define the register layout only once. If the definitions are bit more complicated, so be it. It's a one-time cost. However, you could easily wind up accessing the device from many places in your code, offering repeated opportunities to make mistakes. Chances are the initial investment in a better interface will pay you back over time.
The call is still yours
I don't mean to suggest that my preferences are always best. Hey, we're talking about embedded systems here, where one size rarely fits all. You may have to use tools and components that don't support certain techniques very well, in which case you have to adjust what you do to the realities of what you have to work with. What works well in one environment may be too cumbersome or inefficient in another.
For each problem I tackle in this column, I try to explain a variety of techniques that will solve the problem and how you might decide which choice is best for you. I often state my preferences, but mine don't have to be yours.
Endnotes:
1. Saks, Dan. "Alternative models for memory-mapped devices," Eetindia.co.in, March 2011. http://forum.eetindia.co.in/BLOG_ARTICLE_6981.HTM.
2. Saks, Dan. "Memory-mapped devices as C++ classes," Eetindia.co.in, March 2011. http://forum.eetindia.co.in/BLOG_ARTICLE_7022.HTM.
3. Meyers, Scott. "The Most Important Design Guideline?" IEEE Software, July/August 2004, p.14. www.aristeia.com/Papers/IEEE_Software_JulAug_2004_revised.htm.
4. Saks, Dan. "Compared to what?" Eetindia.co.in, March 2011. http://forum.eetindia.co.in/BLOG_ARTICLE_7029.HTM.
5. Saks, Dan. "Catching errors early with compile-time assertions," Embedded Systems Programming, July 2005, p.7. www.eetimes.com/4025549.
文章评论(0条评论)
登录后参与讨论