51单片机学习笔记-8 DS1302实时时钟

8 DS1302实时时钟

[toc]

注:笔记主要参考B站江科大自化协教学视频“51单片机入门教程-2020版 程序全程纯手打 从零开始入门”。
注:工程及代码文件放在了本人的Github仓库


8.1 芯片介绍:DS1302

RTC(Real Time Clock)实时时钟,是一种集成电路,通常称为时钟芯片。常见的时钟芯片有DS1302,是由美国DALLAS公司推出的具有涓细电流充电能力的低功耗实时时钟芯片。它可以对年、月、日、周、时、分、秒进行计时,且具有闰年补偿等多种功能。本节的关键在于DS1302的时序。

图8-1 DS1302实物图

那为什么不使用单片机上的定时器来驱动实时时钟呢?

  1. CPU内的定时器精度不高。
  2. 使用定时器会占用CPU的内部资源。
  3. 单片机定时器掉电不能继续工作。
图8-2 DS1302原理图

芯片引脚说明:
CE:输入,芯片使能引脚。
SCLK:输入,串行接口时钟。
I/O:双向,输入输出的串行数据,SCLK上升沿触发。

基本逻辑是利用下面的寄存器地址说明(图8-4),写入的时候按需更改相应的秒数,读出的时候则依次进行读出。前八位是读写的选择和读写的地址,后八位才是具体要进行读写的数据,且先传输低位。前八位也被称为命令字,最高位写保护,控制能否串行写入(1/0),但一直可以读;R/C选择是RAM数据/时钟数据(1/0);A4~A0则为5位寄存器地址;R/W位则控制读写(1/0)。下面则给出了DS1602的读/写传输时序、寄存器操作地址:

图8-3 DS1302数据传输时序
图8-4 RTC相关寄存器

注:左侧的八位是直接给出了命令字

值得注意的是,上面寄存器所显示的数据,都采用BCD码(Binary Coded Decimal)进行编码:

  • 用4位二进制数来表示1位十进制数
    例:0001 0011表示13,1000 0101表示85,0001 1010不合法
    在十六进制中的体现:0x13表示13,0x85表示85,0x1A不合法
  • BCD码转十进制:DEC=BCD/16*10+BCD%16; (2位BCD)
    十进制转BCD码:BCD=DEC/10*16+DEC%10; (2位BCD)
  • CH位:时停控制位,写入此位为高电平,则时钟暂停。
  • 12/24位:设置12小时/24小时模式位。12小时模式,BIT5表示AM/PM,BIT4表示十位;24小时模式,[BIT5,BIT4]表示十位。
  • 关于星期:注意这个星期不是实际的星期,而是“处理日”,所以需要用户指定初始的星期偏置。

8.2 实验:DS1302时钟显示

需求:在LCD显示屏第一行显示日期“年-月-日”,第二行显示时间“时-分-秒”,24小时制。

图8-5 “实时时钟”代码调用关系

代码展示:
- main.c

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"

unsigned char DS1302_info[7];

void main(){
  unsigned char i=0;
  // 初始化LCD显示屏
  LCD_Init();
  LCD_ShowString(1,1,"2023-01-01");
  LCD_ShowString(2,1,"00:00:00");
  DS1302_Init();//初始化DS1302
  while(1){
    DS1302_ReadAll(&DS1302_info);
    LCD_ShowNum(1,3,DS1302_info[0],2);
    LCD_ShowNum(1,6,DS1302_info[1],2);
    LCD_ShowNum(1,9,DS1302_info[2],2);
    LCD_ShowDay(1,14,DS1302_info[3]);
    LCD_ShowNum(2,1,DS1302_info[4],2);
    LCD_ShowNum(2,4,DS1302_info[5],2);
    LCD_ShowNum(2,7,DS1302_info[6],2);
  }
}

- DS1302.h

#ifndef __DS1302_H__
#define __DS1302_H__

void DS1302_Init(void);//初始化DS1302
unsigned char DS1302_ReadByte(unsigned char read_comd);//从DS1302中读出数据
void DS1302_WriteByte(unsigned char wri_comd,wri_byte);//向DS1302中写入数据
void DS1302_ReadAll(unsigned char *info);//读出所有信息

#endif

- DS1302.c

#include <REGX52.H>

// 将端口重新定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO   = P3^4;
sbit DS1302_CE   = P3^5;
#define DS1302_RSEC 0x81
#define DS1302_RMIN 0x83
#define DS1302_RHOU 0x85
#define DS1302_RDAT 0x87
#define DS1302_RMON 0x89
#define DS1302_RDAY 0x8B
#define DS1302_RYEA 0x8D
#define DS1302_RWP  0x8F

#define DS1302_WSEC 0x80
#define DS1302_WMIN 0x82
#define DS1302_WHOU 0x84
#define DS1302_WDAT 0x86
#define DS1302_WMON 0x88
#define DS1302_WDAY 0x8A
#define DS1302_WYEA 0x8C
#define DS1302_WWP  0x8E


/**
  * @brief :从DS1302中读出数据
  * @param :需要操作的命令字read_comd。
  * @retval :读出的数据read_byte。
 */
