原文出处:http://blog.csdn.net/wanruoqingkong/article/details/24286625

当你在一个项目小组做一个相对较复杂的工程时,意味着你不再独自单干。你需要和你的小组成员分工合作,一起完成项目,这就要求小组成员各自负责一部分工程。比如你可能只是负责通讯或者显示这一块。这个时候,你就应该将自己的这一块程序写成一个模块,单独调试,留出接口供其它模块调用。最后,小组成员都将自己负责的模块写完并调试无误后,由项目组长进行组合调试。像这些场合就要求程序必须模块化。模块化的好处是很多的,不仅仅是便于分工,它还有助于程序的调试,有利于程序结构的划分,还能增加程序的可读性和可移植性。

初学者往往搞不懂如何模块化编程,其实它是简单易学,而且又是组织良好程序结构行之有效的方法之一.

本文将先大概讲一下模块化的方法和注意事项,最后将以初学者使用最广的keil c编译器为例,给出模块化编程的详细步骤。

模块化程序设计应该理解以下概述:

(1) 模块即是一个.c 文件和一个.h 文件的结合,头文件(.h)中是对于该模块接口的声明;

这一条概括了模块化的实现方法和实质:将一个功能模块的代码单独编写成一个.c文件,然后把该模块的接口函数放在.h文件中.举例:假如你用到液晶显示,那么你可能会写一个液晶驱动模块,以实现字符、汉字和图像的现实,命名为: led_device.c,该模块的.c文件大体可以写成:

/*************************************************************************

* 液晶驱动模块

*

* 文 件: lcd_device.c

* 编 写 人: 小瓶盖

* 描 述:液晶串行显示驱动模块,提供字符、汉字、和图像的实现接口

* 编写时间: 2009.07.03

* 版 本:1.2

*************************************************************************/

#include …

//定义变量

 unsigned char value;//全局变量

//定义函数

//这是本模块第一个函数,起到延时作用,只供本模块的函数调用,所以用到static关键字修饰

/********************延时子程序************************/

static void delay (uint us) //delay time

{}

//这是本模块的第二个函数,要在其他模块中调用

/*********************写字符程序**************************

** 功能:向LCD写入字符

** 参数:dat_comm 为1写入的是数据,为0写入的是指令

content 为写入的数字或指令

******************************************************/

void wr_lcd (uchar dat_comm,uchar content)

{}

……

……

/***************************** END Files***********************************/

注:此处只写出这两个函数,第一个延时函数的作用范围是模块内,第二个,它是其它模块需要的。为了简化,此处并没有写出函数体.

.h文件中给出模块的接口.在上面的例子中, 向LCD写入字符函数:wr_lcd (uchar dat_comm,uchar content)就是一个接口函数,因为其它模块会调用它,那么.h文件中就必须将这个函数声明为外部函数(使用extrun关键字修饰),另一个延时函数:void delay (uint us)只是在本模块中使用(本地函数,用static关键字修饰),因此它是不需要放到.h文件中的。

.h文件格式如下:

/*****************************************************************************

* 液晶驱动模块 头文件

*

* 文 件: lcd_device.h

* 编 写 人: 小瓶盖

* 编写时间: 2010.07.03

* 版 本:1.0

*********************************************************************************/

//声明全局变量

extern unsigned char value;

//声明接口函数

extern void wr_lcd (uchar dat_comm,uchar content); //向LCD写入字符

……

/***************************** END Files***********************************/

这里注意三点:

1. 在keil 编译器中,extern这个关键字即使不声明,编译器也不会报错,且程序运行良好,但不保证使用其它编译器也如此。强烈建议加上,养成良好的编程规范。

2. .c文件中的函数只有其它模块使用时才会出现在.h文件中,像本地延时函数static void delay (uint us)即使出现在.h文件中也是在做无用功,因为其它模块根本不去调用它,实际上也调用不了它(static关键字的限制作用)。

