3 i2c-dev 3.1 概述 之前在介紹I2C子系統時,提到過使用i2c-dev.c文件在應用程式中實現我們的I2C從設備驅動。不過,它實現的是一個虛擬,臨時的i2c_client,隨著設備文件的打開而產生,並隨著設備文件的關閉而撤銷。I2c-dev.c針對每個I2C適配器生成一個主設備號為89
3 i2c-dev
3.1 概述
之前在介紹I2C子系統時,提到過使用i2c-dev.c文件在應用程式中實現我們的I2C從設備驅動。不過,它實現的是一個虛擬,臨時的i2c_client,隨著設備文件的打開而產生,並隨著設備文件的關閉而撤銷。I2c-dev.c針對每個I2C適配器生成一個主設備號為89的設備文件,實現了i2c_driver的成員函數以及文件操作介面,所以i2c-dev.c的主題是”i2c_driver成員函數+字元設備驅動”。
3.2 i2c-dev.c源碼分析
初始化模塊
[cpp] view plaincopy- static int __init i2c_dev_init(void)
- {
- res= register_chrdev(I2C_MAJOR, "i2c", &i2cdev_fops);
- i2c_dev_class= class_create(THIS_MODULE, "i2c-dev");
- /*Keep track of adapters which will be added or removed later */
- res= bus_register_notifier(&i2c_bus_type, &i2cdev_notifier);
- /*綁定已經存在的適配器 */
- i2c_for_each_dev(NULL,i2cdev_attach_adapter);
- }
I2c-dev初始化函數主要做了註冊名為”i2c”的字元設備文件和”i2c-dev”的類
i2cdev_read和i2cdev_write
I2c-dev.c中實現的i2cdev_read和i2cdev_write函數不具有太強的通用性,只適合下麵這種單開始信號情況:
而不適合多開始信號的情況:
所以我們經常會使用i2cdev_ioctl函數的I2C_RDWR,在分析i2cdev_ioctl函數之前,我們需要瞭解一個結構體:
[cpp] view plaincopy- /* This is the structure as used in theI2C_RDWR ioctl call */
- struct i2c_rdwr_ioctl_data {
- structi2c_msg __user *msgs; /* pointersto i2c_msgs */
- __u32nmsgs; /* number ofi2c_msgs */
- };
Msgs 表示單個開始信號傳遞的數據;
Nmsgs 表示有多少個msgs,比如上圖,單開始信號時,nmsgs等於1;多開始信號時,nmsgs等於2
[cpp] view plaincopy
- struct i2c_msg {
- __u16addr; /* slave address */
- __u16flags; /* 預設為寫入 */
- #define I2C_M_TEN 0x0010 /*this is a ten bit chip address */
- #define I2C_M_RD 0x0001 /* read data,from slave to master */
- #define I2C_M_NOSTART 0x4000 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_REV_DIR_ADDR 0x2000 /*if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_IGNORE_NAK 0x1000 /*if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_NO_RD_ACK 0x0800 /* if I2C_FUNC_PROTOCOL_MANGLING */
- #define I2C_M_RECV_LEN 0x0400 /* length will be first received byte */
- __u16len; /* msg length */
- __u8*buf; /* pointer to msgdata */
- };
3.3 eeprom實例
預備知識
使用的ok6410開發板,eeprom的地址為0x50,實驗完成一個數據的讀寫,先看下讀寫時序
AT24C02任意地址位元組寫的時序:
AT24C02任意地址位元組寫的時序:
用戶態驅動:
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <stdlib.h>
//#include <linux/types.h>
#define I2C_RDWR 0x0707
struct i2c_msg {
unsigned short addr; /* slave address */
unsigned short flags;
unsigned short len; /* msg length */
unsigned char *buf; /* pointer to msg data */
};
struct i2c_rdwr_ioctl_data {
struct i2c_msg *msgs; /* pointers to i2c_msgs */
unsigned int nmsgs; /* number of i2c_msgs */
};
int main()
{
int fd;
struct i2c_rdwr_ioctl_data e2prom_data;
//1. 打開通用設備文件
fd = open("/dev/i2c-0", O_RDWR);
//為i2c_rdwr_ioctl_data中的struct i2c_msg *分配空間
e2prom_data.msgs = (struct i2c_msg *)malloc(2*sizeof(struct i2c_msg)); // 構造兩條消息
//2. 構造寫數據到eeprom
e2prom_data.nmsgs = 1; // 只有一條消息
(e2prom_data.msgs[0]).len = 2; //長度等於2,第一個位元組代表的是i2c設備的內部地址,第二個位元組代表的是寫入的數據
(e2prom_data.msgs[0]).addr = 0x50; // 從設備地址(e2prom的地址),註意這裡是不帶方向的!
(e2prom_data.msgs[0]).flags = 0; // 方向由flag標誌位來指明,0代表了寫,1代表了讀
(e2prom_data.msgs[0]).buf = (unsigned char*)malloc(2); // 這裡只分配兩個位元組(內部偏移地址一位元組,數據1位元組)
(e2prom_data.msgs[0]).buf[0] = 0x10; // 數據將寫入e2prom中的內部0x10地址中
(e2prom_data.msgs[0]).buf[1] = 0x60; // 寫入e2prom中內 部0x10地址中的數據位0x60
//3. 使用ioctl寫入數據
ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data, I2C_RDWR); // 這裡面的命令參數對應的是驅動內部的ioctl中的case語句中的參數
//4. 構造從eeprom讀數據的消息
e2prom_data.nmsgs = 2; // 讀數據需要兩條消息
(e2prom_data.msgs[0]).len = 1; //長度為一個位元組,代表的是i2c設備的內部地址
(e2prom_data.msgs[0]).addr = 0x50; // 從設備地址(e2prom的地址),註意這裡是不帶方向的!
(e2prom_data.msgs[0]).flags = 0; // 方向由flag標誌位來指明,0代表了寫,1代表了讀
(e2prom_data.msgs[0]).buf[0] = 0x10; // 數據將寫入e2prom中的內部0x10地址中
// 第二條消息(讀數據)
(e2prom_data.msgs[1]).len = 1; //長度為一個位元組,代表的是i2c設備的內部地址
(e2prom_data.msgs[1]).addr = 0x50; // 從設備地址(e2prom的地址),註意這裡是不帶方向的!
(e2prom_data.msgs[1]).flags = 1; // 方向由flag標誌位來指明,0代表了寫,1代表了讀
(e2prom_data.msgs[1]).buf = (unsigned char*)malloc(2);
(e2prom_data.msgs[1]).buf[0] = 0; // 數據從e2prom中的內部0x10地址中讀出
//5. 使用ioctl讀出數據
ioctl(fd, I2C_RDWR, (unsigned long)&e2prom_data);
printf("buf[0] = %x\n", (e2prom_data.msgs[1]).buf[0]);
//6. 關閉設備
close(fd);
}