人生最大的遗憾是什么| 布洛芬什么时候起效| 顺钟向转位是什么意思| 什么是蒸馏水| 金玉良缘什么意思| 萝莉控是什么意思| 为什么穿堂风最伤人| 邮编什么意思| 男人吃什么| 盆腔炎是什么原因引起的| 脾虚胃热吃什么中成药| 什么是爱情| 慢性萎缩性胃炎吃什么药| 核糖是什么| 江湖是什么| 顺丰到付是什么意思| 强化灶是什么意思| 双子座的幸运花是什么| 放屁多是什么原因引起的| cba什么时候开始比赛| 去医院看脚挂什么科| 为什么尽量不打免疫球蛋白| 北五行属什么| 扁平疣吃什么药| 1998年属虎的是什么命| 什么鸟没有翅膀| 想做肠镜挂什么科| 什么都想吃| xpe是什么材料| 射频消融术是什么意思| 脚指甲盖凹凸不平是什么原因| 电视开不了机是什么原因| 银属于五行属什么| 子宫前置是什么意思| 梦见很多鱼是什么意思| 甲状腺结节吃什么食物好| 吉祥动物是什么生肖| 9月18日是什么日子| 蟑螂中药名称叫什么| 红细胞偏低有什么危害| 4s店是什么意思| 晒伤用什么药| 吃了避孕药有什么反应| 猎德有什么好玩的| 脑梗是什么病| 海带是什么植物| 养兰花用什么土最好| 平步青云什么意思| 梦见别人开车翻车是什么预兆| 珠胎暗结是什么意思| 四级警长是什么级别| 怀孕十天左右有什么反应| 医院特需号是什么意思| 梦到头发长长了是什么意思| 白牡丹属于什么茶| 战狼三什么时候上映| 水弹是什么材料| 前列腺炎不能吃什么| 手指上的斗和簸箕代表什么意思| 狗狗尾巴溃烂用什么药| 口臭是什么原因| p0是什么意思| 打喷嚏很臭是什么原因| 电光性眼炎用什么眼药水| 修罗道是什么意思| Lady什么意思| 芜湖有什么特产| 龙和什么生肖最配| 施食是什么意思| 姐妹是什么生肖| 医院量身高为什么会矮| 锦衣卫是干什么的| 韧带损伤挂什么科| 孕妇为什么会便秘| 乌托邦是什么| 糖类抗原724偏高是什么原因| sap是做什么的| 凉爽的什么| 什么的雪花| 山豆念什么| 火烈鸟吃什么| 中科院是干什么的| 督邮相当于现在什么官| 脑内小缺血灶是什么意思| 朱元璋代表什么生肖| 尿多尿频是什么原因造成的| 牙齿黄用什么牙膏| 尿沉渣检查什么| lucas是什么意思| 沙僧的武器是什么| 老人经常便秘有什么好办法| 贤上腺瘤是什么意思| 疱疹用什么药可以根治| 猫咪取什么名字好听| 停滞是什么意思| 乙肝核心抗体高是什么意思| 检查胰腺挂什么科| 缘故的故是什么意思| 冒菜和麻辣烫有什么区别| 儿女情长是什么意思| 为什么种牙那么贵| 多春鱼为什么全是籽| 口腔溃疡吃什么| 早搏心律不齐吃什么药| 穿山甲吃什么| 10月30是什么星座| 麻痹是什么意思| 木耳不能和什么一起吃| 验光pd是什么意思| 尿蛋白质阳性什么意思| 疏肝理气吃什么药| 退化是什么意思| 德比什么意思| 离婚需要什么手续| ct平扫能检查出什么| 9月29是什么星座| 疝气嵌顿是什么意思| 粉碎性骨折吃什么好| 宽宏大度是什么生肖| 颧骨长斑是什么原因| 晚上七点多是什么时辰| 百香果什么时候成熟| 檀香是什么味道| 拔牙后注意什么| 头痛吃什么药| 梦见杀狗是什么预兆| 行尸走肉什么意思| 菊花不能和什么一起吃| 虫草花有什么功效和作用| 艳阳高照是什么生肖| 吃什么东西可以补血| 圣诞节送女生什么礼物好| 斗智斗勇什么意思| 公历和农历有什么区别| 济公叫什么名字| 五音不全是什么意思| 吃鸭蛋有什么好处和坏处| 真我是什么意思| 人生百味下一句是什么| 肛门瘙痒是什么问题| 骨刺是什么原因引起的| 当兵什么时候体检| 女人性高潮是什么感觉| 什么是翡翠| 邹的左边读什么| 天秤女和什么座最配对| 对什么都不感兴趣| 成人达己是什么意思| 大便失禁吃什么药| 缩量是什么意思| 音序是什么意思| 龙跟什么生肖配对最好| 胃窦隆起是什么意思| 吃什么去胃火口臭| 学霸是什么意思| 生育津贴什么时候到账| 治白内障用什么药最好| 冒菜和麻辣烫有什么区别| luna什么意思| 胰岛素是干什么的| 什么人容易得癌症| 尿酸高吃什么中药能降下来| 荨麻疹挂什么科| 21三体高风险是什么原因造成的| 机智如你是什么意思| 大陆对什么| 什么鱼不能吃| 头皮屑多是什么原因引起的| 舌头变肥大什么原因| 什么食物胆固醇高| 员级职称是什么意思| 揩是什么意思| 为什么喜欢一个人| 口腔溃疡吃什么药| 亥五行属什么| hpv用什么药| 经常不吃晚饭对身体有什么影响| 世界上最毒的蜘蛛叫什么| 揠苗助长是什么意思| 余光是什么意思| 腹泻吃什么水果| 9月21日是什么星座| 好不热闹是什么意思| 鹦鹉能吃什么水果| 什么牛排最好吃| 尿常规能检查出什么| 喜欢趴着睡觉是什么原因| 头疼嗓子疼吃什么药| 为什么长白头发| 三教九流指的是什么| 家里出现蛇是什么征兆| 花枝鼠吃什么| 72年鼠是什么命| 妒忌什么意思| 糖化是什么意思| 什么不什么干| ca724偏高是什么意思| 下面流出发黄的液体是什么原因| 什么是边界感| 一个三点水一个及念什么| 心存善念是什么意思| 黄花胶是什么鱼的胶| 什么的荷叶| 每天拉肚子是什么原因引起的| 月青念什么| 痱子粉和爽身粉有什么区别| 鲭鱼是什么鱼| 猕猴桃什么季节成熟| 尿糖阴性什么意思| 孩子发烧是什么原因引起的| bh是什么意思| 舌面上有裂纹是什么病| 幽门螺旋杆菌什么症状| 中国最高军衔是什么| 便血鲜红色无疼痛是什么原因| 病理性骨折是什么意思| 黄什么| 3月2日什么星座| 乳头痒是怎么回事是什么原因| 自我救赎是什么意思| 肝区疼痛吃什么药| 什么能让男人变大变长| 什么是血尿| 电器发生火灾用什么灭火器| 王为念和王芳什么关系| 什么叫高危行为| 卵巢囊肿术后吃什么食物好| 血小板减少是什么原因造成的| 被蜈蚣咬了用什么药| 鸽子公主是什么意思| 巨峰葡萄为什么叫巨峰| 粽叶是什么植物| 牛的尾巴有什么作用| 增加胃动力最好的药是什么药| 海胆是什么动物| zoom是什么意思| 量贩什么意思| 羲字五行属什么| 凤尾菜又叫什么菜| 文化大革命是什么时候开始的| omega什么意思| 凉虾是什么做的| 鼻子老是出血是什么原因| 嘴角生疮是什么原因| 肚子疼拉肚子吃什么药| 血常规主要检查什么| 两个月小猫吃什么食物| 肾虚什么意思| 肛裂用什么药膏| 乳糖不耐受可以喝什么奶| 什么病会引起牙疼| 秋天有什么特点| 扁桃体炎吃什么消炎药| 什么是中元节| 八面玲珑是指什么生肖| 96年出生的属什么| 果是什么意思| o型血是什么血| 正印是什么意思| 肾是干什么用的| 胃不舒服能吃什么水果| 妃子笑是什么茶| 无稽之谈是什么意思| 7.30是什么星座| 百度
这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 ? 论坛首页 ? 嵌入式开发 ? 软件与操作系统 ? rtthread I2C框架解析

