电子工程师技术服务社区
公告
登录
|
注册
首页
技术问答
厂商活动
正点原子
板卡试用
资源库
下载
文章
社区首页
文章
《rt-thread驱动框架分析》-i2c驱动
分 享
扫描二维码分享
《rt-thread驱动框架分析》-i2c驱动
RTThread
PIN驱动
驱动开发
Rice嵌入式开发
关注
发布时间: 2020-11-12
丨
阅读: 3752
## 简要 - 上一篇分析了RTT的PIN驱动,得到了很多网友的认可,很开心。很多人跟我反映写一些usb,wlan等框架,这个一步一步来,从浅到深。 - 这一片文章我们来分析rt-thread的I2C设备驱动框架,I2C也是我们经常使用到总线。 - I2C驱动框架我准备基于我的开源硬件[《GND studio 开发板》](https://mp.weixin.qq.com/s/dN88on9tVJ-BA73j2O9KRg)来做实验。通过硬件I2C和软件I2C分别来驱动一个OLED。 - 《rt-thread驱动框架分析》专辑回顾: - [《rt-thread驱动框架分析》-pin驱动](https://mp.weixin.qq.com/s/XHwTIgRBK-I_u-bNYsDkug) ## 驱动分析 ### I2C设备驱动框架图: 1. 我们先RT-Thread的I2C框架图(这是我自己理解的框架图,如果不对的地方,请指出): ![](https://rice_chen_1.gitee.io/picture/rt-thread专辑/i2c框架/1.png) - 上图是我分析的RTT的I2C框架图。主要分为三层,驱动层-核心层-设备层。如果你分析过Linux的I2C框架,它的层次也是这样子。所以你了解了RTT的I2C之后再去看Linux的I2C框架,其实问题不大。 - 驱动层:分为硬件I2C驱动和软件I2C驱动。 - 核心层: - ①其中bit_ops是RTT为软件I2C提供的中间层,它的作用:为底层模拟I2C驱动提供回调接口,为核心层提供统一I2C通信接口。 - ②而硬件I2C则直接对接核心层,提供统一I2C通信接口。 - ③RTT在核心层上,也像pin驱动那样,封装了一套API(虚线箭头),供用户直接使用。 - ④dev是提供RTT设备驱动框架的统一的API(实现箭头)。 - ⑤注意的是:模拟I2C驱动到核心层,增加了一层中间层。 - 设备层:设备就是杂七杂八的使用I2C的总线的设备。而这些设备可以选择使用RTT驱动框架的API,也可以选择RTT封装好的API。 2. 通过上述的描述,可能还没了解的很清晰。下面我根据两种不同方式的驱动,两种不同的API,逐一分析,并且会结合试验来验证。 #### driver 层: - RT-Thread的I2C驱动,分为两种类型:硬件I2C和软件I2C。在stm32的BSP中提供了软件I2C的驱动,不过为了全面介绍,硬件I2C的对接,作者也进行简单的对接和实现。 ##### 软件I2C: 1. 软件I2C的层次图: ![](https://rice_chen_1.gitee.io/picture/rt-thread专辑/i2c框架/2.png) 2. drv_soft_i2c层: 主要进行软件I2C所用到scl引脚,sda引脚初始化。scl引脚和sda引脚的获取电平和设置电平接口和延时函数(udelay)。并对接bit_opt层提供的操作结构体:struct rt_i2c_bit_ops。并通过rt_i2c_bit_add_bus注册,提供给bit_opt层回调。 - struct rt_i2c_bit_ops结构体: ``` C struct rt_i2c_bit_ops { void *data; 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; rt_uint32_t timeout; }; ``` | 函数指针 | 功能 | |---------|------| |void (*set_sda)(void *data, rt_int32_t state)| 设置SDA电平 | |void (*set_scl)(void *data, rt_int32_t state);| 设置SCL电平 | |rt_int32_t (*get_sda)(void *data);| 获取SDA电平 | |rt_int32_t (*get_scl)(void *data);| 获取SCL电平 | |void (*udelay)(rt_uint32_t us);| 软件I2C时序所需要的的延时函数 | - rt_i2c_bit_add_bus接口,主要注册软件I2C的引脚操作的回调函数。 3. bit_opt层:可以归纳为驱动层。其主要实现软件I2C的时序等逻辑,并提供对应的I2C的收发处理函数,为drv_soft_i2c层提供提供了(struct rt_i2c_bit_ops)注册接口和(rt_i2c_bit_add_bus)接口,为i2c_core层提供主机模式的数据处理函数。bit_opt层主要对接到i2c_core层提供操作结构体:struct rt_i2c_bus_device_ops以及i2c总线的注册函数rt_i2c_bus_device_register: - struct rt_i2c_bus_device_ops结构体: ``` C struct rt_i2c_bus_device_ops { rt_size_t (*master_xfer)(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num); rt_size_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, rt_uint32_t, rt_uint32_t); }; ``` | 函数指针 | 功能 | |---------|------| |master_xfer| 主机模式的数据收发 | |slave_xfer| 从机模式的数据收发 | |i2c_bus_control| i2c总线的的操作参数等设置 | - rt_i2c_bus_device_register接口:主要注册I2C总线和收发数据的回调函数。 4. 软件I2C驱动总结:rt-thread的软件I2C,如果要对接其他平台,只需要对接好结构体:struct rt_i2c_bit_ops。而软件I2C的逻辑完全不用理会,全部由bit_opt层管理。 ##### 硬件I2C 1. 硬件I2C的层次图: ![](https://rice_chen_1.gitee.io/picture/rt-thread专辑/i2c框架/3.png) 2. drv_hw_i2c层:没有软件I2C的bit_opt层,而是直接对接i2c_core层提供的结构体:struct rt_i2c_bus_device_ops。作者为了简单说明,写了个例子(简单粗暴的例子): ``` C struct rt_i2c_bus_device i2c1_bus; I2C_HandleTypeDef hi2c1; static rt_err_t i2c_hw_init(void) { hi2c1.Instance = I2C1; hi2c1.Init.ClockSpeed = 100000; hi2c1.Init.DutyCycle = I2C_DUTYCYCLE_2; hi2c1.Init.OwnAddress1 = 0; hi2c1.Init.AddressingMode = I2C_ADDRESSINGMODE_7BIT; hi2c1.Init.DualAddressMode = I2C_DUALADDRESS_DISABLE; hi2c1.Init.OwnAddress2 = 0; hi2c1.Init.GeneralCallMode = I2C_GENERALCALL_DISABLE; hi2c1.Init.NoStretchMode = I2C_NOSTRETCH_DISABLE; if (HAL_I2C_Init(&hi2c1) != HAL_OK) { Error_Handler(); } return RT_EOK; } static rt_size_t i2c_xfer(struct rt_i2c_bus_device *bus, struct rt_i2c_msg msgs[], rt_uint32_t num) { struct rt_i2c_msg *msg; rt_int32_t i, ret; for(i = 0; i< num; i++) { msg = &msgs[i]; if(msg->flags & RT_I2C_RD) { HAL_I2C_Master_Receive(&hi2c1, msg->addr, msg->buf, msg->len, 100); } else { HAL_I2C_Master_Transmit(&hi2c1, msg->addr, msg->buf, msg->len, 100); } } ret = i; return ret; } static const struct rt_i2c_bus_device_ops i2c_bus_ops = { i2c_xfer, RT_NULL, RT_NULL }; int rt_i2c_hw_init(void) { i2c_hw_init(); i2c1_bus.ops = &i2c_bus_ops; rt_i2c_bus_device_register(&i2c1_bus, "hw_i2c"); return RT_EOK; } INIT_DEVICE_EXPORT(rt_i2c_hw_init); ``` 3. 以上硬件I2C对接,我只是对接了master_xfer。 4. 硬件I2C驱动总结:如果你是采用硬件I2C,那么就不需要去关乎bit_opt层,其实通过上面的描述,你不难理解bit_opt层和drv_hw_i2c层其实对接都是i2c_core层的结构。这也是我为什么把bit_opt层归纳为驱动层来讲解了。 #### core 层: 1. i2c_core层为驱动层提供结构体:struct rt_i2c_bus_device_ops。为设备层提供I2C的数据收发处理函数。 2. 其实i2c_core层主要是封装了一层API直接提供给用户层设备调用。 | 函数 | 说明 | |---------|------| |rt_i2c_bus_device_find| 查找i2c总线 | |rt_i2c_transfer| 主机模式的i2c数据传输 | |rt_i2c_master_send| 主机模式的i2c数据发送 | |rt_i2c_master_recv| 主机模式的i2c数据接受 | 3. 其中rt_i2c_master_send函数和rt_i2c_master_recv函数是调用rt_i2c_transfer函数,而rt_i2c_transfer函数是调用master_xfer回调函数。如果你是使用这些接口,那么device层可以不用理会。 #### device 层: 1. i2c_dev层,对接rt-thread设备驱动框架。提供read,write,control函数。并通过函数rt_device_register注册到设备驱动框架。下面我也会使用这些接口来实现控制OLED。 | 函数 | 说明 | |---------|------| |i2c_bus_device_read| I2C读操作 | |i2c_bus_device_write| I2C写操作 | |i2c_bus_device_control| I2C总线的控制 | ### 软件I2C设计: - 关键代码, 注意软件I2C的地址需要偏移一位: ``` C struct rt_i2c_bus_device *i2c_bus; #define OLED_I2C_BUS_NAME "i2c1" #define OLED_ADDRESS 0x3c static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t data) { rt_uint8_t buf[2]; struct rt_i2c_msg msgs; buf[0] = reg; buf[1] = data; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_WR; msgs.buf = buf; msgs.len = 2; /@@* 调用I2C设备接口传输数据 */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK; } static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf) { struct rt_i2c_msg msgs; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_RD; msgs.buf = buf; msgs.len = len; /@@* 调用I2C设备接口传输数据 */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK; } int oled_init(void) { i2c_bus = rt_i2c_bus_device_find(OLED_I2C_BUS_NAME); if(i2c_bus == RT_NULL) { rt_kprintf("find i2c bus fail!\n"); return RT_ERROR; } rt_kprintf("find i2c bus success!\n"); ...... } ``` - 效果图: ![](https://rice_chen_1.gitee.io/picture/rt-thread专辑/i2c框架/5.jpg) ### 硬件I2C设计: - 关键代码,注意硬件I2C的地址不需要偏移: ``` C struct rt_i2c_bus_device *i2c_bus; #define OLED_I2C_BUS_NAME "hw_i2c" #define OLED_ADDRESS 0x78 static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t data) { rt_uint8_t buf[2]; struct rt_i2c_msg msgs; buf[0] = reg; buf[1] = data; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_WR; msgs.buf = buf; msgs.len = 2; /@@* 调用I2C设备接口传输数据 */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK; } static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t len, rt_uint8_t *buf) { struct rt_i2c_msg msgs; msgs.addr = OLED_ADDRESS; msgs.flags = RT_I2C_RD; msgs.buf = buf; msgs.len = len; /@@* 调用I2C设备接口传输数据 */ rt_i2c_transfer(bus, &msgs, 1); return RT_EOK; } int oled_init(void) { i2c_bus = rt_i2c_bus_device_find(OLED_I2C_BUS_NAME); if(i2c_bus == RT_NULL) { rt_kprintf("find i2c bus fail!\n"); return RT_ERROR; } rt_kprintf("find i2c bus success!\n"); ...... } ``` ![](https://rice_chen_1.gitee.io/picture/rt-thread专辑/i2c框架/4.jpg) ### 使用驱动框架API实现: - 关键代码: ``` C #define OLED_I2C_BUS_NAME "hw_i2c" struct rt_device *dev_i2c; #define OLED_ADDRESS 0x78 static rt_err_t write_reg(struct rt_device *dev, rt_uint8_t reg, rt_uint8_t data) { rt_uint8_t buf[2]; rt_off_t pos; rt_uint16_t addr = OLED_ADDRESS; rt_uint16_t flags = RT_I2C_WR; buf[0] = reg; buf[1] = data; pos = (flags << 16) | addr; rt_device_write(dev, pos, buf, 2); return RT_EOK; } static rt_err_t read_regs(struct rt_device *dev, rt_uint8_t len, rt_uint8_t *buf) { rt_off_t pos; rt_uint16_t addr = OLED_ADDRESS; rt_uint16_t flags = RT_I2C_WR; pos = (flags << 16) | addr; rt_device_write(dev, pos, buf, 2); return RT_EOK; } int oled_init(void) { dev_i2c = rt_device_find(OLED_I2C_BUS_NAME); if(dev_i2c == RT_NULL) { rt_kprintf("find i2c bus fail!\n"); return RT_ERROR; } rt_kprintf("find i2c bus success!\n"); rt_device_open(dev_i2c, RT_DEVICE_OFLAG_RDWR); .... return RT_EOK; } ``` ## 总结 - 有了I2C驱动框架,对于上层来说,不管硬件I2C还是软件I2C调用接口都是一样的。 - rt-thread为了方便,直接在核心层提供了一套API,这样用户层调用就更加方便。
关注微信公众号『Rice嵌入式开发技术分享』,后台回复“微信”添加作者微信,备注”入群“,便可邀请进入技术交流群。 ![](https://rice_chen_1.gitee.io/picture/logo/logo_.jpg)
原创作品,未经权利人授权禁止转载。详情见
转载须知
。
举报文章
点赞
(
0
)
Rice嵌入式开发
关注
评论
(0)
登录后可评论,请
登录
或
注册
相关文章推荐
MK-米客方德推出工业级存储卡
Beetle ESP32 C3 蓝牙数据收发
Beetle ESP32 C3 wifi联网获取实时天气信息
开箱测评Beetle ESP32-C3 (RISC-V芯片)模块
正点原子数控电源DP100测评
DP100试用评测-----开箱+初体验
Beetle ESP32 C3环境搭建
【花雕体验】16 使用Beetle ESP32 C3控制8X32位WS2812硬屏之二
X
你的打赏是对原创作者最大的认可
请选择打赏IC币的数量,一经提交无法退回 !
100IC币
500IC币
1000IC币
自定义
IC币
确定
X
提交成功 ! 谢谢您的支持
返回
我要举报该内容理由
×
广告及垃圾信息
抄袭或未经授权
其它举报理由
请输入您举报的理由(50字以内)
取消
提交