5
\$\begingroup\$

I'm using the MCP23017 16-bit I2C GPIO Expander in combination with the microcontroller PIC18F47J53. Both products are from Microchip.

Datasheet MCP23017

Datasheet PIC18F47J53

I use MCC (MPLab Code Configurator) to configure the hardware modules in the microcontroller. For I2C, I'm using MSSP2 in Master mode at 400kHz, not using interrupts. SCL is on pin D0 and SDA is on pin D1.

I configured both PortA and PortB on the MCP23017 as inputs using weak pull-up resistors. I then read both GPIOA and GPIOB, which are connected with simple push buttons to ground, on regular basis.

All works fine! But the weird thing is that after receiving a byte from the MCP23017, I need to send a NOT Acknowledge (NACK) instead of an Acknowledge (ACK) before sending my Stop sequence, otherwise my SDA line seems to be pulled down by the MCP23017 locking my I2C bus for ever.

The specifications of the GPIO expander don't mention anything about having to use a NACK (I'm using Byte mode): 3.2.2.2 I2C Read Operation

Although my application works perfectly, I wonder if somebody has an explanation for this?

For completeness, here is my code - you find the NACK at then end - I have marked it with "THIS NEEDS TO BE NACK OR IT WON'T WORK":

/*
 *    A library for controlling MCP23017/MCP23009 via I2C
 *    Copyright (c) Microchip MASTERs Conference 20047 SER2
 *    Practical I2C - Introduction, Implementation and Troubleshooting
 */

#include "xc.h"
#include "stdbool.h"
#include "stdint.h"
#include "I2C_GPIO.h"

void InitGPIO(void)
{
    SSP2CON1bits.SSPEN = 1;

    WriteByteGPIO(0b01001110, 0x00, 0xFF);          // IODIRA = all inputs
    WriteByteGPIO(0b01001110, 0x01, 0xFF);          // IODIRB = all inputs
    WriteByteGPIO(0b01001110, 0x0C, 0xFF);          // IODIRA = all pull ups
    WriteByteGPIO(0b01001110, 0x0D, 0xFF);          // IODIRB = all pull ups
}

void WriteByteGPIO(char SlaveAddr, char RegAddr, char Data)
{
    // Start
    SSP2CON2bits.SEN = 1;       // Initiate Start condition
    while (SSP2CON2bits.SEN);   // Wait for Start condition to complete
    PIR3bits.SSP2IF = 0;        // Clear SSP Interrupt Flag
    
    // Slave Address
    SSP2BUF = SlaveAddr;         // Send the Slave Address and R/W bit kept to 0
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return;                     // Exit
    }

    // Register Address
    SSP2BUF = RegAddr;           // Send the Register Address
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return;                     // Exit
    }
    
    // Send Data
    SSP2BUF = Data;             // Send the Data
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return;                     // Exit
    }
    
    // Stop
    SSP2CON2bits.PEN = 1;       // Initiate Stop condition
    while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
}

char ReadByteGPIO(char SlaveAddr, char RegAddr)
{
    char TempData;
    
    // Start
    SSP2CON2bits.SEN = 1;       // Initiate Start condition
    while (SSP2CON2bits.SEN);   // Wait for Start condition to complete
    PIR3bits.SSP2IF = 0;        // Clear SSP Interrupt Flag

    // Slave Address
    SSP2BUF = SlaveAddr;        // Send the Slave Address and R/W kept to 0
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return 0xFF;                     // Exit
    }

    // Register Address
    SSP2BUF = RegAddr;           // Send the Register Address
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return 0xFF;                     // Exit
    }

    // Restart
    SSP2CON2bits.RSEN = 1;      // Initiate Restart condition
    while (SSP2CON2bits.RSEN);  // Wait for Restart condition to complete
    PIR3bits.SSP2IF = 0;        // Clear SSP Interrupt Flag
    
    // Slave Address
    SSP2BUF = SlaveAddr | 1;    // Send the Slave Address and R/W set to 0
    while (!PIR3bits.SSP2IF);   // Wait for Acknowledge. SSP2IF is set every 9th clock cycle.
    PIR3bits.SSP2IF = 0;        // Clear SSP2 Interrupt Flag
    if (SSP2CON2bits.ACKSTAT)
    {
        SSP2CON2bits.PEN = 1;       // Initiate Stop condition
        while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
        return 0xFF;                     // Exit
    }

    // Receive Data
    SSP2CON2bits.RCEN = 1;      // Receive the Data Byte from Slave
    while (!SSP2STATbits.BF);   // Wait for the Receive to complete
    TempData = SSP2BUF;         // Save the Data Byte
    
    // Acknowledge
    SSP2CON2bits.ACKDT = 1;     // Prepare to send NACK  --- THIS NEEDS TO BE NACK OR IT WON'T WORK
    SSP2CON2bits.ACKEN = 1;     // Initiate NACK
    while (SSP2CON2bits.ACKEN); // Wait for NACK to complete

    // Stop
    SSP2CON2bits.PEN = 1;       // Initiate Stop condition
    while (SSP2CON2bits.PEN);   // Wait for Stop condition to complete
    
    return TempData;
}
\$\endgroup\$
1
  • \$\begingroup\$ As @JustMe (upvoted) answer correctly says, NAK doesn't mean "the byte transferred is wrong/error", it indicates whether another byte transfer will occur. It's flow control rather than error indication. \$\endgroup\$
    – TonyM
    Aug 21, 2022 at 17:47

1 Answer 1

11
\$\begingroup\$

Because that is the proper way to end the data reading from all chips, it's not specific to any chip you use.

The datasheet is not very detailed about the transactions regarding the ACK/NAK, but the datasheet may assume being familiar with I2C in general.

If you do send an ACK, it would mean you want to transfer another byte, and if the next bit is logic 0, the bus data wire is already low so you can't send stop condition on bus.

If you send a NAK, it means you don't want to transfer any more, and the bus is not driven with no data any more by the expander, so the bus is free so PIC can perform the stop condition.

You can read the I2C standard for further details.

\$\endgroup\$
1
  • \$\begingroup\$ Thanks @Justme - I'm indeed not so familiar with I2C, and did not realize that this could indeed be a core-I2C knowledge. Feel a bit stupid now. Thanks for sharing the full standard! \$\endgroup\$ Aug 21, 2022 at 19:05

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.