嵌入式C:寄存器访问

5
假设我们想要在地址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中的u是什么意思?

谢谢。


到目前为止,我找到的细节如下:

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

有人知道吗?


1
"DEFC()" 几乎肯定是一个宏,就像 "READ_LCDCW1()" 和 "#define LCDCW1_ADDR 0xc000" 一样都是宏。你需要在系统的 *.h 头文件中查看它的作用。 "u" 只是表示数字 "0x0021" 是 "unsigned" 类型。你可以安全地省略它。 - paulsm4
1
如果您有一个特定的地址,并且想要在该位置写入/读取数据,您可以使用指针... - user1849534
在msp430g2553.h文件中找到了以下内容: #define DEFC(name, address) __no_init volatile unsigned char name @ address; #define DEFW(name, address) __no_init volatile unsigned short name @ address; - Gaurav K
3个回答

4
这是“编译器如何从我编写的代码中生成代码”,只有编译器编写者才能回答您。显然,上面的代码中存在几个非标准的C组件,例如__no_init、@的使用等。在我的理解中,它告诉编译器:“这是一个硬件端口,提供一个无符号字符,其地址为0xd2”。编译器将产生正确类型的指令来读写这样的端口——如何工作取决于编译器、编译器为其生成代码的处理器等因素。P10out结构定义了位域,这是C标准的一部分。在这里Google是您的朋友。

1

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

#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()定义,最终会找到类似的内容)。


1
DEFC 可能是一些非标准的东西,使变量在调试器中呈现出 #defined 的名称。 - Lundin
@Pavel..谢谢你的回答..但我发表这个问题是为了确切地了解IAR编译器的处理方式和执行过程...以及各种定义的详细信息在哪里可以找到...如果有人擅长编写编译器代码或对IAR有专业知识... :) - Gaurav K

0
我的问题是,在使用任何微控制器时,例如MSP430,如何写入地址(例如0x0021)?我使用的IDE是IAR。
你不是在使用任何微控制器,而是在使用MSP430。它具有内存映射IO(对于我们程序员来说非常好用)。内存映射将根据设备而异。任何与地址相关的问题的答案都在您特定设备的用户指南中。TI制作了非常好的用户指南。找到您特定设备的指南并仔细阅读。
如何写入该地址(例如0x0021)?使用编译器提供的代码。您的编译器供应商将为您提供必要的头文件、宏和函数,以便写入设备地址。除非您能够绝对证明它对您的情况无效,否则请使用编译器供应商的代码(使用IAR,我会认为99.9%有效,你得到你所付出的代价。可能在全新设备上实现存在错误,但除非您能够证明,否则可能不会)。
另外,请告诉我0x0021u中的“u”是什么意思?

从您发布的内容来看,那是端口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确实很好地标准化了它们的名称,因此您也可以在相关部件上使用相同的代码(或非常相似)。如果您切换到其他编译器,所有这些都将无效,您将不得不按照新编译器的方式进行操作。这种方法有好有坏,可能没有“正确”的答案。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接