本来想着i2c和spi是一样的,标题都想抄袭成《rtt设备io框架面向对象学习-i2c总线和设备》,然后看过源码发现,i2c没有分开总线和设备,我想着正常它和spi一样有总线和设备,设备存在竞争。估计是因为i2c设备可以通过i2c地址区分,所以不需要来个i2c设备了吧。
i2c总线分为硬件i2c总线和软件模拟i2c总线。
按照面向对象的思想,要抽象出硬件i2c总线和软件i2c总线的相同点和不同点。相同点就变成了i2c总线基类,不同点就是各个子类的私有特性。
rtt就是这么干的,共同点是什么?方法——都得有配置方法和数据传输方法等,于是抽象出了i2c的方法:
struct rt_i2c_bus_device_ops
{
rt_ssize_t (*master_xfer)(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num);
rt_ssize_t (*slave_xfer)(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num);
rt_err_t (*i2c_bus_control)(struct rt_i2c_bus_device *bus, int cmd, void *args);
};
抽象出3个公共方法,主设备通信接口,从设备通信接口和控制方法。
然后作为成员组成i2c总线基类——rt_i2c_bus_device:
i2c.h中
struct rt_i2c_bus_device
{
struct rt_device parent;
const struct rt_i2c_bus_device_ops *ops; rt_uint16_t flags;
struct rt_mutex lock;
rt_uint32_t timeout;
rt_uint32_t retries;
void *priv;
};
缺图对象图,ops展开方法用纯虚方法表示或者指针,但ops展开后成为一个个纯虚方法更为贴切。
对于硬件i2c总线子类,则通过调用硬件sdk来实现i2c基类的ops操作方法,实现完美闭环。
缺图对象图,ops展开的一个个方法用虚方法表示或者普通方法即可,意味着重写父类方法。
待补充
对于软件i2c总线子类,共同点就是软件gpio也要实现上面3个公共方法,差异点就是要用gpio模拟硬件i2c的2线通信接口——抽象出软件i2c通信接口。因为i2c通信时序是共同的,如果放到驱动层,每个bsp都要实现一样逻辑的通信函数——重复造轮子——对于这种“重复”性的,就抽象出来放到上层最合理。——就此我也知道了在c++中什么时候需要虚函数/纯虚函数那要看下层有没有共性的东西。
至于i2c软总线通信接口的实现需要哪些信息,rtt抽象出来的如下:
i2c-bit-ops.h 中
struct rt_i2c_bit_ops
{
void data; / private data for lowlevel routines */
void (*set_sda)(void *data, rt_int32_t state); void (*set_scl)(void *data, rt_int32_t state); rt_int32_t (*get_sda)(void *data);
rt_int32_t (*get_scl)(void *data);
void (udelay)(rt_uint32_t us);
rt_uint32_t delay_us; / scl and sda line delay / rt_uint32_t timeout; / in tick */
};
为何如此抽象?i2c通信就用到2线,所以只要提供操作2线的高低电平接口即可控制通信了,其时序是共通的,再加上延时即可完成硬件无关的i2c通信时序,所以差异点在于操作硬件高低电平的函数,延时函数等的实现不同,于是把它们抽象出来。
模拟通信接口的方法在i2c-bit-ops.c中,从该c中可以看到它只提供了i2c主设备通信,其他两个i2c公共方法(从设备通信和控制方法)均没有支持。待开发。另外在i2c-bit-ops.c中可以看到i2c通信硬件无关的时序接口,要真正驱动引脚还得实现struct rt_i2c_bit_ops的接口。
为何要抽象出来?因为各个硬件操作gpio具体实现是不同的,但是这些方法是都一样,所以抽象出来——跨硬件平台,这个框架才叫框架,这个框架才有意义。
rtt的h文件中没有关于抽象出的i2c软总线类,不过可以参考它的soft_i2c.c中抽象出i2c软总线类:
struct rt_soft_i2c
{
struct rt_i2c_bus_device i2c_bus;
struct rt_i2c_bit_ops ops;
};
就是继承i2c总线基类+软件i2c通信接口方法。
soft_i2c.c中的实现自洽的,啥叫自洽?就是它用到了pin设备,如果该bsp实现了pin框架对接,那么soft_i2c.c就完成了整个的软件i2c配置,只需要开启宏和定义下软件i2c引脚就行了,不需要bsp再进行创建软件i2c总线设备了。不过也可以,只要名字不一样也可以,不过结果一样,以我看来有pin对接就不要在bsp层重复实现了。
如果没有实现pin设备框架对接,那么就需要bsp驱动层实现软总线,各个厂家bsp创建各自的软i2c总线子类对象,实现软件i2c的ops方法即可。
缺图对象图,
(1)bus—>ops展开的一个个方法用虚方法表示或者普通方法即可,意味着重写父类i2c总线基类的方法。
(2)软i2c总线子类的ops展开的一个个方法用纯虚方法表示或者指针,但ops展开后成为一个个纯虚方法更为贴切。
bsp / stm32 / libraries / HAL_Drivers / drivers 目录:
drv_soft_i2c.h中定义了最终用以实例化的stm32的i2c类:
struct stm32_i2c
{
struct rt_i2c_bit_ops ops;
struct rt_i2c_bus_device i2c_bus;
};
这个看着不舒服,和io框架不一致,如果调整下位置就好了,如下
struct stm32_i2c
{
**struct rt_i2c_bus_device i2c_bus; **
struct rt_i2c_bit_ops ops;
};
除了继承软总线基类外还增加了软总线基类定义的特有方法。
drv_soft_i2c.c中:
实例化了该类:
static struct stm32_i2c i2c_obj[sizeof(soft_i2c_config) / sizeof(soft_i2c_config[0])];
重写了软总线基类的方法:
static const struct rt_i2c_bit_ops stm32_bit_ops_default =
{
.data = RT_NULL,
.set_sda = stm32_set_sda,
.set_scl = stm32_set_scl,
.get_sda = stm32_get_sda,
.get_scl = stm32_get_scl,
.udelay = rt_hw_us_delay,
.delay_us = 1,
.timeout = 100
};
对象的初始化/构造函数:
rt_hw_i2c_init,
发现把重写的i2c软总线基类的方法挂到了i2c基类的priv成员
i2c_obj[i].ops = stm32_bit_ops_default;
i2c_obj[i].i2c_bus.priv = &i2c_obj[i].ops;
所以在components / drivers / i2c / i2c-bit-ops.c 中i2c_bit_xfer/i2c_bit_send_address/i2c_recv_bytes/i2c_send_bytes等函数,它们又通过i2c基类的priv成员拿到了stm32重写的i2c软总线基类方法,就可以操作stm32的gpio来模拟i2c通信。
无。通过i2c从机地址就能区分i2c设备,就没有必要专门搞个设备累了,多余了。
/ components / drivers / i2c下有4个文件:
i2c-bit-ops.c
i2c_core.c
i2c_dev.c
soft_i2c.c
这4个文件的关系:
i2c_dev.c是重写/实现rt_device基类的方法的文件,也是初始化i2c基类的接口所在
rt_i2c_bus_device_device_init。
i2c_core.c是中转站/中间人,因为i2c_dev.c中重写设备基类的方法,这些方法里调用了i2c_core.c提供的方法如rt_i2c_transfer,而i2c_core.c中这些方法比如 rt_i2c_transfer,实现内部是调用的i2c的3个公共方法比如bus->ops->master_xfer,
而这3个公共方法对于硬件i2c总线则走硬件总线的实现,
对于软件模拟i2c总线则走i2c-bit-ops.c中的方法,
前面知道i2c-bit-ops.c中只实现了主设备通信即只有i2c_bit_xfer如下:
static const struct rt_i2c_bus_device_ops i2c_bit_bus_ops =
{
i2c_bit_xfer,
RT_NULL,
RT_NULL
};
以此来看,i2c_core.c可不就是中间人/岔路口/分发站/跳转点么?!而i2c_bit_xfer函数里使用到i2c软总线抽象的那些延时2线高低电平接口,而这些接口可以是bsp的驱动层实现的,也可以是soft_i2c.c里实现的——通过pin设备框架控制2线高低电平(当然还需要实现us延时函数)。
而soft_i2c.c是对于支持pin设备的bsp的软总线实现实例,它已是自洽的,不需要bsp再重复造轮子了。而它则可以作为参照对于没有支持pin和延时函数的bsp实现软总线的参考例子。
它通过pin设备实现了软i2c总线的模拟接口,这样i2c-bit-ops.c中通信方法调用2线控制电平和延迟函数就有了落脚点。而pin设备就需要驱动层的gpio驱动支持的,gpio驱动就调用了硬件sdk,自然能驱动硬件引脚。这样屏蔽了硬件差异,实现跨硬件平台。
官方文档说的就是如何使用的i2c总线,而上面学的是注册/初始化/构造流程。