1.前言

    接着博主的上一篇 玩转 RTC时钟库 + DS1302,这一篇我们重点讲解DS3231时钟模块。没有看过上一篇的同学,麻烦先去阅读一下,因为很多理论基础已经在上一篇做了详细讲解,这里不再重复。

  • DS3231

2.DS3231介绍

2.1 为什么使用DS3231

    常用的DS1302需要使用外置晶振,且没有温度补偿,误差较大。这就促使了更高精度的时钟芯片 —— DS3231。
    DS3231内置晶振且有内部温度补偿,误差可做到1分钟每年。说白了,精度更高。

2.2 DS3231概述

  • DS3231是一款高精度I2C实时时钟器件,具有集成的温度补偿晶体振荡器,集成的晶体振荡器可提高器件的长期精确度;
  • 该器件包含电池输入端(也就是普通纽扣电池),断开主电源时仍可保持精确计时;
  • DS3231的寄存器能保存秒、分、时、星期、日期、月、年和闹钟设置等信息(除了和DS1302有一样的寄存器之外,还额外闹钟寄存器,这是可以做闹钟应用的一个关键点);
  • 少于31天的月份,可自动调整月末日期,包括闰年补偿。时钟的工作格式为24小时或带AM/PM指示的12小时格式;
  • DS3231提供两个可编程日历闹钟和一路可编程方波输出;
  • DS3231与单片机通过I2C双向串行总线传输地址与数据;
  • 自带存储芯片:AT24C32 EEPROM芯片(存储容量32K,可以存不少东西);

2.3 DS3231电路图&引脚关系

  • 电路图

  • 引脚关系

2.4 DS3231寄存器

    跟DS1302一样,对于DS3231的操作就是操作对应的寄存器,其寄存器对应关系如下:

2.5 RTCDS3231库

    老规矩,先看看源码,博主在源码中加入了部分代码注释:



#ifndef __RTCDS3231_H__
#define __RTCDS3231_H__

#include <Arduino.h>

#include "RtcDateTime.h"
#include "RtcTemperature.h"
#include "RtcUtility.h"

//I2C Slave Address
const uint8_t DS3231_ADDRESS = 0x68;

//DS3231 Register Addresses
const uint8_t DS3231_REG_TIMEDATE  = 0x00;//日期时间相关寄存器的第一个地址
const uint8_t DS3231_REG_ALARMONE  = 0x07;//闹钟1寄存器
const uint8_t DS3231_REG_ALARMTWO  = 0x0B;//闹钟2寄存器

const uint8_t DS3231_REG_CONTROL   = 0x0E;//控制寄存器
const uint8_t DS3231_REG_STATUS    = 0x0F;//状态寄存器
const uint8_t DS3231_REG_AGING     = 0x10;

const uint8_t DS3231_REG_TEMP      = 0x11;

//DS3231 Register Data Size if not just 1
const uint8_t DS3231_REG_TIMEDATE_SIZE = 7;//日期时间相关寄存器的数量
const uint8_t DS3231_REG_ALARMONE_SIZE = 4;//闹钟1寄存器占用空间大小 4字节
const uint8_t DS3231_REG_ALARMTWO_SIZE = 3;//闹钟2寄存器占用空间大小 3字节

const uint8_t DS3231_REG_TEMP_SIZE = 2;

// DS3231 Control Register Bits
const uint8_t DS3231_A1IE  = 0;
const uint8_t DS3231_A2IE  = 1;
const uint8_t DS3231_INTCN = 2;
const uint8_t DS3231_RS1   = 3;
const uint8_t DS3231_RS2   = 4;
const uint8_t DS3231_CONV  = 5;
const uint8_t DS3231_BBSQW = 6;
const uint8_t DS3231_EOSC  = 7;
const uint8_t DS3231_AIEMASK = (_BV(DS3231_A1IE) | _BV(DS3231_A2IE));
const uint8_t DS3231_RSMASK = (_BV(DS3231_RS1) | _BV(DS3231_RS2));

// DS3231 Status Register Bits
const uint8_t DS3231_A1F      = 0;
const uint8_t DS3231_A2F      = 1;
const uint8_t DS3231_BSY      = 2;
const uint8_t DS3231_EN32KHZ  = 3;
const uint8_t DS3231_OSF      = 7;
const uint8_t DS3231_AIFMASK = (_BV(DS3231_A1F) | _BV(DS3231_A2F));

// seconds accuracy
enum DS3231AlarmOneControl
{
    // bit order:  A1M4  DY/DT  A1M3  A1M2  A1M1
    DS3231AlarmOneControl_HoursMinutesSecondsDayOfMonthMatch = 0x00,
    DS3231AlarmOneControl_OncePerSecond = 0x17,
    DS3231AlarmOneControl_SecondsMatch = 0x16,
    DS3231AlarmOneControl_MinutesSecondsMatch = 0x14,
    DS3231AlarmOneControl_HoursMinutesSecondsMatch = 0x10,
    DS3231AlarmOneControl_HoursMinutesSecondsDayOfWeekMatch = 0x08,
};

class DS3231AlarmOne
{
public:
    DS3231AlarmOne( uint8_t dayOf,
            uint8_t hour,
            uint8_t minute,
            uint8_t second,
            DS3231AlarmOneControl controlFlags) :
        _flags(controlFlags),
        _dayOf(dayOf),
        _hour(hour),
        _minute(minute),
        _second(second)
    {
    }

    uint8_t DayOf() const
    {
        return _dayOf;
    }

    uint8_t Hour() const
    {
        return _hour;
    }

    uint8_t Minute() const
    {
        return _minute;
    }

    uint8_t Second() const
    {
        return _second;
    }

    DS3231AlarmOneControl ControlFlags() const
    {
        return _flags;
    }

    bool operator == (const DS3231AlarmOne& other) const
    {
        return (_dayOf == other._dayOf &&
                _hour == other._hour &&
                _minute == other._minute &&
                _second == other._second &&
                _flags == other._flags);
    }

    bool operator != (const DS3231AlarmOne& other) const
    {
        return !(*this == other);
    }

protected:
    DS3231AlarmOneControl _flags;

    uint8_t _dayOf;
    uint8_t _hour;
    uint8_t _minute;
    uint8_t _second;
};

// minutes accuracy
enum DS3231AlarmTwoControl
{
    // bit order:  A2M4  DY/DT  A2M3  A2M2
    DS3231AlarmTwoControl_HoursMinutesDayOfMonthMatch = 0x00,
    DS3231AlarmTwoControl_OncePerMinute = 0x0b,
    DS3231AlarmTwoControl_MinutesMatch = 0x0a,
    DS3231AlarmTwoControl_HoursMinutesMatch = 0x08,
    DS3231AlarmTwoControl_HoursMinutesDayOfWeekMatch = 0x04,
};

class DS3231AlarmTwo
{
public:
    DS3231AlarmTwo( uint8_t dayOf,
            uint8_t hour,
            uint8_t minute,
            DS3231AlarmTwoControl controlFlags) :
        _flags(controlFlags),
        _dayOf(dayOf),
        _hour(hour),
        _minute(minute)
    {
    }

    uint8_t DayOf() const
    {
        return _dayOf;
    }

    uint8_t Hour() const
    {
        return _hour;
    }

