http://hi.baidu.com/txz01/item/21ad9d75913a7b28d7a89c12

  这一篇来讲讲混合编程的问题,在网上找了一下,讲混合编程的文件章也有不少,但进行实例操作讲解的不多也不完整,本来书上混合编程的内容看着就让人觉得抽象难懂,再没有个实际操作图例,就很让人觉得云里雾里。在这里我就针对KEIL做个混合编程的实例的文章希望对初学者有所帮助。先搞清几个问题。

  ①混合编程的必要性:也就是为什么需要混合编程,初学者一定会觉得,我C用的好好的为什么要混进汇编呢,不是自找麻烦吗?其实不然,最简单的例子就是延时子程序,用C写的话连你自己也不知道几层的循环后确切地用多少时间吧?但用汇编写你就能很准确地计算出要延时的时间。还有当你要对那些时序要求很高IC模块或步进电机行操作时用汇编来写就能做到操控的直接与精准。

  ②在进行实际操作前要弄清C与汇编之间的调用关系,C的函数大家都会用了,主要分为无返回参数的和有返回参数的,例如 void delay(void);就是无返回参数的,int readdata(void);就是有返回参数的。还有就是有参数传递和无参数传递的,void delay(void);就是无参数传递的,unsigned int add(unsigned char aa,unsigned char bb);就是有参数传递的函数。在教材上讲起C与汇编的混合编程就会说起寄存器最多传递三个函数,这样可以产生高效代码。

在参数返回时寄存器的传递规律为:

下面我们用实际的混合编程操作来讲讲如何实现函数的调用及参数的传递。
      打开KEIL,我的用的版本是绿色免安装2.0中文版,编译器为7.0:无程序代码长度限制。现在有3.0版也是绿色免安装版本,好处是已支持双字节中文注释,但是英文版。用哪个版本都无所谓,只要用着习惯功能够用就行。

下面是版本信息:

在网上经常有朋友说为什么我下载了KEIL解压出目录后运行却不能编译呢,老是报告出错:
--- Error: can't execute 'E:\old_pc\txz001\单片机c51\KEIL4\C51\BIN\C51.EXE'
--- 错误: 不能执行 'E:\old_pc\txz001\单片机c51\KEIL2_70\Keil2\C51\BIN\C51.EXE'
这是由于编译时,C51.exe编译器没能在你给出的路径上找到。你需要修改路径。
在选择KEIL的菜单栏“工程”--“文件扩展名、书籍和编译环境属性”--“环境设置”的如下图:

看到上图的“使用TOOLS.INI设定”前的钩了吗?对了,它是按照你TOOLS.INI里给出的路径去找的。因此的得打开那个tools.ini文件修改它。KEIL的目录结构一般是这样的:

我们KEIL软件运行主程序uvision2是在目录UV2里,而那个设置文件TOOLS.INI文件是在它的上一级目录Keil里,见上图。用记事本打开这个TOOLS.INI文件:

看见红笔圈出的[C51]下的路径了吗?将它修改正确指向你硬盘上KEIL下C51目录,存盘,运行KEIL。就可以正确编译了。(废话又多了。。。)好!言归正传。
    我们在KEIL里创建一个新的工程TEST1。在这个工程里我们添加了两个文件,main.c和delay.c,程序如下:
   文件main.c:

#include <AT89X52.H>
extern void delay(void);
main(void)
{
    delay();
}

文件delay.c

#define uchar unsigned char
void delay(void)
{
    uchar i;
    ,i>,i--);
}

  可以看出,这两个文件里的程序很简单,主程序里先定义了一个外部函数delay();然后就调用了这个无参数函数。而文件delay.c里也就是用for循环做了255次循环。
下面我们先进行编译,调试让程序正确通过编译。然后我们选择左边工程窗口,选中文件delay.c,鼠标右击它出现下图。

选择“文件'delay.c'属性”后如下图:

见上图,有“产生汇编文件”和“汇编源代码文件”两项前的钩选框是灰色的,分别点击它们两次使它呈黑色钩选状态。如下图。

点击下面的确认钮,回到主界面。这时你再进行一次全部的重新编译,就会发现在你建立这个工程的目录下将多产生一个delay.src文件。

用记事本打开这个delay.src文件。发现它就是一个汇编文件。

