PIC单片机C语言非常入门与视频演练
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

1.2 PIC单片机C语言入门

PIC单片机的编程语言主要有两种:汇编语言和C语言。汇编语言的机器代码生成效率很高,但可读性并不强,复杂一点的程序就更难读懂,而C语言虽然机器代码生成效率不如汇编语言,但可读性和可移植性却远远超过汇编语言。因此,开发PIC单片机,采用的一般都是C语言。

1.2.1 为什么采用C语言编程

与汇编语言相比,C语言在功能、结构性、可读性、可维护性上都有明显优势,易学易用。用过汇编语言后再使用C语言来开发,体会更加深刻。下面简要说明单片机采用C语言编程的几点好处。

1.语言简洁,使用方便灵活

C语言是现有程序设计语言中规模最小的语言之一,C语言的关键字很少,ANSIC标准共有32个关键字,9种控制语句,压缩了一切不必要的成分。C语言的书写形式自由,表达方法简单,使用一些简单的方法就可以构造出相当复杂的数据类型和程序结构。同时,当前几乎所有单片机都有相应的C语言级别的仿真调试系统,调试十分方便。

2.代码编译效率较高

当前,较好的C语言编译系统编译出来的代码效率只比直接使用汇编语言低20%左右,如果使用优化编译选项甚至可以更低。况且,PIC系列单片机片上ROM空间可以做得很大,代码效率所差的20%已经不是一个重要问题了。

3.无须深入理解单片机内部结构

采用汇编语言进行编程时,编程者必须对单片机的内部结构及寄存器的使用方法十分清楚。在编程时,一般还要进行RAM分配,稍不小心,就会发生变量地址重复或冲突。

而采用C语言进行设计时,则不必对单片机硬件结构有深入了解,编译器可以自动完成变量存储单元的分配,编程者可以专注于应用软件部分的设计,大大加快了软件的开发速度。

4.可进行模块化开发

C语言是以函数作为程序设计基本单位的,C语言程序中的函数相当于汇编语言中的子程序。各种C语言编译器都会提供一个函数库,此外,C语言还具有自定义函数的功能,用户可以根据自己的需要编制满足某种特殊需要的自定义函数(程序模块),这些程序模块可不经修改,直接被其他项目所用。因此,采用C语言编程,可以最大程度地实现资源共享。

5.可移植性好

用过汇编语言的读者都知道,即使是功能完全相同的一种程序,对于不同的单片机,必须采用不同的汇编语言来编写,这是因为汇编语言完全依赖于单片机硬件。C语言是通过编译来得到可执行代码的,本身不依赖机器硬件系统,用C语言编写的程序基本上不用修改或者只需进行简单的修改,即可方便地移植到另一种结构类型的单片机上。

6.可以直接操作硬件

C语言具有直接访问单片机物理地址的能力,可以直接访问片内或片外存储器,还可以进行各种操作。

总之,用C语言进行单片机程序设计是单片机开发与应用的必然趋势,一旦学会使用C语言,尤其学习过51单片机C语言的读者,再学习PIC单片机C语言是十分方便的,只需要对PIC单片机的硬件结构及相关寄存器做一简单了解即可。

1.2.2 简单的C语言程序

下面以一个简单的流水灯程序为例,了解一下PIC单片机C语言。

1.硬件电路

下面先来看一个实例,这个例子的功能十分简单,就是让单片机RD口的LED灯按流水灯的形式进行闪烁,硬件电路如图1-11所示。

5V电源分别连接8个发光二极管的正极,8个发光二极管的负极分别连接8个1kΩ限流电阻,然后再接到PIC单片机RD口。这样,当单片机的I/O口输出高电平时,发光二极管两端都是高电平,发光二极管不会导通,当I/O口输出低电平时,发光二极管正向导通,同时也就发出光亮了。

图1-11 点亮P0口LED灯电路

