AnsweredAssumed Answered

K66 smbus SHTF timeouts do not arise

Question asked by Marco Behr on Jul 13, 2017
Latest reply on Jul 20, 2017 by Marco Behr

Hello,

I am working on a Kinetis K66FN2M0 with FreeRTOS running.

The K66 uses I2C1 to connect to a SMBUS Interface, where a smart battery and a corresponding charger is also connected. The battery can be removed and reinserted at runtime.

 

I am using the fsl_i2c_freertos Driver. A Task tries periodically to get the charging state of the battery or to detect if there is no battery (Address NAK).

 

At first I modified the fsl_i2c_freertos Driver to Support additional Features of SMBUS vs. I2C. Also there was a bug in I2C_InitTransferStateMachine which lead to the handle->state being busy for ever.

 

===================================================================
--- fsl_i2c.c (revision 44)
+++ fsl_i2c.c (working copy)
@@ -264,6 +264,11 @@
     else /* For normal transfer, send start. */
     {
         result = I2C_MasterStart(base, handle->transfer.slaveAddress, direction);
+        if(result == kStatus_I2C_Busy)
+        {
+         /* we haven't started the transfer yet */
+         handle->state = kIdleState;
+        }
     }

     return result;
@@ -272,7 +277,10 @@
static status_t I2C_CheckAndClearError(I2C_Type *base, uint32_t status)
{
     status_t result = kStatus_Success;
+    uint8_t smbStatus = base->SMB;

+    base->SMB = I2C_SMB_SLTF_MASK | I2C_SMB_SHTF2_MASK;
+
     /* Check arbitration lost. */
     if (status & kI2C_ArbitrationLostFlag)
     {
@@ -280,6 +288,11 @@
         base->S = kI2C_ArbitrationLostFlag;
         result = kStatus_I2C_ArbitrationLost;
     }
+    /* Check SCL Low Timeout Flag */
+    else if(smbStatus & I2C_SMB_SLTF_MASK)
+    {
+     result = kStatus_I2C_SCL_TIMEOUT;
+    }
     /* Check NAK */
     else if (status & kI2C_ReceiveNakFlag)
     {
@@ -298,7 +311,7 @@
     uint32_t statusFlags = base->S;
     *isDone = false;
     volatile uint8_t dummy = 0;
-    bool ignoreNak = ((handle->state == kSendDataState) && (handle->transfer.dataSize == 0U)) ||
+    bool ignoreNak = ((handle->state == kSendDataState) && (handle->transfer.dataSize == 0U) && (handle->smbus == false)) ||
                      ((handle->state == kReceiveDataState) && (handle->transfer.dataSize == 1U));

     /* Add this to avoid build warning. */
@@ -307,7 +320,7 @@
     /* Check & clear error flags. */
     result = I2C_CheckAndClearError(base, statusFlags);

-    /* Ignore Nak when it's appeared for last byte. */
+    /* Ignore Nak when it's appeared for last byte except for SMBUS sending direction */
     if ((result == kStatus_I2C_Nak) && ignoreNak)
     {
         result = kStatus_Success;
@@ -457,15 +470,25 @@

static void I2C_TransferCommonIRQHandler(I2C_Type *base, void *handle)
{
-    /* Check if master interrupt. */
-    if ((base->S & kI2C_ArbitrationLostFlag) || (base->C1 & I2C_C1_MST_MASK))
-    {
-        s_i2cMasterIsr(base, handle);
-    }
-    else
-    {
-        s_i2cSlaveIsr(base, handle);
-    }
+ if(base->C1 & I2C_C1_IICIE_MASK)
+ {
+  /* Check if master interrupt. */
+  if ((base->S & kI2C_ArbitrationLostFlag) || (base->C1 & I2C_C1_MST_MASK))
+  {
+   assert(s_i2cMasterIsr);
+   s_i2cMasterIsr(base, handle);
+  }
+  else
+  {
+   assert(s_i2cSlaveIsr);
+   s_i2cSlaveIsr(base, handle);
+  }
+ }
+ else
+ {
+  //base->S = I2C_S_IICIF_MASK;
+  base->SMB = I2C_SMB_SLTF_MASK | I2C_SMB_SHTF2_MASK;
+ }
     __DSB();
}

@@ -1172,9 +1195,11 @@

     if (isDone || result)
     {
-        /* Send stop command if transfer done or received Nak. */
-        if ((!(handle->transfer.flags & kI2C_TransferNoStopFlag)) || (result == kStatus_I2C_Nak) ||
-            (result == kStatus_I2C_Addr_Nak))
+        /* Send stop command if transfer done or received Nak or SCL Low Timeout occurred. */
+        if ((!(handle->transfer.flags & kI2C_TransferNoStopFlag)) ||
+          (result == kStatus_I2C_Nak) ||
+    (result == kStatus_I2C_Addr_Nak) ||
+    (result == kStatus_I2C_SCL_TIMEOUT))
         {
             /* Ensure stop command is a need. */
             if ((base->C1 & I2C_C1_MST_MASK))
@@ -1728,6 +1753,13 @@
     }
}

+void I2C_MasterEnableSMBUSMode(I2C_Type *base, i2c_master_handle_t *handle, uint16_t sslt)
+{
+ handle->smbus = true;
+ base->SLTH = (uint8_t) (sslt >> 8);
+ base->SLTL = (uint8_t) sslt;
+}
+
#if defined(I2C0)
void I2C0_DriverIRQHandler(void)
{
Index: fsl_i2c.h
===================================================================
--- fsl_i2c.h (revision 44)
+++ fsl_i2c.h (working copy)
@@ -61,6 +61,7 @@
     kStatus_I2C_ArbitrationLost = MAKE_STATUS(kStatusGroup_I2C, 3), /*!< Arbitration lost during transfer. */
     kStatus_I2C_Timeout = MAKE_STATUS(kStatusGroup_I2C, 4),         /*!< Wait event timeout. */
     kStatus_I2C_Addr_Nak = MAKE_STATUS(kStatusGroup_I2C, 5),        /*!< NAK received during the address probe. */
+ kStatus_I2C_SCL_TIMEOUT = MAKE_STATUS(kStatusGroup_I2C, 6),  /*!< SCL Low Timeout occurred */
};

/*!
@@ -230,6 +231,7 @@
     uint8_t state;                                     /*!< A transfer state maintained during transfer. */

     i2c_master_transfer_callback_t completionCallback; /*!< A callback function called when the transfer is finished. */
     void *userData;                                    /*!< A callback parameter passed to the callback function. */
+    bool smbus;           /*! A flag signalling that SMBUS mode is used */
};

/*! @brief I2C slave transfer structure. */
@@ -785,6 +787,9 @@
  */
void I2C_SlaveTransferHandleIRQ(I2C_Type *base, void *i2cHandle);

+void I2C_MasterEnableSMBUSMode(I2C_Type *base, i2c_master_handle_t *handle, uint16_t sslt);
+
+
/* @} */
#if defined(__cplusplus)
}
Index: fsl_i2c_freertos.c
===================================================================
--- fsl_i2c_freertos.c (revision 44)
+++ fsl_i2c_freertos.c (working copy)
@@ -119,3 +119,44 @@
     /* Return status captured by callback function */
     return handle->async_status;
}
+
+
+status_t I2C_RTOS_Transfer_With_Timeout(i2c_rtos_handle_t *handle, i2c_master_transfer_t *transfer,
+  uint32_t timeout, uint32_t busy_waittime)
+{
+    status_t status;
+
+    /* Lock resource mutex */
+    if (xSemaphoreTake(handle->mutex, pdMS_TO_TICKS(timeout)) != pdTRUE)
+    {
+        return kStatus_I2C_Busy;
+    }
+
+    status = I2C_MasterTransferNonBlocking(handle->base, &handle->drv_handle, transfer);
+    if(status == kStatus_I2C_Busy)
+    {
+     vTaskDelay(pdMS_TO_TICKS(busy_waittime));
+     /* try again */
+     status = I2C_MasterTransferNonBlocking(handle->base, &handle->drv_handle, transfer);
+    }
+
+    if (status != kStatus_Success)
+    {
+        xSemaphoreGive(handle->mutex);
+        return status;
+    }
+
+    /* Wait for transfer to finish */
+    if (xSemaphoreTake(handle->semaphore, pdMS_TO_TICKS(timeout)) != pdTRUE)
+ {
+        /* Unlock resource mutex */
+        xSemaphoreGive(handle->mutex);
+        return kStatus_I2C_Timeout;
+ }
+
+    /* Unlock resource mutex */
+    xSemaphoreGive(handle->mutex);
+
+    /* Return status captured by callback function */
+    return handle->async_status;
+}
Index: fsl_i2c_freertos.h
===================================================================
--- fsl_i2c_freertos.h (revision 44)
+++ fsl_i2c_freertos.h (working copy)
@@ -32,6 +32,7 @@

