了解内容
I2C知识
I2C是2线的:SCL和SDA。无数据时SCL线是高电平,有数据时变为正常方波时钟信号。
SCL信号由主机控制。由图可知每一帧时钟保持9个周期,每帧有1字节数据,第9个时钟周期需要ACK(SDA为低)。
-
同一状态的首帧格式均是:从机地址(高7位)+读写状态(低0位,0为写,1为读);
-
主机写入n帧的实现,按照如下传输
1
通信地址(低0位写0) + 数据地址(1字节) + 数据1(1字节,下同)+数据2+...+数据n
这样数据1会写入数据地址a,数据2写入a+1地址,类推。
-
主机读取n帧的实现,需要分两步
-
写入将读取的地址
1
通信地址(低0位写0) + 数据地址(1字节)
-
读取(低0位写1),接下来从机依次发送该地址起始的数据,主机ack响应,不响应就停止。
1
通信地址(低0位写1)
-
关于通信地址是由硬件连接决定的。
比如D7、D6、D5、D4、D3、D2、D1、D0,可能芯片内部已经决定了部分线的高低(ft5x06最高3位固定了3’b011),D0是R/W位,也就是D4、D3、D2、D1是可自定义的地址码(可能是A0,A1,A2这样的命名)。
举例:网上找到的lm75芯片的手册中就有提到通信地址(与本文使用的芯片无关),该设备地址有7位构成,其中高4位是固定的,低3位由设备的三个引脚电平来决定。
Linux I2C知识
和想象中不同的是我们不用根据时序图写i2c的硬件操作函数,这部分已经被硬件实现控制层完成了。设备驱动(driver 驱动层)的工作仅是根据统一的i2c设备操作函数(比如传输函数)完成应用程序调用接口。
Linux中查看i2c总线下的设备信息:
1
ls /sys/bus/i2c/devices/
以“3-0038”举例,表示第3组i2c总线下0038设备地址。
其在平台文件”arch/arm/mach-exynos/mach-itop4412.c”的注册信息如下:
- 0x70»1即0x38,也就是设备通信地址为0x38;
- “ft5x0x_ts”就是该设备的name,查看命令
cat /sys/bus/i2c/devices/3-0038/name
开始
目标设备是图中左侧的LVDS接口设备。
由于内核中已经申请过3-0038的i2c设备信息,首先需要从内核中取消“TOUCHSCREEN_FT5X0X”,路径是“Device Drivers”–>“Input device support”–>“Touchscreens”–>“FT5X0X based touchscreens”。取消后,编译内核镜像进行烧录会发现ls /sys/bus/i2c/devices/
中已经没有“3-0038”设备了。
紧接着添加平台文件mach-itop4412.c中设备信息,模仿ft5x0x_ts平台设备信息添加即可,为了简单实验这里就不用#if条件编译了,也就是始终编译而不用改Kconfig:
这个数组会被i2c_register_board_info(3,…)函数使用,用于注册第3组i2c中的设备信息。
上述完成后,重新编译内核镜像再次烧录。
这时可以看到cat /sys/bus/i2c/devices/3-0038/name
中打印的是”i2c_demo”:
接下来完成i2c_demo的驱动即可。
一些函数
-
注册i2c驱动函数i2c_add_driver,在
include/linux/i2c.h
中有定义。可类比platform_driver_register函数,性质相同。1
int i2c_add_driver(struct i2c_driver *driver);
传参driver,其中包含了I2C设备的名称、probe、remove等接口信息。
-
i2c数据传输函数i2c_transfer
1
int i2c_transfer(struct i2c_adapter *adap, struct i2c_msg *msgs, int num);
-
adap参数,适配器,它是probe函数的参数“client结构体指针”中的成员。
-
msgs参数,一般定义一个数组,每个成员结构体中有用于传输的关键信息;
写入n个地址的一般声明和赋值方法,下面len改为1+n:
读取n字节的一般声明和赋值方法:
-
num参数,作用是i2c传输依次使用msgs[0]…msgs[num-1],比如读n个地址,先声明msgs[2],然后这里写2。
-
-
注销i2c驱动函数i2c_del_driver
1
i2c_del_driver(struct i2c_driver *driver);
例程
驱动例程
i2c_demo.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include <linux/init.h>
#include <linux/module.h>
#include <linux/i2c.h>
#include <linux/gpio.h>
#include <mach/gpio-exynos4.h>
#include <linux/delay.h>
MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("jerry");
#define DRV_NAME "i2c_demo"
static int demo_probe(struct i2c_client *client, const struct i2c_device_id *id){
int ret = 0;
unsigned char buf_r[1] = {0xa6};//寄存器地址:该i2c芯片Firmware ID
unsigned char buf_w[1] = {0};
struct i2c_msg msgs[] = {
{
.addr = client->addr,
.flags = 0,//写
.len = 1,
.buf = buf_w,
},
{
.addr = client->addr,
.flags = I2C_M_RD,//读
.len = 1,
.buf = buf_r,
},
};
//首先打印i2c从机地址,会打印0x38
printk(KERN_EMERG "client addr is 0x%x!\n",client->addr);
ret = i2c_transfer(client->adapter, msgs, 2);
if(ret < 0)
printk(KERN_EMERG "read Firmware ID fail.\n");
else
printk(KERN_EMERG "Firmware ID is %d.\n",buf_r[0]);
printk(KERN_EMERG "demo_probe ok!\n");
return 0;
}
static int demo_remove(struct i2c_client *client){
printk(KERN_EMERG "demo_remove ok!\n");
return 0;
}
static const struct i2c_device_id demo_id[] = {
{ DRV_NAME, 0 },
{ }//最后一个元素需要是空
};
static struct i2c_driver i2c_drv_demo = {
.probe = demo_probe,
.remove = __devexit_p(demo_remove),
.id_table = demo_id,
.driver = {
.name = DRV_NAME,
.owner = THIS_MODULE,
},
};
static void VccConvEn_ctl(void){
//1.8V到3.3V芯片的使能脚控制
int ret;
ret = gpio_request(EXYNOS4_GPL0(2),"VccConv_En");
if(ret){
printk(KERN_EMERG "gpio request fail!\n");
}
gpio_direction_output(EXYNOS4_GPL0(2),1);
gpio_free(EXYNOS4_GPL0(2));
mdelay(5);
}
static int __init demo_init(void){
VccConvEn_ctl();
i2c_add_driver(&i2c_drv_demo);
return 0;
}
static void __exit demo_exit(void){
i2c_del_driver(&i2c_drv_demo);
}
//如果写内核中,要使用late_initcall后加载
module_init(demo_init);
module_exit(demo_exit);
测试结果
本文测试结果和测试程序是不完整的,因为需要外接FT5X0X硬件模块才能测试具体的读写。所以测试程序仅在probe中实现了读取数据就没有继续实现了,读懂后继续实现很容易。
编写简单Makefile:参考这里
编译生成模块后,拷贝到开发板中:
-
首先加载模块后正常执行probe函数,但是没该硬件模块所以i2c读取失败。