Linux驱动第九章:I²C总线驱动子系统

发布于:2025-06-25 ⋅ 阅读:(16) ⋅ 点赞:(0)

第一节、I2C通信原理及时序:

IIC通信协议用一句话来描述就是:主机呼叫从机,从机接受并反馈信号,传输数据后关闭传输。

1、IIC总线简述:(Inter-Integrated Circuit)

由于早期使用uart通信时,当进行多设备通信时,连接过于繁琐复杂,对于近距设备间通信,成本过高,所以在1982年由飞利浦公司开发出了一种基于总线的多设备通讯方式:I²C通信,总线由简洁的SCL时钟线与SDA数据线组成,多个设备都挂载到这两根上。I²C协议的设计初衷是通过减少连接线数量和简化硬件接口,实现低成本、高效的近距离设备通信。该协议允许多个主设备和从设备共享同一总线,使得系统设计更加简便和灵活。是低速设备通信的常用总线。

2、IIC总线通信协议:

下面介绍一下:使用从机E2PROM从机为例,通过时序简要介绍一下IIC通信协议:

1.写数据帧:

2.读数据帧:

3.总结:

通过协议我们得知,如果Soc想通过W/R从机,这个消息数据的组织必须要素有:

1..从机地址:addr

2. 方向:读or写,约定:写为0,读为1

3.读写数据:data

第二节、 I2C总线驱动框架及调用路径:

I²C总线驱动框架是基于platform平台总线实现的,根据I2C子系统中源码的调用逻辑,绘制如图框架:以方便给大家学习时的整体感:(当我阅读了I2C源码后,发现网上广为传播的框图,有所谬误,所以根据调用逻辑重新绘制)

第三节 、I2C设备的设备树节点对引脚的描述方式:

1. 查看手册,获取设备信息:

I2C设备si7006芯片使用i2c的7位从机地址:0x40

I2C设备si7006芯片使用引脚1链接是i2c总线:I2C1_SDA

I2C设备si7006芯片使用引脚6链接是i2c总线:I2C1_SCL

I2C1_SDA线连接的Soc上的引脚:PF15:gpiof_15 复用为AF5(I2C1_SDA)

I2C1_SCL线连接的Soc上的引脚:PF14:gpiof_14 复用为AF5(I2C1_SCL)

Soc芯片中的I2C1控制器的地址:0x40012000 - 0x400123FF

2.绘制一个直观连接关系图:

3.设备树节点描述:

/{
...
//不要在原厂BSP工程师的设备树节点中去修改,而是应该使用引用节点的方式
    &i2c1{
        status = "okay";
        pinctrl-names = "default","sleep","idle","active";
        pinctrl-0 = <&i2c1_pins_b>;
        pinctrl-1 = <&i2c1_sleep_pins_b>;

        si7006@40{
            compatible = "WX, SI7006";
            reg = <0x40>;
            status = "okay";
        };
    };
    ...
};

第四节、I2C子系统常用数据类型及操作接口:

1.认识I2C从机设备驱动的结构体:i2c_driver:

还是按照我们驱动构建的套路:分配对象,注册,注销。i2c驱动也是遵从device , bus, driver的模型。

先来了解I2C驱动框架中的I2c_driver这是驱动部分的结构体:i2c_driver结构体:

struct i2c_driver {
	...
	/* Standard driver model interfaces */
	int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
	int (*remove)(struct i2c_client *client);
 ...    
	struct device_driver driver;//驱动的父类:父类中有匹配方式 of_match_table;


	...

};

//i2c驱动的父类
struct device_driver {
	const char		*name; //驱动的名字,用来生成节点
 ...





	const struct of_device_id	*of_match_table;//设备树匹配方式
 ...

	struct driver_private *p;//私有属性指针
};

2.i2c_driver对象的注册与注销:

//i2c_driver对象的注册宏:
#define i2c_add_driver(driver) \
	    i2c_register_driver(THIS_MODULE, driver)
//对应的注销宏:
void i2c_del_driver(struct i2c_driver *driver);
//一键注册注销宏:
module_i2c_driver(i2c_driver对象);

3.从机设备:struct i2c_client结构体

