Advertisement

Enumerations with noncontiguous values

July 29, 2003

Dan_Saks-July 29, 2003

Enumerations with noncontiguous values pose some complications, especially if you want to iterate through them in a loop.

Several of my recent columns have been about using enumeration variables as loop counters.[1,2,3,4] Until now, I've limited the discussion to enumeraton types whose constants have contiguous values. This month, we'll explore the added complications that come from using enumerations that have noncontiguous values.

Defining noncontiguous values
In both C and C++, an enumeration definition specifies a type and a corresponding set of named constants. For example:

enum color
    {
    red, green, blue
    };
typedef enum color color;

defines a type color with three constants named for different colors. I recommend defining a typedef immediately after each enumeration definition to elevate the tag name, in this case color, to a full-fledged type name.

By default, the first enumeration constant in an enumeration definition has the value 0, and each subsequent constant has the value that is one more than the value of the previous constant. Thus, in the prior example, red's value is 0, green's is 1, and blue's is 2. This enumeration type has constants with contiguous values.

You can specify an explicit value for any enumeration constant. Any enumeration constant without an explicitly specified value has the value one more than the value of the previous constant.

Thus:

enum color
    {
    red = 1, green, blue = 4
    };

defines red's value as 1, green's as 2, and blue's as 4. The gap between the values of green and blue means the enumeration type has constants with noncontiguous values.

Blinking lights
One common use for enumerations with noncontiguous values is to represent individual bits in a hardware register.

For example, the ARM Evaluator-7T is a single board computer built around a Samsung KS32C50100 processor and a small assortment of I/O devices. The Samsung processor uses memory-mapped device addressing. That is, programs communicate with devices by reading from and writing to "special registers" that can be accessed through ordinary data pointers.

Among the output devices on the board is a row of four surface-mounted LEDs. The LEDs are numbered from 1 on the left to 4 on the right. LEDs 1 and 4 are green. LED 2 is orange and 3 is amber.

You can turn the individual LEDs on and off by setting and clearing bits in a special register called IOPDATA residing at address 0x03FF5008. In both C and C++, you can define IOPDATA as a macro:

typedef unsigned int word;

#define IOPDATA \
    (*(word volatile *)(0x3FF5008))

You can define memory-mapped objects in other ways, some more elegant than this macro, but I'll save that discussion for another day. For now, the macro is sufficient.

Table 1: Attributes of the LEDs

LED # Position Color Bit #
1 left green 7
2 inside left orange 6
3 inside right amber 5
4 right green 4

Each LED corresponds to a single bit in IOPDATA, as shown in Table 1. The Samsung processor literature numbers the bits in a word so that the least-significant (rightmost) bit is zero.

You can turn an LED on by setting its corresponding bit in IOPDATA. Thus, executing:

IOPDATA |= 0x80;

turns LED 1 on. You can turn an LED off by clearing its corresponding bit. For example:

IOPDATA &= ~0x20;

turns LED 3 off.

Better living through abstraction
Though simple, these expressions are more cryptic than they have to be. You can create more readable abstractions by encapsulating the expressions within inline functions or macros and using symbols for the masks. Then you can write calls such as:

turn_on(LED_1);
turn_off(LED_3);

Writing such self-documenting code is a Good Thing.

In C++ or C99, you can define turn_on and turn_off as inline functions:

inline
void turn_on(word n)
    {
    IOPDATA |= (n & LED_mask);
    }

inline
void turn_off(word n)
    {
    IOPDATA &= ~(n & LED_mask);
    }

In earlier C dialects, you'd define them as macros:

