单片机C语言入门讲座
C语言概述及其开发环境的建立

🏠 首页 《无线电》杂志 2004年 🔗 第1期 🔗 第38页 分类:电脑与单片机 🔗 周坚 🔗

随着单片机开发技术的不断发展,目前已有越来越多的人从普遍使用汇编语言到逐渐使用高级语言开发,其中主要是以C语言为主,市场上几种常见的单片机均有其C语言开发环境。

学习一种编程语言,最重要的是建立一个练习环境,边学边练才能学好。为此笔者专门开发了一块实验板作为硬件练习的平台,该实验板上有LED、按键、8段数码管、I2C接口、X5045接口、串行口、字符型液晶接口、计数信号等,该板具有仿真功能,不需要昂贵的仿真机就可以进行程序的仿真调试。与之配套的软件就采用Keil软件,该软件是目前最流行开发80C51系列单片机的软件,提供了包括C编译器、宏汇编、连接器、库管理和一个功能强大的仿真调试器等在内的完整开发方案,通过一个集成开发环境(μVision)将这些部分组合在一起。为了让暂时还没有硬件的读者也能够学习,作者还特意开发了相应的实验仿真板。

本教程是针对以80C51为内核的单片机系列的,并非针对某种特定的芯片,教程中通常用80C51作为这类单片机的代称,用到特定的芯片型号时另专门说明。L

本文介绍的实例如图1所示, 作为主芯片的89×××芯片的P1引脚上接8个发光二极管,P3.2~P3.4引脚上接4个按钮开关,我们的任务是让接在P1引脚上的发光二极管按要求发光。

图1
图1 🔍原图 (425×411)

一、简单的C程序介绍

例1:让接在P1.0引脚上的LED发光

#include “reg51.h”

sbit P1—0=P1^0;

void main()

9 {P1—1=0;9 }

下面来分析一下这个C语言程序包含了哪些信息。

程序的第一行是一个“文件包含”处理,所谓“文件包含”是指一个文件将另外一个文件的内容全部包含进来,所以这里的程序虽然只有4行,但C编译器在处理的时候却要处理几十或几百行。为加深理解,可以用任何一个文本编缉器打开Keil\c51\include文件夹下面的reg51.h来看一看里面有些什么内容,在C编译程序处理这个程序时,这些内容也将会被处理。这个程序中包含REG51.h文件的目的是为了要使用P1这个符号,即通知C编译器,程序中所写的P1是指80C51单片机的P1端口而不是其他变量。这是如何做到的呢?

打开reg51.h可以看到:“sfr P1 = 0x90;”即定义符号P1与地址0x90对应,熟悉80C51内部结构的读者不难看出,P1口的地址就是0x90。

其中sfr是Keil为能直接访问80C51中的SFR而提供的一个新的关键词,其用法是:sfr 变量名=地址值。

第二行用符号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的地址值

main称为“主函数”。每一个C语言程序有且只有一个主函数,函数后面一定有一对大括号“9邀9妖”,在大括号里面书写其他程序。

下面我们再来看一个例子,对C程序作进一步的了解。

例2:让接在P1.0引脚上的LED闪烁发光

#include“reg51.h” //包含头文件

sbit P1—0=P1^0;//定义引脚

void mDelay(unsigned int De-layTime)

9 {unsigned int j=0;

for(;DelayTime>0;Delay-Time——)

9 } for(j=0;j<125;j++)9 {;9 }}}

void main()

9 { for(;;)

9 {P1—0=!P1—0;//取反P1.0

mDelay(1000);/*延时1000ms*/

9 }}

分析:主程序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来要求延时时间为1000ms。要做到这一点,必须由我们自己编写的mDelay那段程序决定的,详细情况在循环程序中再作分析,这里就不介绍了。

二、Keil工程的建立、设置与调试

要使用Keil软件,首先要正确安装Keil软件,该软件的Eval版本可以直接去http://www.keil.com下载,安装时选择Eval Vision,其他步骤与一般Windows程序安装类似,这里就不再赘述了。安装完成后,将从本刊网站www.radio.com.cn上下载的dpj.dll文件(此文件还可以从下期配刊光盘中找到)复制到Keil安装目录下的C51\BIN文件夹下,该文件由笔者提供,与Keil软件配合,可在计算机上模拟LED显示、按键操作、数码管显示等功能。

启动μVison,点击“File→New…”,在工程管理器的右侧打开一个新的文件输入窗口,在这个窗口里输入例2中的源程序,注意大小写及每行后的分号,不要错输及漏输。

输入完毕之后,选择“File→Save”,给这个文件取名保存,取名字的时候必须要加上扩展名,一般C语言程序均以“.C”为扩展名,这里将其命名为exam12.c,保存完毕后可以将该文件关闭。

Keil不能直接对单个的C语言源程序进行处理,还必须选择单片机型号,确定编译、汇编、连接的参数,指定调试的方式,而且一些项目中往往有多个文件。为管理和使用方便,Keil使用工程(Project)这一概念,将这些参数设置和所需的所有文件都加在一个工程中,只能对工程而不能对单一的源程序进行编译和连接等操作。

点击Project→New Project…即可打开创建新工程的对话框,为该工程选择CPU、选择编译模式、操作系统的使用情况等,然后将exam12.c加入该工程即可。