共7条 1/1 1 跳转至

rtthread I2C框架解析

高工
2025-08-04 22:56:15     打赏
百度 作为全球腕表界最负盛名的组织,AHCI堪称瑞士腕表界的山峰,也是瑞士工匠精神的核心彰显。

I2C驱动框架解析

代码入口

   代码入口其实挺好找,基本上芯片对I2C驱动代码提供会以以下名称编写,drv_i2c.c(硬件I2C驱动)或drv_soft_i2c.c(软件I2C套在硬件I2C驱动接口上的实现,我个人的观点,如果不支持硬件I2C,其实根本没必要专门写一套软件I2C的驱动,原因是,软件I2C本质上就是gpio操作加延时处理,完全就是业务逻辑的事情,不应该放在驱动层实现)。这里我们主要看硬件I2C,因此直接查看硬件I2C驱动文件。在驱动文件中,我们找到么一个入口:

struct i2c_handle
{
    struct rt_i2c_bus_device bus;
    char bus_name[RT_NAME_MAX];
};

static struct i2c_handle ra_i2cs[] =
{
    ...
}

static const struct rt_i2c_bus_device_ops i2c_ops =
{
    .master_xfer        = ra_i2c_mst_xfer,
    .slave_xfer         = RT_NULL,
    .i2c_bus_control    = RT_NULL
};

