Gaurav K
Gaurav K

Reputation: 2975

Embedded C: Registers Access

Suppose we want to write at address say 0xc000, we can define a macro in C as:

#define LCDCW1_ADDR       0xc000
#define READ_LCDCW1()     (*(volatile uint32_t *)LCDCW1_ADDR)
#define WRITE_LCDCW1(val) ((*(volatile uint32_t *)LCDCW1_ADDR) = (val))

My question is that when using any micro-controller, consider an example MSP430, P1OUT register address is 0x0021.

But when we use P1OUT=0xFFFF; // it assigns P1OUT a value 0xFFFF.

My question is how does it write to that address e.g. in this case 0x0021. The IDE is IAR. I found in header msp430g2553.h below definition:

#define P1OUT_              (0x0021u)  /* Port 1 Output */
DEFC(   P1OUT             , P1OUT_)

I suppose it is defining the address, but where are the other macros to write or read.

Could anyone please explain the flow that how P1OUT writes at that particular address location? Also do let me know what does u mean in 0x0021u ?

Thanks


So far the details I have found are :

in msp430g2553.h

#ifdef __IAR_SYSTEMS_ICC__
#include "in430.h"
#pragma language=extended

#define DEFC(name, address) __no_init volatile unsigned char name @ address;
#define DEFW(name, address) __no_init volatile unsigned short name @ address;
#define DEFXC  volatile unsigned char
#define DEFXW  volatile unsigned short

#endif  /* __IAR_SYSTEMS_ICC__  */


#ifdef __IAR_SYSTEMS_ASM__
#define DEFC(name, address) sfrb name = address;
#define DEFW(name, address) sfrw name = address;

#endif /* __IAR_SYSTEMS_ASM__*/



#define P1OUT_              (0x0021u)  /* Port 1 Output */
DEFC(   P1OUT             , P1OUT_)

The io430g2553.h says

__no_init volatile union
{
  unsigned char P1OUT;   /* Port 1 Output */

  struct
  {
    unsigned char P0              : 1; /*  */
    unsigned char P1              : 1; /*  */
    unsigned char P2              : 1; /*  */
    unsigned char P3              : 1; /*  */
    unsigned char P4              : 1; /*  */
    unsigned char P5              : 1; /*  */
    unsigned char P6              : 1; /*  */
    unsigned char P7              : 1; /*  */
  }P1OUT_bit;
} @0x0021;

Can some one explain what the above definition does? The details I found in MSP430 IAR C/C++ Compiler:

Example of using __write and __read
The code in the following examples use memory-mapped I/O to write to an LCD
display:
__no_init volatile unsigned char LCD_IO @ address;
size_t __write(int Handle, const unsigned char * Buf,
size_t Bufsize)
{
size_t nChars = 0;
/* Check for stdout and stderr
(only necessary if file descriptors are enabled.) */
if (Handle != 1 && Handle != 2)
{
return -1;
}
for (/*Empty */; Bufsize > 0; --Bufsize)
{
LCD_IO = * Buf++;
++nChars;
}
return nChars;
}
The code in the following example uses memory-mapped I/O to read from a keyboard:
__no_init volatile unsigned char KB_IO @ 0xD2;
size_t __read(int Handle, unsigned char *Buf, size_t BufSize)
{
size_t nChars = 0;
/* Check for stdin
(only necessary if FILE descriptors are enabled) */
if (Handle != 0)
{
return -1;
}
for (/*Empty*/; BufSize > 0; --BufSize)
{
unsigned char c = KB_IO;
if (c == 0)
break;
*Buf++ = c;
++nChars;
}
return nChars;
}

Does any one know?

Upvotes: 5

Views: 6023

Answers (3)

Josh Petitt
Josh Petitt

Reputation: 9589

My question is that when using any micro-controller, consider an example MSP430

You're not using any micro-controller, you are using an MSP430. It has memory-mapped IO (which is really nice to use for us programmers). The memory mapping will vary based on device. The answers to any of the address related questions lie within your specific device's User's Guide. TI makes very good User Guide's. Find the one for your specific device and read it thoroughly.

My question is how does it write to that address e.g. in this case 0x0021. The IDE is IAR.

