题目要求:
一、基本要求
二、硬件框图

三、功能描述
3.1 初始化
3.2 显示功能
 
 3.3 按键功能
 
  3.4 闹钟提示功能
底层函数内容:
1.初始化底层驱动专用文件
 
  比如先用3个IO口控制74HC138译码器,控制Y4为低电平;当Y4为低电平时,或非门74HC02控制Y4C为高电平,使74HC573的OE端口有效,OE端口有效时,可使用P0口控制LED的亮灭。
 可以去多了解74HC138译码器,74HC02或非门,74HC573八路输出透明锁存器的相关内容会更好理解
 #include <Init.h>
//关闭外设
 void System_Init()
 {
     P0 = 0xff;
     P2 = P2 & 0x1f | 0x80;
     P2 &= 0x1f;
     P0 = 0x00;
     P2 = P2 & 0x1f | 0xa0;
     P2 &= 0x1f;
 }
#include <STC15F2K60S2.H>
 void System_Init();
2.Led底层驱动专用文件
与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看
 #include <Led.h>
void Led_Disp(unsigned char addr,enable)
 {
     static unsigned char temp = 0x00;
     static unsigned char temp_old = 0xff;
     if(enable)
         temp |= 0x01 << addr;
     else
         temp &= ~(0x01 << addr);
     if(temp != temp_old)
     {
         P0 = ~temp;
         P2 = P2 & 0x1f |0x80;
         P2 &= 0x1f;
         temp_old = temp;
     }
 }
#include <STC15F2K60S2.H>
 void Led_Disp(unsigned char addr,enable);
3.按键底层驱动专用文件
 
  (板子上的按键从按键4开始到按键19,可根据实际硬件修改)
 #include "Key.h"
 unsigned char Key_Read()
 {
     unsigned char temp = 0;
     if(P33 == 0)temp = 4;
     if(P32 == 0)temp = 5;
     if(P31 == 0)temp = 6;
     if(P30 == 0)temp = 7;
     return temp;
 }
 //头文件
 #include <STC15F2K60S2.H>
unsigned char Key_Read();
4.数码管底层驱动专用文件
(这个板子使用的为共阳数码管,若使用的为共阴数码管要更换对应的段码表和位选表;与初始化底层驱动专用文件同理,需要了解对应的锁存器控制,可以在使用的芯片数据手册查看)
 #include <Seg.h>
 code unsigned char Seg_Dula[] = 
 {
 0xc0, //0
 0xf9, //1
 0xa4, //2
 0xb0, //3
 0x99, //4
 0x92, //5
 0x82, //6
 0xf8, //7
 0x80, //8
 0x90, //9
 0xff,
 //0x88, //A
 //0x83, //b
 //0xc6, //C
 //0xa1, //d
 //0x86, //E
 //0x8e //F
 0xbf, //-
 0xc6
 };
 unsigned char Seg_Wela[] = {0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80};
 void Seg_Disp(unsigned char wela,dula,point)
 {
     P0 = 0xff;
     P2 = P2 & 0x1f | 0xe0;
     P2 &= 0x1f;
     P0 = Seg_Wela[wela];
     P2 = P2 & 0x1f | 0xc0;
     P2 &= 0x1f;
     P0 = Seg_Dula[dula];
     if(point)
         P0 &= 0x7f;
     P2 = P2 & 0x1f | 0xe0;
     P2 &= 0x1f;
 }
 //头文件
 #include <STC15F2K60S2.H>
void Seg_Disp(unsigned char wela,dula,point);
5.//温度底层驱动专用头文件
/*    #     单总线代码片段说明
     1.     本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
     2.     参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
         中对单片机时钟频率的要求,进行代码调试和修改。
 */
 /*    #     单总线代码片段说明
     1.     本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
     2.     参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
         中对单片机时钟频率的要求,进行代码调试和修改。
 */
#include "onewire.h"
 sbit DQ = P1^4;
//单总线内部延时函数
void Delay_OneWire(unsigned int t)  
 {
     unsigned char i;
     while(t--){
         for(i=0;i<12;i++);
     }
 }
//单总线写操作
void Write_DS18B20(unsigned char dat)
 {
     unsigned char i;
     for(i=0;i<8;i++)
     {
         DQ = 0;
         DQ = dat&0x01;
         Delay_OneWire(5);
         DQ = 1;
         dat >>= 1;
     }
     Delay_OneWire(5);
 }
