STM8实战
上QQ阅读APP看书,第一时间看更新

2.4 程序

2.4.1 程序的构成

C程序是由一个main函数和若干个其他函数构成的,以下用一个实际的例子来说明C程序的构成。


#include <avr/io.h>
int main(void)
{
        DDRC=0x0F;    //将PORTC端口低4位设为输出        0000  1111
        PORTC=0x01;   //使PORTC端口最低位输出高电平        0000  0001
    while(1)
    {
    }
}

在上面的程序中,第一行#是预处理命令行起始符号,include是预处理命令,表示程序在这里引用了来自另外一个地点的文件,include用于将该文件中编写的程序行包含到本程序中使用。io.h是C编译器(GCC)提供的头文件,用于对AVR单片机的寄存器作规范化的定义,avr/指定了该文件存放的文件夹。

程序的第二行是一个函数。我们知道,C语言是一个模块化的语言,它的主要部分是由多个具有特定功能的函数构成的。main函数和C语言中其他函数在结构上是一致的,但它的名称是固定的main,即主函数的意思。在一个C源程序中,有且仅有一个主函数,而且无论主函数位于源程序的什么位置,程序执行时都必须从它开始。

主函数的函数类型为int,函数名称后面圆括号里面的void表示该函数是没有输入参数的。在main函数的函数体中,第一个语句是一个赋值语句,意思是给寄存器DDRC赋值为十六进制的0F;第二个语句同样是一个赋值语句,意思是给寄存器PORTC赋值为十六进制的01。每一个程序行都以分号“;”结束。

接下来的程序行是一个while语句。它是一个循环语句,用来控制程序段(即循环体)的重复执行。这里需要明确的是,单片机的程序都是一个趋于无限的死循环,程序中使用了while(1)的写法,使程序在此进入持续的循环状态。

程序的运行过程如下:主函数是程序运行的开始。程序从主函数的函数体第一行开始执行,直至while循环之前,这一部分在每次系统复位后会顺序执行一次,程序中变量的声明、系统的初始化等可以放在这一部分运行;之后,程序进入由while语句构成的主循环中,这部分语句在程序运行时会无限地循环执行,适用于软件查询标志位、扫描按键和数码管等需要不间断访问的部分。主函数的运行过程如图2-5所示。

图2-5 主函数的运行过程

2.4.2 程序的注释

为了便于对程序的理解,可以在程序行的适当位置加入注释。注释有两种,一种是单行的注释,即在需要注释的文字前面加入两个斜杠,其格式为:


// 注释的文字

另一种是多行的注释,即在要注释的段落开始位置加入一个斜杠和一个乘号,在段落的结束位置再加入一个乘号和斜杠,具体格式为:


/* 注释的文字  注释的文字  注释的文字  注释的文字  注释的文字  注释的文字
   注释的文字  注释的文字  注释的文字  注释的文字  注释的文字  注释的文字  */

被注释的文字在编辑器中是用绿色来显示的,在对程序进行编译时,被注释的文字不参加编译,也不会干扰程序的运行。适当地对程序代码进行注释是一个好习惯,不但可以帮助别人理解你的代码,也给日后自己的阅读带来方便。

2.4.3 局部变量和全局变量

变量的有效性范围称为变量的作用域,C语言中所有的量都有自己的作用域,变量说明的方式不同,决定了其作用域也不同。C语言中的变量,按作用域范围不同可分为两种,即局部变量和全局变量。

1.局部变量

局部变量也称为内部变量。局部变量是在函数内部进行定义和说明的,其作用域仅限于函数内部,离开该函数后再使用该变量是非法的。例如:


void delay(unsigned int t)
{
    unsigned int x,y;
        for(x=t;x>0;x--)
    {
      for(y=2650;y>0;y--)
      {
      }
    }
}

在上面的delay函数内部,定义了两个变量x和y,这两个变量在delay函数内部使用是合法的,或者说变量x和y的作用域仅限于delay函数内部。C程序中允许在不同的函数中使用相同的局部变量名,但它们代表不同的对象,调用时会分配不同的内存单元,互不干扰。另外,在主函数中定义的变量也是局部变量,只能在主函数中使用,主函数中也不能使用其他函数中定义的变量。

2.全局变量

