C语言封装技术
最近在研究RT_Thread的源码,发现i2c驱动里有个地方使用的封装的技术,很少见,又感觉很是精妙。所以特意写个测试模拟程序,并贴上来和大家分享。个人觉得它非常经典。它使用的模拟的i2c。代码如下:
#include "stdafx.h"
typedef signed int rt_int32_t;
typedef unsigned int rt_uint32_t;
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 */
};
//结构体功能函数的使用 它的精妙之处在这里,用宏把结构体函数使用起来
#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)
//结构体功能函数的使用
#define SDA_L(ops) SET_SDA(ops, 0)
#define SDA_H(ops) SET_SDA(ops, 1)
#define SCL_L(ops) SET_SCL(ops, 0)
//提供对外使用
inline void i2c_delay(struct rt_i2c_bit_ops *ops)
{
ops->udelay((ops->delay_us + 1) >> 1);
}
inline void i2c_delay2(struct rt_i2c_bit_ops *ops)
{
ops->udelay(ops->delay_us);
}
//以下为rt_i2c_bit_ops指针函数
void i2c_set_sda(void *data, rt_int32_t state)
{
if(state)
{
printf("sda->H\r\n");
}else
{
printf("sda->L\r\n");
}
}
void i2c_set_scl(void *data, rt_int32_t state)
{
if(state)
{
printf("scl->H\r\n");
}else
{
printf("scl->L\r\n");
}
}
rt_int32_t i2c_get_sda(void *data)
{
printf("sda return 1\r\n");
return 1;
}
rt_int32_t i2c_get_scl(void *data)
{
printf("scl return 1\r\n");
return 1;
}
void udelay(rt_uint32_t us)
{
printf("udelay %d us\r\n",us);
}
//以上为rt_i2c_bit_ops指针函数
//模拟i2c的其他信号可以类似i2c_restart一样调用
static void i2c_restart(struct rt_i2c_bit_ops *ops)
{
SDA_H(ops);
i2c_delay(ops);
SDA_L(ops);
i2c_delay(ops);
SCL_L(ops);
}
int main(int argc, char* argv[])
{
rt_i2c_bit_ops i2c_ops;
i2c_ops.data = (void *)0x88;
i2c_ops.delay_us = 5;
i2c_ops.udelay = udelay;
i2c_ops.get_scl = i2c_get_scl;
i2c_ops.get_sda = i2c_get_sda;
i2c_ops.set_scl = i2c_set_scl;
i2c_ops.set_sda = i2c_set_sda;
printf("==========TEST===========!\n");
以上设置完专门的i2c接口后,类似与i2c_restart这样的i2c发送和接收的业务逻辑函数基本上不用修改了,直接按不同的i2c接口传参即可完成i2c的功能。
i2c_restart(&i2c_ops);
return 0;
}
函数指针使用的巧妙,高 顶顶,待会研究研究 语法太高深啦,不是太看得明白. 不错,收藏了 谢谢了帮助很大 我的项目刚好可以用上 及时雨 谢谢你的分享,写得精妙, 太晚了,明天研究 ooc思想啊,值得学习。 超核K60库的作者yandld就是使用了这种方法封装SPI设备啊{:lol:} 用指针和结构体,感觉确实要巧妙很多~ 收藏!学习。 高,实在是高。
freeMODBUS里也有类似的做法。 不错,下来学习一下, 正在学习,帮顶 我也上一段吧!
struct PolyFunc
{
void (*oneParam)( struct PolyFunc *, int);
int (*withReturn)( struct PolyFunc *);
};
void someFunc( struct PolyFunc * pf )
{
int r = (pf->withReturn)( pf );
(pf->oneParam)( pf, r );
}
struct PolyBase
{
struct PolyFunc interfacePolyFunc;
int value;
};
void PolyBase_oneParam( struct PolyFunc * pf, int a );
int PolyBase_withReturn( struct PolyFunc * pf );
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc.oneParam = &PolyBase_oneParam;
pb->interfacePolyFunc.withReturn = &PolyBase_withReturn;
}
struct PolyBase pb;
init_PolyBase( &pb );
pb.value= 123;
someFunc( &pb.interfacePolyFunc );
int PolyBase_withReturn( struct PolyFunc * pf )
{
struct PolyBase * this = (struct PolyBase*)pf;
return this->value;
}
struct PolyDerived
{
struct PolyBase base_PolyBase;
char prefix;
};
void PolyDerived_oneParam( struct PolyFunc * pf, int a );
void init_PolyDerived( struct PolyDerived * pd )
{
init_PolyBase( (struct PolyBase*)pd );
pd->base_PolyBase.interfacePolyFunc.oneParam = &PolyDerived_oneParam;
strcpy( pd->prefix, "ABC:" );
}
struct PolyDerived pd;
init_PolyDerived( &pd );
pd.base_PolyBase.value= 123;
someFunc( (struct PolyFunc*)&pd );
// Virtual Table
struct PolyBase
{
struct PolyFunc * interfacePolyFunc;
int value;
};
struct PolyFunc PolyBase_VTable =
{
&PolyBase_oneParam,
&PolyBase_withReturn
};
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc = &PolyBase_VTable;
}
void someFunc( struct PolyFunc * * pf )
{
int r = ((*pf)->withReturn)( pf );
((*pf)->oneParam)( pf, r );
}
void calling()
{
struct PolyBase pb;
init_PolyBase( &pb );
someFunc( &pb.interfacePolyFunc );
}
yaoyutaoTom 发表于 2015-7-24 11:17
我也上一段吧!
struct PolyFunc
{
yaoyutaoTom,这段代码展现了c语言的封装、继承、多态还利用上了虚函数表技术。对于学习c语言的抽象设计,这段代码可以起到启发的作用。稍微加下注解
// struct01.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <string.h>
#if 0
//接口封装
struct PolyFunc
{
void (*oneParam)( struct PolyFunc *, int);
int (*withReturn)( struct PolyFunc *);
};
//使用demo
void someFunc( struct PolyFunc * pf )
{
int r = (pf->withReturn)( pf );
(pf->oneParam)( pf, r );
}
//接口 + 属性 数据
struct PolyBase
{
struct PolyFunc interfacePolyFunc;
int value;
};
void PolyBase_oneParam( struct PolyFunc * pf, int a );
int PolyBase_withReturn( struct PolyFunc * pf );
//初始化接口
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc.oneParam = &PolyBase_oneParam;
pb->interfacePolyFunc.withReturn = &PolyBase_withReturn;
}
//接口实现
int PolyBase_withReturn( struct PolyFunc * pf )
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Get Value\r\n");
return PolyBaseS->value;
}
//接口实现
void PolyBase_oneParam( struct PolyFunc * pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Set Value\r\n");
PolyBaseS->value = 5;
}
//继承PolyBase
struct PolyDerived
{
struct PolyBase base_PolyBase;
char prefix;
};
//多态 重写PolyDerived PolyDerived_oneParam
void PolyDerived_oneParam( struct PolyFunc * pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)pf;
printf("Derived Set Value\r\n");
PolyBaseS->value = 5;
}
//初始化PolyDerived
void init_PolyDerived( struct PolyDerived * pd )
{
init_PolyBase( (struct PolyBase*)pd );
pd->base_PolyBase.interfacePolyFunc.oneParam = &PolyDerived_oneParam;
strcpy( pd->prefix, "ABC:" );
}
//测试demo
int main(int argc, char* argv[])
{
//封装
{
struct PolyBase pb;
init_PolyBase( &pb );
pb.value= 123;
someFunc( &pb.interfacePolyFunc );
}
//继承
{
struct PolyDerived pd;
init_PolyDerived( &pd );
pd.base_PolyBase.value= 123;
someFunc( (struct PolyFunc*)&pd );
}
return 0;
}
#endif
// Virtual Table //虚函数表技术
struct PolyBase
{
struct PolyFunc * interfacePolyFunc;
int value;
};
//对外提供接口
struct PolyFunc
{
void (*oneParam)( struct PolyFunc **, int);
int (*withReturn)( struct PolyFunc **);
};
//实现接口
int PolyBase_withReturn( struct PolyFunc ** pf )
{
struct PolyBase *PolyBaseS= (struct PolyBase*)(*pf);
printf("Get Value\r\n");
return PolyBaseS->value;
}
//实现接口
void PolyBase_oneParam( struct PolyFunc ** pf, int a)
{
struct PolyBase *PolyBaseS= (struct PolyBase*)(*pf);
printf("Set Value\r\n");
PolyBaseS->value = 5;
}
//初始化虚拟函数表
struct PolyFunc PolyBase_VTable =
{
&PolyBase_oneParam,
&PolyBase_withReturn
};
//初始化接口
void init_PolyBase( struct PolyBase * pb )
{
pb->interfacePolyFunc = &PolyBase_VTable;
}
//demo 调用
void someFunc( struct PolyFunc * * pf )
{
int r = ((*pf)->withReturn)( pf );
((*pf)->oneParam)( pf, r );
}
//test demo
void calling()
{
struct PolyBase pb;
init_PolyBase( &pb );
someFunc( &pb.interfacePolyFunc );
}
int main()
{
calling();
return 1;
}
设计的相当精妙,学习了 学习了,谢谢分享 一开始没理解这里#define SET_SDA(ops, val) ops->set_sda(ops->data, val), 后来查了一下,了解到宏定义函数是没有类型的, 任何参数类型都可以。所以传数据的时候一定要注意参数类型。 学习了。 技术宅 发表于 2015-7-25 09:02
一开始没理解这里#define SET_SDA(ops, val) ops->set_sda(ops->data, val), 后来查了一下,了解到 ...
是的,感觉这里用的非常的巧妙,就贴出来和大家分享,看看大伙的意见,一来可以加深自己的理解和印象,二来可以收获不同坛友的不同角度的看法。 是的,要好好研究一下! 咬着牙啃完了,不容易吸收啊 不错,谢谢分享! 学习了 这是常识,做过大规模代码的都会这么干。 amigenius 发表于 2015-7-25 14:11
这是常识,做过大规模代码的都会这么干。
看来这位朋友的代码阅历比较丰富,我要不是碰到了RT_Thread的,还真的没有机会体会到这种感觉。 这常识,,,搞mcu的多是 独行侠,,,,很少用上啊{:lol:}学习了{:victory:} 这有点OO中多态的意思,驱动代码体现了“要做什么”,至于“如何做”,交给指针函数完成 jathenal 发表于 2015-7-25 16:45
这有点OO中多态的意思,驱动代码体现了“要做什么”,至于“如何做”,交给指针函数完成 ...
多态只是其中的一部分,我比较欣赏的是这里宏对结构体函数的调用封装,这部分业务逻辑基本上不用再改。不同的i2c接口可以通用调用。
//模拟i2c的其他信号可以类似i2c_restart一样调用
static void i2c_restart(struct rt_i2c_bit_ops *ops)
{
SDA_H(ops);
i2c_delay(ops);
SDA_L(ops);
i2c_delay(ops);
SCL_L(ops);
} 学习了不错 好东西。 还没有领会到,具体的好处,哪位高手普及一下? 没看懂,还需要努力啊 说白了就是函数指针的妙用,UCGUI中大量使用 这种封装方式好,以后再开发就方便了,顺便问下楼主有没有关于C语言封装的书呢? abcdzhy 发表于 2015-7-29 08:13
这种封装方式好,以后再开发就方便了,顺便问下楼主有没有关于C语言封装的书呢? ...
有一本<c嵌入式编程设计模式>,但是像我发的帖子这样的封装技术,是在国人开发RT_Thread嵌入式操作系统上学来的。 TMD,没看明白,太笨了,还是去焊板子了 道行潜!实在是看不懂!境界不够!ops->set_sda(ops->data, val),这里的ops在哪? lianzhong008 发表于 2015-7-25 07:13
yaoyutaoTom,这段代码展现了c语言的封装、继承、多态还利用上了虚函数表技术。对于学习c语言的抽象设计 ...
醍醐灌顶啊!
真是好东西。 看看 学习了 多谢 刚好最近需要iic,顶一下,拿来试试 rt-thread借鉴了c++的封装继承多态,在开发说明里有写的。上面这种写法在大的项目里用的比较多,要是全是自己写就用不上了。我去年就看过这种方法了,到现在都没用上 不错,这几天正在看java,非常羡慕java里面的封装方式,一直想在C里面实现,看来还真可以模拟实现 尤其是在多人开发项目中很有用。 没有怎么看明白怎么把CLK SDLIO口与程序怎么好应起来 。 waymcu 发表于 2015-8-11 09:07
没有怎么看明白怎么把CLK SDLIO口与程序怎么好应起来 。
IO驱动在有段打印信息那里完善,这个方式的好处就是I2C的时序是固定的,也就是逻辑部分不用改,移植时把IO驱动改了就完了。
这个例子的意义在于提供一种思路,自己做I2C时用不着这么麻烦。 这样的代码很方便,看起也很漂亮,不过我暂时是没精力搞这些了,只能是留待以后了。 jzkn 发表于 2015-8-11 09:24
IO驱动在有段打印信息那里完善,这个方式的好处就是I2C的时序是固定的,也就是逻辑部分不用改,移植时把I ...
IO好像与void *data 这个地址 对应起来了? waymcu 发表于 2015-8-11 09:34
IO好像与void *data 这个地址 对应起来了?
差不多就这意思吧,他也没写死,Data还可以做其他用途 ,看你实现得时候得需求了。 学习一下 mark一下 回来在程序里试一下 值得借鉴!! 用过,不过不多,换不同MCU时就直接可以移植,再调节IO就可以了 果然还是见识太短 好东西,mark 封装的好漂亮~~~ 帅啊,真是不错。谢谢楼主 最近也在开始看RTT Okar 发表于 2015-7-24 10:19
超核K60库的作者yandld就是使用了这种方法封装SPI设备啊
贴出来 大家学习下呀 niba 发表于 2016-12-19 15:18
贴出来 大家学习下呀
感兴趣的肯定自己会去搜了,网上大片的资料。 是不是STM32的驱动库也是类似的包装。之前用那些库函数都直接调用,没仔细研究。 不错,收藏了 linux内核驱动就是这样的。 感谢楼主分享 标记慢慢看,最近一直在焊板子,很少写代码了! C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。
很精妙,学习了。 rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。
说的是! rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。{:smile:} rain73 发表于 2017-1-1 23:07
C本是面向过程的语言,把C当C++来用,不如直接用面向对象的C++。
层主不妨试试用C++写一下楼主位的代码,看看是什么效果。
注意,主楼代码的结构内指针是虚的,即可以指向不同的实现实体。
拭目以待。。 stdio 发表于 2017-1-2 15:49
层主不妨试试用C++写一下楼主位的代码,看看是什么效果。
注意,主楼代码的结构内指针是虚的,即可以指向 ...
有段时间没动代码了,一下子还真写不出。不过要说有什么C能实现而C++不能实现的,那是不太可能!
C++的对象多得实在是太复杂了,相比之下上面几个概念简直是小儿科。 本帖最后由 stdio 于 2017-1-3 14:15 编辑
c++比较复杂。 mcu用io模拟多路有什么简洁的写法? mcu用io模拟多路i2c有什么简洁的写法? RT中很多组件都是这样做的,把应用层和底层分开来,中间层封装和衔接设备,并注册设备,然后就可以在不管底层的情况下使用应用层了,也便于分开协同开发。 linux驱动都是这么玩的!
涨知识了,要实际练习一下。
页:
[1]