• 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
    ");
        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
    ",
                            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
    ", 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
    ", ret, ret == 1 ? "" : "s");
                if (ret < msg->len)
                {
                    if (ret >= 0)
                        ret = -RT_ERROR;
                    goto out;
                }
            }
        }
        ret = i;
    
    out:
        bit_dbg("send stop condition
    ");
        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!
    ", "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
    ", ret);
        }
        rt_thread_mdelay(50);
        ret = rt_device_read(at24c02_dev, 0, tmp_data, 4);
        if(ret != 4)
        {
            rt_kprintf("at24c02 read error %d
    ", ret);
        }
        else
        {
            rt_kprintf("at24c02 read data %02x %02x %02x %02x
    ", tmp_data[0], tmp_data[1], tmp_data[2], tmp_data[3]);
        }
    }
  • 相关阅读:
    逻辑思维杂想
    C++二叉树实现
    斐波那数列递归实现与动态规划实现
    C++双向链表的实现
    C++单链表实现
    C++顺序表实现
    windows下端口占用处理工具
    [项目记录]一个.net下使用HAP实现的吉大校园通知网爬虫工具:OAWebScraping
    [c++]大数运算---利用C++ string实现任意长度正小数、整数之间的加减法
    [C++]几种排序
  • 原文地址:https://www.cnblogs.com/silencehuan/p/10944345.html
Copyright © 2020-2023  润新知