Monday, 8 August 2011

Mapping the Absolute Addresses of Registers from a C Header

Why am I doing this, you may ask? Doesn't the microcontroller manufacturer provide the absolute addresses of the registers? Well in this case, no they don't, and I need them to complete the plugin for the Keil ┬ÁVision debugger which will make them visible and editable via human readable names.

There are a number of ways of mapping hardware into C but due to the limitations of the const keyword [did you know you can cast the const away? add it to the list of things in C like = actually being assign, declare a pointer with * and then deference it's content with *, etc., etc.] most microcontroller manufacturers end up using #define statements as they can be made to result in fixed numeric constants that can then be optimized correctly in all situations.

[For a quick counter example create a function that accesses a global const pointer, call it and then look at the ASM. Does that look optimal to you? But don't blame the compiler - it can't assume you haven't messed with the const pointer with casts! A good discussion is here.]

Anyway the normal way is to begin with several layers of #defines. The reason is for maintainability and portability. Typically the registers for a microcontroller peripheral are created at a fixed offset from a base and typically they will want to use the same peripheral in several micros. So this is how they arrange things for the USB module in the Fujitsu MB9BF506R, an ARM Cortex M3 based microcontroller:

#define FM3_PERIPH_BASE    (0x40000000UL)

...

#define FM3_USB0_BASE      (FM3_PERIPH_BASE + 0x42100UL)

So the USB peripheral is actually to be found starting at address 0x40042100UL. However the actual device registers are laid out by a C struct. The struct creates the offsets for the individual registers internally and maps them to the human readable names.

Structs have several rules imposed on them by the C standard and also have a few gotchas so please treat these header files with great care when used with a different compiler UNTIL you have verified it. The rules are:
  1. The named elements of a struct will be in the same order as declared in C .
  2. Suitable padding may be included between elements to speed access to the data.
For more information refer to this article: http://www.eventhelix.com/RealtimeMantra/ByteAlignmentAndOrdering.htm.
    [If you don't want suitable padding to be added then consider using the pack attribute, e.g. in gcc #pragma pack. This can have terrible performance consequences so as always, know what you are trying to achieve.]

    Here is a fragment of C from the mb9bf506r.h header file which creates the C symbols for the appropriate registers:

    typedef struct
    {
      union {
        union {
          __IO uint16_t HCNT;
          stc_usb_hcnt_field_t HCNT_f;
        };
        struct {
          union {
            __IO  uint8_t HCNT0;
            stc_usb_hcnt0_field_t HCNT0_f;
          };
          union {
            __IO  uint8_t HCNT1;
            stc_usb_hcnt1_field_t HCNT1_f;
          };
        };
      };
    ...
    

    So following the rules of struct's in C we have the first element being an unsigned, 16 bit long register called HCNT at the first address of this struct, 0x40042100UL. Note the use of unions to allow the same address in memory to be accessed several different ways. In this case it is enabling HCNT to be accessed as a 16 bit wide register and as two separate 8 bit ones called HCNT0 and HCNT1.

    Lets look at the same piece but with the next register's definition included:

    ...
      union {
        union {
          __IO uint16_t HCNT;
          stc_usb_hcnt_field_t HCNT_f;
        };
        struct {
          union {
            __IO  uint8_t HCNT0;
            stc_usb_hcnt0_field_t HCNT0_f;
          };
          union {
            __IO  uint8_t HCNT1;
            stc_usb_hcnt1_field_t HCNT1_f;
          };
        };
      };
            uint8_t RESERVED0[2];
      union {
        __IO  uint8_t HIRQ;
        stc_usb_hirq_field_t HIRQ_f;
      };
      union {
        __IO  uint8_t HERR;
        stc_usb_herr_field_t HERR_f;
      };
            uint8_t RESERVED1[2];
    ...
    

    The line of real interest is uint8_t RESERVED0[2]; between the two 16 bit registers. ARM devices' memory map is byte addressing, i.e. each byte attracts a unique address number. So the 16 bit variable is sitting across two 8 bit numbers (which the union allows individual access to). However the main memory of an ARM system is typically organized on 32 bit words, i.e. two 16 bit words and four 8 bit words. The uint8_t RESERVED0[2]; array of two 8 bit words 'pushes' the next register (the 8 bit HIRQ) to the start of the next 32 bit word. This means that the HCNT is at 0x40042100UL while the HIRQ is at 0x40042104UL, 4 bytes offset from the USB peripheral base.