由於 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]); } }