//单总线读操作
unsigned char Read_DS18B20(void)
 {
     unsigned char i;
     unsigned char dat;
   
     for(i=0;i<8;i++)
     {
         DQ = 0;
         dat >>= 1;
         DQ = 1;
         if(DQ)
         {
             dat |= 0x80;
         }        
         Delay_OneWire(5);
     }
     return dat;
 }
//DS18B20初始化
bit init_ds18b20(void)
 {
       bit initflag = 0;
       
       DQ = 1;
       Delay_OneWire(12);
       DQ = 0;
       Delay_OneWire(80);
       DQ = 1;
       Delay_OneWire(10); 
     initflag = DQ;     
       Delay_OneWire(5);
   
       return initflag;
 }
//函数名:读取温度函数
//入口参数:无
 //函数功能:完成温度转换,并返回转换之后的温度数据
 float Read_Temperature()
 {
     unsigned char high,low;//返回温度数据的低八位和高八位
     init_ds18b20();//初始化
     Write_DS18B20(0xcc);//跳过ROM
     Write_DS18B20(0x44);//开始温度转换
     init_ds18b20();//初始化
     Write_DS18B20(0xcc);//跳过ROM
     Write_DS18B20(0xbe);//读取温度
     low = Read_DS18B20();//读取低位
     high = Read_DS18B20();//读取高位
     return ((high << 8) |low) / 16.0;//返回温度保留后两位精度数据
}
 //头文件
 #include <STC15F2K60S2.H>
float Read_Temperature();
6.//时钟底层驱动专用头文件
/*    #     DS1302代码片段说明
     1.     本文件夹中提供的驱动代码供参赛选手完成程序设计参考。
     2.     参赛选手可以自行编写相关代码或以该代码为基础,根据所选单片机类型、运行速度和试题
         中对单片机时钟频率的要求,进行代码调试和修改。
*/    
 #include "ds1302.h"
 #include <intrins.h>
sbit SCK = P1^7;
 sbit SDA = P2^3;
 sbit RST = P1^3;
//写字节
void Write_Ds1302(unsigned  char temp) 
 {
     unsigned char i;
     for (i=0;i<8;i++)         
     { 
         SCK = 0;
         SDA = temp&0x01;
         temp>>=1; 
         SCK=1;
     }
 }   
//向DS1302寄存器写入数据
 
  void Write_Ds1302_Byte( unsigned char address,unsigned char dat )     
 {
      RST=0;    _nop_();
      SCK=0;    _nop_();
      RST=1;     _nop_();  
      Write_Ds1302(address);    
      Write_Ds1302(dat);        
      RST=0; 
 }
//从DS1302寄存器读出数据
unsigned char Read_Ds1302_Byte ( unsigned char address )
 {
      unsigned char i,temp=0x00;
      RST=0;    _nop_();
      SCK=0;    _nop_();
      RST=1;    _nop_();
      Write_Ds1302(address);
      for (i=0;i<8;i++)     
      {        
         SCK=0;
         temp>>=1;    
          if(SDA)
          temp|=0x80;    
          SCK=1;
     } 
      RST=0;    _nop_();
      SCK=0;    _nop_();
     SCK=1;    _nop_();
     SDA=0;    _nop_();
     SDA=1;    _nop_();
     return (temp);            
 }
//设置时钟,可以根据数据手册调整要设置的时钟状态,这里设置的为时分秒
void Set_Rct(unsigned char*ucRct)//unsigned char* ucRtc指向我存放时分秒的数组指针
 {
     unsigned char i;
    Write_Ds1302_Byte(0x8e,0x00);
     for(i=0;i<3;i++)
     Write_Ds1302_Byte(0x84-2*i,ucRct[i]);
     Write_Ds1302_Byte(0x8e,0x80);
 }
//读取时钟
void Read_Rct(unsigned char*ucRct)
 {
     unsigned char i;
     for(i=0;i<3;i++)
     ucRct[i] = Read_Ds1302_Byte(0x85-2*i);
 }
 //头文件
 #include <STC15F2K60S2.H>
void Write_Ds1302(unsigned  char temp) ;
 void Write_Ds1302_Byte( unsigned char address,unsigned char dat );
 unsigned char Read_Ds1302_Byte ( unsigned char address );
 void Set_Rct(unsigned char*ucRct);
 void Read_Rct(unsigned char*ucRct);
