解决Ti CC1310上I2C驱动能力不足(稳定性)问题

在使用Ti CC1310的硬件I2C与外部设备进行通信的时候,无法长时间稳定运行,尤其是平时的测试开发板,使用杜邦线连接的情况下。

同等情况下的SPI总线,一般不会发生此类问题,因此怀疑是GPIO管脚的驱动能力不足导致的。

我的工程是通过简单修改Ti CC1310 SDK自带的Code Composer Studio的例子工程而创建的,因此存在已经进行过默认配置的CC1310_LAUNCHXL.cCC1310_LAUNCHXL.h这两个文件。用Code Composer Studio完全重新创建的工程,缺乏必要的默认配置。因此,我们还是建议根据Ti CC1310 SDK自带的例子进行修改,而不是完全从头创建工程。

我们研究I2C初始化GPIO管脚的代码在simplelink_cc13x0_sdk_2_30_00_20/source/ti/drivers/i2c/I2CCC26XX.c,相关的代码如下:

/*
 *  ======== I2CCC26XX_initIO ========
 *  This functions initializes the I2C IOs.
 *
 *  @pre    Function assumes that the I2C handle is pointing to a hardware
 *          module which has already been opened.
 */
static int I2CCC26XX_initIO(I2C_Handle handle, void *pinCfg) {
    I2CCC26XX_Object            *object;
    I2CCC26XX_HWAttrsV1 const  *hwAttrs;
    I2CCC26XX_I2CPinCfg         i2cPins;
    PIN_Config                  i2cPinTable[3];
    uint32_t i=0;

    /* Get the pointer to the object and hwAttrs */
    object = handle->object;
    hwAttrs = handle->hwAttrs;

    /* If the pinCfg pointer is NULL, use hwAttrs pins */
    if (pinCfg == NULL) {
        i2cPins.pinSDA = hwAttrs->sdaPin;
        i2cPins.pinSCL = hwAttrs->sclPin;
    } else {
        i2cPins.pinSDA = ((I2CCC26XX_I2CPinCfg *)pinCfg)->pinSDA;
        i2cPins.pinSCL = ((I2CCC26XX_I2CPinCfg *)pinCfg)->pinSCL;
    }

    /* Handle error */
    if(i2cPins.pinSDA == PIN_UNASSIGNED || i2cPins.pinSCL == PIN_UNASSIGNED) {
        return I2C_STATUS_ERROR;
    }
    /* Configure I2C pins SDA and SCL*/
    i2cPinTable[i++] = i2cPins.pinSDA | PIN_INPUT_EN | PIN_PULLUP | PIN_OPENDRAIN;
    i2cPinTable[i++] = i2cPins.pinSCL | PIN_INPUT_EN | PIN_PULLUP | PIN_OPENDRAIN;
    i2cPinTable[i++] = PIN_TERMINATE;
    /* Allocate pins*/
    object->hPin = PIN_open(&object->pinState, i2cPinTable);

    if (!object->hPin) {
        return I2C_STATUS_ERROR;
    }

    /* Set IO muxing for the UART pins */
    PINCC26XX_setMux(object->hPin, i2cPins.pinSDA, IOC_PORT_MCU_I2C_MSSDA);
    PINCC26XX_setMux(object->hPin, i2cPins.pinSCL, IOC_PORT_MCU_I2C_MSSCL);
    return I2C_STATUS_SUCCESS;
}

从上面的代码中,我们发现GPIO管脚的驱动能力没有设置,根据CC13x0, CC26x0 SimpleLink ™ Wireless MCU Technical Reference Manual中的介绍,我们发现,如果不设置,默认情况下是PIN_DRVSTR_MIN(< (*) Lowest drive strength)。这个驱动模式下,最省电,但是当有干扰存在的情况下,最容易导致出现错误。

查找Ti CC1310提供的I2C驱动程序,没有找到可以设置管脚驱动能力的接口。

网上有人通过直接修改Ti CC1310提供的I2C驱动程序,也就是simplelink_cc13x0_sdk_2_30_00_20/source/ti/drivers/i2c/I2CCC26XX.c里的初始化代码(见上面的代码段)增加驱动能力属性来实现这个功能。

但是这种修改方式不推荐,修改的侵入太强。

GPIO的操作函数中,我们找到如下函数:

/** @brief Returns pin configuration
 *
 *  @param pinId    Pin ID
 *  @return  Current pin configuration as a device-independent #PIN_Config value
 *  @note    The pin ID is embedded in return value.
 *  @note    There is usually a device-specific version of this function that
 *           returns device-specific options
 *  @par Usage
 *       @code
 *       // Get config of pin 14 to be able to revert later
 *       myPinConfig = PIN_getConfig(PIN_ID(14));
 *       // ...
 *       // Lots of pin reconfigurations
 *       // ...
 *       // Restore previous configuration
 *       PIN_setConfig(hPins, PIN_BM_ALL, myPinConfig);
 *       @endcode
 */
