背景
前面分析硬件I2C框架时,提到了个人不认可在方案不支持硬件I2C时,通过在驱动层用硬件I2C的接口实现软件I2C的逻辑。刚好看RTT框架层中,有软件I2C的对应实现,因此开始分析此接口,以期在后续使用中直接使用该接口。
功能实现分析
软件I2C注册
在查看I2C设备注册入口时,偶然发现组件层也有调用I2C设备注册入口,很好奇为何会有此入口,便顺势查看了这个入口的调用关系:
rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
                                    const char               *bus_name)
{
    rt_err_t res = RT_EOK;
    rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_PRIO);
    if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND;
    res = rt_i2c_bus_device_device_init(bus, bus_name);
    LOG_I("I2C bus [%s] registered", bus_name);
#ifdef RT_USING_DM
    if (!res)
    {
        i2c_bus_scan_clients(bus);
    }
#endif
    return res;
}
static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
    i2c_bit_xfer,
    RT_NULL,
    RT_NULL
};
rt_err_t rt_i2c_bit_add_bus(struct rt_i2c_bus_device *bus,
                            const char               *bus_name)
{
    bus->ops = &i2c_bit_bus_ops;
    return rt_i2c_bus_device_register(bus, bus_name);
}
/* I2C initialization function */
int rt_soft_i2c_init(void)
{
    int err = RT_EOK;
    struct rt_soft_i2c *obj;
    int i;
    for(i = 0; i < sizeof(i2c_bus_obj) / sizeof(i2c_bus_obj[0]); i++)
    {
        struct soft_i2c_config *cfg = &i2c_cfg[i];
        pin_init(cfg);
        obj = &i2c_bus_obj[i];
        obj->ops = soft_i2c_ops;
        obj->ops.data = cfg;
        obj->i2c_bus.priv = &obj->ops;
        obj->ops.delay_us = cfg->timing_delay;
        obj->ops.timeout = cfg->timing_timeout;
        if(rt_i2c_bit_add_bus(&obj->i2c_bus, cfg->bus_name) == RT_EOK)
        {
            i2c_bus_unlock(cfg);
            LOG_D("Software simulation %s init done"
                  ", SCL pin: 0x%02X, SDA pin: 0x%02X"
                  , cfg->bus_name
                  , cfg->scl_pin
                  , cfg->sda_pin
                 );
        }
        else
        {
            err++;
            LOG_E("Software simulation %s init fail"
                  ", SCL pin: 0x%02X, SDA pin: 0x%02X"
                  , cfg->bus_name
                  , cfg->scl_pin
                  , cfg->sda_pin
                 );
        }
    }
    return err;
}
INIT_PREV_EXPORT(rt_soft_i2c_init);当反查到函数INIT_PREV_EXPORT(rt_soft_i2c_init);时,已经可以通过函数名确认了,这个实现是软件I2C的实现,再进一步的查,可以发现以下信息:
if GetDepend('RT_USING_SOFT_I2C'):
    src = src + ['soft_i2c.c']也就是说,只要在.config中使能宏RT_USING_SOFT_I2C便可启用该功能,而此功能的选现在组件的Kconfig中有维护。
