51单片机驱动DS3231实时时钟芯片(程序+仿真)

简介

根据官方文档个人整理的教程,本文将展示51单片机与DS3231进行通讯、设置/读取RTC、设置硬件闹钟。

自己写的I2C通讯程序 + DS3231驱动程序 + 测试程序,写的比较简洁,简单易懂,如有错误或建议欢迎指出。

实验使用的硬件:

  1. STC89C52 @12Mhz
  2. DS3231
  3. OLED12864 I2C
  4. 按键 x3
  5. LED发光二极管

DS3231硬件概述

DS3231是低成本、高精度I2C实时时钟(RTC),具有集成的温补晶体振荡器(TCXO)和晶体。该器件包含电池输入端,断开主电源时仍可保持精确的计时。集成晶体振荡器提高了器件的长期精确度,并减少了生产线的元件数量。DS3231提供商用级和工业级温度范围,采用16引脚、300mil的SO封装。
RTC保持秒、分、时、星期、日期、月和年信息。少于31天的月份,将自动调整月末日期,包括闰年补偿。时钟的工作格式可以是24小时或带AM/PM指示的12小时格式。提供两个可编程日历闹钟和一路可编程方波输出。地址与数据通过I2C双向总线串行传输。


使用I2C接口与DS3231通讯

DS3231支持双向I2C总线和数据传输协议。向总线发送数据的设备定义为发送器,接收数据的设备定义为接收器。控制信令的设备称为主设备。由主设备控制的设备称为从设备。总线必须由主设备来控制,主设备负责产生串行时钟(SCL)、控制总线访问,以及产生START和STOP条件。

DS3231在I2C总线上作为从设备工作。设备通过SCL输入和漏极开路的 SDA IO 线与总线连接。总线规范中定义了标准模式(100kHz最高时钟频率)和快速模式(400kHz最高时钟频率)。DS3231支持这两种工作模式。

I2C标准通讯时序


1. I2C通讯

定义的总线协议

  • 只能在总线空闲时才能启动数据传输。
  • 在数据传输过程中,当时钟线为高电平时,数据线必须保持稳定。如果时钟线为高电平时数据线电平发生变化,会被认为是控制信号。

定义的总线条件

  • 总线空闲:数据和时钟线均保持高电平。
  • 启动数据传输:时钟线为高电平时,数据线状态由高变低,定义为START条件。
  • 停止数据传输:时钟线为高电平时,数据线状态由低变高,定义为STOP条件。
  • 数据有效:产生START条件后,若在时钟信号为高电平期间数据线保持稳定,则此时数据线状态代表有效数据。线上数据必须在时钟信号为低电平期间改变。每个时钟脉冲传送一位数据。

使用START条件启动每次数据传输,并由STOP条件终止传输。在START与STOP条件之间传输的数据字节数没有限制,仅由主设备决定。信息传输以字节为单位,每个接收器使用第9位进行应答。

  • 应答:被寻址的接收设备必须在收到每个字节后发出应答信号。主设备必须生成额外的时钟脉冲,用于该应答位。

应答设备必须在应答时钟脉冲期间拉低SDA线,因此在应答时钟脉冲的高电平期间,SDA线保持稳定的低电平。当然,建立与保持时间必须考虑在内。通过不对从设备同步输出的最后字节产生应答位,主设备向从设备指示数据的结尾。这样,从设备必须保持数据线为高电平以使主设备能够产生STOP条件。

数据传输

  • 由主设备发送器传输至从设备接收器:主设备发送的首字节是从设备地址。接下来是若干数据字节。从设备收到每个字节后返回应答位。数据从最高有效位(MSB)开始传输。
  • 数据由从设备发送器传输至主设备接收器:主设备发送首字节(从设备地址)。然后从设备返回应答位。接下来由从设备向主设备发送若干数据字节。除最后一个字节外,主设备收到每个字节后均返回应答位。收到最后一个字节后,返回非应答位。


2. 通过软件模拟I2C通讯

首先定义引脚,SCL作为串行时钟产生,SDA作为数据传输

程序将按照I2C标准速率模拟

sbit SCL = P2^0;
sbit SDA = P2^1;

