Advertisement

Judgment calls

December 06, 2011

Dan_Saks-December 06, 2011

Writing better hardware interfaces may require writing fairly elaborate declarations. It's probably worth the effort.

About a year and a half ago, I wrote a column explaining some common techniques for representing and manipulating memory-mapped devices in C.1 I followed that with another column explaining an alternative using classes in C++.2 Those initial articles left a lot of details unresolved. Most of the columns I've written since then have been about filling in those missing details.

Nearly all of the techniques I've presented are viable in that real programmers use them in real projects and find the resulting machine code to be adequately fast and compact. Nonetheless, I've suggested that some alternatives are generally better than others, usually on the basis that the better ones yield interfaces that are easier to use correctly and harder to use incorrectly.3

Sometimes writing better interfaces requires writing fairly elaborate declarations to model the hardware. Some programmers see such declarations as not worth the bother and opt for something simpler. This month, I'll explain why I don't think that's a winning approach.

A classic approach

C programmers often define symbols for device register addresses as clusters of related macros. For example:


// timer registers
#define TMOD ((uint32_t volatile *)0xFFFF6000)
#define TDATA ((uint32_t volatile *)0xFFFF6004)
#define TCNT ((uint32_t volatile *)0xFFFF6008)
defines TMOD, TDATA, and TCNT as the addresses of the timer mode register, the timer data register, and the timer count register, respectively, for some hypothetical programmable timer. These macros might be accompanied by additional constants for manipulating the registers, such as:

#define TE 0x01
which defines TE as a mask for setting and clearing the timer enable bit in the TMOD register. Together, all these macros represent the software interface to the timer.

Using this interface, you can disable the timer using an expression such as:

*TMOD &= ~TE;
Unfortunately, when you're putting together thousands of lines of code controlling many devices, you can easily forget to invert the mask, as in:

*TMOD &= TE;
or accidentally name the wrong register, as in:

*TDATA &= ~TE;
If you make such mistakes, the code will compile nonetheless, and you'll get to experience the joy of debugging it.

A better interface
A structure with accompanying functions provides a better interface for the timer than a collection of macros. For example, you can define the timer as:

typedef uint32_t volatile device_register;

typedef struct timer_type timer_type;
struct timer_type
{
device_register TMOD;
device_register TDATA;
device_register TCNT;
};

#define TE 0x01
along with functions that provide basic operations for programming a timer, such as:

inline void timer_disable(timer_type *t)
{
t->TMOD &= ~TE;
}

inline void timer_enable(timer_type *t);
{
t->TMOD |= TE;
}
You can map the timer_type into memory in a variety of ways.4 If you use a pointer declaration, such as:

#define the_timer ((timer_type *)0xFFFF6000)
you can disable the timer very simply, by calling:

timer_disable(the_timer);
This functional interface is better than a collection of macros representing register addresses and masks because it's easier to use correctly. Moreover, this interface works well whether there are many timers or just one.

An even better interface
A C++ class definition for the timer might look like:

class timer_type
{
public:
enum { TICKS_PER_SEC = 50000000 };
typedef uint32_t count_type;
void disable();
void enable();
void set(count_type c);
count_type get() const;
private:
enum { TE = 0x01 };
device_register TMOD;
device_register TDATA;
device_register TCNT;
};
A well-written C++ class provides an even better interface than a C structure because the class is harder to use incorrectly. (Further enhancements to this class would make it even harder to use incorrectly.) With a structure, you can easily bypass the timer functions and do something to a timer register that you'll probably regret. With a class, you have to go out of your way to access the device registers in ways other than those permitted by the public class members.

< Previous
Page 1 of 2
Next >

Loading comments...