目录
前言
通过使用C语言可以进行对MCS—51单片机的开发,实现各种外设功能,我们称用来开发51单片机的“C语言”为C51。C51是基于标准C、专门为开发51单片机而设计的,与标准C有一定区别,有很多内容是专门为51单片机设定的。
本文主要介绍C51的特别之处, 由于C51的很多特有内容与51单片机的内部硬件结构是息息相关的,所以在阅读之前有必要先熟悉一下51单片机的硬件结构,可以参考下面内容:
一、C51与标准C的区别
异:
①库函数不同。标准C的库函数是PC来定义的,而C51的库函数是按照51单片机相应情况来制定的。
②数据类型的区别。C51中增加了几种特有的数据类型。
③变量的存储模式区别。C51中变量的存储模式与51单片机的存储结构紧密相关。
④输入输出处理不同。C51中的输入输出往往是通过串口实现的。
⑤函数使用方面不同。C51有特定的中断函数,以及main()函数无限循环。
同:
其他语法、程序结构及程序设计等方法与标准C语言一致,所以,在第一次玩51单片机时,就会认为它和所学的标准C语言一样。
二、C51数据类型
C51与标准C中的数据类型基本相同,主要不同在于C51中有专门针对51单片机的特殊功能寄存器(SFR)型和位类型。
基本数据类型 | 长度 | 取值范围 |
bit | 1位 | 0或1 |
sbit | 1位 | 0或1 |
sfr | 1字节 | 0~255 |
sfr16 | 2字节 | 0~65535 |
unsigned char | 1字节 | 0~255 |
(signed)char | 1字节 | -128~+127 |
unsigned int | 2字节 | 0~65535 |
(signed)int | 2字节 | -32768~+32767 |
unsigned long | 4字节 | 0~4294967295 |
(signed)long | 4字节 | -2147483648~+2147483647 |
float | 4字节 |
bit类型:位变量,要么是0,要么是1。
如:bit flag = 0;
sfr类型:将特殊功能寄存器的地址重定义,之后便可以把它当作变量直接操作(下面的数据类型也相同)。
如:sfr P1 = 0x90;
例:P1 = 0xFF; //将P1端口的8个引脚全部输出高电平(1)。
sfr16类型:给16位的特殊功能寄存器的地址重定义。
如:sfr16 DPTR = 0x82;
sfr或sfr16:51单片机内的SFR,用于控制单片机的内部定时器/计数器、串口、I/O及其他功能部件,每一个特殊功能寄存器在片内RAM中都对应一个字节单元或两个字节单元。C51提供了一种自主定义的方法,其与标准C不兼容,只适用与51系列单片机。
sbit类型:给特殊功能寄存器的位地址重定义。
如:sbit P1_1 = 0x91;
//另外一种定义方式。
sfr P1 = 0x90;
sbit P1_1 = P1^1;
C51中为了用户处理方便,C51编译器把MCS—51单片机的常用的特殊功能寄存器和特殊位进行了定义,放在一个“reg51.h”或“reg52.h”的头文件中,当用户使用时,只需要包含该头文件即可。如下:
/*--------------------------------------------------------------------------
REG52.H
Header file for generic 80C52 and 80C32 microcontroller.
Copyright (c) 1988-2002 Keil Elektronik GmbH and Keil Software, Inc.
All rights reserved.
--------------------------------------------------------------------------*/
#ifndef __REG52_H__
#define __REG52_H__
/* BYTE Registers */
sfr P0 = 0x80;
sfr P1 = 0x90;
sfr P2 = 0xA0;
sfr P3 = 0xB0;
sfr PSW = 0xD0;
sfr ACC = 0xE0;
sfr B = 0xF0;
sfr SP = 0x81;
sfr DPL = 0x82;
sfr DPH = 0x83;
sfr PCON = 0x87;
sfr TCON = 0x88;
sfr TMOD = 0x89;
sfr TL0 = 0x8A;
sfr TL1 = 0x8B;
sfr TH0 = 0x8C;
sfr TH1 = 0x8D;
sfr IE = 0xA8;
sfr IP = 0xB8;
sfr SCON = 0x98;
sfr SBUF = 0x99;
/* 8052 Extensions */
sfr T2CON = 0xC8;
sfr RCAP2L = 0xCA;
sfr RCAP2H = 0xCB;
sfr TL2 = 0xCC;
sfr TH2 = 0xCD;
/* BIT Registers */
/* PSW */
sbit CY = PSW^7;
sbit AC = PSW^6;
sbit F0 = PSW^5;
sbit RS1 = PSW^4;
sbit RS0 = PSW^3;
sbit OV = PSW^2;
sbit P = PSW^0; //8052 only
/* TCON */
sbit TF1 = TCON^7;
sbit TR1 = TCON^6;
sbit TF0 = TCON^5;
sbit TR0 = TCON^4;
sbit IE1 = TCON^3;
sbit IT1 = TCON^2;
sbit IE0 = TCON^1;
sbit IT0 = TCON^0;
/* IE */
sbit EA = IE^7;
sbit ET2 = IE^5; //8052 only
sbit ES = IE^4;
sbit ET1 = IE^3;
sbit EX1 = IE^2;
sbit ET0 = IE^1;
sbit EX0 = IE^0;
/* IP */
sbit PT2 = IP^5;
sbit PS = IP^4;
sbit PT1 = IP^3;
sbit PX1 = IP^2;
sbit PT0 = IP^1;
sbit PX0 = IP^0;
/* P3 */
sbit RD = P3^7;
sbit WR = P3^6;
sbit T1 = P3^5;
sbit T0 = P3^4;
sbit INT1 = P3^3;
sbit INT0 = P3^2;
sbit TXD = P3^1;
sbit RXD = P3^0;
/* SCON */
sbit SM0 = SCON^7;
sbit SM1 = SCON^6;
sbit SM2 = SCON^5;
sbit REN = SCON^4;
sbit TB8 = SCON^3;
sbit RB8 = SCON^2;
sbit TI = SCON^1;
sbit RI = SCON^0;
/* P1 */
sbit T2EX = P1^1; // 8052 only
sbit T2 = P1^0; // 8052 only
/* T2CON */
sbit TF2 = T2CON^7;
sbit EXF2 = T2CON^6;
sbit RCLK = T2CON^5;
sbit TCLK = T2CON^4;
sbit EXEN2 = T2CON^3;
sbit TR2 = T2CON^2;
sbit C_T2 = T2CON^1;
sbit CP_RL2 = T2CON^0;
#endif
三、C51变量的特殊属性
变量在使用前必须进行定义,其中,可以对变量设置C51特有的存储器模式,以便编译器为它分配相应的存储单元。一般格式为:
[存储种类] 数据类型 [存储器类型] 变量1[可以赋初值],变量2[可以赋初值]......;
其中存储种类和存储器类型可以省略,系统会有默认的存储种类和存储器类型,如下例子:
const char a=0x10;
extern unsigned int b=0x1234;
static float xdata c=123.456;
float code d=123.456;
存储器类型
C51变量所处的单片机的存储器区域,由其变量定义中特殊的存储器类型决定。C51的存储器类型有一下几种:
存储器类型 | 描述 |
data | 直接寻址的片内RAM低128B,访问速度快 |
bdata | 片内RAM的可位寻址区(20H~2FH),允许字节和位混合访问 |
idata | 间接寻址访问的片内RAM,允许访问全部片内RAM |
pdata | 用Ri间接访问的片外RAM低256B |
xdata | 用DPTR间接访问的片外RAM,允许访问全部64kB片外RAM |
code | 程序存储器ROM64kB空间 |
如下,几个实例:
unsigned char data a;//在片内RAM低128B内定义字符型变量a。
unsigned int idata a;//在片内RAM256B内定义整型变量a。
float xdata a;//在片外RAM64kB空间定义浮点型变量a。
unsigned int code a;//在ROM空间内定义整型变量a。
unsigned char bdata a;//在片内RAM位寻址区定义可字节处理和位处理的无符号字符型变量a。
当存储器类型缺省时,C51编译器将按照存储模式默认的存储器类型。
存储模式
C51编译器支持三种存储模式:SMALL模式、COMPACT模式和LARGE模式。不同的存储模式对变量默认的存储器类型不一样。
SMALL模式 | 函数参数和变量被默认在片内RAM中,存储器类型为data |
COMPACT模式 | 函数参数和变量被默认在片外RAM的低256B空间,存储器类型为pdata |
LARGE模式 | 函数参数和变量被默认在片外RAM的64kB空间,存储器类型为xdata |
在程序中变量的存储模式的指定通过#pragma预处理命令来实现。函数的存储模式可以通过在函数定义时后面带存储模式说明。如果没有指定,则系统都隐含为SMALL模式。如下:
/***变量***/
#pragma small //变量的存储模式为SMALL
char a; //存储器类型为:data
int xdata b; //存储器类型为:xdata
#pragma compact //变量的存储模式为COMPACT
char c; //存储器类型为:pdata
int xdata d; //存储器类型为:xdata
/***函数***/
int fun1(int x1,int x2) large //函数的存储器模式为LARGE,形参x1和x2的存储器类型为:xdata
{
return(x1+x2);
}
int fun2(int y1,int y2) //函数的存储器模式隐含为SMALL,形参y1和y2的存储器类型为:data
{
retrun(y1+y2);
}
绝对地址访问
C51通过三种方式实现绝对地址访问,即通过地址来访问存储空间的内容。
1、使用C51运行库中预定义宏
C51编译器提供了一组宏定义对5系列单片机的code、data、pdata和xdata空间进行绝对寻址。规定只能以无符号数的方式访问,定义了8个宏定义,其函数原型如下:
#include <absacc.h> //宏定义所在的头文件,使用时需要引用该头文件。
//如下,8个宏定义为:
#define CBYTE((unsigned char volatile code *)0)
#define DBYTE((unsigned char volatile data *)0)
#define PBYTE((unsigned char volatile pdata *)0)
#define XBYTE((unsigned char volatile xdata *)0)#define CWORD((unsigned int volatile code *)0)
#define DWORD((unsigned int volatile data *)0)
#define PWORD((unsigned int volatile pdata *)0)
#define XWORD((unsigned int volatile xdata *)0)
如下例子:
#include <absacc.h>
#include <reg52.h>
#define uchar unsigned char
#define uint unsigned int
void main(void)
{
uchar a;
uint b = 2024;
a = XBYTE[0xF005];//读片外RAM的F005H字节单元。
XWORD[0xD002] = b;//写片外RAM的D002H字单元。
while(1){}
}
2、通过指针访问
在C语言中,指针可以用来代表地址。采用指针的方法,可以实现对任意指定存储器单元进行访问。如下:
#define uchar unsigned char
#define uint unsigned int
void fun(void)
{
uchar data a; //定义一个data区的变量a。
uchar pdata *p1; //定义一个指向pdata区的指针p1。
uint xdata *p2; //定义一个指向xdata区的指针P2。
uchar data *p3; //定义一个指向data区的指针p3。
p1 = 0x30; //给指针p1赋值,指向pdata区的30H单元
p2 = 0x1000; //给指针p2赋值,指向xdata区的1000H单元
p3 = &a; //给指针p3赋值,指向data区的a变量的地址单元
*p1 = 0x12; //将数据0x12送到片外RAM30H单元
*p2 = 0x1234; //将数据0x234送到片外RAM1000H单元
*p3 = 0x12; //将变量a赋值为0x12
}
3、使用C51扩展关键字:_at_
使用_at_对指定存储器空间的绝对地址进行访问,一般格式如下:
[存储器类型] 数据类型 变量名 _at_ 绝对地址(地址常数)
使用_at_定义的变量必须为全局变量,如下实例:
#define uchar unsigned char
#define uint unsigned int
data uchar a _at_ 0x40; //在data区中定义字节变量a,地址为40H
xdata uint b _at_ 0a2000; //在xdata区中定义字节变量b,地址为2000H
void main(void)
{
a = 0x12; //对片内40H单元赋值
b = 0x1234; //对片外2000H单元赋值
while(1){}
}
四、C51中断函数和输入输出
中断函数
中断函数的特点:
①中断函数不能使用参数传递。若进行参数的申请将产生编译错误。
②中断函数没有返回值。定义时应将该函数定义为void类型。
③中断函数只有相应的中断产生时才能由系统自动调用。其他函数不能调用,否则产生编译错误。
④中断函数最好不要调用别的函数,如需调用,要保证其能尽快执行完,同时被调用函数将使用相同的寄存器组。
⑤中断函数中禁止使用extern存储类型说明。
中断函数的定义格式为:
函数类型 函数名(形式参数表) [interrupt m] [using n]
interrupt m选项(重点):指明定义的函数为中断响应函数,其中m为中断号。
using n选项:指定本函数中使用的寄存器组号(0~3)。
void time0() interrupt 1 using 1 //定时器0中断函数。
{
//中断处理内容。
}
但在平时使用中往往省略[using n]选项。
输入输出
单片机系统的输入、输出(如键盘、显示器、打印机等)往往是通过接口扩展实现的,访问时常采用“绝对地址访问”方式进行。51单片机还提供了一种“串口打印”输入输出方式:需引用头文件“stdio.h”,并配置串口(中断方式)。然后就可以用printf语句通过串口输入和输出数据了。
stdio.h:是标准C的输入输出库,但在C51中,将其制定为通过串口输入输出的相关函数。
完结
可以查阅相关内容:
有误之处望指正