51 模块化编程
模块化编程
在一般的编程方式中,我们会将所有需要用到的代码放在一个.c文件中,即主函数和自定义函数放在同一文件。这样的写法在面对代码量较小的程序时,会显得直观,方便,但是在面对大量的代码、函数时,会导致我们难以直接确定各函数或是某部分代码所在的位置,反而不利于我们管理。因此,将部分常用函数或是实现某部分功能的代码单独放在一个.c文件中,并创建对应的头文件(.h),有利于我们对代码的维护和管理,我们称这种编程方式为模块化编程。
模块化编程的实例:
所用文件
对于模块化编程,我们首先需要了解在编程过程中我们需要用到哪些文件
在模块化编程中,我们主要运用到的有两种文件类型:
- .c(C Source) 文件:.c文件是我们最常用到的文件,我们之前的代码编写都是在.c文件中
- .h(C/C++ Header)文件:.h文件即为我们常说的头文件,里面常存放一些我们对常量的宏定义、函数的声明等。
.c
.c文件主要用于存放我们编写的函数或是用于实现某个功能的代码
.h
.h文件主要用于存放函数的声明以及宏定义的常量等
函数的声明
任何函数在调用前都必须被定义或声明,之前我们所用到的函数都是写在main函数前面,此时我们不需要对其进行声明,但对于写在main函数后面(在VS中,这种写法会报警告,但是不影响编译)或是写在另一个.c文件中的函数,我们就需要对其进行声明。
//函数声明的语法:
返回类型 函数名(传入变量);
可以看出,函数的声明就是把函数的定义进行复述
我们一般将函数的声明存放于头文件中
预编译
在之前所写的程序中,我们都需要引入如STC89C5xRC.H或是stdio.h等头文件,引入头文件实际就是对头文件中的代码进行预编译。
为了防止头文件的重定义(即一个头文件被包含两次),我们常在头文件中添加特定语句
语句 | 意义 |
---|---|
#include<name.h> | 预编译name.h,即将头文件的内容加入程序中 |
#define name value | 将name定义为值为value的常量 |
#define name | 定义一个名为name的常量 |
#ifndef name | 当没有定义name时,执行下列语句 |
#ifdef name | 当定义了name时,执行下列语句 |
#pragma once | 只包含一次头文件 |
#endif | 相当于大括号的右半边 |
还有#if,#else,#elif, #undef等 |
#pragma once指令的作用是告诉编译器,在编译过程中只包含一次该头文件,避免了头文件的重复包含问题。但需要注意的是,#pragma once并不是标准的C/C++语言特性,不是所有编译器都支持。(虽然#pragma once在大多数主流的C/C++编译器中都得到支持,但它并不是C/C++标准中的一部分,因此在使用时应该考虑到跨平台的兼容性。)
在C/C++中,#ifndef指令用于检查一个标识符是否已经被定义过,如果被定义过,则不执行其包含的语句。如果在#ifndef中加上__,比如#ifndef __HEADER_H__(头文件的文件名中不需要包含下划线),这种习惯通常用于用户自定义的头文件,用来避免与系统提供的头文件或其他库文件产生命名冲突。
对于只有大小写不同的文件名而言,在Windows上,文件名的大小写可能会被忽略。即include时也不一定要按照源文件的命名,可以改变大小写。(在Windows上,两个文件的命名只有大小写不同,而字母不同是无法实现的,当你尝试这样改名时,系统会提示”已包含同名文件“)
#ifndef __HEADER_H__
#define __HEADER_H__
// 头文件内容
#endif
模块化编程的运用
这里将延时和数码管显示两个功能分别存入相应.c和.h文件
main.c
#include<STC89C5xRC.H>
#include"Delay.h"
#include"Nixie_display.h"
//<>表示在安装目录寻找文件
//""表示在当前目录寻找文件
void main()
{
while(1)
{
Delay(5000);
NixieTube_Display(3, 3);
Delay(5000);
NixieTube_Display(4, 4);
}
}
Delay.h
#ifndef __DELAY_H__
#define __DELAY_H__
void Delay(unsigned int xms);
#endif
Delay.c
#include<INTRINS.H>
//用到了_nop_注意这里要加头文件
#include<STC89C5xRC.H>
void Delay(unsigned int xms) //@11.0592MHz
{
unsigned char data i, j;
while(xms > 0)
{
_nop_();
i = 2;
j = 199;
do
{
while (--j);
} while (--i);
xms--;
}
}
Nixie_display.h
#ifndef __NIXIE_DISPLAY_H__
#define __NIXIE_DISPLAY_H__
void NixieTube_Display(unsigned char location, unsigned char num);
#endif // !__NIXIE_DISPLAY_H__
Nixie_display.c
#include<STC89C5xRC.H>
#include"Delay.h"
unsigned char NixieTable[10] = {0x3F, 0x06, 0x5B, 0x4F, 0x66, 0x6D, 0x7D, 0x07, 0x7F, 0x6F};
void NixieTube_Display(unsigned char location, unsigned char num)
{
switch(location)
{
case 1:
P2_4 = 1;
P2_3 = 1;
P2_2 = 1;
break;
case 2:
P2_4 = 1;
P2_3 = 1;
P2_2 = 0;
break;
case 3:
P2_4 = 1;
P2_3 = 0;
P2_2 = 1;
break;
case 4:
P2_4 = 1;
P2_3 = 0;
P2_2 = 0;
break;
case 5:
P2_4 = 0;
P2_3 = 1;
P2_2 = 1;
break;
case 6:
P2_4 = 0;
P2_3 = 1;
P2_2 = 0;
break;
case 7:
P2_4 = 0;
P2_3 = 0;
P2_2 = 1;
break;
case 8:
P2_4 = 0;
P2_3 = 0;
P2_2 = 0;
break;
}
P0 = NixieTable[num];
Delay(1);
//让数码管稳定显示,否则亮度会变低
P0 = 0x00;
//清零
}
LCD1602
关于lcd1602的运用,目前为止我只能根据江协科技提供的源码和头文件进行运用,自己还没能力进行编写
使用LCD1602液晶屏作为调试窗口,提供类似printf函数的功能,可实时观察单片机内部数据的变换情况,便于调试和演示。
STC89C52RC开发板上的LCD1602接口(图源:普中科技开发手册)
图源:沃兹济派德
使用LCD后,数码管和LED6、7、8显示会出问题(LCD1602连接的是P0口和P2口,发生冲突)
可以通过调节接口旁的电位器来调节对比度
#include <STC89C5xRC.H>
#include"LCD1602.h"
void main()
{
LCD_Init();
//初始化
LCD_ShowChar(1, 1, 'A');
//在一行一列显示'A'
LCD_ShowString(2, 1, "Hello,World!");
//显示字符串
LCD_ShowNum(1, 3, 52, 2);
//显示无符号数字,长度过短,高位不显示,长度过长,高位补0
LCD_ShowSignedNum(1, 6, -88, 2);
//这里设定显示的位数不包括负号
LCD_ShowHexNum(2, 1, 0xC4, 2);
//显示16进制的数,后面显示的会把前面显示的覆盖
LCD_ShowBinNum(1, 7, 0xA0, 8);
//显示二进制数字
while(1)
{
}
}