STC89C52RC单片机额外篇 | 06 - 认识高内聚低耦合的模块化编程

Posted Neutionwei

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了STC89C52RC单片机额外篇 | 06 - 认识高内聚低耦合的模块化编程相关的知识,希望对你有一定的参考价值。

模块化编程是管理程序的一种方法,代码量少的情况下其实感觉不到模块化编程的好处,但是随着代码量越来越大,为了方便管理与调试定位问题,模块化管理是必须的。模块化是尽可能地实现函数内部高内聚,函数之间低耦合!下面我给大家看看如何实现在单片机编程中实现模块化!

为了给读者达到更好的理解效果,本博文引用博主大学之时的一篇实验报告作为例子。

没错,就是这篇博文:《单片机综合实验 - 06 | 数字温度计设计》。

这个数字温度计主要包含了以下几个模块:

  • DS18B20数字温度传感器
  • LED数码管
  • 蜂鸣器

1 原始程序

//*****************************头文件声明****************************
#include <reg51.h>
//****************************数据类型定义***************************
typedef unsigned char uint8;
typedef unsigned int  uint16;
//****************************I/O口线声明****************************
#define SEG_CODE_PORT P0       
#define BIT_CODE_PORT P2       
sbit    DS18B20_DATA=P3^7;
sbit    BUZZ=P1^0;
//************************常量数组(段码表)声明*********************
uint8 code SegCodeTable[]=
    {0xc0,0xf9,0xa4,0xb0,0x99,0x92,0x82,0xf8,0x80,0x90};      
//****************************全局变量声明***************************
int Temperature;
//****************************函数原型声明***************************
void   DS18B20Init();
void   DS18B20BitWrite(bit Bit);
bit    DS18B20BitRead();
void   DS18B20ByteWrite(uint8 Byte);
uint8  DS18B20ByteRead();
void   GetTemperature();
void   DispTemperature();
void   Delay(uint16 ms);
void   AlarmCheck();
//*******************************主函数******************************
void main()
{
    while(1)
      {
          GetTemperature();        //采集当前温度
          DispTemperature();        //显示当前温度
		  AlarmCheck();
	   } 
}
//*************************DS18B20初始化函数*************************
void DS18B20Init()
{
    uint16  i;  
    while(1)
      {
        DS18B20_DATA=0;
        i=640;           
        while(--i);       //延时800us(STC12C5A60S2,11.0592MHz,代码5级优化)  
        DS18B20_DATA=1;
        i=56;
        while(--i);                     //延时70us
        if(DS18B20_DATA==1) continue;   //无响应则重发复位脉冲
        i=224;
        while(--i);                     //延时280us
        if(DS18B20_DATA==1) break;      //复位成功
      }   
    i=160;
    while(--i);                         //延时200us
}
//***********************DS18B20位写操作函数*************************
void DS18B20BitWrite(bit Bit)
{
    uint16  i;
    DS18B20_DATA=0; 
    i=4;
    while(--i);                   //延时5us
    DS18B20_DATA=Bit;             //发送1位数到DS18B20
    i=48;
    while(--i);                   //延时60us
    DS18B20_DATA=1;     
}
//**********************DS18B20位读操作函数**************************
bit DS18B20BitRead()
{
    bit    temp;
    uint16 i;
    DS18B20_DATA=0;
    i=4;
    while(--i);                   //延时5us
    DS18B20_DATA=1;
    i=4;
    while(--i);                   //延时5us
    temp=DS18B20_DATA;            //读来自DS18B20的1位数
    i=48;
    while(--i);                   //延时60us
    return temp;
}     
//**********************DS18B20字节写操作函数************************
void DS18B20ByteWrite(uint8 Byte)
{
    uint8 i;
    for(i=0;i<8;i++)              //一共发送8位
      {
        if( Byte&0x01==1  )     //先发最低位
          DS18B20BitWrite(1);     //发送1
        else
          DS18B20BitWrite(0);     //发送0
        Byte>>=1;
      }    
}
//**********************DS18B20字节读操作函数************************
uint8 DS18B20ByteRead()
{
    uint8 i,temp=0;
    for(i=0;i<8;i++)              //一共读8位
      {
        temp>>=1;                 //字节变量右移
        if(DS18B20BitRead()==1)   //读取1位数据并存入临时变量temp中
          temp|=0x80;             //temp最高位置1
      }
    return temp;                  //返回读到的8位数
}
//*****************************温度采集函数**************************
void GetTemperature()
{
    uint8  Buff[2],i;
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0x44);       //启动温度转换
    for(i=0;i<250;i++)
    DispTemperature();          //等待750ms,期间不断刷新LED显示
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0xbe);       //准备读转换结果
    Buff[0]=DS18B20ByteRead();    //读温度值低字节
    Buff[1]=DS18B20ByteRead();    //读温度值高字节
    Temperature=(Buff[1]<<8)+Buff[0];  //拼成16位温度值
}
//******************************温度显示函数*************************
void DispTemperature()
{
    uint8 temp;
    temp=(Temperature>>4)/10;                             //显示十位
    if(temp==0)
      SEG_CODE_PORT=0xFF       ;                             //十位为0则隐去
    else
      SEG_CODE_PORT=SegCodeTable[temp];
    BIT_CODE_PORT=0xF5;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(Temperature>>4)%10]&0x7F; //显示个位(带点)
    BIT_CODE_PORT=0xF6;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(Temperature&0x0F)*10/16]; //显示十分位
    BIT_CODE_PORT=0xF7;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;      
}
//********************************超温报警函数***************************//
void AlarmCheck()
{
  uint8 i;
  if(Temperature > 0x01F8 )       //判断温度是否超过31.5℃
    {
      for(i=0;i<50;i++)
        {
          BUZZ=~ BUZZ ;
          Delay(1);             //控制无源蜂鸣器发声50ms
        }
      BUZZ=1;      
      Delay(100);
     }
}
//******************************软件延时函数*************************
void Delay(uint16 ms)
{
    uint16 i;
    do{
        i=790;
        while(--i);   //延时1ms(STC12C5A60S2,11.0592MHz,代码5级优化)
       } while(--ms);
}

