嵌入式C:注册访问

时间:2012-12-21 04:18:34

标签: c compiler-construction embedded msp430 iar

假设我们想要写地址0xc000,我们可以在C中定义一个宏:

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

我的问题是,当使用任何微控制器时,请考虑一个示例MSP430,P1OUT寄存器地址为0x0021。

但是当我们使用P1OUT = 0xFFFF时; //它为P1OUT指定一个值0xFFFF。

我的问题是它如何写入该地址,例如在这种情况下0x0021。 IDE是IAR。我在头文件msp430g2553.h中找到了以下定义:

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

我认为它是定义地址,但其他宏要写入或读取。

有人可以解释一下P1OUT如何在该特定地址位置写入的流程吗?另请告诉我你在0x0021u中的含义是什么?

谢谢


到目前为止,我发现的细节是:

在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_)

io430g2553.h说

__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;

有人可以解释上述定义的作用吗?我在MSP430 IAR C / C ++编译器中找到的详细信息:

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;
}

有人知道吗?

3 个答案:

答案 0 :(得分:4)

这是“编译器如何根据我编写的代码生成代码”,只有编译器编写者才能真正为您解答。

显然,__ no_init上面的代码中有几个非标准的C组件,使用@等等。在我的阅读中,它告诉编译器“这是一个提供无符号字符的HW端口,并且它的地址是0xd2“。编译器将生成正确类型的指令来读取和写入这样的端口 - 具体如何工作取决于编译器,编译器为其生成代码的处理器等。

P10out结构定义了位域,这是C标准的一部分。谷歌是你的朋友。

答案 1 :(得分:1)

间接运算符(一元*)返回与指针地址处的值等效的l值。

#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
     /*...*/
}

基本上,编译器非常聪明,a = *addr;表达式意味着“从addr地址读取值并将其放到a。同时*addr = 0xffffaddr被解释为“把0xffff放到READ_LCDCW1()地址”

在您的情况下,您可以在赋值运算符的左侧和右侧使用WRITE_LCDCW1(val)宏。不需要单独的#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
来自IAR的

LCDCW1宏很可能与上面的DEFC()定义相同(如果您遵循{{1}}定义,您最终会找到类似的内容。)

答案 2 :(得分:0)

  

我的问题是,在使用任何微控制器时,请考虑使用   例如MSP430

你没有使用任何微控制器,你使用的是MSP430。它有内存映射IO(对我们程序员来说非常好用)。内存映射将根据设备而有所不同。任何与地址相关的问题的答案都在您的特定设备的用户指南中。 TI提供了非常好的用户指南。找到适合您特定设备的设备,并仔细阅读。

  

我的问题是它如何写入该地址,例如在这种情况下   0×0021。 IDE是IAR。

编译器胶水代码。您的编译器供应商将为您提供必要的头,宏和函数以写入您的设备地址。使用编译器供应商的代码,除非你绝对可以证明它不适合你的情况(IAR我会假设99.9%它可以工作,你得到你付出的代价。可能有一个全新的设备,实施中有错误,但除非你能证明,否则可能没有。)

  

还要告诉我你在0x0021u中的意思吗?

根据您发布的内容,这是端口1的基地址。看起来您可以控制端口1上有8个引脚。

#pragma language=extended

从这一点开始,你必须假设将会发生各种各样的“神奇”(又称非标准C)事物。您可以推断出您认为编译器正在做什么(并且在很大程度上它是相当清楚的),但这是实现定义的,这意味着只有IAR编译器支持接下来会发生的事情。查看编译器文档以获取特定命令和含义。最值得注意的是__no_init和@符号是非标准的。 __no_init不会在C启动时初始化变量(即在main()运行之前)。 @看起来像是一个绝对地址指令,将被赋予链接器(我可能在这里错了)。

__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;

这定义了一种获取端口1字节特定位的方法。这使您可以操作IO引脚。有人会说OMG位域是可移植的,是实现定义的!是的,他们是对的,但IAR是实施者,所以在这种情况下,只要相信他们做正确的事。

最后请注意,您可能只想使用定义的IAR宏。你为他们付了很多钱(除非你使用免费的kickstart版本)。您可以专注于编写应用程序而不是以这种方式操作位。 IAR确实可以很好地标准化它们的名称,因此您也可以在相关部分使用相同的代码(或非常相似)。如果切换到不同的编译器,所有这些都会从窗口中消失,你必须按照新编译器的方式进行。这种方法有好有坏,可能没有“正确”的答案。