本书部分章节试读——8.1,8.2,8.3,8.4
第8章 混合编程(部分)
尽管用C语言编写单片机程序有很多优势,但在某些特定的场合,例如在特别强调时序的场合或者特别强调效率的场合,我们仍会考虑是否要改用汇编语言。这时,采用两种语言的混合编程应该是一种比较好的选择。本章我们将介绍C51与汇编语言的混合编程。
混合编程的总体思路是:采用C语言来编写程序主体,采用汇编语言来编写局部程序。至于具体的编程手段,主要有以下两种:
(1)在C51源程序中直接嵌入汇编行或汇编段落。
(2)编写具有C51接口的汇编模块,供C程序来调用。
无论采用哪种方法,都要考虑两种语言间的衔接,这种衔接最终将在汇编一级实现。显然,采用后一做法应该更有优势,因为它更符合模块化的编程理念,其代码接口和数据接口的规律性更强,更容易被人们所掌握。
本章,我们将先介绍C15程序的制作背景和目标程序中“段(Segment)”的概念,然后再介绍这两种混合编程的手段,并为它们各举一个例子。
8.1 C51程序的制作过程
一个程序项目可以由头文件、源文件、可重定位目标文件、库文件等多个文件构成。对汇编/C源文件来说,每个文件就是一个汇编/编译单位,经过汇编程序/编译程序的翻译得到相应的目标文件,该文件称“可重定位目标模块文件”或“浮动目标模块文件”。虽然文件中的代码已经是可执行代码,但此时尚不能直接执行,原因是:
(1)它只是程序中的1个或几个模块,还不是一个完整的程序。
(2)其中的代码和数据只拥有相对的存储地址,还不能准确定位到存储器中。
以C51的编译为例,源文件经翻译后生成数据目标和程序目标,它们均以“段”的形式存在。其中,保存代码的段称“代码段”,保存数据(常量和变量)的段称“数据段”。由于数据拥有不同的属性,所以存在着多种数据段,其情况远比代码段要复杂。
各种段,按定位属性分“可重定位段”和“绝对段”两类。
(1)可重定位段——其地址是相对的(相对某个参考地址),在后续的连接操作中,不同文件中具有相同存储属性的可重定位段要进行合并,然后再由定位器来进行定位。我们将合并前的段称“部分段”,合并后的段称“完整段”。为此,编译器会为文件中的每个可重定位段起个段名,名中包含存储类型和所在位置等信息。在C51中,段的存储属性采用段名前缀来进行表示,具有相同存储属性的段其段名前缀相同,我们称它们为“同名段”。
(2)绝对段——其地址在连接前已经确定,定位器将据此为它分配地址。绝对段不进行段的合并操作,也不需要为它起段名。
接下来的工作由连接定位器进行,它主要做两件事:
(1)连接——将目标文件中的所有模块,以及库文件中被调用的模块连接起来,即,将相关文件中的同名段合并为完整段。
(2)定位——在存储器中为各完整段分配绝对地址。
连接定位器生成的目标文件称绝对目标文件。在μVision环境下,该文件支持以下3种应用:
(1)在PC机上通过软件模拟器进行软件模拟调试。
(2)通过PC机和在线仿真器/在线调试器下载到仿真芯片,进行硬件仿真调试。
(3)烧写到目标芯片。烧写前,一般要先用转换程序将它转换为.HEX文件,然后再用编程器来烧写。.HEX文件为文本文件,由intel公司定义,它可以被所有编程器所识别和支持。
除以上文件产物外,编译器和连接定位器还生成其他一些文件产物,包括列表文件(.LST)和存储映像文件(.MAP/.M51)。前者由编译器生成,可提供目标代码、编译出错/警告、程序中符号的交叉引用、汇编源代码等信息;后者由连接定位器生成,以段的形式提供存储器的使用情况和各种符号在存储器中的位置。
8.2 段的属性和段名前缀
经编译器生成的段具有以下属性:
(1)类型——段的存储属性,如code、data、pdata、xdata、bit等。
(2)基址——段的起始地址。
(3)长度——段的长度。
(4)定位——段的定位要求。绝对段为ABSOLUTE;可重定位段有以下几种:
○ INPAGE——要求代码段的范围在256B之内
○ INBLOCK——要求代码段的范围在2KB之内
○ PAGE——要求数据段总量不超出256B
○ BITADDRSSABLE——要求数据段定位在可位寻址区
○ UNIT——允许段可以从任意地址开始
(5)段名——每个可重定位段都有一个段名,但绝对段没有段名。段名中包含段的存储属性和段所在位置等信息。段的存储属性用段名前缀来加以表示。系统形成的段名前缀为两个大写的英文字母,并用字符“?”来界定。表8.1罗列了各种段名前缀,并说明了它所代表的可重定位段的存储类型。

