KEIL的混合编程操作
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的混合编程操作的更多相关文章
- keil C语言与汇编语言混合编程
C与汇编混合编程主要有以下几种:(1)C语言中嵌入汇编(2)无参数传递的函数调用(3)有参数传递的函数调用 一.C语言中嵌入汇编 1.在 C 文件中要嵌入汇编代码片以如下方式加入汇编代码: #prag ...
- KEIl混合编程步骤详解
一.在keil中C函数调用汇编函数: 主要思路:先用C来编写所要实现及调用的汇编函数,然后由此C函数生成相应的汇编代码,这样我们就可以不用去管混合编程调用时复杂的函数接口,我们只要修改相应汇编函数中的 ...
- VC 与Matlab混合编程之引擎操作详解
Visual C++ 是当前主流的应用程序开发环境之一,开发环境强大,开发的程序执行速度快.但在科学计算方面函数库显得不够丰富.读取.显示数据图形不方便. Matlab 是一款将数值分析.矩阵计算.信 ...
- C51与汇编语言混合编程
函数内部混合编程 若想在C语言函数内部使用汇编语言,应使用以下Cx51编译器控制命令: #pragma asm ; Assembly code #pragma endasm 功能作用:asm和end ...
- 单片机C 语言与汇编语言混合编程
在单片机应用系统设计中,过去主要采用汇编语言开发程序. 汇编语言编写的程序对单片机硬件操作很方便,编写的程序代码短,效率高,但系统设计的周期长,可读性和可移植性都很差.C语言程序开发是近年来单片机系统 ...
- 如玫瑰一般的PHP与C#混合编程
故事背景是这样的,有一套项目,服务器端是用C#写的,为了完成某种事情,它需要使用到一个组件,这个组件很小但很重要,很不巧的是,这个这个组件是用PHP语言写的,如果为了使用这个组件而专门搭建一个PHP的 ...
- C#Matlab混合编程类 初始化问题解决方法
************** 异常文本 ************** System.TypeInitializationException: “myPlus.matClass”的类型初始值设定项引发异 ...
- 【转载】ANSYS的APDL与C语言混合编程(实例)
原文地址:http://www.cnblogs.com/lyq105/archive/2010/05/04/1727557.html 本文讨论的不是利用C语言为ANSYS写扩展(或者说是用户子程序), ...
- 玩转cocos2d-x lua-binding, 实现c++与lua混合编程
引言 城市精灵GO(http://csjl.teamtop3.com/)是一款基于cocos2d-x开发的LBS社交游戏, 通过真实地图的探索, 发现和抓捕隐匿于身边的野生精灵, 利用游戏中丰富的玩法 ...
随机推荐
- 【转】锋狂百科:手机也能接外设 OTG技术详解
原文网址:http://www.gfan.com/review/2014030346245.html 说到USB数据接口,相信大家并不陌生,在日常使用各类数码产品时我们几乎都会用到它.例如最常用的U盘 ...
- hdu3534,个人认为很经典的树形dp
题目大意为,求一个树的直径(最长路),以及直径的数量 朴素的dp只能找出某点开始的最长路径,但这个最长路径却不一定是树的直径,本弱先开始就想简单了,一直wa 直到我看了某位大牛的题解... 按照那位大 ...
- cpppp
- windows server2012域服务器降级的方法
最近在一个新的网络环境里建立windows域服务器,准备建立主.备两台域服务器.在一台新服务器上面安装了windows2012R2并配置了DC服务.域和林的级别设置为Windows2012R2级别.在 ...
- Ajax解析
1.Ajax Asynchronous(异步的) javascript and xml 技术组成: CSS + xml +JavaScript +DOM Ajax核心对象: XmlHttpReques ...
- wsdl文件结构分析
WSDL (Web Services Description Language,Web服务描述语言)是一种XML Application,他将Web服务描述定义为一组服务访问点,客户端可以通过这些服务 ...
- (转)Linux整合apache和tomcat构建Web服务器
原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://wenzhongxiang.blog.51cto.com/6370734/1285 ...
- 玩转iOS开发 - 数据缓存
Why Cache 有时候.对同一个URL请求多次,返回的数据可能都是一样的,比方server上的某张图片.不管下载多少次,返回的数据都是一样的. 上面的情况会造成下面问题 (1)用户流量的浪费 (2 ...
- 2.x最终照着教程,成功使用OpenGL ES 绘制纹理贴图,添加了灰度图
在之前成功绘制变色的几何图形之后,今天利用Openg ES的可编程管线绘制出第一张纹理. 学校时候不知道OpenGL的重要性,怕晦涩的语法.没有跟老师学习OpenGL的环境配置,现在仅仅能利用coco ...
- 用C/C++扩展你的PHP(转)
简 介 英文版下载: PHP 5 Power Programming PHP取得成功的一个主要原因之一是她拥有大量的可用扩展.web开发者无论有何种需求,这种需求最有可能在PHP发行包里找到.PHP发 ...