    uint8_t Minute() const
    {
        return _minute;
    }

    DS3231AlarmTwoControl ControlFlags() const
    {
        return _flags;
    }

    bool operator == (const DS3231AlarmTwo& other) const
    {
        return (_dayOf == other._dayOf &&
                _hour == other._hour &&
                _minute == other._minute &&
                _flags == other._flags);
    }

    bool operator != (const DS3231AlarmTwo& other) const
    {
        return !(*this == other);
    }

protected:
    DS3231AlarmTwoControl _flags;

    uint8_t _dayOf;
    uint8_t _hour;
    uint8_t _minute;
};

enum DS3231SquareWaveClock
{
    DS3231SquareWaveClock_1Hz  = 0b00000000,
    DS3231SquareWaveClock_1kHz = 0b00001000,
    DS3231SquareWaveClock_4kHz = 0b00010000,
    DS3231SquareWaveClock_8kHz = 0b00011000,
};

enum DS3231SquareWavePinMode
{
    DS3231SquareWavePin_ModeNone,
    DS3231SquareWavePin_ModeBatteryBackup,
    DS3231SquareWavePin_ModeClock,
    DS3231SquareWavePin_ModeAlarmOne,
    DS3231SquareWavePin_ModeAlarmTwo,
    DS3231SquareWavePin_ModeAlarmBoth
};

enum DS3231AlarmFlag
{
    DS3231AlarmFlag_Alarm1 = 0x01,
    DS3231AlarmFlag_Alarm2 = 0x02,
    DS3231AlarmFlag_AlarmBoth = 0x03,
};

