i2c设备驱动实例 ds1307为例
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 測試代碼就拷貝上面的吧。
總結
以上是生活随笔為你收集整理的i2c设备驱动实例 ds1307为例的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: NOIP2020 赛前总结
- 下一篇: 第三天敏捷冲刺