单片机C语言九个重要的知识点总结

一、概述


1、结合8051介绍单片机C语言的优越性:
   ·无须懂得单片机的具体硬件,也能够编出符合硬件实际的专业水平的程序;
   ·不懂得单片机的指令集,也能够编写完美的单片机程序;
   ·不同函数的数据实行覆盖,有效利用片上有限的RAM空间;
   ·提供auto、static、const等存储类型和专门针对8051单片机的data、idata、pdata、xdata、code等存储类型,自动为变量合理地分配地址;
   ·C语言提供复杂的数据类型(数组、结构、联合、枚举、指针等),极大地增强了程序处理能力和灵活性;
   ·提供small、compact、large等编译模式,以适应片上存储器的大小;
   ·中断服务程序的现场保护和恢复,中断向量表的填写,是直接与单片机相关的,都由C编译器代办;
   ·程序具有坚固性:数据被破坏是导致程序运行异常的重要因素。C语言对数据进行了许多专业性的处理,避免了运行中间非异步的破坏
   ·提供常用的标准函数库,以供用户直接使用;
   ·有严格的句法检查,错误很少,可容易地在高级语言的水平上迅速地被排掉;
   ·可方便地接受多种实用程序的服务:如片上资源的初始化有专门的实用程序自动生成;再如,有实时多任务操作系统可调度多道任务,简化用户编程,提高运行的安全性等等。
   ·头文件中定义宏、说明复杂数据类型和函数原型,有利于程序的移植和支持单片机的系列化产品的开发;
2、HEX文件
  建立了第一个单片机C语言项目,但为了让编译好的程序能通过编程器写入51芯 片中,要先用编译器生成HEX文件
3、C 编译器所支持的注释语句:   一种是以“//”符号开始的语句,符号之后 的语句都被视为注释,直到有回车换行。另一种是在“/*”和“*/”符号之内的为注释。注 释不会被 C 编译器所编译。
4、main函数:
   一个 C 应用程序中应有一个 main 主函数,main 函数能调用别的功能函数,但其它功能函数不允许调用 main 函数。不论 main 函数放在程序中的那个位置, 总是先被执行。
5、最小系统
   其中加了一个电阻和一个 LED,用以显示它的状态,晶体震荡器能根据自己的情况使用, 一般实验板上是用 11.0592MHz 或 12MHz,使用前者的好外是能产生标准的串行口波特率,后 者则一个机器周期为 1 微秒,便于做精确定时。

二、C51常量


1、常量数据类型说明:
   (1)整型常量能表示为十进制如 123,0,-89 等。十六进制则以 0x 开头如 0x34,-0x3B 等。长整型就在数字后面加字母 L,如 104L,034L,0xF340 等。
   (2)浮点型常量可分为 十进 制和指数表示形式。指数表 示形式为[±]数字[.数字]e[±]数字,[]中的内容为可选项,其中内容根据具体情 况可有可无,但其余部分必须有,如125e3,7e9,-3.0e-3。
   (3)字符型常量是单引号内的字符,如‘a’,‘d’等,不能显示的控制字符,能 在该字符前面加一个反斜杠“\”组成专用转义字符。常用转义字符表请看表

转义字符含义ASCII 码(16/10 进制)
\o空字符(NULL)00H/0
\n换行符(LF)0AH/10
\r回车符(CR)0DH/13
\t水平制表符(HT)09H/9
\b退格符(BS)08H/8
\f换页符(FF)0CH/12
\'单引号27H/39

\"双引号22H/34
\\反斜杠5CH/92

  (4)字符串型常量由双引号内的字符组成,如“test”,“OK”等。当引号内的没有字 符时,为空字符串。在使用特殊字符时同样要使用转义字符如双引号。在 C 中字符 串常量是做为字符类型数组来处理的,在存储字符串时系统会在字符串尾部加上\o 转义字符以作为该字符串的结束符。字符串常量“A”和字符常量‘A’是不一样的, 前者在存储时多占用一个字节的字间。
   (5)位标量,它的值是一个二进制。
2、应用
  常量可用在不必改变值的场合,如固定的数据表,字库等。常量的定义方式有几种,下面来加以说明。
  #difine False 0x0;//用预定义语句能定义常量
  #difine True 0x1;//这里定义 False 为 0,True 为 1
  //在程序中用到 False 编译时自动用 0 替换,同理 True 替换为 1
  unsigned int code a=100;//这一句用 code 把 a 定义在程序存储器中并赋值
  const unsigned int c=100;//用 const 定义 c 为无符号 int 常量并赋值 以上两句它们的值都保存在程序存储器中,而程序存储器在运行中是不允许被修改的,所以如果在这两句后面用了类似 a=110,a++这样的赋值语句,编译时将会出错。

三、C51变量


1、变量格式
  [存储种类] 数据类型 [存储器类型] 变量名表
  在定义格式中除了数据类型和变量名表是必要的,其它都是可选项。
2、存储种类    
  存储种类有四种:自动(auto),外部(extern),静态(static)和寄存器(register),缺省类型为自动(auto)。