struct i2c_client {
    unsigned short flags;           /* 设备的标志位 */

    unsigned short addr;            /* 设备的 I2C 地址 */


    char name[I2C_NAME_SIZE];       /* 设备的名称 */

    struct i2c_adapter *adapter;    /* 指向 I2C 适配器的指针,表示该设备连接到哪个适配器上 */

    struct device dev;              /* 通用设备结构,用于描述设备的基本信息 */

...
};

3.1、发数据接口:i2c_master_send

int i2c_master_send(struct i2c_client *client, const char *buf, int count);
client: 指向 struct i2c_client 结构的指针,表示要发送数据的目标设备。
buf: 指向包含要发送数据的缓冲区的指针。
count: 要发送的字节数。
功能和用法:i2c_master_send() 函数用于向连接到 I2C 总线的设备发送数据。
它通过 client 参数确定要发送数据的目标设备,通过 buf 和 count 参数指定要发送的数据内容和长度。
函数返回发送的字节数,如果发生错误,则返回负数

3.2、收数据接口:i2c_master_recv()

int i2c_master_recv(struct i2c_client *client, char *buf, int count);
client: 指向 struct i2c_client 结构的指针,表示要接收数据的目标设备。
buf: 指向存放接收数据的缓冲区的指针。
count: 要接收的字节数。
功能和用法:i2c_master_recv() 函数用于从连接到 I2C 总线的设备接收数据。
它通过 client 参数确定要接收数据的目标设备,通过 buf 和 count 参数指定接收数据的缓冲区和长度。
函数返回接收到的字节数,如果发生错误,则返回负数。

4.拓展内容:

4.1、I2C控制器结构体:i2c_adapter:

struct i2c_adapter 
{
    struct module *owner;// 驱动程序模块的所有者
    unsignedintclass;// 适配器类别
    conststruct i2c_algorithm *algo;// 用于与硬件通信的算法
    void *algo_data;                     // 算法相关数据
    ...
    struct device dev;// 设备结构
    int nr;                              // 适配器号
    char name[48];                       // 适配器名称
    ...
    };

4.2、i2c数据消息结构体:struct i2c_msg:

struct i2c_msg {
    __u16 addr; /* slave address    从机地址        */
    __u16 flags;//数据中的读写标记为0为写,1为读
#define I2C_M_RD        0x0001  /* read data, from slave to master */
                    /* I2C_M_RD is guaranteed to be 0x0001! */
#define I2C_M_TEN       0x0010  /* this is a ten bit chip address */
#define I2C_M_DMA_SAFE      0x0200  /* the buffer of this message is DMA safe */
                    /* makes only sense in kernelspace */
                    /* userspace buffers are copied anyway */
#define I2C_M_RECV_LEN      0x0400  /* length will be first received byte */
#define I2C_M_NO_RD_ACK     0x0800  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_IGNORE_NAK    0x1000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_REV_DIR_ADDR  0x2000  /* if I2C_FUNC_PROTOCOL_MANGLING */
#define I2C_M_NOSTART       0x4000  /* if I2C_FUNC_NOSTART */
#define I2C_M_STOP      0x8000  /* if I2C_FUNC_PROTOCOL_MANGLING */
    __u16 len;      /* msg length          消息长度     */
    __u8 *buf;      /* pointer to msg data  消息的首地址        */
};
因为默认的都是写,即为0,所以并没有给出标志位,当需要读时,再给出这个标记位即可。所以就没有明确的写标志位,因为写是默认的。

4.3. 传输数据函数:int i2c_transfer():

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
//通过adap调用alogrithom对象的操作方法进行收发数据:
参数1:adap即为i2c控制器的抽象层的封装的对象
参数2:msgs即封装的收发的数据信息
参数3:num即收发数据结构体对象的个数
成功返回 num个数,失败返回非num数或错误码.

第五节、综合应用:

1.甲方需求:

按下开发板中的按键1,在应用中获取温度。

按下开发板中的按键2,在应用中获取湿度。

#include <linux/module.h>
#include <linux/init.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/io.h>