int xxxx(void)
{
    fsp_err_t err     = FSP_SUCCESS;
    for (rt_uint32_t i = 0; i < sizeof(ra_i2cs) / sizeof(ra_i2cs[0]); i++)
    {
        // I2C接口初始化
        i2cs[i].bus.ops = &i2c_ops;
        i2cs[i].bus.priv = 0;

        // 注册I2C设备
        rt_i2c_bus_device_register(&i2cs[i].bus, i2cs[i].bus_name);
    }

    return 0;
}


INIT_DEVICE_EXPORT(xxxx);

其中:rt_i2c_bus_device_register即为我们所需要找到的I2C框架的入口位置。

I2C驱动注册函数解析

   找到了设备级的I2C设备注册入口,我们便开始逐层分析注册接口。

#ifdef RT_USING_DEVICE_OPS
const static struct rt_device_ops i2c_ops =
{
    RT_NULL,
    RT_NULL,
    RT_NULL,
    i2c_bus_device_read,
    i2c_bus_device_write,
    i2c_bus_device_control
};
#endif

rt_err_t rt_i2c_bus_device_device_init(struct rt_i2c_bus_device *bus,
                                       const char               *name)
{
    struct rt_device *device;
    RT_ASSERT(bus != RT_NULL);

    device = &bus->parent;

    device->user_data = bus;

    /* set device type */
    device->type    = RT_Device_Class_I2CBUS;
    /* initialize device interface */
#ifdef RT_USING_DEVICE_OPS
    device->ops     = &i2c_ops;
#else
    device->init    = RT_NULL;
    device->open    = RT_NULL;
    device->close   = RT_NULL;
    device->read    = i2c_bus_device_read;
    device->write   = i2c_bus_device_write;
    device->control = i2c_bus_device_control;
#endif

    /* register to device manager */
    rt_device_register(device, name, RT_DEVICE_FLAG_RDWR);

    return RT_EOK;
}

