Search code examples
embeddedmicrocontrolleri2c8051

SMBus (I2C) sending extra ACK then intended


I'm trying to get a basic handshake going. Below is the ISR for the C8051F120's SMBus (System Management Bus). I'm trying to implement an I2C device on it (ads1115 7addr 0x48 for those who are curious). Note this is mainly the example given by silicon labs for the F120.

void SMBUS_ISR (void) interrupt 7
{
   bit FAIL = 0;                       // Used by the ISR to flag failed
                                       // transfers

   static unsigned char sent_byte_counter;
   static unsigned char rec_byte_counter;

   // Status code for the SMBus (SMB0STA register)

   switch (SMB0STA)
   {
      // Master Transmitter/Receiver: START condition transmitted.
      // Load SMB0DAT with slave device address.
      case SMB_START: //0x08

      // Master Transmitter/Receiver: repeated START condition transmitted.
      // Load SMB0DAT with slave device address
      case SMB_RP_START: //0x10
         SMB0DAT = TARGET;             // Load address of the slave.
         SMB0DAT &= 0xFE;              // Clear the LSB of the address for the
                                       // R/W bit
         SMB0DAT |= SMB_RW;            // Load R/W bit
         STA = 0;                      // Manually clear STA bit

         rec_byte_counter = 1;         // Reset the counter
         sent_byte_counter = 1;        // Reset the counter

         break;

      // Master Transmitter: Slave address + WRITE transmitted.  ACK received.
      // For a READ: N/A
      //
      // For a WRITE: Send the first data byte to the slave.
      case SMB_MTADDACK: //0x18

         SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1];
         sent_byte_counter++;

         break;

      // Master Transmitter: Slave address + WRITE transmitted.  NACK received.
      // Restart the transfer.
      case SMB_MTADDNACK: //0x20
         STA = 1;                      // Restart transfer
         break;

      // Master Transmitter: Data byte transmitted.  ACK received.
      // For a READ: N/A
      //
      // For a WRITE: Send all data.  After the last data byte, send the stop
      //  bit.
      case SMB_MTDBACK: //0x28

         if (sent_byte_counter <= NUM_BYTES_WR)
         {
            // send data byte
            SMB0DAT = SMB_DATA_OUT[sent_byte_counter-1];
            sent_byte_counter++;
         }
         else
         {
            STO = 1;                   // Set STO to terminate transfer
            SMB_BUSY = 0;              // And free SMBus interface
         }

         break;

      // Master Transmitter: Data byte transmitted.  NACK received.
      // Restart the transfer.
      case SMB_MTDBNACK: //0x30
         STA = 1;                      // Restart transfer

         break;

      // Master Receiver: Slave address + READ transmitted.  ACK received.
      // For a READ: check if this is a one-byte transfer. if so, set the
      //  NACK after the data byte is received to end the transfer. if not,
      //  set the ACK and receive the other data bytes.
      //
      // For a WRITE: N/A
      case SMB_MRADDACK: //0x40

         if (rec_byte_counter == NUM_BYTES_RD)
         {
            AA = 0;                    // Only one byte in this transfer,
                                       // send NACK after byte is received
         }
         else
         {
            AA = 1;                    // More than one byte in this transfer,
                                       // send ACK after byte is received
         }

         break;

      // Master Receiver: Slave address + READ transmitted.  NACK received.
      // Restart the transfer.
      case SMB_MRADDNACK: //0x48
         STA = 1;                      // Restart transfer

         break;

      // Master Receiver: Data byte received.  ACK transmitted.
      // For a READ: receive each byte from the slave.  if this is the last
      //  byte, send a NACK and set the STOP bit.
      //
      // For a WRITE: N/A
      case SMB_MRDBACK: //0x50

         if (rec_byte_counter < NUM_BYTES_RD)
         {
            SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
            AA = 1;                    // Send ACK to indicate byte received
            rec_byte_counter++;        // Increment the byte counter
         }
         else
         {
            AA = 0;                    // Send NACK to indicate last byte
                                       // of this transfer
         }

         break;

      // Master Receiver: Data byte received.  NACK transmitted.
      // For a READ: Read operation has completed.  Read data register and
      //  send STOP.
      //
      // For a WRITE: N/A
      case SMB_MRDBNACK: //0x58

         SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
         STO = 1;
         SMB_BUSY = 0;
         AA = 1;                       // Set AA for next transfer

         break;

      // Master Transmitter: Arbitration lost.
      case SMB_MTARBLOST: //0x38

         FAIL = 1;                     // Indicate failed transfer
                                       // and handle at end of ISR

         break;

      // All other status codes invalid.  Reset communication.
      default:
         FAIL = 1;

         break;
   }

   if (FAIL)                           // If the transfer failed,
   {
      SMB0CN &= ~0x40;                 // Reset communication
      SMB0CN |= 0x40;
      STA = 0;
      STO = 0;
      AA = 0;

      SMB_BUSY = 0;                    // Free SMBus

      FAIL = 0;
   }

   SI = 0;                             // Clear interrupt flag
}

