1. 基于proteus的51单片机开发实例30-模块化程序设计
1.1. 实验目的
模块化程序设计
不知不觉我们的51单片机开发实例已经进行到第三十篇了,是时候进行一个总结和反思了,总结什么?反思什么呢?我们先从程序结构开始吧。
总结在前面的29个例子中的程序,我们会发现:所有的程序都在一个main.c文件中,第一个程序只有不到20行,电脑屏幕上直接能够从头看到尾,这个程序看起来非常直观,所有程序语句都是一目了然的。随着学习的深入,我们编写的程序是越来越复杂了,程序的长度是疯狂地增加。到了第29个实例,我们的程序量已经接近500行了,假设我们要找最后一个函数的内容,那就要用鼠标拨拉好一会才能找到这个函数,是不是会有“好长啊”的感慨?
更为致命的是,我们这个程序中既有延时程序、又有液晶显示程序、还有DS18B20的读写程序。所有这个程序都挤在一个main.c文件中,即使我们已经按照延时、显示、读写进行了划分,整体感觉是不是有些乱?
现在我们看看DS18B20学习实验的第三个程序,这个程序里面有很多函数,这些函数的声明和定义在整个程序里面占了很大的比例。我们在编写和使用这些函数的时候,需要不停的翻找相关的程序部分。显得很麻烦。而且整个程序显得有些乱。那么能不能把程序精简一下,能不能把具有相关功能的函数放在一起,能不能向我们使用头文件的时候,直接使用一个包含命令就把一些相关功能包含到程序里,而我们在程序里只要调用我们用到的函数就能够实现我们想要达到的目的呢?
答案是肯定的!
那么该怎么办?今天我们就学习一下模块化程序设计,把程序进行合理的规划!
本实例的目的:了解模块化程序设计的思路和方法。
1.2. 设计思路
本实例的设计思路是:将《基于proteus的51单片机开发实例29-单总线DS18B20的读写》中的程序代码按照延时功能、LCD1602液晶显示功能、DS18B20的读写控制功能这三个部分,使用模块化程序设计的方法,将这三个部分分别封装为三个.c和.h文件,然后在main.c文件中使用宏定义语句调用。从而完成模块化程序设计。
模块化程序设计
1.3. 基础知识
现在我们看看《基于proteus的51单片机开发实例29-单总线DS18B20的读写》中的程序代码,这个程序里面有很多函数,这些函数的声明和定义在整个程序里面占了很大的比例。我们在编写和使用这些函数的时候,因为很多函数都不是独立,需要不停的翻找相关的程序部分。显得很麻烦。而且整个程序显得有些乱。
那么能不能把程序精简一下,能不能把具有相关功能的函数放在一起,能不能像我们使用头文件的时候,直接使用一个包含命令就把一些相关功能包含到程序里,而我们在程序里只要调用我们用到的函数就能够实现我们想要达到的目的呢?
答案是肯定的!
解决方法是:化整为零,把负载的程序按照功能分解成不同的小模块,分别实现。
1.3.1. 模块化程序设计简介
模块化程序设计是指将实现同一功能的程序整合起来,封装到一个程序模块中,这样在使用该功能的时候,可以直接调用该模块中的相关函数进行操作。
在进行程序设计时把一个大的程序按照功能划分为若干小的程序,每个小的程序完成一个确定的功能,在这些小的程序之间建立必要的联系,互相协作完成整个程序要完成的功能。通常我们称这些小的程序为程序的模块。
模块化程序设计
具体到程序来说,模块通常是指可以用一个名字调用的一个程序段。对于不同的程序设计语言,模块的实现和名称也不相同,在BASIC,FORTRAN语言中的模块称作子程序;C语言中的模块叫函数
1.3.2. 2、模块化程序设计思路
模块化程序设计的思路是这样的:将一个大的程序按功能分割成一些小模块。
即:把具有相同功能的函数放在一个文件中,然后在主程序里面用#include指令把这个文件包含到主程序文件中,那么在主程序中就可以直接调用这个文件中定义好的函数来实现特定的功能,而在主程序中不用声明和定义这些函数。这样就使主程序显得更加精炼,可读性也会增强。同时,我们把具有相同功能的函数放在同一个文件中,这样有一个很大的优点是便于移植,可以将这个模块化的函数文件很轻松的移植到别的程序中。
综合上述,模块化程序设计的优点是:
●各模块相对独立,功能单一,结构清晰,接口简单.
●控制了程序设计的复杂性.
●缩短了开发周期.
●避免程序开发的重复劳动.
●易于维护和功能扩充.
模块化程序设计
3、模块化程序设计的实现
模块化程序的实现是:
一般的做法是:将不同模块(如LCD1602,DS18B20等)都封装成一个文件,然后在主程序中包含这些文件。
将具有相同功能的函数声明以及该模块涉及的端口定义、初始化设置、宏定义等内容编写成一个xxx.h文件文件,将函数的定义、变量的声明及初始化等内容编写为一个xxx.c文件;注意最好把xxx.c文件和xxx.h文件的名称定义成相同的名称。然后在主程序文件中使用#include预编译命令包含xxx.h文件,这样在主程序中就可以调用这个文件中的函数了,同样的,在其它.c文件中,也可以使用#include预编译命令包含xxx.h文件。(就像我们调用编译器中的各种应用库文件一样,比如在keil c51中要调用I/O定义头文件是我们要使用“#include <a”这条指令将I/O头文件包含到主程序中)。同时将“.c”文件添加到主程序所在的项目组中。这样就初步完成了模块化程序设计框架。
这样做的优点是,我们可以直接在“.h”文件中查找到我们需要的函数名称,从而在主程序里面直接调用,而不用去关心“.c”文件中的具体内容。如果我们要将该程序移植到不同型号的单片机上,我们同样只需在“.h”文件中修改相应的端口定义即可。
1.3.3. 不同模块之间的函数调用
一般情况下,程序中定义的函数和变量是有一定的作用域的,也就是说,我们在一个模块中定义的变量和函数,它的作用域只限于本模块文件和调用它的程序文件范围内,而在没有调用它的模块程序里面,如果调用它,编译器是会提示错误的,它的函数是不能被使用的。
在对较为复杂的程序进行模块化设计的时候,经常会遇到这样一种情况:一个函数在不同的模块之间都会用到,举例来说,几乎每一个程序中都会用到延时函数,也就是说一般的程序中都需要调用延时函数。出现这种情况该怎么办?难道需要在每个模块中都定义相同的函数?但是我们都知道,程序中,不同的变量和函数只能使用不同的名称,如果一个程序中有两个名称相同的变量或者函数的话,那程序编译的时候会提示我们有重复定义的函数。难道要在不同的模块中为相同功能的函数(功能相同,函数体的内容也相同)起不同的名字,这样岂不是做了很多重复劳动,这样的重复劳动还会造成程序的可读性变得很差。怎么办?
模块化程序设计
同样的情况也会出现在不同模块程序之间传递数据变量的时候。
在这样的情况下,一种比较好的解决办法是:使用文件包含命令“#include”将一个模块的文件包含到另一个模块文件中,这种方法在只包含很少的模块文件的时候是很方便的,对于比较大的、很复杂的包含很多模块文件的单片机应用程序中,在每一个模块里面都使用包含命令就很麻烦了,并且很容易出错。
出现这种情况的原因是我们在编写单片机程序的时候,我们所定义的函数和变量都被默认为是局部函数和变量,那么它们的作用范围当然是在调用他们的程序之间了。如果我们将这些函数和变量定义为全局的函数和变量,那么,在整个单片机系统程序中,所有的模块之间都可以使用这些函数和变量。
所以,针对这种情况,最好的解决方法是:将需要在不同模块之间互相调用的文件声明为外部函数、变量(或者全局函数、变量)。
将函数和变量声明为全局函数和变量的方法是:在该函数和变量前面加“extern”修饰符。“extern”的英文意思就是“全局”,这样我们就可以将加了“extern”修饰符的函数和变量声明为全局函数和变量,那么在整个单片机系统程序的任何地方,我们都可以随意调用这些全局函数和变量。
例如:
extern void Usart0_Init(void); //USART寄存器设置
extern void Usart0_PutString(unsigned char *pcString);
在这里,我们将这4个函数都定义成为全局函数,那么,在一个单片机系统中,在整个程序的任何地方,我们都可以直接调用这些函数。
注意:在调用的时候直接使用函数名称即可,“extern”这个修饰符不必再调用的时候写上。
同样的,对于变量的定义我们可以使用同样的方法:
extern int second;
在这里,我们定义了一个全局的整形变量second;
1.4. 电路设计
本实例沿用实例29的电路。
1.5. 程序设计
本实例中,我们分别按照功能划分,将程序中的延时程序中的函数声明封装在Delay.h文件中,变量声明和函数定义封装在Delay.c文件中;
液晶显示程序中的端口定义、函数声明封装在LCD1602.h文件中,变量声明和函数定义封装在LCD1602.c文件中,DS18B20读写程序中的端口定义、函数声明封装在DS18B20.h文件中,变量声明和函数定义封装在DS18B20.c文件中。
按照我们说的步骤建好上面程序文件后,按照下图进行操作。
添加.c文件的方法
在主程序以及各个.c文件中,使用下面的预编译命令将不同程序模块中的函数“包含”进来。
#include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" //
也许有细心的朋友会发现,不同的#include与编译指令还有细微的差别,例如,调用51单片机头文件和C语言库函数使用的是下面的语句
#include<reg51.h> //包含单片机寄存器的头文件 #include<in; //包含_nop_()函数定义的头文件
而针对我们自己编写的.h文件,使用的是下面的语句
#include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" //
看出来了吧?调用keil里面自带的头文件时,文件名是用尖括号<>括起来的,而调用我们自己编写的头文件时,文件名是用""括起来的。这是为什么呢?简单说,使用尖括号<>括起来的头文件,编译器在编译时,先从编译器安装的目录下开始搜索这个文件,而用""括起来的头文件,编译的时候,先从我们建立的单片机项目文件中搜索该文件。
下面是Delay.h文件的代码。
/***************************************************** 函数功能:延时1ms (3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒 ***************************************************/ void delay1ms(); /***************************************************** 函数功能:延时若干毫秒 入口参数:n ***************************************************/ void delaynms(unsigned int n);
下面是Delay.c文件的代码。
#include<reg51.h> //包含单片机寄存器的头文件 #include<in; //包含_nop_()函数定义的头文件 #include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" // /***************************************************** 函数功能:延时1ms (3j+2)*i=(3×33+2)×10=1010(微秒),可以认为是1毫秒 ***************************************************/ void delay1ms() { unsigned char i,j; for(i=0;i<10;i++) for(j=0;j<33;j++) ; } /***************************************************** 函数功能:延时若干毫秒 入口参数:n ***************************************************/ void delaynms(unsigned int n) { unsigned int i; for(i=0;i<n;i++) delay1ms(); }
下面是LCD1602.h文件的代码。
/***************************************************** 函数功能:判断液晶模块的忙碌状态 返回值:result。result=1,忙碌;result=0,不忙 ***************************************************/ bit BusyTest(void); /***************************************************** 函数功能:将模式设置指令或显示地址写入液晶模块 入口参数:dictate ***************************************************/ void WriteInstruction (unsigned char dictate); /***************************************************** 函数功能:指定字符显示的实际地址 入口参数:x ***************************************************/ void WriteAddress(unsigned char x); /***************************************************** 函数功能:将数据(字符的标准ASCII码)写入液晶模块 入口参数:y(为字符常量) ***************************************************/ void WriteData(unsigned char y); /***************************************************** 函数功能:对LCD的显示模式进行初始化设置 ***************************************************/ void LcdInitiate(void); /****************************************************************************** 以下是与温度有关的显示设置 ******************************************************************************/ /***************************************************** 函数功能:显示没有检测到DS18B20 ***************************************************/ void display_error(void); /***************************************************** 函数功能:显示说明信息 ***************************************************/ void display_explain(void); /***************************************************** 函数功能:显示温度符号 ***************************************************/ void display_symbol(void); /***************************************************** 函数功能:显示温度的小数点 ***************************************************/ void display_dot(void); /***************************************************** 函数功能:显示温度的单位(Cent) ***************************************************/ void display_cent(void); /***************************************************** 函数功能:显示温度的整数部分 入口参数:x ***************************************************/ void display_temp1(unsigned char x); /***************************************************** 函数功能:显示温度的小数数部分 入口参数:x ***************************************************/ void display_temp2(unsigned char x);
下面是LCD1602.c文件的代码。
#include<reg51.h> //包含单片机寄存器的头文件 #include<in; //包含_nop_()函数定义的头文件 #include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" // unsigned char code digit[10]={"0123456789"}; //定义字符数组显示数字 unsigned char code Str[]={"laomashitu MCU"}; //说明显示的是温度 unsigned char code Error[]={"Check Error!"}; //说明没有检测到DS18B20 unsigned char code Temp[]={"wendu:"}; //说明显示的是温度 unsigned char code Cent[]={"du"}; //温度单位 /******************************************************************************* 以下是对液晶模块的操作程序 *******************************************************************************/ sbit RS=P2^4; //寄存器选择位,将RS位定义为P2.0引脚 sbit RW=P2^5; //读写选择位,将RW位定义为P2.1引脚 sbit E=P2^6; //使能信号位,将E位定义为P2.2引脚 sbit BF=P0^7; //忙碌标志位,,将BF位定义为P0.7引脚 /***************************************************** 函数功能:判断液晶模块的忙碌状态 返回值:result。result=1,忙碌;result=0,不忙 ***************************************************/ bit BusyTest(void) { bit result; RS=0; //根据规定,RS为低电平,RW为高电平时,可以读状态 RW=1; E=1; //E=1,才允许读写 _nop_(); //空操作 _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 result=BF; //将忙碌标志电平赋给result E=0; //将E恢复低电平 return result; } /***************************************************** 函数功能:将模式设置指令或显示地址写入液晶模块 入口参数:dictate ***************************************************/ void WriteInstruction (unsigned char dictate) { while(BusyTest()==1); //如果忙就等待 RS=0; //根据规定,RS和R/W同时为低电平时,可以写入指令 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" _nop_(); _nop_(); //空操作两个机器周期,给硬件反应时间 P0=dictate; //将数据送入P0口,即写入指令或地址 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:指定字符显示的实际地址 入口参数:x ***************************************************/ void WriteAddress(unsigned char x) { WriteInstruction(x|0x80); //显示位置的确定方法规定为"80H+地址码x" } /***************************************************** 函数功能:将数据(字符的标准ASCII码)写入液晶模块 入口参数:y(为字符常量) ***************************************************/ void WriteData(unsigned char y) { while(BusyTest()==1); RS=1; //RS为高电平,RW为低电平时,可以写入数据 RW=0; E=0; //E置低电平(根据表8-6,写指令时,E为高脉冲, // 就是让E从0到1发生正跳变,所以应先置"0" P0=y; //将数据送入P0口,即将数据写入液晶模块 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=1; //E置高电平 _nop_(); _nop_(); _nop_(); _nop_(); //空操作四个机器周期,给硬件反应时间 E=0; //当E由高电平跳变成低电平时,液晶模块开始执行命令 } /***************************************************** 函数功能:对LCD的显示模式进行初始化设置 ***************************************************/ void LcdInitiate(void) { delaynms(15); //延时15ms,首次写指令时应给LCD一段较长的反应时间 WriteInstruction(0x38); //显示模式设置:16×2显示,5×7点阵,8位数据接口 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x38); //连续三次,确保初始化成功 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x0c); //显示模式设置:显示开,无光标,光标不闪烁 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x06); //显示模式设置:光标右移,字符不移 delaynms(5); //延时5ms ,给硬件一点反应时间 WriteInstruction(0x01); //清屏幕指令,将以前的显示内容清除 delaynms(5); //延时5ms ,给硬件一点反应时间 } /****************************************************************************** 以下是与温度有关的显示设置 ******************************************************************************/ /***************************************************** 函数功能:显示没有检测到DS18B20 ***************************************************/ void display_error(void) { unsigned char i; WriteAddress(0x00); //写显示地址,将在第1行第1列开始显示 i = 0; //从第一个字符开始显示 while(Error[i] != '\0') //只要没有写到结束标志,就继续写 { WriteData(Error[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(100); //延时100ms较长时间,以看清关于显示的说明 } while(1) //进入死循环,等待查明原因 ; } /***************************************************** 函数功能:显示说明信息 ***************************************************/ void display_explain(void) { unsigned char i; WriteAddress(0x00); //写显示地址,将在第1行第1列开始显示 i = 0; //从第一个字符开始显示 while(Str[i] != '\0') //只要没有写到结束标志,就继续写 { WriteData(Str[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(100); //延时100ms较长时间,以看清关于显示的说明 } } /***************************************************** 函数功能:显示温度符号 ***************************************************/ void display_symbol(void) { unsigned char i; WriteAddress(0x40); //写显示地址,将在第2行第1列开始显示 i = 0; //从第一个字符开始显示 while(Temp[i] != '\0') //只要没有写到结束标志,就继续写 { WriteData(Temp[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(50); //延时1ms给硬件一点反应时间 } } /***************************************************** 函数功能:显示温度的小数点 ***************************************************/ void display_dot(void) { WriteAddress(0x49); //写显示地址,将在第2行第10列开始显示 WriteData('.'); //将小数点的字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示温度的单位(Cent) ***************************************************/ void display_cent(void) { unsigned char i; WriteAddress(0x4c); //写显示地址,将在第2行第13列开始显示 i = 0; //从第一个字符开始显示 while(Cent[i] != '\0') //只要没有写到结束标志,就继续写 { WriteData(Cent[i]); //将字符常量写入LCD i++; //指向下一个字符 delaynms(50); //延时1ms给硬件一点反应时间 } } /***************************************************** 函数功能:显示温度的整数部分 入口参数:x ***************************************************/ void display_temp1(unsigned char x) { unsigned char j,k,l; //j,k,l分别储存温度的百位、十位和个位 j=x/100; //取百位 k=(x%100)/10; //取十位 l=x%10; //取个位 WriteAddress(0x46); //写显示地址,将在第2行第7列开始显示 WriteData(digit[j]); //将百位数字的字符常量写入LCD WriteData(digit[k]); //将十位数字的字符常量写入LCD WriteData(digit[l]); //将个位数字的字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 } /***************************************************** 函数功能:显示温度的小数数部分 入口参数:x ***************************************************/ void display_temp2(unsigned char x) { WriteAddress(0x4a); //写显示地址,将在第2行第11列开始显示 WriteData(digit[x]); //将小数部分的第一位数字字符常量写入LCD delaynms(50); //延时1ms给硬件一点反应时间 }
下面是DS18B20.h文件的代码。
sbit DQ=P3^3; /***************************************************** 函数功能:将DS18B20传感器初始化,读取应答信号 出口参数:flag ***************************************************/ bit Init_DS18B20(void); /***************************************************** 函数功能:从DS18B20读取一个字节数据 出口参数:dat ***************************************************/ unsigned char ReadOneChar(void); /***************************************************** 函数功能:向DS18B20写入一个字节数据 入口参数:dat ***************************************************/ WriteOneChar(unsigned char dat); /***************************************************** 函数功能:做好读温度的准备 ***************************************************/ void ReadyReadTemp(void);
下面是DS18B20.c文件的代码。
#include<reg51.h> //包含单片机寄存器的头文件 #include<in; //包含_nop_()函数定义的头文件 #include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" // extern unsigned char time; //设置全局变量,专门用于严格延时 /***************************************************** 函数功能:将DS18B20传感器初始化,读取应答信号 出口参数:flag ***************************************************/ bit Init_DS18B20(void) { bit flag; //储存DS18B20是否存在的标志,flag=0,表示存在;flag=1,表示不存在 DQ = 1; //先将数据线拉高 for(time=0;time<2;time++) //略微延时约6微秒 ; DQ = 0; //再将数据线从高拉低,要求保持480~960us for(time=0;time<200;time++) //略微延时约600微秒 ; //以向DS18B20发出一持续480~960us的低电平复位脉冲 DQ = 1; //释放数据线(将数据线拉高) for(time=0;time<10;time++) ; //延时约30us(释放总线后需等待15~60us让DS18B20输出存在脉冲) flag=DQ; //让单片机检测是否输出了存在脉冲(DQ=0表示存在) for(time=0;time<200;time++) //延时足够长时间,等待存在脉冲输出完毕 ; return (flag); //返回检测成功标志 } /***************************************************** 函数功能:从DS18B20读取一个字节数据 出口参数:dat ***************************************************/ unsigned char ReadOneChar(void) { unsigned char i=0; unsigned char dat; //储存读出的一个字节数据 for (i=0;i<8;i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ = 0; //单片机从DS18B20读书据时,将数据线从高拉低即启动读时序 dat>>=1; _nop_(); //等待一个机器周期 DQ = 1; //将数据线"人为"拉高,为单片机检测DS18B20的输出电平作准备 for(time=0;time<2;time++) ; //延时约6us,使主机在15us内采样 if(DQ==1) dat|=0x80; //如果读到的数据是1,则将1存入dat else dat|=0x00;//如果读到的数据是0,则将0存入dat //将单片机检测到的电平信号DQ存入r[i] for(time=0;time<8;time++) ; //延时3us,两个读时序之间必须有大于1us的恢复期 } return(dat); //返回读出的十进制数据 } /***************************************************** 函数功能:向DS18B20写入一个字节数据 入口参数:dat ***************************************************/ WriteOneChar(unsigned char dat) { unsigned char i=0; for (i=0; i<8; i++) { DQ =1; // 先将数据线拉高 _nop_(); //等待一个机器周期 DQ=0; //将数据线从高拉低时即启动写时序 DQ=dat&0x01; //利用与运算取出要写的某位二进制数据, //并将其送到数据线上等待DS18B20采样 for(time=0;time<10;time++) ;//延时约30us,DS18B20在拉低后的约15~60us期间从数据线上采样 DQ=1; //释放数据线 for(time=0;time<1;time++) ;//延时3us,两个写时序间至少需要1us的恢复期 dat>>=1; //将dat中的各二进制位数据右移1位 } for(time=0;time<4;time++) ; //稍作延时,给硬件一点反应时间 } /************************************************ 函数功能:做好读温度的准备 ***************************************************/ void ReadyReadTemp(void) { Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); // 跳过读序号列号的操作 WriteOneChar(0x44); // 启动温度转换 for(time=0;time<100;time++) ; //温度转换需要一点时间 Init_DS18B20(); //将DS18B20初始化 WriteOneChar(0xCC); //跳过读序号列号的操作 WriteOneChar(0xBE); //读取温度寄存器,前两个分别是温度的低位和高位 }
下面是主程序文件的代码。
//实例72:DS18B20温度检测及其液晶显示 #include<reg51.h> //包含单片机寄存器的头文件 #include<in; //包含_nop_()函数定义的头文件 #include"LCD1602.h" // #include"DS18B20.h" // #include"Delay.h" // unsigned char time; //设置全局变量,专门用于严格延时 /***************************************************** 函数功能:主函数 ***************************************************/ void main(void) { unsigned char TL; //储存暂存器的温度低位 unsigned char TH; //储存暂存器的温度高位 unsigned char TN; //储存温度的整数部分 unsigned int TD; //储存温度的小数部分 LcdInitiate(); //将液晶初始化 delaynms(5); //延时5ms给硬件一点反应时间 if(Init_DS18B20()==1) display_error(); display_explain(); display_symbol(); //显示温度说明 display_dot(); //显示温度的小数点 display_cent(); //显示温度的单位 while(1) //不断检测并显示温度 { ReadyReadTemp(); //读温度准备 TL=ReadOneChar(); //先读的是温度值低位 TH=ReadOneChar(); //接着读的是温度值高位 TN=TH*16+TL/16; //实际温度值=(TH*256+TL)/16,即:TH*16+TL/16 //这样得出的是温度的整数部分,小数部分被丢弃了 TD=(TL%16)*10/16; //计算温度的小数部分,将余数乘以10再除以16取整, //这样得到的是温度小数部分的第一位数字(保留1位小数) display_temp1(TN); //显示温度的整数部分 display_temp2(TD); //显示温度的小数部分 delaynms(10); } }
1.6. 实例仿真
根据前面的说明,按照步骤建立起模块化程序结构。编译后,按照上一实例的方法进行仿真,观察非模块化方法编写的程序,以及用模块化方法编写的程序在运行结果上是完全一样的。
1.7. 总结
通过本实例,我们学习了如何将功能众多,程序量大的单片机程序分成不同的模块,从而使单片机程序看起来结构清晰,可读性强,可移植性强。