unsigned char DS1302_ReadByte(unsigned char read_comd){
  unsigned char read_byte = 0x00;
  unsigned char i;
  DS1302_CE = 1;
  // 写入命令字
  for(i=0;i<8;i++){
    DS1302_IO = read_comd&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  // 读出数据
  for(i=0;i<8;i++){
    DS1302_SCLK = 1;
    DS1302_SCLK = 0;
    read_byte = DS1302_IO ? (read_byte|(0x01<<i)) : read_byte;
  }
  DS1302_IO = 0; //注意最后一定要将数据线清零
  DS1302_CE = 0;
  return read_byte;
 }
 
 /**
  * @brief :向DS1302中写入数据
  * @param :需要操作的命令字wri_comd,需要写入的数据wri_byte。
  * @retval :无。
 */
void DS1302_WriteByte(unsigned char wri_comd,wri_byte){
  unsigned char i;
  DS1302_CE = 1;
  // 写入命令字
  for(i=0;i<8;i++){
    DS1302_IO = wri_comd&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  // 写入8位数据
  for(i=0;i<8;i++){
    DS1302_IO = wri_byte&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  DS1302_SCLK = 0;
  DS1302_CE = 0;
 }

 /**
  * @brief :初始化控制DS1302的引脚。
  * @param :无。
  * @retval :无。
 */
void DS1302_Init(void){
  DS1302_SCLK = 0;
  DS1302_IO   = 0;
  DS1302_CE   = 0;
  DS1302_WriteByte(DS1302_WWP,0x00);//解除写保护
  DS1302_WriteByte(DS1302_WSEC,0x00);//解除时钟暂停
  // 默认为24小时模式
}

/**
  * @brief :从单片机中读出年/月/日/星期/时/分/秒
  * @param :数组指针,存储年/月/日/星期/时/分/秒。
  * @retval :无。程序中通过指针解引用更改信息。
 */
void DS1302_ReadAll(unsigned char *info){
  unsigned char temp;
  // 年
  temp = DS1302_ReadByte(DS1302_RYEA);
  *info = (temp>>4)*10 + (temp&0x0f);
  // 月
  temp = DS1302_ReadByte(DS1302_RMON);
  *(info+1) = (temp>>4)*10 + (temp&0x0f);
  // 日
  temp = DS1302_ReadByte(DS1302_RDAT);
  *(info+2) = (temp>>4)*10 + (temp&0x0f);
  // 星期
  *(info+3) = DS1302_ReadByte(DS1302_RDAY);
  // 时
  temp = DS1302_ReadByte(DS1302_RHOU);
  if(!(temp&0x80)){*(info+4) = (temp>>4)*10 + (temp&0x0f);}
  else            {*(info+4) = ((temp&0x10)>>4)*10 + (temp&0x0f);}
  // 分
  temp = DS1302_ReadByte(DS1302_RMIN);
  *(info+5) = ((temp&0x70)>>4)*10 + (temp&0x0f);
  // 秒
  temp = DS1302_ReadByte(DS1302_RSEC);
  *(info+6) = ((temp&0x70)>>4)*10 + (temp&0x0f);
}

- LCD1602.h:增加了星期显示函数“LCD1602_ShowDay”,可以方便的将数字转换成三位的英文缩写。

#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowDay(unsigned char Line,Column,day);//将数字星期还原成3位英文缩写,并显示

#endif

- LCD1602.c

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始显示星期(英文)
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的星期,范围1~7
  * @retval 无
  */
void LCD_ShowDay(unsigned char Line,Column,day){
	unsigned char i;
  char abbr_day[7][3] = {"Sta","Sun","Mon","Tue","Wen","Thu","Fri"};
	LCD_SetCursor(Line,Column);
	for(i=0;abbr_day[day-1][i]!='\0';i++)
	{
		LCD_WriteData(abbr_day[day-1][i]);
	}
}

编程感想:

  1. 注意 单片机复位后引脚默认为1,所以DS1302的控制引脚都需要初始化。
  2. 若读出的数字不动,则有可能是芯片处于写保护状态;若读出的数字乱跳,需要注意读状态结束后,需要手动将IO线归零。

8.3 实验:可调时钟

需求:在上面8.2实验的基础上,增加可调整日期和时间的功能。

  1. 按键K1用于切换显示模式/调节模式。
  2. 按键K2用于切换所需要调节的位(年/月/日/时/分/秒),切换到哪个哪个就闪烁。
  3. 按键K3增加计数,按键K4减少计数,秒只能归到0或30秒。这个地方注意不能违背闰月28天等常识。

上一节实验没有考虑星期显示的问题,本节首先改进。进过反复的测试,发现表示“星期”的字节有这样奇怪的属性:每次芯片掉电重启后自动归1;但如果不掉电,则会在23:59:59后随时间一起加1,但是芯片本身不会根据日期自动生成“星期”。所以每次对日期进行重写后,也要对“星期”进行重写使其变成1,于是芯片给出的“星期”的物理意义就变成了 “从最近一次设定时间到现在经历的天数(星期表示)”,即“处理日”。所以,还需要再添加一个存储“星期偏置”的常量,将其与“处理日”相结合便可以得到正确的星期。

另外本节实验,使用软件演示来完成按键检测;使用定时器延时来完成修改时间参数时的闪烁状态。

图8-6 “可调时钟”代码调用关系

代码展示:
- mian.c

#include <REGX52.H>
#include "LCD1602.h"
#include "DS1302.h"
#include "PushKey.h"
#include "Timer0.h"

// 全局变量:存储年/月/日/星期/时/分/秒/星期偏置
unsigned char DS1302_info[8] = {23,1,22,0,23,59,55,7};
// 全局变量:闪烁标志信号
unsigned char flag_twinkle=0x00;

/**
  * @brief :RTC显示。
  * @param :数组指针,存储年/月/日/星期/时/分/秒/星期偏置。
  * @retval :无。
 */
void RTC_Display(unsigned char *info){
  LCD_ShowNum(1,3,info[0],2);
  LCD_ShowNum(1,6,info[1],2);
  LCD_ShowNum(1,9,info[2],2);
  LCD_ShowDay(1,14,info[3]);
  LCD_ShowNum(2,1,info[4],2);
  LCD_ShowNum(2,4,info[5],2);
  LCD_ShowNum(2,7,info[6],2);
}

/**
  * @brief :进入修改模式,用户输入日期数据,保证其合理性并更新。
  * @param :数组指针,存储年/月/日/星期/时/分/秒/星期偏置。
  * @retval :无。
 */
void RTC_Modify(unsigned char *info){
  unsigned char MODE = 1;//指示当前是否处于修改模式
  unsigned char key = 0;//按键开关
  unsigned char cursor = 1;//指示要修改第几个元素,范围1~7(4实际更改info的第8个元素)
  while(MODE==1){
    key = PushKey();
    if(key==1){
      // 退出修改模式
      MODE = 0;
      DS1302_SetAll(info);//更新数据
    }else if(key==2){
      //移动修改光标
      cursor = (cursor>=7) ? 1 : (cursor+1);
//      LCD_ShowNum(2,14,cursor,3);//仅供测试使用!!!
    }else if(key==3){
      //对当前光标指定值进行加计数
      switch(cursor){
        case 1: //年
          *info = (*info>=99) ? 0 : (*info)+1;
          break;
        
        case 2: //月
          *(info+1) = (*(info+1)>=12) ? 1 : (*(info+1))+1;
          break;
        
        case 3: //日
          //31天的月份
          if(*(info+1)==1 || *(info+1)==3 || *(info+1)==5 || *(info+1)==7 ||
             *(info+1)==8 || *(info+1)==10|| *(info+1)==12){
             *(info+2)=(*(info+2)>=31)?1:(*(info+2))+1;
          }//30天的月份
          else if(*(info+1)==4 || *(info+1)==6 || *(info+1)==9 || *(info+1)==11){
            *(info+2)=(*(info+2)>=30)?1:(*(info+2))+1;
          }//2月需要判断闰月
          else if(*(info+1)==2){
            if(*info%4==0){*(info+2)=(*(info+2)>=29)?1:(*(info+2))+1;}
            else          {*(info+2)=(*(info+2)>=28)?1:(*(info+2))+1;}
          }
          break;
          
        case 4://星期
          *(info+7) = (*(info+7)>=7) ? 1 : (*(info+7))+1;//更新偏置
          *(info+3) = (*(info+3)>=7) ? 1 : (*(info+3))+1;//更新显示
          break;
        
        case 5://时
          *(info+4) = (*(info+4)>=23) ? 0 : (*(info+4))+1;
          break;
        
        case 6://分
          *(info+5) = (*(info+5)>=59) ? 0 : (*(info+5))+1;
          break;
        
        case 7://秒
          *(info+6) = (*(info+6)>=59) ? 0 : (*(info+6))+1;
          break;
        
        default:;
      }
    }else if(key==4){
      //对当前光标指定值进行减计数
      switch(cursor){
        case 1: //年
          *info = (*info==0) ? 99 : (*info)-1;
          break;
        
        case 2: //月
          *(info+1) = (*(info+1)==1) ? 12 : (*(info+1))-1;
          break;
        
        case 3: //日
          //31天的月份
          if(*(info+1)==1 || *(info+1)==3 || *(info+1)==5 || *(info+1)==7 ||
             *(info+1)==8 || *(info+1)==10|| *(info+1)==12){
             *(info+2)=(*(info+2)==1)?31:(*(info+2))-1;
          }//30天的月份
          else if(*(info+1)==4 || *(info+1)==6 || *(info+1)==9 || *(info+1)==11){
            *(info+2)=(*(info+2)==1)?30:(*(info+2))-1;
          }//2月需要判断闰月
          else if(*(info+1)==2){
            if(*info%4==0){*(info+2)=(*(info+2)==1)?29:(*(info+2))-1;}
            else          {*(info+2)=(*(info+2)==1)?28:(*(info+2))-1;}
          }
          break;
          
        case 4://星期
          *(info+7) = (*(info+7)==1) ? 7 : (*(info+7))-1;//更新偏置(实际起作用)
          *(info+3) = (*(info+3)==1) ? 7 : (*(info+3))-1;//更新显示
          break;
        
        case 5://时
          *(info+4) = (*(info+4)==0) ? 23 : (*(info+4))-1;
          break;
        
        case 6://分
          *(info+5) = (*(info+5)==0) ? 59 : (*(info+5))-1;
          break;
        
        case 7://秒
          *(info+6) = (*(info+6)==0) ? 59 : (*(info+6))-1;
          break;
        
        default:;    
      }
    }
    //更新当前的显示
    if(!flag_twinkle){
      LCD_ShowNum(1,3,*info,2);
      LCD_ShowNum(1,6,*(info+1),2);
      LCD_ShowNum(1,9,*(info+2),2);
      LCD_ShowDay(1,14,*(info+3));
      LCD_ShowNum(2,1,*(info+4),2);
      LCD_ShowNum(2,4,*(info+5),2);
      LCD_ShowNum(2,7,*(info+6),2);
    }else{
      switch(cursor){
        case 1: LCD_ShowString(1,3,"  "); break;
        case 2: LCD_ShowString(1,6,"  "); break;
        case 3: LCD_ShowString(1,9,"  "); break;
        case 4: LCD_ShowString(1,14,"   "); break;
        case 5: LCD_ShowString(2,1,"  "); break;
        case 6: LCD_ShowString(2,4,"  "); break;
        case 7: LCD_ShowString(2,7,"  "); break;
        default:;
      }
    }
  }
}


void main(){
//  unsigned char i=0;
  unsigned char key = 0; //指示按下了哪个按键开关
  //初始化LCD显示屏
  LCD_Init();
  LCD_ShowString(1,1,"2023-01-22");
  LCD_ShowString(2,1,"00:00:00");
  //初始化DS1302
  DS1302_Init();
  DS1302_SetAll(&DS1302_info);
  //初始化定时器T0
  Timer0_Init();
  while(1){
    key = PushKey();
    if(key==1){
    //进入修改模式
      RTC_Modify(&DS1302_info);
    }
    else{
    //正常读取并显示时钟
      DS1302_ReadAll(&DS1302_info);
      RTC_Display(&DS1302_info);
//      LCD_ShowNum(2,11,*(DS1302_info+7),3);//仅供测试使用!!!
    }
  }
}

// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  static unsigned int count_T0; //中断次数
  count_T0++; //更新中断次数
  TH0 = 0xfc; TL0 = 0x66; // 恢复溢出周期,近似1ms
  if(count_T0>200){
    count_T0 = 0;
    flag_twinkle = !flag_twinkle;
  }  
}

- DS1302.h

#ifndef __DS1302_H__
#define __DS1302_H__

void DS1302_Init(void);//初始化DS1302
unsigned char DS1302_ReadByte(unsigned char read_comd);//从DS1302中读出数据
void DS1302_WriteByte(unsigned char wri_comd,wri_byte);//向DS1302中写入数据
void DS1302_ReadAll(unsigned char *info);//读出所有信息
void DS1302_SetAll(unsigned char *info);//写入所有信息

#endif

- DS1302.c

#include <REGX52.H>

// 将端口重新定义
sbit DS1302_SCLK = P3^6;
sbit DS1302_IO   = P3^4;
sbit DS1302_CE   = P3^5;
#define DS1302_RSEC 0x81
#define DS1302_RMIN 0x83
#define DS1302_RHOU 0x85
#define DS1302_RDAT 0x87
#define DS1302_RMON 0x89
#define DS1302_RDAY 0x8B
#define DS1302_RYEA 0x8D
#define DS1302_RWP  0x8F

#define DS1302_WSEC 0x80
#define DS1302_WMIN 0x82
#define DS1302_WHOU 0x84
#define DS1302_WDAT 0x86
#define DS1302_WMON 0x88
#define DS1302_WDAY 0x8A
#define DS1302_WYEA 0x8C
#define DS1302_WWP  0x8E

/**
  * @brief :从DS1302中读出数据
  * @param :需要操作的命令字read_comd。
  * @retval :读出的数据read_byte。
 */
unsigned char DS1302_ReadByte(unsigned char read_comd){
  unsigned char read_byte = 0x00;
  unsigned char i;
  DS1302_CE = 1;
  // 写入命令字
  for(i=0;i<8;i++){
    DS1302_IO = read_comd&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  // 读出数据
  for(i=0;i<8;i++){
    DS1302_SCLK = 1;
    DS1302_SCLK = 0;
    read_byte = DS1302_IO ? (read_byte|(0x01<<i)) : read_byte;
  }
  DS1302_IO = 0; //注意最后一定要将数据线清零
  DS1302_CE = 0;
  return read_byte;
 }
 
 /**
  * @brief :向DS1302中写入数据
  * @param :需要操作的命令字wri_comd,需要写入的数据wri_byte。
  * @retval :无。
 */
void DS1302_WriteByte(unsigned char wri_comd,wri_byte){
  unsigned char i;
  DS1302_CE = 1;
  // 写入命令字
  for(i=0;i<8;i++){
    DS1302_IO = wri_comd&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  // 写入8位数据
  for(i=0;i<8;i++){
    DS1302_IO = wri_byte&(0x01<<i);
    DS1302_SCLK = 0;
    DS1302_SCLK = 1; // 经过测试,不需延时
  }
  DS1302_SCLK = 0;
  DS1302_CE = 0;
 }

/**
  * @brief :从单片机中读出年/月/日/星期/时/分/秒
  * @param :数组指针,存储年/月/日/星期/时/分/秒/星期偏置。
  * @retval :无。程序中通过指针解引用更改信息。
 */
void DS1302_ReadAll(unsigned char *info){
  unsigned char temp;
  // 年
  temp = DS1302_ReadByte(DS1302_RYEA);
  *info = (temp>>4)*10 + (temp&0x0f);
  // 月
  temp = DS1302_ReadByte(DS1302_RMON);
  *(info+1) = (temp>>4)*10 + (temp&0x0f);
  // 日
  temp = DS1302_ReadByte(DS1302_RDAT);
  *(info+2) = (temp>>4)*10 + (temp&0x0f);
  // 星期
  temp = DS1302_ReadByte(DS1302_RDAY);
//  *(info+3) = temp;
  temp = *(info+7) + (temp-1);
  *(info+3) = (temp>7) ? (temp-7) : temp;
  // 时
  temp = DS1302_ReadByte(DS1302_RHOU);
  if(!(temp&0x80)){*(info+4) = (temp>>4)*10 + (temp&0x0f);}
  else            {*(info+4) = ((temp&0x10)>>4)*10 + (temp&0x0f);}
  // 分
  temp = DS1302_ReadByte(DS1302_RMIN);
  *(info+5) = ((temp&0x70)>>4)*10 + (temp&0x0f);
  // 秒
  temp = DS1302_ReadByte(DS1302_RSEC);
  *(info+6) = ((temp&0x70)>>4)*10 + (temp&0x0f);
}

/**
  * @brief :向单片机写入年/月/日/时/分/秒
  * @param :数组指针,存储年/月/日/星期/时/分/秒/星期偏置。
  * @retval :无。
 */
void DS1302_SetAll(unsigned char *info){
  unsigned char temp;
  // 年
  temp = (*info)/10*16 + (*info)%10;
  DS1302_WriteByte(DS1302_WYEA,temp);
  // 月
  temp = (*(info+1))/10*16 + (*(info+1))%10;
  DS1302_WriteByte(DS1302_WMON,temp);
  // 日
  temp = (*(info+2))/10*16 + (*(info+2))%10;
  DS1302_WriteByte(DS1302_WDAT,temp);
  // 星期,每次更改则默认归1
  DS1302_WriteByte(DS1302_WDAY,0x01);
  // 时,24小时模式
  temp = (*(info+4))/10*16 + (*(info+4))%10;
  DS1302_WriteByte(DS1302_WHOU,temp);
  // 分
  temp = (*(info+5))/10*16 + (*(info+5))%10;
  DS1302_WriteByte(DS1302_WMIN,temp);
  // 秒
  temp = (*(info+6))/10*16 + (*(info+6))%10;
  DS1302_WriteByte(DS1302_WSEC,temp);
}

 /**
  * @brief :初始化控制DS1302的引脚。
  * @param :无。
  * @retval :无。
 */
void DS1302_Init(void){
  unsigned char init_info[8] = {0,1,1,0,0,0,0,6};
  DS1302_SCLK = 0;
  DS1302_IO   = 0;
  DS1302_CE   = 0;
  DS1302_WriteByte(DS1302_WWP,0x00);//解除写保护
  DS1302_WriteByte(DS1302_WSEC,0x00);//解除时钟暂停
  // 默认为24小时模式
  //默认复位成2000年1月1日星期六0点0时0分(24小说模式)
  DS1302_SetAll(&init_info);
}

- LCD1602.h

#ifndef __LCD1602_H__
#define __LCD1602_H__

void LCD_Init();
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char);
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String);
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length);
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length);
void LCD_ShowDay(unsigned char Line,Column,day);//将数字星期还原成3位英文缩写,并显示