Compiler glue code. Your compiler vendor will supply you with the necessary headers, macros and functions to write to your device addresses. Use the compiler vendor's code unless you can absolutely prove that it is not working for your case (with IAR I would assume that 99.9% it works, you get what you pay for. Possibly with a brand new device there are bugs in the implementation, but probably not unless you can prove it).

Also do let me know what does u mean in 0x0021u ?

From what you've posted, that is the base address for port 1. It looks like you have 8 pins on port 1 you can control.

#pragma language=extended

From this point on you must assume that there are all sorts of "magical" (aka non-standard C) things that will be happening. You can infer what you think the compiler is doing (and for the most part it is reasonably clear), however this is implementation defined, meaning only IAR compiler supports what will happen next. Look at the compiler docs for specific commands and meanings. Most notably the __no_init and the @ symbol are non-standard. The __no_init will not initialize the variable at C startup (i.e. before main() runs). The @ looks like an absolute address instruction that will be given to the linker (I may be wrong here).

__no_init volatile union
{
  unsigned char P1OUT;   /* Port 1 Output */

  struct
  {
    unsigned char P0              : 1; /*  */
    unsigned char P1              : 1; /*  */
    unsigned char P2              : 1; /*  */
    unsigned char P3              : 1; /*  */
    unsigned char P4              : 1; /*  */
    unsigned char P5              : 1; /*  */
    unsigned char P6              : 1; /*  */
    unsigned char P7              : 1; /*  */
  }P1OUT_bit;
} @0x0021;

This defines a way to get at specific bits of the byte for port 1. This lets you manipulate the IO pins. Some would say OMG bitfields are portable, the are implementation defined! Yes, they are right, but IAR is the implementor, so in this case just trust them to do the right thing.

Final note, you probably just want to use the IAR macros as defined. You paid a lot of money for them (unless you are using the free kickstart edition). You can concentrate on writing your app and not manipulating bits this way. IAR does do a good job of standardizing their names, so you can also use the same code (or very similar) on related parts. If you switch to a different compiler all this goes out the window and you'll have to do it the way of the new compiler. Good and bad points to this approach, probably no "right" answer.

Upvotes: 0

Mats Petersson
Mats Petersson

Reputation: 129494

This is "how does the compiler generate the code from what I've written", and only the compiler writers will actually be able to answer that for you.

Clearly, there are several non standard C components in the code above __no_init, the use of @, etc. In my reading of this, it tells the compiler that "this is a HW port, that provides an unsigned char, and it's address is 0xd2". The compiler will produce the right kind of instructions to read and write such a port - exactly how that works depends on the compiler, the processor that the compiler is producing code for, etc.

The P10out structure defines bitfields, which is part of the C standard. Google is your friend here.

Upvotes: 4

Pavel Zhuravlev
Pavel Zhuravlev

Reputation: 2791

Indirection operator (unary *) returns l-value equivalent to the value at pointer address.

#define LCDCW1_ADDR       0xc000

void f()
{
     uint32_t a = *(volatile uint32_t *)LCDCW1_ADDR; //reading from LCDCW1_ADDR
     *(volatile uint32_t *)LCDCW1_ADDR = 0xffff;     //writing to LCDCW1_ADDR
     /*...*/
}

Basically, compiler is smart enough to see, that a = *addr; expression means "read value from addr address and put it to a. At the same time *addr = 0xffff will be interpreted like "put 0xffff to addr address"

In your case you can use your READ_LCDCW1() macro both on left and right hand side of assignment operator. There is no need for separate WRITE_LCDCW1(val) macro. We can rewrite the previous code as:

#define LCDCW1_ADDR       0xc000
#define LCDCW1     (*(volatile uint32_t *)LCDCW1_ADDR)

void g()
{
     uint32_t a = LCDCW1; //reading from LCDCW1_ADDR
     LCDCW1 = 0xffff;      //writing to LCDCW1_ADDR
     /*...*/
}

P1OUT macro from IAR is most probably defined the same way as LCDCW1 above (if you follow the DEFC() definition you will eventually find something like it).

Upvotes: 1

Related Questions