工程主函数内容:
1.头文件声明(把需要用到的头文件添加进来)
//头文件声明区
 #include <STC15F2K60S2.H>//单片机寄存器专用头文件
 #include "Init.h"//初始化底层驱动专用头文件
 #include "Led.h"//Led底层驱动专用头文件
 #include "Key.h"//按键底层驱动专用头文件
 #include "Seg.h"//数码管底层驱动专用头文件
 #include "ds1302.h"//时钟底层驱动专用头文件
 #include "onewire.h"//温度底层驱动专用头文件
2.变量声明(把需要用到的所有变量现在这里进行声明)
//变量声明区
 unsigned char Key_Val,Key_Old,Key_Down,Key_Up;//按键专用变量
 unsigned char Key_Slow_Down;//按键减速专用变量
 unsigned char Seg_Pos;//数码管扫描专用变量
 unsigned char Seg_Slow_Down;//数码管减速专用变量
 unsigned char Seg_Buf[8] = {10,10,10,10,10,10,10,10};//数码管显示数据存放数组
 unsigned char Seg_Point[8] = {0,0,0,0,0,0,0,0};//数码管小数点数据存放数组
 unsigned char ucLed[8] = {0,0,0,0,0,0,0,0};//Led显示数据存放数组
unsigned char Seg_Disp_Mode;//数码管显示模式变量 0-时间相关 1-温度相关
 unsigned char Seg_Index;//时间相关显示内容 0-时钟显示 1-时钟设置 2-闹钟设置
 unsigned char ucRct[3] = {0x23,0x59,0x50};//实时时钟数据数组 上电默认时间23:59:55
 unsigned char ucRct_Set[3] = {0x23,0x59,0x50};//时钟数据设置数组
 unsigned char ucRct_Index;//时钟设置数组指针
 unsigned int Timer_500ms;//五百毫秒计时变量
 bit Seg_Star_Flag;//数码管闪烁标志位
 unsigned char Alarm[3] = {0x00,0x00,0x00};//闹钟数据储存数组
 unsigned char Alarm_Set[3] = {0x00,0x00,0x00};//闹钟数据设置数组
 unsigned char* Data_Flag[3] = {ucRct,ucRct_Set,Alarm_Set};//简化程序专用指针数组
 bit Beep_Flag;//闹钟使能标志位 0-不使能 1-使能
 unsigned int Timer_200ms;//两百毫秒计时变量
 bit Led_Star_Flag;//Led闪烁标志位
 unsigned char Temperature;//实时温度储存变量