template<class T_WIRE_METHOD> class RtcDS3231
{
public:
    RtcDS3231(T_WIRE_METHOD& wire) :
        _wire(wire),
        _lastError(0)
    {
    }

    void Begin()
    {
        //会把三个引脚设置为输入状态
        _wire.begin();
    }

    uint8_t LastError()
    {
        return _lastError;
    }

    bool IsDateTimeValid()
    {
        uint8_t status = getReg(DS3231_REG_STATUS);
        return !(status & _BV(DS3231_OSF));
    }

    /**
     * 判断时钟是否正在运行
     * @return bool
     *        true 时钟运行
     *        false 时钟停振,进入低功耗态
     */
    bool GetIsRunning()
    {
        //判断控制寄存器 DS3231_EOSC bit位置
        uint8_t creg = getReg(DS3231_REG_CONTROL);
        return !(creg & _BV(DS3231_EOSC));
    }

    /**
     * 设置时钟是否运行
     * @param isRunning
     *        true 时钟运行
     *        false 时钟停振,进入低功耗态
     */
    void SetIsRunning(bool isRunning)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);
        if (isRunning)
        {
            creg &= ~_BV(DS3231_EOSC);
        }
        else
        {
            creg |= _BV(DS3231_EOSC);
        }
        setReg(DS3231_REG_CONTROL, creg);
    }

    /**
     * 设置日期时间
     * @param RtcDateTime 日期时间对象
     */
    void SetDateTime(const RtcDateTime& dt)
    {
        // clear the invalid flag
        uint8_t status = getReg(DS3231_REG_STATUS);
        status &= ~_BV(DS3231_OSF); // clear the flag
        setReg(DS3231_REG_STATUS, status);

        // set the date time  批量设置时间
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_TIMEDATE);

        _wire.write(Uint8ToBcd(dt.Second()));//秒数
        _wire.write(Uint8ToBcd(dt.Minute()));//分钟
        _wire.write(Uint8ToBcd(dt.Hour())); // 24 hour mode only

        uint8_t year = dt.Year() - 2000;
        uint8_t centuryFlag = 0;

        if (year >= 100)
        {
            year -= 100;
            centuryFlag = _BV(7);
        }

        // RTC Hardware Day of Week is 1-7, 1 = Monday
        // convert our Day of Week to Rtc Day of Week
        uint8_t rtcDow = RtcDateTime::ConvertDowToRtc(dt.DayOfWeek());

        _wire.write(Uint8ToBcd(rtcDow));

        _wire.write(Uint8ToBcd(dt.Day()));//天数
        _wire.write(Uint8ToBcd(dt.Month()) | centuryFlag);//月份
        _wire.write(Uint8ToBcd(year));//年份

        _lastError = _wire.endTransmission();
    }

    /**
     * 获取日期时间
     * @return RtcDateTime 日期时间对象
     */
    RtcDateTime GetDateTime()
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_TIMEDATE);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return RtcDateTime(0);
        }

        _wire.requestFrom(DS3231_ADDRESS, DS3231_REG_TIMEDATE_SIZE);
        uint8_t second = BcdToUint8(_wire.read() & 0x7F);//秒数
        uint8_t minute = BcdToUint8(_wire.read());//分钟
        uint8_t hour = BcdToBin24Hour(_wire.read());//小时

        _wire.read();  // throwing away day of week as we calculate it

        uint8_t dayOfMonth = BcdToUint8(_wire.read());//天数
        uint8_t monthRaw = _wire.read();//月份
        uint16_t year = BcdToUint8(_wire.read()) + 2000;//年份

        if (monthRaw & _BV(7)) // century wrap flag
        {
            year += 100;
        }
        uint8_t month = BcdToUint8(monthRaw & 0x7f);

        return RtcDateTime(year, month, dayOfMonth, hour, minute, second);
    }

    RtcTemperature GetTemperature()
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_TEMP);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return RtcTemperature(0);
        }

        // Temperature is represented as a 10-bit code with a resolution
        // of 1/4th �C and is accessable as a signed 16-bit integer at
        // locations 11h and 12h.
        //
        //       |         r11h          | DP |         r12h         |
        // Bit:   15 14 13 12 11 10  9  8   .  7  6  5  4  3  2  1  0  -1 -2
        //         s  i  i  i  i  i  i  i   .  f  f  0  0  0  0  0  0
        //
        // As it takes (8) right-shifts to register the decimal point (DP) to
        // the right of the 0th bit, the overall word scaling equals 256.
        //
        // For example, at +/- 25.25�C, concatenated registers <r11h:r12h> =
        // 256 * (+/- 25+(1/4)) = +/- 6464, or 1940h / E6C0h.

        _wire.requestFrom(DS3231_ADDRESS, DS3231_REG_TEMP_SIZE);
        int8_t  r11h = _wire.read();                  // MS byte, signed temperature
        return RtcTemperature( r11h, _wire.read() );  // LS byte is r12h
    }

    void Enable32kHzPin(bool enable)
    {
        uint8_t sreg = getReg(DS3231_REG_STATUS);

        if (enable == true)
        {
            sreg |= _BV(DS3231_EN32KHZ);
        }
        else
        {
            sreg &= ~_BV(DS3231_EN32KHZ);
        }

        setReg(DS3231_REG_STATUS, sreg);
    }

    /**
     * 设置方波输出
     */
    void SetSquareWavePin(DS3231SquareWavePinMode pinMode)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);

        // clear all relevant bits to a known "off" state
        creg &= ~(DS3231_AIEMASK | _BV(DS3231_BBSQW));
        creg |= _BV(DS3231_INTCN);  // set INTCN to disables SQW

        switch (pinMode)
        {
        case DS3231SquareWavePin_ModeNone:
            break;

        case DS3231SquareWavePin_ModeBatteryBackup:
            creg |= _BV(DS3231_BBSQW); // set battery backup flag
            creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
            break;

        case DS3231SquareWavePin_ModeClock:
            creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
            break;

        case DS3231SquareWavePin_ModeAlarmOne:
            creg |= _BV(DS3231_A1IE);
            break;

        case DS3231SquareWavePin_ModeAlarmTwo:
            creg |= _BV(DS3231_A2IE);
            break;

        case DS3231SquareWavePin_ModeAlarmBoth:
            creg |= _BV(DS3231_A1IE) | _BV(DS3231_A2IE);
            break;
        }

        setReg(DS3231_REG_CONTROL, creg);
    }

    void SetSquareWavePinClockFrequency(DS3231SquareWaveClock freq)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);

        creg &= ~DS3231_RSMASK; // Set to 0
        creg |= (freq & DS3231_RSMASK); // Set freq bits

        setReg(DS3231_REG_CONTROL, creg);
    }

    /**
     * 设置闹钟1
     */
    void SetAlarmOne(const DS3231AlarmOne& alarm)
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_ALARMONE);

        _wire.write(Uint8ToBcd(alarm.Second()) | ((alarm.ControlFlags() & 0x01) << 7));
        _wire.write(Uint8ToBcd(alarm.Minute()) | ((alarm.ControlFlags() & 0x02) << 6));
        _wire.write(Uint8ToBcd(alarm.Hour()) | ((alarm.ControlFlags() & 0x04) << 5)); // 24 hour mode only

        uint8_t rtcDow = alarm.DayOf();
        if (alarm.ControlFlags() == DS3231AlarmOneControl_HoursMinutesSecondsDayOfWeekMatch)
        {
            rtcDow = RtcDateTime::ConvertDowToRtc(rtcDow);
        }

        _wire.write(Uint8ToBcd(rtcDow) | ((alarm.ControlFlags() & 0x18) << 3));

        _lastError = _wire.endTransmission();
    }

    /**
     * 设置闹钟2
     */
    void SetAlarmTwo(const DS3231AlarmTwo& alarm)
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_ALARMTWO);

        _wire.write(Uint8ToBcd(alarm.Minute()) | ((alarm.ControlFlags() & 0x01) << 7));
        _wire.write(Uint8ToBcd(alarm.Hour()) | ((alarm.ControlFlags() & 0x02) << 6)); // 24 hour mode only

        // convert our Day of Week to Rtc Day of Week if needed
        uint8_t rtcDow = alarm.DayOf();
        if (alarm.ControlFlags() == DS3231AlarmTwoControl_HoursMinutesDayOfWeekMatch)
        {
            rtcDow = RtcDateTime::ConvertDowToRtc(rtcDow);
        }

        _wire.write(Uint8ToBcd(rtcDow) | ((alarm.ControlFlags() & 0x0c) << 4));

        _lastError = _wire.endTransmission();
    }

    /**
     * 获取闹钟1
     */
    DS3231AlarmOne GetAlarmOne()
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_ALARMONE);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return DS3231AlarmOne(0, 0, 0, 0, DS3231AlarmOneControl_HoursMinutesSecondsDayOfMonthMatch);
        }

        _wire.requestFrom(DS3231_ADDRESS, DS3231_REG_ALARMONE_SIZE);

        uint8_t raw = _wire.read();
        uint8_t flags = (raw & 0x80) >> 7;
        uint8_t second = BcdToUint8(raw & 0x7F);

        raw = _wire.read();
        flags |= (raw & 0x80) >> 6;
        uint8_t minute = BcdToUint8(raw & 0x7F);

        raw = _wire.read();
        flags |= (raw & 0x80) >> 5;
        uint8_t hour = BcdToBin24Hour(raw & 0x7f);

        raw = _wire.read();
        flags |= (raw & 0xc0) >> 3;
        uint8_t dayOf = BcdToUint8(raw & 0x3f);

        if (flags == DS3231AlarmOneControl_HoursMinutesSecondsDayOfWeekMatch)
        {
            dayOf = RtcDateTime::ConvertRtcToDow(dayOf);
        }

        return DS3231AlarmOne(dayOf, hour, minute, second, (DS3231AlarmOneControl)flags);
    }

    /**
     * 获取闹钟2
     */
    DS3231AlarmTwo GetAlarmTwo()
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_ALARMTWO);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return DS3231AlarmTwo(0, 0, 0, DS3231AlarmTwoControl_HoursMinutesDayOfMonthMatch);
        }

        _wire.requestFrom(DS3231_ADDRESS, DS3231_REG_ALARMTWO_SIZE);

        uint8_t raw = _wire.read();
        uint8_t flags = (raw & 0x80) >> 7;
        uint8_t minute = BcdToUint8(raw & 0x7F);

        raw = _wire.read();
        flags |= (raw & 0x80) >> 6;
        uint8_t hour = BcdToBin24Hour(raw & 0x7f);

        raw = _wire.read();
        flags |= (raw & 0xc0) >> 4;
        uint8_t dayOf = BcdToUint8(raw & 0x3f);

        if (flags == DS3231AlarmTwoControl_HoursMinutesDayOfWeekMatch)
        {
            dayOf = RtcDateTime::ConvertRtcToDow(dayOf);
        }

        return DS3231AlarmTwo(dayOf, hour, minute, (DS3231AlarmTwoControl)flags);
    }

    // Latch must be called after an alarm otherwise it will not
    // trigger again
    DS3231AlarmFlag LatchAlarmsTriggeredFlags()
    {
        uint8_t sreg = getReg(DS3231_REG_STATUS);
        uint8_t alarmFlags = (sreg & DS3231_AIFMASK);
        sreg &= ~DS3231_AIFMASK; // clear the flags
        setReg(DS3231_REG_STATUS, sreg);
        return (DS3231AlarmFlag)alarmFlags;
    }

    void ForceTemperatureCompensationUpdate(bool block)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);
        creg |= _BV(DS3231_CONV); // Write CONV bit
        setReg(DS3231_REG_CONTROL, creg);

        while (block && (creg & _BV(DS3231_CONV)) != 0)
        {
            // Block until CONV is 0
            creg = getReg(DS3231_REG_CONTROL);
        }
    }

    int8_t GetAgingOffset()
    {
        return getReg(DS3231_REG_AGING);
    }

    void SetAgingOffset(int8_t value)
    {
        setReg(DS3231_REG_AGING, value);
    }

private:
    T_WIRE_METHOD& _wire;
    uint8_t _lastError;

    uint8_t getReg(uint8_t regAddress)
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(regAddress);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return 0;
        }

        // control register
        _wire.requestFrom(DS3231_ADDRESS, (uint8_t)1);

        uint8_t regValue = _wire.read();
        return regValue;
    }

    void setReg(uint8_t regAddress, uint8_t regValue)
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(regAddress);
        _wire.write(regValue);
        _lastError = _wire.endTransmission();
    }

};

#endif // __RTCDS3231_H__

2.5.1 Begin() —— 初始化

函数说明:

/**
 * 初始化,会把三个引脚设置为输入状态
 */
void Begin()

2.5.2 LastError() —— 获取上次错误编码

函数说明:

/**
 * 获取上次错误编码
 * @return 返回错误编码
 */
