在刚接触到单片机时,代码较短,单片机功能的实现函数都写在main.c一个文件。随着学习的深入,多模块的使用和代码量的增加使得单用一个.c文件显得程序很混乱。模块化编程的使用可增强代码的可读性,可移植性。这里以51单片机驱动LCD1602的为例讲解。
一、知识储备---#ifndef
作用:防止头文件的重复包含和编译
定义
#ifndef x
#define x
...
#endif
这是宏定义的一种,它可以根据是否已经定义了一个变量来进行分支选择,一般用于调试等等.实际上确切的说这应该是预处理功能中三种(宏定义,文件包含和条件编译)中的一种----条件编译。 C语言在对程序进行编译时,会先根据预处理命令进行“预处理”。C语言编译系统包括预处理,编译和链接等部分。
#ifndef x
//先测试x是否被宏定义过
#define x
//如果没有宏定义下面就宏定义x并编译下面的语句
...
#endif
//如果已经定义了则编译#endif后面的语句
条件指示符#ifndef检查预编译常量在前面是否已经被宏定义。如果在前面没有被宏定义,则条件指示符的值为真,于是从#ifndef到#endif之间的所有语句都被包含进来进行编译处理。相反,如果#ifndef指示符的值为假,则它与#endif指示符之间的行将被忽略。条件指示符#ifndef 的最主要目的是防止头文件的重复包含和编译。
补充一些内容
千万不要忽略了头件的中的#ifndef,这是一个很关键的东西。比如你有两个C文件,这两个C文件都include了同一个头文件。而编译时,这两个C文件要一同编译成一个可运行文件,于是问题来了,大量的声明冲突。
还是把头文件的内容都放在#ifndef和#endif中吧。不管你的头文件会不会被多个文件引用,你都要加上这个。一般格式是这样的:
#ifndef <标识>
#define <标识>
......
#endif
<标识>在理论上来说可以是自由命名的,但每个头文件的这个“标识”都应该是唯一的。标识的命名规则一般是头文件名全大写,前后加下划线,并把文件名中的“.”也变成下划线,如:LCD1602.h
#ifndef __LCD1602_H_
#define __LCD1602_H_
......
#endif
#ifndef xxx//如果没有定义xxx
#define xxx//定义xxx
#endif //结束如果
这个用法主要是在头文件中,主要是为了防止类重复的include,所以在类的头文件之前加上前面两个,用类名替代xxx,在最后加上最后一句。
了解更多:https://blog.youkuaiyun.com/qq_33658067/article/details/79443014
二、keil上的实现
1、创建工程,这里不做过多叙述。
2、工程分组,c文件的添加,按图示步骤进行。
3.头文件的编写及路径添加,LCD1602.h
#ifndef __LCD1602_H_ //检测是否被定义 #define __LCD1602_H_ //未定义则为初次调用该头文件,则定义该宏 #include "reg51.h" //51单片机特殊功能寄存器声明文件 #define LCD1602_DATAPINS P0 //管脚定义 sbit LCD1602_RS=P2^6; //管脚定义 sbit LCD1602_RW=P2^5; sbit LCD1602_E=P2^7; void LcdInit(void); //.c文件中的函数声明 void LCD_CLR(void); void LcdWriteCom(unsigned char com); void LcdWriteData(unsigned char dat); void LCD1602_SYB(unsigned char x,unsigned char y,unsigned char *str); void LCD1602_NUM(unsigned char x,unsigned char y,int a); void LCD1602_FLOAT(unsigned char x,unsigned char y,double dat); #endif //结束如果
如对应c文件有全局变量且要在其它地方使用,可在#define 后声明该变量, 如 extern int val。
如在一些情况下管脚不对应,只需更改宏定义的管脚,很方便移植性强。
按以下步骤添加头文件路径。
4、C文件的编写,LCD1602.c
#include "reg51.h" //51单片机特殊功能寄存器声明文件 #include "LCD1602.h" //对应的头文件 #include "delay.h" //需用到延时函数 #include "stdio.h" //--------写命令 void LcdWriteCom(unsigned char com) { LCD1602_E = 0; LCD1602_RS = 0; LCD1602_RW = 0; LCD1602_DATAPINS = com; LCD1602_E = 1; delay_12us(1); LCD1602_E = 0; delay_12us(1); } //--------写数据 void LcdWriteData(unsigned char dat) { LCD1602_E = 0; LCD1602_RS = 1; LCD1602_RW = 0; LCD1602_DATAPINS = dat; LCD1602_E = 1; delay_12us(1); LCD1602_E = 0; delay_12us(1); } //--------清屏 void LCD_CLR(void) { LcdWriteCom(0x01);//清屏 delay_ms(5); } //--------初始化 void LcdInit(void) { LcdWriteCom(0x38);//设置为8总线,双行显示,5*10点阵字符 LcdWriteCom(0x0c);//开启显示,不显示光标 LcdWriteCom(0x06);//自左向右显示,显示内容不移动 LCD_CLR(); } //--------从x行y列坐标处开始显示字符串 void LCD1602_SYB(unsigned char x,unsigned char y,unsigned char *str) { unsigned char addr,i; if(x==1) addr = 0x00+y-1; else addr = 0x40+y-1; LcdWriteCom(addr+0x80);//设置显示地址 while(*str!='\0') { i++; LcdWriteData(*str++); } // while(i<7)//用空格覆盖上次显示,这里写的不好 // { // i++; // LcdWriteData(' '); // } } //--------将整型数据转换为字符串数组并显示 void LCD1602_NUM(unsigned char x,unsigned char y,signed int a) { char str[7]; sprintf(str,"%d ",a); LCD1602_SYB(x,y,str); LCD1602_SYB(x,y,num); } //--------显示浮点型数据 void LCD1602_FLOAT(unsigned char x,unsigned char y,double dat) { char str[10]; sprintf(str,"%.3f ",dat); LCD1602_SYB(x,y,str); }
三、效果展示
简洁的main.c文件,可读性强。
#include "reg51.h"
#include "LCD1602.h"
void main(void)
{
LcdInit();
//显示测试
LCD1602_SYB(1,1,"Welcome to SWPU");
LCD1602_NUM(2,1,12345);
LCD1602_FLOAT(2,9,-123.01);
while(1);
}
附:
delay.h
#ifndef __DELAY_H_
#define __DELAY_H_
#include "reg51.h"
//超级不准的延时。。。
void delay_ms(unsigned int c);
void delay_12us(unsigned char c);
#endif
delay.c
#include "reg51.h"
#include "delay.h"
void delay_ms(unsigned int c)
{
unsigned char a,b;
for(c;c>0;c--)
for(b=142;b>0;b--)
for(a=2;a>0;a--);
}
void delay_12us(unsigned char c)
{
unsigned char a;
for(c;c>0;c--)
for(a=4;a>0;a--);
}
--By Young