设备树中的i2c

   i2c节点一般表示i2c控制器, 它会被转换为platform_device, 全志H3的总线设备驱动位于busses/i2c-mv64xxx.c ,该文件有对应的platform_driver;在总线驱动器代码中的platform_driver的probe函数中会调用i2c_add_numbered_adapter,来增加一个i2c适配器。

 i2c_adap_s3c_init
  s3c24xx_i2c_probe
    i2c_add_numbered_adapter   // drivers/i2c/i2c-core-base.c
        __i2c_add_numbered_adapter
            i2c_register_adapter
                of_i2c_register_devices(adap);   // drivers/i2c/i2c-core-of.c  i2c板级信息在设备树中描述
                    for_each_available_child_of_node(bus, node) {   //在i2c节点下遍历所有子节点
                        client = of_i2c_register_device(adap, node);
                                        client = i2c_new_device(adap, &info);   // 设备树中的i2c子节点被转换为i2c_client

 由调用流程可以得知,i2c节点在of_i2c_register_devices函数中遍历其下的子节点,转换成一个个i2c_client。由此可以得出结论在i2c总线驱动程序中,才会处理I2C的设备树节点,找到处理代码, 通过i2c_new_device(adap, &info)创建i2c设备。

void of_i2c_register_devices(struct i2c_adapter *adap)
{
	struct device_node *bus, *node;
	struct i2c_client *client;

	/* Only register child devices if the adapter has a node pointer set */
	if (!adap->dev.of_node)
		return;

	dev_dbg(&adap->dev, "of_i2c: walking child nodes\n");

	bus = of_get_child_by_name(adap->dev.of_node, "i2c-bus");
	if (!bus)
		bus = of_node_get(adap->dev.of_node);

	for_each_available_child_of_node(bus, node) {
		if (of_node_test_and_set_flag(node, OF_POPULATED))
			continue;

		client = of_i2c_register_device(adap, node);
		if (IS_ERR(client)) {
			dev_err(&adap->dev,
				 "Failed to create I2C device for %pOF\n",
				 node);
			of_node_clear_flag(node, OF_POPULATED);
		}
	}

	of_node_put(bus);
}

由以上分析可以初步得出设备树是如何处理解析i2c节点。在本文我们通过一个实例来实现一个i2c设备驱动。在nanopi上挂接一个光线传感器GY30的i2c设备于i2c适配器0上。由数据手册得出,当光线传感器的add脚接低电平是i2c地址为0x23,为高时地址为0x5c。

 SDA ======>PA12     数据线
 SCL ======>PA11     时钟线

  • i2c-tools使用

进行I2C相关程序开发时,很多时候我们需要确认硬件是否正常连接,设备是否正常工作,设备的地址是多少等等,这里我们就需要使用一个用于测试I2C总线的工具——i2c tools,

i2c-tools编译,安装步骤

wget  https://mirrors.edge.kernel.org/pub/software/utils/i2c-tools/i2c-tools-4.0.tar.gz  //下载i2c-tools-4.0
tar -xzvf i2c-tools-4.0.tar.gz     //解压
Makefile中的CC该为使用的交叉编译器
Make

编译好后,挂载NFS,出现错误,提示缺少共享库

root@wu:/mnt/i2c-tools-4.0/tools# ./i2cget
./i2cget: error while loading shared libraries: libi2c.so.0: cannot open shared object file: No such file or directory

将i2c-tools-4.0/lib/的共享库 拷贝到开发板 lib 目录下

cp -rfd  *.so.*  /lib 

然后使用i2cdetect检测,出现了4个i2c适配器

root@wu:/mnt/i2c-tools-4.0/tools# ./i2cdetect -l
i2c-3   i2c             DesignWare HDMI                         I2C adapter
i2c-1   i2c             mv64xxx_i2c adapter                     I2C adapter
i2c-2   i2c             mv64xxx_i2c adapter                     I2C adapter
i2c-0   i2c             mv64xxx_i2c adapter    

现在在适配器0上接了光线传感器,使用i2cdetect -y指定适配器编号就可以检测到该i2c设备的地址,由命令也可以得出设备地址为0x23

root@wu:~# i2cdetect -y 0
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:          -- -- -- -- -- -- -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- 23 -- -- -- -- -- -- -- -- -- -- -- --
30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
40: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --

  • 传感器操作流程

普遍采用的工作方式为 Continuously H-Resolution Mode,即连续高分辨率模式。通过i2c接口设置若干命令就可以从gy30传感器得到光照强度信息。

1 初始化 写寄存器0x01 上电

2 设置 0x11 设置成高精度模式 Continuously H-Resolution Mode,即连续高分辨率模式。

3 读数据 unsigned char Buf[3] data=Buf[0]; data=(data<<8)+Buf[1] tmp=data/1.2

  • 使用用户空间直接访问i2c接口的光线传感器

在内核文档Documentation/i2c/instantiating-devices中给出了编写i2c设备驱动程序的方法。

Method 1:
  Method 1a: Declare the I2C devices by bus number
  Method 1b: Declare the I2C devices via devicetree
  Method 1c: Declare the I2C devices via ACPI
Method 2: Instantiate the devices explicitly
Method 3: Probe an I2C bus for certain devices
Method 4: Instantiate from user-space

其中方法1使用最普遍,1a这种方法在以前的文章中阐述过,也就是把设备平台信息放在i2c_board_info,然后通过i2c_register_board_info。1b方法大同小异,只是把i2c设备信息放在i2c设备节点的子节点中,然后在驱动加载时去解析。这里我选择方法4通过用户层创建一个i2c设备。

直接通过用户空间就可以操作设备。在内核配置中需要使能I2C device interface

代码很简单,首先打开设备文件然后设置i2c设备的地址,然后按照gy30数据手册中操作流程,连续写0x01和0x11,就可以读出光照信息

#include <stdio.h>
#include <fcntl.h>
#include <linux/i2c-dev.h>
#include <errno.h>
#define I2C_ADDR 0x23
int main(void)
{
    int fd;
    char buf[3];
    char val,value;
    float flight;
    fd=open("/dev/i2c-0",O_RDWR);
    if(fd<0)
    {
        printf("打开文件错误:%s\r\n",strerror(errno)); return 1;
    }
    if(ioctl( fd,I2C_SLAVE,I2C_ADDR)<0 )
    {
        printf("ioctl 错误 : %s\r\n",strerror(errno));return 1;
    }
    val=0x01;
    if(write(fd,&val,1)<0)
    {
        printf("上电失败\r\n");
    }
    val=0x11;
    if(write(fd,&val,1)<0)
    {
                printf("开启高分辨率模式2\r\n");
    }
    usleep(200000);
    while(1)
    {
     if(read(fd,&buf,3)){
         flight=(buf[0]*256+buf[1])*0.5/1.2;
         printf("光照度: %6.2flx\r\n",flight);
     }
     else{
         printf("读取错误\r\n");
     }
    sleep(5);
    }
}
  • 基于设备树的驱动程序

在设备树文件sun8i-h3-nanopi-m1.dts引用i2c0节点,并添加一个gy@23子节点,指定reg属性(设备地址)和compatible 属性值

132 &i2c0 {
133     status = "okay";
134     gy@23{
135         compatible = "gy,bh1750fvi";
136         reg = <0x23>;
137     };
138 };

编译设备树,sun8i-h3-nanopi-m1.dtb拷贝到启动卡的第一个分区,重启开发板进入文件系统,就会增加一个i2c设备0-0023和这个设备在设备树中相关资源

root@wu:~# ls /sys/bus/i2c/devices/
0-0023  0-0068  2-003c  i2c-0   i2c-1   i2c-2   i2c-3

root@wu:~# cat /sys/bus/i2c/devices/0-0023/of_node/compatible
gy,bh1750fviroot@wu:~#

给gy@23编写驱动程序

  • 构造一个platform_driver,其中的of_match_table字段需要与 gy@23节点的compatible属性值一致,当匹配时则调用platform_driver的probe函数
static const struct of_device_id ids[]=
{
	{.compatible="gy,bh1750fvi"},
	{}
};
/* 1. 分配/设置i2c_driver */
static struct i2c_driver gy_sensor_driver = {
	.driver	= {
		.name	= "wu",
		.owner	= THIS_MODULE,
		.of_match_table=ids,
	},
	.probe		= gy_sensor_probe,
	.remove		= gy_sensor_remove,
};

static int gy_sensor_drv_init(void)
{
	/* 2. 注册i2c_driver */
	i2c_add_driver(&gy_sensor_driver);
	return 0;
}
  • 在i2c_driver的probe函数中得到在总线驱动程序中解析得到的i2c_client,并为该光线传感器注册一个字符设备
static int  gy_sensor_probe(struct i2c_client *client,
				  const struct i2c_device_id *id)
{
	gy_sensor_client = client;

	printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	major = register_chrdev(0, "gy_sensor", &gy_sensor_fops);
	class = class_create(THIS_MODULE, "gy_sensor");
	device_create(class, NULL, MKDEV(major, 0), NULL, "gy_sensor"); /* /dev/gy_sensor */
       return 0;
}
  • 填充字符设备中的file_operations结构体 ,在驱动的open函数中,初始化光线传感器 ,在驱动的read函数中,读出传感器的两个字节,怎么转换成光强值由应用程序来负责。
int gy_sensor_open (struct inode *inode, struct file *file)
{
	printk("open gy_sensor\n");
	gy_sensor_write_reg(0x01); //power up
	gy_sensor_write_reg(0x11);
	return 0;
}
static ssize_t gy_sensor_read(struct file * file, char __user *buf, size_t count, loff_t *off)
{
	unsigned char addr=0, data[2];
	gy_sensor_read_reg(data);
	copy_to_user(buf, data, 2);
	return 1;
}
  • 构造i2c_msg通过这个client调用i2c_tansfer来读写
static int gy_sensor_write_reg(unsigned char addr)
{
    int ret = -1;
    struct i2c_msg msgs;

	printk("gy_sensor_client -> addr=%d\n",gy_sensor_client -> addr);
    msgs.addr  = gy_sensor_client -> addr;////GY30_ADDR,直接封装于i2c_msg
    msgs.buf   = &addr;
    msgs.len   = 1; //长度1 byte
    msgs.flags = 0; //表示写

    ret = i2c_transfer(gy_sensor_client ->adapter, &msgs, 1);//这里都封装好了,本来根据i2c协议写数据需要先写入器件写地址,然后才能读
	 if (ret < 0)
	  {
	  	printk("i2c_transfer write err\n");
	 	 return -1;
	  }
    return 0;
}
static int gy_sensor_read_reg(unsigned char *buf)  
{
    int ret = -1;
    struct i2c_msg msg;
    msg.addr  = gy_sensor_client -> addr;//GY30_ADDR
    msg.buf   = buf;
    msg.len   = 2;  //长度1 byte
    msg.flags = I2C_M_RD; //表示读
    ret = i2c_transfer(gy_sensor_client ->adapter, &msg, 1);//这里都封装好了,本来根据i2c协议读数据需要先写入读地址,然后才能读
	if (ret < 0)
	 {
	   printk("i2c_transfer write err\n");
		return -1;
	 }
    return 0;
}
  • 在platform_driver的remove函数中,注销该字符设备
static int  gy_sensor_remove(struct i2c_client *client)
{
	//printk("%s %s %d\n", __FILE__, __FUNCTION__, __LINE__);
	device_destroy(class, MKDEV(major, 0));
	class_destroy(class);
	unregister_chrdev(major, "gy_sensor");

	return 0;
}

编写测试程序来读写光线传感器的光照强度

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char **argv)
{
	int fd;
	char val;
	unsigned char buf[3];
	float flight;
	fd = open("/dev/gy_sensor", O_RDWR);
	if (fd < 0)
	{
		printf("can't open /dev/gy_sensor\n");
		return -1;
	}

	usleep(200000);
	while(1)
		{
	     if(read(fd,&buf,3)){
	         flight=(buf[0]*256+buf[1])*0.5/1.2;
	         printf("light: %6.2flx\r\n",flight);
	     }
	     else{
	         printf("read err!\r\n");
	     }
	    sleep(4);
	}

	return 0;
}

猜你喜欢

转载自blog.csdn.net/qq_18737805/article/details/86427845
今日推荐