#define turn_on(n) \
    (IOPDATA |= ((n & LED_mask))

#define turn_off(n) \
    (IOPDATA &= ~((n & LED_mask))

Using LED_mask (defined later) ensures that the functions alter only those bits in IOPDATA that control LEDs.

Reality check: Your system might have other devices for which turn_on and turn_off are appropriate function names. In C++, you could declare these functions as members of a class or namespace to avoid name collisions. In C, you would probably adorn the names with a prefix, as in LED_turn_on and LED_turn_off.

You can define the LED names as enumeration constants. I can think of several ways to express the values for the names. One way is to define each constant with the hexadecimal value for its bitmask, as in:

enum LED_name
    {
    LED_1 = 0x80,
    LED_2 = 0x40,
    LED_3 = 0x20,
    LED_4 = 0x10
    };
typedef enum LED_name LED_name;

Another way is to define each mask as a shift-expression, as in:

enum LED_name
    {
    LED_1 = 1 << 7,
    LED_2 = 1 << 6,
    LED_3 = 1 << 5,
    LED_4 = 1 << 4
    };

or in:

enum LED_name
    {
    LED_1 = 1 << 7,
    LED_2 = LED_1 >> 1,
    LED_3 = LED_2 >> 1,
    LED_4 = LED_3 >> 1
    };

Each form has its pros and cons; I don't think there's a clear winner. In some applications, you might want to refer to LEDs by their colors instead of the numbers. In that case, you'd add enumeration constants such as:

    LED_left_green = LED_1,
    LED_red = LED_2,
    LED_amber = LED_3,
    LED_right_green = LED_4,

Then you can write calls such as:

turn_on(LED_amber);
turn_off(LED_red);

You can define the symbol LED_mask (used previously in the definitions of turn_on and turn_off) as:

    LED_mask=LED_1|LED_2|LED_3|LED_4

You might also want to define another symbol:

    LED_all = LED_mask

so that you can write calls such as:

turn_off(LED_all);

Scrolling left or right
Suppose you'd like the LEDs to blink on and off, scrolling to the right. It would be nice to be able to write this in a conventional loop, such as:

LED_name n;
for (n=LED_begin; n!=LED_end; ++n)
    {
    turn_off(LED_all);
    turn_on(n);
    /* pause for a brief time */
    }

Defining the symbols LED_begin and LED_end is pretty straightforward:

enum LED_name
    {
    ...
    LED_begin = LED_1,
    LED_end = LED_4 >> 1
    };
typedef enum LED_name LED_name;

However, the expression ++n is a problem. By default, it doesn't step through the LED constants.

The expression ++n adds one to n. We really need to advance n to the next LED constant by shifting its value to the right, as in:

for (n=LED_begin; n!=LED_end; n>>=1)

This works, but it's rather unconventional. It's easy to make a mistake, such as reversing the direction of the shift, which would produce an infinite loop.

In C++, ++n doesn't even compile because the built-in ++ operator doesn't apply to enumeration values. Fortunately, you can define operator++ for any enumeration type. For LED_name, an appropriate definition for the prefix operator++ is:

inline
LED_name &operator++(LED_name &n)
    {
    return n = LED_name(n >> 1);
    }

Using this ++ operator, you can write a loop that advances through the LED names in the conventional way, as:

for (n=LED_begin; n!=LED_end; ++n)

Behind the scenes, ++n "increments" n by shifting its underlying value one bit to the right.

Some of you might think that defining a ++ operator so that it actually performs a shift is a pretty sinister thing to do. After all, ++ has always meant "add one," not "shift right by one." However, the C++ Standard Library takes similar liberties with the ++ operator applied to types known as iterators. It's now a well-established convention in C++ that ++n means simply "advance n to the next value in sequence." Defining the ++ operator for LED_name as I have is a logical extension.

The corresponding prefix operator - - is:

inline
LED_name &operator--(LED_name &n)
    {
    return n = LED_name(n << 1);
    }

Using this - - operator, you can get the LEDs to blink on and off, scrolling to the left:

LED_name n;
for (n=LED_end; n!=LED_begin;)
    {
    --n;
    turn_off(LED_all);
    turn_on(n);
    /* pause for a brief time */
    }

This loop uses Koenig and Moo's canonical form for loops that count down.[4]

If you're programming in C rather than C++, you can still apply this style. You can implement an increment operator; you just can't call it operator ++ or pass its argument by reference. Your best bet is to implement the "increment operator" as a macro, such as:

#define LED_pre_incr(n) (n >>= 1)

The corresponding "decrement operator" looks like:

#define LED_pre_decr(n) (n <<= 1)

More on the horizon
The technique I've just described should work for almost any enumeration type with noncontiguous values, provided the values follow a pattern that you can generate from a fairly simple formula. In my example, for instance, the LED_name constant values are all powers of two—a very common pattern.

If the enumeration values don't follow such a pattern, writing loops that step through those values gets harder, but not impossible. I will present solutions to this problem in the not-too-distant future.

Dan Saks is the president of Saks & Associates, a C/C++ training and consulting company. You can write to him at dsaks@wittenberg.edu.

References

  1. Saks, Dan. "Enumerations as Counters," Embedded Systems Programming, December 2002, p.36.
  2. Saks, Dan. "Well-Behaved Enumerations," Embedded Systems Programming, April 2003, p.41.
  3. Saks, Dan. "Enumerations Q & A," Embedded Systems Programming, May 2003, p.27.
  4. Saks, Dan. "More on Enumerations," Embedded Systems Programming, July 2003, p.38.

Loading comments...