(1)static(静态局部)变量
   在程序整个运行期间都不会释放内存。如果定义局部变量的时候不赋值,则编译的时候自动赋值为0。而对于自动变量而言,定义的时候不赋值,则是一个不确定的值。其他函数不能引用。
(2)用extern声明外部变量
   一个程序能由多个源程序文件组成。如果一个程序中需要引用另外一个文件中已经定义的外部变量,就需要使用extern来声明。
    例: 一个文件中:   int abc;
       另外一个文件中: extern abc;
3、数据类型
(1)数据类型

数据类型

长    度

值    域

unsigned  char

单字节

0~255

signed  char

单字节

-128~+127

unsigned  int

双字节

0~65535

signed  int

双字节

-32768~+32767

unsigned  long

四字节

0~4294967295

signed  long

四字节

-2147483648~+2147483647

float

四字节

±1.175494E-38~±3.402823E+38

*

1~3 字节

对象的地址

bit

0 或 1

sfr

单字节

0~255

sfr16

双字节

0~65535

sbit

0 或 1

  字节中最高位字节表示数据的符号,“0”表示正数,“1”表示负数, 负数用补码表示。
(2)特殊
   bit 位标量是 c51 编译器的一种扩充数据类型,利用它可定义一个位标量,但不能定义 位指针,也不能定义位数组。它的值是一个二进制位,不是 0 就是 1,类似一些高级语 言中的 Boolean 类型中的 True 和 False。
   sfr 也是一种扩充数据类型,点用一个内存单元,值域为 0~255。利用它能访问 51 单片机内部的所有特殊功能寄存器。
   sfr16 占用两个内存单元,值域为 0~65535。sfr16 和 sfr 一样用于操作特殊功能寄存 器,所不一样的是它用于操作占两个字节的寄存器,如定时器 T0 和 T1。sfr16 T2 = 0xCC; //这里定义8052定时器2,地址为T2L=CCH,T2H=CDH。用sfr16定义16位特殊功能寄存器时,等号后面是它的低位地址,高位地址一定要位于物理低位地址之上。注意的是不能用于定时器0和1的定义。
   sbit 同样是 单片机c语言 中的一种扩充数据类型,利用它能访问芯片内部的 RAM 中的可寻址位或特殊功能寄存器中的可寻址位。如先前定义了sfr P1=0x90;//因 P1 端口的寄存器是可位寻址的,所以能定义sbit P1_1=P1^1;//P1_1 为 P1 中的 P1.1 引脚。同样我们能用 P1.1 的地址去写,如 sbit P1_1=0x91;这样在以后的程序语句中就能用 P1_1 来对 P1.1 引脚进行读写操作了。
(3)重新定义数据类型的的语句typedef
  typedef 的语法:typedef 已有的数据类型 新的数据类型名
  样写:typedef  int  integer; integer  a,b;
   typedef 不能直接用来定义变量,它只是对已有的数据类型作一个名字上的置换,并不是产生一个新的数据类型。
4、存储器类型
    指定该变量在单片机c语言硬件系统中所使用的存储区域,并在编译时准确的定位。

存储器类型说明
data直接访问内部数据存储器(128字节),访问速度最快
bdata可位寻址内部数据存储器(16字节),允许位与字节混合访问
idata间接访问内部数据存储器(256字节),允许访问全部内部地址
pdata分页访问外部数据存储器(256字节),用MOVX @Ri指令访问
xdata外部数据存储器(64KB),用MOVX @DPTR指令访问
code程序存储器(64KB),用MOVC @A+DPTR指令访问

  注意的是在AT89c51芯片中RAM只有低128位,位于80H到FFH的高128位则在52芯片中才有用,并和特殊寄存器地址重叠。如果省略存储器类型,系统则会按编译模式SMALL,COMPACT或LARGE所规定的默认存储器类型去指定变量的存储区域。无论什么存储模式都能声明变量在任何的8051存储区范围,然而把最常用的命令如循环计数器和队列索引放在内部数据区能显著的提高系统性能。还有要指出的就是变量的存储种类与存储器类型是完全无关的。
(1)特殊寄存器(SFR)的地址
   AT89C51特殊功能寄存器列表(适用于同一架构的芯片)

符 号

地 址

注 释

*ACC

E0H

累加器

*B

F0H

乘法寄存器

*PSW

D0H

程序状态字

SP

81H

堆栈指针

DPL

82H

数据存储器指针低8位

DPH

83H

数据存储器指针高8位

*IE

A8H

中断允许控制器

*IP

D8H

中断优先控制器

*P0

80H

端口0

*P1

90H

端口1

*P2

A0H

端口2

*P3

B0H

端口3

PCON

87H

电源控制及波特率选择

*SCON

98H

串行口控制器

SBUF

99H

串行数据缓冲器

*TCON

88H

定时器控制

TMOD

89H

定时器方式选择

TL0

8AH

定时器0低8位

TL1

8BH

定时器1低8位

TH0

8CH

定时器0高8位

TH1

8DH

定时器1高8位

