有关linux的i2c相关文章有一下几篇,他们互相关联,应该一同看:
- i2c 驱动一:简介
- i2c 驱动二:devfs文件系统
- i2c 驱动三:自己实现设备和驱动分离
- i2c 驱动四:sysfs文件系统
- i2c 驱动五:gpio模拟i2c
gpio模拟i2c驱动可以解决i2c控制器不足的问题,但是,相对的可能要占用更多的cpu时间,此程序依然使用的是jz2440开发板
我们从 i2c-gpio.c 开始,文件路径:drivers/i2c/busses
struct i2c_gpio_private_data {
struct i2c_adapter adap;
struct i2c_algo_bit_data bit_data;
struct i2c_gpio_platform_data pdata;
};
第二个成员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 */
};
/**
* 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;/* */
};
/* 改变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);
}
/*
* 改变 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);
}
/* 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);
}
/*
* 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);
}
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);
}
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);
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),
};
#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
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;
}
make menuconfig
选择 Device Drivers --->
<*> I2C support --->
I2C Hardware Bus support --->
<*> GPIO-based bitbanging I2C
重新将uImage放到开发板上,在根文件系统下会多出个节点:
./sys/bus/platform/drivers/i2c-gpio
对于上边的程序,在driver中已经存在,对于我们需要做的是编写 设备程序
设备可以在用户板级的配置文件中编写,也可以单独的写一个模块,然后加载,以下程序采用后者
由于手头上有个 mpu6050,所以,就以mpu6050为例,采用的传感器的小板子是 GY-521
mpu6050_gpio_dev.c
#include
#include
#include
#include
#include
#include
#include
#include
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, */ /* 从这里可以推测的是这里是设置芯片,如果是 0,则,这个引脚不设置成开漏的模式 */
/* .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
【2】我们使用的时候只需要像 i2c驱动二:devfs文件系统中的方法一样就行,只需要将读取的文件改成 i2c-1 即可,应用程序如下
mpu6050_devfs.c
#include
#include
#include
#include
#include
#include
#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;
}
arm-none-linux-gnueabi-gcc mpu6050_devfs.c -o mpu6050_devfs -march=armv4t
【2】以下是运行成功的打印信息