8.3 数据目标
编译后,程序中的常量和全局变量形成数据目标。而且,不同的编译模式会生成不同的数据目标。编译器以“?段名前缀?所在文件名”的形式来命名相应的数据段。表8.2给出了各种数据目标的段名前缀。

8.4 程序目标
编译后,程序中的函数及函数中使用的数据(包括参数和局部变量)将形成程序目标。需要提醒读者的是,采用不同的编译模式和优化方式,将得到不同的程序目标。
8.4.1 程序目标中的3种段
不同的编译模式,所形成的程序目标中的段将有所不同,表8.3列出了3种编译模式下程序目标中的3种段(代码段及两种数据段)。

从表8.3中我们看到,不同编译模式下所形成的程序目标,可能包含以下3种段:① 代码段,② 字节型局部变量和字节型存储器参数段(非寄存器参数),③ 位局部变量/位参数段。其段名前缀有以下规律:
(1)代码段的段名前缀总是“?PR?”,表明不管采用哪种编译模式,它的存储总在code区。
(2)位局部变量/位参数段的段名前缀总是“?BI?”,表明不管采用哪种编译模式,它的存储总在bit区。
(3)函数中定义的字节型局部变量与字节型存储器参数,被存放在同一段中,其存放位置随编译模式而变化,所以其段名前缀也随编译模式而变化,在smal、compact、large等3种编译模式下,其段名前缀分别是“?DT?”、“?PD?”、“?XD?”,表示它们的存储区域分别是data、pdata、xdata。
值得注意的是段名中的函数名,它也用“?”来界定,而且根据函数的以下属性在函数名前加有不同的前缀:
(1)函数无参数或者不用寄存器来传递参数——此时函数名无前缀。
(2)函数用寄存器传递参数——此时函数名加前缀“_”。
(3)函数为可重入函数——此时函数名加前缀“_?”。
表8.4给出3种函数的具体举例。注意编译前它们的函数名均为“func”,但编译后函数名前加有不同的前缀。

8.4.2 程序目标中的函数参数
早期的C51编译器,曾通过分配固定的存储位置来传递函数参数。当前的C51编译器首先选用工作寄存器来传递函数参数(这种参数称寄存器参数)。但通过寄存器传递的参数,在最佳组态下最多也就是3个,其中还不包括位参数。因此,所有不能通过寄存器传递的参数,仍将通过编译器分配的固定存储位置来进行传递,这种参数我们称存储器参数。
实际上,函数的存储器参数分字节型和位型两种,它与函数中具有相同存储属性的局部变量安排在同一数据段中。表8.5给出存储器参数在相应段中的符号地址。其中,“?函数名?BYTE”为字节型存储参数的首地址,“?函数名?BIT”为位型存储器参数的首地址。

此外,如果局部变量/存储参数数据段选择了可覆盖模式,则它可以被连接定位器覆盖和重复使用,以进一步节约单片机的存储资源。
下一页 源程序下载1——用4个按键实现键控24小时整点报时数字钟(C51)


