C51
该文章的内容仅限于STC15F2K60S2单片机的程序设计
一.常用功能
1.逻辑运算和位运算,
1.逻辑运算符:与(&&),或(||);非(!); 返回0为假,1为真,运算符连接的多为条件语句,例如 (a<10)&&(b==1);
2.位运算符
位与(&),位或(|),位异或(^),取反(~),左移(<<),右移(>>)
位运算符出现的时候,都需要将运算数变成二进制形式,进行位运算,其中位与可以用于清0,位或可以用于置1,对于有符号数,在右移时,符号位将随同移动,当为正数时,最高位补0,而为负数时,最高位为1,最高位补0或是1取决于编译系统的规定(编译系统的规定是什么东西??)在左移的时候不需要考虑正负数的情况吗??
2.预处理
1.宏定义(define)
1.无参宏定义
#define 标识符 字符串 字符串可以是常数,表达式,格式串等 终止宏定义,可以用#undef命令
2.带参宏定义
#define 宏名(形参表) 字符串
2.文件包含(include)
#include "文件名" 功能是把指定的文件插入该命令行位置取代该命令行,从而把指定的文件和当前的源程序文件连接成一个源文件。
比如一些公用的符号常量或者宏定义等可以单独组成一个文件,在其他文件的开头包含该文件,就避免了重复书写公用量
亲试 ,可以包含.c,.h文件,而且在.c 或者.h文件中编写的程序,定义的变量,在main()中都可以直接使用
#include "文件名" #include <文件名> 二者的区别是: 使用尖括号表示在包含文件目录中去查找(包含目录由用户在开发环境中设置,不清楚怎么设置,从未设置过。。),而不在源文件目录去查找,使用双引号表示首先在当前的源文件目录中查找,若找不到,才到包含目录中去查找。
3.条件编译
就是按照不同 的条件去编译不同的程序部分,从而产生不同的目标代码文件,特别是在操作系统的裁剪中,经常用到条件编译。
1.#ifdef 标识符
程序段1
#else
程序段2
#endif
它的功能是:如果标识符已经被#define命令定义过,则对程序段1进行编译,否则对于程序段2进行编译,当然也可以没有#else 程序段2
2.#ifndef 标识符
程序段1
#else
程序段2
#endif
与1.的功能正好相反,如果没有被定义过,则编译1
3.#if 常量表达式
程序段1
#else
程序段2
#endif
功能是:如果常量表达式的值为真(非0),则对1进行编译
条件编译当然也可以用条件语句来实现,但是用条件语句会对整个源程序进行编译,生成的目标代码程序较长,而采用条件编译,则根据条件只编译程序段1,或者程序段2,生成的目标程序较短。
二. Keil C 和 ANSI C
C51的基本语法和ANSI C相同,但对ANSI C进行了扩展,大多数扩展功能都是直接针对8051内核单片机的
1.扩展关键字(19个)
_at_ sbit sfr bit sfr16 idata bdata xdata pdata data code alien small compact large using reentrant interrupt _task_
1.内存区域
1.程序存储器
code: 程序存储区,可以使用code定义表格常数
2.内部数据存储器(内部RAM)
.data 直接寻址区,内部RAM的低128B,地址范围是00H-7FH
.idata 间接寻址区,包括整个内部的RAM区 256B,地址范围是00H-0FFH
.bdata 可位寻址区,地址范围是20H-2FH
3。外部数据存储器
外部RAM视使用情况可由以下关键字标示
xdata 可指定多达64KB的外部直接寻址区,地址范围是0000H-0FFFFH.
pdata 能访问1页(256B)的外部RAM(很少用)
例如 unsigned char xdata arr[10][4][4];
4.特殊功能寄存器(sfr)
STC15F2K60S2单片机的特殊功能寄存器(sfr)寻址区,用来控制定时器,计数器,串口,I/O即其他部件,为了支持SFR及其可位寻址的声明,引入sfr,sbit等关键词
sfr 字节寻址 sfr P0=0X80; 0x80为P0口的地址,=后为常数,并且这个常数必须在特殊功能寄存器的地址范围内,位于0x80到0xff之间
sfr16 字寻址 sfr16 DPTR = 0X82; //指定DPTR的地址 DPL = 0X82 DPH = 0X83
sbit 位寻址 用于声明可位寻址的特殊功能寄存器的位变量 sbit CY = PSW^7; sbit OV = 0XD0^2; sbit EA = 0XAF; (PSW为已经定义的SFR的名字)
对于大多数8051内核单片机成员,KEIL提供了一个包含所有特殊功能寄存器和他们位定义的同文件reg51.h
2._at_ 关键字
若要实现变量的绝对定位(称为绝对变量),可以直接在数据定义后加上“_at_ 常数地址”即可 注意: 一。绝对变量不能初始化 二。bit型函数以及变量不能用_at_指定
例如: unsigned char idata ADCdata _at_ 0x40;
unsigned char xdata buffer[20] _at_ 0x0010; 指定buffer数组从XRAM的0010H单元开始
3.存储模式
1.small 模式
所有的变量默认在内部数据存储器,和使用data 指定存储器类型的方式一样 优点是: 效率高,访问速度快,缺点是:空间有限,只适合小程序
2.compact 模式
缺省变量位于外部RAM区的一页内,和pdata指定存储器类型一样 特点: 空间比small宽裕,速度比small慢,比large快,是一种中间状态
3.large 模式
。。。。。。多大64kb的外部RAM区,和使用xdata一样,使用数据指针DPTR进行寻址,效率低(DPTR是什么啊。。。。。。。)
4.变量或数据类型
bit
bit型变量用于变量类型和函数声明。函数返回值等,存储于内部RAM的20H-2FH单元中
注意: 使用禁止中断(#pragma disable)或包含明确的寄存器切换(using n)的函数不能返回位值,否则,编译器会识别出来并产生一个错误信息
位不能声明为一个指针 不能有bit数组
sbit可以 声明可独立访问可位寻址对象的位, sbit声明要求基址对象的存储器的类型是bdata,否则只有绝对的位声明方法是合法的。 位的位置的最大值依赖于指定的基类型,对于char/unsigned char 是 0-7,对于 int /unsigned int /short/unsigned short 是0-15,对于long/unsigned long 是0-31,
例如 int bdata bittest _at_ 0x20; //可以省略“_at_ 0x20"
sbit bit0 = bittest^0; //0x20单元的第0位
sbit bit1 = bittest^15; //0x21单元的第7位
注意:可位寻址对象的位声明必须放到main函数外部,作为全局变量使用
2.扩展i/o口的使用
由于使用C语言访问外部I/O时,要用到指针的功能,首先介绍Keil C51指针
1.Keil C51
Keil C51支持一般指针和存储器指针。一般指针的使用和声明和标准C相同,同时还可以说明指针的存储类型
例如,如下指针都为指向保存在外部RAM中的unsigned char 数据的指针
unsigned char xdata *pt; //pt本身依据存储模式存放
unsigned char xdata *data pt; //pt被保存在内部的RAM中
unsigned char xdatat *xdata pt; //Pt被保存在外部RAM中
一般指针使用三个字节存放,分别为存储器类型,高位偏移量和低位偏移量
基于存储器的指针,说明时即指定了存储类型,例如 char data *str; //str指向data区中的char型数据 这种指针存放时只需要两个字节
堆栈指针SP 在编译后生成的.51文件中可以查看栈顶的位置,看一下是不是有足够的栈空间可用 另外,C51在startup.A51中设置SP指针,用CODE选项生成的汇编代码中找不到这段代码 startup.A51 是C51的初始化代码,单片机复位以后先执行这一段代码,完成初始化后由它调用main()函数,特殊需要时,可以修改这段代码,然后连接到用户的程序中去。
2.外部扩展I/O口的访问
1.使用自定义指针。由于片外I/O端口和片外存储器统一编址,所以可以定义xdata类型的指针访问外部i/o端口 例如 char xdata *com; com= 0x7ff3; *com = 0x81; //输出81H到端口 char xdata *com; com = 0x7FF0; char i; i = *com; //读PA端口到变量i
2.使用C51预定义指针
#define CBYTE ((unsigned char volatile code*) 0)
................
例如 #include <absacc.h>
#define PORTA XBYTE[0x7ff0] //PORTA为程序定义的端口名
void main(void ){char a; PORTA = 0X81; //输出81H到端口0x7ff0 a = PORTA; //读端口7ff0H到变量a
3.Keil C51 函数
1.中断函数的声明
通过使用关键字interrupt 和中断号(0-31),来声明,中断号告诉编译器中断服务程序的入口地址
2.指定工作寄存器区
使用关键字using 后跟一个0-3的数字,对应着工作寄存器0-3区 unsigned char GetKey(void) using 1{}
3.指定存储模式 用户可以使用small ,compact 及 large 说明存储模式 例如void fun(void) small{}
4.函数的参数传递规则
最多只能有3个参数通过寄存器传递
5.函数返回值一律放在寄存器中
6.函数的重入
可以在函数前声明函数的可重入性,只对一个函数有效,如果函数声明为不可重入的,说明该函数调用过程中不可被中断。因为单片机一般使用寄存器传递参数,内部变量一般在RAM中,函数重入时会破坏上次调用的数据
1.在相应函数前使用”#pragma disable"声明,只允许主程序或者中断之一调用。
2.将该函数声明为可重入的 例如void func(param...) reentrant;
由于一般可重入函数由主程序和中断调用,所以通常中断程序使用和主程序不一样的工作寄存器组,另外对于可重入函数,在相应的函数前面加上开关#pragma noaregs ,以禁止编译器使用绝对寄存器寻址,可生成不依赖于寄存器组的代码
在某些实时应用中,非重入函数是不可取得,因为,函数调用时可能被中断,而在中断程序中可能再次调用这个函数,所以C51允许将函数定义为重入函数,重入函数可被递归调用和多重调用,而不用担心变量被覆盖,因为每次函数调用时的局部变量都会被单独保存。 重入函数运行比较慢,因为有模拟堆栈
补充知识点: #pragma 的用法 :其中的#pragma message(" ");可以用于调试,亲试可用,很方便。
http://baike.baidu.com/link?url=QtvFurlWfDOP91v2VL4WlZr0Onvrin4H2R6RcH3nK-dJeUr7TTq6DehEWFagKCCGscEP_I7rFCRh_0491fjnK#2_1
volatile 的用法
http://baike.baidu.com/link?url=ewte5J9jkcAgWrWkhh3fBa7TrbhWTkxWmajalpbd5FhV9uKSwc-27XXXJzsOS4uWa2VfbH6sXE-kdc-R3o5iPa
个人认为volatile的用法很重要,尤其是它的适用情况