#endif

- LCD1602.c

#include <REGX52.H>

//引脚配置:
sbit LCD_RS=P2^6;
sbit LCD_RW=P2^5;
sbit LCD_EN=P2^7;
#define LCD_DataPort P0

//函数定义:
/**
  * @brief  LCD1602延时函数,12MHz调用可延时1ms
  * @param  无
  * @retval 无
  */
void LCD_Delay()
{
	unsigned char i, j;

	i = 2;
	j = 239;
	do
	{
		while (--j);
	} while (--i);
}

/**
  * @brief  LCD1602写命令
  * @param  Command 要写入的命令
  * @retval 无
  */
void LCD_WriteCommand(unsigned char Command)
{
	LCD_RS=0;
	LCD_RW=0;
	LCD_DataPort=Command;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602写数据
  * @param  Data 要写入的数据
  * @retval 无
  */
void LCD_WriteData(unsigned char Data)
{
	LCD_RS=1;
	LCD_RW=0;
	LCD_DataPort=Data;
	LCD_EN=1;
	LCD_Delay();
	LCD_EN=0;
	LCD_Delay();
}

/**
  * @brief  LCD1602设置光标位置
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @retval 无
  */
void LCD_SetCursor(unsigned char Line,unsigned char Column)
{
	if(Line==1)
	{
		LCD_WriteCommand(0x80|(Column-1));
	}
	else if(Line==2)
	{
		LCD_WriteCommand(0x80|(Column-1+0x40));
	}
}

/**
  * @brief  LCD1602初始化函数
  * @param  无
  * @retval 无
  */
void LCD_Init()
{
	LCD_WriteCommand(0x38);//八位数据接口,两行显示,5*7点阵
	LCD_WriteCommand(0x0c);//显示开,光标关,闪烁关
	LCD_WriteCommand(0x06);//数据读写操作后,光标自动加一,画面不动
	LCD_WriteCommand(0x01);//光标复位,清屏
}

/**
  * @brief  在LCD1602指定位置上显示一个字符
  * @param  Line 行位置,范围:1~2
  * @param  Column 列位置,范围:1~16
  * @param  Char 要显示的字符
  * @retval 无
  */
void LCD_ShowChar(unsigned char Line,unsigned char Column,char Char)
{
	LCD_SetCursor(Line,Column);
	LCD_WriteData(Char);
}

/**
  * @brief  在LCD1602指定位置开始显示所给字符串
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的字符串
  * @retval 无
  */
void LCD_ShowString(unsigned char Line,unsigned char Column,char *String)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=0;String[i]!='\0';i++)
	{
		LCD_WriteData(String[i]);
	}
}