建立通讯起始条件

/**
 * @brief IIC起始信号
*/
void I2C_StartCondition(void)
{
    SDA = 1;				//SCL和SDA为高时总线空闲
    SCL = 1;
	Delay5us();		        //总线空闲时间需大于4.7us

    SDA = 0;				//SCL高电平期间SDA被拉低视为起始信号
    Delay5us();             //建立起始条件周期需大于4.7us
    SCL = 0;				//传输准备,SCL低电平期间允许改变SDA
}

建立通讯停止条件

/**
 * @brief IIC停止信号
*/
void I2C_StopCondition(void)
{
    SDA = 0;
    SCL = 1;
	Delay5us();		        //停止条件建立周期需大于4.7us
    SDA = 1;				//SCL高电平期间SDA被拉高视为停止信号,总线空闲
    Delay5us();
}

通过I2C发送一个字节

此函数包含了接收从机应答,函数将返回从机的应答状态

/**
 * @brief 通过IIC发送一个字节
 * @param byte 要发送的字节
 * @retval 1为从机应答 0为从机未应答
*/
unsigned char I2C_Send(unsigned char byte)
{
    unsigned char i,ACK_Status;
	
    for (i = 0; i < 8; i++)
    {
        SDA = (byte & (0x80 >> i)) ? 1 : 0;
        SCL = 1;
		Delay5us();	        //从机接收周期保持5us
        SCL = 0;
    }
    SDA = 1;				//接收应答前释放SDA
	Delay5us();			//从机应答周期10us
    SCL = 1;
	ACK_Status = SDA;		//接收从机应答
    SCL = 0;
    return !ACK_Status;
}

通过I2C接收一个字节

此函数包含了向从机发送应答,应答状态可在参数选择

/**
 * @brief 通过IIC接收一个字节
 * @param ACK_Select 主机发应答选择位 1应答 0为非应答
 * @retval 接收到的字节
*/
unsigned char I2C_Receive(bit ACK_Select)
{
    unsigned char i,byte = 0;
	
    SDA = 1;				//主机释放SDA,允许从机改变SDA	
	Delay5us();		        //从机发送周期保持5us
    for (i = 0; i < 8; i++)
    {
		SCL = 1;
		if (SDA)
			byte = byte | (0x80 >> i);
        SCL = 0;
		Delay5us();	        //主机接收周期保持5us
    }
    SDA = !ACK_Select;		//主机发送应答
    SCL = 1;
	Delay5us();		        //从机读应答,周期保持5us
    SCL = 0;
    return byte;
}


3. DS3231

DS3231交流电气特性及通讯时序

交流电气特性
通讯时序

由上图可知几个重要的参数

  • 停止条件之后建立起始条件前需使总线空闲时间大于4.7us
  • 建立开始条件后需保持4us后才可进行数据传输
  • SCL时钟低电平期间(允许变更数据周期)需大于4.7us
  • SCL时钟高电平期间(读取数据周期)需大于4us

DS3231寄存器概述

寄存器地址分配表

上图给出了DS3231计时寄存器的地址分配表。在多字节访问过程中,当地址指针到达寄存器空间的末尾(12h)时将会返回到地址00h。在I2C的START条件下或者地址指针递增至地址00h时,当前的时间会传输至辅助寄存器中在时钟继续运行的同时,可从辅助寄存器中读取时间信息。这样在读操作期间发生主寄存器更新时,可以避免重新读取寄存器。

可以通过读取适当的寄存器字节获得时钟和日历信息:图1给出了RTC寄存器的配置说明。通过写入适当的寄存器字节来设定或者初始化时钟和日历数据。时钟和日历寄存器的内容采用二到十进制编码(BCD)格式。

