Question

I am programming the AT91SAM7X256 from Atmel and I am a little confused by the macros that define registers and how to use them.

I use lines as the following to set a bit in a register:

AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_PWMC;

now when I look at this after the macro expansion it looks something like this:

((AT91PS_PMC) 0xFFFFFC00)->PMC_PCER = 1 << (10);

Definitions that I could find in the file AT91SAM7X256.h

At the top of the file:

#ifndef __ASSEMBLY__
typedef volatile unsigned int AT91_REG;// Hardware register definition
#define AT91_CAST(a) (a)
#else
#define AT91_CAST(a)
#endif

and then I find the defintion of AT91C_BASE_PMC further down

...
#define AT91C_BASE_CKGR      (AT91_CAST(AT91PS_CKGR)    0xFFFFFC20) // (CKGR) Base Address
#define AT91C_BASE_PMC       (AT91_CAST(AT91PS_PMC)     0xFFFFFC00) // (PMC) Base Address
#define AT91C_BASE_RSTC      (AT91_CAST(AT91PS_RSTC)    0xFFFFFD00) // (RSTC) Base Address
...

So it seems like that this get expanded pretty straightforward. Similarly the macro AT91C_ID_PWMC is defined as

...
#define AT91C_ID_TWI    ( 9) // Two-Wire Interface
#define AT91C_ID_PWMC   (10) // PWM Controller
#define AT91C_ID_UDP    (11) // USB Device Port
...

But how does the dereferencing ((AT91PS_PMC) 0xFFFFFC00)->PMC_PCER work? I can find a few definitions for PMC_PCER.

#ifndef __ASSEMBLY__
typedef struct _AT91S_SYS {
    ...
    AT91_REG     Reserved20[1];     // 
    AT91_REG     PMC_PCER;  // Peripheral Clock Enable Register
    AT91_REG     PMC_PCDR;  // Peripheral Clock Disable Register
    ...
} AT91S_SYS, *AT91PS_SYS;
#else

#endif

and further down again.

#ifndef __ASSEMBLY__
typedef struct _AT91S_PMC {
    ...
    AT91_REG     Reserved0[1];  // 
    AT91_REG     PMC_PCER;  // Peripheral Clock Enable Register
    AT91_REG     PMC_PCDR;  // Peripheral Clock Disable Register
    ...
} AT91S_PMC, *AT91PS_PMC;
#else
...
#define PMC_SCSR        (AT91_CAST(AT91_REG *)  0x00000008) // (PMC_SCSR) System Clock Status Register
#define PMC_PCER        (AT91_CAST(AT91_REG *)  0x00000010) // (PMC_PCER) Peripheral Clock Enable Register
#define PMC_PCDR        (AT91_CAST(AT91_REG *)  0x00000014) // (PMC_PCDR) Peripheral Clock Disable Register
...
#endif

So my questions are

  • How do those macros work?
  • I have the impression, that I can set a single bit with the expression AT91C_BASE_PMC->PMC_PCER = 1 << AT91C_ID_PWMC; ? Is this true?
  • How would I unset that bit?
  • Why is it done this complicated and not by simply setting the values directly?
Was it helpful?

Solution

It is not portable code, but it is illustrative of the power of C and its ability to interface with memory-mapped I/O devices.

  1. The cast ((AT91PS_PMC) 0xFFFFFC00) means that the memory address 0xFFFFFC00 will be treated as a pointer to a structure of the type AT91PS_PMC. Within that structure, there is the Peripheral Clock Enable Register, the PMC_PCER field, at some suitable offset from the address 0xFFFFFC00.

    Thus, the assignment arranges to write 1024 (1 << 10) to the PMC_PCER register, assuming that the base address 0xFFFFFC00 is correct.

  2. This sets the register to a value containing a single set bit. It is not the same as what most people would mean by 'setting a single bit'; that would be done by a line such as:

    AT91C_BASE_PMC->PMC_PCER |= 1 << AT91C_ID_PWMC;
    

    Note that this reads the current value of the PMC_PCER, modifies it, and writes it back. Whether it is safe to read and then write depends on the details of the chip.

  3. There are at least two ways to do that:

    AT91C_BASE_PMC->PMC_PCER = 0;
    AT91C_BASE_PMC->PMC_PCER &= ~(1 << AT91C_ID_PWMC);
    

    The first assumes that only a single bit was set, so setting the value to 0 unsets that single bit. The second reads the current value of the PMC_PCER register, does a bitwise AND with a value that has a 1-bit everywhere except the AT91C_ID_PWMC bit (thus zeroing just that bit), and writes the value back.

  4. Why is it done like this? To collect all the platform-specific details together and to avoid having to write each address manually, and so that the board can be revised later and code just recompiled instead of having to be rewritten.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top