uint8_t LastError()

注意:

  • 错误编码请参考 https://www.arduino.cc/en/Reference/WireEndTransmission

2.5.3 IsDateTimeValid() —— 判断时间是否有效

函数说明:

/**
 * 判断时间是否有效
 * @return false 通常意味着电池没电或日期和时间从未设置
 *         true  意味时间有效
 */
bool IsDateTimeValid()

2.5.4 GetIsRunning() —— 判断时钟是否正在运行

函数说明:

/**
 * 判断时钟是否正在运行
 * @return bool
 *        true 时钟运行
 *        false 时钟停振,进入低功耗态
 */
bool GetIsRunning()

源码说明:

    /**
     * 判断时钟是否正在运行
     * @return bool
     *        true 时钟运行
     *        false 时钟停振,进入低功耗态
     */
    bool GetIsRunning()
    {
        //判断控制寄存器 DS3231_EOSC bit位置
        uint8_t creg = getReg(DS3231_REG_CONTROL);
        return !(creg & _BV(DS3231_EOSC));
    }

2.5.5 SetIsRunning() —— 设置时钟是否运行

函数说明:

/**
 * 设置时钟是否运行
 * @param isRunning
 *        true 时钟运行
 *        false 时钟停振,进入低功耗态
 */
void SetIsRunning(bool isRunning)

源码说明:

    /**
     * 设置时钟是否运行
     * @param isRunning
     *        true 时钟运行
     *        false 时钟停振,进入低功耗态
     */
    void SetIsRunning(bool isRunning)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);
        if (isRunning)
        {
            creg &= ~_BV(DS3231_EOSC);
        }
        else
        {
            creg |= _BV(DS3231_EOSC);
        }
        setReg(DS3231_REG_CONTROL, creg);
    }

2.5.6 SetDateTime() —— 设置日期时间

函数说明:

/**
 * 设置日期时间
 * @param RtcDateTime 日期时间对象
 */
void SetDateTime(const RtcDateTime& dt)

源码说明:

/**
     * 设置日期时间
     * @param RtcDateTime 日期时间对象
     */
    void SetDateTime(const RtcDateTime& dt)
    {
        // clear the invalid flag
        uint8_t status = getReg(DS3231_REG_STATUS);
        status &= ~_BV(DS3231_OSF); // clear the flag
        setReg(DS3231_REG_STATUS, status);

        // set the date time  批量设置时间
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_TIMEDATE);

        _wire.write(Uint8ToBcd(dt.Second()));//秒数
        _wire.write(Uint8ToBcd(dt.Minute()));//分钟
        _wire.write(Uint8ToBcd(dt.Hour())); // 24 hour mode only

        uint8_t year = dt.Year() - 2000;
        uint8_t centuryFlag = 0;

        if (year >= 100)
        {
            year -= 100;
            centuryFlag = _BV(7);
        }

        // RTC Hardware Day of Week is 1-7, 1 = Monday
        // convert our Day of Week to Rtc Day of Week
        uint8_t rtcDow = RtcDateTime::ConvertDowToRtc(dt.DayOfWeek());

        _wire.write(Uint8ToBcd(rtcDow));

        _wire.write(Uint8ToBcd(dt.Day()));//天数
        _wire.write(Uint8ToBcd(dt.Month()) | centuryFlag);//月份
        _wire.write(Uint8ToBcd(year));//年份

        _lastError = _wire.endTransmission();
    }

2.5.7 GetDateTime() —— 获取日期时间

函数说明:

/**
 * 获取日期时间
 * @return RtcDateTime 日期时间对象
 */
RtcDateTime GetDateTime()

源码说明:

/**
     * 获取日期时间
     * @return RtcDateTime 日期时间对象
     */
    RtcDateTime GetDateTime()
    {
        _wire.beginTransmission(DS3231_ADDRESS);
        _wire.write(DS3231_REG_TIMEDATE);
        _lastError = _wire.endTransmission();
        if (_lastError != 0)
        {
            return RtcDateTime(0);
        }

        _wire.requestFrom(DS3231_ADDRESS, DS3231_REG_TIMEDATE_SIZE);
        uint8_t second = BcdToUint8(_wire.read() & 0x7F);//秒数
        uint8_t minute = BcdToUint8(_wire.read());//分钟
        uint8_t hour = BcdToBin24Hour(_wire.read());//小时

        _wire.read();  // throwing away day of week as we calculate it

        uint8_t dayOfMonth = BcdToUint8(_wire.read());//天数
        uint8_t monthRaw = _wire.read();//月份
        uint16_t year = BcdToUint8(_wire.read()) + 2000;//年份

        if (monthRaw & _BV(7)) // century wrap flag
        {
            year += 100;
        }
        uint8_t month = BcdToUint8(monthRaw & 0x7f);

        return RtcDateTime(year, month, dayOfMonth, hour, minute, second);
    }

2.5.8 Enable32kHzPin() —— 使能32kHz引脚输出

函数说明:

/**
 * 使能32kHz引脚输出
 * @param enable true 使能
 *                false 禁止
 */
void Enable32kHzPin(bool enable)

2.5.10 SetSquareWavePin() —— 设置方波输出

函数说明:

/**
 * 设置方波输出
 * @param DS3231SquareWavePinMode 方波引脚模式
 */
void SetSquareWavePin(DS3231SquareWavePinMode pinMode)

DS3231SquareWavePinMode 参数说明:

  • DS3231SquareWavePin_ModeNone 禁止引脚输出
  • DS3231SquareWavePin_ModeBatteryBackup 如果外部电源电压低于电池电压,该引脚会触发中断
  • DS3231SquareWavePin_ModeClock 该引脚触发频率由SetSquareWavePinClockFrequency方法定义
  • DS3231SquareWavePin_ModeAlarmOne 闹钟1会触发
  • DS3231SquareWavePin_ModeAlarmTwo 闹钟2会触发
  • DS3231SquareWavePin_ModeAlarmBoth 闹钟1或者闹钟2都会触发

源码说明:

 /**
     * 设置方波输出
     */
    void SetSquareWavePin(DS3231SquareWavePinMode pinMode)
    {
        uint8_t creg = getReg(DS3231_REG_CONTROL);

        // clear all relevant bits to a known "off" state
        creg &= ~(DS3231_AIEMASK | _BV(DS3231_BBSQW));
        creg |= _BV(DS3231_INTCN);  // set INTCN to disables SQW

        switch (pinMode)
        {
        case DS3231SquareWavePin_ModeNone:
            break;

        case DS3231SquareWavePin_ModeBatteryBackup:
            creg |= _BV(DS3231_BBSQW); // set battery backup flag
            creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
            break;

        case DS3231SquareWavePin_ModeClock:
            creg &= ~_BV(DS3231_INTCN); // clear INTCN to enable SQW
            break;

        case DS3231SquareWavePin_ModeAlarmOne:
            creg |= _BV(DS3231_A1IE);
            break;

        case DS3231SquareWavePin_ModeAlarmTwo:
            creg |= _BV(DS3231_A2IE);
            break;

        case DS3231SquareWavePin_ModeAlarmBoth:
            creg |= _BV(DS3231_A1IE) | _BV(DS3231_A2IE);
            break;
        }

        setReg(DS3231_REG_CONTROL, creg);
    }

