RT-Thread 设备驱动I2C浅析及使用
Posted silencehuan
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了RT-Thread 设备驱动I2C浅析及使用相关的知识,希望对你有一定的参考价值。
由于 I2C 可以控制多从机的属性,设备驱动模型分为 I2C总线设备(类似与Linux里面的I2C适配器) + I2C从设备;
系统I2C设备驱动主要实现 I2C 总线设备驱动,而具体的I2C 从设备的实现则调用I2C总线设备ops
访问 I2C 总线设备
一般情况下 MCU 的 I2C 器件都是作为主机和从机通讯,在 RT-Thread 中将 I2C 主机虚拟为 I2C总线设备,I2C 从机通过 I2C 设备接口和 I2C 总线通讯,相关接口如下所示:
函数 | 描述 |
---|---|
rt_device_find() | 根据 I2C 总线设备名称查找设备获取设备句柄 |
rt_i2c_transfer() | 传输数据 |
使用方式参考官方文档即可,在此不做赘述。
驱动源码分析
i2c_core.c i2c总线协议控制的核心实现
i2c_dev.c i2c总线设备框架输线
i2c-bit-ops.c I/O模拟I2C的驱动实现
drv_soft_i2c.c I/O模拟I2C的底层硬件支持
先分析 I2C 总线设备注册的流程
在 drv_soft_i2c.c 中
INIT_BOARD_EXPORT(rt_hw_i2c_init);
则 OS 运行时会自启动 rt_hw_i2c_init 进行 模拟I2C 相关硬件IO的初始化
rt_hw_i2c_init -> rt_i2c_bit_add_bus -> rt_i2c_bus_device_register -> rt_i2c_bus_device_device_init -> rt_device_register
初始的配置
#ifdef BSP_USING_I2C1 #define I2C1_BUS_CONFIG \ { .scl = BSP_I2C1_SCL_PIN, .sda = BSP_I2C1_SDA_PIN, .bus_name = "i2c1", } #endif
这样使用时就可以通过 "i2c1" 来控制从设备了
I2C传输功能源码分析
rt_i2c_transfer -> i2c_bit_xfer
static rt_size_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 = bus->priv; rt_int32_t i, ret; rt_uint16_t ignore_nack; bit_dbg("send start condition\n"); i2c_start(ops); for (i = 0; i < num; i++) { msg = &msgs[i]; ignore_nack = msg->flags & RT_I2C_IGNORE_NACK; if (!(msg->flags & RT_I2C_NO_START)) // 没有RT_I2C_NO_START { if (i) // 主要用于读操作 { i2c_restart(ops); } ret = i2c_bit_send_address(bus, msg); //发送器件地址 if ((ret != RT_EOK) && !ignore_nack) { bit_dbg("receive NACK from device addr 0x%02x msg %d\n", msgs[i].addr, i); goto out; } } if (msg->flags & RT_I2C_RD) //读取数据 { ret = i2c_recv_bytes(bus, msg); if (ret >= 1) bit_dbg("read %d byte%s\n", 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) bit_dbg("write %d byte%s\n", ret, ret == 1 ? "" : "s"); if (ret < msg->len) { if (ret >= 0) ret = -RT_ERROR; goto out; } } } ret = i; out: bit_dbg("send stop condition\n"); i2c_stop(ops); return ret; }
我们以 24c02 的 读写 来分析 i2C驱动
static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct at24cxx_device *at24cxx; const struct at24cxx_config *cfg; struct rt_i2c_msg msg[2]; rt_uint8_t mem_addr[2] = {0,}; rt_size_t ret = 0; RT_ASSERT(dev != 0); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != 0); cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size) { return 0; } if(pos + size > cfg->size) { size = cfg->size - pos; } // 写入寻址地址 msg[0].addr = cfg->addr; msg[0].flags = cfg->flags | RT_I2C_WR; if(cfg->size < 257) // at24c01 at24c02, 一页8字节,寻址地址8位 { mem_addr[0] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 1; } else // at24c04/08/16 一页16字节,寻址地址9/10/11位 { mem_addr[0] = (pos >> 8); mem_addr[1] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 2; } // 使用RT_I2C_NO_START,直接写入buffer数据 msg[1].addr = cfg->addr; msg[1].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START; msg[1].buf = (rt_uint8_t *) buffer; msg[1].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, 2); return (ret == 2) ? size : 0; }
static rt_size_t at24cxx_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { struct at24cxx_device *at24cxx; const struct at24cxx_config *cfg; struct rt_i2c_msg msg[2]; rt_uint8_t mem_addr[2] = {0,}; rt_size_t ret = 0; RT_ASSERT(dev != 0); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != 0); cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos >= cfg->size) //寻址地址超标 { return 0; } if(pos + size > cfg->size) // size超标 { size = cfg->size - pos; } msg[0].addr = cfg->addr; msg[0].flags = cfg->flags | RT_I2C_WR; if(cfg->size < 257) // at24c01 at24c02, 一页8字节,寻址地址8位 { mem_addr[0] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 1; } else // at24c04/08/16 一页16字节,寻址地址9/10/11位 { mem_addr[0] = (pos >> 8); mem_addr[1] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 2; } msg[1].addr = cfg->addr; msg[1].flags = cfg->flags | RT_I2C_RD; msg[1].buf = (rt_uint8_t *) buffer; msg[1].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, 2); return (ret == 2) ? size : 0; }
可以看到 i2c 读写 EEPROM 通过发送多个 msg 来实现 写寻址地址在进行读写操作,同时通过 RT_I2C_NO_START 使用读写场景
i2C设备应用实例
24c02设备实例代码
#include <rtthread.h> #include <rtdevice.h> #include "at24cxx.h" /** at24cxx设备结构体 */ struct at24cxx_device { struct rt_device parent; struct rt_i2c_bus_device *bus; }; /* RT-Thread device interface */ static rt_err_t at24cxx_init(rt_device_t dev) { return RT_EOK; } static rt_err_t at24cxx_open(rt_device_t dev, rt_uint16_t oflag) { return RT_EOK; } static rt_err_t at24cxx_close(rt_device_t dev) { return RT_EOK; } static rt_err_t at24cxx_control(rt_device_t dev, int cmd, void *args) { return RT_EOK; } /** * @brief at24cxx设备读操作 * @param[in] dev 设备句柄 * @param[in] pos i2c写寻址地址 * @param[in] *buffer 读出数据的指针 * @param[in] size 读出数据的长度 * @return 返回读出成功的字节数 * - 0 读出失败 * - Others 读出成功的字节数 */ static rt_size_t at24cxx_read(rt_device_t dev, rt_off_t pos, void *buffer, rt_size_t size) { struct at24cxx_device *at24cxx; const struct at24cxx_config *cfg; struct rt_i2c_msg msg[2]; rt_uint8_t mem_addr[2] = {0,}; rt_size_t ret = 0; RT_ASSERT(dev != 0); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != 0); cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos >= cfg->size) //寻址地址超标 { return 0; } if(pos + size > cfg->size) // size超标 { size = cfg->size - pos; } msg[0].addr = cfg->addr; msg[0].flags = cfg->flags | RT_I2C_WR; if(cfg->size < 257) // at24c01 at24c02, 一页8字节,寻址地址8位 { mem_addr[0] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 1; } else // at24c04/08/16 一页16字节,寻址地址9/10/11位 { mem_addr[0] = (pos >> 8); mem_addr[1] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 2; } msg[1].addr = cfg->addr; msg[1].flags = cfg->flags | RT_I2C_RD; msg[1].buf = (rt_uint8_t *) buffer; msg[1].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, 2); return (ret == 2) ? size : 0; } /** * @brief at24cxx设备写操作 * @param[in] dev 设备句柄 * @param[in] pos i2c写寻址地址 * @param[in] *buffer 写入数据的指针 * @param[in] size 写入数据的长度 * @return 返回写入成功的字节数 * - 0 写入失败 * - Others 写入成功的字节数 */ #if 0 // 连续页写测试 static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct at24cxx_device *at24cxx; const struct at24cxx_config *cfg; struct rt_i2c_msg msg[2]; rt_uint8_t mem_addr[2] = {0,}; rt_size_t ret = 0; RT_ASSERT(dev != 0); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != 0); cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size) { return 0; } if(pos + size > cfg->size) { size = cfg->size - pos; } /*计算出要写的页数和分页*/ rt_uint8_t NumOfPage = 0; // 总页写数,包括写的不完整页 rt_uint8_t Addr = 0; // 页写的寻址地址 rt_uint8_t count = 0; // 页写入的字节数 rt_uint8_t i; if(size > (PAGE_SIZE - pos%PAGE_SIZE)) { count = PAGE_SIZE - pos%PAGE_SIZE; // 写入的第一页长度 NumOfPage = (size - count)/PAGE_SIZE; // 剩余写入的完整页数 if((size - (count + NumOfPage*PAGE_SIZE)) > 0) NumOfPage = NumOfPage + 2; else NumOfPage = NumOfPage + 1; } else { NumOfPage = 1; } for(i=0; i<NumOfPage; i++) { if(i == 0) { Addr = pos; if(NumOfPage == 1) { count = size; } else { count = PAGE_SIZE - pos%PAGE_SIZE; } } else if((i == NumOfPage-1) && (NumOfPage > 1)) { Addr = pos - pos%PAGE_SIZE + i*PAGE_SIZE; count = pos + size - Addr; } else { Addr = pos - pos%PAGE_SIZE + i*PAGE_SIZE; count = PAGE_SIZE; } // 写入寻址地址 msg[0].addr = cfg->addr; msg[0].flags = cfg->flags | RT_I2C_WR; if(cfg->size < 257) // at24c01 at24c02, 一页8字节,寻址地址8位 { mem_addr[0] = (rt_uint8_t) Addr; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 1; } else // at24c04/08/16 一页16字节,寻址地址9/10/11位 { mem_addr[0] = (Addr >> 8); mem_addr[1] = (rt_uint8_t) Addr; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 2; } // 使用RT_I2C_NO_START,直接写入buffer数据 msg[1].addr = cfg->addr; msg[1].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START; msg[1].buf = (rt_uint8_t *) buffer+count; msg[1].len = count; ret = rt_i2c_transfer(at24cxx->bus, msg, 2); if(ret != 2) return 0; } return count; } #endif #if 1 // 不支持连续页写 static rt_size_t at24cxx_write(rt_device_t dev, rt_off_t pos, const void *buffer, rt_size_t size) { struct at24cxx_device *at24cxx; const struct at24cxx_config *cfg; struct rt_i2c_msg msg[2]; rt_uint8_t mem_addr[2] = {0,}; rt_size_t ret = 0; RT_ASSERT(dev != 0); at24cxx = (struct at24cxx_device *) dev; RT_ASSERT(at24cxx->parent.user_data != 0); cfg = (const struct at24cxx_config *) at24cxx->parent.user_data; if(pos > cfg->size) { return 0; } if(pos + size > cfg->size) { size = cfg->size - pos; } // 写入寻址地址 msg[0].addr = cfg->addr; msg[0].flags = cfg->flags | RT_I2C_WR; if(cfg->size < 257) // at24c01 at24c02, 一页8字节,寻址地址8位 { mem_addr[0] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 1; } else // at24c04/08/16 一页16字节,寻址地址9/10/11位 { mem_addr[0] = (pos >> 8); mem_addr[1] = (rt_uint8_t) pos; msg[0].buf = (rt_uint8_t *) mem_addr; msg[0].len = 2; } // 使用RT_I2C_NO_START,直接写入buffer数据 msg[1].addr = cfg->addr; msg[1].flags = cfg->flags | RT_I2C_WR | RT_I2C_NO_START; msg[1].buf = (rt_uint8_t *) buffer; msg[1].len = size; ret = rt_i2c_transfer(at24cxx->bus, msg, 2); return (ret == 2) ? size : 0; } #endif #ifdef RT_USING_DEVICE_OPS /** at24cxx设备操作ops */ const static struct rt_device_ops at24cxx_ops = { at24cxx_init, at24cxx_open, at24cxx_close, at24cxx_read, at24cxx_write, at24cxx_control }; #endif /** * @brief at24cxx设备注册 * @param[in] *fm_device_name 设备名称 * @param[in] *i2c_bus i2c总线设备名称 * @param[in] *user_data 用户数据 at24cxx_config * @return 函数执行结果 * - RT_EOK 执行成功 * - Others 失败 */ rt_err_t at24cxx_register(const char *fm_device_name, const char *i2c_bus, void *user_data) { static struct at24cxx_device at24cxx_drv; struct rt_i2c_bus_device *bus; bus = rt_i2c_bus_device_find(i2c_bus); if (bus == RT_NULL) { return RT_ENOSYS; } at24cxx_drv.bus = bus; at24cxx_drv.parent.type = RT_Device_Class_Block; #ifdef RT_USING_DEVICE_OPS at24cxx_drv.parent.ops = &at24cxx_ops; #else at24cxx_drv.parent.init = at24cxx_init; at24cxx_drv.parent.open = at24cxx_open; at24cxx_drv.parent.close = at24cxx_close; at24cxx_drv.parent.read = at24cxx_read; at24cxx_drv.parent.write = at24cxx_write; at24cxx_drv.parent.control = at24cxx_control; #endif at24cxx_drv.parent.user_data = user_data; return rt_device_register(&at24cxx_drv.parent, fm_device_name, RT_DEVICE_FLAG_RDWR); }
/** at24cxx设备用户操作配置结构体 */ struct at24cxx_config { rt_uint32_t size; //设备的总容量 rt_uint16_t addr; //设备地址 rt_uint16_t flags; //I2C操作标志 }; /** * @brief at24cxx设备注册 * @param[in] *fm_device_name 设备名称 * @param[in] *i2c_bus i2c总线设备名称 * @param[in] *user_data 用户数据 at24cxx_config * @return 函数执行结果 * - RT_EOK 执行成功 * - Others 失败 */ extern rt_err_t at24cxx_register(const char *e2m_device_name, const char *i2c_bus, void *user_data);
24c02设备驱动使用示例
static struct at24cxx_config at24c02_config = { .size = 256, // 容量,单位字节 .addr = 0x50, // 注意该地址为没有移位之前的地址不是0xA0 .flags = 0, }; static void at24c02_sample(int argc, char *argv[]) { rt_err_t ret; rt_uint8_t test_data[100] = {0x12, 0x34, 0x56, 0x78}; rt_uint8_t tmp_data[100]; // memset(test_data, 0x55, 100); ret = at24cxx_register("at24c02", "i2c1", &at24c02_config); rt_device_t at24c02_dev = rt_device_find("at24c02"); if (at24c02_dev == RT_NULL) { rt_kprintf("at24c02 sample run failed! can‘t find %s device!\n", "at24c02"); return; } rt_device_open(at24c02_dev, RT_DEVICE_FLAG_RDWR); ret = rt_device_write(at24c02_dev, 0, test_data, 4); if(ret != 4) { rt_kprintf("at24c02 write error %d\n", ret); } rt_thread_mdelay(50); ret = rt_device_read(at24c02_dev, 0, tmp_data, 4); if(ret != 4) { rt_kprintf("at24c02 read error %d\n", ret); } else { rt_kprintf("at24c02 read data %02x %02x %02x %02x\n", tmp_data[0], tmp_data[1], tmp_data[2], tmp_data[3]); } }
以上是关于RT-Thread 设备驱动I2C浅析及使用的主要内容,如果未能解决你的问题,请参考以下文章
RT-Thread&ART-PI使用软件I2C读取mpu6050
RT-Thread&ART-PI使用软件I2C读取mpu6050