嵌入式C语言模块编写
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语言模块编写的更多相关文章
- 嵌入式C语言不可不用的关键字
1.static关键字 这个关键字前面也有提到,它的作用是强大的. 要对static关键字深入了解,首先需要掌握标准C程序的组成. 标准C程序一直由下列部分组成: 1)正文段——CPU执行的机器指令部 ...
- 适合学习C语言开源项目——嵌入式脚本语言 Berry
嵌入式脚本语言 Berry github网址 :https://github.com/Skiars/berry Berry 是一款面向小型嵌入式系统的脚本语言,目前发布了 0.1.0 版本.相比于其他 ...
- 嵌入式C语言经常使用keyword
1.statickeyword 这个keyword前面也有提到.它的作用是强大的. 要对statickeyword深入了解.首先须要掌握标准C程序的组成. 标准C程序一直由下列部分组成: ...
- 嵌入式C语言优化小技巧
嵌入式C语言优化小技巧 1 概述 嵌入式系统是指完成一种或几种特定功能的计算机系统,具有自动化程度高,响应速度快等优点,目前已广泛应用于消费电子,工业控制等领域.嵌入式系统受其使用的硬件以及运行环境的 ...
- 第14讲:嵌入式SQL语言(基本技巧)
一.交互式SQL的局限 & 嵌入式SQL的必要性 专业人员(如DBA)可以熟练地运用交互式SQL语言,但普通用户却不是那么容易上手,所以需要通过数据库应用程序来使用数据库.编写一个可以与数据库 ...
- 适用于Java的嵌入式脚本语言
此文已由作者赵昕授权网易云社区发布. 欢迎访问网易云社区,了解更多网易技术产品运营经验. fakescript 轻量级嵌入式脚本语言 https://github.com/esrrhs/fakescr ...
- 嵌入式 C 语言编程总结
嵌入式 C 语言编程总结 目录: 全局变量 1.全局变量 在纯 C 语言(Pure C)开发的嵌入式程序中,需要在多处用到同一个变量,需要注意几点: 不要在头文件中对变量进行定义 头文件中变量的声明添 ...
- 用Kotlin语言重新编写Plaid APP:经验教训(I)
原文标题:Converting Plaid to Kotlin: Lessons learned (Part 1) 原文链接:http://antonioleiva.com/plaid-kotlin- ...
- 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- ...
随机推荐
- npm ERR! Unexpected end of JSON input while parsing near '...inimist":"^1.2.0"}
简介 在项目中执行npm install安装依赖包的时候.出现npm ERR! Unexpected end of JSON input while parsing near '...inimist& ...
- Win10更新后,IE和Edge以外的浏览器打开网页速度慢的解决方案
下载修复工具,提取码:you0 以管理员身份运行修复工具,点击“修复” 点击“确定” 提示“修复成功” 参考链接:Win10下极速模式无法打开网页的解决办法_360社区
- MVC设计模式应用
MVC登录程序清单 1 User JAVABean 用户登录操作类,跟数据库中表的信息对应 2 DatabaseConnection JavaBean 负责数据库的连接和关闭操作 3 IUserDAO ...
- UIView封装动画--iOS利用系统提供方法来做关键帧动画
iOS利用系统提供方法来做关键帧动画 ios7以后才有用. /*关键帧动画 options:UIViewKeyframeAnimationOptions类型 */ [UIView animateKey ...
- MotionEvent分析及ImageView缩放实现
这个类在各种View和用户的手势操作之间的交互存在很大的自定义空间.要理解清楚这个类的一些特性和意义,对自定义的新型控件很有帮助 先翻译一下开发者文档的描述 Overview Motion event ...
- Jenkins安装部署及tomcat的入门介绍
这里我们使用的方法是用servlet容器来部署jenkins,使用的是tomcat 下载下来tomcat,解压 bin目录下存放的一些启动关闭批处理文件 conf目录下放的一些配置文件,配置虚拟主机之 ...
- 一个测试基础面试题——如何测试web银行开户
之前面试被问到过这样一个问题,自己答的都是一些UI界面上的case,看了一些大神的关于这类面试题的总结才知道自己差的不是一点半点,今天也总结下. 内管银行开户,有账号.用户名.用户证件类型.证件号三个 ...
- svg矢量图制作工具(Sketsa SVG Editor) v7.1.1 中文免费版
下载地址:https://www.jb51.net/softs/555253.html Sketsa SVG Editor中文版是一款强大好用的矢量图绘制工具,该工具的最大特色就是集成了中文语言,且支 ...
- Android studio 添加assets文件夹
我们知道Eclipse创建的工程默认是有个assets文件夹的,但是Android studio默认没有帮我们创建,那么我们就自己创建一个就好啦. (1)手动创建 在项目的顶部有个下拉,默认选择的是A ...
- mongodb压缩——snappy、zlib块压缩,btree索引前缀压缩
MongoDB 3.0 WiredTiger Compression and Performance One of the most exciting developments over the li ...