i2c设备驱动实例 ds1307为例

来源:互联网 发布:行会2修改数据 编辑:程序博客网 时间:2024/06/06 03:16

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
测试代码就拷贝上面的吧。
0 0
原创粉丝点击