SMBus (I2C) 发送额外的 ACK 然后预期

SMBus (I2C) sending extra ACK then intended

我正在尝试进行基本的握手。下面是 C8051F120 的 SMBus(系统管理总线)的 ISR。我正在尝试在其上实现一个 I2C 设备(ads1115 7addr 0x48 对于那些好奇的人)。请注意,这主要是硅实验室为 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
}

main 连续执行以下操作:发送 3 个字节。第一个字节是设备寄存器指针。然后读取相同的寄存器(因为指针已经设置)。它确实这样做了。

   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();
   }

这里是 trace capture of transfer:

在我看来,主接收端正在发送一个额外的 ACK。所以我主要关注案例:

SMB_MRADDACK: //0x40

SMB_MRADDNACK: //0x48

SMB_MRDBACK: //0x50

我的主要关注点是 SMB_MRADDNACK: //0x48 以及它在 ISR 调用期间通过该 if 语句的次数。我在思考确切的故障点时遇到了一些麻烦。那么这个额外的 ACK 是从哪里来的呢?如果到那时我自己还没有弄明白的话,我会在周一下午回头看看。

额外问题:是否有某种嵌入式堆栈交换?在社区中没有看到任何让我印象深刻的东西..

您的跟踪显示(不包括寻址)发送了三个字节,读取了三个字节。我假设您希望写入三个字节,然后只读取 两个 字节。如果那是真的,那么问题就不仅仅是一个虚假的 ACK,因为您的主机也继续为第三个字节计时。

如果您希望使用 SiLabs1 中的示例代码仅读取两个字节,您需要将 NUM_BYTES_RD 定义为 2 而不是提供的 3。该值用于 SMB_MRADDACKSMB_MRDBACK 状态决定是 ACK 还是 STOp。

以防万一(因为您询问的是 ACK 而不是额外的字节),如果您的问题是关于跟踪中 SDL 行的最终下降(在 0xff 之后),因为您担心那是额外的 ACK,那就不用担心了。这是一个 STO(在高 SCL 期间上升)并且是主机终止传输的正确行为。

编辑:klamb 在下面的评论中是正确的,SMB_MRDBACK 状态中存在错误。保存 SMB0DAT 和递增 rec_byte_counter 应该发生在检查 rec_byte_counterNUM_BYTES_RD 之前。令人惊讶的是,这样从 SiLabs 中脱颖而出。

  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;