带*号的特殊功能寄存器都是可以位寻址的寄存器
(2)数据存储模式
   ①Small模式:所有缺省变量参数均装入内部RAM,优点是访问速度快,缺点是空间有限,只适用于小程序。
   ②Compact模式:所有缺省变量均位于外部RAM区的一页(256Bytes),具体哪一页可由P2口指定,在STARTUP.A51文件中说明,也可用pdata指定,优点是空间较Small为宽裕速度较Small慢,较large要快,是一种中间状态。
   ③large模式:所有缺省变量可放在多达64KB的外部RAM区,优点是空间大,可存变量多,缺点是速度较慢。
5、Keil c51指针变量
  单片机c语言支持一般指针(Generic Pointer)和存储器指针(Memory_Specific Pointer)。
  (1)一般指针
   一般指针的声明和使用均与标准C相同,不过同时还能说明指针的存储类型,例如:char * xdata ptr;ptr为一个指向char数据的指针,而ptr本身放于外部RAM区。一般指针本身用3个字节存放,分别为存储器类型,高位偏移,低位偏移量。
  (2)存储器指针
   基于存储器的指针说明时即指定了存贮类型,例如:char data * str;str指向data区中char型数据;这种指针存放时,只需一个字节或2个字节就够了,因为只需存放偏移量。
  (3)指针转换
  当基于存储器的指针作为一个实参传递给需要一般指针的函数时,指针自动转化。如果不说明外部函数原形,基于存储器的指针自动转化为一般指针,导致错误,因而请用“#include”说明所有函数原形。
6、单片机c语言中变量的空间分配几个方法
  (1)data区空间小,所以只有频繁用到或对运算速度要求很高的变量才放到data区内,比如for循环中的计数值。
   data区内最好放局部变量。局部变量空间在退出该函数是就释放,当然静态局部变量除外,其内存使用方式与全局变量相同;
  (2)确保你的程序中没有未调用的函数。在Keil C里遇到未调用函数,编译器就将其认为可能是中断函数。函数里用的局部变量的空间是不释放,也就是同全局变量一样处理。
  (3)程序中遇到的逻辑标志变量能定义到bdata中,能大大降低内存占用空间。
  (4)其他不频繁用到和对运算速度要求不高的变量都放到xdata区。如果想节省data空间就必须用large模式,将未定义内存位置的变量全放到xdata区。当然最好对所有变量都要指定内存类型。
  (5)当使用到指针时,要指定指针指向的内存类型。未定义指向内存类型的通用指针占用3个字节;而指定指向data区的指针只占1个字节;指定指向xdata区的指针占2个字节。如指针p是指向data区,则应定义为:char data *p;。还可指定指针本身的存放内存类型,如:char data *xdata p;

四、C51运算符和表达式

  运算符按其表达式中与运算符的关系可分为单目运算符,双目运算符和三目运算符。单目就是指需要有一个运算对象,双目就要求有两个运 算对象,三目则要三个运算对象。表达式则是由运算及运算对象所组成的具有特定含义的式子。表达式后面加“;”号就构成了一个表达式语句。

1、运算符和表达式
  ①赋值运算符:变量=表达式;
  ②算术运算符:+,/,-,*,%;除法运算符和一般的算术运算规则有所不一样,如是两浮点数相除,其结果为浮点数,如10.0/20.0 所得值为 0.5,而两个整数相除时,所得值就是整数,如7/3,值为2。
  ③++ 增量运算符,-- 减量运算符。这两个运算符是 C 语言中特有的一种运算符。在 VB,PASCAL 等都是没有的。I++(或I--)是先使用I的值,再执行 I+1(或 I-1);++I(或--I)是先执行I+1(或 I-1),再使用I的值。
  ④关系运算符:>,<,>=,<=,==,!=。“==”在 VB 或 PASCAL 等中是用“=”,“!=”则是用“not”。
  ⑤逻辑运算符:逻辑与:条件式1&&条件式2;逻辑或:条件式1||条件式2;逻辑非: !条件式2。注意的是用逻辑运算符的运算结果只有0和1两种,也就是逻辑的真与假,换句话说也就是逻辑量。
  ⑥位运算符:~,<<,>>,&,^,|。位运算符的作用是按位对变量进行运算,但是并不改变参与运算的变量的值。如果要求按位改变变量的值,则要利用相应的赋值运算。位运算符是不能用来对浮点型数据进行操作的。
  ⑦复合赋值运算符:在赋值运算符“=”的前面加上其他运算符。+=,-=,*=,/=,>>=,&=,|=,^=,%=,!=,<<=。a+=56等价于a=a+56;y/=x+9 等价于y=y/(x+9)。
  ⑧逗号运算符
  ⑨条件运算符:逻辑表达式? 表达式1 : 表达式2。当逻辑表达式的值为真时(非0值)时,整个表达式的值为表达式1的值;当逻辑表达式的值为假(值为0)时,整个表达式的值为表达式2的值。
  ⑩指针和地址运算符:*: 取内容;&:取地址;取内容和地址的一般形式分别为:变量=*指针变量;指针变量=&目标变量。  ⑪sizeof 运算符:语法:sizeof(数据类型)。例句:printf("char 是多少个字节?½字节\n",sizeof(char)),结果是:char 是多少个字节?1字节。
  ⑫强制类型转换运算符:语句:(类型) 表达式。