rt_err_t rt_i2c_bus_device_register(struct rt_i2c_bus_device *bus,
                                    const char               *bus_name)
{
    rt_err_t res = RT_EOK;

    // 注册设备锁,用于不同线程调用I2C设备时的互斥操作
    rt_mutex_init(&bus->lock, "i2c_bus_lock", RT_IPC_FLAG_PRIO);

    if (bus->timeout == 0) bus->timeout = RT_TICK_PER_SECOND;

    // 注册I2C设备
    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;
}

     完整代码一贴出来,我们便可以发现,所谓的I2C总线设备,实际上只是在posix标准框架上标准化了一套I2C接口框架,这个框架没有init,open和close。只有read,write和control。而这个注册最核心的部分,恰恰是rtt设备框架中的驱动注册入口rt_device_register。

应用获取I2C入口

    既然I2C设备是通过rt_device_register注册的,因此应用端寻找I2C设备,也只需要调用rt_device_find即可,若存在,则返回设备操作句柄,若不存在,则返回null。

       但是呢,实际上,rtt还封装了一层接口。

struct rt_i2c_bus_device *rt_i2c_bus_device_find(const char *bus_name)
{
    struct rt_i2c_bus_device *bus;
    rt_device_t dev = rt_device_find(bus_name);
    if (dev == RT_NULL || dev->type != RT_Device_Class_I2CBUS)
    {
        LOG_E("I2C bus %s not exist", bus_name);

        return RT_NULL;
    }

    bus = (struct rt_i2c_bus_device *)dev->user_data;

    return bus;
}

      从接口实现上看,其实可以看出来了,加了个判断,判断获取到的设备的设备类类型是否是I2C设备

I2C读写函数解析

    由RTT官方的I2C总线设备使用手册可知,I2C读写入口可以分为I2C读写入口,I2C读入口和I2C写入口

I2C 读写入口

rt_ssize_t rt_i2c_transfer(struct rt_i2c_bus_device *bus,
                          struct rt_i2c_msg         msgs[],
                          rt_uint32_t               num)
{
    rt_ssize_t ret;
    rt_err_t err;

    if (bus->ops->master_xfer)
    {
#ifdef RT_I2C_DEBUG
        for (ret = 0; ret < num; ret++)
        {
            LOG_D("msgs[%d] %c, addr=0x%02x, len=%d", ret,
                  (msgs[ret].flags & RT_I2C_RD) ? 'R' : 'W',
                  msgs[ret].addr, msgs[ret].len);
        }
#endif
        err = rt_mutex_take(&bus->lock, RT_WAITING_FOREVER);
        if (err != RT_EOK)
        {
            return (rt_ssize_t)err;
        }
        ret = bus->ops->master_xfer(bus, msgs, num);
        err = rt_mutex_release(&bus->lock);
        if (err != RT_EOK)
        {
            return (rt_ssize_t)err;
        }
        return ret;
    }
    else
    {
        LOG_E("I2C bus operation not supported");
        return -RT_EINVAL;
    }
}

   看到这,我们可以大致知道这些信息,驱动需要实现xfer函数,而xfer函数的作用需要实现读写操作。因此我们拿一份简化驱动实现来看。

static rt_ssize_t i2c_mst_xfer(struct rt_i2c_bus_device *bus,
                                  struct rt_i2c_msg msgs[],
                                  rt_uint32_t num)
{
    rt_size_t i;
    RT_ASSERT(bus != RT_NULL);

    for (i = 0; i < num; i++)
    {
        if (msg[i].flags & RT_I2C_NO_START)
        {
            // 设置是否需要起始位
        }
        if (msg[i].flags & RT_I2C_ADDR_10BIT)
        {
            // 设置成10bit地址工作模式
        }
        else
        {
            // 设置成8bit地址工作模式
        }

        if (msg[i].flags & RT_I2C_RD)
        {
            // 读操作,读失败跳出
        }
        else
        {
            // 写操作,写失败跳出
        }
    }
    return (rt_ssize_t)i;
}

    实际上驱动的实现也是按照此思路实现的,根据flags的标记做对应功能的实现,根据传入的数据量决定读写多少数据。

I2C 写入口

