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. CCF 送货 + 欧拉路模板

    #include <bits/stdc++.h> using namespace std; stack<int> st; vector<]; ][]; ],cp[]; i ...

  2. Hibernate Validation使用示例及讲解

    Hibernate Validation使用示例及讲解 时间 -- :: ITeye-博客 原文 http://wdmcygah.iteye.com/blog/2174680 主题 Java 在项目开 ...

  3. ie浏览器中 overflow:hidden无作用的解决方案

    原因: overflow:hidden失效 当父元素的直接子元素或者下级子元素的样式拥有position:relative属性时,父元素的overflow:hidden属性就会失效. 我在ie内发现子 ...

  4. JS浏览器对象-History对象

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  5. solr 3.5 配置及server设置

    一.solr 的简单介绍 Apache Solr 是一个开源的搜索server.Solr 使用 Java 语言开发,主要基于 HTTP 和 Apache Lucene 实现.Apache Solr 中 ...

  6. 常用渗透性测试工具(Tools for penetration testing)

    常用渗透性测试工具 原文:http://hi.baidu.com/limpid/item/14a2df166adfa8cb38cb3068 对一个应用项目进行渗透性测试一般要经过三个步骤.  第一步, ...

  7. slide from one widget to another

    int main(int argc, char **argv) { QApplication app(argc, argv); QWidget panel; QVBoxLayout *l = new ...

  8. Android/Linux boot time优化

    基于analyze_boot.py分析Android/Linux的kernel boot时间 1.修改HiKey的BoardConfig.mk文件,使能initcall_debug,增加dmesg b ...

  9. C# 还原SQL数据库(非存储过程方式)

    Winform的代码,最好是在数据所在电脑上运行代码,不然会因为权限问题导致失败. 数据库备份: SqlConnection con = new SqlConnection("Data So ...

  10. jQuery中DOM操作

    1 定义:jquery中对DOM的操作就是对DOM元素进行增删查改操作 2 分类:      1)DOM Core(核心):用途广泛 支持多种编程语言 2)HTML DOM:代码简短 只用于处理web ...