在线汇编编写代码

本文详细介绍了如何在C语言程序中嵌入汇编语言,以提高程序效率和实现特定功能。文章讨论了两种汇编与C结合的方法:独立汇编程序和在线汇编。重点讲述了GCC的在线汇编指令格式,包括汇编指令模板、操作数、约束符等关键要素,并给出了多个实例,展示了如何通过GCC的嵌入式汇编实现对端口寄存器的读写操作。

为了使C语言程序具有更高的效率和更多的功能,需在C语言程序里嵌入用汇编语言编写的子程序。一方面是为提高子程序的执行速度和效率;另一方面,可解决某些用C语言程序无法实现的机器语言操作。而C语言代码与汇编语言代码的接口是任何C编译器毋庸置疑要解决的问题。

    通常,有两种方法可将汇编语言代码与C语言代码联合在一起。一种是把独立的汇编语言程序用C函数连接起来,通过API (Application Program Interface) 的方式调用;另一种就是我们下面要讲的在线汇编方法,即将直接插入式汇编指令嵌入到C函数中。

 C语言的嵌入式汇编

    为了使C语言程序具有更高的效率和更多的功能,需在C语言程序里嵌入用汇编语言编写的子程序。一方面是为提高子程序的执行速度和效率;另一方面,可解决某些用C语言程序无法实现的机器语言操作。而C语言代码与汇编语言代码的接口是任何C编译器毋庸置疑要解决的问题。

    通常,有两种方法可将汇编语言代码与C语言代码联合在一起。一种是把独立的汇编语言程序用C函数连接起来,通过API (Application Program Interface) 的方式调用;另一种就是我们下面要讲的在线汇编方法,即将直接插入式汇编指令嵌入到C函数中。

     采用GCC规定的在线汇编指令格式进行指令的输入,是GCC实现将?nSP?汇编指令嵌入C函数中的方法。GCC在线汇编指令格式规定如下:

asm (“汇编指令模板”:输出参数:输入参数:clobbers参数)

若无clobber参数,则在线汇编指令格式可简化为:

asm (“汇编指令模板”:输出参数:输入参数)

下面,将对在线汇编指令格式中的各种成分之内容进行介绍。

1) 汇编指令模板

    模板是在线汇编指令中的主要成分,GCC据此可在当前位置产生汇编指令输出。例如,下面一条在线汇编指令:

    asm ("%0 += %1" : "+r" (foo) : "r" (bar))

    此处,"%0 += %1"就是模板。其中,操作数"%0""%1"作为一种形式参数,分别会由第一个冒号后面实际的输出、输入参数取代。带百分号的数字表示的是第一个冒号后参数的序号。

    如下例:

    asm ("%0 = %1 + %2" : "=r" (foo) : "r" (bar), "i" (10))

    "%0"会由参数foo取代,"%1"会由参数bar取代,而"%2"则会由数值10取代。

    在汇编输出中,一个汇编指令模板里可以挂接多条汇编指令。其方法是用换行符'\n'来结束每一条指令,并用Tab键符'\t'将同一模板产生在汇编输出中的各条指令在换行显示时缩进到同一列,以使汇编指令显示清晰。如下例:

asm ("%0 += %1\n\t%0 += %1" : "+r" (foo) : "r" (bar))

2) 操作数

    在线汇编指令格式中,第一冒号后的参数为输出操作数,第二冒号后的参数为输入操作数,第三冒号后跟着的则是clobber操作数。在各类操作数中,引号里的字符代表的是其存储类型约束符;括弧里面的字符串表示的是实际操作数。

    如果输出参数有若干个,可用逗号“,”将每个参数隔开。同样,该法则适用于输入参数或clobber参数。

3) 操作数约束符

    约束符的作用在于指示GCC,使用在汇编指令模板中的操作数的存储类型。表4.9列出了一些约束符和它们分别代表的操作数不同的存储类型,也列出了用在操作数约束符之前的两个约束符前缀。

4) GCC在线汇编指令举例