//-----------------------------------------------------------------------------
// Support Functions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// SMB_Write
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// Writes a single byte to the slave with address specified by the <TARGET>
// variable.
// Calling sequence:
// 1) Write target slave address to the <TARGET> variable
// 2) Write outgoing data to the <SMB_DATA_OUT> array
// 3) Call SMB_Write()
//
void SMB_Write (void)
{
   char SFRPAGE_SAVE = SFRPAGE;        // Save Current SFR page

   SFRPAGE = SMB0_PAGE;

   while (SMB_BUSY);                   // Wait for SMBus to be free.
   SMB_BUSY = 1;                       // Claim SMBus (set to busy)
   SMB_RW = 0;                         // Mark this transfer as a WRITE
   STA = 1;                            // Start transfer

   SFRPAGE = SFRPAGE_SAVE;             // Restore SFR page detector
}

//-----------------------------------------------------------------------------
// SMB_Read
//-----------------------------------------------------------------------------
//
// Return Value : None
// Parameters   : None
//
// Reads a single byte from the slave with address specified by the <TARGET>
// variable.
// Calling sequence:
// 1) Write target slave address to the <TARGET> variable
// 2) Call SMB_Write()
// 3) Read input data from <SMB_DATA_IN> array
//
void SMB_Read (void)
{
   char SFRPAGE_SAVE = SFRPAGE;        // Save Current SFR page

   SFRPAGE = SMB0_PAGE;

   while (SMB_BUSY);                   // Wait for bus to be free.
   SMB_BUSY = 1;                       // Claim SMBus (set to busy)
   SMB_RW = 1;                         // Mark this transfer as a READ

   STA = 1;                            // Start transfer

   while (SMB_BUSY);                   // Wait for transfer to complete

   SFRPAGE = SFRPAGE_SAVE;             // Restore SFR page detector
}

The main continuously does the following: Sends 3 bytes. The first byte is the device register pointer. Then reads the same register (since the pointer is already set). It does do this.

   while (1)
   {
      TARGET = SLAVE_ADDR;             // Target the Slave for next SMBus
                                       // transfer
      SMB_DATA_OUT[0] = 0x01;          // Device register
      SMB_DATA_OUT[1] = 0x0A;          // Register MSByte
      SMB_DATA_OUT[2] = 0x03;          // Register LSbyte
      SMB_Write();                     // Initiate SMBus write

      // SMBus Read Sequence
      TARGET = SLAVE_ADDR;             // Target the Slave for next SMBus
                                       // transfer
      SMB_Read();
   }

And here is a trace capture of transfer:

Looks to me like the master receive is sending an extra ACK. So my main focus has been on cases:

SMB_MRADDACK: //0x40

SMB_MRADDNACK: //0x48

SMB_MRDBACK: //0x50

My main focus is more so SMB_MRADDNACK: //0x48 and the number of times it goes through that if statement during the ISR calls. I'm having a little trouble wrapping my head around the exact failure point. So where is this extra ACK coming from? I'll look back here Monday afternoon if I don't figure it out myself by then.

Bonus question: Is there a embedded stack exchange of some sort? Didn't see anything that stood out for me in the communities..


Solution

  •   case SMB_MRDBACK: //0x50
    
         SMB_DATA_IN[rec_byte_counter-1] = SMB0DAT; // Store received byte
         rec_byte_counter++;        // Increment the byte counter
    
         if (rec_byte_counter < NUM_BYTES_RD)
         {
            AA = 1;                    // Send ACK to indicate byte received
         }
         else
         {
            AA = 0;                    // Send NACK to indicate last byte
                                       // of this transfer
         }
    
         break;