上一篇主要是介绍了下芯片手册 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。
上一篇:S5PV210开发 -- I2C 你知道多少?(一)
下一篇:S5PV210开发 -- I2C 你知道多少?(三)
推荐阅读
史海拾趣
在电子行业竞争日益激烈的背景下,安普康深知创新是企业发展的核心动力。因此,公司始终将研发投入作为重要战略之一。通过引进先进的生产设备和技术,安普康不断提升产品的品质和性能。同时,公司还积极与国内外知名企业和研究机构合作,共同开展技术研发和创新。这些努力使得安普康在光纤产品、布线产品等领域取得了多项专利,并成功推出了一系列具有市场竞争力的新产品。
随着全球环保意识的提升,CalRamic Technologies也积极响应,开始在生产过程中引入环保材料和工艺。公司不仅优化了生产流程,减少了废弃物和污染物的排放,还加大了对可再生能源的使用。这些举措不仅提升了公司的社会责任形象,也为其赢得了更多客户的青睐。同时,公司还开展了一系列环保宣传活动,倡导员工和客户共同参与环保行动,为可持续发展贡献力量。
随着公司业务的不断发展,Electech Electronics开始实施国际化战略,积极开拓海外市场。公司先后在亚洲、欧洲和北美等地设立了分公司和办事处,与当地的企业和渠道商建立了紧密的合作关系。同时,Electech Electronics还积极参加国际电子产品展会,展示公司的最新产品和技术,吸引更多的海外客户。
在取得初步成功的基础上,Array Microsystems Inc深知技术创新是企业持续发展的核心动力。因此,公司加大了对研发的投入力度,不断推出新的阵列传感器产品和技术。这些新产品不仅具有更高的性能和更低的成本,还满足了市场不断变化的需求。通过持续的创新和研发,Array Microsystems Inc在电子行业中保持了领先地位。
Array Microsystems Inc公司自创立之初,便专注于阵列传感器技术的研发。在成立初期,公司面临资金短缺和技术瓶颈的双重挑战。然而,通过不懈的努力和持续的技术创新,Array Microsystems Inc成功研发出了一款高灵敏度、低功耗的阵列传感器。这一突破性的技术不仅填补了市场的空白,还为公司带来了可观的利润。随着产品的推广和应用,Array Microsystems Inc逐渐在电子行业中崭露头角。
在电子行业中,产品质量是企业生存和发展的关键。Crameda Intersys公司始终将质量管理放在首位,建立了严格的质量管理体系。公司从原材料采购、生产制造到产品检验等各个环节都严格把关,确保产品质量的稳定性和可靠性。这种对质量的执着追求赢得了客户的信任和好评,也为公司的长期发展奠定了坚实的基础。
http://www.verycd.com/topics/2754295/ [ 本帖最后由 open82977352 于 2010-2-10 16:33 编辑 ]… 查看全部问答∨ |
|
从mpc8313e的LA[0:25]的LA22 LA23 LA24引出三根线接到DSP的HPI管脚,请问如果DSP的片选基地址设为0xF2000000,那么这个UHPI的基地址是怎么算的? 基地址是0xF2000000 + (1 << (25 - 24))还是0xF200000 + (1 << 22U) 也就是说这个地址该从 ...… 查看全部问答∨ |
|
我下了protel转pads工具的文件为什么里面的ddb2.exe文档不能运行 我习惯于用pads但是公司给的却是protel文件。所以我想将prote转换成pads文件,所以我下了工具,但是当我运行里面的ddb2.exe文件时提示:“cannot find the font file romansim.fnt"但是文件夹里明明有romansim.fnt 文件,为此我很急,请大家帮帮 ...… 查看全部问答∨ |
最近刚开始在研究rfid,就是想试着做一个简单的应用,用的是复旦的FM1715,对于datasheet里面有些描述看不是很懂,上来向大家请教下:对于Read指令, READ 控制单元 --> 射频卡 command:0x30 Len : 1 Data[0] : _Adr 块地址 (0~ ...… 查看全部问答∨ |
wince4.2系统下,新建连接->拨号连接->...+cgdcont=1,"ip","cmnet" *99***1# 其他的都是默认的设置,,但是连接不上,,出错在: 端口不可用:该端口配置不正确,或者其他程序正在使用该端口. 请问GPRS模块上网到底要怎么配置啊 ...… 查看全部问答∨ |
小弟现在在开发上位机程序 用到USB 通讯 ,但是驱动资料上只给除了两个管道名 由于种种原因 这两个都不符合要求 我该怎么样得到管道名 (用一个共享软件 人家都可以得到的) 请高手指点!!… 查看全部问答∨ |
读大学的时候就打算自己做个MP3玩的,搞了一堆的资料来看。 后来参加了电子大赛,DIY MP3的想法就搁置在一边了。 电子大赛完了就基本上开始找工作,直到现在MP3连个影子都木有。 要不大家就一起DIY一个MP3玩玩吧。 虽然现在买一个MP ...… 查看全部问答∨ |
我用STC12C5204AD单片机的IO口控制直流电机开关,接法如电路图,用外部中断1,低电平控制; 有个问题是:上电的时候,空载电机会动一下,适配器的指示灯会暗一会。开关按下电机启动,不过需要长按一两秒之后,电机才会常转,不然会动一会就不转了 ...… 查看全部问答∨ |