#include <linux/of.h>
#include <linux/of_gpio.h>
#include <linux/gpio.h>
#include <asm-generic/gpio.h>
#include <linux/of_platform.h>
#include <linux/interrupt.h>
#include <linux/of_irq.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/delay.h>
#include <linux/workqueue.h>
#include <linux/input.h>
#include <linux/i2c.h>


struct MyKey
{
    u32 key1_interrupts;
    u8 key1_gpios;
    u32 key2_interrupts;
    u8 key2_gpios;
    u32 key3_interrupts;
    u8 key3_gpios;
    //添加一下irq属性:
    volatile u32 irq;
    //添加input_dev属性:
    struct input_dev* i_dev;
};
struct MyKey mykey = {0};

//封装一个si7006设备类型
struct si7006 
{
    struct i2c_client* client;
    u8 Humidity;//湿度
    u8 Temperature;//温度
    u8 Reset;//重启
    int16_t data;
};

//定义一个si7006设备对象:
struct si7006 si7006 = {
    .client = NULL,
    .Temperature = 0xe3,
    .Humidity = 0xe5,
    .Reset = 0xfe,
    .data = 0,
};

struct of_device_id of_i2c_table[] = {
    [0] = {.compatible ="WX, SI7006", },
    [1] = {/*代表结束*/},
};

int si7006_driver_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    si7006.client = client;
    return 0;
}
int si7006_driver_remove(struct i2c_client *client)
{
    return 0;
}

//定义一个从机设备驱动对象:
struct i2c_driver si7006_driver = {
    .probe = si7006_driver_probe,
    .remove = si7006_driver_remove,
    .driver = {
        .name = "si7006_driver",
        .of_match_table = of_i2c_table
    },
};


irqreturn_t threaded_irq_functions(int irq, void *dev)
{
    //mdelay(100);
    //阻塞式延时函数:
    msleep(50);
    if(mykey.irq == mykey.key1_interrupts)
    {
        if(gpio_get_value(mykey.key1_gpios) == 0)
        {
            //key1按下了:获取温度并上报:
            i2c_master_send(si7006.client,&si7006.Temperature, 1);
            i2c_master_recv(si7006.client,(char*)&si7006.data,2);
            input_event(mykey.i_dev,EV_MSC,MSC_RAW,si7006.data);
            input_sync(mykey.i_dev);
        }
        else{
            //key1抬起了:重置传感器,传感器停止工作
            i2c_master_send(si7006.client,&si7006.Reset,1);
        }
    }
    if(mykey.irq == mykey.key2_interrupts)
    {
        if(gpio_get_value(mykey.key2_gpios) == 0)
        {
            //key2按下了:获取相对湿度并上报
            i2c_master_send(si7006.client,&si7006.Humidity, 1);
            i2c_master_recv(si7006.client,(char*)&si7006.data,2);
            input_event(mykey.i_dev,EV_MSC,MSC_SCAN,si7006.data);
            input_sync(mykey.i_dev);
        }
        else{
            //key2抬起了:停止传感器工作。
            i2c_master_send(si7006.client,&si7006.Reset,1);
        }
    }
    return IRQ_HANDLED;
}

irqreturn_t key_ISR(int irq, void * dev)
{
    mykey.irq = irq; 
    return IRQ_WAKE_THREAD;
}