DS3231可以运行于12小时或者24小时模式。小时寄存器的第6位定义为12或24小时模式选择位。该位为高时,选择12小时模式。在12小时模式下,第5位为AM/PM指示位,逻辑高时为PM。在24小时模式下,第5位为二十小时位(20至23小时)。当年寄存器由99溢出至00时,会转换世纪位(月寄存器的第7位)。
星期寄存器在午夜时递增。对应于星期的值由用户定义但是该值必须连续(即,如果1等于星期日,那么2等于星期一,依次类推)。不合逻辑的时间和日历输入会导致不确定的操作。

 
读取或写入时间和日历寄存器时,辅助(用户)缓存器用于防止内部寄存器更新时可能出现的错误。读取时间和日历寄存器时,用户缓存器在任何START条件下或者寄存器指针返回到零时与内部寄存器同步。时间信息从这些辅助寄存器读取,此时时钟继续保持运行状态。这样在读操作期间发生主寄存器更新时可以避免重新读取寄存器。任何时候写秒寄存器时,倒计时链都会复位。

在DS3231应答后进行写传输操作。一旦倒计时链复位,为避免翻转问题,必须在1秒钟之内写入剩余的时间和日历寄存器。倘若振荡器已经工作,并且使能方波输出,那么1Hz方波输出在秒数据传输完成后500ms转换为高电平。


DS3231包含两个星期/日期闹钟。闹钟1可通过写入寄存器07h至0Ah来设定。闹钟2可通过写入寄存器0Bh至0Dh来设定。可对闹钟进行编程(通过控制寄存器的闹钟使能位和INTCN位),从而在闹钟匹配条件下触发INT/SQW输出。每个星期/日期闹钟寄存器的第7位是屏蔽位(表2)。当每个闹钟的屏蔽位均为逻辑0时,闹钟只有在计时寄存器中的值与存储于星期/日期闹钟寄存器的对应值相匹配时才会告警。闹钟也可以编程为每秒、分、时、星期或日期重复告警。表2给出了可能的设置。如果不按照表中配置,会导致不合逻辑的操作。


DY/DT位(闹钟星期/日期寄存器的第6位)用于控制存储于寄存器第0位至第5位的闹钟值是反映星期几还是月份中的日期。如果DY/DT设为逻辑0,闹钟将是与月份日期匹配的结果。如果DY/DT设为逻辑1,闹钟则是与星期几匹配的结果。
当RTC寄存器值与闹钟寄存器的设定值相匹配时,相应的闹钟标志位'A1F'或'A2F'置为逻辑 1。如果对应的闹钟中断使能'A1IE'或'A2IE'也设定为逻辑1,并且INTCN位设定为逻辑1,闹钟条件将会触发INT/SQW信号。在时间和日期寄存器每秒更新时都会检测匹配情况。 


DS3231具有两个附加寄存器(控制和状态),可以控制实时时钟、闹钟和方波输出。

第2位:中断控制(INTCN)。该位控制INT/SQW信号:INTCN设定为0时,INT/SQW引脚输出方波。INTCN设定为1时,若计时寄存器与任一个闹钟寄存器相匹配,则会触发INT/SQW输出(如果也使能闹钟的话)。匹配时相应的闹钟标志总是置位,而与INTCN位的状态无关。初次上电时,INTCN位设定为逻辑1。


第1位:闹钟2中断使能(A2IE)。该位设定为逻辑1时允许状态寄存器中的闹钟2标志位(A2F)触发INT/SQW信号(当INTCN=1时)。当A2IE位设定为0或者INTCN设定为0时,A2F位不启动中断信号。初次上电时,A2IE位清零(逻辑 0)。


第0位:闹钟1中断使能(A1IE)。该位设定为逻辑1时,允许状态寄存器中的闹钟1标志位(A1F)触发INT/SQW信号(当INTCN=1时)。当A1E位设定为0或者INTCN设定为0时,A1F位不启动INT/SQW信号。初次上电时,A1IE位清零(逻辑0)。

第1位:闹钟2标志(A2F)。闹钟2标志位为逻辑1时表示时间与闹钟2寄存器匹配。如果A2IE位为逻辑1,并且INTCN位设定为逻辑1,则同时触发INT/SQW引脚。写入逻辑0时A2F位清零。该位仅能写入逻辑0。试图写入逻辑1的操作不改变原逻辑值。 