if RT_USING_I2C config RT_I2C_DEBUG bool "Use I2C debug message" default n config RT_USING_I2C_BITOPS bool "Use GPIO to simulate I2C" default y if RT_USING_I2C_BITOPS config RT_I2C_BITOPS_DEBUG bool "Use simulate I2C debug message" default n endif config RT_USING_SOFT_I2C bool "Use GPIO to soft simulate I2C" default n select RT_USING_PIN select RT_USING_I2C_BITOPS if RT_USING_SOFT_I2C config RT_USING_SOFT_I2C1 bool "Enable I2C1 Bus (software simulation)" default y if RT_USING_SOFT_I2C1 config RT_SOFT_I2C1_SCL_PIN int "SCL pin number" range 0 32767 default 1 config RT_SOFT_I2C1_SDA_PIN int "SDA pin number" range 0 32767 default 2 config RT_SOFT_I2C1_BUS_NAME string "Bus name" default "i2c1" config RT_SOFT_I2C1_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C1_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C2 bool "Enable I2C2 Bus (software simulation)" default n if RT_USING_SOFT_I2C2 config RT_SOFT_I2C2_SCL_PIN int "SCL pin number" range 0 32767 default 3 config RT_SOFT_I2C2_SDA_PIN int "SDA pin number" range 0 32767 default 4 config RT_SOFT_I2C2_BUS_NAME string "Bus name" default "i2c2" config RT_SOFT_I2C2_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C2_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C3 bool "Enable I2C3 Bus (software simulation)" default n if RT_USING_SOFT_I2C3 config RT_SOFT_I2C3_SCL_PIN int "SCL pin number" range 0 32767 default 5 config RT_SOFT_I2C3_SDA_PIN int "SDA pin number" range 0 32767 default 6 config RT_SOFT_I2C3_BUS_NAME string "Bus name" default "i2c3" config RT_SOFT_I2C3_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C3_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C4 bool "Enable I2C4 Bus (software simulation)" default n if RT_USING_SOFT_I2C4 config RT_SOFT_I2C4_SCL_PIN int "SCL pin number" range 0 32767 default 7 config RT_SOFT_I2C4_SDA_PIN int "SDA pin number" range 0 32767 default 8 config RT_SOFT_I2C4_BUS_NAME string "Bus name" default "i2c4" config RT_SOFT_I2C4_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C4_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C5 bool "Enable I2C5 Bus (software simulation)" default n if RT_USING_SOFT_I2C5 config RT_SOFT_I2C5_SCL_PIN int "SCL pin number" range 0 32767 default 9 config RT_SOFT_I2C5_SDA_PIN int "SDA pin number" range 0 32767 default 10 config RT_SOFT_I2C5_BUS_NAME string "Bus name" default "i2c5" config RT_SOFT_I2C5_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C5_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C6 bool "Enable I2C6 Bus (software simulation)" default n if RT_USING_SOFT_I2C6 config RT_SOFT_I2C6_SCL_PIN int "SCL pin number" range 0 32767 default 11 config RT_SOFT_I2C6_SDA_PIN int "SDA pin number" range 0 32767 default 12 config RT_SOFT_I2C6_BUS_NAME string "Bus name" default "i2c6" config RT_SOFT_I2C6_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C6_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C7 bool "Enable I2C7 Bus (software simulation)" default n if RT_USING_SOFT_I2C7 config RT_SOFT_I2C7_SCL_PIN int "SCL pin number" range 0 32767 default 13 config RT_SOFT_I2C7_SDA_PIN int "SDA pin number" range 0 32767 default 14 config RT_SOFT_I2C7_BUS_NAME string "Bus name" default "i2c7" config RT_SOFT_I2C7_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C7_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif config RT_USING_SOFT_I2C8 bool "Enable I2C8 Bus (software simulation)" default n if RT_USING_SOFT_I2C8 config RT_SOFT_I2C8_SCL_PIN int "SCL pin number" range 0 32767 default 15 config RT_SOFT_I2C8_SDA_PIN int "SDA pin number" range 0 32767 default 16 config RT_SOFT_I2C8_BUS_NAME string "Bus name" default "i2c8" config RT_SOFT_I2C8_TIMING_DELAY int "Timing delay (us)" range 0 32767 default 10 config RT_SOFT_I2C8_TIMING_TIMEOUT int "Timing timeout (tick)" range 0 32767 default 10 endif endif endif
此Kconfig路径为: ./components/drivers/Kconfig
而从注册函数的实现上看,我们可以看出,本质上RTT所实现的软件I2C和在驱动层套用硬件I2C框架实现软件I2C的思路是一致的,唯一的区别是,把这套实现挪至组件层,那所有不支持硬件I2C的器件都能直接实现软件I2C,而不需要再去单独写一套软件I2C的实现。而既然弄清楚了软件I2C的注册过程,其实软件I2C的使用方法也很清晰了,各层的调用逻辑和硬件I2C逻辑一致,唯一的区别可能就是软件I2C的设备名与硬件I2C的设备名一致。
软件I2C实现逻辑
软件I2C的实现,无非就是实现以下几个接口:
SetSCL
SetSDA
GetSDA
Delay
在这个接口的基础上,封装以下几个接口:
Start
Stop
ReadAck
SendBit
需要注意的是:ReadAck需要一个超时机制,以防止从机无响应而死等。
最后基于这些接口上,实现以下接口:
Read
Write
而交给应用层的接口,其实仅仅是把Read和Write接口以某种形式暴露给上层使用。
我们可以按照这么个思路去套用,看RTT的软件I2C框架是否与该思路一致:
最底层接口
// 最底层的IO操作定义
// 至于ops对应的接口定义,在 rt_soft_i2c_init有定义,具体可见文件soft_i2c.c
#define SET_SDA(ops, val)   ops->set_sda(ops->data, val)
#define SET_SCL(ops, val)   ops->set_scl(ops->data, val)
#define GET_SDA(ops)        ops->get_sda(ops->data)
#define GET_SCL(ops)        ops->get_scl(ops->data)
// 实现了两个版本的延时,为何这么操作,估计得看后面的具体操作
rt_inline void i2c_delay(struct rt_i2c_bit_ops *ops)
{
    ops->udelay((ops->delay_us + 1) >> 1);
}
rt_inline void i2c_delay2(struct rt_i2c_bit_ops *ops)
{
    ops->udelay(ops->delay_us);
}
// 设置输出高低电平
#define SDA_L(ops)          SET_SDA(ops, 0)
#define SDA_H(ops)          SET_SDA(ops, 1)
#define SCL_L(ops)          SET_SCL(ops, 0)
static rt_err_t SCL_H(struct rt_i2c_bit_ops *ops)
{
    rt_tick_t start;
    SET_SCL(ops, 1);
    if (!ops->get_scl)
        goto done;
    start = rt_tick_get();
    while (!GET_SCL(ops))  // 由于在框架里,IO口被初始化成开漏输出,因此置高需要等待外部拉高,因此需要等待外部实际变高
    {
        if ((rt_tick_get() - start) > ops->timeout)
            return -RT_ETIMEOUT;
        i2c_delay(ops);
    }
#ifdef RT_I2C_BITOPS_DEBUG
    if (rt_tick_get() != start)
    {
        LOG_D("wait %ld tick for SCL line to go high",
              rt_tick_get() - start);
    }
#endif
done:
    i2c_delay(ops);
    return RT_EOK;
}中间层接口
static void i2c_start(struct rt_i2c_bit_ops *ops)
{    // 起始信号,SDA低电平后SCL低电平
#ifdef RT_I2C_BITOPS_DEBUG
    if (ops->get_scl && !GET_SCL(ops))
    {
        LOG_E("I2C bus error, SCL line low");
    }
    if (ops->get_sda && !GET_SDA(ops))
    {
        LOG_E("I2C bus error, SDA line low");
    }
#endif
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}
static void i2c_restart(struct rt_i2c_bit_ops *ops)
{    // I2C复位操作, SDA和SCL全高后再发出起始波形,其实起始波形可以复用i2c_start
    SDA_H(ops);
    SCL_H(ops);
    i2c_delay(ops);
    SDA_L(ops);
    i2c_delay(ops);
    SCL_L(ops);
}
static void i2c_stop(struct rt_i2c_bit_ops *ops)
{    //I2C停止波形,由于I2C通信的最后一个波形是等待从机拉低,因此若未等到从机拉低时,需主动拉低保证波形符合标准,因此先拉低SDA后,再拉高SCL,最后拉高SDA,保证波形为停止波形。
    SDA_L(ops);
    i2c_delay(ops);
    SCL_H(ops);
    i2c_delay(ops);
    SDA_H(ops);
    i2c_delay2(ops);
}
rt_inline rt_bool_t i2c_waitack(struct rt_i2c_bit_ops *ops)
{    //等待ACK信号,在8bit数据发送完毕后,从机端若收到信息,则需要在第九个时钟位置拉低SDA
     // 主机端读到高电平判定为NACK,低电平判定位ACK
     // 这个函数并没有SDA输入输出态转换的操作,原因是在soft_i2c.c的pin_init中,框架直接将IO口设置为开漏输出,
     // 也就是说,这个管脚硬件需接上拉,而管脚的输出低电平,本质上是将接口引到地上,输出高电平,本质上是变成输入高阻态
     // 另外,为何在拉高SCL的时候去读SDA,这操作没看明白,个人理解实际波形为低电平时去读取
    rt_bool_t ack;
    SDA_H(ops);
    i2c_delay(ops);
    if (SCL_H(ops) < 0)
    {
        LOG_W("wait ack timeout");
        return -RT_ETIMEOUT;
    }
    ack = !GET_SDA(ops);    /* ACK : SDA pin is pulled low */
    LOG_D("%s", ack ? "ACK" : "NACK");
    SCL_L(ops);
    return ack;
}
static rt_int32_t i2c_writeb(struct rt_i2c_bus_device *bus, rt_uint8_t data)
{    // 写一个字节,由低到高的顺序写完8bit后,拉低SCL再去读SDA
    rt_int32_t i;
    rt_uint8_t bit;
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    for (i = 7; i >= 0; i--)
    {
        SCL_L(ops);
        bit = (data >> i) & 1;
        SET_SDA(ops, bit);
        i2c_delay(ops);
        if (SCL_H(ops) < 0)
        {
            LOG_D("i2c_writeb: 0x%02x, "
                    "wait scl pin high timeout at bit %d",
                    data, i);
            return -RT_ETIMEOUT;
        }
    }
    SCL_L(ops);
    i2c_delay(ops);
    return i2c_waitack(ops);
}
static rt_int32_t i2c_readb(struct rt_i2c_bus_device *bus)
{//读取一个字节的数据,拉高SDA后,由低到高的顺序读取8bit
    rt_uint8_t i;
    rt_uint8_t data = 0;
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    SDA_H(ops);
    i2c_delay(ops);
    for (i = 0; i < 8; i++)
    {
        data <<= 1;
        if (SCL_H(ops) < 0)
        {
            LOG_D("i2c_readb: wait scl pin high "
                    "timeout at bit %d", 7 - i);
            return -RT_ETIMEOUT;
        }
        if (GET_SDA(ops))
            data |= 1;
        SCL_L(ops);
        i2c_delay2(ops);
    }
    return data;
}Read Write接口
static rt_ssize_t i2c_send_bytes(struct rt_i2c_bus_device *bus,
                                struct rt_i2c_msg        *msg)
{ // 写msg->len个字节
    rt_int32_t ret;
    rt_size_t bytes = 0;
    const rt_uint8_t *ptr = msg->buf;
    rt_int32_t count = msg->len;
    rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
    while (count > 0)
    {
        ret = i2c_writeb(bus, *ptr);
        if ((ret > 0) || (ignore_nack && (ret == 0)))
        {
            count --;
            ptr ++;
            bytes ++;
        }
        else if (ret == 0)
        {
            LOG_D("send bytes: NACK.");
            return 0;
        }
        else
        {
            LOG_E("send bytes: error %d", ret);
            return ret;
        }
    }
    return bytes;
}
static rt_err_t i2c_send_ack_or_nack(struct rt_i2c_bus_device *bus, int ack)
{    // 连续读时,需要在读完一个字节时读取一次ack标记,若标记为ack,则继续读,若标记为nack,则停止读取,因此有这函数的实现
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    if (ack)
        SET_SDA(ops, 0);
    i2c_delay(ops);
    if (SCL_H(ops) < 0)
    {
        LOG_E("ACK or NACK timeout.");
        return -RT_ETIMEOUT;
    }
    SCL_L(ops);
    return RT_EOK;
}
static rt_ssize_t i2c_recv_bytes(struct rt_i2c_bus_device *bus,
                                struct rt_i2c_msg        *msg)
{    // 连续读入口,每读完一个字节便读取一下ack状态,并决定是否继续读写
    rt_int32_t val;
    rt_int32_t bytes = 0;   /* actual bytes */
    rt_uint8_t *ptr = msg->buf;
    rt_int32_t count = msg->len;
    const rt_uint32_t flags = msg->flags;
    while (count > 0)
    {
        val = i2c_readb(bus);
        if (val >= 0)
        {
            *ptr = val;
            bytes ++;
        }
        else
        {
            break;
        }
        ptr ++;
        count --;
        LOG_D("recieve bytes: 0x%02x, %s",
                val, (flags & RT_I2C_NO_READ_ACK) ?
                "(No ACK/NACK)" : (count ? "ACK" : "NACK"));
        if (!(flags & RT_I2C_NO_READ_ACK))
        {
            val = i2c_send_ack_or_nack(bus, count);
            if (val < 0)
                return val;
        }
    }
    return bytes;
}
static rt_int32_t i2c_send_address(struct rt_i2c_bus_device *bus,
                                   rt_uint8_t                addr,
                                   rt_int32_t                retries)
{ // 正常I2C读取的时候,地址发送失败后不会再去尝试
  // 但rtt的框架中,增加了这个操作,发现读地址失败后,尝试多次读取,读取次数由retries决定
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    rt_int32_t i;
    rt_err_t ret = 0;
    for (i = 0; i <= retries; i++)
    {
        ret = i2c_writeb(bus, addr);
        if (ret == 1 || i == retries)
            break;
        LOG_D("send stop condition");
        i2c_stop(ops);
        i2c_delay2(ops);
        LOG_D("send start condition");
        i2c_start(ops);
    }
    return ret;
}
static rt_err_t i2c_bit_send_address(struct rt_i2c_bus_device *bus,
                                     struct rt_i2c_msg        *msg)
{
    rt_uint16_t flags = msg->flags;
    rt_uint16_t ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    rt_uint8_t addr1, addr2;
    rt_int32_t retries;
    rt_err_t ret;
    retries = ignore_nack ? 0 : bus->retries;
    if (flags & RT_I2C_ADDR_10BIT)
    {    // 发送10位地址的方式,本质上是发送两个字节的数据,没数据的位默认置1,10bit模式,如果是读,则后面补了个读的操作
        addr1 = 0xf0 | ((msg->addr >> 7) & 0x06);
        addr2 = msg->addr & 0xff;
        LOG_D("addr1: %d, addr2: %d", addr1, addr2);
        ret = i2c_send_address(bus, addr1, retries);
        if ((ret != 1) && !ignore_nack)
        {
            LOG_W("NACK: sending first addr");
            return -RT_EIO;
        }
        ret = i2c_writeb(bus, addr2);
        if ((ret != 1) && !ignore_nack)
        {
            LOG_W("NACK: sending second addr");
            return -RT_EIO;
        }
        if (flags & RT_I2C_RD)
        {
            LOG_D("send repeated start condition");
            i2c_restart(ops);
            addr1 |= 0x01;
            ret = i2c_send_address(bus, addr1, retries);
            if ((ret != 1) && !ignore_nack)
            {
                LOG_E("NACK: sending repeated addr");
                return -RT_EIO;
            }
        }
    }
    else
    { //7bit操作,如果是读,则直接把读写位标记了,然后发出地址
        /* 7-bit addr */
        addr1 = msg->addr << 1;
        if (flags & RT_I2C_RD)
            addr1 |= 1;
        ret = i2c_send_address(bus, addr1, retries);
        if ((ret != 1) && !ignore_nack)
            return -RT_EIO;
    }
    return RT_EOK;
}
static rt_ssize_t i2c_bit_xfer(struct rt_i2c_bus_device *bus,
                              struct rt_i2c_msg         msgs[],
                              rt_uint32_t               num)
{
    struct rt_i2c_msg *msg;
    struct rt_i2c_bit_ops *ops = (struct rt_i2c_bit_ops *)bus->priv;
    rt_int32_t ret;
    rt_uint32_t i;
    rt_uint16_t ignore_nack;
    if((ops->i2c_pin_init_flag == RT_FALSE) && (ops->pin_init != RT_NULL))
    {   // 如果模拟I2C从未被使用过,则使用前初始化io口并置已初始化标记,前提是接口被注册
        // 不过很可惜,这个接口并未在soft_i2c.c中定义,而是在rt_soft_i2c_init调用时直接初始化了
        ops->pin_init();
        ops->i2c_pin_init_flag = RT_TRUE;
    }
    if (num == 0) return 0;
    for (i = 0; i < num; i++)
    {
        msg = &msgs[i];
        ignore_nack = msg->flags & RT_I2C_IGNORE_NACK;
        if (!(msg->flags & RT_I2C_NO_START))    // 如果有地址,则写地址
        {
            if (i)
            {
                i2c_restart(ops);
            }
            else
            {
                LOG_D("send start condition");
                i2c_start(ops);
            }
            ret = i2c_bit_send_address(bus, msg);
            if ((ret != RT_EOK) && !ignore_nack)
            {
                LOG_D("receive NACK from device addr 0x%02x msg %d",
                        msgs[i].addr, i);
                goto out;
            }
        }
        if (msg->flags & RT_I2C_RD)    //如果是读,则调用读接口,否则调用写接口
        {
            ret = i2c_recv_bytes(bus, msg);
            if (ret >= 1)
            {
                LOG_D("read %d byte%s", ret, ret == 1 ? "" : "s");
            }
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = -RT_EIO;
                goto out;
            }
        }
        else
        {
            ret = i2c_send_bytes(bus, msg);
            if (ret >= 1)
            {
                LOG_D("write %d byte%s", ret, ret == 1 ? "" : "s");
            }
            if (ret < msg->len)
            {
                if (ret >= 0)
                    ret = -RT_ERROR;
                goto out;
            }
        }
    }
    ret = i;