1asm ("%0 = %1 + %2" : "=m" (foo) : "r" (bar), "i" (10))

   操作数foobar都是局部变量。bar的值会分配给寄存器(此例中寄存器为R1),而foo的值会置入存储器中,其地址在此由BP寄存器指出。GCC对此会产生如下代码:

// GCC在线汇编起始

[BP] = R1 + 10

// GCC在线汇编结束

注意,本在线汇编指令产生的汇编代码不能被正确汇编。正确的在线汇编指令应当是:

asm ("%0 = %1 + %2" : "=r" (foo) : "r" (bar), "i" (10))

它产生如下的汇编代码:

// GCC在线汇编起始

R1 = R4 + 10

// GCC在线汇编结束

2

int a;

int b;

#define SEG(A,B) asm("%0 = seg %1" : "=r" (A) : "p" (&B));

int main(void)

{

  int foo;

  int bar;

  SEG(foo, a);

  SEG(bar, b);

  return foo;

}

3asm ("%0 += %1" : "+r" (foo) : "r" (bar))

操作数foo在被赋值前先要参加运算,故其约束符为"+r",而非"=r"

利用嵌入式汇编实现对端口寄存器的操作

C的嵌入式汇编中,当使用端口寄存器名称时,需要在C文件中加入汇编的包含文件,如下所示:

asm(.include hardware.inc);

那么,我们就可以使用端口寄存器的名称,而不必去使用端口的实际的地址。

1) 写端口寄存器

现举例说明:要设定PortA端口为输出端口,需要对P_IOA_Dir赋值0xffff。那么在C中的嵌入式汇编的实现方式如下:

C中有一个int型的变量i,传送到P_IOA_Dir中,则嵌入汇编的实现方式如下:

.

asm(.include hareware.inc);

.

int main(void){

int i;

.

asm("[P_IOA_Dir] = %0"

:

//没有输出参数

:"r"(i)

//只有输入参数,通过寄存器传递变量i的内容

);

}

如果需要对端口寄存器直接赋值一个立即数(比如对P_IOA_Dir赋值0x1234),那么内嵌式汇编为:

.

asm(.include hareware.inc);

.

int main(void){

.

asm("[P_IOA_Dir] = %0"

:

//没有输出参数

:"r"(0x1234)

//只有输入参数,通过寄存器传递立即数0x1234

);

}

2) 读端口寄存器

对端口寄存器进行读操作的方法,与写类似,下面仍然以P_IOA_Dir为例,进行说明。

如果要实现把端口的寄存器P_IOA_Dir的值读出并保存在C中的一个int变量j里,那么可以通过下面的方法来实现。

.

asm(.include hareware.inc);

.

int main(void){

int j;

.

asm("%0 = [P_IOA_Dir]"

:"=r"(j)

//只有输出参数,而无输入参数

);

}

3) 利用GCC编程举例

下面是一段GCC的代码,实现对A口的初始化:设定A口为同向输出高电平。

asm("[P_IOA_Attrib] = %0\n\t"

"[P_IOA_Data] = %0\n\t"

"[P_IOA_Dir] = %0\n\t"

:

:

"r"(0xffff)

);

上面代码通过GCC编译后的代码为:

R1=(-1) // QImode move

// GCC inline ASM start

[P_IOA_Attrib] = R1

[P_IOA_Data] = R1

[P_IOA_Dir] = R1

// GCC inline ASM end

下面是一段GCC的代码,实现对B口的初始化:设定B口为具有上拉电阻的输入。

asm("[P_IOB_Attrib] = %0\n\t"

"[P_IOB_Data] = %1\n\t"

"[P_IOB_Dir] = %0\n\t"

:

:

"r"(0),

"r"(0xffff)

);

上面一段代码通过GCC编译后的汇编代码为:

R2=(-1)

// QImode move

R1=0

// QImode move

// GCC inline ASM start

[P_IOB_Attrib] = R2

[P_IOB_Data] = R1

[P_IOB_Dir] = R2