第0位:闹钟1标志(A1F)。闹钟1标志位为逻辑1时表示时间与闹钟1寄存器匹配。如果A1IE位为逻辑1,并且[NTCN位设定为逻辑1,则同时触发INT/SQW引脚。写入逻辑0时A1F位清零。该位仅能写入逻辑0。试图写入逻辑1的操作不改变原逻辑值。


4. DS3231驱动程序

寄存器宏定义和全局变量

#define DS3231_I2C_ADDRESS_WR		0xD0
#define DS3231_I2C_ADDRESS_RD		0xD1
//寄存器宏定义
#define DS3231_ADDRESS_SECOND		0x00
#define DS3231_ADDRESS_MINUTE		0x01
#define DS3231_ADDRESS_HOUR			0x02
#define DS3231_ADDRESS_DAY			0x03
#define DS3231_ADDRESS_DATE			0x04
#define DS3231_ADDRESS_MOUTH		0x05
#define DS3231_ADDRESS_YEAR			0x06
#define DS3231_ADDRESS_A1_SECOND	0x07
#define DS3231_ADDRESS_A1_MINUTE	0x08
#define DS3231_ADDRESS_A1_HOUR		0x09
#define DS3231_ADDRESS_A1_DAY       0x0A
#define DS3231_ADDRESS_A2_MINUTE	0x0B
#define DS3231_ADDRESS_A2_HOUR		0x0C
#define DS3231_ADDRESS_A2_DAY		0x0D
#define DS3231_ADDRESS_CONTROL		0x0E
#define DS3231_ADDRESS_STATUS		0x0F
//存放初始时间,数组内容顺序对应:秒、分、时、星期、日期、月、年。
unsigned char DS3231_TimeConfig[7] = {00,50,23,4,29,2,24};
//存放实时时间
unsigned char DS3231_RTC[7];
//存放设置闹钟信息
unsigned char DS3231_Alarm1[4] = {15,50,22,5};        //秒,分,时,星期
unsigned char DS3231_Alarm2[3] = {51,22,5};           //分,时,星期

向DS3231写入一个字节

 从设备接收模式(DS3231写模式):通过SDA和SCL接收串行数据和时钟。收到每个字节后,发送应答位。START和STOP条件作为串行传输的开始和结束。在收到从设备地址和传输方向位之后,由硬件实现地址识别。主设备产生START条件后,从设备地址字节是收到的第一个字节。从设备地址字节包括7位DS3231地址,即1101000,接着是传输方向位(R/W)。该位为0,表示写操作。在收到并译码从设备地址字节后,DS3231向SDA发出应答信号。

在DS3231应答从设备地址+写位之后,主设备发送一个字地址至DS3231。这用于设定DS3231的寄存器指针,DS3231对该传输做出应答。主设备随后可以发送0或者更多字节的数据,DS3231应
答每个收到的字节。每个数据字节传输完后,寄存器指针自动递增。主设备产生STOP条件以终止数据写入。

/**
 * @brief DS3231写单字节
 * @param WordAddress DS3231要写入的寄存器地址
 * @param byte 要写入的字节
*/
void DS3231_Write(unsigned char WordAddress,unsigned char byte)
{
	I2C_StartCondition();
    I2C_Send(DS3231_I2C_ADDRESS_WR);
    I2C_Send(WordAddress);
	I2C_Send(byte);
	I2C_StopCondition();
}

从DS3231中读出一个字节 

从设备发送模式(DS3231读模式):接收和处理首字节的方式与从设备接收模式相同。但是,在这种模式下,方向位指示的传输方向是相反的。DS3231向SDA发送串行数据,并由SCL输入串行时钟。START和STOP条件作为串行传输的开始和结束。在收到从设备地址和方向位后,由硬件进行地址识别。主设备产生START条件后,从设备地址字节是收到的首字节。从设备地址字节包括7位DS3231地址,即1101000,接下来是方向位(R/W)。该位为1,表示读操作。在接收和译码从设备地址字节后,DS3231向SDA发出应答信号然后DS3231开始发送数据,并从寄存器指针所指向的寄存器地址开始。如果在启动读模式之前未写寄存器指针,所读取的首地址是最后存储的寄存器指针值。DS3231必须收到非应答信号以结束读操作。

主设备产生所有的串行时钟脉冲和START、STOP条件。传输由STOP条件或者重复START条件结束。由于重复START条件同时也是下一个串行传输的开始,因此不会释放总线。数据从最高有效位(MSB)开始传输。

/**
 * @brief DS3231读单字节
 * @param WordAddress DS3231要读取的寄存器地址
 * @retval 接收到的字节
*/
unsigned char DS3231_Read(unsigned char WordAddress)
{
	unsigned char byte;
	
	I2C_StartCondition();						//起始信号
	I2C_Send(DS3231_I2C_ADDRESS_WR);			//发从机地址写
	I2C_Send(WordAddress);						//发目标字地址
	I2C_StartCondition();						//再次起始信号
	I2C_Send(DS3231_I2C_ADDRESS_RD);			//发从机地址读
	byte = I2C_Receive(0);						//接收数据
	I2C_StopCondition();						//停止信号
	return byte;
}

设置DS3231时钟

由于时间寄存器是由BCD格式存储的,需要把预设的时间信息转换为BCD码。

之前学江科大DS1302用到的转换公式,直接抄过来

BCD = (DEC / 10*16) + (DEC % 10)

/**
 * @brief DS3231设置RTC时间信息,可在数组 DS3231_TimeConfig[] 中配置时间信息
*/
void DS3231_SetTime(void)
{
	unsigned char i,temp_array[7];
	
	for (i = 0; i < 7; i++)		//将初始时间数组引用到临时数组,循环将数组中内容转换为BCD码
		temp_array[i] = (DS3231_TimeConfig[i] / 10*16) + (DS3231_TimeConfig[i] % 10);
	
	I2C_StartCondition();
	I2C_Send(DS3231_I2C_ADDRESS_WR);
	I2C_Send(DS3231_ADDRESS_SECOND);		//写秒寄存器地址
	for (i = 0; i < 7; i++)		//每次数据传送完成后DS3231内部寄存器将自动递增
		I2C_Send(temp_array[i]);
	
	I2C_StopCondition();
}

时间的预设值存在DS3231_TimeConfig数组中,直接在循环里套公式转换为BCD码到临时数组,确保不修改时间预设数组的数据。

DS3231在每个字节传输完后寄存器指针会自动递增,在传输之前发送秒寄存器地址,之后将临时数组中数据依次发送。


读取DS3231 RTC

 DEC = (BCD / 16*10) + BCD % 16);

