4

假设我们想写地址 say 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;
}

有人知道吗?

4

3 回答 3

4

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

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

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

于 2012-12-21T09:57:29.863 回答
1

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

#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 = 0xffff将被解释为“将 0xffff 放入addr地址”

在您的情况下,您可以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()定义,您最终会找到类似的东西)。

于 2012-12-21T06:49:27.760 回答
0

我的问题是,当使用任何微控制器时,请考虑一个示例 MSP430

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

我的问题是它如何写入该地址,例如在本例中为 0x0021。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 确实很好地标准化了它们的名称,因此您也可以在相关部分上使用相同的代码(或非常相似的代码)。如果您切换到不同的编译器,所有这些都会消失,您将不得不按照新编译器的方式进行操作。这种方法的优点和缺点,可能没有“正确”的答案。

于 2013-02-24T07:36:49.903 回答