/**
  * @brief  返回值=X的Y次方
  */
int LCD_Pow(int X,int Y)
{
	unsigned char i;
	int Result=1;
	for(i=0;i<Y;i++)
	{
		Result*=X;
	}
	return Result;
}

/**
  * @brief  在LCD1602指定位置开始显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~65535
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以有符号十进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:-32768~32767
  * @param  Length 要显示数字的长度,范围:1~5
  * @retval 无
  */
void LCD_ShowSignedNum(unsigned char Line,unsigned char Column,int Number,unsigned char Length)
{
	unsigned char i;
	unsigned int Number1;
	LCD_SetCursor(Line,Column);
	if(Number>=0)
	{
		LCD_WriteData('+');
		Number1=Number;
	}
	else
	{
		LCD_WriteData('-');
		Number1=-Number;
	}
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number1/LCD_Pow(10,i-1)%10+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始以十六进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~0xFFFF
  * @param  Length 要显示数字的长度,范围:1~4
  * @retval 无
  */
void LCD_ShowHexNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i,SingleNumber;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		SingleNumber=Number/LCD_Pow(16,i-1)%16;
		if(SingleNumber<10)
		{
			LCD_WriteData(SingleNumber+'0');
		}
		else
		{
			LCD_WriteData(SingleNumber-10+'A');
		}
	}
}