/**
 * @brief DS3231获取当前时钟信息并存储到数组 DS3231_RTC[]
*/
void DS3231_GetTime(void)
{
	unsigned char i;
	
	I2C_StartCondition();
	I2C_Send(DS3231_I2C_ADDRESS_WR);
	I2C_Send(DS3231_ADDRESS_SECOND);
	I2C_StartCondition();
	I2C_Send(DS3231_I2C_ADDRESS_RD);
	for (i = 0; i < 7; i++)		//每次数据传送完成后DS3231内部寄存器将自动递增
		//接收最后一个字节主机必须发送非应答
        DS3231_RTC[i] = (i < 6) ? I2C_Receive(0) : I2C_Receive(1);    

	I2C_StopCondition();
	for (i = 0; i < 7; i++)		//数组中内容转为DEC码
		DS3231_RTC[i] = (DS3231_RTC[i] / 16*10) + (DS3231_RTC[i] % 16);
}

这里也是同样使用循环将实时时钟数据读出并写到DS3231_RTC数组,最后再转换为DEC码。注意这里读取到最后一个字节主机要发送非应答,否则无法结束读操作。


设置Alarm1闹钟

/**
 * @brief DS3231设置闹钟Alarm1,触发条件:当A1所有的寄存器与RTC匹配时。
 * 可在数组 DS3231_Alarm1[] 中配置,闹钟设置范围:星期、时、分、秒
*/
void DS3231_SetAlarm1(void)
{
	unsigned char i,temp_array[4],ret_value = DS3231_Read(DS3231_ADDRESS_CONTROL);
	
	for (i = 0; i < 4; i++)                 //数组中内容转BCD码
		temp_array[i] = (DS3231_Alarm1[i] / 10*16) + (DS3231_Alarm1[i] % 10);

	//控制寄存器写位,INTCN(总中断) A1IE(A1中断使能)置为1,其他位保持不变
	DS3231_Write(DS3231_ADDRESS_CONTROL,ret_value | 0x05);
	DS3231_Write(DS3231_ADDRESS_A1_SECOND,temp_array[0]);
	DS3231_Write(DS3231_ADDRESS_A1_MINUTE,temp_array[1]);
	DS3231_Write(DS3231_ADDRESS_A1_HOUR,temp_array[2]);
	DS3231_Write(DS3231_ADDRESS_A1_DAY,temp_array[3] | 0x40);    //DY/DT 位为1,星期模式
}