extern PIN_Config PIN_getConfig(PIN_Id pinId);


/** @brief Sets complete pin configuration
 *
 *  @param handle  Handle provided by previous call to PIN_open()
 *  @param updateMask  Bitmask specifying which fields in cfg that should take
 *                 effect, the rest keep their current value.
 *  @param pinCfg  #PIN_Config entry with pin ID and pin configuration
 *  @return #PIN_SUCCESS if successful, else error code
 *  @par Usage
 *       @code
 *       // Set drive strength on pin 15
 *       PIN_setConfig(hPins, PIN_BM_DRVSTR, PIN_ID(15)|PIN_DRVSTR_MAX);
 *       @endcode
 */
extern PIN_Status PIN_setConfig(PIN_Handle handle, PIN_Config updateMask, PIN_Config pinCfg);

我们尝试从上层找到I2C驱动使用的GPIO管脚,然后调整其驱动能力。

/* I2C */
#define CC1310_LAUNCHXL_I2C0_SCL0            (IOID_5) 
#define CC1310_LAUNCHXL_I2C0_SDA0            (IOID_6)
/*
 *  =============================== I2C ===============================
*/
#include <ti/drivers/I2C.h>
#include <ti/drivers/i2c/I2CCC26XX.h>

I2CCC26XX_Object i2cCC26xxObjects[CC1310_LAUNCHXL_I2CCOUNT];

const I2CCC26XX_HWAttrsV1 i2cCC26xxHWAttrs[CC1310_LAUNCHXL_I2CCOUNT] = {
    {
        .baseAddr    = I2C0_BASE,
        .powerMngrId = PowerCC26XX_PERIPH_I2C0,
        .intNum      = INT_I2C_IRQ,
        .intPriority = ~0,
        .swiPriority = 0,
        .sdaPin      = CC1310_LAUNCHXL_I2C0_SDA0,
        .sclPin      = CC1310_LAUNCHXL_I2C0_SCL0,
    }
};

const I2C_Config I2C_config[CC1310_LAUNCHXL_I2CCOUNT] = {
    {
        .fxnTablePtr = &I2CCC26XX_fxnTable,
        .object      = &i2cCC26xxObjects[CC1310_LAUNCHXL_I2C0],
        .hwAttrs     = &i2cCC26xxHWAttrs[CC1310_LAUNCHXL_I2C0]
    }
};

const uint_least8_t I2C_count = CC1310_LAUNCHXL_I2CCOUNT;

从上面我们看到I2C句柄的.object指针指向了I2CCC26XX_Object类型的结构体,这个结构体在simplelink_cc13x0_sdk_2_30_00_20/source/ti/drivers/i2c/I2CCC26XX.c里的static int I2CCC26XX_initIO(I2C_Handle handle, void *pinCfg)函数中被初始化,这个结构体记录了详细的GPIO操作句柄。

因此我们可以使用如下的代码对GPIO管脚的驱动能力进行调整:

#include <ti/drivers/I2C.h>
#include <ti/drivers/i2c/I2CCC26XX.h>
#include "CC1310_LAUNCHXL.h"

static I2C_Handle initI2c(void) {
    I2C_Params      i2cParams;
    /* Create I2C for usage */
    I2C_Params_init(&i2cParams);
    i2cParams.bitRate = I2C_400kHz;
    i2cParams.transferMode = I2C_MODE_BLOCKING;

    I2C_Handle i2c = I2C_open(Board_I2C_TMP, &i2cParams);
    if (NULL == i2c) {
        return NULL;
    }

    /* Change I2C Pin Drive Strength*/
    /*CC1310_LAUNCHXL.c I2CCC26XX_Object i2cCC26xxObjects[CC1310_LAUNCHXL_I2CCOUNT];*/
    I2CCC26XX_Object* i2cObj = i2c->object;
    if (NULL != i2cObj) {
        PIN_Config pinCfg = PIN_getConfig(PIN_ID(CC1310_LAUNCHXL_I2C0_SDA0));
        PIN_Status status = PIN_setConfig(i2cObj->hPin, PIN_BM_ALL, pinCfg | PIN_DRVSTR_MAX);
        if (PIN_SUCCESS != status){
            I2C_close(i2c);
            return NULL;
        }
        pinCfg = PIN_getConfig(PIN_ID(CC1310_LAUNCHXL_I2C0_SCL0));
        status = PIN_setConfig(i2cObj->hPin, PIN_BM_ALL, pinCfg | PIN_DRVSTR_MAX);
        if (PIN_SUCCESS != status){
            I2C_close(handle->i2c);
            return NULL;
        }
    }
    return i2c;
}

参考链接