接着博主的上一篇 玩轉 RTC時鐘庫 + DS1302,這一篇咱們重點講解DS3231時鐘模塊。沒有看過上一篇的同窗,麻煩先去閱讀一下,由於不少理論基礎已經在上一篇作了詳細講解,這裏再也不重複。編程
經常使用的DS1302須要使用外置晶振,且沒有溫度補償,偏差較大。這就促使了更高精度的時鐘芯片 —— DS3231。
DS3231內置晶振且有內部溫度補償,偏差可作到1分鐘每一年。說白了,精度更高。less
跟DS1302同樣,對於DS3231的操做就是操做對應的寄存器,其寄存器對應關係以下:ide
老規矩,先看看源碼,博主在源碼中加入了部分代碼註釋:函數
#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__
函數說明:oop
/** * 初始化,會把三個引腳設置爲輸入狀態 */ void Begin()
函數說明:測試
/** * 獲取上次錯誤編碼 * @return 返回錯誤編碼 */ uint8_t LastError()
注意:ui
函數說明:this
/** * 判斷時間是否有效 * @return false 一般意味着電池沒電或日期和時間從未設置 * true 意味時間有效 */ bool IsDateTimeValid()
函數說明:編碼
/** * 判斷時鐘是否正在運行 * @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)); }
函數說明:
/** * 設置時鐘是否運行 * @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); }
函數說明:
/** * 設置日期時間 * @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(); }
函數說明:
/** * 獲取日期時間 * @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); }
函數說明:
/** * 使能32kHz引腳輸出 * @param enable true 使能 * false 禁止 */ void Enable32kHzPin(bool enable)
函數說明:
/** * 設置方波輸出 * @param DS3231SquareWavePinMode 方波引腳模式 */ void SetSquareWavePin(DS3231SquareWavePinMode pinMode)
DS3231SquareWavePinMode 參數說明:
源碼說明:
/** * 設置方波輸出 */ 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); }
函數說明:
/** * 設置方波時鐘頻率 * @param DS3231SquareWaveClock 方波時鐘頻率 */ void SetSquareWavePinClockFrequency(DS3231SquareWaveClock freq)
DS3231SquareWaveClock 參數說明:
函數說明:
/** * 設置鬧鐘1 * @param DS3231AlarmOne 鬧鐘1 */ void SetAlarmOne(const DS3231AlarmOne& alarm)
注意點:
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)
函數說明:
/** * 獲取鬧鐘1 * @return DS3231AlarmOne 鬧鐘1 */ DS3231AlarmOne GetAlarmOne()
函數說明:
/** * 設置鬧鐘2 * @param DS3231AlarmTwo 鬧鐘2 */ void SetAlarmTwo(const DS3231AlarmTwo& alarm)
注意點:
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 * @return DS3231AlarmTwo 鬧鐘2 */ DS3231AlarmTwo GetAlarmTwo()
函數說明:
/** * 處理鬧鐘觸發 * @return DS3231AlarmFlag * --- DS3231AlarmFlag_Alarm1 鬧鐘1觸發 * --- DS3231AlarmFlag_Alarm2 鬧鐘2觸發 * --- DS3231AlarmFlag_AlarmBoth 鬧鐘一、2觸發 */ DS3231AlarmFlag LatchAlarmsTriggeredFlags()
注意點:
前面說到了,DS3231時鐘模塊集成了AT24c32 eeprom存儲芯片,若是咱們須要用到存儲數據功能,就得引入 EepromAt24c32庫。那麼,咱們來看看該庫有什麼方法。
函數說明:
/** * 初始化引腳 */ void Begin()
函數說明:
/** * 獲取上次錯誤編碼 * @return 返回錯誤編碼 */ uint8_t LastError()
注意:
函數說明:
/*** * 寫入數據 * @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)
函數說明:
/*** * 讀取數據 * @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)
DS3231採用I2C總線方式,SCLK、SDA。
測試用例分爲三個:
實驗內容
實驗器材
引腳鏈接
模塊引腳 | 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); }
實驗結果:
實驗內容
實驗器材
引腳鏈接
模塊引腳 | 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); }
實驗結果:
實驗內容
實驗器材
引腳鏈接
模塊引腳 | 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); }
實驗結果:
本篇主要針對DS3231進行講解RTC庫,相對比較簡單,基本上看完例子都能熟練使用,讀者能夠繼續自行研究DS3234庫,思想很是類似。