The USC Andrew and Erna Viterbi School of Engineering USC Ming Hsieh Department of Electrical Engineering University of Southern California

EE 459Lx - Embedded System Design Laboratory

Notes on the Freescale MC908JL16's I2C Interface

by
Allan Weber

USC Dept. of Electrical Engineering - Systems
May 22, 2012 (minor edits 12/3/14)

The Freescale MC908JL16 microcontroller includes an I2C or IIC (or MMIIC) interface. This page discusses several issues that we have encountered while trying to use it in student projects. Most of what I've learned about the operation of the JL16 I2C interface was obtained by inserting numerous instructions in our I2C code to copy the contents of the MMSR register to one of the JL16's I/O ports and then staring at the state of various bits along with the I2C clock and data on a oscilliscope that had I2C triggering capability.

If anyone has suggestions on ways to solve the problems discussed below, or better explanations of what is happening, please let me know at the email address above.

1. Dummy data required at end of writes

The JL16 has an oddity in its write sequence that needs to be observed if you want the write operation to finish properly. After the last data byte is loaded into the MMDTR register it will later be moved by the hardware into the output circuit so the bits can be shifted out the data line. All eight data bits, and the accompanying clock pulses, go out fine. However after the eighth bit, the JL16 will not generate a ninth clock pulse to read the acknowlege bit from the slave device unless there is some data in the MMDTR register. Loading MMDTR with dummy data at the end of the write operation is shown in Freescale's documentation without much of a justification as to why it's needed. Worse than not generating the ACK clock, without the dummy data the I2C clock and data lines will be left in the busy state (pulled low) preventing any more I2C transactions. This happens even though the program resets the MIMCR_MMAST bit to zero which should generate a STOP condition.

After the dummy data is loaded into the MMDTR, the program can then go and set the MMAST bit to zero. This will cause a STOP signal to be generated after the data in the output circuit (the real last data byte) is transmitted, and then stop sending any more data. Doing this seems to ensure that the dummy data never actuallly gets transmitted.

The following scope pictures show these effects happening. In these examples two bytes of data (0x41, 0x42) are being written starting at location 0x0190 in an EEPROM at I2C device address 0xA0.

Fig. 1a: Writing two bytes with dummy data in the MMDTR after the second byte. The JL16 generates a clock for the ACK/NAK response and a STOP condition.
Fig. 1b: Writing two bytes with no dummy data in the MMDTR after the second byte. No clock is generated for the ACK/NAK, no STOP and the I2C clock and data lines are left in the busy state (low).
Fig. 2a: Expanded view of the last byte being transmitted in Fig. 1a.
Fig. 2b: Expanded view of the last byte being transmitted in Fig. 2a. Note absense of a ninth clock pulse after the 0x42 is sent.

2. ACK or NAK?

An issue for our student projects was how to check for ACK/NAK after a write transfer. The only Freescale application note on HC08 I2C (App Note AN3291: "How to Use IIC Module on M68HC08, HCS08, and HCS12 MCUs") gives examples of reading and writing for the AP64 family member but nothing about watching for a NAK. In there, and in other sources, the general method for writing data is to loop doing something like this.

    while (!(MMSR_MMTXBE));     // wait till data transferred
    while (MMSR_MMRXAK);        // wait for ACK from slave
       //-----Slave ACK occurred------------
    MMDTR = wr_data;            // write data into EEPROM

This seemed to imply that the second "while" loop would cause the program to wait until an ACK arrives. After looking at the status bits on a scope, it's obvious that on the JL16 (and maybe the AP64) this doesn't work at all. The MMSR_MMRXAK bit shows the status of whether an ACK or NAK was received for the previous byte transmitted and will not change again until the next time an ACK or NAK is read in. If the last byte caused an ACK to be received, MMRXAK will be a zero all during the transmission of the next byte, and the program will fall through the "wait for ACK from slave" loop immediately. Looping on a test of the MMRXAK bit doesn't accomplish anything. It also will cause the code to get stuck if a NAK is received.

This is shown in Fig. 3. The green trace is the MMRXAK bit. It goes to zero after the ACK is received for transmission of the address byte and never changes after that since ACKs are received for the following bytes.

Fig. 3: Writing two bytes to an EEPROM. State of MMRXAK bit is shown in the green trace.

The bigger problem is that it doesn't appear that there is any status bit designed to tell you when the state of MMSR_MMRXAK is valid for the byte just transmitted so you have no clue as to when to test it for the last ACK/NAK received. The closest I've been able to find for determining this is the MMSR_MMTXBE (Transmit Buffer Empty) bit which is shown in the purple trace in Fig. 3. The MMRXAK bit changes to relect the ACK/NAK situation at the roughly the same time the MMTXBE bit goes high to show the transmit buffer is empty. MMRXAK appears to always be in the correct state by the time MMTXBE goes high. As a result we've changed the code in our I2C routine to this.

    while (!(MMSR_MMTXBE));     // Wait for TX buffer empty
    if (MMSR_MMRXAK) {          // Check for ACK/NAK flag
       status = 1;              // Got NAK, set status and
       goto nakstop;            // go send a STOP
    }
    MMDTR = *data++;            // Put next byte in data register

This has been confirmed, more or less, to do what we want in that it can detect an NAK resulting from something like an incorrect I2C device address. However, there is still one problem with the NAK stuff as described below.

3. Determining ACK/NAK of last byte

The I2C hardware apparently contains two buffers for data to be transmitted, one that the program moves data into (MMDTR), and a second that is a shift register for sending the bits. Once the shift register has sent the eighth bit, any data in MMDTR is moved there and this causes the MMTXBE bit to go high. The result is that the status bits are telling you stuff about older data. If you use the code above to check for ACK/NAK, when MMTXBE goes high (transmit buffer empty) the MMRXAK bit tell you the status of byte 'k-1', while byte 'k' has now been moved from MMDTR to the shift register and MMDTR is now ready to be loaded with byte 'k+1'.

For the most part this works fine, but it does appear to create a problem at the end of a data transfer in that there is no way to determine the ACK/NAK of the last byte. This is shown in Fig. 4 below.

Fig. 4: Writing two bytes to an EEPROM. ACKs can be checked for all but the last byte transmitted.

In this example we are sending an address byte and four data bytes to the device. At some point MMTXBE will go high and we can check the ACK/NAK status of the second data byte (0x90) and then load the fourth byte (0xA2) in MMDTR. At the same time the hardware starts sending the third data byte (0x41).

The next time MMTXBE goes high we can check the status of third byte (0x41), load MMDTR with the dummy data, and the hardware starts sending the fourth data byte (0x42). If we were to wait again for MMTXBE to go high in order to check the ACK/NAK status of the fourth data byte, the dummy data will get moved into the shift register and be sent out, which we don't want. Instead we set MIMCR_MMAST to zero to send a STOP condition after the current byte (0x42) is sent and the dummy data never gets transmitted. The problem here is that since we aren't going to wait for MMTXBE to go high there is no way to have the program wait and check the ACK/NAK status for the last byte. MMRXAK probably will contain the correct status value, but how is the program supposed to know when to check that bit?