80351(2):软件C语言编译器与人类程序员使用8051汇编语言编程的方法对比(上)
本帖最后由 杨为民 于 2024-12-3 17:25 编辑(1)通常所说的计算机语言是指二进制的机器码指令、低级的汇编语言(比如Ax51)和高级计算机语言(Cx51)。对于51类型的单片机,一般程序员用C来编写通常的单片机应用程序,高级程序员用汇编语言来实现单片机特殊应用。(2)由于计算机CPU只认识二进制机器码指令,因此就必须要有一个将人类的语言“翻译”成为机器码的软件--“编译器”。Keil的C51编译器是C语言编译器,它将C51规则的C语言翻译为基于Intel 8051指令集的A51汇编语言,Keil的A51编译器是汇编语言编译器,它将A51规则的汇编语言翻译为Intel 8051指令集的二进制机器码。(3)金水151同时是C语言和汇编语言编译器,它将C351规则的C语言翻译为基于金水明80151指令集的A351汇编语言,然后它将所有A351规则的汇编语言翻译为Intel 8051(或者80251)指令集的二进制机器码,这样就可以将WD-80151单片机“寄生”在STC的基于8051或者基于80251指令集的任何一款单片机上了。
一、C语言编译器对变量赋初值语句的不同实现方法(4)在计算机语言里,给一个变量赋一个数值称为“变量初值语句”。
其中第24行程序是给浮点数变量“A1”赋初始值实数“123.5678”。(5)上图中第28行到第33行是Keil的 C51 编译出来的8051汇编语言程序。第28行程序是将变量“A1”在XDATA空间的地址赋值给DPTR这个8051CPU中唯一的16位地址寄存器中。由于给4字节变量(浮点数和长整数)赋一个初值在C语言程序中常见的语句,因此Keil将这个操作写成一个公共库函数“?C?LSTKXDATA”,然后调用这个函数将其后面跟随的4个字节内容传送到DPTR指向的XDATA地址中去。上面程序中的大端数据“042F6E9DFH”是Keil编译器将单精度浮点数(32位)“123.5678”转换为IEEE754标准中的存储格式,其中:31位:符号位(S),0代表正数,1代表负数。30-23位:阶码(E),使用移码表示,偏移量为127。22-0位:尾数(M),纯小数,最高位隐含为1。(6)上图中第38行和第39行是金水151这个 C351 编译出来的80151汇编语言程序。第38行程序的“MVR”(80151指令)指令是将立即数“0x42F6E9DF”赋值给32位的寄存器“EAX”,第39行程序的“STX”(80151指令)指令是将32位的寄存器“EAX”里的值存放在XDATA空间4字节变量“A1”里。(7)对比C51编译器和金水151的C语言编译器的结果,可以看出C51编译器是将C语言直接编译为8051指令集的汇编语言,再通过A51汇编语言编译器编译为8051CPU可以执行的二进制机器码。而金水151编译器是将C语言编译为间接的“金水明80151虚拟指令集”的80151汇编语言,然后再通过A351汇编语言编译器将80151汇编语言编译为“寄主单片机”可以执行的二进制机器码,这样就可以将WD-80151单片机“寄生”到任何一种CPU的单片机上去了。(8)目前金水151编译器内置了8051/80251两种指令集的汇编语言编译器,因此只要在C语言程序中指定“寄主单片机”的单片机的类型和模式,就可以将WD-80151单片机“寄生”到STC的任一款单片机中去。
二、人类程序员对C语言变量赋初值语句的汇编语言实现方法(9)人类程序员编写一般的汇编语言程序,其解决的问题是具体的和局部,除了当年的MS-DOS和PC BIOS外,笔者很少看到有谁将整个应用程序用汇编语言来写的。程序员写汇编语言程序,通常是按照指令集来编写,下图是程序员对C语言变量赋初值语句用8051汇编语言的实现方法:
(10)上图中第65行和第66行是程序员编写的金水明80151汇编语言程序。因为金水明80151指令集是32位指令集和金水151编译器是32位编译器,所以在第65行程序的“MVR”(80151指令)指令中程序员可以将立即数直接写为浮点数的形式(中间含小数点的数),将“123.45678”直接赋值给32位的寄存器“EAX”,其中浮点数到十六进制数的转换由编译器自动完成,方便了程序员的编程工作。(11)Intel 8051指令集是一个设计十分精美的8位指令集,为了对16位XDATA空间地址和16位的CODE空间地址进行数据的存取操作,其安排了唯一的1个16指针寄存器DPTR,然后利用它对其指向的地址进行8位数据(字节)的读或者写操作。上图第49行到第60行是最简洁的对一个4字节XDATA变量赋值的汇编语言程序。从其中可以看到8051汇编语言的3个显著特点:1)由于8051是8位指令集,一次只能存取1个字节(8位)的数据,4字节变量数据要分4次去进行。2)对于浮点数“123.45678= 0x42F6E9DF”,程序员必须自己将其转换为十六进制数,然后再将其两位两位地写入XDATA空间。3)8051指令集最精彩点之一是对于DPTR的操作指令只有加1指令“INCDPTR”,没有减1指令“DECDPTR”,因此对4字节变量赋值应该从变量的低位地址(一般称为“变量地址”)开始进行。那么对于浮点数“123.45678= 0x42F6E9DF”的十六进制表示,是先放左边的“42”还是先放右边的“DF”,就是“大端存储格式”与“小端存储格式”的差别。Keil编译器与金水151编译器都采用大端存储格式。(12)8051指令集没有规定采用其指令集的单片机采用哪一种数据存储格式。比如标准的8052单片机寄存器地址采用的就是小端存储格式,它的定时器2的16位计数器T2的SFR地址是连续的,其低字节寄存器地址TL2在低端(TL2=0xCC)),其高字节寄存器地址TH2在高端(TH2=0xCD)。(13)各个 MCU 厂商自行扩展的寄存器地址因厂商而不同, STC单片机采用的是与 Keil一样的大端存储格式。比如:sfr TH4 = 0xd2;sfr TL4 = 0xd3;sfr TH3 = 0xd4;sfr TL3 = 0xd5;sfr TH2 = 0xd6;sfr TL2 = 0xd7;
(14)对于STC最新的单片机AI8051U和即将推出的AI8052U,其DPU32对32位运算操作数的存储格式就是大端存储格式:
这些DPU32硬件加速单元在8051寄存器“R4R5R6R7-R0R1R2R3”上实现了整数的32位乘除法和32位浮点数的加减乘除等运算以及三角函数等运算,成百倍地提高了这些运算的速度。而高位寄存器R0和R4正是位于DATA空间的低位地址00H和04H。
(15)STC单片机采用的80251指令集里的16/32位寄存器是大端存储格式,从下图可以看到其32位寄存器DR0、DR4与8位寄存器的对应关系:
从下图可以看到8位寄存器与内存地址的对应关系:
(16)笔者认为STC的基于8051单片机采用大端存储格式的决策是出于长远的战略考虑。因为在80251指令集中有一次性从内存读写4字节(32位)数据的指令:MOV DRk, Dir8 //DATA 空间MOV Dir8, DRk //DATA 空间MOV DRk, Dir16 //EDATA 空间MOV Dir16, DRk //EDATA 空间并且在STC32位单片机中DATA/EDATA总线是32位的,这些32位指令都是单周期(1T)指令。但是目前基于80251指令集的主流编译器只有Keil的C251编译器,因此为了实现STC8位单片机到STC32位单片机的硬件和软件的无缝过渡,STC单片机都采用了与Keil编译器一致的大端存储格式。(17)值得一提的是SDCC的数据存储是采用小端存储格式的。因此采用小端存储格式的SDCC编译器在用于大端存储格式的STC的8051类8位单片机的时候,多字节的内存数据和SFR寄存器尚可以分别读取高低字节的数据然后拼接,编译出正确的运行代码。(18)但是如果要将小端存储格式的SDCC编译器改造为用于32位寄存器是大端存储格式的80251指令集(或者其他指令集)的单片机的32位编译器,工作量肯定十分巨大,甚至是伤筋动骨。笔者猜想这也许是直到今天SDCC编译器都不支持32位单片机的主要原因。
{:4_165:} 确实如此,小端模式适用于单一宽度运算模式,程序最短小精练,效率最高。
但处于在混合宽度(8/16/32位)运算模式下,各种效果明显不及大端模式。
所以,除4/8位机外,16位以上的普遍使用大端模式。 LAOXU 发表于 2024-11-8 12:32
确实如此,小端模式适用于单一宽度运算模式,程序最短小精练,效率最高。
但处于在混合宽度(8/16/32位)运 ...
我初步阅读分析了SDCC的源码,在原C51基础上,增加点功能,比如双DPTR,修改增加各种库函数等,工作量不大。
但编译器解释程序改写成C251,工作量不小,很多地方要做相应处理。 学习了 如何获取A51? 只有C51和C151,如何下载A51?
页:
[1]