有关linux的i2c相关文章有一下几篇,他们互相关联,应该一同看:
1. 简介:
gpio模拟i2c驱动可以解决i2c控制器不足的问题,但是,相对的可能要占用更多的cpu时间,此程序依然使用的是jz2440开发板
2. 内核提供的代码分析:
我们从 i2c-gpio.c 开始,文件路径:drivers/i2c/busses
(1)i2c的gpio的私有的数据 结构体如下:
struct i2c_gpio_private_data { struct i2c_adapter adap; struct i2c_algo_bit_data bit_data; struct i2c_gpio_platform_data pdata; };
其中第一个成员 i2c_adapter 不在展开,他提供了i2c的通信方法
第二个成员i2c_algo_bit_data,功能和参数写在函数中,他提供了操作具体硬件上的方法
/* --- Defines for bit-adapters --------------------------------------- */ /* * 这个结构体中包含了对线的操作的函数,从名字上我们可以看出 */ struct i2c_algo_bit_data { void *data; /* private data for lowlevel routines */ void (*setsda) (void *data, int state); /* 一些操作线的高低电平的函数 */ void (*setscl) (void *data, int state); int (*getsda) (void *data); int (*getscl) (void *data); int (*pre_xfer) (struct i2c_adapter *); void (*post_xfer) (struct i2c_adapter *); /* local settings */ int udelay; /* half clock cycle time in us, minimum 2 us for fast-mode I2C, minimum 5 us for standard-mode I2C and SMBus, maximum 50 us for SMBus */ int timeout; /* 单位 jiffies */ };
第三个成员i2c_gpio_platform_data,用于保存具体的硬件资源
/** * 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; /* sda 对应的引脚 */ unsigned int scl_pin; /* scl 对应的引脚 */ int udelay; /* 信号触发延时,直接决定SCL引脚的频率:(500/udelay)kHz */ int timeout; /* 如果从设备的SCL低电平保持大于timeout jiffies,传输过程认为超时 */ unsigned int sda_is_open_drain:1; /* 将SDA引脚设置成开漏输出,开漏的意思是,如果设置成开漏,引脚外部没有上拉,高电平输不出来 */ unsigned int scl_is_open_drain:1; /* 将SCL引脚设置成开漏输出 */ unsigned int scl_is_output_only:1;/* */ };
(2)i2c_gpio_setsda_dir,设置 i2c_gpio_platform_data 结构体中的 SDA 引脚的方向:1输入,0输出
/* 改变SDA引脚的方向 */ static void i2c_gpio_setsda_dir(void *data, int state) { struct i2c_gpio_platform_data *pdata = data; /* */ if (state) gpio_direction_input(pdata->sda_pin); else gpio_direction_output(pdata->sda_pin, 0); }
(3)i2c_gpio_setsda_val,设置SDA引脚上的值高/低,state可以的取值是GPIO_HIGHT / GPIO_LOW
/* * 改变 SDA 引脚上的电平. This is only * valid for pins configured as open drain (i.e. setting the value * high effectively turns off the output driver.) */ static void i2c_gpio_setsda_val(void *data, int state) { struct i2c_gpio_platform_data *pdata = data; gpio_set_value(pdata->sda_pin, state); }
(4)i2c_gpio_setscl_dir,设置 SDA 引脚的输入输出方向:1入,0出
/* Toggle SCL by changing the direction of the pin. */ static void i2c_gpio_setscl_dir(void *data, int state) { struct i2c_gpio_platform_data *pdata = data; if (state) gpio_direction_input(pdata->scl_pin); else gpio_direction_output(pdata->scl_pin, 0); }
(5)i2c_gpio_setscl_val,设置 SCL 引脚上的高低电平,state可以取值是GPIO_HIGHT / GPIO_LOW
/* * Toggle SCL by changing the output value of the pin. This is used * for pins that are configured as open drain and for output-only * pins. The latter case will break the i2c protocol, but it will * often work in practice. */ static void i2c_gpio_setscl_val(void *data, int state) { struct i2c_gpio_platform_data *pdata = data; gpio_set_value(pdata->scl_pin, state); }
(6)得到 SDA , SCL 引脚上的电平,返回值的取值是 GPIO_HIGHT /GPIO_LOW
static int i2c_gpio_getsda(void *data) { struct i2c_gpio_platform_data *pdata = data; return gpio_get_value(pdata->sda_pin); } static int i2c_gpio_getscl(void *data) { struct i2c_gpio_platform_data *pdata = data; return gpio_get_value(pdata->scl_pin); }
(7)然后我们来看看模块的初始化和退出
static int __init i2c_gpio_init(void) { int ret; ret = platform_driver_register(&i2c_gpio_driver); /* 将驱动注册到系统中 */ if (ret) printk(KERN_ERR "i2c-gpio: probe failed: %d\n", ret); return ret; } subsys_initcall(i2c_gpio_init); static void __exit i2c_gpio_exit(void) { platform_driver_unregister(&i2c_gpio_driver); /* 相应的释放函数 */ } module_exit(i2c_gpio_exit);
(8)注册的这个驱动 i2c_gpio_driver 如下
static struct platform_driver i2c_gpio_driver = { .driver = { .name = "i2c-gpio", .owner = THIS_MODULE, .of_match_table = of_match_ptr(i2c_gpio_dt_ids), }, .probe = i2c_gpio_probe, .remove = __devexit_p(i2c_gpio_remove), };
其中的 of_match_ptr 由于用的是平台文件的匹配方式,CONFIG_OF 这个宏(跟设备树有关)没有开启,因此,此处的 of_match_ptr 返回值是 i2c_gpio_dt_ids
#if defined(CONFIG_OF) static const struct of_device_id i2c_gpio_dt_ids[] = { { .compatible = "i2c-gpio", }, { /* sentinel */ } }; MODULE_DEVICE_TABLE(of, i2c_gpio_dt_ids); #endif
可以看到,即使 i2c_gpio_dt_ids 也依赖于 CONFIG_OF 这个宏,因此 .of_match_table 没有定义,根据匹配的规则,最后检查的是 .name ,这要是有 设备的名字是 i2c-gpio 就能匹配上了,匹配上,将调用 i2c_gpio_probe,此部分代码加到,部分代码将用语言代替他的实现
static int __devinit i2c_gpio_probe(struct platform_device *pdev) { struct i2c_gpio_private_data *priv; struct i2c_gpio_platform_data *pdata; struct i2c_algo_bit_data *bit_data; struct i2c_adapter *adap; int ret; priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL); adap = &priv->adap; bit_data = &priv->bit_data; pdata = &priv->pdata; if (pdev->dev.of_node) { /* 没有设备树,执行 else */ ret = of_i2c_gpio_probe(pdev->dev.of_node, pdata); if (ret) return ret; } else { if (!pdev->dev.platform_data) return -ENXIO; memcpy(pdata, pdev->dev.platform_data, sizeof(*pdata)); } /* 检查 SDA 和 SCL 引脚是不是有效 */ if (pdata->sda_is_open_drain) { /* 这里实现的是当输出的时候,将开漏打开,引脚上有上拉电阻,输入的时候,关闭开漏,引脚上无上拉电阻 */ gpio_direction_output(pdata->sda_pin, 1); /* 将 SDA 引脚设置成输出,并且输出为 1 */ bit_data->setsda = i2c_gpio_setsda_val; } else { gpio_direction_input(pdata->sda_pin); bit_data->setsda = i2c_gpio_setsda_dir; } if (pdata->scl_is_open_drain || pdata->scl_is_output_only) { gpio_direction_output(pdata->scl_pin, 1); bit_data->setscl = i2c_gpio_setscl_val; } else { gpio_direction_input(pdata->scl_pin); bit_data->setscl = i2c_gpio_setscl_dir; } if (!pdata->scl_is_output_only) /* 绑定i2c_algo_bit_data结构体 bit_data 中的 getscl 函数 */ bit_data->getscl = i2c_gpio_getscl; bit_data->getsda = i2c_gpio_getsda; /* 绑定i2c_algo_bit_data结构体 bit_data 中的 getsda 函数 */ if (pdata->udelay) /* 绑定udelay,用于设置scl的频率 */ bit_data->udelay = pdata->udelay; else if (pdata->scl_is_output_only) /* 默认的,并且 scl 设置成单输出,频率是 10kHz */ 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 */ bit_data->data = pdata; adap->owner = THIS_MODULE; snprintf(adap->name, sizeof(adap->name), "i2c-gpio%d", pdev->id); /* i2c_adapter 的名字 */ adap->algo_data = bit_data; adap->class = I2C_CLASS_HWMON | I2C_CLASS_SPD; adap->dev.parent = &pdev->dev; adap->dev.of_node = pdev->dev.of_node; /* * 如果 "dev->id" 是负数,我们认为是0. * The reason to do so is to avoid sysfs names that only make * sense when there are multiple adapters. */ adap->nr = (pdev->id != -1) ? pdev->id : 0; ret = i2c_bit_add_numbered_bus(adap); /* adapter 跟 i2c总线关联,如果 nr = -1,i2c的号自动分配,得到适配器,返回0成功 */ if (ret) goto err_add_bus; of_i2c_register_devices(adap); /* CONFIG_OF_I2C和CONFIG_OF_I2C_MODULE都没定义,不执行这句 */ platform_set_drvdata(pdev, priv); /* ... */ dev_info(&pdev->dev, "using pins %u (SDA) and %u (SCL%s)\n", pdata->sda_pin, pdata->scl_pin, pdata->scl_is_output_only ? ", no clock stretching" : ""); return 0; err_add_bus: gpio_free(pdata->scl_pin); err_request_scl: gpio_free(pdata->sda_pin); err_request_sda: return ret; }
相应的要有删除函数
static int __devexit i2c_gpio_remove(struct platform_device *pdev) { struct i2c_gpio_private_data *priv; struct i2c_gpio_platform_data *pdata; struct i2c_adapter *adap; priv = platform_get_drvdata(pdev); adap = &priv->adap; pdata = &priv->pdata; i2c_del_adapter(adap); /* 删除适配器 */ gpio_free(pdata->scl_pin); gpio_free(pdata->sda_pin); return 0; }
要用这个文件,需要做的是让内核支持,我用的内核版本是3.4.112
make menuconfig
选择 Device Drivers --->
<*> I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
重新将uImage放到开发板上,在根文件系统下会多出个节点:
./sys/bus/platform/drivers/i2c-gpio
3. 实例
对于上边的程序,在driver中已经存在,对于我们需要做的是编写 设备程序
设备可以在用户板级的配置文件中编写,也可以单独的写一个模块,然后加载,以下程序采用后者
由于手头上有个 mpu6050,所以,就以mpu6050为例,采用的传感器的小板子是 GY-521
mpu6050_gpio_dev.c
#include <linux/kernel.h> #include <linux/module.h> #include <linux/platform_device.h> #include <linux/i2c-gpio.h> #include <mach/regs-gpio.h> #include <mach/hardware.h> #include <linux/i2c.h> #include <linux/delay.h> MODULE_LICENSE("GPL"); static struct i2c_gpio_platform_data i2c_gpio_adapter_data = { .sda_pin = S3C2410_GPB(8), .scl_pin = S3C2410_GPB(7), .udelay = 50, //5,100kHz 50,10kHz .timeout = 200, /* .sda_is_open_drain = 1, */ /* .scl_is_open_drain = 1, */ /* .scl_is_output_only = 1, */ }; static void mxs_nop_release(struct device *dev) { printk("mpu6050_i2c_gpio_dev release\n"); } static struct platform_device i2c_gpio = { .name = "i2c-gpio", .id = 1, .dev = { .platform_data = &i2c_gpio_adapter_data, .release = mxs_nop_release, }, }; static int mpu6050_i2c_gpio_dev_init(void) { printk("mpu6050_i2c_gpio_dev_init.\n"); platform_device_register(&i2c_gpio); return 0; } static void mpu6050_i2c_gpio_dev_exit(void) { printk("mpu6050_dev_exit.\n"); platform_device_unregister(&i2c_gpio); } module_init(mpu6050_i2c_gpio_dev_init); module_exit(mpu6050_i2c_gpio_dev_exit);
Makefile
ifeq ($(KERNELRELEASE),) #KERNELDIR ?= /lib/modules/$(shell uname -r)/build KERNELDIR ?= ~/wor_lip/linux-3.4.112 PWD := $(shell pwd) modules: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules modules_install: $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install clean: rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions modules* Module* .PHONY: modules modules_install clean else obj-m := mpu6050_gpio_dev.o endif
【1】将编译生成的文件,拷贝到开发板的文件系统中去,加载后出现匹配成功的字符,会在 /dev 文件夹下创建 i2c-1 ,还有一个 i2c-0 是用控制器控制的i2c接口,前边章节有讲
【2】我们使用的时候只需要像 i2c驱动二:devfs文件系统中的方法一样就行,只需要将读取的文件改成 i2c-1 即可,应用程序如下
mpu6050_devfs.c
#include <stdio.h> #include <linux/i2c.h> #include <linux/i2c-dev.h> #include <unistd.h> #include <fcntl.h> #include <sys/ioctl.h> #define ARRAY_SIZE(arr) (sizeof(arr) / sizeof((arr)[0])) #define SMPLRT_DIV 0x19 #define CONFIG 0x1A #define GYRO_CONFIG 0x1B #define ACCEL_CONFIG 0x1C #define ACCEL_XOUT_H 0x3B #define ACCEL_XOUT_L 0x3C #define ACCEL_YOUT_H 0x3D #define ACCEL_YOUT_L 0x3E #define ACCEL_ZOUT_H 0x3F #define ACCEL_ZOUT_L 0x40 #define TEMP_OUT_H 0x41 #define TEMP_OUT_L 0x42 #define GYRO_XOUT_H 0x43 #define GYRO_XOUT_L 0x44 #define GYRO_YOUT_H 0x45 #define GYRO_YOUT_L 0x46 #define GYRO_ZOUT_H 0x47 #define GYRO_ZOUT_L 0x48 #define PWR_MGMT_1 0x6B #define ADDR_MPU6050 0x68 static int mpu6050_read_byte(int fd, unsigned char reg) { int ret = 0; unsigned char txbuf[1] = {reg}; unsigned char rxbuf[1]; struct i2c_rdwr_ioctl_data mpu_data; ioctl(fd, I2C_TIMEOUT, 1); ioctl(fd, I2C_RETRIES, 2); struct i2c_msg msg[] = { { .addr = ADDR_MPU6050, /* 设备的地址 */ .flags= 0, /* 0 是写,I2C_RDWR 是读 */ .len = ARRAY_SIZE(txbuf), /* msg 的长度 */ .buf = txbuf }, {ADDR_MPU6050, I2C_M_RD, ARRAY_SIZE(rxbuf), rxbuf}, }; mpu_data.msgs = msg; mpu_data.nmsgs = ARRAY_SIZE(msg); ret = ioctl(fd, I2C_RDWR, &mpu_data); if (ret < 0) { printf("ret = %d\n", ret); return ret; } return rxbuf[0]; } static int mpu6050_write_byte(int fd, unsigned char reg, unsigned char val) { unsigned char txbuf[2] = {reg, val}; struct i2c_rdwr_ioctl_data mpu_data; ioctl(fd, I2C_TIMEOUT, 1); ioctl(fd, I2C_RETRIES, 2); struct i2c_msg msg[] = { {ADDR_MPU6050, 0, ARRAY_SIZE(txbuf), txbuf}, }; mpu_data.msgs = msg; mpu_data.nmsgs = ARRAY_SIZE(msg); ioctl(fd, I2C_RDWR, &mpu_data); return 0; } static void read_mpu6050(int fd) { unsigned short accel_x = 0, accel_y = 0, accel_z = 0; unsigned short gyro_x = 0, gyro_y = 0, gyro_z = 0; unsigned short temp = 0; mpu6050_write_byte(fd, PWR_MGMT_1, 0x00); mpu6050_write_byte(fd, SMPLRT_DIV, 0x07); mpu6050_write_byte(fd, CONFIG, 0x06); mpu6050_write_byte(fd, GYRO_CONFIG, 0x18); mpu6050_write_byte(fd, ACCEL_CONFIG, 0x01); while(1) { accel_x = mpu6050_read_byte(fd, ACCEL_XOUT_L); accel_x |= mpu6050_read_byte(fd, ACCEL_XOUT_H) << 8; accel_y = mpu6050_read_byte(fd, ACCEL_YOUT_L); accel_y |= mpu6050_read_byte(fd, ACCEL_YOUT_H) << 8; accel_z = mpu6050_read_byte(fd, ACCEL_ZOUT_L); accel_z |= mpu6050_read_byte(fd, ACCEL_ZOUT_H) << 8; printf("acceleration data: x = %04x, y = %04x, z = %04x\n", accel_x, accel_y, accel_z); gyro_x = mpu6050_read_byte(fd, GYRO_XOUT_L); gyro_x |= mpu6050_read_byte(fd, GYRO_XOUT_H) << 8; gyro_y = mpu6050_read_byte(fd, GYRO_YOUT_L); gyro_y |= mpu6050_read_byte(fd, GYRO_YOUT_H) << 8; gyro_z = mpu6050_read_byte(fd, GYRO_ZOUT_L); gyro_z |= mpu6050_read_byte(fd, GYRO_ZOUT_H) << 8; printf("gyroscope data: x = %04x, y = %04x, z = %04x\n", gyro_x, gyro_y, gyro_z); temp = mpu6050_read_byte(fd, TEMP_OUT_L); temp |= mpu6050_read_byte(fd, TEMP_OUT_H) << 8; printf("temperature data: %x\n", temp); usleep(1000*1000); } } int main(int argc, const char *argv[]) { int fd; fd = open("/dev/i2c-1", O_RDWR); if (fd < 0) perror("open error"); read_mpu6050(fd); close(fd); return 0; }
【1】编译命令别忘了:
arm-none-linux-gnueabi-gcc mpu6050_devfs.c -o mpu6050_devfs -march=armv4t
【2】以下是运行成功的打印信息
作者:qqliyunpeng 发表于2016/11/16 18:45:10 原文链接
阅读:51 评论:0 查看评论