; .\delay.SRC generated from: delay.c
; COMPILER INVOKED BY:
;        E:\old_pc\txz001\单片机c51\KEIL2_70\Keil\C51\BIN\C51.EXE delay.c BROWSE DEBUG OBJECTEXTEND SRC(.\delay.SRC)

NAME DELAY

?PR?delay?DELAY      SEGMENT CODE
PUBLIC delay
; #define uchar unsigned char
; void delay(void)

RSEG ?PR?delay?DELAY
delay:
USING
    ; SOURCE LINE # 2
; { uchar i;
    ; SOURCE LINE # 3
;   for(i=255;i>0;i--);
    ; SOURCE LINE # 4
;---- Variable 'i?040' assigned to Register 'R7' ----
MOV R7,#0FFH
?C0001:
DJNZ R7,?C0001
; }
    ; SOURCE LINE # 5
?C0004:
RET
; END OF delay

END

可以看出原来的C程序都变成了汇编的注释了。我们将注释都去掉。

NAME DELAY
?PR?delay?DELAY      SEGMENT CODE
PUBLIC delay
RSEG ?PR?delay?DELAY
delay:
      USING
      MOV R7,#0FFH
?C0001:
      DJNZ R7,?C0001
?C0004:
      RET
      END

  现在看看是不是很简呢。在标号delay:前是程序的说明,就是定义函数的名字,将代码放在哪里等,看不懂也没关系,别乱改它就行。从delay:标号后就是汇编的程序部分了。里面的标号最好也别乱改。添加你要操作的程序就行了,好!我们先不改动程序,就将上面十行汇编别存为delay.asm文件。回到KEIL界面,我们在工程窗里(是KEIL主界面左边的工程窗口而不是在工程目录里)的将delay.c删除。然后再添加上delay.asm程序,如下图:

这样,你再进行编译,你会发现你已经通过了混合编程的编译,虽然这次你对程序的功能什么都没有改变,但你已经知道如何做出一个C程序调用汇编子程序的例子了。下面我们可以对这个汇编了程序进行一些修改看它是否仍能很好的工作。
  今天我们就来对那个汇编的delay子程序进行修改,为了让运行的结果能显示出来,我先加进一个LCD的显示子程序12864put.c。

我们先修改主程序如下:

//****************
//    主函数
//****************
main(void)
{
    uchar aa,bb;

    TMOD=0x01;    //定义T0为模式1即16位计数方式
    TH0=;        //将计数器高位初值清0
    TL0=;        //将计数器低位初值清0

    TR0=;        //计数器开始计数
    //delay();    //调用汇编的子函数
    TR0=;        //停止计数

    aa=TH0;        //把计数的值高位交给aa
    bb=TL0;        //把计数的值低位交给aa
    LcmInit();    //初始化LCD12864
    LcmClear();    //清屏LCD
    LcmPutstr( ,,"C&A TEST" );    //显示
    LcmPutstr( ,,"TH0:" );
    LcmPutstr( ,,uchartostr(aa) );
    LcmPutstr( ,,"TL0:");
    LcmPutstr( ,,uchartostr(bb) );
    LcmPutstr( ,,"BLOG:http://" );
    LcmPutstr( ,,"hi.baidu.com/txz01" );
    LcmPutstr( ,,"Email:TXZ001@139.com" );

}    

看见上面的程序了吗?我用了T0在调用汇编子函数delay()前开始计数,调用完后就关掉,然后看计数器内的计数值来知道我们这个子函数的精确程度。我先把delay()函数给注释掉,看看开始计数后就立即关掉要用去多少时间。结果显示为1,就是说用了一个脉冲的时间。12M的晶振就是一微秒。见下图:

看到没有,用了TR0=1;TR0=0;本身就用去了一个脉冲。好!现在我们将那个调用汇编子函数delay()语句启用,但我将汇编内的语句给清空。也就是说我把delay.asm这个子程序让它什么也没做。是个空函数,看它要用掉几个脉冲时间。汇编程序如下:

NAME DELAY
?PR?delay?DELAY      SEGMENT CODE
PUBLIC delay
RSEG ?PR?delay?DELAY
delay:

RET
END

看到了吗?标号delay:下面什么也没有了,直接就RET返回了。好!编译,烧写,运行!如下图:

结果是用了5个脉冲,其中一个是调用计数器本身用的,也就是说调用一个空函数用了4个脉冲时间。好!我们再来修改一下汇编程序:

NAME DELAY
?PR?delay?DELAY  SEGMENT CODE
PUBLIC delay
RSEG ?PR?delay?DELAY
delay:

     djnz r7,$
   RET
   END

在标号delay:下面我加了两行,我们计算一下,第一行MOV r7,#100要用一个机器周期,也就是一个脉冲。第二行djnz r7,$要循环100次每次用2个机器周期,这样算来共是201个脉冲再加上刚才我们计算过的调用函数要4个脉冲和开关计数器用1个,总共是206个。编译,烧写,运行!

看来计算的没错呀!我们再循环多些:

NAME DELAY
?PR?delay?DELAY      SEGMENT CODE
PUBLIC delay
RSEG ?PR?delay?DELAY
delay:

       
  
     djnz r6,$        ;50×100×2
   djnz r7,loop    ;100×2
   RET
   END

这次的计算应该是1+100+50×100×2+100×2+5=10306。再次编译烧写运行!

高位数值为40,低位数值为66,则总数=40×256+66=10306。精准吧!好了!无参函数的调用就讨论到此。

  下面接着说说带参数据函数的调用
  我们重新建立一个目录TEST2(因为一个项目有很多个文件如果都放在一个目录里会很混乱,以后想挪到U盘带到其它机子上用时就很困难了),建立新的项目test2.Uv2,里面还是main.c主程序和12864put.c显示子程序:

主函数main()如下:

#include <AT89X52.H>
#include <intrins.H>
#define uchar unsigned char
#define uint unsigned int
extern void LcmClear( void );       //清屏
extern void LcmInit( void );        //初始化
extern void LcmPutstr( uchar row,uchar y,uchar * str ); //在设定位置显示字符串
//row:是LCD的行数(0-7)
//y:是LCD的列数(0-127)
//str:是字符串的首地址

extern uint add(uchar aa,uchar bb);
extern void inttostr(uint intval,uchar data * str);
uchar str[];//定义四个字节空间用来存放数值转换成的字符值

//****************
//    主函数
//****************
main(void)
{
    uchar aa,bb;
    uint cc;
    aa=;
    bb=;

    cc=add(aa,bb);

    LcmInit();                        //初始化LCD12864
    LcmClear();                       //清屏LCD
    LcmPutstr( ,,"C&A TEST" );     //显示
    inttostr(aa,str);
    LcmPutstr( ,,str );
    LcmPutstr( ,," + " );
    inttostr(bb,str);
    LcmPutstr( ,,str);
    LcmPutstr( ,," = ");
    inttostr(cc,str);
    LcmPutstr( ,,str);
    //LcmPutstr( 3,46,"TL0:");
    //LcmPutstr( 3,70,uchartostr(bb) );
    LcmPutstr( ,,"BLOG:http://" );
    LcmPutstr( ,,"hi.baidu.com/txz01" );
    LcmPutstr( ,,"Email:TXZ001@139.com" );
    );
}

项目中还有uinttostr.c是无符号整型转字符串子程序和我们要做汇编调用的这个有返回参数有传递参数的子程序add.c,子程序add.c如下。

#define uchar unsigned char
#define uint unsigned int

uint add(uchar aa,uchar bb)
{
    uint cc;
    cc=aa+bb;
    return(cc);
}

我们主要目的是为了表达清楚怎样在C程序里去调用汇编子函数,所以程序还是很简单,就是把主程序传过来的无符号字符型变量aa和bb相加,相加的结果交给无符号整型变量cc返回给主程序。编译前我们还是点取add.c文件属性,让它产生src文件。上面的图已显示了编译的过程信息。现在我们打开这个add.src文件:

; .\add.SRC generated from: add.c
; COMPILER INVOKED BY:
;        E:\old_pc\txz001\单片机c51\KEIL2_70\Keil\C51\BIN\C51.EXE add.c BROWSE DEBUG OBJECTEXTEND SRC(.\add.SRC)
NAME ADD?
?PR?_add?ADD         SEGMENT CODE
PUBLIC _add
; #define uchar unsigned char
; #define uint unsigned int
;
; uint add(uchar aa,uchar bb)

RSEG ?PR?_add?ADD
_add:
USING
    ; SOURCE LINE # 4