咋一看,感觉这个程序写得挺不错,不过稍微细心斟酌,其实模块化的程度只是一般般,有些函数的内部还是有一定的耦合度!下面对各模块函数进行分析:

2 DS18B20驱动函数

//*************************DS18B20初始化函数*************************
void DS18B20Init()
{
    uint16  i;  
    while(1)
      {
        DS18B20_DATA=0;
        i=640;           
        while(--i);       //延时800us(STC12C5A60S2,11.0592MHz,代码5级优化)  
        DS18B20_DATA=1;
        i=56;
        while(--i);                     //延时70us
        if(DS18B20_DATA==1) continue;   //无响应则重发复位脉冲
        i=224;
        while(--i);                     //延时280us
        if(DS18B20_DATA==1) break;      //复位成功
      }   
    i=160;
    while(--i);                         //延时200us
}
//***********************DS18B20位写操作函数*************************
void DS18B20BitWrite(bit Bit)
{
    uint16  i;
    DS18B20_DATA=0; 
    i=4;
    while(--i);                   //延时5us
    DS18B20_DATA=Bit;             //发送1位数到DS18B20
    i=48;
    while(--i);                   //延时60us
    DS18B20_DATA=1;     
}
//**********************DS18B20位读操作函数**************************
bit DS18B20BitRead()
{
    bit    temp;
    uint16 i;
    DS18B20_DATA=0;
    i=4;
    while(--i);                   //延时5us
    DS18B20_DATA=1;
    i=4;
    while(--i);                   //延时5us
    temp=DS18B20_DATA;            //读来自DS18B20的1位数
    i=48;
    while(--i);                   //延时60us
    return temp;
}     
//**********************DS18B20字节写操作函数************************
void DS18B20ByteWrite(uint8 Byte)
{
    uint8 i;
    for(i=0;i<8;i++)              //一共发送8位
      {
        if( Byte&0x01==1  )     //先发最低位
          DS18B20BitWrite(1);     //发送1
        else
          DS18B20BitWrite(0);     //发送0
        Byte>>=1;
      }    
}
//**********************DS18B20字节读操作函数************************
uint8 DS18B20ByteRead()
{
    uint8 i,temp=0;
    for(i=0;i<8;i++)              //一共读8位
      {
        temp>>=1;                 //字节变量右移
        if(DS18B20BitRead()==1)   //读取1位数据并存入临时变量temp中
          temp|=0x80;             //temp最高位置1
      }
    return temp;                  //返回读到的8位数
}

这部分函数的实现几乎很接近高内聚的方式了,模块化程度很高!

3 温度采集函数

//*****************************温度采集函数**************************
void GetTemperature()
{
    uint8  Buff[2],i;
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0x44);       //启动温度转换
    for(i=0;i<250;i++)
    DispTemperature();          //等待750ms,期间不断刷新LED显示
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0xbe);       //准备读转换结果
    Buff[0]=DS18B20ByteRead();    //读温度值低字节
    Buff[1]=DS18B20ByteRead();    //读温度值高字节
    Temperature=(Buff[1]<<8)+Buff[0];  //拼成16位温度值
}

温度采集函数,我们可以理解为在驱动层之上的逻辑,换句话说,我们要用DS18B20驱动函数实现温度采集的功能,所以温度采集函数调用DS18B20驱动函数的接口是要足够简单并可读性高。

