singleywy的笔记 https://bbs.21ic.com/?632881 [收藏] [复制] [RSS]

日志

Keil中C语言与汇编语言混合编程需要注意的几个地方

已有 3656 次阅读2010-11-5 10:05 |系统分类:单片机| keil, 混合编程, 汇编

转载自 mcu99

最终编辑 mcu99







在keil C语言与汇编语言的混合编程中曾经遇到过的一些问题,写下来留作以后参考。(本文写于09.08.15,在8.18日加入了第4点的内容)
1、C语言中加入汇编语言模块的方法:

例子:
void func()
{

    C语言代码……
#pragma asm
         MOV R6,#23
DELAY2:    MOV R7,#191
DELAY1:    DJNZ R7,DELAY1
DJNZ R6,DELAY2
RET

#pragma endasm
    C语言代码……
}
其中红色为C语言部分,绿色为嵌入的汇编语言部分。汇编部分需要用#pragma asm#pragma endasm包起来



2、Keil提示“
asm/endasm”出错的解决方法

如果只是像1中那样直接加入汇编代码的话,编译将会报错,错误如下:
compiling sendata.c...
sendata.c(81): error C272: 'asm/endasm' requires src-control to be active
sendata.c(87): error C272: 'asm/endasm' requires src-control to be active
Target not created

解决方法如下:


首先右键单击包含有汇编部分的c语言文件名,然后在如上图所示的菜单中选择带有红色方框的选项



在弹出的对话框中,将上图中红色方框选中的两项打上勾(默认的情况下,前面的勾是灰色的,要让这两项前的勾变为黑色的),点击确定。



3、
?C_START等相关警告的处理

按照2中的方法处理完之后,再编译不会出现错误信息了,但是会出现如下的警告信息:
linking...
*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL
SYMBOL:  ?C_START
MODULE:  STARTUP.obj (?C_STARTUP)
*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL
SYMBOL:  ?C_START
MODULE:  STARTUP.obj (?C_STARTUP)
ADDRESS: 000DH

处理方法如下:


在如上图所示的“Source Group 1”上点右键,在菜单中选择 “Add Files to Group 'Source Group 1' ”


找到你的KEIL安装目录,选择其中的“C51”目录下的“LIB”目录下的“C51S.LIB”文件,点击Add,然后Close即可。

注意,上图所示的文件选择框进入LIB目录下后,默认只显示.c文件,需要在“文件类型”中选择“Library file (*.lib)”,即可显示LIB文件了。

添加
C51S.LIB到工程后,再次编译,警告信息消失。
linking...
Program Size: data=9.0 xdata=0 code=28
creating hex file from "sendata"...
"sendata" - 0 Error(s), 0 Warning(s).

4、寄存器冲突问题的解决

汇编程序块中常常会使用到51的通用寄存器,比如R0-R7。这种情况下可能会和C语言程序中已经使用到的R0-R7产生冲突,以至于产生一些非常隐蔽和古怪的错误。虽然有人说KEIL可以自动分配寄存器组使之不产生冲突。但是在我这里具体测试时还是有冲突的(或者是我的KEIL设置有问题?),下面是测试小程序:

/*************************************************************
* 测试程序
*************************************************************/

#include<reg51.h>
#include<stdio.h>

typedef unsigned char uint8 ;

uint8 buf[16]=
{
0x55 
};
uint8 i=0 ;
/************************************************************
* 初始化单片机相关寄存器
***********************************************************/

void UartInit()
{
SCON=0x50 ;
TMOD|=0x21 ;
PCON|=0x80 ;
TH1=0xE8 ;
TL1=0xE8 ;
IE|=0x90 ;
TR1=1 ;
}

/**************************************************
*    延时
***************************************************/

void delay()
{
#pragma asm 
MOV R6,#19 
DELAY2 :    MOV R7,#18 
DELAY1 :    DJNZ R7,DELAY1 
DJNZ R6,DELAY2 
RET 
#pragma endasm 
}

/**********************************************
*    向COM1发送一个字符
**********************************************/

void SendChar(uint8 byteToSend)
{
SBUF=byteToSend ;
while(!TI);
TI=0 ;
}

/************************************************************
*    读取一个字节
***********************************************************/

uint8 read_byte()
{
uint8 recvdata=8 ;

delay();            //延时
    
return recvdata ;
}
/**************************************************
*        主   程   序
***************************************************/

int main()
{
UartInit();//串口初始化
    while(1)
{
buf[i++]=read_byte();
}
}
/**************************************************
*        串口中断处理
***************************************************/

void chuankou()interrupt 4 
{
if(RI)
for(i=0;i<sizeof(buf);i++)
{
SendChar(buf);
}
RI=0 ;
}


程序本身非常简单,一目了然。main函数的作用就是不断把read_byte()的返回值读入buf中,再待有串口中断时,将buf中的内容输出到串口。read_byte()函数也已经做了简化,返回值固定为8。