/**
  * @brief  在LCD1602指定位置开始以二进制显示所给数字
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  Number 要显示的数字,范围:0~1111 1111 1111 1111
  * @param  Length 要显示数字的长度,范围:1~16
  * @retval 无
  */
void LCD_ShowBinNum(unsigned char Line,unsigned char Column,unsigned int Number,unsigned char Length)
{
	unsigned char i;
	LCD_SetCursor(Line,Column);
	for(i=Length;i>0;i--)
	{
		LCD_WriteData(Number/LCD_Pow(2,i-1)%2+'0');
	}
}

/**
  * @brief  在LCD1602指定位置开始显示星期(英文)
  * @param  Line 起始行位置,范围:1~2
  * @param  Column 起始列位置,范围:1~16
  * @param  String 要显示的星期,范围1~7
  * @retval 无
  */
void LCD_ShowDay(unsigned char Line,Column,day){
	unsigned char i;
  char abbr_day[7][3] = {"Mon","Tue","Wen","Thu","Fri","Sta","Sun"};
	LCD_SetCursor(Line,Column);
	for(i=0;abbr_day[day-1][i]!='\0';i++)
	{
		LCD_WriteData(abbr_day[day-1][i]);
	}
}

- PushKey.h

#ifndef __PUSHKEY_H__
#define __PUSHKEY_H__