rt_ssize_t rt_i2c_master_send(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             const rt_uint8_t         *buf,
                             rt_uint32_t               count)
{
    rt_ssize_t ret;
    struct rt_i2c_msg msg;

    msg.addr  = addr;
    msg.flags = flags;
    msg.len   = count;
    msg.buf   = (rt_uint8_t *)buf;

    ret = rt_i2c_transfer(bus, &msg, 1);

    return (ret == 1) ? count : ret;
}

   从数据上结构上看,写入口仅仅是多开放了一种实现方式,将struct rt_i2c_msg放在函数内部实现,应用是现实不再需要维护这么个结构体,仅仅需要将读写参数以变量的形式传递至rt_i2c_master_send即可

I2C读入口

rt_ssize_t rt_i2c_master_recv(struct rt_i2c_bus_device *bus,
                             rt_uint16_t               addr,
                             rt_uint16_t               flags,
                             rt_uint8_t               *buf,
                             rt_uint32_t               count)
{
    rt_ssize_t ret;
    struct rt_i2c_msg msg;
    RT_ASSERT(bus != RT_NULL);

    msg.addr   = addr;
    msg.flags  = flags | RT_I2C_RD;
    msg.len    = count;
    msg.buf    = buf;

    ret = rt_i2c_transfer(bus, &msg, 1);

    return (ret == 1) ? count : ret;
}

   读入口,从功能上看,和写入口一致,仅仅是将struct rt_i2c_msg封装,另外,把读标记的操作方式固化下来。

   看完这几个入口,其实我们已经可以看出,RTT的I2C框架,目前仅支持主模式工作的I2C,并不支持I2C从模式。另外,还有一个疑问,I2C设备不是注册了几个标准事件接口(i2c_bus_device_read, i2c_bus_device_write, i2c_bus_device_control)吗,怎么感觉完全没用上?实际上,i2c_bus_device_read 和 i2c_bus_device_write这两个接口,从使用上来说,就是调用rtt文档中暴露的 rt_i2c_master_recv 和 rt_i2c_master_send接口,其实现如下:

static rt_ssize_t i2c_bus_device_read(rt_device_t dev,
                                     rt_off_t    pos,
                                     void       *buffer,
                                     rt_size_t   count)
{
    rt_uint16_t addr;
    rt_uint16_t flags;
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;

    RT_ASSERT(bus != RT_NULL);
    RT_ASSERT(buffer != RT_NULL);

    LOG_D("I2C bus dev [%s] reading %u bytes.", dev->parent.name, count);

    addr = pos & 0xffff;
    flags = (pos >> 16) & 0xffff;

    return rt_i2c_master_recv(bus, addr, flags, (rt_uint8_t *)buffer, count);
}

static rt_ssize_t i2c_bus_device_write(rt_device_t dev,
                                      rt_off_t    pos,
                                      const void *buffer,
                                      rt_size_t   count)
{
    rt_uint16_t addr;
    rt_uint16_t flags;
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;

    RT_ASSERT(bus != RT_NULL);
    RT_ASSERT(buffer != RT_NULL);

    LOG_D("I2C bus dev [%s] writing %u bytes.", dev->parent.name, count);

    addr = pos & 0xffff;
    flags = (pos >> 16) & 0xffff;

    return rt_i2c_master_send(bus, addr, flags, (const rt_uint8_t *)buffer, count);
}

I2C控制接口

   至于i2c_bus_device_control接口,我们可以通过代码层级来看

rt_err_t rt_i2c_control(struct rt_i2c_bus_device *bus,
                        int                       cmd,
                        void                      *args)
{
    rt_err_t ret;

    if(bus->ops->i2c_bus_control)
    {
        ret = bus->ops->i2c_bus_control(bus, cmd, args);
        return ret;
    }
    else
    {
        LOG_E("I2C bus operation not supported");
        return -RT_EINVAL;
    }
}