int my_dev_driver_probe(struct platform_device *pdev)
{
    struct device_node* key_node;
    printk("my_dev_driver_probe probe函数执行\n");
        //获取key设备树象节点的对象指针:
    key_node = pdev->dev.of_node;
    //4.获取Key对象的linux中断号:
    mykey.key1_interrupts = of_irq_get(key_node,0);
    mykey.key1_gpios = of_get_named_gpio(key_node,"mykey_gpios",0);
    gpio_direction_input(mykey.key1_gpios);
    printk("irq = %d\n",mykey.key1_interrupts);
    printk("gpio = %d\n",mykey.key1_gpios);
    mykey.key2_interrupts = of_irq_get(key_node,1);
    mykey.key2_gpios = of_get_named_gpio(key_node,"mykey_gpios",1);
    gpio_direction_input(mykey.key2_gpios);
    mykey.key3_interrupts = of_irq_get(key_node,2);
    mykey.key3_gpios = of_get_named_gpio(key_node,"mykey_gpios",2);
    gpio_direction_input(mykey.key3_gpios);

    //5.请求中断并注册中断处理程序的函数;
    request_threaded_irq(mykey.key1_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key1-interrupts",NULL);
    request_threaded_irq(mykey.key2_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key2-interrupts",NULL);
    request_threaded_irq(mykey.key3_interrupts,key_ISR,threaded_irq_functions,IRQF_TRIGGER_RISING | IRQF_TRIGGER_RISING,"key3-interrupts",NULL);
    //....
    
    //6.初始化mykey中的input_dev对象:
    mykey.i_dev = input_allocate_device();

    //对输入设备匹配方式及事件相关进行设置:
    mykey.i_dev->id.bustype =  ID_BUS;

    set_bit(EV_MSC,mykey.i_dev->evbit);
    set_bit(MSC_RAW, mykey.i_dev->mscbit);
    set_bit(MSC_SCAN, mykey.i_dev->mscbit);
    
    mykey.i_dev->name = "KEY";
    mykey.i_dev->phys = "soc/pf9/key";
    mykey.i_dev->uniq = "KEY";

    //把这个设备注册到输入子系统中:
    input_register_device(mykey.i_dev);

    //定义一个从机驱动对象,并注册到i2c总线上:
    i2c_add_driver(&si7006_driver);

    return 0;
}

int my_dev_driver_remove(struct platform_device *pdev)
{
    printk("my_dev_driver_remover函数执行了\n");
    i2c_del_driver(&si7006_driver);
    input_unregister_device(mykey.i_dev);
    free_irq(mykey.key1_interrupts,NULL);
    free_irq(mykey.key2_interrupts,NULL);
    free_irq(mykey.key3_interrupts,NULL);
    return 0;
}

//设备树匹配方式:
struct of_device_id of_node_match_table[] = { 
    [0] = {.compatible = "WX,my_device_key"},
    [2] = {/*最后一个一定要给一个空元素,代表结束*/}
};

// 1.定义一个平台驱动对象:
struct platform_driver my_platform_driver = {
    .probe = my_dev_driver_probe,
    .remove = my_dev_driver_remove,
    .driver = {
        .name = "WX,my_device_driver",
        //设备树的匹配方式:
        .of_match_table = of_node_match_table,
    },

};

// 入口函数:
int __init my_test_module_init(void)
{
    int ret = 0;
    ret = platform_driver_register(&my_platform_driver);
    if (ret < 0)
    {
        return -1;
    }
    return 0;
}

// 出口函数:
void __exit my_test_module_exit(void)
{
    platform_driver_unregister(&my_platform_driver);
}

// 指定许可:
MODULE_LICENSE("GPL");
MODULE_AUTHOR("gaowanxi, email:gaonetcom@163.com");
// 指定入口及出口函数:
module_init(my_test_module_init);
module_exit(my_test_module_exit);

2.应用测试代码

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <stdbool.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <linux/input.h>
#include <sys/signal.h>
#include <arpa/inet.h>

int main(int argc, char const *argv[])
{
    int fd1 = open("/dev/input/event0", O_RDWR);
    if (fd1 == -1)
    {
        perror("open err");
        return -1;
    }
    struct input_event event_data = {0};
    int nbytes = 0;

    while (true)
    {
        memset(&event_data,0 ,sizeof(event_data));
        nbytes = read(fd1,&event_data,sizeof(event_data));
        if(nbytes == -1)
        {
            perror("read err:");
            return -1;
        }
        if(event_data.code == MSC_RAW) //获取温度:
        {
            float temp = 175.72 * ntohs(event_data.value) / 65536 - 46.85;
            printf("获取温度 = %.2f\n",temp);
        }

        if(event_data.code == MSC_SCAN)//获取湿度:
        {
            float humt = 125 * ntohs(event_data.value) / 65536 - 6;
            printf("获取当前相对湿度 = %.2f\n",humt);
        }
    }
    close(fd1);
    return 0;
}


网站公告

今日签到

点亮在社区的每一天
去签到