|
数据类型 | 位数 | 字节数 | 范围 |
bit | 1 | 0~1 | |
signed char | 8 | 1 | -128~+127 |
unsinged char | 8 | 1 | 0~255 |
enum | 8/16 | 1/2 | -128~+127或-32768~+32767 |
signed short | 16 | 2 | -32768~+32767 |
unsinged short | 16 | 2 | 0~+65535 |
signed int | 16 | 2 | -32768~+32767 |
unsigned int | 16 | 2 | 0~+65535 |
signed long | 32 | 4 | -2147483648~+21473647 |
unsigned long | 32 | 4 | 0~4294967295 |
float | 32 | 4 | ±1.175494E-38~±3.402823E+38 |
sbit | 1 | 0~1 | |
sfr | 8 | 1 | 0~255 |
sfr16 | 16 | 2 | 0~65535 |
存储类型 | 与硬件存储器空间的对应关系 |
code | 程序存储器:使用MOVC @A+DPTR指令访问 |
data | 直接寻址的内部数据存储器:访问速度最快(128字节) |
idata | 间接访问的内部数据存储器:可以访问所有的内部存储器空间(256字节) |
bdata | 可位寻址的内部数据存储器:可以字节方式也可以位方式访问(16字节) |
xdata | 外部数据存储器(64KB),能过MOVX @DPTR指令访问 |
pdata | 外部数据存储器的一页(256字节),使用MOVX @Ri指令访问 |
5.3 C语言程序结构
程序入口为main函数,每个函数内部可以使用结构化程序设计技术的三种结构
5.3.1函数
1、函数定义
C语言一般采用模块化设计,最基本的模块就是同函数表示,MCS-51的C语言程序中,在定义函数时还可以指定是否为中断算是函数、是否为可重入函数,可以选择工作寄存器组以及确定其存储模式,函数定义的基本格式如下
[返回值类型] 函数名称(表达式) [{small ︱ compact ︱ large}]
[reentrant] [interrupt n] [using n]
若省略返回值类型部分,则默认为整形( int),可以指定该函数的存储模式,以取代默认值;若使用using,编译程序将产生切换工作寄存器组的代码;对于有返回值的函数,不能使用using,因为返回值是通过寄存器传递的。
2、参数传递
参数用于几函数传递数据,作为函数的输入,MSC-51参数传递是通过存储器和寄存器传递的,通过寄存器传递速度快是默认的传递方式,传递时所使用的寄存器分配如下表,这时最多能3个参数,若函数参数较多,寄存器不足以传递所有参数则使用固定地址的存储器单元作为函数的存放位置,当第一个参数是bit型时,无法用寄存器传递参数。若参数个数不超过3个,可以将bit型参数放在参数表最后
表5-4传递参数的寄存器分配
参数个数 |
char或字节指针 |
int或2个字节指针 |
long或float |
通用指针 |
1 |
R7 |
R6&R7 |
R4~R7 |
R1~R3 |
2 |
R5 |
R4&R5 |
R4~R7 |
R1~R3 |
3 |
R3 |
R2&R3 |
|
R1~R3 |
3、返回值
与传递参数不同,函数的返回值总是通过寄存器送回的如下表
表5-5函数返回值所用寄存器分配
返回值类型 |
寄存器 |
描述 |
bit |
CY标志 |
无 |
char,unsigned char,或1个字节指针 |
R7 |
无 |
int,unsigned int,或2个字节指针 |
R6&R7 |
最高有效位在R6中,最低有效位在R7中 |
long或unsigned long |
R4~R7 |
最高有效位在R4中,最低有效位在R7中 |
float |
R4~R7 |
32位IEEE格式 |
通用指针 |
R1~R3 |
存储器类型在R3中,最高有效位在R2中,最低有效位在R1中 |
4、内部函数和外部函数
如果一个函数只能在其定义的文件中被调用,则称为内部函数,也称为静太函数,定义内部函数时需要用static存储类型说明
Static unsigned int fun(unsigned char the_byte,bit the_flag)
函数fun在包含其定义的文件外不可访问。
允许在其它文件中调用的函数为外部函数,可以使用extern存储类型说明符指明。函数定义时若无存储类型说明,默认为外部函数。
5、可重入函数
单片机的C编译程序通常的局部变量分配在存储器的固定位置,如果正在执行该函数时发生了中断,而中断服务程序中也调用该函数,先前的局部变量值便会被破坏,类似的情况在实现函数递归调用时也会发生,对于一个函数,如果确实需要递归调用,或者确实非中断服务程序代码与中断服务程序都要调用,应当将它定义为可重入函数,使编译程序产生能够保护局部变量的代码,可以重入函数是使用reentrant来说明。
例:有一个延时函数,在程序中多次被调用,包括中断服务程序,请将其定义为可重入函数。
void delay(void)reentrant
{
int i;
for(i=0;i<1000;i++)
;
}
其实若非递归调用,也可以不编写可重入函数,而是将同一函数改写为非中断服务程序调用和中断服务程序调用的两个函数,变量所需存储空间没有显著减少,代码脚加长了
6、中断处理函数
中断处理函数也称作中断服务程序,是CPU响应中断后要执行的一段程序,在C语言中组织成一个函数的形式,编写中断处理函数时,程序员只需要中断类型号和寄存器组的选择,编译程序会自动产生中断向量和返回地址的入栈及出栈代码。在函数定义时可以使用interrupt将其指定为一个中断处理函数,还可以用using分配中断处理函数所使用的寄存器组。
例:说明下面函数定义的作用
unsigned int interruptcnt;
unsigned char second;
void timer0 (void) interrupt 1 using 2
{
if(++interruptcnt= =1000)
{
second++;
interruptcnt=0;
}
}
函数timer0是一个中断处理函数,所对应的中断类型号为1,使用第二组工作寄存器
7、intrinsic函数
在MSC-51 C语言中,intrinsic函数是一类用汇编语言代码实现的短小函数,若C语言程序中有对intrinsic函数的调用,编译程序将会直接用被调用函数代码替换函数调用语句。
常见intrinsic函数的原型如下,它们一般在intris.h文件中
extern void _nop_ (void);
extern bit _testbit_ (bit);
extern unsigned char _cror_ (unsigned char,unsigned char);
extern unsigned int _iror_ (unsigned int,unsigned int);
extern unsigned long _lror_ (unsigned long,unsigned long);
extern unsigned char _crol_ (unsigned char,unsigned char);
extern unsigned int _irol_ (unsigned int,unsigned int);
extern unsigned long _lrol_ (unsigned long,unsigned long);
这些函数名称前后都有下划线,这是与其它库函数的最明显区别,以上函数实现的功能分别是空操作、位测试以及字符型、整形和长整形数据的左、右移位。
例:编写代码,若位变量flag值为1,则8位位数据data8右移两位并将flag清零,否则左移3位。
if(_teatbit_(flag))
data8=_cror_(data8,2);
else
data8=_crol_(data8,3);
5.3.2流程控制
1、分支,C语言有两种分支方式
A、if语句
If(表达式) 语句1
这种形式实现了单分摊结构,若表达式值非0,则执行后面的语句1,然后继续往下执行,若表达式的值为0,则跳过语句1直接往下执行
两个分支的if语句形式为
if(表达式)
语句1
else
语句2
若表达式值是非0,则执行后的语句1,然后执行表达式语句2后面的语句,若表过式的值为0则跳过语句1执行语句2,然后继续执行下面语句
多分支if语句形式为
if(表达式)
语句1
else if(表达式2)
语句2
else if(表达式3)
语句3
…………..
else
语句n
多选结构n个语句中只能执行一个,即第一个值非0表达式后面的语句。
以上三种形式中,所有语句都可以是复合语句,即用花括号引起来的语句组。
B、switch-case结构
当选择较多时使用if语句和程序结构会变得臃肿,switch-case结构是比较简洁的写法形式为
switch(表达式)
{
csae 常量表达式1:语句组1;break;
csae 常量表达式2:语句组2;break;
………
csae 常量表达式n:语句组n;break;
default; 语句组n+1;break;
}
Switch后的表达式可以是整形或字符型、枚举型数据,case后的各常量大达式须与其类型相同或可以相互转换,当前者的值与某一case后表达式的值相等时,执行其后的语句组,然后执行break退出switch语句,若所有case后表达式与之皆不相等,则执行default后语句组,case后表达式须各不相乖。
2、循环
C语言中实现循环结构的语句也有多种
A、 goto语句:用来实现转移,结合if语句,可以实现简单的循环,类似于指令系统中的条件转移指令的作用,但是goto语句可以转向程序中任何位置,所以受到结构化程序设计支持者的强烈抵制
B、 while结构,形式为
while(表达式)
语句
其中表达式为循环条件,语句构成循环体。若循环条件值非0,则执行循环体,一种常见的形式为
while(1)
{
……….
}
这种形式可以称为无限循环,一般单片机软件就是这种形式,如下代码
while(!(P1&0x01))
;
实现的是等待P1.0为1,循环体部分为空语句,循环条件是输入的P1值最低有效位为0.
C、 do….while结构,形式为
do
语句
While(表达式);
不像while结构先判断条件,do….while结构是先执行一次语句(循环体),然后再判断条件,若条件表达式值非0,则继续下次循环。
D、for结构,for结构是使用最灵活的循环控制语句,形式为
for(表达式1;表达式2;表达式3)
语句
for结构的执行过程为:先对表达式1求值;再对表达式2求值,若表达式2的值是非0,则执行一次语句,然后对表达式3求值,再一次对表达式2求值,若非0,则在此形成循环,直到表达式2的值为0,则循环结束。如下代码
for(;P1&0x01;)
;
实现的也是等待P1.0输入为1,而
for(i=0;(i<10000)&&(P1&0x01);i++)
;
实现的是有时间限制的等待P1.0为1,具体时间可以通过检查编译产生的代码计算得到,或者在仿真器上设置断点观察得知。
E、 break和continue语句
break语句不公能够跳出switch结构,还可以从循环体中跳出,提前结束循环而执行循环后面的语句。Break只能用在循环语句(包括while、do….whilet和for结构)和switch语句中。
Continue语句则是提前结束本次循环,跳过循环体中continue后面未执行的语句,接着进行一次循环条件的判定。
break和continue语句其实是结构化程序设计方法中实现非结构化的一种手段,在退出循环或提前结束循环的条件不易表达时,这类语句可以使程序更容易理解。
5.3.3输入与输出
一些C开发环境提供了流式输入/输出函数,可以实现通过串口或用户自定义I/O接口的输入/输出操作,例如getchar、gets、scanf、putchar、puts、printf等,输入/输出功能需要调用_getkey和putchar两个函数,这两个函数的默认实现是通过串行口实现的,所以如果使用输入/输出函数,还需要在程序中加入一些代码,以便调用时已经对串行口进行了适当的初始公工作。
例:说明以下代码的运行结果
#include <reg51.h> /*初始化时要用到SFR*/
#include <stdio.h> /*引入输入/输出函数原型*/
Void main(void)
{
int x,y /*变量*/
SCON =0x50; /*开始对串行口的初始化代码*/
PCON &=0x7F;
TMOD &=0xCF;
TMOD &=0x20;
TH1 =0xFD;
TR1 =1;
TI =1; /*初始化结束*/
While(1)
{
scanf(“%d%d,&x,&y); /*输入*/
printf(“x=%04x,y=%04x\n”,x,y); /*输出*/
}
}
该程序接收用户输入的十进制数值,然后从串行口以十六进制格式输出
程序员可以根据系统输入/输出接口的配置情况重写_getkey和putchar两个函数,其他函数功能保持不变。
5.3.4程序的入口
C语言程序的入口是main函数,而单片机复位后是从0000H开始执行代码程序,main函数位于系统程序存储器的何处呢?下面观察一下例子程序的可执行代码
0000H |
010012 |
LJMP |
0012H |
0003H |
EF |
MOV |
A,R7 |
0004H |
C4 |
SWAP |
A |
0005H |
540F |
ANL |
A,#0FH |
0007H |
75F00A |
MOV |
B,#0AH |
000AH |
A4 |
MUL |
AB |
000BH |
FE |
MOV |
R6,A |
000CH |
EF |
MOV |
A,R7 |
000DH |
540F |
ANL |
A,#0FH |
000EH |
2E |
ADD |
A,R6 |
000FH |
FF |
MOV |
R7,A |
0010H |
22 |
RET |
|
0012H |
787F |
MOV |
R0,7FH |
0014H |
E4 |
CLR |
A |
0015H |
F6 |
MOV |
@R0,A |
0016H |
DBFD |
DJNZ |
R0,0015H |
0018H |
758108 |
MOV |
SP,#08H |
001BH |
02001E |
LJMP |
001EH |
001EH |
7F56 |
MOV |
R7,#56H |
0020H |
120003 |
LCALL |
0003H |
0023H |
8F08 |
MOV |
08H,R7 |
0025H |
80FE |
SJMP |
0025H |
最左边一列是程序存储器地址,第二列是指令机器码,最右边一列为助记符表示,可以看到单片机复位后,先转移到0012H,将内部RAM单元00H~7FH清零,置SP为08H后,转移到main函数(001EH)处执行,即在main函数执行之前,已经做了一些初始化处理。
这是默认的初始化操作,至于堆栈,取决于编译程序在内部RAM中为局部变量分配空间的大小,若有在main函数执行之前就应当初始化的资源,或者需要将存储区初始化谜团特定的值,程序员可以在汇编语言程序STARTUP.A51中修改或添加代码,在使用C语言开发的单片机软件中,单片机程序的入口其实还是0000H,在STARTUP.51中初始化代码的最后一条指令才转向main函数执行。
5.4 C语言与汇编语言的混合编程
C语言和汇编语言各有优缺点,C语言中数据类型丰富,程序结构清晰,但是在执行速度、精确定时、控制硬件等方面不如汇编语言方便,如果要在各方面都获得满意的结果,可以使用C语言与汇编语言的混合编程。
用C语言调用汇编语言程序时,被调用函数(汇编语言函数)要在调用函数(C语言函数)所在文件中说明,对于汇编语言程序有以下要求
1、 要使用SEGMENT伪指令定义可重定的CODE段。
2、 要根据不同的情况对函数名进行转换,见下表5-6.
3、 须使用PUBLIC伪指令将被调用函数说明为外部可用函数
4、 若有参数传递,按照表5-4所列的规则使用参数。
5、 若有返回值,按照表5-5所列的规则存储寄存器。
表5-6函数名转换规则
函数首部 |
符号名 |
说明 |
void func(void) |
FUNC |
无参数传递或不含寄存器参数的函数名不作改变 |
void func(char) |
_FUNC |
带寄存器参数的函数名加“_“ |
void func(void) reentrant |
_?FUNC |
可重入函数前加“_?” |
例:编写汇编语言函数max,参数为两个8位无符号数,功能是求出其中的大数返回。
在C语言中可按如下方式声明和调用
extern unsigned char max(unsigned, unsigned); /*声明*/
void main(void)
{
unsigned char x,y;
x=130;y=131;
x=max(x,y); /*调用*/
while(1);
}
两个参数分别在R7和R5中传递到子程序,返回值应保存在R7中,汇编语言程序文件可如下编写,
PUBLIC _MAX ;声明
MIXED SEGMENT CODE ;定义一个可重定位的段MIXED
ESEG MIXED ;选择MIXED为当前段
_MAX:
MOV A,R7 ;第一个参数
CLR C
SUBB A,R5 ;减去第二个参数
JNC _MAX_RET ;无借位,第一个参数值大
MOV A,R5 ;有借位,第二个参数值大
MOV R7,A ;返回值在R7
_MAX_RET:
RET
EDN
例:编写汇编语言函数delayms,参数为一个8位无符号数,功能是按照参数指定的毫秒数实现延时。这个函数有参数传递,但是无返回值。
汇编语言文件可如下编写
PUBLIC _DELAYMS ;声明
HAHA SEGMENT CODE ;定义一个可重定位的段HAHA
RSEG HAHA ;选择HAHA为当前段
_DELAYMS:
MOV R6,#2 ;以下实现约1ms的延时
_DELAY_NEXT:
MOV R5,#248
DJNZ R5,$
DJNZ R6,_DELAY_NEXT
DJNZ R7,_DELAYMS
RET
END ;返回
例:编写汇编语言函数delay10ms,没有参数,功能是延时10ms,这个问题与上例的区别在于无参数传递,所以函数名无需加“_”。
汇编语言文件可写成如下形式
PUBILIC EDLAY10MS
MIXED SEGMENT CODE
RSEG MIXED
DELAY10MS:
MOV R6,#20
DELAY_NEXT:
MOV R5,#248
DJNZ R5,$
DJNZ R6,DELAY_NEXT
RET
END
以上例子中,只是各编写了一个供C语言程序调用的汇编语言函数,若需要多个,也是可以写在同一个汇编语言程序文件中,汇编语言程序中也可以调用C语言函数,但不常用。