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浅析及使用的主要内容,如果未能解决你的问题,请参考以下文章

I2C协议和驱动框架分析

RT-Thread&ART-PI使用软件I2C读取mpu6050

RT-Thread&ART-PI使用软件I2C读取mpu6050

RT-Thread&ART-PI使用软件I2C读取mpu6050

I2C实时时钟rx-8025板卡实际应用

九i2c设备驱动