全局变量也称为外部变量,它是在函数的外部定义的变量。全局变量不属于某一个函数,而是属于某一个源程序文件。全局变量的作用域是整个源程序,在函数中使用全局变量,同样需要先定义后使用。例如:


#include <avr/io.h>               //包含AVR头文件
unsigned int  NUM;                //定义全局变量NUM用于显示
void  display(unsigned int K);    //数码管显示函数声明……
int main(void)
{
     ……
    while(1)
    {
       ……
       display(NUM);                      //扫描数码管
    }
}
void  display(unsigned int K)
{
     unsigned char NUM4,NUM3,NUM2,NUM1;   //定义4个局部变量
     ……
}

在以上的代码中,变量NUM是一个全局变量,它的定义位置是在函数的外面,因此它的作用域是整个程序,NUM这个变量在程序的任何地方调用都是合法的。全局变量经常用来在函数间传递数据。在display函数的内部定义的变量NUM1~NUM4则是局部变量,它属于display函数内部,也只能在该函数内部使用。

2.4.4 变量修饰关键词

1.外部型变量声明extern

一个C程序往往需要包含多个源文件,如果在程序中想使用另一个源文件中已经定义好的外部变量,可以用extern来修饰该变量,将该变量的作用域扩展到本文件中。例如在一个名为number.h的源文件中有如下定义:


unsigned char  NUM ;

之后,在当前程序中有如下程序行:


include <avr/io.h>
include “number.h”

在接下来的程序行里直接使用NUM这个变量是非法的。但为了不重复定义NUM,可以使用如下的方式:


extern  unsigned char  NUM ;

这时再使用NUM这个变量就是合法的了。

2.易变型变量声明volatile

变量修饰词volatile用来描述变量的活跃程度,用它修饰的变量表明其值是会随机变化的。volatile类型定义告诉编译器为这个变量要保留有固定的地址空间,以方便随时读取,例如:


volatile  unsigned char  DOTO  @0x12;

3.常数型变量声明const

用const类型修饰的变量为常数,程序在运行过程中将不能对其值进行修改。除了位变量以外,其他所有基本类型的变量在使用const修饰变成常数后,将被保存在程序存储器(FLASH)中,而不再在数据存储空间(RAM)内存储,这样可以大大地节省RAM的空间。例如可以将数码管的段码表数组用const类型来修饰:


const unsigned char table[]={0x3f,0x06,0x5b,0x4f,0x66,0x6d};

这里有一点需要说明的是,const类型修饰的位变量仍然会被放置在RAM中,但程序不能对其值进行修改。

2.4.5 指针

指针是C语言中广泛使用的一种数据类型,它实际上是专门用于存储地址的变量。在程序中,对变量的访问通常有两种方式:一种是通过使用变量名来对该变量所在的存储单元进行访问,另一种方式是通过访问变量的存储单元地址来访问该变量。这就好像我们要给某人送东西,既可以呼叫对方的名字,也可以将东西直接送到他所在的地址,最后的效果都是一样的。也就是说变量的地址和变量名是相关联的,在C语言中将变量的地址形象化地称为“指针”。

既然指针也是变量的一种,那么在使用之前也同样需要定义。定义指针变量的方法如下:


类型说明符*变量名1[,*变量名2,……];

其中,“*”是定义指针变量的说明符,表示此处定义的是一个指针变量。“变量名”即为要定义的指针变量名称,“类型说明符”表示本指针变量所指向变量的数据类型。定义指针变量可以参考以下代码:


int *P1;  //定义指向整型变量的指针变量P1

指针变量定义好后,在使用之前需要向其赋值。假定指针变量P1要指向的变量是NUM,需要先将变量NUM的存储地址获取到,获取变量存储地址的方法可以参考以下代码:


P1 = &NUM;  //将变量NUM的地址取出并赋值给指针型变量P1

在上面的代码中,&是取地址运算符,可以加在变量的前面,其意义是取出变量的地址。变量的地址取出后,将其赋值给定义好的指针变量P1。一旦给指针变量赋予了地址,也就相当于将指针指向了该变量,使二者建立起了关联。这时通过指针访问该变量的方法可以参考以下代码:


*P1 = 25;  //给指针指向的变量赋值为25

在上面的代码中,“*”是指针运算符,可以加在指针变量的前面,表示指针变量所指向的内存单元。这里我们给指针变量指向的内存单元赋值,也就相当于给变量NUM赋值了。