注:程序进行编译时由编译器自动去处理完成的转换称为隐式转换。其规则如下:
  变量赋值时发生的隐式转换,“=”号右边的表达式的数据类型转换成左边变量的数据类型。
  所有 char 型的操作数转换成 int 型。
  两个具有不一样数据类型的操作数用运算符连接时,隐式转换会按以下次序进行:如有一操作数是float类型,则另一个操作数也会转换成float类型;如果一个操作数为long类型,另一个也转换成long;如果一个操作数是 unsigned 类型,则另一个操作会被转换成 unsigned 类型。  
2、运算符优先级和结合性

级别

类 别

名 称

运算符

结合性

1

强制转换、数组、结构、联合

强制类型转换

 

下标

 

存取结构或联合成员

( )

 

[ ]

 

->或.

右结合

2

逻 辑

 

字 位

 

增 量

 

减 量

 

指 针

 

算 术

 

长度计算

逻辑非

 

按位取反

 

加一

 

减一

 

取地址、取内容

 

单目减

 

长度计算

!

 

~

 

++

 

--

 

&、*

 

-

 

sizeof

左结合

3

算 术

 

 

取模

*

 

/

 

%

 

右结合

4

算术和指针运算

 

+

 

-

右结合


 

5

字 位

左移

 

右移

<<

 

>>

右结合


 

6

关系

大于等于

 

大于

 

小于等于

 

小于

>=

 

>

 

<=

 

<

 

右结合

7

关系

恒等于

 

不等于

==

 

!=

右结合


 

8

字 位

按位与

&

右结合

9

字 位

按位异或

^

右结合

10

字 位

按位或

|

右结合

11

逻 辑

逻辑与

&&

左结合

12

逻 辑

逻辑或

||

左结合

13

条 件

条件运算

?:

左结合

14

赋 值

赋值

 

复合赋值

=

 

Op=

左结合

15

逗 号

逗号运算

,

右结合

五、C51表达式语句

1、不一样的程序设计语言都会有不一样的表达式语句,如VB就是在表达式后面加入回车就构成了VB 的表达式语句,而在51单片机的C语言中则是加入分号“;”构成表达式语句。
2、一个特殊的表达式语句,称为空语句
  通常用while,for 构成的循环语句后面加一个分号,形成一个不执行其它操作的空循环体。
  示例:#include  <AT89x51.h>

       void  main(void)

       { unsigned  int  a;

         do

          {

           P1  =  0xFF;  //关闭 P1 上的 LED

           while(P3_7);  //空语句,等待 P3_7 按下为低电平,低电平时执行下面的语句 P1  =  0;  //点亮 LED

           for(;a<60000;a++);  //这也是空语句的使用方法,注意 a 的初值为当前值

           }  //这样第一次按下时会有一延时点亮一段时间,以后按多久就亮多久

         while(1);  //点亮一段时间后关闭再次判断 P3_7,如此循环

        }

3、复合语句:将若干条语句组合在一起形成一种功能块,这种由若干条语句组合 而成的语句就叫复合语句。它内部的各条语句还是需要以分号“;” 结束。复合语句是允许嵌套的。对于一个函数而言,函数体就是一个复合语句,也许大家会因 此知道复合语句中不单能用可执行语句组成,还能用变量定义语句组成。
4、条件语句:

  C 语言供给了 3 种形式的条件语句:

  ①if(条件表达式) 语句。当条件表达式的结果为真时,就执行语句,不然就跳过。 如if(a==b) a++; 当a等于b时,a就加1

  ②if(条件表达式) 语句1 else 语句2。当条件表达式成立时,就执行语句1,不然就执行语句2。如if(a==b) a++;else a--;当a等于b时,a加1,不然a-1。

  ③if(条件表达式1) 语句1;

   else if(条件表达式2) 语句2;

   else if(条件表达式3) 语句3;

   else if(条件表达式m) 语句n;

   else 语句m;

5、开关语句:

  语法:switch(表达式)

       {case 常量表达式1: 语句1;break;

        case 常量表达式2: 语句2;break;

        case 常量表达式3: 语句3;break;

        case 常量表达式n: 语句n;break;

        default:语句}


6、循环语句:在C语言中构成循环控制的语句有while,do-while,for和goto语句。
(1)goto语句
   一个无条件的转向语句。语法:goto 语句标号; 其中的语句标号为一个带冒号的标识符。示例如下:

    void  main(void)

    {

      unsigned  char  a;

      start:  a++;

      if  (a==10)  goto  end;

      goto  start;

      end:;

    }


   常见的 goto 语句使用方法是用它来跳出多重循环,不过它只能从内层循环 跳到外层循环,不能从外层循环跳到内层循环。(2)while语句
   语法:while(条件表达式) 语句;

   当条件表达式为真时,它才执行后面的语句,执行完后再次回到 while 执行条件判断,为真时重复执行语句,为假时退出循环体。当条件一开始就为假时, 那么 while 后面的循环体(语句或复合语句)将一次都不执行就退出循环。在调试程序时要注意 while 的判断条件不能为假而造成的死循环,调试时适当的在 while 处加入断点,也许 会使你的调试工作更加顺利。

