1

Here is the code that is declaring a lot of constants:

#define TD_DATA_PID               0  /* control pipe state for td_sie_done */
#define TD_STATUS_PID             2  /* control pipe state for td_sie_done */
#define TD_DONE_PID               4  /* control pipe state for td_sie_done */
#define TD_DELETE_PID           0xee /* pipe command for td_sie_done */
#define TD_SETUP_PID            0xd  /* also used for control pipe state for tf_sie_done */
#define TD_IN_PID               0x9
#define TD_OUT_PID              0x1
#define TD_SOF_PID              0x5
#define TD_PREAMBLE_PID         0xc
#define TD_NAK_PID              0xa
#define TD_STALL_PID            0xe
#define TD_DATA0_PID            0x3
#define TD_DATA1_PID            0xb

#define TD_NORMAL_TIMEOUT         0
#define TD_LONG_TIMEOUT           1

enum TD_CTRL_BITS
{
    TD_CTRL_ARM         = 0x01,
    TD_CTRL_ISOCH       = 0x10,
    TD_CTRL_SYNC_SOF    = 0x20,
    TD_CTRL_DTOGGLE     = 0x40,
    TD_CTRL_PREAMBLE    = 0x80
};


enum TD_STATUS_BITS
{
    TD_STATUS_ACK       = 0x01,
    TD_STATUS_ERROR     = 0x02,
    TD_STATUS_TIMEOUT   = 0x04,
    TD_STATUS_SEQ       = 0x08,
    TD_STATUS_SETUP     = 0x10,
    TD_STATUS_OVERFLOW  = 0x20,
    TD_STATUS_NAK       = 0x40,
    TD_STATUS_STALL     = 0x80
};

This code is for a USB OTG controller, low level embedded systems stuff and thus uses C rather than C++.

All the #define valus are used to assign value to a single one byte variable. The variable is packet ID, thus all the #define constants end with _PID. That is clear.

However, the part of enums is not clear.

Basically, there is a single byte long register that contains TD_CONTROL_BITS and may want to read/write it. Each bit effects behaviour of the hardware. The values given to each constant inside the enum is the bit that corresponds to that function e.g ARM bit is bit 0 and isochronous transfer bit is bit 2 e.t.c. This register is read and also written to.

Similar applies to TD_STATUS_BITS also. Each bit in the byte long status register provides independant information about the status of transfer. Thus, the constants TD_STATUS_ACK, TD_STATUS_ERROR e.t.c can be used as bit masks to read the status register. This register is only read back as it is updated by external hardware and only a value of 0 is written to reset it.

Now my question is, why not use #define constants for everything? What benefit does enum bring in this case? Also, in case of TD_CTRL_BITS we may want to logically OR bits together e.g TD_CTRL_ARM | TD_CTRL_DTOGGLE. I do not think that enums allow that.

