简介
根据官方文档个人整理的教程,本文将展示51单片机与DS3231进行通讯、设置/读取RTC、设置硬件闹钟。
自己写的I2C通讯程序 + DS3231驱动程序 + 测试程序,写的比较简洁,简单易懂,如有错误或建议欢迎指出。
实验使用的硬件:
- STC89C52 @12Mhz
- DS3231
- OLED12864 I2C
- 按键 x3
- 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支持这两种工作模式。
![](https://img-blog.csdnimg.cn/img_convert/3a05cfc9a41f05a725128133b0d68e3e.png)
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交流电气特性及通讯时序
![](https://img-blog.csdnimg.cn/img_convert/1b947050bb3fd1eefebcf310b48c6b8f.png)
![](https://img-blog.csdnimg.cn/img_convert/b9c2ce4dac2dcfda8b930cb93dbfa184.png)
由上图可知几个重要的参数
- 停止条件之后建立起始条件前需使总线空闲时间大于4.7us
- 建立开始条件后需保持4us后才可进行数据传输
- SCL时钟低电平期间(允许变更数据周期)需大于4.7us
- SCL时钟高电平期间(读取数据周期)需大于4us
DS3231寄存器概述
![](https://img-blog.csdnimg.cn/img_convert/880f0d5621688d4af57c7a038e94f9bd.png)
上图给出了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();
}
}