历史上的今天
今天是:2024年08月30日(星期五)
2019年08月30日 | S5PV210开发 -- I2C 你知道多少?(二)
2019-08-30 来源:eefocus
上一篇主要是介绍了下芯片手册 I2C 部分,都应该看些什么,以及上拉电阻取值和传输速率模式选择。
这一篇该来点程序了,首先以 AT24C02 (EEPROM)为基础介绍一下I2C设备驱动编程,然后以 MT9P031 为基础介绍 LINUX 下内核配置。 最后是 MPU6050 为基础的单片机下 I2C 通信程序。
一、I2C设备驱动编程
该部分我会以嵌入式Linux软硬件开发详解第 12 章,和Linux设备驱动开发详解第 15 章为参考来展开。
(1)I2C 设备驱动程序结构
1. I2C设备驱动层次结构
从系统的角度来看,Linux中 I2C 设备程序所处的位置如下图所示。

I2C设备驱动程序包含总线驱动层和设备驱动层两部分。设备驱动层为应用的 open、read、write 等提供相对应的接口函数,但是涉及具体的硬件操作,例如寄存器的操作等,则由总线驱动层来完成。
一般来说,针对具体的硬件平台,生产厂家通常已经写好总线驱动层相关内容,用户只要在内核配置选项中选择就可以了。


进行上述操作即为 S5PV210 选择了总线驱动层的代码,而程序设计者只需要编写设备驱动层的代码。
2. I2C 设备驱动程序
在设备驱动层,Linux内核对 I2C 设备驱动代码的组织符合 Linux 的设备驱动模型。如下图所示:

Linux 内核提供了 i2c_bus_type 总线来管理设备和驱动,左侧为多个 I2C 设备组成的设备链表,以 i2c_client 结构体来表示各个设备;右侧为适用于多个具体 I2C 设备驱动程序组成的驱动链表,以 i2c_driver 结构体来表示不同的驱动程序,下面我们对其进行简要的介绍。
这些结构体都是在 linux-2.6.32.17/include/linux/i2c.h 头文件下定义的,可自行查看相关源码。
1)I2C 设备
描述 I2C 设备的结构体为 i2c_client,其代码如下:
struct i2c_client {
unsigned short flags; /* div., see below */
unsigned short addr; /* chip address - NOTE: 7bit */
/* addresses are stored in the */
/* _LOWER_ 7 bits */
char name[I2C_NAME_SIZE];
struct i2c_adapter *adapter; /* the adapter we sit on */
struct i2c_driver *driver; /* and our access routines */
struct device dev; /* the device structure */
int irq; /* irq issued by device */
struct list_head detected;
};
I2C 设备结构体 i2c_client 是描述 I2C 设备的基本模板,驱动程序的设备结构应该包含该结构。
2)I2C 驱动
描述 I2C 驱动的结构体为 i2c_driver,其代码如下。
struct i2c_driver {
unsigned int class;
/* Notifies the driver that a new bus has appeared or is about to be
* removed. You should avoid using this if you can, it will probably
* be removed in a near future.
*/
int (*attach_adapter)(struct i2c_adapter *);
int (*detach_adapter)(struct i2c_adapter *);
/* Standard driver model interfaces */
int (*probe)(struct i2c_client *, const struct i2c_device_id *);
int (*remove)(struct i2c_client *);
/* driver model interfaces that don't relate to enumeration */
void (*shutdown)(struct i2c_client *);
int (*suspend)(struct i2c_client *, pm_message_t mesg);
int (*resume)(struct i2c_client *);
/* a ioctl like command that can be used to perform specific functions
* with the device.
*/
int (*command)(struct i2c_client *client, unsigned int cmd, void *arg);
struct device_driver driver;
const struct i2c_device_id *id_table;
/* Device detection callback for automatic device creation */
int (*detect)(struct i2c_client *, int kind, struct i2c_board_info *);
const struct i2c_client_address_data *address_data;
struct list_head clients;
};
i2c_driver 结构体中,probe 成员为加载 I2C 驱动程序时探测 I2C 设备所调用的函数,而 remove 函数实现相反的功能。i2c_device_id 结构体代码如下。
struct i2c_device_id {
char name[I2C_NAME_SIZE];
kernel_ulong_t driver_data /* Data private to the driver */
__attribute__((aligned(sizeof(kernel_ulong_t))));
};
代码中 name 成员保存设备的名称,如“at24c02”等。
i2c_driver 结构体成员中我们只需要初始化 probe 和 remove 就够了,其他的函数都是可选的。
3)I2C 总线
描述I2C总线的结构体为i2c_bus_type,其代码如下。
struct bus_type i2c_bus_type = {
.name = "i2c",
.match = i2c_device_match,
.probe = i2c_device_probe,
.remove = i2c_device_remove,
.shutdown = i2c_device_shutdown,
.suspend = i2c_device_suspend,
.resume = i2c_device_resume,
};
i2c_bus_type 总线进行设备和驱动程序的匹配,依靠的是其 match 成员函数,其代码如下。
static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
struct i2c_client *client = i2c_verify_client(dev);
struct i2c_driver *driver;
if (!client)
return 0;
driver = to_i2c_driver(drv);
/* match on an id table if there is one */
if (driver->id_table)
return i2c_match_id(driver->id_table, client) != NULL;
return 0;
}
该函数调用了 i2c_match_id 函数,i2c_match_id 函数的内容如下。
static const struct i2c_device_id *i2c_match_id(const struct i2c_device_id *id,
const struct i2c_client *client)
{
while (id->name[0]) {
if (strcmp(client->name, id->name) == 0)
return id;
id++;
}
return NULL;
}
我们可以看出,match 函数实质是监测 client 描述的设备名称和驱动程序对应的设备名是否一致,如果一致,即找到了和设备对匹配的驱动程序。
上述为 2.6.35 版本 Linux 内核下的 I2C 设备驱动的框架,这里还涉及一些其他的结构体和函数,我们在示例中进行讲解。
(2)AT24C02 设备驱动程序
1. AT24C02 设备驱动程序
S5PV210 开发板具有一片 AMTEL 公司的 I2C 接口 EEPROM 芯片,型号是 AT24C02,其驱动程序代码如下。
1)AT24Cxx 设备代码
//设备驱动 at24cxx_dev.c
#include #include #include #include #include #include //声明i2c_board_info 结构体 static struct i2c_board_info at24cxx_info = { //设备名 和 设备地址 I2C_BOARD_INFO ("at24c02", 0x50); } //初始化 i2c_client static struct i2c_client *at24cxx_client; static int at24cxx_dev_init (void) { //获取 I2C 总线适配器 struct i2c_adapter *i2c_adap; //获取0号适配器 i2c_adap = i2c_get_adapter (0); //将AT24CXX 加入0号适配器对应的总线管理设备链表中 at24cxx_client = i2c_new_device (i2c_adap, &at24cxx_info); i2c_put_adapter (i2c_adap); return 0; } static void at24cxx_dev_exit (void) { i2c_unregister_device (at24cxx_client); } module_init (at24cxx_dev_init); module_exit (at24cxx_dev_exit); MODULE_LICENSE ("GPL"); 2)AT24Cxx驱动代码 //驱动代码 at24cxx_drv.c #include #include #include #include #include #include #include #include static int major; static struct class *class; static struct i2c_client *at24cxx_client; static ssize_t at24cxx_read(struct file * file, char __user *buf, size_t count, loff_t *off) { unsigned char addr, data; copy_from_user(&addr, buf, 1); data = i2c_smbus_read_byte_data(at24cxx_client, addr); copy_to_user(buf, &data, 1); return 1; } static ssize_t at24cxx_write(struct file *file, const char __user *buf, size_t count, loff_t *off) { unsigned char ker_buf[2]; unsigned char addr, data; copy_from_user(ker_buf, buf, 2); addr = ker_buf[0]; data = ker_buf[1]; printk("addr = 0x%02x, data = 0x%02xn", addr, data); if (!i2c_smbus_write_byte_data(at24cxx_client, addr, data)) return 2; else return -EIO; } static struct file_operations at24cxx_fops = { .owner = THIS_MODULE, .read = at24cxx_read, .write = at24cxx_write, }; static int __devinit at24cxx_probe(struct i2c_client *client, const struct i2c_device_id *id) { at24cxx_client = client; //printk("%s %s %dn", __FILE__, __FUNCTION__, __LINE__); major = register_chrdev(0, "at24cxx", &at24cxx_fops); class = class_create(THIS_MODULE, "at24cxx"); device_create(class, NULL, MKDEV(major, 0), NULL, "at24cxx"); return 0; } static int __devexit at24cxx_remove(struct i2c_client *client) { //printk("%s %s %dn", __FILE__, __FUNCTION__, __LINE__); device_destroy(class, MKDEV(major, 0)); class_destroy(class); unregister_chrdev(major, "at24cxx"); return 0; } static const struct i2c_device_id at24cxx_id_table[] = { { "at24c08", 0 }, {} }; /* 分配/设置i2c_driver */ static struct i2c_driver at24cxx_driver = { .driver = { .name = "100ask", .owner = THIS_MODULE, }, .probe = at24cxx_probe, .remove = __devexit_p(at24cxx_remove), .id_table = at24cxx_id_table, }; static int at24cxx_drv_init(void) { /* 注册i2c_driver */ i2c_add_driver(&at24cxx_driver); return 0; } static void at24cxx_drv_exit(void) { i2c_del_driver(&at24cxx_driver); } module_init(at24cxx_drv_init); module_exit(at24cxx_drv_exit); MODULE_LICENSE("GPL"); 3)AT24Cxx测试程序 //测试程序 i2c_test.c #include #include #include #include #include #include /* i2c_test r addr i2c_test w addr val */ void print_usage(char *file) { printf("%s r addrn", file); printf("%s w addr valn", file); } int main(int argc, char **argv) { int fd; unsigned char buf[2]; if ((argc != 3) && (argc != 4)) { print_usage(argv[0]); return -1; } fd = open("/dev/at24cxx", O_RDWR); if (fd < 0) { printf("cant open /dev/at24cxxn"); return -1; } if (strcmp(argv[1], "r") == 0) { buf[0] = strtoul(argv[2], NULL, 0); read(fd, buf, 1); printf("data: %c, %d, 0x%2xn", buf[0], buf[0], buf[0]); } else if ((strcmp(argv[1], "w") == 0) && (argc == 4)) { buf[0] = strtoul(argv[2], NULL, 0); buf[1] = strtoul(argv[3], NULL, 0); if (write(fd, buf, 2) != 2) printf("write err, addr = 0xx, data = 0xxn", buf[0], buf[1]); } else { print_usage(argv[0]); return -1; } return 0; } 4)Makefile ifneq ($(KERNELRELEASE),) obj-m += at24cxx_drv.o else KERNELDIR=/opt/kernel all: PWD=$(shell pwd) $(MAKE) -C $(KERNELDIR) M=$(PWD) clean: rm -rf .*.cmd *.o *.mod.c *.ko .tmp_versions module* Module* endif 5)开发板测试 执行 make 生成驱动 at24cxx_drv.ko # ls at24cxx_dev.c at24cxx_drv.ko at24cxx_drv.mod.o built-in.o Makefile modules.order at24cxx_drv.c at24cxx_drv.mod.c at24cxx_drv.o i2c_test.c Makefile~ Module.symvers 在开发板新建一个 driver 文件夹,使用 tftp 工具将其拷贝到开发板 /driver 目录下 tftp -g -r at24cxx_drv.ko 192.168.x.xx 加载驱动: insmod /driver/at24c02_drv.ko 然后编译测试文件 i2c_test.c 生成可执行文件 i2c_test # arm-none-linux-gnueabi-gcc i2c_test.c -o i2c_test 其中这里如果对于交叉编译不熟悉的人会出现问题,line 1: syntax error: unexpected word (expecting ")") 解决,参看:S5PV210开发 -- 交叉编译器 最后,拷贝到开发板执行 ./i2c_test 因为编译kernel 和 运行的内核版本不一致,加载驱动时会出现问题: # insmod at24cxx_drv.ko [ 188.062254] at24cxx_drv: version magic '2.6.35.7-Concenwit preempt mod_unload ARMv7 ' should be '2.6.35.7 preempt mod_unload ARMv7 ' insmod: can't insert 'at24cxx_drv.ko': invalid module format 解决方法: 参看:内核模块编译怎样绕过insmod时的版本检查 (1)执行 modinfo 命令,先看一下 at24cxx_drv.ko 的信息。 # modinfo at24cxx_drv.ko filename: at24cxx_drv.ko license: GPL depends: vermagic: 2.6.35.7-Concenwit preempt mod_unload ARMv7 (2)修改 kernel 的 vermagic,再重新编译设备驱动 vermagic 的第一个值 2.6.35.7-Concenwit 是由这 kernel/include/generated/utsrelease.h 里的 UTS_RELEASE 所定义。(可能utsrelease.h头文件位置不一样,使用find指令自己搜一下)。 之后再由 kernel/include/linux/vermagic.h 去组合出 VERMAGIC_STRING,也就是 kernel 的vermagic。 
史海拾趣
|
我的电路能够得到一系列的方波信号,但是占空比不一样。因为占空比不一样,如果我直接加不可调的低通滤波器转换成正弦波的话,得到的正弦波有些就不能做到关于时间轴对称。那么如果我要实现所有的正弦波都关于时间轴对称(即对任意占空比的方波都能 ...… 查看全部问答> |
|
为什么同一程序在PB下生成的DLL文件和EVC下的DLL不一样? 在PB下将一个类似驱动程序的源代码编译生成一个DLL文件,通过注册表导入系统中可以工作起来,然后现在想通过EVC来修改这个程序,在EVC下将之前的源代码也可以编译生成DLL文件,这是DLL文件比之前的文件大了几K,此时将这个DLL导入系统中,同样的注 ...… 查看全部问答> |
|
不需要代码,只要论文,基本的可以自己搞,就是不知道具体的要做个什么东西,希望好心人指点下,提供这方面的资料借鉴下~! 邮箱:wleuler@163.com… 查看全部问答> |
|
关于CEDDK的READ_PORT_UCHAR(<端口号地址>),其中的端口号地址是在哪里定义的呢? 小弟最近在写一个GPIO驱动,在GPI_Read()流接口函数中想用READ_PORT_UCHAR()从指定的GPIO的端口接受数据。我用的是PXA270,其中的GPIO的端口号地址是在哪里定义的呢?我看了PXA270的datasheet好像没有发现。… 查看全部问答> |
|
我是大四的一名学生,我想毕业可从事嵌入式系统这方面的工作,现在我想学习驱动编程,我只在书上看了一些WIN CE的基本驱动的结构, 我想深入了解, 1)应该从那一方面入手呢,或者说先从那一个模块开始深入了解呢? 2)我需要什么专业知识 ...… 查看全部问答> |
|
我是广州IBM HP两大品牌的服务器代理商。符件内是我公司的资料和参考报价。李伟强:13828482007,QQ:160299358有什么需要随时联系我… 查看全部问答> |




