玩轉 RTC時鐘庫 DS3231

1.前言

    接着博主的上一篇 玩轉 RTC時鐘庫 + DS1302,這一篇咱們重點講解DS3231時鐘模塊。沒有看過上一篇的同窗,麻煩先去閱讀一下,由於不少理論基礎已經在上一篇作了詳細講解,這裏再也不重複。編程

  • DS3231

2.DS3231介紹

2.1 爲何使用DS3231

    經常使用的DS1302須要使用外置晶振,且沒有溫度補償,偏差較大。這就促使了更高精度的時鐘芯片 —— DS3231。
    DS3231內置晶振且有內部溫度補償,偏差可作到1分鐘每一年。說白了,精度更高。less

2.2 DS3231概述

  • DS3231是一款高精度I2C實時時鐘器件,具備集成的溫度補償晶體振盪器,集成的晶體振盪器可提升器件的長期精確度;
  • 該器件包含電池輸入端(也就是普通鈕釦電池),斷開主電源時仍可保持精確計時;
  • DS3231的寄存器能保存秒、分、時、星期、日期、月、年和鬧鐘設置等信息(除了和DS1302有同樣的寄存器以外,還額外鬧鐘寄存器,這是能夠作鬧鐘應用的一個關鍵點);
  • 少於31天的月份,可自動調整月末日期,包括閏年補償。時鐘的工做格式爲24小時或帶AM/PM指示的12小時格式;
  • DS3231提供兩個可編程日曆鬧鐘和一路可編程方波輸出;
  • DS3231與單片機經過I2C雙向串行總線傳輸地址與數據;
  • 自帶存儲芯片:AT24C32 EEPROM芯片(存儲容量32K,能夠存很多東西);

2.3 DS3231電路圖&引腳關係

  • 電路圖

image

  • 引腳關係

image

2.4 DS3231寄存器

    跟DS1302同樣,對於DS3231的操做就是操做對應的寄存器,其寄存器對應關係以下:ide

image

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() —— 初始化

函數說明:oop

/**
 * 初始化,會把三個引腳設置爲輸入狀態
 */
void Begin()

2.5.2 LastError() —— 獲取上次錯誤編碼

函數說明:測試

/**
 * 獲取上次錯誤編碼
 * @return 返回錯誤編碼
 */
uint8_t LastError()

注意:ui

  • 錯誤編碼請參考 https://www.arduino.cc/en/Reference/WireEndTransmission

2.5.3 IsDateTimeValid() —— 判斷時間是否有效

函數說明:this

/**
 * 判斷時間是否有效
 * @return false 一般意味着電池沒電或日期和時間從未設置
 *         true  意味時間有效
 */
bool IsDateTimeValid()

2.5.4 GetIsRunning() —— 判斷時鐘是否正在運行

函數說明:編碼

/**
 * 判斷時鐘是否正在運行
 * @return bool
 *        true 時鐘運行
 *        false 時鐘停振,進入低功耗態
 */
bool GetIsRunning()

源碼說明:3d

/**
     * 判斷時鐘是否正在運行
     * @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    鬧鐘一、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);
}

實驗結果:

image

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);
}

實驗結果:

image

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);
}

實驗結果:

image

4.總結

本篇主要針對DS3231進行講解RTC庫,相對比較簡單,基本上看完例子都能熟練使用,讀者能夠繼續自行研究DS3234庫,思想很是類似。

相關文章
相關標籤/搜索