从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx

上传人:b****4 文档编号:7033433 上传时间:2023-05-11 格式:DOCX 页数:24 大小:904.26KB
下载 相关 举报
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第1页
第1页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第2页
第2页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第3页
第3页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第4页
第4页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第5页
第5页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第6页
第6页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第7页
第7页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第8页
第8页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第9页
第9页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第10页
第10页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第11页
第11页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第12页
第12页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第13页
第13页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第14页
第14页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第15页
第15页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第16页
第16页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第17页
第17页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第18页
第18页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第19页
第19页 / 共24页
从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx_第20页
第20页 / 共24页
亲,该文档总共24页,到这儿已超出免费预览范围,如果喜欢就下载吧!
下载资源
资源描述

从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx

《从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx》由会员分享,可在线阅读,更多相关《从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx(24页珍藏版)》请在冰点文库上搜索。

从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识.docx

从单片机初学者迈向单片机工程师LED篇第三章模块化编程初识

“从单片机初学者迈向单片机工程师”LED篇第三章模块化编程初识

好的开始是成功的一半

  通过上一章的学习,我想你已经掌握了如何在程序中释放CPU了。

希望能够继续坚持下去。

一个良好的开始是成功的一半。

我们今天所做的一切都是为了在单片机编程上做的更好。

在谈论今天的主题之前,先说下我以前的一些经历。

在刚开始接触到C语言程序的时候,由于学习内容所限,写的程序都不是很大,一般也就几百行而矣。

所以所有的程序都完成在一个源文件里面。

记得那时候大一参加学校里的一个电子设计大赛,调试了一个多星期,所有程序加起来大概将近1000行,长长的一个文件,从上浏览下来都要好半天。

出了错误简单的语法错误还好定位,其它一些错误,往往找半天才找的到。

那个时候开始知道了模块化编程这个东西,也尝试着开始把程序分模块编写。

最开始是把相同功能的一些函数(譬如1602液晶的驱动)全部写在一个头文件(.h)文件里面,然后需要调用的地方包含进去,但是很快发现这种方法有其局限性,很容易犯重复包含的错误。

而且调用起来也很不方便。

很快暑假的电子设计大赛来临了,学校对我们的单片机软件编程进行了一些培训。

由于学校历年来参加国赛和省赛,因此积累了一定数量的驱动模块,那些日子,老师每天都会布置一定量的任务,让我们用这些模块组合起来,完成一定功能。

而正是那些日子模块化编程的培训,使我对于模块化编程有了更进一步的认识。

并且程序规范也开始慢慢注意起来。

此后的日子,无论程序的大小,均采用模块化编程的方式去编写。

很长一段时间以来,一直有单片机爱好者在QQ上和我一起交流。

有时候,他们会发过来一些有问题的程序源文件,让我帮忙修改一下。

同样是长长的一个文件,而且命名极不规范,从头看下来,着实是痛苦,说实话,还真不如我重新给他们写一个更快一些,此话到不假,因为手头积累了一定量的模块,在完成一个新的系统时候,只需要根据上层功能需求,在底层模块的支持下,可以很快方便的完成。

而不需要从头到尾再一砖一瓦的重新编写。

藉此,也可以看出模块化编程的一个好处,就是可重复利用率高。

下面让我们揭开模块化神秘面纱,一窥其真面目。

   C语言源文件*.c

      提到C语言源文件,大家都不会陌生。

因为我们平常写的程序代码几乎都在这个XX.C文件里面。

编译器也是以此文件来进行编译并生成相应的目标文件。

作为模块化编程的组成基础,我们所要实现的所有功能的源代码均在这个文件里。

理想的模块化应该可以看成是一个黑盒子。

即我们只关心模块提供的功能,而不管模块内部的实现细节。

好比我们买了一部手机,我们只需要会用手机提供的功能即可,不需要知晓它是如何把短信发出去的,如何响应我们按键的输入,这些过程对我们用户而言,就是是一个黑盒子。

在大规模程序开发中,一个程序由很多个模块组成,很可能,这些模块的编写任务被分配到不同的人。

而你在编写这个模块的时候很可能就需要利用到别人写好的模块的借口,这个时候我们关心的是,它的模块实现了什么样的接口,我该如何去调用,至于模块内部是如何组织的,对于我而言,无需过多关注。

而追求接口的单一性,把不需要的细节尽可能对外部屏蔽起来,正是我们所需要注意的地方。

   C语言头文件*.h

      谈及到模块化编程,必然会涉及到多文件编译,也就是工程编译。

在这样的一个系统中,往往会有多个C文件,而且每个C文件的作用不尽相同。

在我们的C文件中,由于需要对外提供接口,因此必须有一些函数或者是变量提供给外部其它文件进行调用。

假设我们有一个LCD.C文件,其提供最基本的LCD的驱动函数

  LcdPutChar(charcNewValue);  //在当前位置输出一个字符

而在我们的另外一个文件中需要调用此函数,那么我们该如何做呢?

  头文件的作用正是在此。

可以称其为一份接口描述文件。

其文件内部不应该包含任何实质性的函数代码。