3.按键处理函数(在这里编写按键控制的函数)
//键盘处理函数
 void Key_Proc()
 {
     unsigned char i;//For循环专用变量
     if(Key_Slow_Down)return;
     Key_Slow_Down = 1;//键盘减速程序
     Key_Val = Key_Read();//实时读取键码值
     Key_Down = Key_Val & (Key_Val ^ Key_Old);//捕捉按键下降沿
     Key_Up = ~ Key_Val & (Key_Val ^ Key_Old);//捕捉按键上升沿
     Key_Old = Key_Val;//辅助扫描变量
     
     if(Beep_Flag == 1)//闹钟使能状态
     {
         if(Key_Down != 0)//按下任意按键
             Beep_Flag = 0;//关闭闹钟
         return;//跳出按键子程序 避免执行下面的语句
     }
     if(Seg_Index == 0)//处于非设置界面
     {
         if(Key_Old == 4)
             Seg_Disp_Mode = 1;
         else
             Seg_Disp_Mode = 0;
     }
     switch (Key_Down)
     {
         case 7://时钟设置
             if(Seg_Disp_Mode == 0)//处于时钟相关界面
             {
                 if(Seg_Index == 0)//处于时钟显示界面
                 {
                     for(i=0;i<3;i++)
                         ucRct_Set[i] = ucRct[i];//读取实时数据
                     Seg_Index = 1;//切换到时钟设置界面
                 }
                 else if(Seg_Index == 1)//处于时钟设置界面
                 {
                     if(++ucRct_Index == 3)
                     {
                         Set_Rct(ucRct_Set);//保存时钟数据
                         ucRct_Index = 0;//切换到时钟显示界面
                         Seg_Index = 0;//指针复位
                     }
                 }
             }
             break;
         case 6://闹钟设置
             if(Seg_Disp_Mode == 0)//处于时钟相关界面
             {
                 if(Seg_Index == 0)//处于时钟显示界面
                     Seg_Index = 2;//切换到闹钟设置界面
                 else if(Seg_Index == 2)//处于闹钟设置界面
                 {
                     if(++ucRct_Index == 3)
                     {
                         for(i=0;i<3;i++)
                         Alarm[1] = Alarm_Set[i];//保存闹钟数据
                         ucRct_Index = 0;//切换到时钟显示界面
                         Seg_Index = 0;//指针复位
                     }
                 }
             }
             break;
         case 5://参数自加
             if(Seg_Disp_Mode == 0)//时钟相关
             {
 //                if(Seg_Index == 1)//设置时钟
 //                {
 //                    ucRct_Set[ucRct_Index]++;
 //                    if(ucRct_Set[ucRct_Index] % 16 == 0x0a)//16进制 (9+1)=A
 //                        ucRct_Set[ucRct_Index] += 6;//16进制(A+6)=10
 //                    if(ucRct_Set[ucRct_Index] == (ucRct_Index?0x60:0x24))//使用BCD码的形式存储,用十六进制计算显示
 //                        ucRct_Set[ucRct_Index] = (ucRct_Index?0x59:0x23);
 //                }
                 Data_Flag[Seg_Index][ucRct_Index]++;
                 if(Data_Flag[Seg_Index][ucRct_Index] % 16 == 0x0a)//16进制 (9+1)=A //使用BCD码的形式存储,用十六进制计算显示
                     Data_Flag[Seg_Index][ucRct_Index] += 6;//16进制(A+6)=10
                 if(Data_Flag[Seg_Index][ucRct_Index] == (ucRct_Index?0x60:0x24))//设置上限
                     Data_Flag[Seg_Index][ucRct_Index] = (ucRct_Index?0x59:0x23);
                     
             }
             break;
             case 4:
             if(Seg_Disp_Mode == 0)//时钟相关
             {
 //                if(Seg_Index == 1)//设置时钟
 //                {
 //                    ucRct_Set[ucRct_Index]--;
 //                    if(ucRct_Set[ucRct_Index] % 16 == 0x0f)//16进制 (10-1)=F //BCD码需要手动进行十进制进位
 //                        ucRct_Set[ucRct_Index] -= 6;//16进制 (F-6)=9
 //                    if(ucRct_Set[ucRct_Index] == 0xf9)//16进制(0-7)=FFFF FFFF FFFF FFF9 取最后两位
 //                        ucRct_Set[ucRct_Index] = 0;
 //                }
                 Data_Flag[Seg_Index][ucRct_Index]--;
                 if(Data_Flag[Seg_Index][ucRct_Index] % 16 == 0x0f)//16进制 (10-1)=F //BCD码需要手动进行十进制进位
                     Data_Flag[Seg_Index][ucRct_Index] -= 6;//16进制 (F-6)=9
                 if(Data_Flag[Seg_Index][ucRct_Index] == 0xf9)//16进制(0-7)=FFFF FFFF FFFF FFF9 取最后两位 //设置下限
                     Data_Flag[Seg_Index][ucRct_Index] = 0;
                 
             }
             break;
     }
 }
