MCP7940 and AT30TS750A Tutorial: Time and Temp and Error Checking
The I2C and SPI Education Shield features two devices on the I2C bus, the MCP7940 and AT30TS750A chips, a real time clock and temperature sensor respectively. Let’s combine the two and retrieve the temperature along side a time stamp.
Learn how to check for and handle errors.
Understand the error codes that can be returned.
Define the registers necessary to utilize both modules.
Retrieve data from both devices in the shortest time possible.
Our architecture presents a single master on the I2C bus and multiple I2C devices, that means we’ll only be able to interact with a single device at a time, but nothing prevents us from interacting with the two devices rapidly in sequence. Provided the bus is allowed to return to the free state in between interactions, data can flow back and forth without issue from different sensors to the master.
Fortunately, the I2C library (and the original Wire library as well) handle establishing the free state on our behalf, meaning we don’t have to do anything very special in our code to rapidly switch between the two. The only thing we have to take into account, is the possibility that an error might occur while dealing with one device, causing the system to lock up.
In the I2C library, each read or write command will return either a 0 telling us that the interaction was successful, or an error code generated by the Atmel TWI library that manages the fundamental interactions. The library checks each step of the process, verifying that no error has occurred. Each address transmission, each ACK/NACK, each data byte, everything is verified for status before moving on to the next step. If an error is returned, or a timeout occurs, the reason for that error is returned from the function and can be used.
The library author, Wayne Truchsess, provides a description for the error codes he assigns to various time out results, in a comment block within the body of the code. “MR” stands for “Master Receiver” (the master is executing a read command), “MT” stands for “Master Transmitter” (the master is executing a write command).
Function executed with no errors
Timed out waiting for successful completion of a Start bit
Timed out waiting for ACK/NACK while addressing slave in transmit mode (MT)
Timed out waiting for ACK/NACK while sending data to the slave
Timed out waiting for successful completion of a Repeated Start
Timed out waiting for ACK/NACK while addressing slave in receiver mode (MR)
Timed out waiting for ACK/NACK while receiving data from the slave
Timed out waiting for successful completion of the Stop bit
0x08 - 0xFF
See datasheet for exact meaning
By now, the descriptions of those first seven errors should be self explanatory, however if you need to review, read through the module describing I2C Signaling. In the event an error is detected, the library will immediately reinitialize the I2C bus using the following C code:
TWCR=_BV(TWEN)|_BV(TWEA); All that’s doing is setting the Two Wire Enable and Two Wire Enable Acknowledge bits in the Two Wire Control Register.
The first seven error codes were established by the library author, however the remaining error codes, 0x08 – 0xFF cover a large range of possible values. Here is a table describing them as well…
TWI Status Code
MT Error - Address + Write transmitted and NACK received
MT Error - Data byte has been transmitted and NACK received
MR Error - Address + Read transmitted and NACK received
MR Error - Data byte has been received and NACK transmitted
These are the possible return values that could appear not originally listed by the author. The reason for the large gaps in the sequences is due to the AVR status codes containing values related to operating the microcontroller as a bus slave, rather than as a bus master, and that is outside the scope of the libraries function.
Arduino I2C Error Handling
When we read or write to our I2C devices, we’ll need to start capturing the status code returned so that we are aware of whether there are errors occurring on the bus. It doesn’t have to be very elaborate, but it does involve making our reads and writes a little more complicated. Here is a small sample…
Basic I2C Error Checking
In here, we create a single byte value to hold the status result of our I2C interaction, then load it with the result of an I2C Read command, and finally check to see if it’s some value other than 0. If it is, then we’ll run a routine called I2CError in which we’ll take some kind of action, based on the errorStatus we’re sending it. If you want to be very granular in your handling, you can also pass the device you were talking to and the register you were working with to the error handler as well, but in this case, we’ll keep it simple and just send the error code.
Basic I2C Error Checking
Serial.print("Error Code = 0x");
Serial.print(" Error Count = ");
In this function, we receive the error code generated by the original I2C command, increment an error counter by 1, and just display the error that occurred as well as how many errors have been generated since the last reset. If you were sending the I2C address and register values those could be displayed as well, and if necessary, a chip initialization could be performed, or some configuration register reset, all depending on the values that were sent. Occasionally, when doing initial troubleshooting, I also include a counter for every time an I2C command is executed, then do a quick calculation to determine how many errors are being generated per million executions, essentially the PPM error value. That formula would look like this
Depending on how many I2C commands you’re checking, execCount could grow very large very quickly, so it would most likely need to be an unsigned long variable, and if it grows too fast, you might need to specify that the execution count be reset after every error and do the math yourself by adding up all the execution counts that are listed, in order to prevent an overflow.
As with all of our sketches, we need to define our registers for ease of use in our code, and establish some global variables that we can access in different functions. For the MCP7940, we’ll need the I2C address and time stamp registers, and for the AT30TS750A we’ll need the I2C Address and the temperature register.
Before working with the code below, review the datasheets and look at your previous sketches to answer the following questions…
Which configuration registers will be needed for the AT30TS750A?
Hover mouse to read answer: At a minimum, we will want to specify the temperature resolution in the control register, 0x01. If you wanted to use temperature alarms as well, you would configure the lower limit and upper limit registers, 0x02 and 0x03 as well as setting the alarm interrupt type in the configuration register.
Which configuration registers will be needed for the MCP7940?
Hover mouse to read answer: The MCP7940 has several configuration areas. We’ll need to work with the RTCHOUR register to configure 12/24 mode, CONTROL to specify course trimming, OSCTRIM to set our trim value, and RTCSEC to turn the oscillator on. Additional registers would be necessary to configure the alarm or backup battery values as well.
Time and Temp Declarations
Time and Temp Declarations
constbyteAT30TS750_I2C=0x48;// I2C Address for the temperature sensor
constbyteAT30TS750_REG_TEMP=0x00;// Register Address: Temperature Value
constbyteAT30TS750_REG_CONFIG=0x01;// Register Address: Temperature sensor configuration
constbyteMCP7940_I2C=0x6F;// I2C Address for the RTC
constbyteMCP7940_REG_RTCSEC=0x00;// Register Address: Time Second
constbyteMCP7940_REG_RTCMIN=0x01;// Register Address: Time Minute
constbyteMCP7940_REG_RTCHOUR=0x02;// Register Address: Time Hour
constbyteMCP7940_REG_RTCWKDAY=0x03;// Register Address: Date Day of Week
constbyteMCP7940_REG_RTCDATE=0x04;// Register Address: Date Day
constbyteMCP7940_REG_RTCMTH=0x05;// Register Address: Date Month
constbyteMCP7940_REG_RTCYEAR=0x06;// Register Address: Date Year
constbyteMCP7940_REG_CONTROL=0x07;// Register Address: RTC Feature Control
constbyteMCP7940_REG_OSCTRIM=0x08;// Register Address: Oscillator Digital Trim
bytetimeStamp;// Byte array holding a full time stamp.
unsignedinterrorCount=0;// Large integer to hold a running error count
byteerrorStatus=0;// Byte value to hold any returned I2C error code
All the I2C addresses and registers for our two I2C chips are now loaded, as well as the byte array to hold our time stamp values, and two variables to assist us with error checking. The errorCount variable is an unsigned integer, just in case something goes terribly wrong, we wont overflow the variable too quickly. The names of the register variables have had the name of the chip added because REG_CONFIG is no longer sufficiently explicit to tell us which chip we’re configuring any more, so it has to be AT30TS750_REG_CONFIG.
Time and Temp Setup
Time and Temp Setup
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
// Initialize the RTC configuration
// Initialize the Temp Sensor configuration
// These are the values that will be written to the MCP7940
timeStamp=convertToBcd(6);// DAY OF WEEK (arbitrary value 1 - 7)
// Write our time stamp to the time/date registers
The setup looks very similar to any that we’ve performed so far, in that we’re specifying our I2C configuration, initializing the MCP7940 and AT30TS750A chips in turn, and finally setting our RTC with a start time. Notice though, that we’ve begun our error handling by saving the output from the I2c.write() command and checking to make sure it’s a zero value.
There is a lot of stuff happening here, but it’s only a combination of the temperature configuration and the RTC configuration from previous sketches. The temperature configuration is merely setting the resolution to 9 bits, and the time configuration is setting the 12/24 hour mode, course trim option, oscillator trim value and enabling the battery backup bit, wrapping it all up by starting the oscillator. As with the function when we set the time back in the Setup function, we’ve added error handling wrappers around each of the I2C commands to make sure we’ll know if something happens.
In the loop, the we grab the current time stamp and display it to the serial monitor, and we grab the current temperature and display that as well. Everything is neatly wrapped in error handling and the little delay at the bottom is just so we don’t spam the serial monitor with output, although you can eliminate it entirely if you want.
The only thing not shown in the code breakouts is the BCD converters that existed in all the previous sketches and are included here, in the full sketch…
Time and Temp
A sketch to interact with both the MCP7940 and AT30TS750A real time clock and
temperature sensor subsystems on the Rheingold Heavy I2C and SPI Education Shield.