• linux内核GPIO模拟I2C实例


    linux内核GPIO模拟I2C实例
     

    2010-10-11 作者:cvip302814 来源:cvip302814的blog

     

    前言:

    在许多情况下,我们并没有足够的I2C总线,本文主在介绍如何利用Linux内核中的i2c-gpio模块,利用2条GPIO线模拟i2c总线,并挂载设备。

    思路:

    先通过对i2c-gpio所定义的结构体初始化(包括初始化i2c的2条线,频率,timeout等)并将i2c-gpio模块编译进内核,实现用GPIO_X,GPIO_Y 2条GPIO线注册新的i2c总线。此时这个模块对i2c设备是透明的,及挂在这2条GPIO线的i2c设备可以直接使用Linux内核通用的i2c设备注册,传输和注销等方法。

    步骤:

    首先确认在注册i2c-gpio模块前,所要用到的2条GPIO口是没有被系统其它地方所调用的。

    在每个系统平台启动时,都会打开一系列的设备,他们通常实现在arch/目录下相应的平台子目录中的例如setup.c,devices.c文件中,在这里我们进行i2c总线的注册以及设备的挂载。i2c-gpio定义的结构在include/linux/i2c-gpio.h中:

    /**

     * struct i2c_gpio_platform_data - Platform-dependent data for i2c-gpio

     * @sda_pin: GPIO pin ID to use for SDA

     * @scl_pin: GPIO pin ID to use for SCL

     * @udelay: signal toggle delay. SCL frequency is (500 / udelay) kHz

     * @timeout: clock stretching timeout in jiffies. If the slave keeps

     * SCL low for longer than this, the transfer will time out.

     * @sda_is_open_drain: SDA is configured as open drain, i.e. the pin

     * isn't actively driven high when setting the output value high.

     * gpio_get_value() must return the actual pin state even if the

     * pin is configured as an output.

     * @scl_is_open_drain: SCL is set up as open drain. Same requirements

     * as for sda_is_open_drain apply.

     * @scl_is_output_only: SCL output drivers cannot be turned off.

     */

    struct i2c_gpio_platform_data {

    unsigned int sda_pin;

    unsigned int scl_pin;

    int udelay;

    int timeout;

    unsigned int sda_is_open_drain:1;

    unsigned int scl_is_open_drain:1;

    unsigned int scl_is_output_only:1;

    };

    其中sda_pin和scl_pin分别是i2c总线的数据线和时钟线,在i2c-gpio中会通过gpio_request函数对这2个口进行申请,udelay和timeout如果不设初值,i2c-gpio中会自动将其设为默认值。

    if (pdata->udelay)

    bit_data->udelay = pdata->udelay;

    else if (pdata->scl_is_output_only)

    bit_data->udelay = 50; /* 10 kHz */

    else

    bit_data->udelay = 5; /* 100 kHz */

    if (pdata->timeout)

    bit_data->timeout = pdata->timeout;

    else

    bit_data->timeout = HZ / 10; /* 100 ms */

    初始化这个结构体后再将其装入platform_device结构体,方便注册:

    static struct platform_device i2c_device = {

    .name = "device-name",

    .id = your-id,

    .dev = {

    .platform_data = &i2c_data,       // i2c_gpio_platform_data

    },

    };

    注册i2c-gpio设备

    将i2c设备挂入我们注册的总线:

    platform_device_register(&i2c_device);

    static struct i2c_board_info i2c_device[] = {

    {

    I2C_BOARD_INFO("name", i2c_device_addr),

    }

    };

    i2c_register_board_info(your-id, i2c_device, ARRAY_SIZE(i2c_device));

    此时我们就可以在i2c设备的驱动程序中通过遍历所在i2c总线,得到其所在的地址i2c_device_addr。

    在i2c驱动中,需要注册一个i2c_driver的结构体,例如:

    static const struct i2c_device_id lis35de_id[] = {

    { "lis35de", 0 },

    { }

    };

    static struct i2c_driver st_lis35de_driver = {

    .probe = st_lis35de_probe,

    .remove = st_lis35de_remove,

    .suspend = st_lis35de_suspend,

    .resume = st_lis35de_resume,

    .id_table  = lis35de_id,

    .driver = {

    .name = "lis35de",

    },

    };

    static int __init st_lis35de_init(void)

    {

    printk(KERN_INFO "st_lis35de_init\n");

    return i2c_add_driver(&st_lis35de_driver);

    }

    在init时用i2c_add_driver(&st_lis35de_driver),此时将会对所在i2c总线进行遍历并得到该设备的适配器等信息,主要目的即是使驱动得到自己的i2c_client,在这个i2c_client中,已经有了该i2c设备的地址等信息,我们在驱动中定义一个新的i2c_client全局变量,把得到的这个i2c_client传给这个全局变量,从而可以继续后面的i2c操作。

    此时我们就可以使用通用的i2c读写操作了。

    总结:

    直接用GPIO口模拟I2C时序和利用内核模块i2c-gpio虚拟i2c总线的区别:

    1. 用GPIO口模拟I2C时序不需要在系统启动时注册I2C总线,只需要在I2C设备驱动中单独实现。用i2c-gpio模块虚拟i2c总线需要在系统启动时注册新的I2C总线,并将i2c设备挂载到新的i2c总线,涉及的范围较广。

    2. 用GPIO口模拟I2C时序,代码操作较繁琐,且不方便挂载多个i2c设备。用i2c-gpio模块可以完全模拟i2c总线,可以挂载多个设备。

    3. 在i2c读写操作时,用GPIO口模拟I2C时序需要每次根据读/写操作发送器件地址<<1+1/0,然后再发送寄存器地址。用i2c-gpio模块相当于直接在i2c总线上操作,在系统启动挂载i2c设备时已经告诉了i2c总线它的地址,在该设备自己的驱动中,只需要通过i2c_add_driver操作即可以得到其地址等诸多信息,读写操作只需要发送寄存器地址即可。

    附:i2c一般的读写操作

    #include <linux/i2c.h>

    /*

    读操作:

    */

    static int i2c_RxData(char *rxData, int length)

    {

    struct i2c_msg msgs[] = {

             /* 1个字节的i2c设备寄存器地址告诉总线 */

    {

     .addr = client->addr,

     .flags = 0,                     //写操作

     .len = 1,

     .buf = rxData,

     },

              /* 从总线读取length个字节的数据,存入rxData */

    {

     .addr =client ->addr,

     .flags = I2C_M_RD,             //I2C_M_RDi2c.h中被定义为1,读操作

     .len = length,

     .buf = rxData,

     },

    };

    if (i2c_transfer(client->adapter, msgs, 2) < 0) {   /* 传输并判断是否传输错误 */

    printk(KERN_ERR "I2C_RxData: transfer error\n");

    return -EIO;

    } else

    return 0;

    }

    /*

    写操作

    */

    static int i2c_TxData(char *txData, int length)

    {

    struct i2c_msg msg[] = {

         /* 1个字节是器件寄存器地址,后面的字节是写入的数据 */

    {

     .addr = client->addr,

     .flags = 0,

     .len = length,

     .buf = txData,

     },

    };

    if (i2c_transfer(client->adapter, msg, 1) < 0) {

    printk(KERN_ERR "I2C_TxData: transfer error\n");

    return -EIO;

    } else

    return 0;

    }


  • 相关阅读:
    (2)javascript的基本语法、数据结构、变量
    (1)认识javascript
    CSS 浅析position:relative/absolute定位方式
    jquery实现下拉框多选
    Vue.js not detected
    手机代理调试Charles Proxy和Fiddler
    render函数之jsx应用
    vue组件通信方式(多种方案)
    点击页面空白处地方,隐藏弹窗
    css圆角不圆和1px方案
  • 原文地址:https://www.cnblogs.com/yuzaipiaofei/p/4124354.html
Copyright © 2020-2023  润新知