工程建立好以后,还要对工程进行进一步的设置,以满足要求,使用菜单Project→Option for Target“Target 1”或点击快捷按钮进入工程设置对话框,点击OutPut页,选中Creat Hex file,以便输出可供写片的HEX文件,然后选中Debug页,选择调试方式。在这个例子里我们先用实验仿真板作调试之用,硬件连接及调试的方法将在后面的例子中介绍。打开Debug页,可以看到Dialog DLL对话框后的Parmeter:输入框中已有默认值-pAT52,在其后键入空格后再输入-dledkey,如图2所示。

设置好工程后,即可进行编译、连接。选择菜单Project->Build target,对当前工程进行连接,如果当前文件已修改,将先对该文件进行编译,然后再连接以产生目标代码;如果选择Rebuild All target files将会对当前工程中的所有文件重新进行编译然后再连接,确保最终生产的目标代码是最新的,而Translate ….项则仅对当前文件进行编译,不进行连接。

有关Keil软件的使用请参考《无线电》杂志2003年第7期至第11期的“Keil实例教程”系列文章。

点击菜单Peripherals,即会多出一项“键盘LED仿真板(K)”,选中该项,即会出现如图3所示界面。运行该程序,可以看到P1.0所示LED在闪烁(闪烁的速度与计算机的性能有关,如果闪烁速度过快,请将mDelay(1000)中的1000改为2000或更大的数字,执行时间无法与真实硬件执行速度一致是软件仿真的固有弱点。)

图2
图2 🔍原图 (536×349)

三、C语言的一些特点

通过上述的两个例子,可以得出一些结论:

1.C程序是由函数构成的。一个C源程序至少包括一个函数,一个C源程序有且只有一个名为main()的函数,也可能包含其他函数,因此,函数是C程序的基本单位。主程序通过直接书写语句和调用其他函数来实现有关功能,这些其他函数可以是由C语言本身提供给我们的(如—crol—(…)函数),这样的函数称之为库函数,也可以是用户自己编写的(如例2中用的mDelay(…)函数),这样的函数称之为用户自定义函数。那么库函数和用户自定义函数有什么区别呢?简单地说,任何使用Keil C语言的人,都可以直接调用C的库函数而不需要为这个函数写任何代码,只需要包含具有该函数说明的相应的头文件即可;而自定义函数则是完全个性化的,是用户根据自己需要而编写的。Keil C提供了100多个库函数供我们直接使用。

2.一个函数由两部分组成:

(1)函数的首部,即函数的第一行。包括函数名、函数类型、函数属性、函数参数(形参)名、参数类型。

例如:void mDelay (unsigned int Delay-Time)

一个函数名后面必须跟一对圆括号,即便没有任何参数也是如此。

(2)函数体,即函数首部下面的大括号“ { }”内的部分。如果一个函数内有多个大括号,则最外层的一对“9邀9妖”为函数体的范围。

函数体一般包括:

声明部分:在这部分中定义所用到的变量,例2中unsigned char j。

执行部分:由若干个语句组成。

在某些情况下也可以没有声明部分,甚至既没有声明部分,也没有执行部分,如:

void mDelay()

9 { }

这是一个空函数,什么也不干,但它是合法的。

在编写程序时,可以利用空函数,比如主程序需要调用一个延时函数,可具体延时多少,怎么个延时法,暂时还不清楚,我们可以主程序的框架结构弄清,先编译通过,把架子搭起来再说,至于里面的细节,可以在以后慢慢地填,这时利用空函数,先写这么一个函数,这样在主程序中就可以调用它了。

3.一个C语言程序,总是从main函数开始执行的,而不管物理位置上这个main()放在什么地方。例2中就是放在了最后,事实上这往往是最常用的一种方式。

4.主程序中的mDelay如果写成mde-lay就会编译出错,即C语言区分大小写,这一点往往是让初学者非常困惑的,尤其是学过一门其他语言的人,有人喜欢,有人不喜欢,但不管怎样,你得遵守这一规定。

5. C语言书写的格式自由,可以在一行写多个语句,也可以把一个语句写在多行。没有行号(但可以有标号),书写的缩进没有要求。但是建议读者自己按一定的规范来写,可以给自己带来方便。

6.每个语句和资料定义的最后必须有一个分号,分号是C语句的必要组成部份。

7.可以用/*…..*/的形式为C程序的任何一部分作注释,在“/*”开始后,一直到“*/”为止的中间的任何内容都被认为是注释,所以在书写特别是修改源程序时特别要注意,有时无意之中删掉一个“*/”,结果,从这里开始一直要遇到下一个“*/”中的全部内容都被认为是注释了。原本好好的一个程序,编译已通过了,稍作修改,一下出现了几十甚至上百个错误,初学C的人往往对此深感头痛,这时就要检查一下,是不是有这样的情况,如果有的话,赶紧把这个“*/”补上。

特别地,Keil C也支持C++风格的注释,就是用”//“引导的后面的语句是注释,例:

P1—0=!P1—0;//取反P1.0

这种风格的注释,只对本行有效,所以不会出现上面的问题,而且书写比较方便,所以在只需要一行注释的时候,我们往往采用这种格式。但要注意,只有Keil C支持这种格式,早期的Franklin C以及PC机上用的TC都不支持这种格式的注释,用上这种注释,编译时通不过,会报告编译错误。

下期笔者以按键灯亮为例,为读者介绍c语言分支程序的设计。

(周坚)