3.注意本句最后一定要加分号”;”,相信有不少同学遇到过这个奇怪的编译器报错: error C132: 'xxxx': not in formal parameter list,这个错误其实是.h的函数声明的最后少了分号的缘故。

模块的应用:假如需要在LCD菜单模块lcd_menu.c中使用液晶驱动模块lcd_device.c中的函数void wr_lcd (uchar dat_comm,uchar content),只需在LCD菜单模块的lcd_menu.c文件中加入液晶驱动模块的头文件lcd_device.h即可.

/***************************************************************************

* 液晶菜单模块

*

* 文 件: lcd_menu.c

* 编 写 人: 小瓶盖

* 说 明:LCD菜单模块,最多实现256级菜单,与硬件无关。

* 编写时间: 2010.07.03

* 版 本:1.0

**************************************************************************/

#include“lcd_device.h //包含液晶驱动程序头文件,之后就可以在该.c文件中调用//lcd_device.h中的全局函数,使用液晶驱动程序里的全局//变量(如果有的话)。

//调用向LCD写入字符函数

wr_lcd (0x01,0x30);

//对全局变量赋值

value=0xff;

(2) 某模块提供给其它模块调用的外部函数及数据需在.h 中文件中冠以extern 关键字声明;

这句话在上面的例子中已经有体现,即某模块提供给其它模块调用的外部函数和全局变量需在.h 中文件中冠以extern 关键字声明,下面重点说一下全局变量的使用。使用模块化编程的一个难点(相对于新手)就是全局变量的设定,初学者往往很难想通模块与模块公用的变量是如何实现的,常规的做法就是本句提到的,在.h文件中外部数据冠以extern关键字声明。比如上例的变量value就是一个全局变量,若是某个模块也使用这个变量,则和使用外部函数一样,只需在使用的模块.c文件中包含#include“lcd_device.h”即可。

另一种处理模块间全局变量的方法来自于嵌入式操作系统uCOS-II,这个操作系统处理全局变量的方法比较特殊,也比较难以理解,但学会之后妙用无穷,这个方法只需用在头文件中定义一次。方法为:

在定义所有全局变量(uCOS-II将所有全局变量定义在一个.h文件内)的.h头文件中:

#ifdef xxx_GLOBALS

#define xxx_EXT

#else

#define xxx_EXT extern

#endif

.H 文件中每个全局变量都加上了xxx_EXT的前缀。xxx 代表模块的名字。

该模块的.C文件中有以下定义:

#define xxx_GLOBALS

#include "includes.h"

当编译器处理.C文件时,它强制xxx_EXT(在相应.H文件中可以找到)为空,(因为xxx_GLOBALS已经定义)。所以编译器给每个全局变量分配内存空间,而当编译器处理其他.C 文件时,xxx_GLOBAL没有定义,xxx_EXT 被定义为extern,这样用户就可以调用外部全局变量。为了说明这个概念,可以参见uC/OS_II.H,其中包括以下定义:

#ifdef OS_GLOBALS

#define OS_EXT

#else

#define OS_EXT extern

#endif

OS_EXT INT32U OSIdleCtr;

OS_EXT INT32U OSIdleCtrRun;

OS_EXT INT32U OSIdleCtrMax;

同时,uCOS_II.H 有中以下定义:

#define OS_GLOBALS

#include “includes.h”

当编译器处理uCOS_II.C 时,它使得头文件变成如下所示,因为OS_EXT 被设置为空。

INT32U OSIdleCtr;

INT32U OSIdleCtrRun;

INT32U OSIdleCtrMax;

这样编译器就会将这些全局变量分配在内存中。当编译器处理其他.C 文件时,头文件变成了如下的样子,因为OS_GLOBAL没有定义,所以OS_EXT 被定义为extern。

extern INT32U OSIdleCtr;

extern INT32U OSIdleCtrRun;

extern INT32U OSIdleCtrMax;

在这种情况下,不产生内存分配,而任何 .C文件都可以使用这些变量。这样的就只需在 .H文件中定义一次就可以了。

(3) 模块内的函数和全局变量需在.c 文件开头冠以static 关键字声明;