2.5.11 SetSquareWavePinClockFrequency() —— 设置方波时钟频率

函数说明:

/**
 * 设置方波时钟频率
 * @param DS3231SquareWaveClock 方波时钟频率
 */
void SetSquareWavePinClockFrequency(DS3231SquareWaveClock freq)

DS3231SquareWaveClock 参数说明:

  • DS3231SquareWaveClock_1Hz
  • DS3231SquareWaveClock_1kHz
  • DS3231SquareWaveClock_4kHz
  • DS3231SquareWaveClock_8kHz

2.5.12 SetAlarmOne() —— 设置闹钟1

函数说明:

/**
 * 设置闹钟1
 * @param DS3231AlarmOne 闹钟1
 */
void SetAlarmOne(const DS3231AlarmOne& alarm)

注意点:

  • 当达到闹钟定义的条件,就会触发中断,INT/SQW输出低电平信号;

DS3231AlarmOne源码解析:

class DS3231AlarmOne
{
public:
    DS3231AlarmOne( uint8_t dayOf,
            uint8_t hour,
            uint8_t minute,
            uint8_t second,
            DS3231AlarmOneControl controlFlags) :
        _flags(controlFlags),
        _dayOf(dayOf),
        _hour(hour),
        _minute(minute),
        _second(second)
    {
    }

    /**
     * 返回一周的一天或者一个月中的一天
     */
    uint8_t DayOf() const
    {
        return _dayOf;
    }

    /**
     * 返回一天的小时 24h制
     */
    uint8_t Hour() const
    {
        return _hour;
    }

    /**
     * 返回分钟
     */
    uint8_t Minute() const
    {
        return _minute;
    }

    /**
     * 返回秒数
     */
    uint8_t Second() const
    {
        return _second;
    }

    DS3231AlarmOneControl ControlFlags() const
    {
        return _flags;
    }

    bool operator == (const DS3231AlarmOne& other) const
    {
        return (_dayOf == other._dayOf &&
                _hour == other._hour &&
                _minute == other._minute &&
                _second == other._second &&
                _flags == other._flags);
    }

    bool operator != (const DS3231AlarmOne& other) const
    {
        return !(*this == other);
    }

protected:
    DS3231AlarmOneControl _flags;

    uint8_t _dayOf;
    uint8_t _hour;
    uint8_t _minute;
    uint8_t _second;
};

重点看构造函数:

/**
 * 建立闹钟1对象
 * @param dayOf - (0-6) (1-31) day of the week or the day of the month , see flags below
 * @param hour - (0-23) the hour of the day
 * @param minute - (0-59) the minute of the hour
 * @param second - (0-59) the second of the minute
 * @param controlFlags
 *  -- DS3231AlarmOneControl_HoursMinutesSecondsDayOfMonthMatch  月天时分秒都匹配才会触发中断
 *  -- DS3231AlarmOneControl_OncePerSecond 每一秒都触发
 *  -- DS3231AlarmOneControl_SecondsMatch  每一分钟的秒数匹配才触发
 *  -- DS3231AlarmOneControl_MinutesSecondsMatch 每小时里面的分秒都匹配才触发
 *  -- DS3231AlarmOneControl_HoursMinutesSecondsMatch  一天中时分秒都匹配才触发
 *  -- DS3231AlarmOneControl_HoursMinutesSecondsDayOfWeekMatch 一个星期中天时分秒都匹配才触发
 */
DS3231AlarmOne( uint8_t dayOf, uint8_t hour, uint8_t minute, uint8_t second, DS3231AlarmOneControl controlFlags)

2.5.13 GetAlarmOne() —— 获取闹钟1

函数说明:

/**
 * 获取闹钟1
 * @return DS3231AlarmOne 闹钟1
 */
DS3231AlarmOne GetAlarmOne()

2.5.14 SetAlarmTwo() —— 设置闹钟2

函数说明:

/**
 * 设置闹钟2
 * @param DS3231AlarmTwo 闹钟2
 */
void SetAlarmTwo(const DS3231AlarmTwo& alarm)

注意点:

  • 当达到闹钟定义的条件,就会触发中断,INT/SQW输出低电平信号;

DS3231AlarmTwo源码解析:

class DS3231AlarmTwo
{
public:
    DS3231AlarmTwo( uint8_t dayOf,
            uint8_t hour,
            uint8_t minute,
            DS3231AlarmTwoControl controlFlags) :
        _flags(controlFlags),
        _dayOf(dayOf),
        _hour(hour),
        _minute(minute)
    {
    }

    /**
     * 返回一周的一天或者一个月中的一天
     */
    uint8_t DayOf() const
    {
        return _dayOf;
    }

    /**
     * 返回一天的小时 24h制
     */
    uint8_t Hour() const
    {
        return _hour;
    }

    /**
     * 返回分钟
     */
    uint8_t Minute() const
    {
        return _minute;
    }

    DS3231AlarmTwoControl ControlFlags() const
    {
        return _flags;
    }

    bool operator == (const DS3231AlarmTwo& other) const
    {
        return (_dayOf == other._dayOf &&
                _hour == other._hour &&
                _minute == other._minute &&
                _flags == other._flags);
    }

    bool operator != (const DS3231AlarmTwo& other) const
    {
        return !(*this == other);
    }

protected:
    DS3231AlarmTwoControl _flags;

    uint8_t _dayOf;
    uint8_t _hour;
    uint8_t _minute;
};

重点看构造函数:

/**
 * 建立闹钟2对象
 * @param dayOf - (0-6) (1-31) day of the week or the day of the month , see flags below
 * @param hour - (0-23) the hour of the day
 * @param minute - (0-59) the minute of the hour
 * @param controlFlags
 *  -- DS3231AlarmTwoControl_HoursMinutesDayOfMonthMatch  每月天时分都匹配才会触发中断
 *  -- DS3231AlarmTwoControl_OncePerMinute  每一分钟都触发
 *  -- DS3231AlarmTwoControl_MinutesMatch   每一小时的分钟匹配才触发
 *  -- DS3231AlarmTwoControl_HoursMinutesMatch  每天里面的时分都匹配才触发
 *  -- DS3231AlarmTwoControl_HoursMinutesDayOfWeekMatch   每星期的天时分匹配才触发
 */
DS3231AlarmTwo( uint8_t dayOf, uint8_t hour, uint8_t minute, DS3231AlarmTwoControl controlFlags)

2.5.15 GetAlarmTwo() —— 获取闹钟2

函数说明:

/**
 * 获取闹钟2
 * @return DS3231AlarmTwo 闹钟2
 */
DS3231AlarmTwo GetAlarmTwo()

2.5.16 LatchAlarmsTriggeredFlags() —— 处理闹钟触发

函数说明:

/**
 * 处理闹钟触发
 * @return DS3231AlarmFlag
 *        --- DS3231AlarmFlag_Alarm1  闹钟1触发
 *        --- DS3231AlarmFlag_Alarm2   闹钟2触发
 *        --- DS3231AlarmFlag_AlarmBoth    闹钟1、2触发
 */
DS3231AlarmFlag LatchAlarmsTriggeredFlags()

注意点:

  • 当闹钟触发之后必须要调用该方法,不然不会再次触发,用来确保我们处理了闹钟事件;

