C 语言源文件 *.c

理想的模块化应该可以看成是一个黑盒子。即我们只关心模块提供的功能,而不管模块内部的实现细节。在大规模程序开发中,一个程序由很多个模块组成,这些模块的编写任务被分配到不同的人,编写这个模块的时候很可能就需要利用到别人写好的模块的接口,至于模块内部是如何组织的,外界不需要知道。而追求接口的单一性,把不需要的细节尽可能对外部屏蔽起来,正是我们所需要注意的地方。

C 语言头文件 *.h

模块化编程,必然会涉及到多文件编译,也就是工程编译。在这样的一个系统中,往往会有多个C 文件,而且每个C 文件的作用不尽相同。在我们的C文件中,由于需要对外提供接口,因此必须有一些函数或者是变量提供给外部其它文件进行调用。头文件的作用正是在此。可以称其为一份接口描述文件。其文件内部不应该包含任何实质性的函数代码。我们可以把这个头文件理解成为一份说明书,说明的内容就是我们的模块对外提供的接口函数或者是接口变量,同时还包含一些很重要的宏定义以及一些结构体的信息。

  总的原则是:不该让外界知道的信息就不应该出现在头文件里,而外界调用模块内接口函数或者是接口变量所必须的信息就一定要出现在头文件里,否则,外界就无法正确的调用我们提供的接口功能。同时,我们自身模块也需要包含这份模块头文件(因为其包含了模块源文件中所需要的宏定义或者是结构体)。

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

LcdPutChar(char cNewValue) ;  //在当前位置输出一个字符 
    而在我们的另外一个文件中需要调用此函数,那么我们该如何做呢?

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

于是便得到了 LCD.C 的头文件LCD.h 其内容如下。 
        #ifndef    _LCD_H_ 
                #define     _LCD_H_ 
                extern   LcdPutChar(char cNewValue) ; 
        #endif

这与我们在源文件中定义函数时有点类似。不同的是,在其前面添加了extern 修饰符表明其是一个外部函数,可以被外部其它模块进行调用。

     #ifndef     _LCD_H_ 
       #define     _LCD_H_ 
 #endif

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

假如有两个不同源文件需要调用LcdPutChar(char cNewValue)这个函数,他们分别都通过#include “Lcd.h” 把这个头文件包含了进去。在第一个源文件进行编译时候,由于没有定义过 _LCD_H_ 因此 #ifndef _LCD_H_  条件成立,于是定义_LCD_H_并将下面的声明包含进去。在第二个文件编译时候,由于第一个文件包含时候,已经将_LCD_H_定义过了。因此#ifndef _LCD_H_不成立,整个头文件内容就没有被包含。

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

  这里要提及:typedef与define?

     很多朋友似乎了习惯程序中利用如下语句来对数据类型进行定义 
                    #define uint  unsigned int 
                    #define uchar  unsigned char 
      然后在定义变量的时候  直接这样使用 :uint  g_nTimeCounter = 0 ; 
        不可否认,这样确实很方便,而且对于移植起来也有一定的方便性。但是考虑下面这种情况你还会  这么认为吗?

     #define pINT unsigned int *    // 定义unsigned int  指针类型 
     A pINT2 a,b;  的效果同int *a, b;表示定义了一个整型指针变量a和整型变量b。

     typedef    (int*)      pINT;
    B pINT a,b;的效果同int *a; int *b;表示定义了两个整型指针变量。

typedef为了给变量起一个别名:

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

  51单片机的C 语言编程的时候,整形变量的范围是16位,而在基于32的微处理下的整形变量是32位。倘若我们在8位单片机下编写的一些代码想要移植到32位的处理器上,那么很可能我们就需要在源文件中到处修改变量的类型定义。这就是一个庞大的任务了,因此,在一开始,我们就应该养成良好的习惯,用变量的别名进行定义。

     如在8位单片机的平台下,有如下一个变量定义 
                  uint16    g_nTimeCounter  =  0 ; 
     如果移植32单片机的平台下,想要其的范围依旧为16位。可以直接修改 uint16  的定义,即 
                  typedef  unsigned  short  int    uint16 ; 
     这样就可以了,而不需要到源文件处处寻找并修改。

 1    将常用的数据类型全部采用此种方法定义,形成一个头文件,便于我们以后编程直接调用。