这句话主要讲述了关键字static的作用。Static是一个相当重要的关键字,他能对函数和变量做一些约束,而且可以传递一些信息。比如上例在LCD驱动模块.c文件中定义的延时函数static void delay (uint us),这个函数冠以static修饰,一方面是限定了函数的作用范围只是在本模块中起作用,另一方面也给人传达这样的信息:该函数不会被其他模块调用。下面详细说一下这个关键字的作用,在C 语言中,关键字static 有三个明显的作用:

1.在函数体,一个被声明为静态的变量在这一函数被调用过程中维持其值不变。

2.在模块内(但在函数体外),一个被声明为静态的变量可以被模块内所用函数访问,但不能被模块外其它函数访问。它是一个本地的全局变

量。

3.在模块内,一个被声明为静态的函数只可被这一模块内的其它函数调用。那就是,这个函数被限制在声明它的模块的本地范围内使用。

前两个都比较容易理解,最后一个作用就是刚刚举例中提到的延时函数(static void delay (uint us)),本地化函数是有相当好的作用的。

(4) 永远不要在.h 文件中定义变量!

呵呵,似乎有点危言耸听的感觉,但我想也不会有多少人会在.h文件中定义变量的。

比较一下代码:

代码一:

/*module1.h*/

int a = 5; /* 在模块1 的.h 文件中定义int a */

/*module1 .c*/

#include "module1.h" /* 在模块1 中包含模块1 的.h 文件 */

/*module2 .c*/

#include "module1.h" /* 在模块2 中包含模块1 的.h 文件 */

/*module3 .c*/

#include "module1.h" /* 在模块3 中包含模块1 的.h 文件 */

以上程序的结果是在模块1、2、3 中都定义了整型变量a,a 在不同的模块中对应不同的地址元,这个世界上从来不需要这样的程序。正确的做法是:

代码二:

/*module1.h*/

extern int a; /* 在模块1 的.h 文件中声明int a */

/*module1 .c*/

#include "module1.h" /* 在模块1 中包含模块1 的.h 文件 */

int a = 5; /* 在模块1 的.c 文件中定义int a */

/*module2 .c*/

#include "module1.h" /* 在模块2 中包含模块1 的.h 文件 */

/*module3 .c*/

#include "module1.h" /* 在模块3 中包含模块1 的.h 文件 */

这样如果模块1、2、3 操作a 的话,对应的是同一片内存单元。

注:

一个嵌入式系统通常包括两类(注意是两类,不是两个)模块:

(1)硬件驱动模块,一种特定硬件对应一个模块;

(2)软件功能模块,其模块的划分应满足低偶合、高内聚的要求。

下面以keil C 编译器为例,讲一下模块化编程的步骤。

下面这个程序分为三层,共7个模块,共同为主程序服务(它们之间也会相互调用)。

程序的结构图如下所示:

程序主要模块和功能简介:

一. 底层驱动

1. 红外键盘:程序通过红外键盘进行操作。红外键盘独占定时器0和外部中断0,以实现红外解码和键盘键值的识别。红外键盘定义了五个按键,分别为上翻、下翻、左翻、右翻和确认键。

2. LCD液晶显示:程序主要通过LCD显示信息,LCD液晶显示驱动提供显示汉字、图形和ASCII码的函数接口。可以全屏、单行显示汉字,任意位置显示ASCII码,还可以全屏、半屏显示图形。

二. 功能模块

1. LCD菜单程序:菜单程序可以使人机交互更加方便、容易。本菜单程序的菜单级别深度受RAM大小的限制,每增加一级菜单将多消耗4字节的RAM。菜单程序主要完成菜单功能函数的调度,LCD显示刷新。

2. 计算器程序:实现65536以内的加、减、乘、除,超出范围会出现溢出,溢出发生时,LCD显示“错误:出现溢出”的错误提示,同时本次运算被忽略。对于负数会显示“-”号,除数为零时LCD显示“错误:除数为零”的错误提示。

