Linux总线之I2C

1.i2c总线

1.1i2c总线驱动硬件知识复习

(1)i2c几根线?

scl:时钟线

sda:数据线

(2)几种信号?

start:起始信号:scl为高电平的时候,sda从高到低的跳变

stop:结束信号:scl为高电平的时候,sda从低到高的跳变

ack:在第九个时钟周期的时候,sda总线是低电平就代表应答

	(master--->slave   slave ---->master)

No ack:在第九个时钟周期的时候,sda总线持续高电平

(3)i2c读写时序

写时序:

\*\*start+ (从机地址 7bit + 0(写) 1bit) + ack + 寄存器地址(8bit|16bit)+ack \*\*

**+data(8bit|16bit) + ack + stop**

读时序:

**start+ (从机地址 7bit + 0(写) 1bit) + ack + 寄存器地址(8bit|16bit)+ack **

 **start+ (从机地址 7bit + 1(读) 1bit) + ack + data(8bit 从机给主机) +NO ack+stop**

(4)i2c总线的特点

i2c是一个半双工,同步的,串行,具备应答机制的总线协议。

(5)i2c总线速率(控制器)

100K  低速    400K 全速   3.4M 高速

1.2i2c总线驱动框架结构

在这里插入图片描述

1.3i2c总线驱动的API

1.分配并初始化对象
    struct i2c_driver {
        int (*probe)(struct i2c_client *client, const struct i2c_device_id *id);
        int (*remove)(struct i2c_client *client);
        struct device_driver driver;
        //const struct i2c_device_id *id_table;
    }
	struct device_driver {
		const char		*name;
        const struct of_device_id	*of_match_table;
    }
	//i2c的匹配方式只有两种:1.idtable,2.设备树
2.注册、注销
    #define i2c_add_driver(driver) i2c_register_driver(THIS_MODULE, driver)  //注册
    void i2c_del_driver(struct i2c_driver *driver); //注销
3.一键注册的函数
    module_i2c_driver(变量名);

1.4i2c总线驱动的实例

#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/module.h>
#include <linux/mod_devicetable.h>

int myi2c_probe(struct i2c_client* client,
    const struct i2c_device_id* id)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}
int myi2c_remove(struct i2c_client* client)
{
    printk("%s:%d\n", __func__, __LINE__);
    return 0;
}

struct of_device_id oftable[] = {
    {.compatible = "hqyj,myi2c",},
    {}
};
MODULE_DEVICE_TABLE(of,oftable);

struct i2c_driver myi2c = {
    .probe = myi2c_probe,
    .remove = myi2c_remove,
    .driver = {
        .name = "myi2c123456",
        .of_match_table = oftable,
    }
};
module_i2c_driver(myi2c);
MODULE_LICENSE("GPL");

2.i2c总线驱动

2.1si7006(sht20)设备树的编写

2.1.1画出硬件连接图

在这里插入图片描述

2.1.2找到控制器的设备树

stm32mp151.dtsi

i2c1: i2c@40012000 {
    compatible = "st,stm32mp15-i2c";  //和控制器匹配的名字
    reg = <0x40012000 0x400>;         //控制器的地址
    interrupt-names = "event", "error";
    interrupts-extended = <&exti 21 IRQ_TYPE_LEVEL_HIGH>,
                  <&intc GIC_SPI 32 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&rcc I2C1_K>;
    resets = <&rcc I2C1_R>;
    #address-cells = <1>;
    #size-cells = <0>;     //修饰子节点 reg成员个数
    dmas = <&dmamux1 33 0x400 0x80000001>,
           <&dmamux1 34 0x400 0x80000001>;
    dma-names = "rx", "tx";
    power-domains = <&pd_core>;
    st,syscfg-fmp = <&syscfg 0x4 0x1>;
    wakeup-source;
    i2c-analog-filter;
    status = "disabled";  //控制器没有使能
};

2.1.3编写自己的设备树

参考:linux-5.10.61/Documentation/devicetree/bindings/i2c
&i2c1{
    pinctrl-names = "default", "sleep";
    pinctrl-0 = <&i2c1_pins_b>;       //管脚复用,工作状态                                                      
    pinctrl-1 = <&i2c1_sleep_pins_b>; //管脚复用,休眠状态
    i2c-scl-rising-time-ns = <100>;   //scl上升沿和下降沿的时间                                              
    i2c-scl-falling-time-ns = <7>;
    status = "okay";                  //使能控制器
    /delete-property/dmas;            //删除dma属性                                          
    /delete-property/dma-names;
    
    si7006@40{
        compatible = "hqyj,si7006";  //和设备驱动匹配的名字
        reg = <0x40>;                //从机地址
    };
};