2 文件名 MacroAndConst.h
3 其内容如下:
4 #ifndef _MACRO_AND_CONST_H_
5 #define _MACRO_AND_CONST_H_
6 typedef unsigned int uint16;
7 typedef unsigned int UINT;
8 typedef unsigned int uint;
9 typedef unsigned int UINT16;
10 typedef unsigned int WORD;
11 typedef unsigned int word;
12 typedef int int16;
13 typedef int INT16;
14 typedef unsigned long uint32;
15 typedef unsigned long UINT32;
16 typedef unsigned long DWORD;
17 typedef unsigned long dword;
18 typedef long int32;
19 typedef long INT32;
20 typedef signed char int8;
21 typedef signed char INT8;
22 typedef unsigned char byte;
23 typedef unsigned char BYTE;
24 typedef unsigned char uchar;
25 typedef unsigned char UINT8;
26 typedef unsigned char uint8;
27 typedef unsigned char BOOL;
28 #endif

我们编写的LED 闪烁函数进行模块划分并重新组织进行编译:主要完成的功能是P0口所驱动的LED 以1Hz 的频率闪烁。其中用到了定时器,以及LED 驱动模
块。因而我们可以简单的将整个工程分成三个模块,定时器模块,LED 模块,以及主函数对应的文件关系如下:

main.c    ; Timer.c  --?Timer.h      ;Led.c      --?Led.h

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

 1 #include <reg52.h>
2 bit g_bSystemTime1Ms = 0 ; // 1MS 系统时标
3 void Timer0Init(void)
4 {
5 TMOD &= 0xf0 ;
6 TMOD |= 0x01 ; // 定时器0工作方式1
7 TH0 = 0xfc ; //定时器初始值
8 TL0 = 0x66 ;
9 TR0 = 1 ;
10 ET0 = 1 ;
11 }
12 void Time0Isr(void) interrupt 1
13 {
14 TH0 = 0xfc ; //定时器重新赋初值
15 TL0 = 0x66 ;
16 g_bSystemTime1Ms = 1 ; //1MS 时标标志位置位
17 }

由于在Led.c 文件中需要调用我们的 g_bSystemTime1Ms变量。同时主函数需要调用 Timer0Init()初始化函数,所以应该对这个变量和函数在头文件里作外部声明。以方便其它函数调用。

1 Ti mer.h  内容如下。
2 #ifndef _TIMER_H_
3 #define _TIMER_H_
4 extern void Timer0Init(void) ;
5 extern bit g_bSystemTime1Ms ;
6 #endif

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

 1 Led.c  内容如下:
2 #include <reg52.h>
3 #include "MacroAndConst.h"
4 #include "Led.h"
5 #include "Timer.h"
6 static uint16 g_u16LedTimeCount = 0 ; //LED 计数器
7 static uint8 g_u8LedState = 0 ; //LED 状态标志, 0表示亮,1表示熄灭
8 #define LED P0 // 定义 LED 接口
9 #define LED_ON() LED = 0x00 ; // 所有LED 亮
10 #define LED_OFF() LED = 0xff ; //所有LED 熄灭
11 void LedProcess(void)
12 {
13 if(0 == g_u8LedState) //如果LED 的状态为亮,则点亮LED
14 {
15 LED_ON() ;
16 }
17 else //否则熄灭 LED
18 {
19 LED_OFF() ;
20 }
21 }
22
23 void LedStateChange(void)
24 {
25 if(g_bSystemTime1Ms) // 系统1MS时标到
26 {
27 g_bSystemTime1Ms = 0 ;
28 g_u16LedTimeCount++ ; //LED 计数器加一
29 if(g_u16LedTimeCount >= 500) // 计数达到500, 即500MS到了, 改变 LED 的状态。
30 {
31 g_u16LedTimeCount = 0 ;
32 g_u8LedState = ! g_u8LedState ;
33 }
34 }
35 }

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

1 Led.h  内容:
2 #ifndef _LED_H_
3 #define _LED_H_
4 extern void LedProcess(void) ;
5 extern void LedStateChange(void) ;
6 #endif

这两个模块完成后,我们将其C 文件添加到工程中。然后开始编写主函数里的代码:

 1 #include <reg52.h>
2 #include "MacroAndConst.h"
3 #include "Timer.h"
4 #include "Led.h"
5 sbit LED_SEG = P1^4; //数码管段选
6 sbit LED_DIG = P1^5; //数码管位选
7 sbit LED_CS11 = P1^6; //led 控制位
8 void main(void)
9 {
10 LED_CS11 = 1 ; //74HC595输出允许
11 LED_SEG = 0 ; //数码管段选和位选禁止(因为它们和LED 共用P0口)
12 LED_DIG = 0 ;
13 Timer0Init() ;
14 EA = 1 ;
15 while(1)
16 {
17 LedProcess() ;
18 LedStateChange() ;
19 }
20 }