3. 开机次数记忆程序:主要对基于IIC总线的EEPROM进行读写,单片机每次上电后,将开机次数写入EEPROM.

4. 串口测试程序:进入该程序后,单片机向电脑发送字符串“Hello Word!”,发送数字24(以字符的形式显示)。编写此程序的目的是为了能够方便的向电脑发送字符串和变量,便于程序的调试。串口占用串口资源,与频率测量程序共享定时器1

5. 频率测量:复用定时器1,占用外部中断1,实现5~20KHZ频率的测量.

三. 主程序

主程序主要完成程序的初始化,LCD菜单显示,监视键盘程序并根据键值更新菜单。

步骤为:

1.新建工程。

2.点击File—New(或者点击快捷图标:),新建一个文档。

3.点击File—Save(或者点击快捷图标:),保存新建的文档,在文件名后填写LCD_device.c(液晶驱动模块: LCD_device,提供显示汉字、字符和图像的接口),点击确定。

在该文档内编写LCD驱动程序。

4. 点击File—New(或者点击快捷图标:),再新建一个文档。

5. 点击File—Save(或者点击快捷图标:),保存新建的文档,在文件名后填写LCD_device.h(液晶驱动模块的头文件,模块的接口和全局变量在这里定义)。点击确定。在该文档中整理全局变量和接口函数。以上步骤之后的效果见下图:

至此,液晶驱动模块书写完毕,可以对这个模块单独的调试。

6.重复以上步骤2~5,定义 红外键盘模块:key.c与key.h

菜单模块:menu.c与menu.h

串口通信模块:uart_.c与uart.h

计算器模块:counter.c与counter.h

频率测量模块:mea_fre.c与mea_fre.h

开机次数记忆模块:eepram.c与eepram.h

7.重复以上步骤2~3,定义主程序main.c

最终效果如下图所示:

完成1~7个步骤后,有些小白就习惯性的点击编译按钮了,这时候会出现两个警告信息:

*** WARNING L1: UNRESOLVED EXTERNAL SYMBOL

*** WARNING L2: REFERENCE MADE TO UNRESOLVED EXTERNAL

这是因为你只是编写好了程序模块,却没有把他们加入到工程的缘故。

解决方法:在Project Workspace框中,右击Source group 1文件夹,选择Add Files to Group‘Source Group 1’,在弹出的对话框中添加你的.c文件即可。

遥想很久很久以前,我也对上面的两个警告有过亲身体会。那时候我还在大学,周围有一大群的好哥们一起玩,想逃课逃课,想睡觉睡觉,现在…想起来只剩唏嘘!!!