将这个程序在KEIL中编译,下载运行。奇怪的情况出现了,buf中的内容输出到串口总是0(输出理所当然应该是8)。

delay();这一句注释掉后,程序就输出正常了。

delay();不过起到了一个延时作用而已,怎么可能改变到函数返回值呢?

看看
read_byte()函数生成的汇编程序

USING    0
MOV      R7,#08H
ACALL    delay
RET

才晓得,原来read_byte()函数将返回值放到R7中,然后调用delay函数,再返回。后面的程序调用
read_byte()返回值时,直接从R7中取数。

但是“
ACALL delay”时,已经存放了#08H的R7,在延时中被递减到了0,这也是为什么buf中存放的内容都是0的缘故。

知道了原因就好办一些了,由于本人是菜鸟,单片机水平很有限,目前只想到了如下4种解决方法:

(1)避开C语言部分已经使用了的Rn

编汇编模块时,看看C语言部分生成的汇编程序,把那些C语言已经使用到的,且可能对汇编部分构成冲突的Rn避开就好了。比如上面的延时程序中,把R6、R7换成R3、R4,程序就正常了。

(2)用USING X + ARX的方式调用其他组Rn寄存器

51单片机有4组R0-R7的寄存器,据说main中一般使用的都是第0组。那么在汇编部分中使用其他的组就可以了,delay函数的内容可以改成如下这样:

void delay()
{
#pragma asm 
USING 2
MOV AR6,#19
DELAY2:    MOV AR7,#18
DELAY1:    DJNZ AR7,DELAY1
DJNZ AR6,DELAY2

RET 
#pragma endasm 
}


但是注意,如果这样用的话,delay延时的长度可能需要重新计算。因为AR6R6有所不同,以下是在网上找到的两段英文描述:
R0 – R7 The eight 8 bit general purpose 8051 registers in the currently active register bank.
A Maximum of four register banks are available. 

AR0 – AR7 Represent the absolute data addresses of R0 through R7 in the current register bank.
The absolute address for these registers changes depending on the register bank that is currently selected.
These symbols are only available when the USING assembler statement is given.
Refer to the USING assembler statement for more information on selecting the register bank.
These representations are suppressed by the NOAREGS directive off the Cx51 compiler. 

看这意思,似乎
R0 – R7是寄存器,而AR0 – AR7是地址,所以“MOV AR6,#19”和MOV R6,#19”所花的时钟周期数是不同的。我调一个采集数据的程序时,刚开始没有注意到这个问题,因此很奇怪为什么用AR6和R6,采集到的数据是不一样的,后来才反应过来它们延时不同。(关于51单片机的各条指令的时钟周期数可以在百度上搜到很多,这里就不列出了)

(3)对汇编部分中使用到的寄存器采用入栈保护

简单的说就是在汇编部分使用Rn之前,将它们的内容塞进栈中存起来,延时循环结束之后再从栈中取出来重新赋给它们。

比如上面的delay程序,可以改成这样:
void delay()
{
#pragma asm
MOV A,R6
PUSH ACC
MOV A,R7
PUSH ACC
MOV R6,#19
DELAY2: MOV R7,#18
DELAY1: DJNZ R7,DELAY1
DJNZ R6,DELAY2
POP ACC
MOV R7,A
POP ACC
MOV R6,A
RET
#pragma endasm
}


(4)使用RS0和RS1切换使用的寄存器组

51单片机中使用RS0和RS1来选择使用哪一组Rn,因此在汇编程序通过修改这两位的值,即可实现切换寄存器组。
上面的delay函数可修改为如下形式:
void delay()
{
#pragma asm
SETB RS1
SETB RS0
MOV R6,#19
DELAY2: MOV R7,#18
DELAY1: DJNZ R7,DELAY1
DJNZ R6,DELAY2
CLR RS1
CLR RS0

RET
#pragma endasm
}

进入汇编部分后选择采用第3组寄存器,退出汇编部分前更换回之前使用的第x组寄存器(在本程序中是第0组寄存器)


不过对于比较精细的延时的话,方法(3)和方法(4)这样或许会稍有些影响,毕竟增加了一些入栈出栈或者置位清零的语句。因此如果对延时精度要求较高,且采用方法(3)或(4)的话,计算延时的循环次数时,需要把入栈出栈或置位清零语句花费的时间也考虑进去。



另外,除上面四种方法之外,我还曾经尝试过以下方法,但是失败:

一种是在void delay()的函数定义后面加上 using 2,使之变成
void delay() using 2
{
................................
}
或者是在汇编中加上USING 2,使之变成
void delay()
{
#pragma asm
USING 2   
................................
#pragma endasm
}
但是似乎都不行。。。

路过

鸡蛋

鲜花

握手

雷人

评论 (0 个评论)