#include "Delay.h"
/**
  * @brief :检测按下了哪个按键开关
  * @param :无
  * @retval :输出按键开关编号1~4,不按返回0,松开触发
 */
unsigned char PushKey(){
  unsigned char key = 0;
  if(!P3_1){Delay(10);while(!P3_1);Delay(10);key=1;}
  else if(!P3_0){Delay(10);while(!P3_0);Delay(10);key=2;}
  else if(!P3_2){Delay(10);while(!P3_2);Delay(10);key=3;}
  else if(!P3_3){Delay(10);while(!P3_3);Delay(10);key=4;}
  return key;
}

#endif

- Timer0.h

#ifndef __TIMER0_H__
#define __TIMER0_H__

#include <REGX52.H>
/**
  * @brief :对定时器0进行初始化,初始化完成后定时器0即可正常工作。
  * 注:对11.0592MHz进行12分频(脉冲周期1.0850694us)。
  * 注:配置过程中,由于掉电复位后中断都默认不开启,所以只需配置定时器0
  *     相关的寄存器即可,不要定义其他中断的寄存器,以保证程序的复用性。
 */
void Timer0_Init(){
  // 配置定时器T0的相关寄存器
  TMOD&=0xf0; TMOD|=0x01; // 选择T0的GATE=0/允许计数/模式1
  // 上面这个方法目的是不干扰高四位,对低四位先清零再加值。
  TF0 = 0; TR0 = 1; // 溢出标志位清空,运行控制位置1
  TH0 = 0xfc; TL0 = 0x66; // 离溢出近似1ms
  // 注:上面这个初值只在第一次溢出生效,后面都是从0开始计数。
  // 配置中断寄存器
  EA = 1; ET0 = 1; // 不屏蔽所有中断,允许T0溢出中断
  PT0 = 0; // T0优先级保持默认,不写这句话也可以
}

/*中断函数模板
// 定义定时器T0中断后要执行的动作
void Timer0_Routine() interrupt 1{
  static unsigned int count_T0; //中断次数
  count_T0++; //更新中断次数
  TH0 = 0xfc; TL0 = 0x66; // 恢复溢出周期,近似1ms
  if(count_T0>500){
    count_T0 = 0;
    
  }  
}
*/
#endif

