掘金 后端 ( ) • 2022-01-26 17:26

「这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战


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

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

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

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

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

1 原始程序

//*****************************头文件声明****************************
#include 
//****************************数据类型定义***************************
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);
     }
}

来,我们继续!超温报警函数也是我们需求想要的功能,这里可以提取出蜂鸣器发声函数,温度变量也进一步提取,当然还可选择传入一个超温报警值作为变量,传入的超温值可以使用宏定义来表示,方便修改:

//***************************温度报警值定义**************************
#define MAX_TEMP 0x01F8 // 31.5℃
...
//******************************蜂鸣器发声函数***************************//
void BuzzerSounds()
{
uint8 i;
for(i=0;i<50;i++)
    {
  BUZZ=~ BUZZ ;
  Delay(1);             //控制无源蜂鸣器发声50ms
    }
 BUZZ=1;      
 Delay(100);
}

//********************************超温报警函数***************************//
void AlarmCheck(int temperature, int max_temperature)
{
if(temperature > max_temperature)
{
BuzzerSounds();// 蜂鸣器发声
}
}

6 main函数

前面把temperature变量通过函数传值的方式来获取温度值与显示温度值,那么对于全局变量中Temperature就可以去掉了,最终main函数是这样子的:

//*******************************主函数******************************
void main()
{
int temperature = 0;
    while(1)
      {
          GetTemperature(&temperature);  //采集当前温度
          DispTemperature(temperature);  //显示当前温度
  AlarmCheck(temperature, MAX_TEMP);       //判断温度是否超过31.5℃
   } 
}

7 最终文件

//*****************************头文件声明****************************
#include 
//****************************数据类型定义***************************
typedef unsigned char uint8;
typedef unsigned int  uint16;
//***************************温度报警值定义**************************
#define MAX_TEMP 0x01F8 // 31.5℃
//****************************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};      
//****************************函数原型声明***************************
void   DS18B20Init();
void   DS18B20BitWrite(bit Bit);
bit    DS18B20BitRead();
void   DS18B20ByteWrite(uint8 Byte);
uint8  DS18B20ByteRead();
void   GetTemperature(int *temperature);
void   DispTemperature(int temperature);
void   Delay(uint16 ms);
void   AlarmCheck(int temperature, int max_temperature);
//*******************************主函数******************************
void main()
{
int temperature = 0;
    while(1)
    {
      GetTemperature(&temperature);  //采集当前温度
      DispTemperature(temperature);  //显示当前温度
  AlarmCheck(temperature, MAX_TEMP);       //判断温度是否超过31.5℃
} 
}
//*************************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位数
}
//*************************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(*temperature);          // 等待750ms,期间不断刷新LED显示
DS18B20Read(Buff);// 读取DS18B20温度数据
    *temperature=(Buff[1]<<8)+Buff[0];  //拼成16位温度值
}
//******************************温度显示函数*************************
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;      
}
//******************************蜂鸣器发声函数***************************//
void BuzzerSounds()
{
uint8 i;
for(i=0;i<50;i++)
    {
  BUZZ=~ BUZZ ;
  Delay(1);             //控制无源蜂鸣器发声50ms
    }
 BUZZ=1;      
 Delay(100);
}

//********************************超温报警函数***************************//
void AlarmCheck(int temperature, int max_temperature)
{
if(temperature > max_temperature)
{
BuzzerSounds();// 蜂鸣器发声
}
}
//******************************软件延时函数*************************
void Delay(uint16 ms)
{
    uint16 i;
    do{
        i=790;
        while(--i);   //延时1ms(STC12C5A60S2,11.0592MHz,代码5级优化)
    } while(--ms);
}

看到最后,我们发现把这么多函数挤在一个文件中,看起来非常乱,下一篇博文我会介绍如何多文件式部署工程,让你的工程管理显得更轻松!