嵌入式编程之模块化编程相关推荐

  1. python模块化编程 pdf_模块化编程ModularProgramming-GitHub.PDF

    模块化编程ModularProgramming-GitHub 第4章模块化编程 Modular Programming 申丽萍 lpshen@ 第4章模块化编程  模块化程序设计  函数  自顶 ...

  2. python模块化编程_Python模块化编程

    目录 模块化 在Python中,一个.py文件就称之为一个模块(Module),为了避免模块名冲突,Python又引入了按目录来组织模块的方法,称为包(Package).举个例子,一个abc.py的文 ...

  3. Arduino模块化编程

    当用Arduino做复杂工程项目时,程序难免会变得很大.这时候要修改个别参数或函数的时候会变得麻烦,简而言之,项目程序管理难度增高了,程序代码维护会变得困难.这时候,就产生了将一个ino文件分解成多个 ...

  4. 单片机零基础入门(9-1)实战:模块化编程(模块化两个案例含源码--以及无法显示头文件(.h)的解决方案)

    单片机零基础入门(9-1)实战:模块化编程-(以及无法显示头文件(.h)的解决方案) 本文作为单片机零基础入门(8-5)模块化编程的拓展和补充,比前面的单片机零基础入门(8-5)模块化编程更为详细. ...

  5. AutoLeaders控制组——51单片机学习笔记(模块化编程、LCD_1602、矩阵键盘)

    本篇内容是观看B站江科大自化协UP主的教学视频所做的笔记,对其中内容有所引用,并结合自己的单片机板块进行了更改调整. 以下笔记内容以一个视频为一个片段(内容较多,可能不适合速食,望见谅) 一些内容涉及 ...

  6. c++头文件被c语言调用需要注意什么_嵌入式C语言之模块化编程

    C语言中的模块化体现在两个方面: 1 函数. 函数是C语言的最小单位,每个函数均实现一个独立的功能,于是每个函数均可以当做是一个最小的功能模块.这样,C语言就实现了最基本的模块化. 2 文件. 在C语 ...

  7. 嵌入式C语言之---模块化编程

    当你在一个项目小组做一个相对较复杂的工程时,意味着你不再独自单干.你需要和你的小组成员分工合作,一起完成项目,这就要求小组成员各自负责一部分工程.比如你可能只是负责通讯或者显示这一块.这个时候,你就应 ...

  8. 嵌入式——模块化编程

    传统方法编程:所有的函数均放在main.c里,若使用的模块比较多,则一个文件内会出现很多的代码,不利于代码的组织和管理,而且很影响编程者的思路. 模块化编程:把各个模块的代码放在不同的.c文件里,在. ...

  9. 单片机实现模块化编程:思维+实例+系统教程(实用程度令人发指)

    单片机怎么实现模块化编程?思维+实例+系统教程(实用程度令人发指) \\\插播一条:文章末尾有惊喜哟~/// 在刚初始从事研发工作的那几年,我主要做单片机软件开发的工作,虽然功能吧都能实现,但是总觉得 ...

最新文章

  1. 获取 GPG 密钥失败:[Errno 14] 2011-05-26 14:43
  2. 【移动战略说·第十六期】创业者面临的机遇与挑战(杭州站)
  3. mysql avg 报错_MySQL报错汇总
  4. 如何将nodejs项目程序部署到阿里云服务器上
  5. 动脑2017android_您肯定要在2017年初尝试的25个新Android库
  6. 浅谈Java的数据结构
  7. php输出多行多列,数据库查询记录php 多行多列显示
  8. mysql 跨服务器 etl_mysql数据库跨服务器查询【需要确定mysql支持FEDERATED ,可以参照文章内容自己配置】...
  9. SpringMVC用注解写第一个程序HelloSpringMVC
  10. 框架实现修改功能的原理_从无到有RPC框架 - RPC原理及实现(文末还有开源的优秀RPC框架)...
  11. 2022“杭电杯”中国大学生算法设计超级联赛(5)杭电多校第五场
  12. Win10系统隐藏文件资源管理器中的3D对象等七个文件夹
  13. Unity学习:瓦片地图
  14. Debian10.6 Xfce 系统安装教程
  15. 搭建iis自己可以别人_自己也可以搭建一台好用实惠的软导一体机!
  16. linux 蓝牙 iphone,Linux On iPhone 7 现在可运行 Wayland
  17. 语音识别学习记录 [kaldi中的openfst]
  18. eNSP第三篇:STP,生成树,xSTP,MSTP,多生成树,交换机工作原理,环路的形成
  19. 机器自动翻译古文拼音 - 十大宋词 - 青玉案 凌波不过横塘路 贺铸
  20. 设置单选框radio不可选(禁用)

热门文章

  1. 简单实用的Windows命令(一)
  2. C#与Java的比较(转)
  3. 诗歌rails之 定时任务 rufus-scheduler
  4. linux程序员的proc文件系统
  5. Lua Behavior Tree For Unity3D(Lua描述行为树For Unity3D)
  6. 自定义Unity对象生命周期管理集成ADO.NET Entity Framework
  7. 从阿里孵化钉钉谈起,大公司内部创业到底有多难?
  8. 【翻译自mos文章】OGG replicat 进程使用的 TCP port
  9. Laravel 5.1 artisan 的使用
  10. Throwable、Error、Exception、RuntimeException 区别 联系