- Delay.h

#ifndef __DEALY_H_
#define __DEALY_H_

// 延时cycles ms,晶振@11.0592MHz
void Delay(unsigned char cycles){
  unsigned char i, j;
  do{
    i = 2;
    j = 199;
    do{
      while (--j);
    }while (--i);
  }while(--cycles);
}

#endif

编程感想:

  1. 天坑:每次复位后,“星期”的显示都归成1,意思为“处理日”,不是实际的星期。
  2. 缺陷:由于按键检测使用 while循环等待按键松开,所以按键不松手的时候时钟显示会“暂停”(但是不影响DS1302继续计数),只有松手后,画面才会继续显示。后续会使用定时器来检测按键的上升沿,可以改善这个问题。
  3. 已解决错误:实测的时候,上电后第一次调整“星期”经常会出现自动加1,后需调整则不会出现这样的错误。
  • 后续“蜂鸣器播放提示音”实验发现,原来是开关按键3过于灵敏,有时会自动检测到“按下”。TMD,还以为是我程序有问题,苦思冥想半天没结果,当时好伤心。
  • 5
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
实现实时时钟,电源拔插时间不重置(内置电池供电维持时间变化)。功能有查看日期,查看星期,查看闹钟和分别的调整时间,调整日期,调整闹钟,调整星期。且默认情况下不可以调整,当按下可调控键之后才可以调整、再次按可调控键关闭变为不可调整状态。还有整点报时功能(有个小瑕疵就是闹钟正好是整点的时候和这个整点报时同时间的时候不会报时和闹钟,这个很容易改好,因为下午3点要答辩了,懒得改了机子老是写不进去,学校穷便宜机子没办法)。然后这个闹钟可以设置星期几几点闹也可以设置为普通的每天的这个时间点闹铃,这些都是可以调控的。时钟所有的功能都有,只差一个秒表,,这个很简单,,,外部中断来一个或者定时器T1中断来一个都可以,我没弄,因为我这个已经代码很长了,头疼、加中断还得加显示函数和秒表变化函数if分大于60 时++啥的,但因为这个采用的显示是低四位高四位控制的,我强行加一个也比较麻烦所有就没加了。欢迎下载干货,难看懂的都有备注,写了断断续续一周+时间左右(恕在下才疏学浅,因为书上上课的时候没学过I2C总线和pcf8563所以写的比较久。)部分代码如下: #define MAIN_Fosc 22118400L //定义主时钟 频率也是计数计时周期一秒的计数值 #include "STC15Fxxxx.H" /***********************************************************/ #define DIS_DOT 0x20 #define DIS_BLACK 0x10 #define DIS_ 0x11 /****************************** 用户定义宏***********************************/ #define Timer0_Reload (65536UL -(MAIN_Fosc / 1000)) //Timer 0 中断频率,1000次/秒 频率倍数计数即周期 周期为1秒 除以一千就是1000次每秒 /*****************************************************************************/ /************* 本地常量声明 **************/ u8 code t_display[]={ //标准字库 // 0 1 2 3 4 5 6 7 8 9 A B C D E F //共阴 0x3F,0x06,0x5B,0x4F,0x66,0x6D,0x7D,0x07,0x7F,0x6F,0x77,0x7C,0x39,0x5E,0x79,0x71, //black - H J K L N o P U t G Q r M y 0x00,0x40,0x76,0x1E,0x70,0x38,0x37,0x5C,0x73,0x3E,0x78,0x3d,0x67,0x50,0x37,0x6e, 0xBF,0x86,0xDB,0xCF,0xE6,0xED,0xFD,0x87,0xFF,0xEF,0x46}; //0. 1. 2. 3. 4. 5. 6. 7. 8. 9. -1 u8code T_COM[]={0x01,0x02,0x04,0x08,0x10,0x20,0x40,0x80}; //位码 取反就是书上213面 0从低位到高位 /************* IO口定义 **************/ sbit P_HC595_SER = P4^0; //pin 14 SER datainput sbit P_HC595_RCLK = P5^4; //pin 12 RCLk store(latch) clock sbit P_HC595_SRCLK = P4^3; //pin 11 SRCLK Shift data clock sbit led=P1^7; sbit led1=P4^7; sbit key=P3^2; /************* 本地变量声明 **************/ u8 LED8[8]; //显示缓冲 u8 display_index; //显示位索引 bit B_1ms; //1ms标志 u8IO_KeyState, IO_KeyState1, IO_KeyHoldCnt; //行列键盘变量 u8 KeyHoldCnt; //键按下计时 u8 KeyCode; //给用户使用的键码, 1~16有效 u8 cnt50ms; u8hello; u8nao; u8minute2; u8KeyCode0; u8 hour,hour1,minute,minute1,second,day,week,week1,month,year; //RTC变量 u16 msecond; u16msecond1; u8hello; u8naofu; u8zhuangtai; u8xunhuan; u8tuinao; //闹钟控制开启退出 u8xinqinao;//星期·闹钟 /************* 本地函数声明 **************/ void CalculateAdcKey(u16 adc); void IO_KeyScan(void); //50ms call void WriteNbyte(u8 addr, u8 *p, u8 number); void ReadNbyte( u8 addr, u8 *p, u8 number); void DisplayRTC(void); void DisplayRTC1(void); void DisplayRTC2(void); void ReadRTC(void);//读取时钟 void ReadRTC1(void); void ReadRTC2(void); void ReadRTC3(void); void WriteRTC(void); void WriteRTC1(void); void WriteRTC2(void); void WriteRTC3(void); void DisplayRTC3(void); void DisplayRTC4(void); void DisplayRTC5(void); /**************** 外部函数声明和外部变量声明*****************/ /**********************************************/ voidmain(void) { u8 i; P0M1= 0; P0M0 = 0; //设置为准双向口 P1M1= 0; P1M0 = 0; //设置为准双向口 P2M1= 0; P2M0 = 0; //设置为准双向口 P3M1= 0; P3M0 = 0; //设置为准双向口 P4M1= 0; P4M0 = 0; //设置为准双向口 P5M1= 0; P5M0 = 0; //设置为准双向口 P6M1= 0; P6M0 = 0; //设置为准双向口 P7M1= 0; P7M0 = 0; //设置为准双向口 display_index= 0; AUXR= 0x80;//T0时钟无分频 TMOD=0x00; //Timer0 set as 1T, 16 bits timer auto-reload,T0时钟无分频,16位自动重装 TH0= (u8)(Timer0_Reload / 256); //定时器取其高低8位为状态值 TL0= (u8)(Timer0_Reload % 256); ET0= 1; //Timer0 interrupt enable T0中断允许 TR0= 1; //Tiner0 run T0启动 EA= 1; //打开总中断 ,总中断允许 hello=0; if(nao==1){}//断电重置 elseif(nao==2){} else { nao=0; } for(i=0;i= 60) F0 = 1; //错误 if(minute>= 60) F0 = 1; //错误 if(hour >= 24) F0= 1; //错误 if(F0==1) //有错误, 默认12:00:00 { second= 0; minute= 0; hour = 12; WriteRTC(); } if(day>=32) F0=2; if(week>=8) F0=2; if(month>=13)F0=2; if(year>=100)F0=2; if(F0==2) //有错误, 默认12:00:00 { day=6; week=3; month=6; year=18; WriteRTC1(); } if(minute1>= 60) F0 = 3; //错误 if(hour1 >= 24) F0= 3; //错误 if(F0==3) { minute1=30; hour1=7; WriteRTC2(); } if(week1>=8) { WriteRTC3(); } DisplayRTC(); KeyHoldCnt= 0; //键按下计时 KeyCode= 0; //给用户使用的键码,1~16有效 IO_KeyState= 0; IO_KeyState1= 0; IO_KeyHoldCnt= 0; cnt50ms= 0; zhuangtai=0; KeyCode0=0; tuinao=0; minute2=61; naofu=0;//控制闹钟加1加5与分钟同步 xunhuan=0;//控制循环闹钟 xinqinao=0;//星期闹钟控制 if(msecond1==0){} else { msecond1=0; } while(1) { if(B_1ms) //1ms到 { B_1ms= 0; if(++msecond>= 500) //1秒到 刷新时钟 { if(hello==0) { ReadRTC(); DisplayRTC(); if(minute==0) //整点报时 { if(hour==hour1) { if(minute==minute1) {}else{ led1=0; } }else { led1=0; } } else{ led1=1; } if(hour==hour1) //闹钟实现块 { if(second==0)//保证和时钟分钟同步++乘其下一分钟前进行+5 +1操作 { msecond1=0; naofu=1; } else { if(naofu==1) { if(++msecond1>=60) {msecond1=0; naofu=0; } } } if(minute==minute1-1)//取出闹钟分钟值 { ReadRTC2(); minute2=minute1; }
51单片机(也称为STC单片机)是一种广泛应用于嵌入式系统的单片机系列,具有强大的处理能力和丰富的外设。其中包括了实时时钟(RTC)模块,可以用来实现精确的时间计时和日期显示。 在51单片机中使用RTC时钟模块,需要通过对相应的寄存器进行操作来实现。具体步骤如下: 1. 设置RTC模块的工作方式:可以选择24小时制或12小时制、设置闹钟时间等。通过配置控制寄存器来设置这些参数。 2. 设置RTC模块的时钟源:RTC模块需要一个外部时钟源来提供计时的稳定时钟信号,一般可以选择从晶振(如32.768kHz的晶振)或外部时钟输入引脚获取时钟源。 3. 初始化RTC模块的计数器:可以通过写入初始时间和日期来初始化RTC计数器,设置计时的起始时间。 4. 启动RTC模块计时:将控制寄存器相应位设置为1,启动RTC计时器开始计时。 5. 使用RTC模块的计时功能:可以通过读取RTC计数器的值来获取当前的时间和日期信息,可以实现精确的计时和日期显示。 除了以上基本的操作外,还可以通过设置RTC模块的中断功能来实现定时中断功能,可以在达到设置的闹钟时间或某个特定的时间点时触发中断,并执行相应的中断服务程序。 总结:51单片机RTC时钟汇编主要包括对RTC模块的配置设置、计数器的初始化、启动计时和使用计时功能等基本操作,可以实现精确计时和日期显示。此外,还可以通过中断功能实现定时中断。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

虎慕

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值