#include "FreeRTOSConfig.h"
#include "FreeRTOS.h"
+#include "task.h"
#include "portable.h"
#include "semphr.h"

@@ -113,6 +114,9 @@
  */
status_t I2C_RTOS_Transfer(i2c_rtos_handle_t *handle, i2c_master_transfer_t *transfer);

+status_t I2C_RTOS_Transfer_With_Timeout(i2c_rtos_handle_t *handle, i2c_master_transfer_t *transfer,
+  uint32_t timeout, uint32_t busy_waittime);
+
/*!
  * @}
  */



===================================================================

 

 

This all works fine until the battery is removed. I monitored the bus with an oscilloscope and found out that the last Frame is not followed by a STOP condition. This leads to the I2C Peripheral indicating Bus Busy (which is correct for I2C). For SMBUS I would expect the SHTF1 flag to arise (SLTH, SLTL configured so an SCL Low can be detected after about 30 milliseconds).

 

So the Driver will never attempt to Access the bus as it always thinks the bus is blocked. The following listing is the States of the I2C Registers under the above mentioned condition (Battery removed -> last Frame on Bus not finished by STOP condition) when executing I2C_MasterStart which checks the busy bit.

 

base I2C_Type * 0x40067000 
 A1 volatile uint8_t 0x0 
 F volatile uint8_t 0x2c 
 C1 volatile uint8_t 0x80 
 S volatile uint8_t 0x21 <-- Busy Bit set
 D volatile uint8_t 0xb6 
 C2 volatile uint8_t 0x0 
 FLT volatile uint8_t 0x10 
 RA volatile uint8_t 0x0 
 SMB volatile uint8_t 0x0 <-- SHTF Bit not set although SCL/SDA high several seconds
 A2 volatile uint8_t 0xc2 
 SLTH volatile uint8_t 0x6d 
 SLTL volatile uint8_t 0xdd 

 

The I2C module is clocked at 60 MHz and the Baudrate is set to approx. 100 kBit/s.

The is terminated by the battery charger so I don't know the exact resistors value. But the slopes and Levels of SDA/SCL look well so I don't think there is an issue with the resistors values.

 

Thanks in advance

Outcomes