;---- Variable 'bb?041' assigned to Register 'R5' ----
;---- Variable 'aa?040' assigned to Register 'R7' ----
; {
    ; SOURCE LINE # 5
;   uint cc;
;   cc=aa+bb;
    ; SOURCE LINE # 7
MOV A,R5
ADD A,R7
MOV R7,A
CLR A
RLC A
MOV R6,A
;---- Variable 'cc?042' assigned to Register 'R6/R7' ----
;   return(cc);
    ; SOURCE LINE # 8
; }
    ; SOURCE LINE # 9
?C0001:
RET
; END OF _add
END

我们还是将注释的部分删去,这样便于我们分析:

NAME ADD?
?PR?_add?ADD         SEGMENT CODE
PUBLIC _add
RSEG ?PR?_add?ADD
_add:
USING
MOV A,R5
ADD A,R7
MOV R7,A
CLR A
RLC A
MOV R6,A

RET
END

现在我们首先来看函数名,上面我们讲过的那个无参数函数delay()的调用,产生的汇编子函数名就是delay,而这次我我们原来C的函数名add变成了汇编的_add。前面多了个下划线,这就是有参数函数的特征。C语言函数名转变为汇编函数名的规律为:无参数传递时void func(void)----FUNC。寄存器参数传递时char func(char)----_FUNC。再入函数使用时void func(void) reentrant----_?FUNC。
  不过这些名字的变化规律记没记住好象关系并不大。我们想要用到汇编调用时,就先用C做个假函数然后产生汇编文件名字就自然出来,并不用我们去管它的命名,然后去修改成我们想做的汇编程序就行了。
  但是,这参数传递的位置规律就必须得知道,否则你就无法使用这个汇编了,我们看上面的汇编程序,第一句是将寄存器R5的值传到A中,第二句将A与寄存器R7相加,第三句将相加的结果A的值传给R7,后面的几句是将刚才相加的进位值C,传给R6,然后返回。对照本篇最上面给的那两张表我们可以看出C子函数add.c的第一个参数aa被传到了汇编的R7,第二个参数bb被传到了R5,将它们相加后,返回值的低位交给了R7,高位交给了R6。完全符合参数传递表和返回值表所述。下面我们将汇编子程序另存为asm文件后替换掉原来的C子程序:

编译、烧写后运行:

这个加法函数我们没有改动任何参数当然运行起来是不会错的,下面我们将在汇编里将它改成乘法试试,
将标号_add:下面的语句全都改掉,程序如下?

NAME ADD?
?PR?_add?ADD         SEGMENT CODE
PUBLIC _add
RSEG ?PR?_add?ADD
_add:
USING 

MOV A,R7
MOV B,R5
MUL AB   ;A与B相乘,乘积的高位值在B中,低位值在A中
MOV R7,A  ;将低位值传给R7
MOV R6,B  ;将高位值传给R6

RET
END

上面的改动我们已将原来的加法依照寄存器的传递规律改为乘法函数,看看是否还能正常运行并正确,改完后仍编译烧写运行:

哈哈!完全正确。

总结:我们可以经常性地采用在C中建立简单的子函数,转成汇编后看它的操作方法和传递规律,慢慢地熟悉掌握和运用如何在C中调用汇编函数。