2.6 EepromAt24c32库

    前面说到了,DS3231时钟模块集成了AT24c32 eeprom存储芯片,如果我们需要用到存储数据功能,就得引入 EepromAt24c32库。那么,我们来看看该库有什么方法。

2.6.1 Begin() —— 初始化

函数说明:

/**
 * 初始化引脚
 */
void Begin()

2.6.2 LastError() —— 获取上次错误编码

函数说明:

/**
 * 获取上次错误编码
 * @return 返回错误编码
 */
uint8_t LastError()

注意:

  • 错误编码请参考 https://www.arduino.cc/en/Reference/WireEndTransmission

2.6.3 SetMemory() —— 存储数据

函数说明:

/***
 * 写入数据
 * @param  memoryAddress 地址偏移量
 * @param  value 数据
 */
void SetMemory(uint16_t memoryAddress, uint8_t value)

/**
 * 批量写入数据
 * @param pValue 批量数据
 * @param countBytes 数据字节数
 */
uint8_t SetMemory(uint16_t memoryAddress, const uint8_t* pValue, uint8_t countBytes)

2.6.4 GetMemory() —— 读取数据

函数说明:

/***
 * 读取数据
 * @param  memoryAddress 地址偏移量
 * @return  数据
 */
uint8_t GetMemory(uint16_t memoryAddress)

/***
 * 批量读取数据
 * @param  memoryAddress 地址偏移量
 * @param pValue 存储空间
 * @param countBytes 数据字节数
 */
uint8_t SetMemory(uint16_t memoryAddress, const uint8_t* pValue, uint8_t countBytes)

2.7 DS3231接线

DS3231采用I2C总线方式,SCLK、SDA。

3.测试用例

测试用例分为三个:

  • 测试时间
  • 测试闹钟
  • 测试存储

3.1 测试时间

实验内容

  • 设置时间并在串口上打印时间

实验器材

  • Mega2560 + DS3231

引脚连接

模块引脚 Mega2560引脚
VCC VCC5V
GND GND
SDA SDA(20)
SCL SCL(21)

实验代码


// CONNECTIONS:
// DS3231 SDA --> SDA
// DS3231 SCL --> SCL
// DS3231 VCC --> 3.3v or 5v
// DS3231 GND --> GND

/* for software wire use below
#include <SoftwareWire.h>  // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>

SoftwareWire myWire(SDA, SCL);
RtcDS3231<SoftwareWire> Rtc(myWire);
 for software wire use above */

/* for normal hardware wire use below */
#include <Wire.h> // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>
RtcDS3231<TwoWire> Rtc(Wire);
/* for normal hardware wire use above */

void setup ()
{
    Serial.begin(57600);

    Serial.print("compiled: ");
    Serial.print(__DATE__);
    Serial.println(__TIME__);

    //--------RTC SETUP ------------
    // if you are using ESP-01 then uncomment the line below to reset the pins to
    // the available pins for SDA, SCL
    // Wire.begin(0, 2); // due to limited pins, use pin 0 and 2 for SDA, SCL

    Rtc.Begin();

    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    printDateTime(compiled);
    Serial.println();

    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            // Common Cuases:
            //    1) first time you ran and the device wasn't running yet
            //    2) the battery on the device is low or even missing

            Serial.println("RTC lost confidence in the DateTime!");

            // following line sets the RTC to the date & time this sketch was compiled
            // it will also reset the valid flag internally unless the Rtc device is
            // having an issue

            Rtc.SetDateTime(compiled);
        }
    }

    if (!Rtc.GetIsRunning())
    {
        Serial.println("RTC was not actively running, starting now");
        Rtc.SetIsRunning(true);
    }

    RtcDateTime now = Rtc.GetDateTime();
    if (now < compiled)
    {
        Serial.println("RTC is older than compile time!  (Updating DateTime)");
        Rtc.SetDateTime(compiled);
    }
    else if (now > compiled)
    {
        Serial.println("RTC is newer than compile time. (this is expected)");
    }
    else if (now == compiled)
    {
        Serial.println("RTC is the same as compile time! (not expected but all is fine)");
    }

    // never assume the Rtc was last configured by you, so
    // just clear them to your needed state
    Rtc.Enable32kHzPin(false);
    Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone);
}

void loop ()
{
    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            // Common Cuases:
            //    1) the battery on the device is low or even missing and the power line was disconnected
            Serial.println("RTC lost confidence in the DateTime!");
        }
    }

    RtcDateTime now = Rtc.GetDateTime();
    printDateTime(now);
    Serial.println();

    RtcTemperature temp = Rtc.GetTemperature();
    temp.Print(Serial);
    // you may also get the temperature as a float and print it
    // Serial.print(temp.AsFloatDegC());
    Serial.println("C");

    delay(10000); // ten seconds
}

#define countof(a) (sizeof(a) / sizeof(a[0]))

void printDateTime(const RtcDateTime& dt)
{
    char datestring[20];

    snprintf_P(datestring,
            countof(datestring),
            PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
            dt.Month(),
            dt.Day(),
            dt.Year(),
            dt.Hour(),
            dt.Minute(),
            dt.Second() );
    Serial.print(datestring);
}

实验结果:

3.2 测试闹钟

实验内容

  • 设置时间并设置闹钟

实验器材

  • Mega2560 + DS3231

引脚连接

模块引脚 Mega2560引脚
VCC VCC5V
GND GND
SDA SDA(20)
SCL SCL(21)
SQW 19

实验代码


// CONNECTIONS:
// DS3231 SDA --> SDA
// DS3231 SCL --> SCL
// DS3231 VCC --> 3.3v or 5v
// DS3231 GND --> GND
// SQW --->  (Pin19) Don't forget to pullup (4.7k to 10k to VCC)

/* for software wire use below
#include <SoftwareWire.h>  // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>

SoftwareWire myWire(SDA, SCL);
RtcDS3231<SoftwareWire> Rtc(myWire);
 for software wire use above */

/* for normal hardware wire use below */
#include <Wire.h> // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>
RtcDS3231<TwoWire> Rtc(Wire);
/* for normal hardware wire use above */

// Interrupt Pin Lookup Table
// (copied from Arduino Docs)
//
// CAUTION:  The interrupts are Arduino numbers NOT Atmel numbers
//   and may not match (example, Mega2560 int.4 is actually Atmel Int2)
//   this is only an issue if you plan to use the lower level interupt features
//
// Board           int.0    int.1   int.2   int.3   int.4   int.5
// ---------------------------------------------------------------
// Uno, Ethernet    2       3
// Mega2560         2       3       21      20     [19]      18
// Leonardo         3       2       0       1       7

#define RtcSquareWavePin 19 // Mega2560
#define RtcSquareWaveInterrupt 4 // Mega2560

// marked volatile so interrupt can safely modify them and
// other code can safely read and modify them
volatile uint16_t interuptCount = 0;
volatile bool interuptFlag = false;

void InteruptServiceRoutine()
{
    // since this interupted any other running code,
    // don't do anything that takes long and especially avoid
    // any communications calls within this routine
    interuptCount++;
    interuptFlag = true;
}