这里解释一下LED灯上串联电阻大小的选择问题。LED灯的工作电压为1.6~2.8V(一般为2V),工作电流为2~30mA(一般控制在4~10mA),如果系统供电VCC为5V,LED上串联的电阻为1kΩ,并取LED上电压为2V,那么,此时通过LED的电流则为(5V-2V)/1000Ω=3mA。如果需要提高亮度,需要增大LED灯的工作电流,当工作电流为10mA时,此时电阻应该选择(5V-2V)/10mA=300Ω,所以,LED灯的串联电阻一般在0.3Ω~1kΩ之间选择。

2.程序实现

8位流水灯源程序如下:

#include<pic.h>
#define uchar unsigned char
#define uint  unsigned int
__CONFIG(HS&WDTDIS&LVPDIS);
/********延时函数********/
void Delay_ms(uint xms)
{
    int i,j;
    for(i=0;i<xms;i++)
         { for(j=0;j<71;j++) ; }
}
/********主函数********/
void main (void)
{
     TRISD=0x00;  //RD口设置为输出
     while(1)
     {
    PORTD=0xFE;   //点亮第1个LED灯
        Delay_ms(500); //延时
        PORTD=0xFD;  //点亮第2个LED灯
        Delay_ms(500);
        PORTD=0xFB;  //点亮第3个LED灯
        Delay_ms(500);
        PORTD=0xF7;  //点亮第4个LED灯
        Delay_ms(500);
        PORTD=0xEF;  //点亮第5个LED灯
        Delay_ms(500);
        PORTD=0xDF;  //点亮第6个LED灯
        Delay_ms(500);
        PORTD=0xBF;  //点亮第7个LED灯
        Delay_ms(500);
        PORTD=0x7F;  //点亮第8个LED灯
        Delay_ms(500);
     }
}

下面对这个程序进行简要分析。

(1)程序的第一行是“文件包含”。“文件包含”是指一个文件将另一个文件的内容全部包含进来。所以,这里的程序虽然只有几行,但C编译器(如HI-TECH编译器,即PICC软件)在处理的时候却要处理几十行或几百行。为加深理解,可以用任何一个文本编辑器打开HT-PIC\include文件夹下面的pic.h来看一看里面有什么内容,如下所示(摘取部分):

#ifndef _PIC_H
#define _PIC_H
……
#if defined(_16F87) || defined(_16F88)
    #include <pic16f87.h>
#endif
#if defined(_16F873) || defined(_16F874) ||\
    defined(_16F876)|| defined(_16F877) ||\
    defined(_16F872)|| defined(_16F871) ||\
    defined(_16F870)
     #include <pic1687x.h>
#endif
#if defined(_16F873A) || defined(_16F874A) ||\
    defined(_16F876A) || defined(_16F877A)
    #include <pic168xa.h>
 ……

PIC单片机与80C51系列单片机的不同之处在于其包含了一个庞大的系列,这个系列中的很多芯片有其特定的头文件。为了编写程序的方便,PICC编译器给出了一个统一的头文件pic.h,在这个文件中,根据编译环境所定义的器件名称,调入定义这个器件的头文件。在编译这段程序时,需要先建立一个工程,在建立工程时,假设定义器件名称为PIC16F877A,相当于满足了下述条件中的defined(_16F877A)部分,因此,在编译程序时会执行:

#include <pic168xa.h>

即开始调pic168xa.h头文件,下面再来看看这个头文件,用记事本打开后,内容如下(部分):

/*
* Header file for the Microchip
* PIC 16F873A chip
* PIC 16F874A chip
* PIC 16F876A chip
* PIC 16F877A chip
* Midrange Microcontroller
*/
#if defined(_16F874A) || defined(_16F877A)
#define __PINS_40
#endif
static volatile unsigned char INDF @ 0x00;
static volatile unsigned char TMR0 @ 0x01;
static volatile unsigned char PCL @ 0x02;
static volatile unsigned char STATUS @ 0x03;
static          unsigned char FSR @ 0x04;
static volatile unsigned char PORTA @ 0x05;
static volatile unsigned char PORTB @ 0x06;
static volatile unsigned char PORTC @ 0x07;
#ifdef __PINS_40
static volatile unsigned char PORTD @ 0x08;
static volatile unsigned char PORTE @ 0x09;
#endif
……