KEIL的混合编程操作的更多相关文章

  1. keil C语言与汇编语言混合编程

    C与汇编混合编程主要有以下几种:(1)C语言中嵌入汇编(2)无参数传递的函数调用(3)有参数传递的函数调用 一.C语言中嵌入汇编 1.在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码: #prag ...

  2. KEIl混合编程步骤详解

    一.在keil中C函数调用汇编函数: 主要思路:先用C来编写所要实现及调用的汇编函数,然后由此C函数生成相应的汇编代码,这样我们就可以不用去管混合编程调用时复杂的函数接口,我们只要修改相应汇编函数中的 ...

  3. VC 与Matlab混合编程之引擎操作详解

    Visual C++ 是当前主流的应用程序开发环境之一,开发环境强大,开发的程序执行速度快.但在科学计算方面函数库显得不够丰富.读取.显示数据图形不方便. Matlab 是一款将数值分析.矩阵计算.信 ...

  4. C51与汇编语言混合编程

    函数内部混合编程 若想在C语言函数内部使用汇编语言,应使用以下Cx51编译器控制命令: #pragma asm ; Assembly code #pragma endasm  功能作用:asm和end ...

  5. 单片机C 语言与汇编语言混合编程

    在单片机应用系统设计中,过去主要采用汇编语言开发程序. 汇编语言编写的程序对单片机硬件操作很方便,编写的程序代码短,效率高,但系统设计的周期长,可读性和可移植性都很差.C语言程序开发是近年来单片机系统 ...

  6. 如玫瑰一般的PHP与C#混合编程

    故事背景是这样的,有一套项目,服务器端是用C#写的,为了完成某种事情,它需要使用到一个组件,这个组件很小但很重要,很不巧的是,这个这个组件是用PHP语言写的,如果为了使用这个组件而专门搭建一个PHP的 ...

  7. C#Matlab混合编程类 初始化问题解决方法

    ************** 异常文本 ************** System.TypeInitializationException: “myPlus.matClass”的类型初始值设定项引发异 ...

  8. 【转载】ANSYS的APDL与C语言混合编程(实例)

    原文地址:http://www.cnblogs.com/lyq105/archive/2010/05/04/1727557.html 本文讨论的不是利用C语言为ANSYS写扩展(或者说是用户子程序), ...

  9. 玩转cocos2d-x lua-binding, 实现c++与lua混合编程

    引言 城市精灵GO(http://csjl.teamtop3.com/)是一款基于cocos2d-x开发的LBS社交游戏, 通过真实地图的探索, 发现和抓捕隐匿于身边的野生精灵, 利用游戏中丰富的玩法 ...

随机推荐

  1. POJ3581---Sequence 后缀树组

    题意:n个数字组成的序列,第一个数字最大,,把序列分成3部分,每个部分分别翻转,输出翻转后字典序最小的序列.. 后缀数组变一下,,先求出 第一个分割的位置,,然后再求一次后缀数组,,求出第二个位置.. ...

  2. 型牌男装施春蕾:分拆让马云对淘宝定位更清晰--互联网 -- CCTIME飞象网

    型牌男装施春蕾:分拆让马云对淘宝定位更清晰--互联网 -- CCTIME飞象网 型牌男装施春蕾:分拆让马云对淘宝定位更清晰          2011年6月17日 13:16  CCTIME飞象网   ...

  3. HDFS文件系统的操作

    package com.bank.utils; import java.io.BufferedInputStream;import java.io.BufferedOutputStream;impor ...

  4. ORA-00314,redolog 损坏,或丢失处理方法

    alertsid.log报错信息: Fri Sep 27 15:18:39 2013 Started redo scan Fri Sep 27 15:18:39 2013 Errors in file ...

  5. mac 环境下使用virtual box 虚拟机(win7)与主机之间互相ping通

    首先选择virtual box设置网络连接方式为网桥 混杂模式设置为全部允许 如下图: 进入虚拟机把虚拟机IP设置和主机在一个网段.如主机是192.168.1.100虚拟机可以设置为192.168.1 ...

  6. I/O输出端口照明LED

    方案特点:I/O输出端口照明LED.而区间0.2秒闪烁!(非计时器延迟) (P1.0销被连接到LED) LED EQU P1.0 ;宏定义 ORG 0000H LJMP MAIN ORG 0200H ...

  7. iOS、mac开源项目及库(感谢原作者的分享)

    目录 模糊效果 富文本 表相关 HUD与Toast 其他UI 其他动画 网络测试 网络聊天 Model 数据库 PDF 摄像照相视频音频处理 消息相关 消息推送服务器端 版本新API的Demo 测试及 ...

  8. uploadify3.1 参数 中文详解

    langFile: 'http://www.static-xxx.nu/uploader/uploadifyLang_en.js',//语言包的路径,能设置所有的提示文字 swf: 'http://w ...

  9. RDD 重新分区,排序 repartitionAndSortWithinPartitions

    需求:将rdd数据中相同班级的学生分到一个partition中,并根据分数降序排序. 此实例用到的repartitionAndSortWithinPartitions是Spark官网推荐的一个算子,官 ...

  10. Asp.Net--回调技术

    实现回调技术需要以下步骤: 1.实现ICallbakEventHandler 2.实现接口中的方法:RaiseCallbackEvent 3.实现GetCallbackResult 方法 解释 参数 ...