设置Alarm2闹钟

/**
 * @brief DS3231设置闹钟Alarm2,触发条件:当A2所有的寄存器与RTC匹配时。
 * 可在数组 DS3231_Alarm2[] 中配置,闹钟设置范围:星期、时、分
*/
void DS3231_SetAlarm2(void)
{
    unsigned char i,temp_array[3],ret_value = DS3231_Read(DS3231_ADDRESS_CONTROL);

    for (i = 0; i < 3; i++)                 //数组中内容转BCD码
		temp_array[i] = (DS3231_Alarm2[i] / 10*16) + (DS3231_Alarm2[i] % 10);

    //控制寄存器写位,INTCN(总中断) A2IE(A2中断使能)置为1,其他位保持不变
	DS3231_Write(DS3231_ADDRESS_CONTROL,ret_value | 0x06);
	DS3231_Write(DS3231_ADDRESS_A2_MINUTE,temp_array[0]);
	DS3231_Write(DS3231_ADDRESS_A2_HOUR,temp_array[1]);
	DS3231_Write(DS3231_ADDRESS_A2_DAY,temp_array[2] | 0x80);    //禁用星期匹配
}

关闭或禁用所有闹钟

/**
 * @brief DS3231关闭或禁用所有闹钟,停止INT/SQW 输出
 * @param mode 关闭方式: 0 仅关闭已触发的闹钟   1 关闭并禁用所有闹钟 
*/
void DS3231_StopAlarm(bit mode)
{
    unsigned char ret_value = DS3231_Read(DS3231_ADDRESS_STATUS);
    
    //先清除所有闹钟标志位再关闭所有闹钟使能,否则已经被闹钟触发的 INT/SQW 不会复位
    DS3231_Write(DS3231_ADDRESS_STATUS,ret_value & 0xFC);    //状态寄存器写位,A1F、A2F置0
    if (mode)
    {
        ret_value = DS3231_Read(DS3231_ADDRESS_CONTROL);
        DS3231_Write(DS3231_ADDRESS_CONTROL,ret_value & 0xFC);    //控制寄存器写位,A1IE、A2IE置0
    }
}

测试程序和仿真

Proteus仿真电路图


测试程序功能:

OLED屏幕上显示实时时间,有三个定义的按键,长按 '=' 进入闹钟设置界面,通过按键 '+' '-' 变更设定值,按键 '=' 为确定,设定值带有数值越界修正,当设置的闹钟被触发时,LED被DS3231点亮,屏幕显示提示信息,此时长按 '+' 将关闭本次闹钟,LED熄灭清除提示信息。

建议直接用软件实现闹钟功能,个人觉得DS3231的硬件闹钟太鸡肋了用起来也不灵活。


测试程序代码:

#include <REGX52.h>
#include "DS3231.h"
#include "OLED12864.h"
#include "DELAY.h"

/**
 * @brief 获取一次按键输入
 * @retval 输入按键定义的值
*/
unsigned char get_key()
{
	P3 = P3 | 0x07;
	while (1)
	{
		if (P3_0 == 0)
		{delay(10);while (P3_0 == 0);delay(10);return '+';}
		if (P3_1 == 0)
		{delay(10);while (P3_1 == 0);delay(10);return '-';}
		if (P3_2 == 0)
		{delay(10);while (P3_2 == 0);delay(10);return '=';}
	}
}