2.2i2c中相关的结构体

i2c_client结构体:当驱动进入到probe函数之前,内核会创建i2c_client,

这个i2c_client是用来记录数据的。比如i2c_adapter(控制器驱动的对象)就

被存放到这个结构体中。后面在发消息的时候会用到这个i2c_adapter

https://www.totalphase.com/support/articles/200349176-7-bit-8-bit-and-10-bit-I2C-Slave-Addressing

struct i2c_client {
	unsigned short flags;		//i2c的标志位  I2C_M_TEN
	unsigned short addr;		//从机地址
	char name[I2C_NAME_SIZE];  //设备的名
	struct i2c_adapter *adapter;//控制器的对象
};

i2c_msg结构体:i2c设备驱动和控制器驱动传输数据的时候是通过i2c_msg

结构体完成的。

//消息结构体
struct i2c_msg {
	__u16 addr;	    //从机地址
	__u16 flags;    //读写标志位 0 写  1读
	__u16 len;		//消息的长度
	__u8 *buf;		//消息的首地址
};

2.3封装数据发送数据的过程

2.3.1消息的封装过程

有多少起始位就有多少消息,消息的长度是以字节表示的。

写的消息:

start+(slave addr 7bit 0 1bit)+ack+reg+ack+data+ack+stop

(slave addr 7bit 0 1bit)+reg+data

char wbuf[] = {reg,data};
struct i2c_msg w_msg = {
    .addr = client->addr,
    .flags = 0,
    .len = 2,
    .buf = wbuf,
};

读的消息:

start+(slave addr 7bit 0 1bit)+ack+reg+ack+

start+(slave addr 7bit 1 1bit)+ack+data+NO ack+stop

char rbuf[] = {reg};
unsigned char val;
struct i2c_msg r_msg[] = {
    [0] = {
        .addr = client->addr,
        .flags = 0,
        .len = 1,
        .buf = rbuf,
    },
    [1] = {
        .addr = client->addr,
        .flags = 1,
        .len = 1,
        .buf = &val,
    },
};

2.3.2消息的发送过程

int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num)
功能:发送消息
参数:
    @adap:控制器驱动对象的指针
	@msgs:消息的首地址
	@num:消息的个数
返回值:成功返回值等于num,否则就是失败

2.4读取si7006串号和固件号实例

在这里插入图片描述
si7006.h

#ifndef __SI7006_H__
#define __SI7006_H__

#define GET_SERIAL  _IOR('j',0,int)
#define GET_FIRWARE  _IOR('j',1,int)

#define SERIAL_ADDR 0xfcc9
#define FIRWARE_ADDR 0x84b8

#endif

si7006.c

#include "si7006.h"
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#include <linux/device.h>

#define CNAME "si7006"
struct i2c_client* gclient;
int major;
const int count = 1;
struct class* cls;
struct device* dev;

int i2c_read_serial_firware(unsigned short reg)
{
    int ret;
    unsigned char val;
    char r_buf[] = { reg >> 8, reg & 0xff };
    // 1.封装消息结构体
    struct i2c_msg r_msg[] = {
        [0] = {
            .addr = gclient->addr,
            .flags = 0,
            .len = 2,
            .buf = r_buf,
        },
        [1] = {
            .addr = gclient->addr,
            .flags = 1,
            .len = 1,
            .buf = &val,
        },
    };

    // 2.发送消息
    ret = i2c_transfer(gclient->adapter, r_msg, ARRAY_SIZE(r_msg));
    if (ret != ARRAY_SIZE(r_msg)) {
        printk("i2c read error\n");
        return -EAGAIN;
    }

    return val;
}
int si7006_open(struct inode* inode, struct file* file)
{
    return 0;
}
long si7006_ioctl(struct file* filp,
    unsigned int cmd, unsigned long arg)
{
    int data, ret;
    switch (cmd) {
    case GET_SERIAL:
        data = i2c_read_serial_firware(SERIAL_ADDR);
        if (data < 0) {
            printk("i2c read serial error\n");
            return data;
        }
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data serial to user error\n");
            return -EIO;
        }
        break;
    case GET_FIRWARE:
        data = i2c_read_serial_firware(FIRWARE_ADDR);
        if (data < 0) {
            printk("i2c read firware error\n");
            return data;
        }
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data firware to user error\n");
            return -EIO;
        }
        break;
    }
    return 0;
}
int si7006_close(struct inode* inode, struct file* file)
{
    return 0;
}
static struct file_operations fops = {
    .open = si7006_open,
    .unlocked_ioctl = si7006_ioctl,
    .release = si7006_close,
};