quantum231
  • 2,420
  • 3
  • 31
  • 53
  • The crappy issue with C (compared to, say, C++) is that [enums are not really anything more than constants](http://stackoverflow.com/a/4713514/69809). Meaning that no C compiler (that I am aware of) will even slightly complain if you pass a wrong enum value to a function expecting an enum of a different type. So the code will be slightly more "self-documenting" (i.e. you can quickly find the list of intended parameters for a function), but nothing will prevent you from using unintended parameters. – vgru Feb 15 '17 at 11:56
  • Can't I declare a variable of enum type (including as function parameter) so it does not accept values from another enum? – quantum231 Feb 15 '17 at 12:15
  • 1
    @Groo Easily solved. In case type safety matters, then instead of `void func (something x)` which isn't type safe, you should write `void func (something* x)`. Now if you try to pass a `something_else*` to this function, you should get a compiler error. C doesn't have much in the way of type safety for primitive data types, but more so when it comes to pointers. – Lundin Feb 15 '17 at 12:57
  • Are you saying that enum is not type safe and gets treated like any other integer bit pointer to enum types are type safe? – quantum231 Feb 15 '17 at 13:05
  • @quantum231: you will usually get warnings when you try passing wider integer types (or when you mismatch signed/unsigned types). But you will not even get an informational message when you pass a wrong enum type, or assign a different enum type to a variable (provided that the file extension is .c, of course; C++ will complain). – vgru Feb 15 '17 at 13:55
  • @Lundin: I just tried that in MSVC compiler, and it didn't complain. I.e. defined `void test(enum ENUM_A *ctrl) { }`, and then called `test((enum ENUM_B*)&stuff);`. – vgru Feb 15 '17 at 13:57
  • @Groo You can't use MSVC, you have to use a standard conforming C compiler. For example gcc or clang. Gives error/warning: "passing argument 1 of 'foo' from incompatible pointer type. note: expected 'enum a *' but argument is of type 'enum b *' " – Lundin Feb 15 '17 at 14:06
  • @Lundin: that's a good point, thanks. – vgru Feb 15 '17 at 14:18
  • See [_Symbolic Constants_](http://www.embedded.com/electronics-blogs/programming-pointers/4023858/Symbolic-Constants) and other articles by Dan Saks on this subject. – kkrambo Feb 15 '17 at 15:26

2 Answers2

2

This is a pretty standard coding convention and you'll see it many times in your career.

Using an enum is a way of associating a bunch of flags so that you can tell that they belong together. From the code it is clear which are control flags and which are status flags because it says so in the enum label. Some languages even enforce the flag type so that only control flags can be used with control parameters and ditto with status flags. C# does that, I don't think that C does, C++ might.

And more readable code is a good thing because it means fewer bugs and less maintenance overhead

Ray Fischer
  • 936
  • 7
  • 9
  • there are many other registers, here I have only put the control and status registers. Do enums implicitly convert to integer types so one can do logical operations on them? – quantum231 Feb 15 '17 at 02:53
  • In C enum constants are int values so no conversion is needed. – Ray Fischer Feb 15 '17 at 03:05
2

There is no obvious difference in this case. You can either use enumeration constants or defines, there's no obvious right or wrong in this specific case.

Overall though, enumeration constants are dangerous for hardware-related programming, since they are required to always be of type int, which is always signed. You should avoid signed operands when doing things like bitwise arithmetic.

The same problem exists with your defined integer constant such as 0xee - they are also of signed int type. But with defines you have the option to suffix the integer constants, for example 0xeeu. Now you get unsigned int which is much more convenient and safe.

Some examples of where signed int constants would cause bugs:

  • if(~TD_CTRL_ARM > 0) Bug, the operand has turned negative.
  • TD_STATUS_STALL << 24 Bug, assuming 32 bit int. The code shifts data into the sign bits of an int, invoking undefined behavior.
  • ~TD_CTRL_ARM >> n Bug, you may end up with either arithmetic shifts or logical shifts. Code is non-portable and may behave in unexpected ways.
  • uint8_t data; ... if((~TD_CTRL_ARM & data) > 0). Bug, the signed left operand means that the implicitly promoted data will remain a signed type. The result may turn out negative even though the right operand was explicitly declared as unsigned. If the left operand had been unsigned int, the right operand would have become unsigned too and the code would have worked as expected.

And so on.


Advise/good practice:

  • Only use signed types in embedded systems if they are explicitly needed for math etc.
  • Never use enum for any form of bit representations or bitwise operations.
  • Always suffix integer constants with U/u.
  • Be explicit about type size and signedness: use stdint.h whenever possible.
  • Be very careful whenever using small integer types in expressions. unsigned char, bool, unsigned short, uint8_t etc. They might get a change in signedness because of integer promotion.
  • Actually learn and understand how implicit type promotions work in C. A frightening amount of C programmers don't know this. All C programmers should know how integer promotions, the usual arithmetic conversions (balancing) and type conversion during assignment work. If you don't know about these, you will write subtle and often severe bugs, repeatedly.
  • Adopt a professional coding standard which is a safe subset of C, such as MISRA-C, and use static code analysis. Then you'll get all of the above for free.
Lundin
  • 195,001
  • 40
  • 254
  • 396
  • Signed or unsigned matters with shift outstrips operators << and >>. However, they should not matter with other operators like &, |, ~ e.t c. Am I missing something here? – quantum231 Feb 15 '17 at 12:21
  • Only rarely do I see #define constant having U or L at end. Why not just declare a constant using the cost word that is type safe? – quantum231 Feb 15 '17 at 12:22
  • @quantum231 It matters as soon as you risk overwriting the sign bit, which all bitwise operators can potentially do. `const` variables are fine too and can be used instead of `#define` in most places. – Lundin Feb 15 '17 at 12:47
  • I understand fully what you mean. If I do a shift of this value to put it into a bigger int, it shall extend the sign in case of the right shift and sign bit must not get corrupted. But... here is the thing. The control and status bytes do not represent a numerical quantity, they are bit fields. The actual per bit value matters. Thus, shifting would not cause any corruption as such. I still agree that I must use unsigned integers here. Therefore, in any case I assume that since no numerical quantity is represented, rather discrete binary states by each bit, binary operations shouldn't matter – quantum231 Feb 15 '17 at 13:03
  • Shifting flags and using negation instead of not are programming errors. The signed/unsigned issue is a red herring when it comes to control and status flags because you should never be shifting them and you should never use negation – Ray Fischer Feb 15 '17 at 23:28