嵌入式C语言模块编写的更多相关文章

  1. 嵌入式C语言不可不用的关键字

    1.static关键字 这个关键字前面也有提到,它的作用是强大的. 要对static关键字深入了解,首先需要掌握标准C程序的组成. 标准C程序一直由下列部分组成: 1)正文段——CPU执行的机器指令部 ...

  2. 适合学习C语言开源项目——嵌入式脚本语言 Berry

    嵌入式脚本语言 Berry github网址 :https://github.com/Skiars/berry Berry 是一款面向小型嵌入式系统的脚本语言,目前发布了 0.1.0 版本.相比于其他 ...

  3. 嵌入式C语言经常使用keyword

    1.statickeyword 这个keyword前面也有提到.它的作用是强大的. 要对statickeyword深入了解.首先须要掌握标准C程序的组成. 标准C程序一直由下列部分组成:       ...

  4. 嵌入式C语言优化小技巧

    嵌入式C语言优化小技巧 1 概述 嵌入式系统是指完成一种或几种特定功能的计算机系统,具有自动化程度高,响应速度快等优点,目前已广泛应用于消费电子,工业控制等领域.嵌入式系统受其使用的硬件以及运行环境的 ...

  5. 第14讲:嵌入式SQL语言(基本技巧)

    一.交互式SQL的局限 & 嵌入式SQL的必要性 专业人员(如DBA)可以熟练地运用交互式SQL语言,但普通用户却不是那么容易上手,所以需要通过数据库应用程序来使用数据库.编写一个可以与数据库 ...

  6. 适用于Java的嵌入式脚本语言

    此文已由作者赵昕授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. fakescript 轻量级嵌入式脚本语言 https://github.com/esrrhs/fakescr ...

  7. 嵌入式 C 语言编程总结

    嵌入式 C 语言编程总结 目录: 全局变量 1.全局变量 在纯 C 语言(Pure C)开发的嵌入式程序中,需要在多处用到同一个变量,需要注意几点: 不要在头文件中对变量进行定义 头文件中变量的声明添 ...

  8. 用Kotlin语言重新编写Plaid APP:经验教训(I)

    原文标题:Converting Plaid to Kotlin: Lessons learned (Part 1) 原文链接:http://antonioleiva.com/plaid-kotlin- ...

  9. nginx自定义模块编写-实时统计模块--转载

    原文:http://www.vimer.cn/2012/05/nginx%E8%87%AA%E5%AE%9A%E4%B9%89%E6%A8%A1%E5%9D%97%E7%BC%96%E5%86%99- ...

随机推荐

  1. Java Message Service

    en.wikipedia.org/wiki/Java_Message_Service Messaging is a form of loosely coupled distributed commun ...

  2. Delphi的类方法不是静态方法

    Delphi中,类方法不是你理解的静态方法 Delphi中的类方法与C++类中的static方法完全没有可比性.Delphi中的类方法是有Self的,而Self就是类本身(注意不是对象),而这个Sel ...

  3. 修改MySQL的连接数

    实际项目中出现“too many connnections...”错误提示,发现MySQL的最大连接数满了,于是我就查了一下使用的MySQL的最大连接数是多少? 安装好数据库也没有修改过,这应该是默认 ...

  4. CALayer的隐式动画

    CALayer的使用 在我的理解中CALayer就是iOS中利用图层精简非交互式绘图.那么那些核心动画类.也就是变化图层的非交互式绘制规则而已.其中的本质就是将CALayer中的内容转化为map图.从 ...

  5. Protocol_BGP

    BGP协议 作者:Danbo 2015-7-8 BPG最重要的就是属性,下面我们针对路径属性分析一下.

  6. Android Weekly Notes Issue #243

    Android Weekly Issue #243 February 5th, 2017 Android Weekly Issue #243 本期内容包括: ConstraintLayout的动画; ...

  7. vuejs实现折叠面板展开收缩动画

    vuejs通过css3实现元素固定高度到auto高度的动画和auto高度到固定高度的动画. 循环列表,html: <template> <div class="newsli ...

  8. UVA10870 Recurrences —— 矩阵快速幂

    题目链接:https://vjudge.net/problem/UVA-10870 题意: 典型的矩阵快速幂的运用.比一般的斐波那契数推导式多了几项而已. 代码如下: #include <bit ...

  9. hdu-5744 Keep On Movin(思维)

    题目链接: Keep On Movin Time Limit: 4000/2000 MS (Java/Others)     Memory Limit: 65536/65536 K (Java/Oth ...

  10. 【CQ18高一暑假前挑战赛3】标程

    [A:LCM] #include<bits/stdc++.h> using namespace std; #define ll long long int main() { ll a,b, ...