static rt_err_t i2c_bus_device_control(rt_device_t dev,
                                       int         cmd,
                                       void       *args)
{
    rt_err_t ret;
    struct rt_i2c_priv_data *priv_data;
    struct rt_i2c_bus_device *bus = (struct rt_i2c_bus_device *)dev->user_data;

    RT_ASSERT(bus != RT_NULL);

    switch (cmd)
    {
    /* set 10-bit addr mode */
    case RT_I2C_DEV_CTRL_10BIT:
        bus->flags |= RT_I2C_ADDR_10BIT;
        break;
    case RT_I2C_DEV_CTRL_TIMEOUT:
        bus->timeout = *(rt_uint32_t *)args;
        break;
    case RT_I2C_DEV_CTRL_RW:
        priv_data = (struct rt_i2c_priv_data *)args;
        ret = rt_i2c_transfer(bus, priv_data->msgs, priv_data->number);
        if (ret < 0)
        {
            return -RT_EIO;
        }
        break;
    default:
        return rt_i2c_control(bus, cmd, args);
    }

    return RT_EOK;
}

    可以看出,在i2c_dev.c这一层,标准化了I2C常用格式的控制逻辑,并将这部分数据在读写I2C时传递至驱动实现。另外,该接口也预留了驱动自定义实现接口的部分,方便各家在实际应用中自定义一些内部参数控制入口。

总结

   至此,我们基本上了理清了RTT的硬件I2C框架结构,其设计上的分层逻辑如下:

2.jpg

对于用于应用来说,最好的使用方式是直接调用设备驱动暴露的接口或者是i2s_dev.c中暴露的接口。而对于板载驱动来说,最好的方式是在驱动中调用i2c_core.c中暴露的接口来实现功能。这样便可实现代码的逻辑分层,便于后期代码迭代维护。





关键词: rtthread     框架     I2C     设备    

专家
2025-08-04 08:04:37     打赏
2楼

厉害啊



专家
2025-08-04 11:05:42     打赏
3楼

在RTT中加载某些外设驱动,我看到有好多文章说是利用应用工具自动设置的。楼主这种分析,或帮助理解驱动的处理过程,谢谢分享!


院士
2025-08-04 14:12:59     打赏
4楼

话说I2C不是要使用中断的方式来实现吗?

这个要怎么处理等待时间啊


专家
2025-08-04 20:32:42     打赏
5楼

感谢分享


专家
2025-08-04 21:37:42     打赏
6楼

感谢分享


院士
2025-08-04 09:33:38     打赏
7楼

明白了。谢谢 楼主分享


共7条 1/1 1 跳转至

回复

匿名不能发帖!请先 [ 登陆 注册 ]
口苦吃什么好 耳朵疼吃什么药 为什么会阳痿 什么的屏障 jdv是什么牌子
舌吻什么感觉 sjh是什么意思 三阳开泰是什么生肖 大佐是什么军衔 山的五行属什么
吃羊肉有什么好处 什么体质容易长结石 为什么打哈欠会流泪 8月底是什么星座 伟哥有什么副作用
舌炎吃什么药效果最好 手指长倒刺是什么原因 单车是什么意思 倒着走路有什么好处 hcg下降是什么原因
为什么邓超对鹿晗很好hcv8jop9ns1r.cn 哈工大全称是什么hcv8jop6ns7r.cn 逼是什么dajiketang.com 西红柿拌白糖又叫什么hcv8jop5ns3r.cn 甲功异常有什么症状hcv7jop4ns7r.cn
如意是什么意思hanqikai.com national是什么牌子hcv9jop1ns2r.cn 苏慧伦为什么不老hcv9jop5ns8r.cn 孔雀男是什么意思hcv7jop6ns8r.cn 云南属于什么地区hcv8jop2ns4r.cn
电轴左偏是什么原因hcv9jop8ns1r.cn 拔牙后不能吃什么食物hcv7jop5ns6r.cn 89岁属什么生肖hcv8jop7ns4r.cn 伟哥是什么hcv8jop1ns1r.cn 容易中暑是什么原因hcv8jop5ns9r.cn
口腔出血是什么病征兆hcv9jop5ns5r.cn 过敏性鼻炎用什么药效果好hcv8jop7ns2r.cn 这是什么颜色onlinewuye.com 女性得疱疹是什么症状hcv8jop3ns3r.cn p波高尖代表什么tiangongnft.com
百度