int si7006_probe(struct i2c_client* client,
    const struct i2c_device_id* id)
{
    gclient = client;
    printk("%s:%d\n", __func__, __LINE__);

    // 1.注册字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register chrdev error\n");
        return -EAGAIN;
    }
    // 2.创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        return PTR_ERR(cls);
    }
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
    if (IS_ERR(dev)) {
        printk("device create error\n");
        return PTR_ERR(dev);
    }

    return 0;
}
int si7006_remove(struct i2c_client* client)
{
    printk("%s:%d\n", __func__, __LINE__);
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, CNAME);
    return 0;
}

struct of_device_id oftable[] = {
    {
        .compatible = "hqyj,si7006",
    },
    {}
};
MODULE_DEVICE_TABLE(of, oftable);

struct i2c_driver si7006 = {
    .probe = si7006_probe,
    .remove = si7006_remove,
    .driver = {
        .name = "si7006123456",
        .of_match_table = oftable,
    }
};
module_i2c_driver(si7006);
MODULE_LICENSE("GPL");

test.c

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include "si7006.h"

int main(int argc, const char *argv[])
{
	int fd,serial,firware;
	if((fd = open("/dev/si7006",O_RDWR)) < 0){
		perror("open error");
		exit(EXIT_FAILURE);
	}

    ioctl(fd,GET_SERIAL,&serial);
    ioctl(fd,GET_FIRWARE,&firware);

    printf("serial = %#x,firware = %#x\n",serial,firware);
	close(fd);
	return 0;
}

2.5读取si7006温湿度实例

在这里插入图片描述
在这里插入图片描述

si7006.h

#ifndef __SI7006_H__
#define __SI7006_H__

#define GET_SERIAL  _IOR('j',0,int)
#define GET_FIRWARE  _IOR('j',1,int)
#define GET_TMP  _IOR('j',2,int)
#define GET_HUM  _IOR('j',3,int)
#define SERIAL_ADDR 0xfcc9
#define FIRWARE_ADDR 0x84b8
#define TMP_ADDR 0xe3
#define HUM_ADDR 0xe5
#endif

si7006.c

#include "si7006.h"
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/i2c.h>
#include <linux/init.h>
#include <linux/mod_devicetable.h>
#include <linux/module.h>
#include <linux/uaccess.h>

#define CNAME "si7006"
struct i2c_client* gclient;
int major;
const int count = 1;
struct class* cls;
struct device* dev;
int i2c_read_serial_firware(unsigned short reg)
{
    int ret;
    unsigned char val;
    char r_buf[] = { reg >> 8, reg & 0xff };
    // 1.封装消息结构体
    struct i2c_msg r_msg[] = {
        [0] = {
            .addr = gclient->addr,
            .flags = 0,
            .len = 2,
            .buf = r_buf,
        },
        [1] = {
            .addr = gclient->addr,
            .flags = 1,
            .len = 1,
            .buf = &val,
        },
    };

    // 2.发送消息
    ret = i2c_transfer(gclient->adapter, r_msg, ARRAY_SIZE(r_msg));
    if (ret != ARRAY_SIZE(r_msg)) {
        printk("i2c read error\n");
        return -EAGAIN;
    }

    return val;
}