从温度采集函数的内部实现可以看出(我们先不看DispTemperature()函数),启动温度转换与转换读取结果这个步骤其实也可以封装成DS18B20驱动函数的一个接口,对于buffTemperature应该是用于接收接口中返回的数据(一般接口就得掩盖其内部的实现细节)。如下所示:

//*************************DS18B20温度转换函数***********************
void DS18B20Conversion()
{
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0x44);       //启动温度转换
}

//*************************DS18B20温度读取函数***********************
void DS18B20Read(uint8 buff[])
{
    DS18B20Init();                //DS18B20初始化
    DS18B20ByteWrite(0xCC);       //跳过ROM匹配(因为只有一个DS18B20)
    DS18B20ByteWrite(0xbe);       //准备读转换结果
    buff[0]=DS18B20ByteRead();    //读温度值低字节
    buff[1]=DS18B20ByteRead();    //读温度值高字节
}

//*****************************温度采集函数**************************
void GetTemperature(int *temperature)
{
    uint8  Buff[2],i;
	DS18B20Conversion();		// 启动DS18B20温度转换
    for(i=0;i<250;i++)
    	DispTemperature();          // 等待750ms,期间不断刷新LED显示
	DS18B20Read(Buff);			// 读取DS18B20温度数据
    *temperature=(Buff[1]<<8)+Buff[0];  //拼成16位温度值
}

咦,这里的temperature变量为啥是指针类型?不知道大家初次学C语言的时候是否还记得使用普通变量作为形参传入到函数内部却无法修改这个变量的值这个例子。如果实在想不起怎么办?这里博主有篇博文可以帮到大家:《C语言知识汇总 | 53-C语言指针变量作为函数参数》。

4 温度显示函数

//******************************温度显示函数*************************
void DispTemperature()
{
    uint8 temp;
    temp=(Temperature>>4)/10;                             //显示十位
    if(temp==0)
      SEG_CODE_PORT=0xFF       ;                             //十位为0则隐去
    else
      SEG_CODE_PORT=SegCodeTable[temp];
    BIT_CODE_PORT=0xF5;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(Temperature>>4)%10]&0x7F; //显示个位(带点)
    BIT_CODE_PORT=0xF6;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(Temperature&0x0F)*10/16]; //显示十分位
    BIT_CODE_PORT=0xF7;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;      
}

继续沿用前面的思维,从温度显示函数的内部实现来看,函数的执行是包括对Temperature变量的某些位进行取出、然后对数码管各个位进行显示,其实我们也可实现单独对数码管的显示函数,但是这个可能意义不是太大,为什么呢?因为数码管的电子线路连接有几种方式,且也有共阴与共阳之分,另外数码管的位选数量也可能不一样,要达到“统一”是没这么简单的!所以这里只是单独把Temperature变量抽取出来变成函数的实参变量即可:

//******************************温度显示函数*************************
void DispTemperature(int temperature)
{
    uint8 temp;
    temp=(temperature>>4)/10;                             //显示十位
    if(temp==0)
      SEG_CODE_PORT=0xFF       ;                             //十位为0则隐去
    else
      SEG_CODE_PORT=SegCodeTable[temp];
    BIT_CODE_PORT=0xF5;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(temperature>>4)%10]&0x7F; //显示个位(带点)
    BIT_CODE_PORT=0xF6;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;
    SEG_CODE_PORT=SegCodeTable[(temperature&0x0F)*10/16]; //显示十分位
    BIT_CODE_PORT=0xF7;                                   //选择显示位置
    Delay(1);
    BIT_CODE_PORT=0xFF;      
}

5 超温报警函数

//********************************超温报警函数***************************//
void AlarmCheck()
{
  uint8 i;
  if(Temperature > 0x01F8 )       //判断温度是否超过31.5℃
    {
      for(i=0;i<50;i++)
        {
          BUZZ=~ BUZZ ;
          Delay(1);             //控制无源蜂鸣器发声50ms
        }
      BUZZ=1;      
      Delay(100);
     }
}

来,我们

以上是关于STC89C52RC单片机额外篇 | 06 - 认识高内聚低耦合的模块化编程的主要内容,如果未能解决你的问题,请参考以下文章

STC89C52RC单片机额外篇 | 01 - 认识中断中断源以及中断优先级

STC89C52RC单片机额外篇 | 02 - 认识串行通信波特率以及数据包

STC89C52RC单片机额外篇 | 05 - 把NOP指令封装成微秒级延时函数

STC89C52RC单片机额外篇 | 03 - 认识C51编译器支持的数据类型

STC89C52RC单片机额外篇 | 07 - 使用Keil搭建与管理项目式多文件工程

STC89C52RC单片机额外篇 | 08 - 认识I2C协议以及E2PROM存储器(AT24Cxx)