void setup ()
{
    Serial.begin(57600);

    // set the interupt pin to input mode
    pinMode(RtcSquareWavePin, INPUT);

    //--------RTC SETUP ------------
    // if you are using ESP-01 then uncomment the line below to reset the pins to
    // the available pins for SDA, SCL
    // Wire.begin(0, 2); // due to limited pins, use pin 0 and 2 for SDA, SCL

    Rtc.Begin();

    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);

    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            Serial.println("RTC lost confidence in the DateTime!");
            Rtc.SetDateTime(compiled);
        }
    }

    if (!Rtc.GetIsRunning())
    {
        Serial.println("RTC was not actively running, starting now");
        Rtc.SetIsRunning(true);
    }

    RtcDateTime now = Rtc.GetDateTime();
    if (now < compiled)
    {
        Serial.println("RTC is older than compile time!  (Updating DateTime)");
        Rtc.SetDateTime(compiled);
    }

    Rtc.Enable32kHzPin(false);
    Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeAlarmBoth); 

    // Alarm 1 set to trigger every day when
    // the hours, minutes, and seconds match
    RtcDateTime alarmTime = now + 88; // into the future
    DS3231AlarmOne alarm1(
            alarmTime.Day(),
            alarmTime.Hour(),
            alarmTime.Minute(),
            alarmTime.Second(),
            DS3231AlarmOneControl_HoursMinutesSecondsMatch);
    Rtc.SetAlarmOne(alarm1);

    // Alarm 2 set to trigger at the top of the minute
    DS3231AlarmTwo alarm2(
            0,
            0,
            0,
            DS3231AlarmTwoControl_OncePerMinute);
    Rtc.SetAlarmTwo(alarm2);

    // throw away any old alarm state before we ran
    Rtc.LatchAlarmsTriggeredFlags();

    // setup external interupt
    attachInterrupt(RtcSquareWaveInterrupt, InteruptServiceRoutine, FALLING);
}

void loop ()
{
    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            Serial.println("RTC lost confidence in the DateTime!");
        }
    }

    RtcDateTime now = Rtc.GetDateTime();

    printDateTime(now);
    Serial.println();

    // we only want to show time every 10 seconds
    // but we want to show responce to the interupt firing
    for (int timeCount = 0; timeCount < 20; timeCount++)
    {
        if (Alarmed())
        {
            Serial.print(">>Interupt Count: ");
            Serial.print(interuptCount);
            Serial.println("<<");
        }
        delay(500);
    }
}

bool Alarmed()
{
    bool wasAlarmed = false;
    if (interuptFlag)  // check our flag that gets sets in the interupt
    {
        wasAlarmed = true;
        interuptFlag = false; // reset the flag

        // this gives us which alarms triggered and
        // then allows for others to trigger again
        DS3231AlarmFlag flag = Rtc.LatchAlarmsTriggeredFlags();

        if (flag & DS3231AlarmFlag_Alarm1)
        {
            Serial.println("alarm one triggered");
        }
        if (flag & DS3231AlarmFlag_Alarm2)
        {
            Serial.println("alarm two triggered");
        }
    }
    return wasAlarmed;
}

#define countof(a) (sizeof(a) / sizeof(a[0]))

void printDateTime(const RtcDateTime& dt)
{
    char datestring[20];

    snprintf_P(datestring,
            countof(datestring),
            PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
            dt.Month(),
            dt.Day(),
            dt.Year(),
            dt.Hour(),
            dt.Minute(),
            dt.Second() );
    Serial.print(datestring);
}

实验结果:

3.3 测试存储

实验内容

  • 设置时间并在串口上打印时间,同时存储“What time is it in Greenwich?”字符串进EEPROM

实验器材

  • Mega2560 + DS3231

引脚连接

模块引脚 Mega2560引脚
VCC VCC5V
GND GND
SDA SDA(20)
SCL SCL(21)

实验代码


// CONNECTIONS:
// DS1307 SDA --> SDA
// DS1307 SCL --> SCL
// DS1307 VCC --> 5v
// DS1307 GND --> GND

#define countof(a) (sizeof(a) / sizeof(a[0]))

/* for software wire use below
#include <SoftwareWire.h>  // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>
#include <EepromAt24C32.h>

SoftwareWire myWire(SDA, SCL);
RtcDS1307<SoftwareWire> Rtc(myWire);
/* for software wire use above */

/* for normal hardware wire use below */
#include <Wire.h> // must be included here so that Arduino library object file references work
#include <RtcDS3231.h>
#include <EepromAt24C32.h>

RtcDS3231<TwoWire> Rtc(Wire);
EepromAt24c32<TwoWire> RtcEeprom(Wire);

// if you have any of the address pins on the RTC soldered together
// then you need to provide the state of those pins, normally they
// are connected to vcc with a reading of 1, if soldered they are
// grounded with a reading of 0.  The bits are in the order A2 A1 A0
// thus the following would have the A2 soldered together
// EepromAt24c32<TwoWire> RtcEeprom(Wire, 0b011);

/* for normal hardware wire use above */

// nothing longer than 32 bytes
// rtc eeprom memory is 32 byte pages
// writing is limited to each page, so it will wrap at page
// boundaries.
// But reading is only limited by the buffer in Wire class which
// by default is 32
const char data[] = "What time is it in Greenwich?";
const uint16_t stringAddr = 64; // stored on page boundary

void setup ()
{
    Serial.begin(57600);

    Serial.print("compiled: ");
    Serial.print(__DATE__);
    Serial.println(__TIME__);

    //--------RTC SETUP ------------
    // if you are using ESP-01 then uncomment the line below to reset the pins to
    // the available pins for SDA, SCL
    // Wire.begin(0, 2); // due to limited pins, use pin 0 and 2 for SDA, SCL

    Rtc.Begin();
    RtcEeprom.Begin();

    RtcDateTime compiled = RtcDateTime(__DATE__, __TIME__);
    printDateTime(compiled);
    Serial.println();

    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            Serial.println("RTC lost confidence in the DateTime!");
            Rtc.SetDateTime(compiled);
        }
    }

    if (!Rtc.GetIsRunning())
    {
        Serial.println("RTC was not actively running, starting now");
        Rtc.SetIsRunning(true);
    }

    RtcDateTime now = Rtc.GetDateTime();
    if (now < compiled)
    {
        Serial.println("RTC is older than compile time!  (Updating DateTime)");
        Rtc.SetDateTime(compiled);
    }

    // never assume the Rtc was last configured by you, so
    // just clear them to your needed state
    Rtc.Enable32kHzPin(false);
    Rtc.SetSquareWavePin(DS3231SquareWavePin_ModeNone); 

/* comment out on a second run to see that the info is stored long term */
    // Store something in memory on the Eeprom

    // store starting address of string
    RtcEeprom.SetMemory(0, stringAddr);
    // store the string, nothing longer than 32 bytes due to paging
    uint8_t written = RtcEeprom.SetMemory(stringAddr, (const uint8_t*)data, sizeof(data) - 1); // remove the null terminator strings add
    // store the length of the string
    RtcEeprom.SetMemory(1, written); // store the
/* end of comment out section */
}