// GCC inline ASM end

     通过上述两断代码,使得SPCE061AB口为输入,A口为输出,如果我们要实现把B口得到的数据从A口输出,这样的GCC编程需要在C中先建立个int型的中间变量,通过这个中间变量,写出两个GCC的代码来实现。

int temp;

asm("%0 = [P_IOB_Data]"

:"=r"(temp)

);

asm("[P_IOA_Buffer] = %0"

:

:"r"(temp)

);

    通过GCC后的代码如下所示。这里将看不到temp的影子,GCC会进行优化处理。

R1 = [P_IOB_Data]

[P_IOA_Buffer] = R1

    通过上述方法的介绍,我们就可以在C语言中直接对SPCE061A的硬件进行操作。在对硬件读写语句较少的情况下,如果采用C调用汇编函数的方法显得有些臃肿,而使用嵌入式汇编会使得代码高效简洁!

     采用GCC规定的在线汇编指令格式进行指令的输入,是GCC实现将?nSP?汇编指令嵌入C函数中的方法。GCC在线汇编指令格式规定如下:

asm (“汇编指令模板”:输出参数:输入参数:clobbers参数)

若无clobber参数,则在线汇编指令格式可简化为:

asm (“汇编指令模板”:输出参数:输入参数)

下面,将对在线汇编指令格式中的各种成分之内容进行介绍。

1) 汇编指令模板

    模板是在线汇编指令中的主要成分,GCC据此可在当前位置产生汇编指令输出。例如,下面一条在线汇编指令:

    asm ("%0 += %1" : "+r" (foo) : "r" (bar))

    此处,"%0 += %1"就是模板。其中,操作数"%0""%1"作为一种形式参数,分别会由第一个冒号后面实际的输出、输入参数取代。带百分号的数字表示的是第一个冒号后参数的序号。

    如下例:

    asm ("%0 = %1 + %2" : "=r" (foo) : "r" (bar), "i" (10))

    "%0"会由参数foo取代,"%1"会由参数bar取代,而"%2"则会由数值10取代。

    在汇编输出中,一个汇编指令模板里可以挂接多条汇编指令。其方法是用换行符'\n'来结束每一条指令,并用Tab键符'\t'将同一模板产生在汇编输出中的各条指令在换行显示时缩进到同一列,以使汇编指令显示清晰。如下例:

asm ("%0 += %1\n\t%0 += %1" : "+r" (foo) : "r" (bar))

2) 操作数

    在线汇编指令格式中,第一冒号后的参数为输出操作数,第二冒号后的参数为输入操作数,第三冒号后跟着的则是clobber操作数。在各类操作数中,引号里的字符代表的是其存储类型约束符;括弧里面的字符串表示的是实际操作数。

    如果输出参数有若干个,可用逗号“,”将每个参数隔开。同样,该法则适用于输入参数或clobber参数。

3) 操作数约束符

    约束符的作用在于指示GCC,使用在汇编指令模板中的操作数的存储类型。表4.9列出了一些约束符和它们分别代表的操作数不同的存储类型,也列出了用在操作数约束符之前的两个约束符前缀。

4) GCC在线汇编指令举例

1asm ("%0 = %1 + %2" : "=m" (foo) : "r" (bar), "i" (10))

   操作数foobar都是局部变量。bar的值会分配给寄存器(此例中寄存器为R1),而foo的值会置入存储器中,其地址在此由BP寄存器指出。GCC对此会产生如下代码:

// GCC在线汇编起始

[BP] = R1 + 10

// GCC在线汇编结束

注意,本在线汇编指令产生的汇编代码不能被正确汇编。正确的在线汇编指令应当是:

asm ("%0 = %1 + %2" : "=r" (foo) : "r" (bar), "i" (10))

它产生如下的汇编代码:

// GCC在线汇编起始

R1 = R4 + 10

// GCC在线汇编结束

2

int a;

int b;

#define SEG(A,B) asm("%0 = seg %1" : "=r" (A) : "p" (&B));

int main(void)