4.信息处理函数(需要使用到到的函数进行简单的预处理)
//信息处理函数
 void Seg_Proc()
 {
     unsigned char i;//For循环专用变量
     if(Seg_Slow_Down)return;
     Seg_Slow_Down = 1;//数码管减速程序
     
     //数据读取区域
     Read_Rct(ucRct);//实时读取时钟数据
     Temperature = Read_Temperature();//实时读取温度数据
     //数据显示区域
 //    if(Seg_Disp_Mode == 0)//时钟  
 //        switch (Seg_Index)
 //        {
 //            case 0://时钟显示
 //                Seg_Buf[2] = Seg_Buf[5] = 11;
 //                for(i=0;i<3;i++)
 //                {
 //                    Seg_Buf[3*i] = ucRct[i] / 16;
 //                    Seg_Buf[3*i+1] = ucRct[i] % 16;
 //                }
 //                break;
 //            case 1://时钟设置
 //                Seg_Buf[2] = Seg_Buf[5] = 11;
 //                for(i=0;i<3;i++)
 //                {
 //                    Seg_Buf[3*i] = ucRct_Set[i] / 16;
 //                    Seg_Buf[3*i+1] = ucRct_Set[i] % 16;
 //                }
 //                Seg_Buf[3*ucRct_Index] = Seg_Star_Flag?10:ucRct_Set[ucRct_Index] / 16;
 //                Seg_Buf[3*ucRct_Index+1] = Seg_Star_Flag?10:ucRct_Set[ucRct_Index] % 16;
 //                break;
 //        }
 /* 
             遇到在某个状态下显示格式不变 
             但是显示数据数组需要发生改变时
             可以将这些数组按照顺序放入一个指针数组内
             然后通过访问指针数组达到简化程序的目的
         */
         if(Seg_Disp_Mode == 0)//时钟
         {
             Seg_Buf[2] = Seg_Buf[5] = 11;
             for(i=0;i<3;i++)
                 {
                     Seg_Buf[3*i] = Data_Flag[Seg_Index][i] / 16;
                     Seg_Buf[3*i+1] = Data_Flag[Seg_Index][i] % 16;
                 }
                 if(Seg_Index >0)//闪烁使能
                 {
                     Seg_Buf[3*ucRct_Index] = Seg_Star_Flag?10:Data_Flag[Seg_Index][ucRct_Index] / 16;
                     Seg_Buf[3*ucRct_Index+1] = Seg_Star_Flag?10:Data_Flag[Seg_Index][ucRct_Index] % 16;
                 }
         }
         else//温度
         {
             for(i=0;i<5;i++)
                 Seg_Buf[i] = 10;
             Seg_Buf[5] = Temperature / 10;
             Seg_Buf[6] = Temperature % 10;
             Seg_Buf[7] = 12;
         }
 }
5.其他函数(其他编写的函数,在这里书写会比较方便理解)
//其他函数
 void Led_Prov()
 {
     if(Alarm[0] == ucRct[0] && Alarm[1] == ucRct[1] && Alarm[2] == ucRct[2])//闹钟使能
         Beep_Flag = 1;
     if((ucRct[2] % 16)== (Alarm[2] % 16 + 5))//过五秒后
         Beep_Flag = 0;    
     ucLed[0] = Led_Star_Flag & Beep_Flag;//只有在闹钟使能条件下闪烁 //char 和bit 不能相乘,所以把*改成&
 }
6.定时器中断初始化函数
 
  (这个可以使用STC的定时器计算那里生成c代码,后面要自己添加ET0,EA打开中断)
 //定时器中断初始化函数
 void Timer0Init(void)        //1毫秒@12.000MHz
 {
     AUXR &= 0x7F;        //定时器时钟12T模式
     TMOD &= 0xF0;        //设置定时器模式
     TL0 = 0x18;        //设置定时初值
     TH0 = 0xFC;        //设置定时初值
     TF0 = 0;        //清除TF0标志
     TR0 = 1;        //定时器0开始计时
     ET0 = 1;        //定时器中断0打开
     EA = 1;             //总中断打开
 }
7.定时器1中断服务函数
(为了定时执行特定的任务,如此处设置了定时的时间触发了数码管和LED产生特定反应)//中断在测试时可以先注释掉,但是这里按键状态有延时,测试按键时可以解除注释
//定时器中断服务函数
 void Timer0server()interrupt 1
 {
     if(++Key_Slow_Down == 10)Key_Slow_Down = 0;//键盘减速专用
     if(++Seg_Slow_Down == 200)Seg_Slow_Down = 0;//数码管减速时间过长有时候会影响数码管的刷新,有问题的时候减短减速时间
     if(++Seg_Pos == 8)Seg_Pos = 0;//数码管显示专用
     Seg_Disp(Seg_Pos,Seg_Buf[Seg_Pos],Seg_Point[Seg_Pos]);
     Led_Disp(Seg_Pos,ucLed[Seg_Pos]);
     if(++Timer_500ms == 500)
     {
         Timer_500ms = 0;
         Seg_Star_Flag ^= 1;
     }
     if(++Timer_200ms == 200)
     {
         Timer_200ms = 0;
         Led_Star_Flag ^= 1;
     }
 }
8.主函数Main(调用书写的函数实现所需的相应功能)
 
  //Main
 void main()
 {   
     Set_Rct(ucRct);//上电初始化实时时钟
     Timer0Init();
     Sys_Init();
     while(1)
     {
         Key_Proc();
         Seg_Proc();
         Led_Prov();
     }
 }


