(3)do while语句
   语法:do  语句  while(条件表达式);
   先执行循环体,再根据条件判断是否要退出循环。
(4)for语句
        for([初值设定表达式];[循环条件表达式];[条件更新表达式]) 语句;语句中括号中的表达式是可选的。
7、continue 语句(也叫中断语句)
  一个无条件跳转语句。作用是结束本次循环,跳过循环体中没有执行的语句,跳转到下一次循环周期。和前面说到的break语句有所不一样,continue执行后不是跳出循环,而是跳到循环的开始并执行下一次的循环。
8、return语句
   语法:return (表达式);返回时先计算表达式,再返回表达式的值。不带表达式则返回的值不确定。

六、C51函数


1、函数的定义

  定义的模式如下:

    函数类型 函数名称(形式参数表)

   {

     函数体

    }

   函数类型是说明所定义函数返回值的类型。返回值其实就是一个变量,只要按变量类型来定义函数类型就行了。如函数不需要返回值函数类型能写作“void”表示该函数没 有返回值。注意的是函数体返回值的类型一定要和函数类型一致。形式参数是指调用函数时要传入到函数体内参与运算的变量,它能有一个、几个或没有,当不需要形式参数也就是无参函数,括号内能为空或写入“void”表示,但括号不能少。函数体中能包含有局部变量的定义和程序语句,如函数要返回运算值则要使用return语句进行返回。在函数的{}号中也能什么也不写,这就成了空函数,在一个程序项目中能写一些空函数,在以后的修改和升级中能方便的在这些空函数中进行功能扩充。

2、函数调用
  (1)在调用函数前,必须对函数的类型进行说明,就算是标准库函数也不例外。标准库函数的说明会被按功能分别写在不一样的头文件中,使用时只要在文件最前面用#include< .h> 预处理语句引入相应的头文件。
     (2)函数语句。如printf("Hello World!\n"); 这是在我们的第一个程序中出现的,它以"Hello World!\n"为参数调用printf这个库函数。在这里函数调用被看作了一条语句。
     (3)函数参数:如 temp=StrToInt(CharB(16));CharB 的返回值作为 StrToInt 函数的实际参数传递。
  (4)函数表达式:temp=Count();这样一句,这个时候函数的调用作为一个运算对象出现在表达式中,能称为函数表达式。
     (5)调用的是自定义的函数则要用如下形式编写函数类型说明:类型标识符 函数的名称(形式参数表); 这样的说明方式是用在被调函数定义和主调函数是在同一文件中。
  (6)写到 文件名.h  的文件中用#include "文件名.h"引入。
     如果被调函数的定义和主调函数不是在同 一文件中的,则要用如下的方式进行说明,说明被调函数的定义在同一项目的不一样文件之上
extern 类型标识符 函数的名称(形式参数表);
3、中断服务函数
  扩展的关键字是 interrupt,它是函数定义时的一个选项。
   形式:函数类型 函数名(形式参数) interrupt n [using n]
AT89C51芯片中断号和中断向量