{

  int foo;

  int bar;

  SEG(foo, a);

  SEG(bar, b);

  return foo;

}

3asm ("%0 += %1" : "+r" (foo) : "r" (bar))

操作数foo在被赋值前先要参加运算,故其约束符为"+r",而非"=r"

利用嵌入式汇编实现对端口寄存器的操作

C的嵌入式汇编中,当使用端口寄存器名称时,需要在C文件中加入汇编的包含文件,如下所示:

asm(.include hardware.inc);

那么,我们就可以使用端口寄存器的名称,而不必去使用端口的实际的地址。

1) 写端口寄存器

现举例说明:要设定PortA端口为输出端口,需要对P_IOA_Dir赋值0xffff。那么在C中的嵌入式汇编的实现方式如下:

C中有一个int型的变量i,传送到P_IOA_Dir中,则嵌入汇编的实现方式如下:

.

asm(.include hareware.inc);

.

int main(void){

int i;

.

asm("[P_IOA_Dir] = %0"

:

//没有输出参数

:"r"(i)

//只有输入参数,通过寄存器传递变量i的内容

);

}

如果需要对端口寄存器直接赋值一个立即数(比如对P_IOA_Dir赋值0x1234),那么内嵌式汇编为:

.

asm(.include hareware.inc);

.

int main(void){

.

asm("[P_IOA_Dir] = %0"

:

//没有输出参数

:"r"(0x1234)

//只有输入参数,通过寄存器传递立即数0x1234

);

}

2) 读端口寄存器

对端口寄存器进行读操作的方法,与写类似,下面仍然以P_IOA_Dir为例,进行说明。

如果要实现把端口的寄存器P_IOA_Dir的值读出并保存在C中的一个int变量j里,那么可以通过下面的方法来实现。

.

asm(.include hareware.inc);

.

int main(void){

int j;

.

asm("%0 = [P_IOA_Dir]"

:"=r"(j)

//只有输出参数,而无输入参数

);

}

3) 利用GCC编程举例

下面是一段GCC的代码,实现对A口的初始化:设定A口为同向输出高电平。

asm("[P_IOA_Attrib] = %0\n\t"

"[P_IOA_Data] = %0\n\t"

"[P_IOA_Dir] = %0\n\t"

:

:

"r"(0xffff)

);

上面代码通过GCC编译后的代码为:

R1=(-1) // QImode move

// GCC inline ASM start

[P_IOA_Attrib] = R1

[P_IOA_Data] = R1

[P_IOA_Dir] = R1

// GCC inline ASM end

下面是一段GCC的代码,实现对B口的初始化:设定B口为具有上拉电阻的输入。

asm("[P_IOB_Attrib] = %0\n\t"

"[P_IOB_Data] = %1\n\t"

"[P_IOB_Dir] = %0\n\t"

:

:

"r"(0),

"r"(0xffff)

);

上面一段代码通过GCC编译后的汇编代码为:

R2=(-1)

// QImode move

R1=0

// QImode move

// GCC inline ASM start

[P_IOB_Attrib] = R2

[P_IOB_Data] = R1

[P_IOB_Dir] = R2

// GCC inline ASM end

     通过上述两断代码,使得SPCE061AB口为输入,A口为输出,如果我们要实现把B口得到的数据从A口输出,这样的GCC编程需要在C中先建立个int型的中间变量,通过这个中间变量,写出两个GCC的代码来实现。

int temp;

asm("%0 = [P_IOB_Data]"

:"=r"(temp)

);

asm("[P_IOA_Buffer] = %0"

:

:"r"(temp)

);

    通过GCC后的代码如下所示。这里将看不到temp的影子,GCC会进行优化处理。

R1 = [P_IOB_Data]

[P_IOA_Buffer] = R1

    通过上述方法的介绍,我们就可以在C语言中直接对SPCE061A的硬件进行操作。在对硬件读写语句较少的情况下,如果采用C调用汇编函数的方法显得有些臃肿,而使用嵌入式汇编会使得代码高效简洁!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值