Toggle navigation
首页
问答
文章
积分商城
专家
专区
更多专区...
文档中心
返回主站
搜索
提问
会员
中心
登录
注册
I2C_IIC
RTT下的I2C框架
发布于 2024-04-23 17:31:01 浏览:1682
订阅该版
[tocm] # I2C驱动框架 ## 前言 I2C得益于它的便捷性与简洁性,已经成为我们身边最常见的一个通信协议。最近刚好学习了RTT的I2C框架,结合星火一号上板载的AP3216C传感器来看看RTT是如何使用I2C的,以及在不同板子上,I2C的驱动有哪些区别。(着急的同学可以直接看前面的框架图和后面的初始化流程图) ## 正文 ### 框架图 我们这里先看一下我总结的一个 I2C 框架图。这个框架图展示了 I2C 的分层设计以及对应层之间的一个API接口。其中 Core层 跟 Device层 的 API 接口是可以供给给用户去使用的。我们按照这个框架,然后再结合 AP3216C 所给的示例代码去进行学习与了解。 ![I2C框架.drawio.png](https://oss-club.rt-thread.org/uploads/20240423/0b547d58732b0dd5189ab04b6a9f776a.png.webp) ### 代码 我们先来看一下 main() 函数里的代码。其主要的功能是确定了i2c总线为"i2c2",完成AP3216C的初始化工作,然后循环一百次读取接近感应值跟光照强度值并打印出来。 ```C #include
#include
#include
#include "ap3216c.h" #define DBG_TAG "main" #define DBG_LVL DBG_LOG #include
int main(void) { ap3216c_device_t dev; const char *i2c_bus_name = "i2c2"; int count = 0; /* 初始化 ap3216c */ dev = ap3216c_init(i2c_bus_name); if (dev == RT_NULL) { LOG_E("The sensor initializes failure."); return 0; } while (count++ < 100) { rt_uint16_t ps_data; float brightness; /* 读接近感应值 */ ps_data = ap3216c_read_ps_data(dev); if (ps_data == 0) { LOG_D("object is not proximity of sensor."); } else { LOG_D("current ps data : %d.", ps_data); } /* 读光照强度值 */ brightness = ap3216c_read_ambient_light(dev); LOG_D("current brightness: %d.%d(lux).", (int)brightness, ((int)(10 * brightness) % 10)); rt_thread_mdelay(1000); } return 0; } ``` 我们再进入到`ap3216c_init()`初始化函数中,查看一下其是如何进行初始化的。其先寻找到了相应I2CBus的句柄,并且将其赋值给了`ap3216c_device`结构体中的`i2c`成员,然后再重置ap3216c设备,如果没有出现问题的话,ap3216c设备已经成功挂载到i2c总线上了,我们可以在后续代码中正常使用了。看到这里我们可能会疑惑,i2c总线相关的配置我们明明还没有初始化,我们怎么就能够直接使用了呢?--这个就是RTT的一个特点,像这类设备的初始化步骤我们在板级初始化的时候就自动完成,不需要我们再手动调用(后面会讲解到)。 ```C ap3216c_device_t ap3216c_init(const char *i2c_bus_name) { ap3216c_device_t dev; RT_ASSERT(i2c_bus_name); dev = rt_calloc(1, sizeof(struct ap3216c_device)); if (dev == RT_NULL) { LOG_E("Can't allocate memory for ap3216c device on '%s' ", i2c_bus_name); rt_free(dev); return RT_NULL; } //寻找i2c总线句柄 dev->i2c = rt_i2c_bus_device_find(i2c_bus_name); if (dev->i2c == RT_NULL) { LOG_E("Can't find ap3216c device on '%s'", i2c_bus_name); rt_free(dev); return RT_NULL; } //设置互斥锁 dev->lock = rt_mutex_create("mutex_ap3216c", RT_IPC_FLAG_FIFO); if (dev->lock == RT_NULL) { LOG_E("Can't create mutex for ap3216c device on '%s'", i2c_bus_name); rt_free(dev); return RT_NULL; } /* reset ap3216c */ reset_sensor(dev); rt_thread_delay(rt_tick_from_millisecond(100)); // delay at least 100ms ap3216c_set_param(dev, AP3216C_SYSTEM_MODE, AP3216C_MODE_ALS_AND_PS); rt_thread_delay(rt_tick_from_millisecond(100)); // delay at least 100ms #ifdef AP3216C_USING_HW_INT /* init interrupt mode */ ap3216c_int_init(dev); #endif /* AP3216C_USING_HW_INT */ return dev; } ``` 初始化完成后,最重要的就是如何进行消息的收发了。在软件包提供的库函数`ap3216c.c`文件中,我们能找到其封装好的收发函数:`read_regs()`、`write_reg()`(`main()`中的`ap3216c_read_ps_data()`与`ap3216c_read_ambient_light()`里面也是通过调用这两个最基本的收发函数来实现功能的)。在这里我们就能看到了上面核心层框架中Core层的一个API接口`rt_i2c_transfer()`,调用这个接口搭配上配置好的msgs结构体,就能够实现我们的一个I2C的发送接收了。 ```c struct rt_i2c_msg { rt_uint16_t addr; //I2C设备地址 rt_uint16_t flags; //标志位,读、写... rt_uint16_t len; //msg的长度(即要发送多少个信息块) rt_uint8_t *buf; //发送/接收的一个缓冲区 }; static rt_err_t write_reg(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t data) { struct rt_i2c_msg msgs; rt_uint8_t temp[2]; temp[0] = reg; temp[1] = data; msgs.addr = AP3216C_ADDR; msgs.flags = RT_I2C_WR; //写数据标志 msgs.buf = temp; msgs.len = 2; if (rt_i2c_transfer(bus, &msgs, 1) == 1) { return RT_EOK; } else { LOG_E("Writing command error"); return -RT_ERROR; } } static rt_err_t read_regs(struct rt_i2c_bus_device *bus, rt_uint8_t reg, rt_uint8_t len, rt_uint8_t *buf) { struct rt_i2c_msg msgs[2]; msgs[0].addr = AP3216C_ADDR; msgs[0].flags = RT_I2C_WR; //读数据标志 msgs[0].buf = ® msgs[0].len = 1; msgs[1].addr = AP3216C_ADDR; msgs[1].flags = RT_I2C_RD; msgs[1].buf = buf; msgs[1].len = len; if (rt_i2c_transfer(bus, msgs, 2) == 2) { return RT_EOK; } else { LOG_E("Reading command error"); return -RT_ERROR; } } ``` 到这里我们就完成软件引用层的使用,进入到了Core层。我们先看一下Core层的传输接口`rt_i2c_transfer()`。其通过bus->ops->master_xfer来完成发送,那这个master_xfer的函数是什么,又在哪里配置的呢?这里就是Core层下面接触底层硬件的一个分水岭,如果此时你使用的是软件I2C,那么这个时候你将会进入一层bit_ops层(bit位操作层);如果是硬件I2C,那么这个时候就会回调到驱动库里面相应的I2C配置函数。 ```C 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; } } ``` 我们以软件I2C为例,进入到bit_ops层中,其主要文件为:`rt-thread\components\drivers\i2c\i2c-bit-ops.c`,在此文件夹中,首先完成了SDA、SCL拉高拉低的函数定义,然后实现I2C通讯中`start`、`stop`、`restart`、`writeb`、`readb`、`SendByte`、`ReceiveByte`等操作,最后就是最重要的一个`i2c_bit_xfer()`函数,这个是通过上面的基础函数来完成对I2C设备的数据交换功能。 如果我们读一遍这个文件夹里的内容,就会发现,这些函数的设计流程是先基于每一bit位去设计的,通过逐一操作每一bit位然后操作一个Byte,再通过操作每一Byte来完成操作不同的读写功能,运用读写功能,我们就能够实现跟I2C设备进行数据交换了。理解完后你就知道其为何命名位bit_ops层了。 在认真阅读一下,会发现SDA、SCL的操作函数都是通过一个 `rt_i2c_bit_ops`结构体里面的函数指针所指向的函数完成的,为什么要这么做呢?绕来绕去,我们好像都还没有看到到底是如何实现拉高拉低电平的,它到底放在了哪里? ```C //文件路径:components\drivers\include\drivers\i2c-bit-ops.h struct rt_i2c_bit_ops { void *data; /* private data for lowlevel routines */ void (*pin_init)(void); 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 */ rt_bool_t i2c_pin_init_flag; }; --------------------------------------------------------------- //文件路径:components\drivers\i2c\i2c-bit-ops.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) /** * release scl line, and wait scl line to high. */ 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)) { 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 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)) { 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; } ``` 答案就在每个厂商的驱动里,就拿Stm32为例,SDA与SCL的具体操作就放在`rt-thread\bsp\stm32\libraries\HAL_Drivers\drivers\drv_soft_i2c.c`目录下。在这里我们需要配置每块厂商自己的一个i2c_bit_ops,需要实现相应的引脚操作功能,然后把这个ops在配置到i2c总线下。 这样做的好处是什么呢?--方便我们去适配不同厂商的芯片。当我们需要更换芯片的时候,我们只需要配置该芯片的一个bit_ops 就好了,不需要把每一层的代码都重新更换一遍。 ```C static void stm32_set_sda(void *data, rt_int32_t state) { struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)data; if (state) { rt_pin_write(cfg->sda, PIN_HIGH); } else { rt_pin_write(cfg->sda, PIN_LOW); } } static void stm32_set_scl(void *data, rt_int32_t state) { struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)data; if (state) { rt_pin_write(cfg->scl, PIN_HIGH); } else { rt_pin_write(cfg->scl, PIN_LOW); } } static rt_int32_t stm32_get_sda(void *data) { struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)data; return rt_pin_read(cfg->sda); } static rt_int32_t stm32_get_scl(void *data) { struct stm32_soft_i2c_config* cfg = (struct stm32_soft_i2c_config*)data; return rt_pin_read(cfg->scl); } static const struct rt_i2c_bit_ops stm32_bit_ops_default = { .data = RT_NULL, .pin_init = stm32_pin_init, .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, .i2c_pin_init_flag = RT_FALSE }; ``` 说了这么多,可能大家还是会一知半解的样子,我这里再给大家重头梳理一下AP3216C使用I2C接受一次数据是怎么实现的。 > ①板级初始化---------->初始化 `I2C2` > > ②AP3216C初始化---->初始化AP3216相关设置(其实这里有调用到`write_reg()`,但是为了方便下一步再讲解) > > ③读取PS数据---------->ap3216c_read_ps_data() --------------------软件应用层 > > ----------->read_low_and_high() ------------------------------------------软件应用层 > > ----------->read_regs() -------------------------------------------------------软件应用层 > > ----------->rt_i2c_transfer() -------------------------------------------------Core层 > > ----------->i2c_bit_xfer() -----------------------------------------------------bit_ops层 > > ----------->i2c_recv/send_bytes()------------------------------------------bit_ops层 > > ----------->SDA\SCL_H\L() -------------------------------------------------bit_ops层 > > ----------->stm32_set_sda()... ---------------------------------------------厂商驱动层 > > ....... 讲了这么多,为什么只见到Core层,Device层呢?这是因为Device层的函数调用也是通过调用Core层的一个API去实现读写控制的功能的,一般来说,调用这个`rt_i2c_transfer()`已经够用了。而且,`i2c_bus_device`是`rt_device`下的一个分类,如果要使用Device层里的内容,需要取出`i2c_bus_device`里的`rt_device parent`,再对`parent->read()`、`parent->write()`完成读写的话会有点麻烦。 ```C 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; }; ``` ### 初始化 I2C的使用是从上到下一层一层去使用的,那么I2C的初始化应该从下往上一层层去进行。 ![I2C初始化流程.drawio.png](https://oss-club.rt-thread.org/uploads/20240423/aedc7cc0b777c34748407c4be06e12bd.png.webp) 而在第一步`rt_hw_i2c_init()`中,我们使用了RTT自动初始化的一个功能`INIT_BOARD_EXPORT(fn)`,这个宏可以使我们的函数能够在板级初始化的时候就能够直接自动运行,不需要我们再手动调用,这就是我们能够直接使用I2C的原因。 ## 结语 本文粗略的讲解了I2C结构框架的一个使用与初始化流程,片中讲的不够详细的地方还请各位指教以方便修改。
5
条评论
默认排序
按发布时间排序
登录
注册新账号
关于作者
Zerro
这家伙很懒,什么也没写!
文章
3
回答
3
被采纳
1
关注TA
发私信
相关文章
1
NXP的I2C应该比ST的好用吧
2
Use of I2C device driver
3
关于I2C 驱动问题请教
4
我如何知道这个iic的io配置和我电路设计的是一致的?
5
I2C模拟读操作失败,不知道问什么进不去读函数
6
RTT的I2C有官方文档资料没有
7
求 STM32F103 IIC 自定义IO初始化 代码
8
报一个LPC4008代码中I2C的bug
9
RTOS IIC总线使用
10
关于在RTT中使用STM32 I2C的疑问
推荐文章
1
RT-Thread应用项目汇总
2
玩转RT-Thread系列教程
3
国产MCU移植系列教程汇总,欢迎查看!
4
机器人操作系统 (ROS2) 和 RT-Thread 通信
5
五分钟玩转RT-Thread新社区
6
【技术三千问】之《玩转ART-Pi》,看这篇就够了!干货汇总
7
关于STM32H7开发板上使用SDIO接口驱动SD卡挂载文件系统的问题总结
8
STM32的“GPU”——DMA2D实例详解
9
RT-Thread隐藏的宝藏之completion
10
【ART-PI】RT-Thread 开启RTC 与 Alarm组件
热门标签
RT-Thread Studio
串口
Env
LWIP
SPI
AT
Bootloader
Hardfault
CAN总线
FinSH
ART-Pi
USB
DMA
文件系统
RT-Thread
SCons
RT-Thread Nano
线程
MQTT
STM32
RTC
FAL
rt-smart
ESP8266
I2C_IIC
UART
WIZnet_W5500
ota在线升级
PWM
freemodbus
flash
cubemx
packages_软件包
BSP
潘多拉开发板_Pandora
定时器
ADC
GD32
flashDB
socket
中断
编译报错
Debug
rt_mq_消息队列_msg_queue
SFUD
msh
keil_MDK
ulog
C++_cpp
MicroPython
本月问答贡献
踩姑娘的小蘑菇
7
个答案
2
次被采纳
a1012112796
16
个答案
1
次被采纳
Ryan_CW
5
个答案
1
次被采纳
红枫
4
个答案
1
次被采纳
张世争
4
个答案
1
次被采纳
本月文章贡献
YZRD
3
篇文章
6
次点赞
catcatbing
3
篇文章
6
次点赞
lizimu
2
篇文章
9
次点赞
qq1078249029
2
篇文章
2
次点赞
xnosky
2
篇文章
1
次点赞
回到
顶部
发布
问题
投诉
建议
回到
底部