i2c设备驱动实例 ds1307为例
http://blog.csdn.net/airk000/article/details/21345457 http://blog.csdn.net/creazyapple/article/details/7290680 本例的所有代码,可以写在一个.c文件里面。 测试用代码例外。 本例中可能存在隐性的不完整,因为我也不是太懂。
总体思路,1、注册设备。 2、注册驱动。3、注册字符设备。 1、设备注册 关于设备注册,也叫设备实例化,在kernel目录下面的Documentation/i2c/instantiating-devices 可以看看,说了有四种实例化方法。 本例中用的是第二种,显示的实例化设备。 static const unsigned short normal_i2c[] = { 0x68, I2C_CLIENT_END }; //探测用的i2c地址列表// static struct i2c_client *ds1307_client;//这是一个i2c_client指针。驱动中可以直接使用i2c_client指针和设备通信,留到后面与设备通信是用 void ds1307_client_init(void) { struct i2c_adapter *i2c_adap; struct i2c_board_info i2c_info; i2c_adap = i2c_get_adapter(4);//根据总线号获取适配器adapter指针,因为我知道它挂在在4号i2c总线上,所以括号里面是4 memset(&i2c_info,0,sizeof(struct i2c_board_info)); strlcpy(i2c_info.type,"ds1307",I2C_NAME_SIZE);//这里设置了i2c设备的名字ds1307 ds1307_client = i2c_new_probed_device(i2c_adap,&i2c_info,normal_i2c,NULL);//注册i2c设备,依附到4号i2c总线适配器上,返回一个i2c_client指针,这个指针在后面就可以用来和设备通信 i2c_put_adapter(i2c_adap);//释放指针 } void ds1307_client_exit(void) { i2c_unregister_device(ds1307_client);//注销i2c设备 } 在模块初始化时调用前者,退出时调用后者。设备注册这部分就完了。
2、驱动注册 struct time{//自己写的一个结构体,用来存时间 char sec; char minu; char hour; char week; char day; char month; char year; }; struct ds1307_data{//注册驱动可能需要的一个结构体,其中需要i2c_client指针 struct time ds1307_time; struct i2c_client *client; }; 探测函数,在里面可以实现自己的探测方法,应该是返回0表示探测成功。 static int ds1307_probe(struct i2c_client *client,const struct i2c_device_id *id) { struct ds1307_data *data = NULL; struct device *pstDev = &client->dev; printk("~~my ds1307 module ds1307_probe ing~~\n"); data = kzalloc(sizeof(struct ds1307_data),GFP_KERNEL);//申请数据内存 if(NULL == data){ dev_err(pstDev, "alloc ds1307 data memory fail!\n"); return -ENOMEM; } data->client = client; i2c_set_clientdata(client,data);//设置client的数据域,我看名字猜的 dev_info(pstDev,"creat ds1307 client OK~~~\n");//打印消息,自己看的 return 0; }
static int ds1307_remove(struct i2c_client *client) { struct ds1307_data *data = i2c_get_clientdata(client);//获取数据空间地址 kfree(data);//释放 printk("~~my ds1307 module ds1307_remove ing~~\n"); return 0; }
static const struct i2c_device_id ds1307_ids[] = {//i2c驱动支持的设备列表 为设备名字和 设备私有数据? { "ds1307", 0 }, { } }; MODULE_DEVICE_TABLE(i2c, ds1307_ids);//一个宏,将上面的添加到某链表 关键结构体 static struct i2c_driver ds1307_driver = { .driver = { .name = "ds1307",//驱动名字 .owner = THIS_MODULE, }, .probe = ds1307_probe,//探测函数 .remove = ds1307_remove,//卸载函数 .id_table = ds1307_ids,//设备支持表 }; 通常这后面接一句 module_i2c_driver(ds1307_driver);就完了, 但是这样的话,在用户空间写代码也控制不了。而且驱动基本自己跑不起来,需要在某处调用它。 接着写字符设备驱动,这样才能从用户空间访问。
宏module_i2c_driver(ds1307_driver);展开后就是(好像是)
static int __init ds1307_driver_init(void) { return i2c_add_driver(&ds1307_driver);//注册驱动 } module_init(ds1307_init);
static void __exit ds1307_driver_cleanup(void) { i2c_del_driver(&ds1307_driver);//注销驱动 } module_exit(ds1307_cleanup);
3、字符设备驱动 要自己实现的四个函数 static int ds1307_open(struct inode *inode, struct file *file); static int ds1307_release(struct inode *inode, struct file *file); static ssize_t ds1307_read(struct file *file,char __user *user,size_t t,loff_t *f); static ssize_t ds1307_write(struct file *file,const char __user *user,size_t t,loff_t *f);
#define MAX_SIZE 2048 static int device_num = 0;//设备号 static int counter = 0;//计数用 static int mutex = 0;//互斥用 static char *devName = "ds1307";//设备名字 static struct time ds1307_time1;
static int ds1307_open(struct inode *inode, struct file *file) { printk("ds1307_open ing\n"); if(mutex)//用于互斥才有这东西 return -EBUSY; mutex=1; printk("<1>main device : %d\n", MAJOR(inode->i_rdev)); printk("<1>slave device : %d\n", MINOR(inode->i_rdev)); printk("<1>%d times to call the device\n", ++counter); try_module_get(THIS_MODULE); //模块使用量加1 return 0; }
static int ds1307_release(struct inode *inode, struct file *file) { printk("ds1307_release ing\n"); module_put(THIS_MODULE); //模块使用量减1 mutex = 0;//开锁 return 0; }
//与i2c设备通信的函数,需要用到前面返回的i2c_client指针 extern int i2c_master_send(const struct i2c_client * client,const char * buf,int count); extern int i2c_master_recv(const struct i2c_client * client,char * buf,int count); static ssize_t ds1307_read(struct file *file,char __user *user,size_t t,loff_t *f) { int i; char addr,data; char *dstime = &ds1307_time1; printk("ds1307_read ing\n"); //if(copy_to_user(user,message,sizeof(message))) //return -EFAULT; for(i=0;i<=0x06;i++){ addr=i;//秒分时地址从0–>6 if(1 != i2c_master_send(ds1307_client,&addr,1)){//第一个参数是l2c_client指针,第2个是i2c设备上寄存器的地址 注意应该放地址的指针,,第三个参数是发送的个数。 也可以将第二个参数更改为一个数组,这个数组第一位放寄存器地址,第2位放寄存器要设置的数据,然后将要发送的数据置2. 一次发送多个的同理。 printk( KERN_ERR " ds1307_i2c_read fail! \n" ); return -1; } msleep(10);//等一会收取 if(1 != i2c_master_recv(ds1307_client,&data,1)){//第一个参数同上,第二个参数用来存收到的数据,应该填入指针,可以收取一个,可以收取多个,同上。 printk( KERN_ERR " ds1307_i2c_read fail! \n" ); return -1; } //BCD—> DEC 后存储 dstime[i]=(data>>4)*10+(data&0x0f);//因为ds1307中数据是BCD码,所以改成10进制存储以便后面使用 } if(copy_to_user(user,(char *)(&ds1307_time1),sizeof(struct time)));//将内核空间数据发送的用户空间。前者目的地址,后者源地址,第三个是传输数据的大小。第二个需要时char型指针,我将结构体指针强制转换,反正内部全是char型,影响不懂。如果过于复杂,则在用户空间定义一个同样的结构体,用来对收到的数据指针进行强制转换后使用。 return sizeof(ds1307_time1);//返回读取数据的大小 }
//同上面读取函数,这里我就没有修改了,因为在我的测试代码里面,我暂时没有加入写的功能。 static ssize_t ds1307_write(struct file *file,const char __user *user,size_t t,loff_t *f) { printk("ds1307_write ing\n"); if(copy_from_user(message,user,sizeof(message))) return -EFAULT; return sizeof(message); } 字符设备驱动的重要结构体 static const struct file_operations ds1307_fops = { .owner = THIS_MODULE, .read = ds1307_read,//读 .write = ds1307_write,//写 .open = ds1307_open,//打开,此时做初始化的时 .release = ds1307_release,//释放,做改做的释放工作 }; 这个是字符设备模块初始化要做的一些工作,做成一个函数,以便在这整个实例中的这个大模块初始化调用 int ds1307_char_dev_init(void) { int ret; ret = register_chrdev(0, devName, &ds1307_fops); if (ret < 0) { printk("ds1307 char dev—regist failure!\n"); return -1; } else{ printk("the device has been registered!\n"); device_num = ret; printk("<1>the virtual device's major number %d.\n", device_num); printk("<1>Or you can see it by using\n"); printk("<1>——more /proc/devices——-\n"); printk("<1>To talk to the driver,create a dev file with\n"); printk("<1>——'mknod /dev/myDevice c %d 0'——-\n", device_num); printk("<1>Use \"rmmode\" to remove the module\n"); return 0; } } 模块卸载时调用,用来注销字符设备驱动 void ds1307_char_dev_exit(void) { unregister_chrdev(device_num, devName); printk("ds1307_char_dev—-unregister it success!\n"); }
整个模块初始化与卸载 static int __init ds1307_init(void) { ds1307_client_init();//注册设备 ds1307_char_dev_init();//注册字符设备 printk("~~my ds1307 module init ing~~\n"); return i2c_add_driver(&ds1307_driver);//注册驱动 } module_init(ds1307_init);
static void __exit ds1307_cleanup(void) { printk("~~my ds1307 module exit ing~~\n"); i2c_del_driver(&ds1307_driver);//注销驱动 ds1307_client_exit();//注销设备 ds1307_char_dev_exit();//注销字符设备 } module_exit(ds1307_cleanup); 其他 MODULE_DESCRIPTION("mr yangbinbin"); MODULE_AUTHOR("my ds1307 driver"); MODULE_LICENSE("GPL"); 到这里整个驱动部分就写完了。 至于如何编译成模块,参考我前面提供的连接 或者 自行百度。
接下来是测试用的文件代码,这个代码需i2c设备上面编写出来运行。 因为我是在主机上面编写驱动模块,同步到板子上面使用。 用主机上面编译测试代码,生成.o文件,同步过去执行,这会报错,是执行不了的。 因为我gcc用linux内核和编译模块用的Linux内核不一样。。。。
#include #include #include #include #include #include #include #include #define MAX_SIZE 1024 struct time{ char sec; char minu; char hour; char week; char day; char month; char year; };
int main(void) { int fd; char i=0; struct time * time1;//结构体指针 char buf[MAX_SIZE]; //char get[MAX_SIZE]; system("ls /dev/"); fd = open("/dev/ds1307", O_RDWR | O_NONBLOCK);//打开字符设备 if (fd != -1) { while(i<100)//循环读取100次就退出 { read(fd, buf, sizeof(buf));从字符设备中读取数据,改数据来自前面的read函数中copy_to_user函数中的第二个参数 //system("dmesg");内核打印消息,可选 time1=(struct time *)buf;//将消息数组指针强制转换成结构体,方便后面使用 printf("\ntime now is: 20%d-%d-%d %d:%d:%d %d\n",time1->year,time1->month,time1->day,time1->hour,time1->minu,time1->sec,time1->week);//打印时间 格式为 time now is: 2015-11-19 19:36:11 4 i++; sleep(1);//等待1s }
调试过程。 1、编译模块,把.ko下载到板子,使用insmod xx.ko加载 2、使用dmesg看内核打印,里面有打印提示说,主设备号是多少。然后mknod /dev/myDevice c 主设备号 此设备号,这样来增加字符设备。 3、编译测试代码,生成可执行文件。编译命令 gcc xxx.c -o sb,执行文件 写如下命令 ./sb即可 4、 可以看到打印数据,格式如上面所示。 有些地方可能不详尽,请参考提供的两个网址去看一看。 这里是我的模块代码 hello.c http://pan.baidu.com/s/1dDyQHGX 测试代码就拷贝上面的吧。