从以上定义可以看出,这些符号的定义规定了符号名与地址的对应关系。其中有

static volatile unsigned char PORTD @ 0x08;

这样的一行,即定义PORTD与地址0x08对应,PORTD的地址就是0x08。

熟悉PIC内部结构的读者不难看出,PORTD口的地址就是0x08。在这个宏定义中,volatile是C语言的关键字,加上volatile关键字后,将不进行编译优化。

(2)源程序中有一行语句:

__CONFIG(HS&WDTDIS& LVPDIS);

此行程序称为配置文件,其功能是为这款单片机进行配置。PIC单片机内部具有多种功能,可以根据需要来进行配置,以便在不同场合正确地工作。这种配置工作可以在烧写芯片时手工设置,不过更好的方法是将配置写在程序中,在生成可烧写文件后,就将配置信息也包含在内了。对于大部分编程器所配套的软件而言,它们能够识别这种信息,从而自动完成配置,避免了手工设置可能带来的错误。

下面简要说明这条配置语句的意义:

① PIC16F877A芯片可以在4种不同类型的振荡方式下工作,实验板上采用的是外接4MHz的高频晶体振荡器,因此,配置时要选择HS。

为了避免在刚开始学习时由于芯片内部看门狗复位而造成误判,一般在学习程序过程中总是关掉看门狗,因此,配置时选择WDTDIS(禁止看门狗)。

实验时,一般采用高电压编程方式来下载程序,因此,这里选择LVPDIS(低电压编程禁止)。

② Delay_ms(500)的用途是延时,由于单片机执行指令的速度很快,如果不进行延时,灯亮之后马上就灭,灭了之后马上就亮,速度太快,人眼根本无法分辨,所以,需要进行适当的延时,这里采用自定义函数Delay_ms(500)实现延时,函数前面的void表示该延时函数没有返回值。

Delay_ms(500)函数是一个自定义函数,它不是由PIC编译器提供的,也就是说不能在任何情况下写这样一行程序以实现延时,如果在编写其他程序时写上这么一行,会发现编译通不过。注意观察本程序会发现,在使用Delay_ms(500)之前,已对Delay_ms(int k)函数进行了事先定义,因此,在主程序中才能对Delay_ms(500)进行使用。

注意:在延时函数Delay_ms(uint xms)定义中,参数xms称为“形式参数”(简称形参);而在调用延时函数Delay_ms(500)中,小括号里的数据“500”称为“实际参数”(简称实参),参数的传递是单向的,即只能把实参的值传给形参,而不能把形参的值传给实参。另外,实参可以在一定范围内调整,这里的“500”表示延时时间为0.5s,若为“1000”,则延时时间是1000ms,即1s。

③ PIC单片机的I/O口是标准的I/O口,I/O口的功能是负责实现CPU通过系统总线把I/O电路和外围设备联系在一起,标准的I/O口具有输入、输出、高阻3种状态,PIC单片机通过两个寄存器来控制I/O口的状态:输入和输出方向寄存器TRISx(x表示端口,如TRISD表示端口RD的方向寄存器)、输出寄存器PORTx。

程序中,“TRISD=0x00;”就是将端口RD口设置为输出。

“PORTD=0xFE;”的含义就是将端口RD的输出寄存器设置为0xFE,即让端口RD的高7位输出高电平,低1位输出低电平,其他依次类推。

④ 在单片机程序中,让程序进入一个while(1){}死循环中,这样保证程序一直运行。程序都是一步一步向下执行的,执行到程序的结尾就会停止,这时即使外界再有什么动作,单片机也不再响应了,加上死循环,那么程序就会一直在这个循环体中运行。如果在这个循环体中进行相应操作,程序就会很快检测到并给出响应。

在本例中,while(1){}死循环的功能轮流点亮PORTD口的LED灯,使PORTD口的LED灯流动显示。