我们可以把这个头文件理解成为一份说明书,说明的内容就是我们的模块对外提供的接口函数或者是接口变量。

同时该文件也包含了一些很重要的宏定义以及一些结构体的信息,离开了这些信息,很可能就无法正常使用接口函数或者是接口变量。

但是总的原则是:

不该让外界知道的信息就不应该出现在头文件里,而外界调用模块内接口函数或者是接口变量所必须的信息就一定要出现在头文件里,否则,外界就无法正确的调用我们提供的接口功能。

因而为了让外部函数或者文件调用我们提供的接口功能,就必须包含我们提供的这个接口描述文件----即头文件。

同时,我们自身模块也需要包含这份模块头文件(因为其包含了模块源文件中所需要的宏定义或者是结构体),好比我们平常所用的文件都是一式三份一样,模块本身也需要包含这个头文件。

下面我们来定义这个头文件,一般来说,头文件的名字应该与源文件的名字保持一致,这样我们便可以清晰的知道哪个头文件是哪个源文件的描述。

      于是便得到了LCD.C的头文件LCD.h其内容如下。

      #ifndef  _LCD_H_

          #define    _LCD_H_

          extern  LcdPutChar(charcNewValue);

      #endif

  这与我们在源文件中定义函数时有点类似。

不同的是,在其前面添加了extern修饰符表明其是一个外部函数,可以被外部其它模块进行调用。

      #ifndef    _LCD_H_

          #define    _LCD_H_

      #endif

          这个几条条件编译和宏定义是为了防止重复包含。

假如有两个不同源文件需要调用LcdPutChar(charcNewValue)这个函数,他们分别都通过#include“Lcd.h”把这个头文件包含了进去。

在第一个源文件进行编译时候,由于没有定义过_LCD_H_因此#ifndef_LCD_H_条件成立,于是定义_LCD_H_并将下面的声明包含进去。

在第二个文件编译时候,由于第一个文件包含时候,已经将_LCD_H_定义过了。

因此#ifndef_LCD_H_不成立,整个头文件内容就没有被包含。

假设没有这样的条件编译语句,那么两个文件都包含了extern  LcdPutChar(charcNewValue);就会引起重复包含的错误。

  不得不说的typedef 

      很多朋友似乎了习惯程序中利用如下语句来对数据类型进行定义

      #defineuint  unsignedint 

      #defineuchar  unsignedchar

  然后在定义变量的时候直接这样使用

  uint  g_nTimeCounter=0;

  不可否认,这样确实很方便,而且对于移植起来也有一定的方便性。

但是考虑下面这种情况你还会这么认为吗?

  #definePINTunsignedint*  //定义unsignedint指针类型

  PINT  g_npTimeCounter,g_npTimeState;

    那么你到底是定义了两个unsignedint型的指针变量,还是一个指针变量,一个整形变量呢?

而你的初衷又是什么呢,想定义两个unsignedint型的指针变量吗?

如果是这样,那么估计过不久就会到处抓狂找错误了。

  庆幸的是C语言已经为我们考虑到了这一点。

typedef正是为此而生。

为了给变量起一个别名我们可以用如下的语句

  typedef  unsigned  int  uint16;  //给指向无符号整形变量起一个别名uint16

    typedef  unsigned  int  *puint16;  //给指向无符号整形变量指针起一个别名puint16

  在我们定义变量时候便可以这样定义了:

  uint16  g_nTimeCounter  =  0;  //定义一个无符号的整形变量

  puint16  g_npTimeCounter  ;  //定义一个无符号的整形变量的指针

  在我们使用51单片机的C语言编程的时候,整形变量的范围是16位,而在基于32的微处理下的整形变量是32位。

倘若我们在8位单片机下编写的一些代码想要移植到32位的处理器上,那么很可能我们就需要在源文件中到处修改变量的类型定义。

这是一件庞大的工作,为了考虑程序的可移植性,在一开始,我们就应该养成良好的习惯,用变量的别名进行定义。

如在8位单片机的平台下,有如下一个变量定义

  uint16  g_nTimeCounter  =  0;

      如果移植32单片机的平台下,想要其的范围依旧为16位。

  可以直接修改uint16的定义,即

  typedef  unsigned  short  int  uint16; 

      这样就可以了,而不需要到源文件处处寻找并修改。

将常用的数据类型全部采用此种方法定义,形成一个头文件,便于我们以后编程直接调用。

文件名MacroAndConst.h

其内容如下:

#ifndef  _MACRO_AND_CONST_H_

#define  _MACRO_AND_CONST_H_

typedef  unsignedint  uint16; 

typedef  unsignedint  UINT; 

typedef  unsignedint  uint; 

typedef  unsignedint  UINT16; 

typedef  unsignedint  WORD; 

typedef  unsignedint  word;

typedef    int      int16; 

typedef    int      INT16; 

typedef  unsignedlong  uint32; 

typedef  unsignedlong    UINT32; 

typedef  unsignedlong  DWORD; 

typedef  unsignedlong  dword; 

typedef  long        int32; 

typedef  long        INT32; 

typedef  signed  char    int8;