out:
    if (!(msg->flags & RT_I2C_NO_STOP))    //如果操作完毕后存在停止命令,则发送停止波形
    {
        LOG_D("send stop condition");
        i2c_stop(ops);
    }
    return ret;
}从实现思路上看,RTT的软件I2C实现思路与通用做法基本一致,其中 i2c-bit-ops.c封装了通用思路中的顶层和中间层,而 soft_i2c.c则实现了最基础的底层接口。
总结
分析了那么多,其实还漏了一个最关键的点,如何使用软件I2C,这里简要说一下步骤:
a.命令行执行menuconfig后,去component目录下找到driver层,在此层找到软件I2C开关并使能
b.使能软件I2C时,需配置对应的SDA和SCL对应的gpio管脚编号,根据以往的经验,各家对GPIO编号的规则都有差异,但最终都是转换成十六进制数以符合RTT的gpio框架,因此此处仅需将对应管脚的编号换算成对应的十进制数写进去即可
c.设置好延时时间,这个延时时间其实就是对应的软件I2C的总线速度
d.scons --target=mdk5 mdk5也可以是其他平台,这里只是举个例子
e.打开对应工程,编写I2C使用代
        另外,RTT的软件I2C框架默认芯片的GPIO支持开漏模式,因此使用RTT的软件I2C,需要gpio驱动实现开漏模式。实际上,不支持开漏模式的GPIO也可以用作模拟I2C,但操作上会稍微复杂一些,且需要修改RTT的模拟I2C框架。此外若实现了不支持开漏模式的模拟I2C,外部I2C主设备便不能并入监控设备状态了。

 
					
				
 
			
			
			
						
			 我要赚赏金
 我要赚赏金 STM32
STM32 MCU
MCU 通讯及无线技术
通讯及无线技术 物联网技术
物联网技术 电子DIY
电子DIY 板卡试用
板卡试用 基础知识
基础知识 软件与操作系统
软件与操作系统 我爱生活
我爱生活 小e食堂
小e食堂