void loop ()
{
    if (!Rtc.IsDateTimeValid())
    {
        if (Rtc.LastError() != 0)
        {
            // we have a communications error
            // see https://www.arduino.cc/en/Reference/WireEndTransmission for
            // what the number means
            Serial.print("RTC communications error = ");
            Serial.println(Rtc.LastError());
        }
        else
        {
            // Common Cuases:
            //    1) the battery on the device is low or even missing and the power line was disconnected
            Serial.println("RTC lost confidence in the DateTime!");
        }
    }

    RtcDateTime now = Rtc.GetDateTime();

    printDateTime(now);
    Serial.println();

    delay(5000);

    // read data

    // get the offset we stored our data from address zero
    uint8_t address = RtcEeprom.GetMemory(0);
    if (address != stringAddr)
    {
        Serial.print("address didn't match ");
        Serial.println(address);
    }

    {
        // get the size of the data from address 1
        uint8_t count = RtcEeprom.GetMemory(1);
        uint8_t buff[64];

        // get our data from the address with the given size
        uint8_t gotten = RtcEeprom.GetMemory(address, buff, count);

        if (gotten != count ||
            count != sizeof(data) - 1) // remove the extra null terminator strings add
        {
            Serial.print("something didn't match, count = ");
            Serial.print(count, DEC);
            Serial.print(", gotten = ");
            Serial.print(gotten, DEC);
            Serial.println();
        }
        Serial.print("data read (");
        Serial.print(gotten);
        Serial.print(") = \"");
        for (uint8_t ch = 0; ch < gotten; ch++)
        {
            Serial.print((char)buff[ch]);
        }
        Serial.println("\"");
    }

    delay(5000);
}

void printDateTime(const RtcDateTime& dt)
{
    char datestring[20];

    snprintf_P(datestring,
            countof(datestring),
            PSTR("%02u/%02u/%04u %02u:%02u:%02u"),
            dt.Month(),
            dt.Day(),
            dt.Year(),
            dt.Hour(),
            dt.Minute(),
            dt.Second() );
    Serial.print(datestring);
}

实验结果:

4.总结

本篇主要针对DS3231进行讲解RTC库,相对比较简单,基本上看完例子都能熟练使用,读者可以继续自行研究DS3234库,思想非常相似。

玩转 RTC时钟库 DS3231的更多相关文章

  1. 玩转 RTC时钟库 DS1302

    1.前言     最近博主在弄8266编程的时候,偶然发现两个全新时钟模块压仓货: DS1302 DS3231     为了避免资源浪费以及重复编写代码,博主还是抱着尝试的心态去寻找能够同时兼容 DS ...

  2. 树莓派配置RTC时钟(DS3231,I2C接口)

    1.购买基于DS3231的RTC时钟模块,并且支持3.3V的那种 2.配置树莓派 a.打开树莓派的i2c接口 sudo raspi-config -->Interfacing Options - ...

  3. 张高兴的 Windows 10 IoT 开发笔记:RTC 时钟模块 DS3231

    GitHub:https://github.com/ZhangGaoxing/windows-iot-demo/tree/master/DS3231 注意:不包含闹钟设置

  4. S3C2440上RTC时钟驱动开发实例讲解(转载)

    嵌入式Linux之我行,主要讲述和总结了本人在学习嵌入式linux中的每个步骤.一为总结经验,二希望能给想入门嵌入式Linux的朋友提供方便.如有错误之处,谢请指正. 共享资源,欢迎转载:http:/ ...

  5. STM32 RTC时钟的配置

    1) 使能电源时钟和备份区域时钟. RCC_APB1PeriphClockCmd(RCC_APB1Periph_PWR | RCC_APB1Periph_BKP, ENABLE); //使能电源时钟和 ...

  6. 教你在树莓派使用上RTC实时时钟,不用再担心断电后时间归零的问题,开机后自动同步RTC时钟!!!

    准备工作:1.系统建议使用官方最新的镜像文件 2.RTC时钟模块板(I2C接口)建议使用DS1307时钟模块,或者RTC时钟模块RTC时钟模块: 大家知道arduino的电平是5V,树莓派是3.3V, ...

  7. RTC时钟和BKP的配置stm32

    摘自:https://blog.csdn.net/gtkknd/article/details/52233605 RTC和后备寄存器通过一个开关供电,在VDD有效的时候选择VDD供电,否则选择VBAT ...

  8. 玩转u8g2 OLED库 MAX7219_32X8点阵模块

    u8g2 OLED库 + MAX7219_32X8点阵模块 理论基础 玩转u8g2 OLED库,一篇就够 玩转u8g2 OLED库,一篇就够(字数太多 要分篇) 实验内容 Full screen bu ...

  9. 玩转u8g2 OLED库,一篇就够

    授人以鱼不如授人以渔,目的不是为了教会你具体项目开发,而是学会学习的能力.希望大家分享给你周边需要的朋友或者同学,说不定大神成长之路有博哥的奠基石... QQ技术互动交流群:ESP8266&3 ...

随机推荐

  1. 深入理解Three.js中线条Line,LinLoop,LineSegments

    前言 在可视化开发中,无论是2d(canvas)开发还是3d开发,线条的绘制应用都是比较普遍的.比如绘制城市之间的迁徙图,运行轨迹图等.本文主要讲解的是Three.js中三种线条Line,LineLo ...

  2. 夯实Java基础系列4:一文了解final关键字的特性、使用方法,以及实现原理

    目录 final使用 final变量 final修饰基本数据类型变量和引用 final类 final关键字的知识点 final关键字的最佳实践 final的用法 关于空白final final内存分配 ...

  3. ZooKeeper单机服务端的启动源码阅读

    程序的入口QuorumPeerMain public static void main(String[] args) { // QuorumPeerMain main = new QuorumPeer ...

  4. linux 查看文件大小命令

    1.# ls -l (k) ls -l total -rw-r----- root root Oct : catalina.--.log -rw-r----- root root Oct : cata ...

  5. Spring Cloud Gateway 使用

    简介 Spring Cloud Gateway是Spring Cloud官方推出的网关框架,网关作为流量入口,在微服务系统中有着十分重要的作用,常用功能包括:鉴权.路由转发.熔断.限流等. Sprin ...

  6. H5刮刮卡效果

    效果图: 核心就是使用ctx.globalCompositeOperation = 'destination-out'; 全部代码: <!DOCTYPE html> <html> ...

  7. 向net core 3.0进击——Swagger的改变

    目录 前言 引入 测试 小结 前言 十一小长假在不知不觉间可都没了,在这个小尾巴的空隙,把这两天鼓捣的net core 3.0升级过程记录一下,首先还是根据之前的顺序一个个补充进来,先从Swagger ...

  8. 3. Git与TortoiseGit基本操作

    1. GitHub操作 本节先简单介绍 git 的使用与操作, 然后再介绍 TortoiseGit 的使用与操作. 先看看SVN的操作吧, 最常见的是 检出(Check out ...), 更新 (U ...

  9. java架构之路-(面试篇)JVM虚拟机面试大全

    下文连接比较多啊,都是我过整理的博客,很多答案都在博客里有详细说明,理解记忆是最扎实的记忆.而且我的答案不一定是最准确的,但是我的答案不会让你失望,而且几乎每个答案都是问题的扩展答案. 1.JVM内存 ...

  10. Kafka 学习笔记之 Producer/Consumer (Scala)

    既然Kafka使用Scala写的,最近也在慢慢学习Scala的语法,虽然还比较生疏,但是还是想尝试下用Scala实现Producer和Consumer,并且用HashPartitioner实现消息根据 ...