void AlarmSetup()
{
    /*显示信息*/
	OLED_Display_ClearAll();
	OLED_ShowString_8x16(1,1,"Alarm Setup");
	OLED_ShowString_8x16(4,1,"Enter");
	get_key();      //等待用户响应
	/*显示信息*/
	OLED_Display_ClearAll();
	OLED_ShowString_8x16(1,1,"00:00 ~ 23:59");
	OLED_ShowString_8x16(2,1,"Set hour:");
	OLED_ShowString_8x16(4,14,":");
	OLED_ShowNumber_8x16(4,12,DS3231_Alarm2[1] = 0,2);       //显示初始值
	OLED_ShowNumber_8x16(4,15,DS3231_Alarm2[0] = 0,2);
	/*设置闹钟时间,第 1 步(共 2 步)*/
    while (1)
	{
		OLED_ShowNumber_8x16(4,12,DS3231_Alarm2[1],2);       //刷新显示设定值
		switch (get_key())                          //获取用户输入
		{
			case '+':        //输入'+',如果当前设定值不大于等于23,设定值+1,否则设置为0
				DS3231_Alarm2[1] = (DS3231_Alarm2[1] >= 23) ? 0 : ++DS3231_Alarm2[1];       
				continue;
			case '-':        //输入'-',如果当前设定值不小于等于0,设定值-1,否则设置为23
				DS3231_Alarm2[1] = (DS3231_Alarm2[1] <= 0) ? 23 : --DS3231_Alarm2[1];       
				continue;
			case '=':        //输入'=',确认操作,退出                                                               
				break;
		}
        break;
	}
	
	/*设置闹钟时间,第 2 步(共 2 步)*/
	OLED_ShowString_8x16(2,1,"Set minute:");
	while (1)
	{
		OLED_ShowNumber_8x16(4,15,DS3231_Alarm2[0],2);
		switch (get_key())
		{
			case '+':
				DS3231_Alarm2[0] = (DS3231_Alarm2[0] >= 59) ? 0 : ++DS3231_Alarm2[0];
				continue;
			case '-':
				DS3231_Alarm2[0] = (DS3231_Alarm2[0] <= 0) ? 59 : --DS3231_Alarm2[0];
				continue;
			case '=':
				break;
		}
        break;
	}
	OLED_Display_ClearAll();
	OLED_ShowString_8x16(1,1,"Alarm is set");
	OLED_ShowString_8x16(2,1,"Back");
	get_key();              //等待用户响应
	DS3231_SetAlarm2();     //设定闹钟2
    OLED_ShowString_8x16(1,1,"20  /  /  /");
	OLED_ShowString_8x16(2,1,"  :  :");
}

void main()
{
    /*显示时钟格式*/
    OLED_Init();
	OLED_ShowString_8x16(1,1,"20  /  /  /");
	OLED_ShowString_8x16(2,1,"  :  :");
	DS3231_SetTime();       //设置初始时间
	while (1)
	{
		DS3231_GetTime();       //获取时间
        /*展示时间*/
		OLED_ShowNumber_8x16(1,3,DS3231_RTC[6],2);
		OLED_ShowNumber_8x16(1,6,DS3231_RTC[5],2);
		OLED_ShowNumber_8x16(1,9,DS3231_RTC[4],2);
		OLED_ShowNumber_8x16(1,12,DS3231_RTC[3],1);
		OLED_ShowNumber_8x16(2,1,DS3231_RTC[2],2);
		OLED_ShowNumber_8x16(2,4,DS3231_RTC[1],2);
		OLED_ShowNumber_8x16(2,7,DS3231_RTC[0],2);
        /* OLED_ShowNumber_8x16(3,1,DS3231_Read(DS3231_ADDRESS_STATUS),5);
        OLED_ShowNumber_8x16(3,10,DS3231_Read(DS3231_ADDRESS_CONTROL),5); */
        if (P2_7 == 0)
            OLED_ShowString_6x8(8,1,"DS3231 Alarm Output");
        
        if (P3_0 == 0)
        {
            DS3231_StopAlarm(0);
            OLED_Dispaly_ClearPage(8);
        }
		if (P3_2 == 0)
			AlarmSetup();
	}
}

  • 11
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值