typedef  signed  char    INT8; 

typedef  unsignedchar    byte; 

typedef  unsignedchar    BYTE; 

typedef  unsignedchar    uchar;

typedef  unsignedchar    UINT8; 

typedef  unsignedchar  uint8;

typedef  unsignedchar  BOOL; 

#endif

至此,似乎我们对于源文件和头文件的分工以及模块化编程有那么一点概念了。

那么让我们趁热打铁,将上一章的我们编写的LED闪烁函数进行模块划分并重新组织进行编译。

在上一章中我们主要完成的功能是P0口所驱动的LED以1Hz的频率闪烁。

其中用到了定时器,以及LED驱动模块。

因而我们可以简单的将整个工程分成三个模块,定时器模块,LED模块,以及主函数

对应的文件关系如下

main.c  

Timer.hTimer.c  --

Led.hLed.c    --

在开始重新编写我们的程序之前,先给大家讲一下如何在KEIL中建立工程模板吧,这个模板是我一直沿用至今。

希望能够给大家一点启发。

下面的内容就主要以图片为主了。

同时辅以少量文字说明。

我们以芯片AT89S52为例。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

OK,到此一个简单的工程模板就建立起来了,以后我们再新建源文件和头文件的时候,就可以直接保存到src文件目录下面了。

下面我们开始编写各个模块文件。

首先编写Timer.c这个文件主要内容就是定时器初始化,以及定时器中断服务函数。

其内容如下。

#include

bitg_bSystemTime1Ms=0;          //1MS系统时标

voidTimer0Init(void)

{

  TMOD&=0xf0;

  TMOD|=0x01;    //定时器0工作方式1

  TH0  =  0xfc;    //定时器初始值

  TL0  =  0x66;

  TR0  =1;

  ET0  =1;

}

voidTime0Isr(void)interrupt1

{

  TH0  =  0xfc;        //定时器重新赋初值

  TL0  =  0x66;

  g_bSystemTime1Ms=1;  //1MS时标标志位置位

}

由于在Led.c文件中需要调用我们的g_bSystemTime1Ms变量。

同时主函数需要调用Timer0Init()初始化函数,所以应该对这个变量和函数在头文件里作外部声明。

以方便其它函数调用。

Timer.h内容如下。

#ifndef_TIMER_H_

#define_TIMER_H_

externvoidTimer0Init(void);

externbitg_bSystemTime1Ms;

#endif

完成了定时器模块后,我们开始编写LED驱动模块。

Led.c内容如下:

#include

#include"MacroAndConst.h"

#include"Led.h"

#include"Timer.h"

staticuint16  g_u16LedTimeCount=0;//LED计数器

staticuint8  g_u8LedState=0;    //LED状态标志,0表示亮,1表示熄灭

#defineLEDP0        //定义LED接口

#defineLED_ON()    LED=0x00;  //所有LED亮

#defineLED_OFF()  LED=0xff;  //所有LED熄灭

voidLedProcess(void)

{

  if(0==g_u8LedState)  //如果LED的状态为亮,则点亮LED

  {

      LED_ON();

  }

  else          //否则熄灭LED

  {

      LED_OFF();

  }

}

voidLedStateChange(void)

{

  if(g_bSystemTime1Ms)        //系统1MS时标到

  {

      g_bSystemTime1Ms=0;

      g_u16LedTimeCount++;    //LED计数器加一

      if(g_u16LedTimeCount>=500)//计数达到500,即500MS到了,改变LED的状态。

      {

        g_u16LedTimeCount=0;

        g_u8LedState  =!

g_u8LedState  ;

      }

  }

}

这个模块对外的借口只有两个函数,因此在相应的Led.h中需要作相应的声明。

Led.h内容:

#ifndef_LED_H_

#define_LED_H_

externvoidLedProcess(void);

externvoidLedStateChange(void);

#endif

这两个模块完成后,我们将其C文件添加到工程中。

然后开始编写主函数里的代码。

如下所示:

#include

#include"MacroAndConst.h"

#include"Timer.h"

#include"Led.h"

sbitLED_SEG  =P1^4;  //数码管段选

sbitLED_DIG  =P1^5;  //数码管位选

sbitLED_CS11=P1^6;  //led控制位

voidmain(void)

{

      LED_CS11=1;//74HC595输出允许

  LED_SEG=0;  //数码管段选和位选禁止(因为它们和LED共用P0口)

      LED_DIG=0;

      Timer0Init();

      EA=1;

      while

(1)

    {

  LedProcess();

  LedStateChange();

    }

}

整个工程截图如下:

 

至此,第三章到此结束。

一起来总结一下我们需要注意的地方吧

1.  C语言源文件(*.c)的作用是什么

2.  C语言头文件(*.h)的作用是什么

3.  typedef的作用

4.  工程模板如何组织

5.  如何创建一个多模块(多文件)的工程

展开阅读全文
相关资源
猜你喜欢
相关搜索
资源标签

当前位置:首页 > 教学研究 > 教学案例设计

copyright@ 2008-2023 冰点文库 网站版权所有

经营许可证编号:鄂ICP备19020893号-2