中断号中断源中断向量
0外部中断 00003H
1定时器/计数器 0000BH
2外部中断 10013H
3定时器/计数器 1001BH
4串行口0023H

  例:

  1. #include <at89x51.h>
  2. unsigned char P3State(void); //函数的说明,中断函数不用说明
  3. void main(void)
  4. {
  5. IT0 = 0; //设外部中断 0 为低电平触发
  6. EX0 = 1; //允许响应外部中断 0
  7. EA = 1; //总中断开关

复制代码

七、C51数组的使用


   例:unsigned int xcount [10]; //定义无符号整形数组,有 10 个数据单元

     char inputstring [5]; //定义字符形数组,有 5 个数据单元

     float outnum [10],[10];//定义浮点型数组,有 100 个数据单元

     unsigned char LEDNUM[2]={12,35}; //一维数组赋初值

     int Key[2][3]={{1,2,4},{2,2,1}}; //二维数组赋初值

     unsigned char IOStr[]={3,5,2,5,3}; //没有指定数组长度,编译器自动设置

     unsigned char code skydata[]={0x02,0x34,0x22,0x32,0x21,0x12}; //数据保存在 code 区

八、C51指针的使用


  指针变量的内容是另一个变量的地址,地址所属的变量称为指针变量所指向的变量。
  方法是先用&STR 取变量地址并赋于 STRIP 指针变量,然后就能用*STRIP 来对 STR 进行访问了。‘*’是指针运算符,用它能取得指针变量所指向的地址的值。
   形式:数据类型 [存储器类型] *变量名;
  例: unsigned char xdata *pi;//指针会占用二字节,指针自身存放在编译器默认存储区,指向xdata存储区的char类型

     unsigned char xdata * data pi; //除指针自身指定在 data 区,其它同上

     int * pi; //定义为一般指针,指针自身存放在编译器默认存储区,占三个字节

  指针变量最大的值为 0xFFFF,这样就决定了一般指针在内存会占用3个字节,第一字节存放该指针存储器类型编码,后两个则存放该指针的高低位址。而基于存储器的指针因为不用识别存储器类型所以会占一或二个字节,idata,data,pdata存储器指针占一个字节,code,xdata则会占二个字节。

九、C51结构、联合和枚举的使用


1、结构:一种数据的集合体
(1)结构类型一般定义格式:struct 结构名 {结构元素表};
    例:truct FileInfo{unsigned char FileName[4]; unsigned long Date; unsigned int Size;}
(2)定义结构变量格式:struct 结构名 结构变量名1,结构变量名2,……结构变量N;
    例:struct FileInfo NewFileInfo, OleFileInfo;
   只有结构变量才能参与程序的执行,结构类型只是用于说明结构变量是属于那一种结构。通过上面的定义 NewFileInfo 和 OleFileInfo 都是 FileInfo 结构,都具有一个字符型数组一个长整型和一个整形数据。定义结构类型只是给出了这个结构的组织形式,它不会占用存储空间,也就说结构名是不能进行赋值和运算等操作的。结构变量则是结构中的具体成员, 会占用空间,能对每个成员进行操作。结构是允许嵌套的,也就是说在定义结构类型时,结构的元素能由另一个结构构成。
    例: struct clock{unsigned char sec, min, hour;}

       struct date{

           unsigned int year;

           unsigned char month,day;

           struct clock Time; //这是结构嵌套

          }

       struct date NowDate; //定义 data 结构变量名为 NowDate

  (3)引用、赋值
    格式:结构变量名.结构元素。
    要存取上例结构变量中的月份时,就要写成 NowDate.year=2021。
    NowDate.Time.min++; //分针加 1,嵌套时只能引用最低一级元素一个结构变量中元素的名字能和程序中其他地方使用的变量同名,因为元素是属于它所在的结构中,使用时要用成员运算符指定。
  (4)其他定义方式

   struct{结构元素表} 结构变量名1,结构变量名2……结构变量名N;

   例:struct{unsigned char FileName[4]; unsigned long Date; unsigned int Size;} NewFileInfo, OleFileInfo;

   这一种定义方式定义没有使用结构名,称为无名结构。通常会用于程序中只有几个确定 的结构变量的场合,不能在其它结构中嵌套。

   struct 结构名{结构元素表} 结构变量名1,结构变量名2……结构变量名N;

   例:struct FileInfo{unsigned char FileName[4]; unsigned long Date; unsigned int Size;} NewFileInfo, OleFileInfo;

   使用结构名能便于阅读程序和便于以后要在定义其它结构中使用。

2、枚举:把某些整型常量的集合用一个名字表示,其中的整型常量就是这种枚举类型变量的可取的合法值。
   (1)定义格式
         enum 枚举名 {枚举值列表} 变量列表;例:enum TFFlag {False, True} TFF;
         enum 枚举名 {枚举值列表}; emum 枚举名 变量列表;例:enum Week {Sun,Mon,Tue,Wed,Thu,Fri,Sat};enum Week OldWeek,NewWeek;
   (2)在枚举列表中,每一项名称代表一个整数值,在默认的情况下,编译器会自动为每一项赋值,第一项赋值为0,第二项为1……如Week中的Sun为0,Fri为5。C语言也允许对各项值做初始化赋值,要注意的是在对某项值初始化后,它的后续的各项值也随之递增。
          例: enum Week {Mon=1, Tue, Wed, Thu, Fri, Sat, Sun};
3、联合
   (1)它和其他结构类型一样能包含不一样类型的数据元素。所不一样的是其他类型的数据元素都是从同一个数据地址开始存放,结构变量占用的内 存大小是该结构中数据元素所占内存数的总和,而联合变量所占用内存大小只是该联合中最长的元素所占用的内存大小。
(2)程序先为联合中的 int 赋值 1000,后来又为 char 赋值 10,那么这个时候就不能引用int了,不然程序会出错,起作用的是最后一次赋值的元素,而上一次赋值的元素就失效了。使用中还要注意定义联合变量时不能对它的值初始化、能使用指向联合变量的指针对其操作、联合变量不能作为函数的参数进行传递,数组和结构能出现在联合中。
   (3)定义形式:union 结构名{结构元素表};
           union 结构名 结构变量名1,结构变量名2,……结构变量N;
           union {结构元素表} 结构变量名1,结构变量名2……结构变量名N;
           union 结构名{结构元素表} 结构变量名1,结构变量名2……结构变量名N;

单片机的C 语言轻松入门 随着单片机开发技术的不断发展,目前已有越来越多的人从普遍使用汇编语言到逐渐使 用高级语言开发,其中主要是以C 语言为主,市场上几种常见的单片机均有其C 语言开发 环境。这里以最为流行的80C51 单片机为例来学习单片机的C 语言编程技术。 本书共分六章,每章一个专题,以一些待完成的任务为中心,围绕该任务介绍C 语言 的一些知识,每一个任务都是可以独立完成的,每完成一个任务,都能掌握一定的知识,等 到所有的任务都完成后,即可以完成C 语言的入门工作。 第1 章 C 语言概述及其开发环境的建立 学习一种编程语言,最重要的是建立一个练习环境,边学边练才能学好。Keil 软件是目 前最流行开发80C51 系列单片机的软件,Keil 提供了包括C 编译器、宏汇编、连接器、库 管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境 (μVision)将这些部份组合在一起。 在学会使用汇编语言后,学习C 语言编程是一件比较容易的事,我们将通过一系列的 实例介绍C 语言编程的方法。图1-1 所示电路图使用89S52 单片机作为主芯片,这种单片 机性属于80C51 系列,其内部有8K 的FLASH ROM,可以反复擦写,并有ISP 功能,支 持在线下载,非常适于做实验。89S52 的P1 引脚上接8 个发光二极管,P3.2~P3.4 引脚上接 4 个按钮开关,我们的任务是让接在P1 引脚上的发光二极管按要求发光。 1.1 简单的C 程序介绍 例1-1: 让接在P1.0 引脚上的LED 发光。 /************************************************* 平凡单片机工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周坚 dddl.c 单灯点亮程序 *************************************************/ 图1-1 接有LED 的单片机基本电路 P1.0 EA/VPP VCC XTAL2 XTAL1 GND RST +5V +5V + R1 E1 10K 10U 27P CY 27P PZ1 1K D8 D1 89××× #include “reg51.h” sbit P1_0=P1^0; void main() { P1_1=0; } 这个程序的作用是让接在P1.0 引脚上的LED 点亮。下面来分析一下这个C 语言程序包 含了哪些信息。 1)“文件包含”处理。 程序的第一行是一个“文件包含”处理。 所谓“文件包含”是指一个文件将另外一个文件的内容全部包含进来,所以这里的程序 虽然只有4 行,但C 编译器在处理的时候却要处理几十或几百行。这里程序中包含REG51.h 文件的目的是为了要使用P1 这个符号,即通知C 编译器,程序中所写的P1 是指80C51 单 片机的P1 端口而不是其它变量。这是如何做到的呢? 打开reg51.h 可以看到这样的一些内容: /*------------------------------------------------------------------------- REG51.H Header file for generic 80C51 and 80C31 microcontroller. Copyright (c) 1988-2001 Keil Elektronik GmbH and Keil Software, Inc. All rights reserved. --------------------------------------------------------------------------*/ /* BYTE Register */ 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; /* BIT Register */ /* PSW */ sbit CY = 0xD7; sbit AC = 0xD6; sbit F0 = 0xD5; sbit RS1 = 0xD4; sbit RS0 = 0xD3; sbit OV = 0xD2; sbit P = 0xD0; /* TCON */ sbit TF1 = 0x8F; sbit TR1 = 0x8E; sbit TF0 = 0x8D; sbit TR0 = 0x8C; sbit IE1 = 0x8B; sbit IT1 = 0x8A; sbit IE0 = 0x89; sbit IT0 = 0x88; /* IE */ sbit EA = 0xAF; sbit ES = 0xAC; sbit ET1 = 0xAB; sbit EX1 = 0xAA; sbit ET0 = 0xA9; sbit EX0 = 0xA8; /* IP */ sbit PS = 0xBC; sbit PT1 = 0xBB; sbit PX1 = 0xBA; sbit PT0 = 0xB9; sbit PX0 = 0xB8; /* P3 */ sbit RD = 0xB7; sbit WR = 0xB6; sbit T1 = 0xB5; sbit T0 = 0xB4; sbit INT1 = 0xB3; sbit INT0 = 0xB2; sbit TXD = 0xB1; sbit RXD = 0xB0; /* SCON */ sbit SM0 = 0x9F; sbit SM1 = 0x9E; sbit SM2 = 0x9D; sbit REN = 0x9C; sbit TB8 = 0x9B; sbit RB8 = 0x9A; sbit TI = 0x99; sbit RI = 0x98; 熟悉80C51 内部结构的读者不难看出,这里都是一些符号的定义,即规定符号名与地 址的对应关系。注意其中有 sfr P1 = 0x90; 这样的一行(上文中用黑体表示),即定义P1 与地址0x90 对应,P1 口的地址就是0x90 (0x90 是C 语言中十六进制数的写法,相当于汇编语言中写90H)。 从这里还可以看到一个频繁出现的词:sfr sfr 并标准C 语言的关键字,而是Keil 为能直接访问80C51 中的SFR 而提供了一个新 的关键词,其用法是: sfrt 变量名=地址值。 2)符号P1_0 来表示P1.0 引脚。 在C 语言里,如果直接写P1.0,C 编译器并不能识别,而且P1.0 也不是一个合法的C 语言变量名,所以得给它另起一个名字,这里起的名为P1_0,可是P1_0 是不是就是P1.0 呢?你这么认为,C 编译器可不这么认为,所以必须给它们建立联系,这里使用了Keil C 的关键字sbit 来定义,sbit 的用法有三种: 第一种方法:sbit 位变量名=地址值 第二种方法:sbit 位变量名=SFR 名称^变量位地址值 第三种方法:sbit 位变量名=SFR 地址值^变量位地址值 如定义PSW 中的OV 可以用以下三种方法: sbit OV=0xd2 (1)说明:0xd2 是OV 的位地址值 sbit OV=PSW^2 (2)说明:其中PSW 必须先用sfr 定义好 sbit OV=0xD0^2 (3)说明:0xD0 就是PSW 的地址值 因此这里用sfr P1_0=P1^0;就是定义用符号P1_0 来表示P1.0 引脚,如果你愿意也可以 起P10 一类的名字,只要下面程序中也随之更改就行了。 3)main 称为“主函数”。 每一个C 语言程序有且只有一个主函数,函数后面一定有一对大括号“{}”,在大括号 里面书写其它程序。 从上面的分析我们了解了部分C 语言的特性,下面再看一个稍复杂一点的例子。 例1-2 让接在P1.0 引脚上的LED 闪烁发光 /************************************************* 平凡单片机工作室 http://www.mcustudio.com Copyright 2003 pingfan's mcustudio All rights Reserved 作者:周坚 ddss.c 单灯闪烁程序 *************************************************/ #include "reg51.h" #define uchar unsigned char #define uint unsigned int sbit P10=P1^0; /*延时程序 由Delay 参数确定延迟时间 */ void mDelay(unsigned int Delay) { unsigned int i; for(;Delay>0;Delay--) { for(i=0;i<124;i++) {;} } } void main() { for(;;) { P10=!P10; //取反P1.0 引脚 mDelay(1000); } } 程序分析:主程序main 中的第一行暂且不看,第二行是“P1_0=!P1_0;”,在P1_0 前有 一个符号“!”,符号“!”是C 语言的一个运算符,就像数学中的“+”、“-”一样,是一种 运算任号,意义是“取反”,即将该符号后面的那个变量的值取反。 注意:取反运算只是对变量的值而言的,并不会自动改变变量本身。可以认为C 编译 器在处理“!P1_0”时,将P1_0 的值给了一个临时变量,然后对这个临时变量取反,而不 是直接对P1_0 取反,因此取反完毕后还要使用赋值符号(“=”)将取反后的值再赋给P1_0, 这样,如果原来P1.0 是低电平(LED 亮),那么取反后,P1.0 就是高电平(LED 灭),反之, 如果P1.0 是高电平,取反后,P1.0 就是低电平,这条指令被反复地执行,接在P1.0 上灯就 会不断“亮”、“灭”。 该条指令会被反复执行的关键就在于main 中的第一行程序:for(;;),这里不对此作详细 的介绍,读者暂时只要知道,这行程序连同其后的一对大括号“{}”构成了一个无限循环语 句,该大括号内的语句会被反复执行。 第三行程序是:“mDelay(1000);”,这行程序的用途是延时1s 时间,由于单片机执行指 令的速度很快,如果不进行延时,灯亮之后马上就灭,灭了之后马上就亮,速度太快,人眼 根本无法分辨。 这里mDelay(1000)并不是由Keil C 提供的库函数,即你不能在任何情况下写这样一行 程序以实现延时。如果在编写其它程序时写上这么一行,会发现编译通不过。那么这里为什 么又是正确的呢?注意观察,可以发现这个程序中有void mDelay(…)这样一行,可见, mDelay 这个词是我们自己起的名字,并且为此编写了一些程序行,如果你的程序中没有这 么一段程序行,那就不能使用mDelay(1000)了。有人脑子快,可能马上想到,我可不可 以把这段程序也复制到我其它程序中,然后就可以用mDelay(1000)了呢?回答是,那当然 就可以了。还有一点需要说明,mDelay 这个名称是由编程者自己命名的,可自行更改,但 一旦更改了名称,main()函数中的名字也要作相应的更改。 mDelay 后面有一个小括号,小括号里有数据(1000),这个1000 被称之“参数”,用它 可以在一定范围内调整延时时间的长短,这里用1000 来要求延时时间为1000 毫秒,要做到 这一点,必须由我们自己编写的mDelay 那段程序决定的,详细情况在后面循环程序中再作 分析,这里就不介绍了。 1.2 Keil 工程的建立 要使用Keil 软件,首先要正确安装Keil 软件,该软件的Eval 版本可以直接去 http://www.keil.com 下载,安装时选择Eval Vision,其它步骤与一般Windows 程序安装类似, 这里就不再赘述了。安装完成后,将Ledkey.dll 文件复制到Keil 安装目录下的C51\BIN 文 件夹下,这是作者提供的键盘与LED 实验仿真板,可与Keil 软件配合,在计算机上模拟LED 和按键的功能。 启动μVison,点击“File
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值