With the clock set and the oscillator adjusted to allow for accurate time keeping, we need to make sure we don’t lose these values when the power is removed from our RTC. Let’s explore the use of the battery backup.
Objectives
- Learn which registers are preserved on power down.
- Enable and disable the battery backup mode.
- Retrieve and display the power failure time stamps
Background
MCP7940 Functionality Overview
I2C Library Functions
I2C Signaling
Microchip MCP7940 RTC Datasheet
Schematic
Education Shield – MCP7940 RTCC Subsystem
Setup
For this module, you’ll need the following equipment:
- I2C and SPI Education Shield or the MCP7940 Breakout Board and the Debounce Button Breakout Board
- Arduino UNO R3
1. Insert a CR2032 button cell battery in the battery holder on the edge of the Education Shield. It should be placed positive side up.
2. Mate the Education Shield with your Arduino UNO R3. If you’re using the MCP7940 Breakout Board with the Debounce Button Breakout Board, connect 5V to 5V, GND to GND, SCL to Analog 5, SDA to Analog 4 and MFP to Digital3 on the MCP7940 and connect 5V to 5V, GND to GND and B1 to Digital8.
3. Connect your Arduino to your USB cable, and use the Arduino IDE to upload the “Bare Minimum” sketch from the Basics section of the Examples.
Preserving Settings on the MCP7940
In the event of a power loss (like the USB cable being removed from your Arduino), the MCP7940 will automatically move to the battery backup, provided by the CR2032 on the Education Shield. In order for this to occur, you have to set the third bit, </span class=”binary”>VBATEN, in the RTCWKDAY register.
A power failure for the RTC is defined as Vcc dropping below VTRIP, listed in the datasheet as 1.5V. While running on the battery, the RTC will draw roughly 925nA so you should be able to sit in a disconnected state for quite some time. The detected power failure will cause a final time stamp to be written to the PWRDN time stamp registers and the chip will enter shutdown. When power is restored, and voltage rises above VTRIP, a time stamp is written to the PWRUP time stamp registers and the PWRFAIL bit is set. As long as that bit is set, the failure time stamps are maintained, but when the bit is cleared, those registers are emptied so that the next failure can be logged.
While in power down, the following items are preserved…
- Timekeeping functionality.
- All alarm configurations.
- Any current alarm output state.
- Oscillator trimming values.
- All RTC register and SRAM contents.
However, no I2C communications will be acknowledged (NACK on ninth bit after address if the RTC address is sent) and square wave output or GPIO functionality on the Multifunction Pin will be discontinued. Essentially, the MFP will continue to function as an alarm interrupt trigger, but not as a representative of the oscillator.
Testing the MCP7940 Battery Backup
The first thing we’ll need to do is make sure we turn on the VBATEN bit in RTCWKDAY. Let’s modify the init_MCP7940 routine to accomplish that. Remember, it’s bit 3, so we’ll need to take care not to change anything else in the register. We keep stuffing things into the initialization function though, so let’s move the actual I2C commands out into their own section, and just keep some easier to read calls to that function in the init routine.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
void init_MCP7940() { byte trimVal = 0x45; // The amount of clock cycles to add/subtract. See datasheet byte twelveHour = 0x00; // 0 = 24 Hour Clock Mode / 1 = 12 Hour Clock Mode byte crsTrim = 0x00; // 0 = Disable Course Trim / 1 = Enable Course Trim byte batEnable = 0x01; // 0 = Disable Battery Backup / 1 = Enable Battery Backup byte startClock = 0x01; // 0 = Start Oscillator / 1 = Stop Oscillator I2c.write (MCP7940_I2C, REG_CONTROL, 0x00); // Ensure the Control register starts as 0x00 I2c.write (MCP7940_I2C, REG_OSCTRIM, trimVal); // Clock Cycle Trim Value - see datasheet ConfigureRegister (REG_RTCHOUR, twelveHour, 6); // 12 Hour Clock: 0 = NO / 1 = YES ConfigureRegister (REG_CONTROL, crsTrim, 2); // Course Trim Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCWKDAY, batEnable, 3); // Battery Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCSEC, startClock, 7); // Start Oscillator: 0 = NO / 1 = YES } void ConfigureRegister(byte registerAddress, byte value, byte positions) { // registerAddress: the single byte register that you will be writing to // value: a single bit of data, 1 or 0, that you want set or cleared in the register // positions: the location of that single bit of data within the register, 0 indexed byte registerValue = 0x00; // Holds each config value when read from the register I2c.read (MCP7940_I2C, registerAddress, 1); registerValue = I2c.receive(); if (value == 0x00) I2c.write (MCP7940_I2C, registerAddress, bitClear (registerValue, positions)); if (value == 0x01) I2c.write (MCP7940_I2C, registerAddress, bitSet (registerValue, positions)); } |
You can see we create the register configuration entry for the battery backup by creating a batEnable variable, and setting it to 1, and then sending it to REG_RTCWKDAY. This will make the RTC aware that the battery is present. Also note that I’ve preserved the oscillator trim settings from the previous module.
The rest of the sketch is just a copy of the SetGetTime sketch developed in the second module, combined with the oscillator trimming feature and the use of a button press to set the time (we don’t want it resetting everytime the serial monitor window opens). Here it is in full…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 |
/* A sketch to test the functionality of the battery backup feature of the MCP7940. Website: https://rheingoldheavy.com/mcp7940-tutorial-04-battery-backup Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20005010F.pdf */ #include "I2C.h" const byte MCP7940_I2C = 0x6F; // I2C Address for the RTC const byte REG_RTCSEC = 0x00; // Register Address: Time Second const byte REG_RTCMIN = 0x01; // Register Address: Time Minute const byte REG_RTCHOUR = 0x02; // Register Address: Time Hour const byte REG_RTCWKDAY = 0x03; // Register Address: Date Day of Week const byte REG_RTCDATE = 0x04; // Register Address: Date Day const byte REG_RTCMTH = 0x05; // Register Address: Date Month const byte REG_RTCYEAR = 0x06; // Register Address: Date Year const byte REG_CONTROL = 0x07; // Register Address: RTC Feature Control const byte REG_OSCTRIM = 0x08; // Register Address: Oscillator Digital Trim byte timeStamp[7]; // Byte array holding a full time stamp. // Array position is the same as the register address. int setTimeButton = 8; // Setting the DATA button as our set time button int oePin = 5; // The Output Enable pin which we'll want to drive high. void setup() { Serial.begin (9600); I2c.begin (); // Initialize the I2C library I2c.pullup (0); // Disable the internal pullup resistors I2c.setSpeed (0); // Enable 100kHz I2C Bus Speed I2c.timeOut (250); // Set a 250ms timeout before the bus resets // Set the pin modes and states pinMode (setTimeButton, INPUT); pinMode (oePin, OUTPUT); digitalWrite (oePin, HIGH); } void loop() { int buttonPressSetTime = 0; while (digitalRead(setTimeButton) == HIGH) { if (buttonPressSetTime == 0) { setTime(); buttonPressSetTime = 1; } } I2c.read(MCP7940_I2C, REG_RTCSEC, 7, timeStamp); Serial.print ("Current Time: "); Serial.print (convertFromBcd(timeStamp[2] & 0x3F)); Serial.print (":"); if (convertFromBcd(timeStamp[1] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(timeStamp[1] & 0x7F)); Serial.print (":"); if (convertFromBcd(timeStamp[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(timeStamp[0] & 0x7F)); delay(1000); } void setTime() { Serial.println ("Setting Time..."); // These are the values that will be written to the MCP7940 timeStamp[0] = convertToBcd( 0); // SECONDS timeStamp[1] = convertToBcd( 52); // MINUTES timeStamp[2] = convertToBcd( 10); // HOURS timeStamp[3] = convertToBcd( 7); // DAY OF WEEK (arbitrary value 1 - 7) timeStamp[4] = convertToBcd( 4); // DAY timeStamp[5] = convertToBcd( 4); // MONTH timeStamp[6] = convertToBcd( 15); // YEAR // Write our time stamp to the time/date registers I2c.write(MCP7940_I2C, REG_RTCSEC, timeStamp, 7); // Initialize our chip with any further configuration data init_MCP7940(); } void init_MCP7940() { byte trimVal = 0x45; // The amount of clock cycles to add/subtract. See datasheet byte twelveHour = 0x00; // 0 = 24 Hour Clock Mode / 1 = 12 Hour Clock Mode byte crsTrim = 0x00; // 0 = Disable Course Trim / 1 = Enable Course Trim byte batEnable = 0x01; // 0 = Disable Battery Backup / 1 = Enable Battery Backup byte startClock = 0x01; // 0 = Start Oscillator / 1 = Stop Oscillator I2c.write (MCP7940_I2C, REG_CONTROL, 0x00); // Ensure the Control register starts as 0x00 I2c.write (MCP7940_I2C, REG_OSCTRIM, trimVal); // Clock Cycle Trim Value - see datasheet ConfigureRegister (REG_RTCHOUR, twelveHour, 6); // 12 Hour Clock: 0 = NO / 1 = YES ConfigureRegister (REG_CONTROL, crsTrim, 2); // Course Trim Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCWKDAY, batEnable, 3); // Battery Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCSEC, startClock, 7); // Start Oscillator: 0 = NO / 1 = YES } void ConfigureRegister(byte registerAddress, byte value, byte positions) { // registerAddress: the single byte register that you will be writing to // value: a single bit of data, 1 or 0, that you want set or cleared in the register // positions: the location of that single bit of data within the register, 0 indexed byte registerValue = 0x00; // Holds each config value when read from the register I2c.read (MCP7940_I2C, registerAddress, 1); registerValue = I2c.receive(); if (value == 0x00) I2c.write (MCP7940_I2C, registerAddress, bitClear (registerValue, positions)); if (value == 0x01) I2c.write (MCP7940_I2C, registerAddress, bitSet (registerValue, positions)); } byte convertToBcd(byte byteDecimal) { return (byteDecimal / 10) << 4 | (byteDecimal % 10); } byte convertFromBcd(byte byteBCD) { byte byteMSB = 0; byte byteLSB = 0; byteMSB = (byteBCD & B11110000) >> 4; byteLSB = (byteBCD & B00001111); return ((byteMSB * 10) + byteLSB); } |
And the test I just ran showed that it worked. Power was gone for about 20 minutes, and when it was restored, it was still completely in synch.
Did The Power Go Out?
If the power goes out but is restored before you notice, how can you tell? The answer is by polling the PWRFAIL bit to see if it reads as a 0 or 1. Combine that with an “if” statement and you’ll be ready to take some form of action in the event of a power drop.
PWRFAIL is bit 4 in RTCWKDAY, so we’ll check that each time through our loop to check it’s state and take action if it reads as a “1”. I’ve created a function called “CheckPowerBit()” to do just that. There’s a small difference here between what we’re doing in this sketch, and what we would do in reality. Since all of our output is confined to the serial monitor, which resets our Arduino each time we open it, we have to make sure that we’re not overwriting any values on reset. If we polled for a power failure directly in the main loop, and included a bit reset with debug output, that would execute when you restore the power, well before you had an opportunity to open up the serial monitor to see it happen. So instead, we’ll just make CheckPowerBit an indicator and save the power failure reset for a button press.
The first function is the CheckPowerBit function, which we call every time we go through the loop.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
void CheckPowerBit() { byte pwrFail = 0; // Retrieve the PWRFAIL bit and mask it off from the rest of the WKDAY data I2c.read(MCP7940_I2C, REG_RTCWKDAY, 1); pwrFail = I2c.receive() & 0x10; if (pwrFail) { Serial.println ("Power Failure Detected"); pinMode (clockPin, OUTPUT); pinMode (dataPin, OUTPUT); pinMode (latchPin, OUTPUT); digitalWrite (oePin, LOW); digitalWrite (latchPin, LOW); shiftOut (dataPin, clockPin, MSBFIRST, LEDPattern); LEDPattern = ~LEDPattern; digitalWrite (latchPin, HIGH); pinMode (clockPin, INPUT); pinMode (dataPin, INPUT); pinMode (latchPin, INPUT); } } |
We immediately check the bit in the REG_RTCWKDAY register, and isolate the bit with a little bit masking. If the bit is set, we print to the serial monitor, and then start the LED’s blinking. Pin definitions, states and the LED pattern are all set in the declarations and setup as you would expect (you’ll see them in the full code below, but you should be familiar with how this works by now).
The next function is ResetPowerFail, which we’ll tie to the LATCH button to actually reset the power failure subsystem and report the time stamps to the serial monitor. This only happens on button press, which will allow us to track the output in the serial monitor.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 |
void ResetPowerFail() { byte pwrDn[2]; byte pwrUp[2]; // Grab the power down time stamp I2c.read(MCP7940_I2C, REG_PWRDNMIN, 2); pwrDn[0] = I2c.receive(); pwrDn[1] = I2c.receive(); // Grab the power up time stamp I2c.read(MCP7940_I2C, REG_PWRUPMIN, 2); pwrUp[0] = I2c.receive(); pwrUp[1] = I2c.receive(); // Clear the PWRFAIL bit ConfigureRegister (REG_RTCWKDAY, 0x00, 4); // Print the time stamps... Serial.print ("*-*-*-*-*-*-*-*-*-*-*-* Power Failed: "); if (convertFromBcd(pwrDn[1] & 0x3F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(pwrDn[1] & 0x3F)); Serial.print (":"); if (convertFromBcd(pwrDn[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(pwrDn[0] & 0x7F)); Serial.print ("*-*-*-*-*-*-*-*-*-*-*-* Power Restored: "); if (convertFromBcd(pwrUp[1] & 0x3F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(pwrUp[1] & 0x3F)); Serial.print (":"); if (convertFromBcd(pwrUp[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(pwrUp[0] & 0x7F)); // Turn off the LEDs pinMode (clockPin, OUTPUT); pinMode (dataPin, OUTPUT); pinMode (latchPin, OUTPUT); digitalWrite (oePin, LOW); digitalWrite (latchPin, LOW); shiftOut (dataPin, clockPin, MSBFIRST, 0x00); digitalWrite (latchPin, HIGH); pinMode (clockPin, INPUT); pinMode (dataPin, INPUT); pinMode (latchPin, INPUT); } |
We retrieve the power down and power up time stamps from their registers, and write them to two byte arrays (could have used one with four positions, but this made for easier reading). Then we clear the power failure bit that was set by the RTC when the USB cable was pulled by using the “Configure Register” function normally used in the initialization. The rest is serial output and turning off the LEDs again.
Of course we have to tie this all into the loop, so as you would expect, there is a CheckPowerBit call in it now, and a button press check routine for the PowerFailReset call.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
void loop() { int buttonPressSetTime = 0; while (digitalRead(setTimeButton) == HIGH) { if (buttonPressSetTime == 0) { setTime(); buttonPressSetTime = 1; } } int buttonPressResetPwr = 0; while (digitalRead(rstPwrButton) == HIGH) { if (buttonPressResetPwr == 0) { ResetPowerFail(); buttonPressResetPwr = 1; } } CheckPowerBit(); I2c.read(MCP7940_I2C, REG_RTCSEC, 7, timeStamp); Serial.print ("Current Time: "); Serial.print (convertFromBcd(timeStamp[2] & 0x3F)); Serial.print (":"); if (convertFromBcd(timeStamp[1] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(timeStamp[1] & 0x7F)); Serial.print (":"); if (convertFromBcd(timeStamp[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(timeStamp[0] & 0x7F)); Serial.println (); delay(1000); } |
And pulling it all together into the final sketch…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 |
/* A sketch to test the functionality of the battery backup feature of the MCP7940. Website: https://rheingoldheavy.com/mcp7940-tutorial-04-battery-backup Datasheet: http://ww1.microchip.com/downloads/en/DeviceDoc/20005010F.pdf */ #include "I2C.h" const byte MCP7940_I2C = 0x6F; // I2C Address for the RTC const byte REG_RTCSEC = 0x00; // Register Address: Time Second const byte REG_RTCMIN = 0x01; // Register Address: Time Minute const byte REG_RTCHOUR = 0x02; // Register Address: Time Hour const byte REG_RTCWKDAY = 0x03; // Register Address: Date Day of Week const byte REG_RTCDATE = 0x04; // Register Address: Date Day const byte REG_RTCMTH = 0x05; // Register Address: Date Month const byte REG_RTCYEAR = 0x06; // Register Address: Date Year const byte REG_CONTROL = 0x07; // Register Address: RTC Feature Control const byte REG_OSCTRIM = 0x08; // Register Address: Oscillator Digital Trim const byte REG_PWRDNMIN = 0x18; // Register Address: Power Failure Time Minute const byte REG_PWRDNHR = 0x19; // Register Address: Power Failure Time Hour const byte REG_PWRUPMIN = 0x1C; // Register Address: Power Restore Time Minute const byte REG_PWRUPHR = 0x1D; // Register Address: Power Restore Time Hour byte timeStamp[7]; // Byte array holding a full time stamp. // Array position is the same as the register address. int setTimeButton = 8; // Setting the DATA button as our set time button int rstPwrButton = 7; // Setting the LATCH button to check for power failure int oePin = 5; // The Output Enable pin which we'll want to drive high. int clockPin = 6; // Setting up shift register pins int dataPin = 8; // Setting up shift register pins int latchPin = 7; // Setting up shift register pins int LEDPattern = 0xAA; // An LED pattern to display after power failure void setup() { Serial.begin (9600); I2c.begin (); // Initialize the I2C library I2c.pullup (0); // Disable the internal pullup resistors I2c.setSpeed (0); // Enable 100kHz I2C Bus Speed I2c.timeOut (250); // Set a 250ms timeout before the bus resets pinMode (setTimeButton, INPUT); pinMode (rstPwrButton, INPUT); pinMode (clockPin, INPUT); pinMode (oePin, OUTPUT); digitalWrite (oePin, HIGH); } void loop() { int buttonPressSetTime = 0; while (digitalRead(setTimeButton) == HIGH) { if (buttonPressSetTime == 0) { setTime(); buttonPressSetTime = 1; } } int buttonPressResetPwr = 0; while (digitalRead(rstPwrButton) == HIGH) { if (buttonPressResetPwr == 0) { ResetPowerFail(); buttonPressResetPwr = 1; } } CheckPowerBit(); I2c.read(MCP7940_I2C, REG_RTCSEC, 7, timeStamp); Serial.print ("Current Time: "); Serial.print (convertFromBcd(timeStamp[2] & 0x3F)); Serial.print (":"); if (convertFromBcd(timeStamp[1] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(timeStamp[1] & 0x7F)); Serial.print (":"); if (convertFromBcd(timeStamp[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(timeStamp[0] & 0x7F)); Serial.println (); delay(1000); } void setTime() { Serial.println ("Setting Time..."); // These are the values that will be written to the MCP7940 timeStamp[0] = convertToBcd( 0); // SECONDS timeStamp[1] = convertToBcd( 52); // MINUTES timeStamp[2] = convertToBcd( 10); // HOURS timeStamp[3] = convertToBcd( 7); // DAY OF WEEK (arbitrary value 1 - 7) timeStamp[4] = convertToBcd( 4); // DAY timeStamp[5] = convertToBcd( 4); // MONTH timeStamp[6] = convertToBcd( 15); // YEAR // Write our time stamp to the time/date registers I2c.write(MCP7940_I2C, REG_RTCSEC, timeStamp, 7); // Initialize our chip with any further configuration data init_MCP7940(); } void CheckPowerBit() { byte pwrFail = 0; // Retrieve the PWRFAIL bit and mask it off from the rest of the WKDAY data I2c.read(MCP7940_I2C, REG_RTCWKDAY, 1); pwrFail = I2c.receive() & 0x10; if (pwrFail) { Serial.println ("Power Failure Detected"); pinMode (clockPin, OUTPUT); pinMode (dataPin, OUTPUT); pinMode (latchPin, OUTPUT); digitalWrite (oePin, LOW); digitalWrite (latchPin, LOW); shiftOut (dataPin, clockPin, MSBFIRST, LEDPattern); LEDPattern = ~LEDPattern; digitalWrite (latchPin, HIGH); pinMode (clockPin, INPUT); pinMode (dataPin, INPUT); pinMode (latchPin, INPUT); } } void ResetPowerFail() { byte pwrDn[2]; byte pwrUp[2]; // Grab the power down time stamp I2c.read(MCP7940_I2C, REG_PWRDNMIN, 2); pwrDn[0] = I2c.receive(); pwrDn[1] = I2c.receive(); // Grab the power up time stamp I2c.read(MCP7940_I2C, REG_PWRUPMIN, 2); pwrUp[0] = I2c.receive(); pwrUp[1] = I2c.receive(); // Clear the PWRFAIL bit ConfigureRegister (REG_RTCWKDAY, 0x00, 4); // Print the time stamps... Serial.print ("*-*-*-*-*-*-*-*-*-*-*-* Power Failed: "); if (convertFromBcd(pwrDn[1] & 0x3F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(pwrDn[1] & 0x3F)); Serial.print (":"); if (convertFromBcd(pwrDn[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(pwrDn[0] & 0x7F)); Serial.print ("*-*-*-*-*-*-*-*-*-*-*-* Power Restored: "); if (convertFromBcd(pwrUp[1] & 0x3F) / 10 == 0) Serial.print ("0"); Serial.print (convertFromBcd(pwrUp[1] & 0x3F)); Serial.print (":"); if (convertFromBcd(pwrUp[0] & 0x7F) / 10 == 0) Serial.print ("0"); Serial.println (convertFromBcd(pwrUp[0] & 0x7F)); // Turn off the LEDs pinMode (clockPin, OUTPUT); pinMode (dataPin, OUTPUT); pinMode (latchPin, OUTPUT); digitalWrite (oePin, LOW); digitalWrite (latchPin, LOW); shiftOut (dataPin, clockPin, MSBFIRST, 0x00); digitalWrite (latchPin, HIGH); pinMode (clockPin, INPUT); pinMode (dataPin, INPUT); pinMode (latchPin, INPUT); } void init_MCP7940() { byte trimVal = 0x45; // The amount of clock cycles to add/subtract. See datasheet byte twelveHour = 0x00; // 0 = 24 Hour Clock Mode / 1 = 12 Hour Clock Mode byte crsTrim = 0x00; // 0 = Disable Course Trim / 1 = Enable Course Trim byte batEnable = 0x01; // 0 = Disable Battery Backup / 1 = Enable Battery Backup byte startClock = 0x01; // 0 = Start Oscillator / 1 = Stop Oscillator I2c.write (MCP7940_I2C, REG_CONTROL, 0x00); // Ensure the Control register starts as 0x00 I2c.write (MCP7940_I2C, REG_OSCTRIM, trimVal); // Clock Cycle Trim Value - see datasheet ConfigureRegister (REG_RTCHOUR, twelveHour, 6); // 12 Hour Clock: 0 = NO / 1 = YES ConfigureRegister (REG_CONTROL, crsTrim, 2); // Course Trim Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCWKDAY, batEnable, 3); // Battery Enable: 0 = NO / 1 = YES ConfigureRegister (REG_RTCSEC, startClock, 7); // Start Oscillator: 0 = NO / 1 = YES } void ConfigureRegister(byte registerAddress, byte value, byte positions) { // registerAddress: the single byte register that you will be writing to // value: a single bit of data, 1 or 0, that you want set or cleared in the register // positions: the location of that single bit of data within the register, 0 indexed byte registerValue = 0x00; // Holds each config value when read from the register I2c.read (MCP7940_I2C, registerAddress, 1); registerValue = I2c.receive(); if (value == 0x00) I2c.write (MCP7940_I2C, registerAddress, bitClear (registerValue, positions)); if (value == 0x01) I2c.write (MCP7940_I2C, registerAddress, bitSet (registerValue, positions)); } byte convertToBcd(byte byteDecimal) { return (byteDecimal / 10) << 4 | (byteDecimal % 10); } byte convertFromBcd(byte byteBCD) { byte byteMSB = 0; byte byteLSB = 0; byteMSB = (byteBCD & B11110000) >> 4; byteLSB = (byteBCD & B00001111); return ((byteMSB * 10) + byteLSB); } |
The test method is as follows…
- Press the DATA button to set the time with the SetTime function.
- Verify the clock is running in the serial monitor.
- Close the serial monitor and pull the USB cable.
- Wait a while, then plug your Arduino back in. You should see the LEDs dancing.
- Open the serial monitor, and you should see the correct time with the power failure message.
- Press the LATCH button to display the power failure time stamps and clear the power failure bit.