ccastingc-preprocessorcortex-mmemory-mapping

Casting troubles when using bit-banding macros with a pre-cast address on Cortex-M3


TL;DR:

  1. Why isn't (unsigned long)(0x400253FC) equivalent to (unsigned long)((*((volatile unsigned long *)0x400253FC)))?
  2. How can I make a macro which works with the former work with the latter?

Background Information

Environment

I'm working with an ARM Cortex-M3 processor, the LM3S6965 by TI, with their StellarisWare (free download, export controlled) definitions. I'm using gcc version 4.6.1 (Sourcery CodeBench Lite 2011.09-69). Stellaris provides definitions for some 5,000 registers and memory addresses in "inc/lm3s6965.h", and I really don't want to redo all of those. However, they seem to be incompatible with a macro I want to write.

Bit Banding

On the ARM Cortex-M3, a portion of memory is aliased with one 32-bit word per bit of the peripheral and RAM memory space. Setting the memory at address 0x42000000 to 0x00000001 will set the first bit of the memory at address 0x40000000 to 1, but not affect the rest of the word. To change bit 2, change the word at 0x42000004 to 1. That's a neat feature, and extremely useful. According to the ARM Technical Reference Manual, the algorithm to compute the address is:

bit_word_offset = (byte_offset x 32) + (bit_number × 4)
bit_word_addr = bit_band_base + bit_word_offset

where:

Implementation of Bit Banding

The "inc/hw_types.h" file includes the following macro which implements this algorithm. To be clear, it implements it for a word-based model which accepts 4-byte-aligned words and 0-31-bit offsets, but the resulting address is equivalent:

#define HWREGBITB(x, b)                                               \
    HWREGB(((unsigned long)(x) & 0xF0000000) | 0x02000000 |               \
           (((unsigned long)(x) & 0x000FFFFF) << 5) | ((b) << 2))

This algorithm takes the base which is either in SRAM at 0x20000000 or the peripheral memory space at 0x40000000) and ORs it with 0x02000000, adding the bit band base offset. Then, it multiples the offset from the base by 32 (equivalent to a five-position left shift) and adds the bit number.

The referenced HWREG simply performs the requisite cast for writing to a given location in memory:

#define HWREG(x)                                                              \
    (*((volatile unsigned long *)(x)))

This works quite nicely with assignments like

HWREGBITW(0x400253FC, 0) = 1;

where 0x400253FC is a magic number for a memory-mapped peripheral and I want to set bit 0 of this peripheral to 1. The above code computes (at compile-time, of course) the bit offset and sets that word to 1.

What doesn't work

Unfortunately, the aforememntioned definitions in "inc/lm3s6965.h" already perform the cast done by HWREG. I want to avoid magic numbers and instead use provided definitions like

#define GPIO_PORTF_DATA_R       (*((volatile unsigned long *)0x400253FC))

An attempt to paste this into HWREGBITW causes the macro to no longer work, as the cast interferes:

HWREGBITW(GPIO_PORTF_DATA_R, 0) = 1;

The preprocessor generates the following mess (indentation added):

(*((volatile unsigned long *)
    ((((unsigned long)((*((volatile unsigned long *)0x400253FC)))) & 0xF0000000)
    | 0x02000000 |
    ((((unsigned long)((*((volatile unsigned long *)0x400253FC)))) & 0x000FFFFF) << 5)
    | ((0) << 2))
)) = 1;

Note the two instances of

(((unsigned long)((*((volatile unsigned long *)0x400253FC)))))

I believe that these extra casts are what is causing my process to fail. The following result of preprocessing HWREGBITW(0x400253FC, 0) = 1; does work, supporting my assertion:

(*((volatile unsigned long *)
    ((((unsigned long)(0x400253FC)) & 0xF0000000) 
    | 0x02000000 |
    ((((unsigned long)(0x400253FC)) & 0x000FFFFF) << 5)
    | ((0) << 2))
)) = 1;

The (type) cast operator has right-to-left precedence, so the last cast should apply and an unsigned long used for the bitwise arithmetic (which should then work correctly). There's nothing implicit anywhere, no float to pointer conversions, no precision/range changes...the left-most cast should simply nullify the casts to the right.

My question (finally...)

  1. Why isn't (unsigned long)(0x400253FC) equivalent to (unsigned long)((*((volatile unsigned long *)0x400253FC)))?
  2. How can I make the existing HWREGBITW macro work? Or, how can a macro be written to do the same task but not fail when given an argument with a pre-existing cast?

Solution

  • 1- Why isn't (unsigned long)(0x400253FC) equivalent to (unsigned long)((*((volatile unsigned long *)0x400253FC)))?

    The former is an integer literal and its value is 0x400253FCul while the latter is the unsigned long value stored in the (memory or GPIO) address 0x400253FC

    2- How can I make the existing HWREGBITW macro work? Or, how can a macro be written to do the same task but not fail when given an argument with a pre-existing cast?

    Use HWREGBITW(&GPIO_PORTF_DATA_R, 0) = 1; instead.