int i2c_read_tmp_hum(unsigned char reg)
{
    int ret;
    unsigned short val;
    char r_buf[] = { reg };
    // 1.封装消息结构体
    struct i2c_msg r_msg[] = {
        [0] = {
            .addr = gclient->addr,
            .flags = 0,
            .len = 1,
            .buf = r_buf,
        },
        [1] = {
            .addr = gclient->addr,
            .flags = 1,
            .len = 2,
            .buf = (__u8*)&val,
        },
    };

    // 2.发送消息
    ret = i2c_transfer(gclient->adapter, r_msg, ARRAY_SIZE(r_msg));
    if (ret != ARRAY_SIZE(r_msg)) {
        printk("i2c read error\n");
        return -EAGAIN;
    }

    return (val >> 8 | val << 8);
}
int si7006_open(struct inode* inode, struct file* file)
{
    return 0;
}
long si7006_ioctl(struct file* filp,
    unsigned int cmd, unsigned long arg)
{
    int data, ret;
    switch (cmd) {
    case GET_SERIAL:
        data = i2c_read_serial_firware(SERIAL_ADDR);
        if (data < 0) {
            printk("i2c read serial error\n");
            return data;
        }
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data serial to user error\n");
            return -EIO;
        }
        break;
    case GET_FIRWARE:
        data = i2c_read_serial_firware(FIRWARE_ADDR);
        if (data < 0) {
            printk("i2c read firware error\n");
            return data;
        }
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data firware to user error\n");
            return -EIO;
        }
        break;
    case GET_TMP:
        data = i2c_read_tmp_hum(TMP_ADDR);
        if (data < 0) {
            printk("i2c read tmp error\n");
            return data;
        }
        data &= 0xffff;
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data tmp to user error\n");
            return -EIO;
        }
        break;
    case GET_HUM:
        data = i2c_read_tmp_hum(HUM_ADDR);
        if (data < 0) {
            printk("i2c read hum error\n");
            return data;
        }
        data &= 0xffff;
        ret = copy_to_user((void*)arg, &data, sizeof(int));
        if (ret) {
            printk("copy data hum to user error\n");
            return -EIO;
        }
        break;
    }
    return 0;
}
int si7006_close(struct inode* inode, struct file* file)
{
    return 0;
}

static struct file_operations fops = {
    .open = si7006_open,
    .unlocked_ioctl = si7006_ioctl,
    .release = si7006_close,
};

int si7006_probe(struct i2c_client* client,
    const struct i2c_device_id* id)
{
    gclient = client;
    printk("%s:%d\n", __func__, __LINE__);

    // 1.注册字符设备驱动
    major = register_chrdev(0, CNAME, &fops);
    if (major < 0) {
        printk("register chrdev error\n");
        return -EAGAIN;
    }
    // 2.创建设备节点
    cls = class_create(THIS_MODULE, CNAME);
    if (IS_ERR(cls)) {
        printk("class create error\n");
        return PTR_ERR(cls);
    }
    dev = device_create(cls, NULL, MKDEV(major, 0), NULL, CNAME);
    if (IS_ERR(dev)) {
        printk("device create error\n");
        return PTR_ERR(dev);
    }

    return 0;
}
int si7006_remove(struct i2c_client* client)
{
    printk("%s:%d\n", __func__, __LINE__);
    device_destroy(cls, MKDEV(major, 0));
    class_destroy(cls);
    unregister_chrdev(major, CNAME);
    return 0;
}

struct of_device_id oftable[] = {
    {
        .compatible = "hqyj,si7006",
    },
    {}
};
MODULE_DEVICE_TABLE(of, oftable);

struct i2c_driver si7006 = {
    .probe = si7006_probe,
    .remove = si7006_remove,
    .driver = {
        .name = "si7006123456",
        .of_match_table = oftable,
    }
};
module_i2c_driver(si7006);
MODULE_LICENSE("GPL");

test.c

#include "si7006.h"
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

int main(int argc, const char* argv[])
{
    int fd, serial, firware, tmpcode, humcode;
    float tmp, hum;
    if ((fd = open("/dev/si7006", O_RDWR)) < 0) {
        perror("open error");
        exit(EXIT_FAILURE);
    }

    ioctl(fd, GET_SERIAL, &serial);
    ioctl(fd, GET_FIRWARE, &firware);

    printf("serial = %#x,firware = %#x\n", serial, firware);
    while (1) {
        ioctl(fd, GET_TMP, &tmpcode);
        ioctl(fd, GET_HUM, &humcode);

        hum = 125.0 * humcode / 65536 - 6;
        tmp = 175.72 * tmpcode / 65536 - 46.85;

        printf("tmp=%.2f,hum=%.2f\n",tmp,hum);
        sleep(1);
    }
    close(fd);
    return 0;
}

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值