温湿度传感器si7020-a20 linux驱动编写

    温湿度传感器在工业当中运用的比较广泛,通常用于检测设备所处环境的温度和湿度,温度过高,设备就有可能自动关机来保证设备不被烧坏。
我所采用的温湿度传感器是si7020-a20的芯片,I2C接口,地址如图描述:温湿度传感器si7020-a20 linux驱动编写_第1张图片

也就是说,作为从机,si7020地址是0x40。另外,cpu采用atml 9x35,内核2.6.39。知道0x40这个地址之后,就要在板级文件board-sam9x5ek中添加相应的代码。
找到ek_i2c_devices数组,在数组中添加如下代码:

[cpp]  view plain  copy
 
  1. {  
  2.     I2C_BOARD_INFO("si7020", 0x40)  
  3. },  
    第一个是这个模块的名字,待会儿要和驱动中的.name 匹配。0x40是i2c地址。这个数组在ek_board_init函数中,通过i2c_register_board_info(0,ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices)); 注册进内核。
    新建makefile:
[cpp]  view plain  copy
 
  1. obj-m := si7020.o  
  2. KDIR := ../linux-2.6.39  
  3. all:  
  4.         make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=arm  
  5. clean:  
  6.         rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order  

    然后新建驱动代码源文件si7020.c:
[cpp]  view plain  copy
 
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6. #include   
  7. #include   
  8. #include   
  9. #include   
  10. #include   
  11. #include   
  12. #include   
  13. #include   
  14. #include   
  15. #include   
  16. #include   
  17.   
  18. struct si7020_data{  
  19.     struct i2c_client *client;  
  20.     struct mutex lock;  
  21.     int buf[2];  
  22. };  
  23.   
  24. enum   
  25. {  
  26.     humidity,  
  27.     temperature,  
  28. };  
  29.   
  30. struct si7020_data *si7020;  
  31.   
  32. static void get_humi_val()  
  33. {  
  34.     unsigned int ret = 0;  
  35.     char tmp[2] = {0};  
  36.     //printk(KERN_INFO "enter get_humi_val\n");  
  37.   
  38.     i2c_smbus_write_byte(si7020->client, 0xf5);  
  39.     msleep(100);  
  40.     i2c_master_recv(si7020->client, tmp, 2);  
  41.     ret =((tmp[0]<<8)|tmp[1]);  
  42.     if(ret < 0)  
  43.         dev_err(&si7020->client->dev, "Read Error\n");  
  44.       
  45.     //printk(KERN_INFO "humi ret %d\n",ret);  
  46.     si7020->buf[humidity] = 125*ret/65536 - 6;  
  47.     if(si7020->buf[humidity] < 1)  
  48.         si7020->buf[humidity] = 0;  
  49.     if(si7020->buf[humidity] > 99)  
  50.         si7020->buf[humidity] = 100;  
  51.     //printk("si7020->buf[humidity] : %d\n", si7020->buf[humidity]);  
  52.     //printk(KERN_INFO "exit get_humi_val\n");  
  53. }  
  54.   
  55. static void get_temperature()  
  56. {  
  57.     unsigned int ret = 0;  
  58.     char tmp[2] = {0};  
  59.     //printk(KERN_INFO "enter get_temperature\n");  
  60.   
  61.     i2c_smbus_write_byte(si7020->client, 0xf3);  
  62.     msleep(100);  
  63.     i2c_master_recv(si7020->client, tmp, 2);  
  64.     ret =((tmp[0]<<8)|tmp[1]);  
  65.     if(ret < 0)  
  66.         dev_err(&si7020->client->dev, "Read Error\n");  
  67.       
  68.     //printk(KERN_INFO "temperature ret %d\n",ret);  
  69.     si7020->buf[temperature] = ret;  
  70.     //printk("si7020->buf[temperature] : %d\n", si7020->buf[temperature]);  
  71.     //printk(KERN_INFO "exit get_temperature\n");  
  72. }  
  73.   
  74. static int si7020_open(struct inode *inode, struct file *filp)  
  75. {  
  76.     filp->private_data = si7020;  
  77.     nonseekable_open(inode, filp);//设置为不可随机读取。  
  78.     return 0;  
  79. }  
  80.   
  81. static ssize_t si7020_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos)  
  82. {  
  83.     return 0;  
  84. }  
  85.   
  86. static ssize_t si7020_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)  
  87. {  
  88.     int missing;  
  89.     struct si7020_data *dev = filp->private_data;  
  90.   
  91.     //printk(KERN_INFO "enter si7020_read\n");  
  92.     mutex_lock(&dev->lock);  
  93.     get_humi_val();  
  94.     msleep(100);  
  95.     get_temperature();  
  96.     missing = copy_to_user(buf, &dev->buf, 8);  
  97.     mutex_unlock(&dev->lock);  
  98.     return 8;  
  99. }  
  100.   
  101. static long si7020_ioctl(struct file *filp, unsigned int cmd, unsigned long args)  
  102. {  
  103.     return 0;  
  104. }  
  105.   
  106. static int si7020_release(struct inode *inode, struct file *filp)  
  107. {  
  108.     return 0;  
  109. }  
  110.   
  111. struct file_operations si7020_fops = {  
  112.     .owner = THIS_MODULE,  
  113.     .open = si7020_open,  
  114.     .write = si7020_write,  
  115.     .read = si7020_read,  
  116.     .unlocked_ioctl = si7020_ioctl,  
  117.     .release = si7020_release,  
  118. };  
  119.   
  120. struct miscdevice si7020_misc = {  
  121.     .minor = MISC_DYNAMIC_MINOR,  
  122.     .name= "si7020",  
  123.     .fops = &si7020_fops,  
  124. };   
  125.   
  126. static int __devinit si7020_probe(struct i2c_client*client, const struct i2c_device_id *id)  
  127. {  
  128.     int ret;  
  129.       
  130.     si7020 = kzalloc(sizeof(*si7020), GFP_KERNEL);  
  131.     if (!si7020) {  
  132.         return -ENOMEM;  
  133.     }  
  134.       
  135.     si7020->client = client;  
  136.     mutex_init(&si7020->lock);  
  137.     i2c_set_clientdata(client, si7020);  
  138.     ret = misc_register(&si7020_misc);  
  139.     if (ret != 0) {  
  140.         printk(KERN_INFO "cannot register miscdev err = %d\n", ret);  
  141.     }  
  142.       
  143.     return 0;  
  144. }  
  145.   
  146.   
  147. static int __devexit si7020_remove(struct i2c_client *client)  
  148. {  
  149.     struct si7020_data *si7020;  
  150.     si7020 = i2c_get_clientdata(client);  
  151.     kfree(si7020);  
  152.     misc_deregister(&si7020_misc);  
  153.     return 0;  
  154. }  
  155.   
  156.   
  157. /*-------------------------------------------------------------------------*/  
  158.   
  159. static const struct i2c_device_id si7020_id[]= {  
  160.     {"si7020",0},  
  161. };  
  162.   
  163. static struct i2c_driver si7020_driver = {  
  164.     .driver = {  
  165.         .name = "si7020",  
  166.         .owner = THIS_MODULE,  
  167.     },  
  168.     .probe = si7020_probe,  
  169.     .remove = __devexit_p(si7020_remove),  
  170.     .id_table = si7020_id,  
  171. }  
  172. ;  
  173.   
  174. static int __init si7020_init(void)  
  175. {  
  176.     printk(KERN_INFO "enter si7020_init\n");      
  177.     return i2c_add_driver(&si7020_driver);  
  178. }  
  179.   
  180. module_init(si7020_init);  
  181.   
  182. static void __exit si7020_exit(void)  
  183. {  
  184.     printk(KERN_INFO "exit si7020\n");  
  185.     i2c_del_driver(&si7020_driver);  
  186. }  
  187. module_exit(si7020_exit);  
  188.   
  189. MODULE_DESCRIPTION("Driver for SPI SC16IS752");  
  190. MODULE_AUTHOR("tianyu");  
  191. MODULE_LICENSE("GPL");  
  192. MODULE_ALIAS("sct");  
    通过i2c_add_driver向内核注册了一个i2c驱动,名字为si7020,通过这个名字匹配到板级文件,匹配成功之后,驱动就有了spi控制器的权限,然后会调用probe函数。整体的思路就是,分配一个si7020_data空间,将获取的struct i2c_client(配成功后,自动会通过probe参数传入进来)传给si7020_data中的client成员变量。
    下面分段介绍代码。
[cpp]  view plain  copy
 
  1. struct si7020_data{  
  2.     struct i2c_client *client;  
  3.     struct mutex lock;  
  4.     int buf[2];  
  5. };  
    client成员用于保存从probe传入的client指针,lock成员用于读取数据时加锁,buf用来保存温度和湿度的数据,buf[0]保存湿度,buf[1]保存温度。

[cpp]  view plain  copy
 
  1. static int __devinit si7020_probe(struct i2c_client*client, const struct i2c_device_id *id)  
  2. {  
  3.     int ret;  
  4.       
  5.     si7020 = kzalloc(sizeof(*si7020), GFP_KERNEL);  
  6.     if (!si7020) {  
  7.         return -ENOMEM;  
  8.     }  
  9.       
  10.     si7020->client = client;  
  11.     mutex_init(&si7020->lock);  
  12.     i2c_set_clientdata(client, si7020);  
  13.     ret = misc_register(&si7020_misc);  
  14.     if (ret != 0) {  
  15.         printk(KERN_INFO "cannot register miscdev err = %d\n", ret);  
  16.     }  
  17.       
  18.     return 0;  
  19. }  
    probe函数非常简单,使用kzalloc分配一个si7020_data空间,然后初始化si7020,初始化锁,注册一个混杂设备驱动。

    接下来主要讲讲open、read、函数。因为这个设备比较简单,不需要向此设备写入数据,也不需要设置模式,并且该设备的寄存器一般用不上。为了简单起见,不需要写太多的冗余代码。

[cpp]  view plain  copy
 
  1. static int si7020_open(struct inode *inode, struct file *filp)  
  2. {  
  3.     filp->private_data = si7020;  
  4.     nonseekable_open(inode, filp);//设置为不可随机读取。  
  5.     return 0;  
  6. }  
    open中,就是将si7020_data传给该驱动的私有数据,然后设置为该设备不可随机读取。

[cpp]  view plain  copy
 
  1. static ssize_t si7020_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos)  
  2. {  
  3.     int missing;  
  4.     struct si7020_data *dev = filp->private_data;  
  5.   
  6.     //printk(KERN_INFO "enter si7020_read\n");  
  7.     mutex_lock(&dev->lock);  
  8.     get_humi_val();  
  9.     msleep(100);  
  10.     get_temperature();  
  11.     missing = copy_to_user(buf, &dev->buf, 8);  
  12.     mutex_unlock(&dev->lock);  
  13.     return 8;  
  14. }  
    read中调用了get_humi_val函数获取湿度,并放到si7020->buf[0]中。调用get_temperature获取温度,并放到si7020->buf[1]中,通过copy_to_user将buf数组拷贝到应用空间。

    get_humi_val函数和get_temperature函数比较类似,分析一种就可以了,以分析get_humi_val为例:

[cpp]  view plain  copy
 
  1. static void get_humi_val()  
  2. {  
  3.     unsigned int ret = 0;  
  4.     char tmp[2] = {0};  
  5.     //printk(KERN_INFO "enter get_humi_val\n");  
  6.   
  7.     i2c_smbus_write_byte(si7020->client, 0xf5);  
  8.     msleep(100);  
  9.     i2c_master_recv(si7020->client, tmp, 2);  
  10.     ret =((tmp[0]<<8)|tmp[1]);  
  11.     if(ret < 0)  
  12.         dev_err(&si7020->client->dev, "Read Error\n");  
  13.       
  14.     //printk(KERN_INFO "humi ret %d\n",ret);  
  15.     si7020->buf[humidity] = 125*ret/65536 - 6;  
  16.     if(si7020->buf[humidity] < 1)  
  17.         si7020->buf[humidity] = 0;  
  18.     if(si7020->buf[humidity] > 99)  
  19.         si7020->buf[humidity] = 100;  
  20.     //printk("si7020->buf[humidity] : %d\n", si7020->buf[humidity]);  
  21.     //printk(KERN_INFO "exit get_humi_val\n");  
  22. }  
    函数首先调用i2c_smbus_write_byte,向设备写一个0xf5命令,该命令是向si7020芯片发送测量湿度指令,并设置为非主机模式,也就是从机模式。然后调用i2c_master_recv函数,接收2byte的数据。下图是芯片指令说明,芯片读写协议。

温湿度传感器si7020-a20 linux驱动编写_第2张图片

                                                                        si7020指令说明

温湿度传感器si7020-a20 linux驱动编写_第3张图片

                                                      读写协议


    细心的人可能会发现,为啥接收使用i2c_master_recv来接收,而不使用i2c_smbus_read_xxx类型的函数来接收。下面表格是i2c_smbus系列读写函数说明。

函数

作用

i2c_smbus_read_byte()

从设备读取一个字节(不定义位置偏移,使用以前发起的命令的偏移)

i2c_smbus_write_byte()

从设备写入一个字节(使用以前发起的命令的偏移)

i2c_smbus_write_quick()

向设备发送一个比特 ( 取代清单 8.1 中的 Rd/Wr 位 ).

i2c_smbus_read_byte_data()

从设备指定偏移处读取一个字节

i2c_smbus_write_byte_data()

向设备指定偏移处写入一个字节

i2c_smbus_read_word_data()

从设备指定偏移处读取二个字节

i2c_smbus_write_word_data()

向设备指定偏移处写入二个字节

i2c_smbus_read_block_data()

从设备指定偏移处读取一块数据 .

i2c_smbus_write_block_data()

向设备指定偏移处写入一块数据 . (<= 32 字节 )

     传感器发送过来的i2c数据是16bit的,有人会问,可以用 i2c_smbus_read_byte读取两次,不就把数据读取出来了吗?一个i2c_smbus_read_byte结束后,会产生stop位。连续读取2byte时,第一个byte结束后,应该是一个ACK应答位,cpu虽然接收到了应答位,但是一个i2c_smbus_read_byte结束后,cpu的I2C控制器就停止接收ACK了,意味着我现在只是接收一byte数据。根据实测,两次i2c_smbus_read_byte读取出来的数据是一样的,并且是温度传感器发送的16bit数据的高8位,而低8位丢弃了。所以采用i2c_master_recv函数,来接收连续的两字节数据。

   在写入0xf5之前,要让模块等一会儿,也就是执行msleep(100),若立即读取数据则会返回-6,说明设备正忙,得不到数据。


    温湿度传感器传入的数据先是发送高字节,然后发送低字节,所以,定义了一个 char tmp[2]数组,tmp[0]存放的是数据高字节,tmp[1]存放的是数据低字节,所以,在进行取数据的时候,要用ret =((tmp[0]<<8)|tmp[1]);代码进行转换。

    下图是温湿度计算公式。


    在内核中应该尽量避免使用浮点运算,所以传上去的温度值还需要在用户空间处理才能得到正确值,儿湿度没有浮点运算,因此在内核空间就计算好了,便传到用户空间。

    下面是测试代码:

[cpp]  view plain  copy
 
  1. #include   
  2. #include   
  3. #include   
  4. #include   
  5. #include   
  6.   
  7. enum  
  8. {  
  9.     humidity,  
  10.     temperature,  
  11. };  
  12.   
  13. int main()  
  14. {  
  15.     /* 
  16.     内核空间传到应用空间是大小为 2 的int型数组。 
  17.     buf用来存放内核空间传上来的值。 
  18.     buf[0]存放湿度,湿度已经计算好了。 
  19.     buf[1]存放温度。温度没有计算好,因为内核尽量避免使用浮点运算。 
  20.     温度计算公式为:175.72*buf[temperature]/65536-46.85; 
  21.     */  
  22.     int buf[2] = {0};  
  23.     int fd;  
  24.     int humi;  
  25.     float temper;  
  26.     fd = open("/dev/si7020", O_RDWR);  
  27.     while(1)  
  28.     {  
  29.         read(fd, buf, sizeof(buf));  
  30.         humi = buf[humidity];  
  31.         printf("humidity is: %d\n",buf[humidity]);  
  32.         temper = 175.72*buf[temperature]/65536-46.85;  
  33.         printf("temperature is %.2f\n",temper);  
  34.         sleep(2);  
  35.     }  
  36.     close(fd);  
  37.     return 0;     
  38. }  
    测试结果如图:

温湿度传感器si7020-a20 linux驱动编写_第4张图片


在未编写驱动之前,可以先使用i2c-tools来测试i2c硬件是否已经通了。首先检查系统有几组i2c总线。

命令:i2cdetect -l


这说明只有一组i2c。

然后查看i2c-0总线挂载的i2c设备。

命令:i2cdetect -y 0

温湿度传感器si7020-a20 linux驱动编写_第5张图片

可以知道,i2c-0总线上有0x1a,0x40,和0x51这三个设备,其中UU表示已经有驱动程序为这个设备服务。0x40正是我们的温湿度传感器,现在是没有驱动为温湿度传感器服务的,待会加载了驱动之后,就会变为UU。

寄存器写入格式:如果向I2C设备中写入某字节,可输入指令i2cset -y 1 0x50 0x00 0x13

    -y        代表曲线用户交互过程,直接执行指令
    1         代表I2C总线编号
    0x50    代表I2C设备地址,此处选择AT24C04的低256字节内容
    0x00    代表存储器地址
    0x13    代表存储器地址中的具体内容
我们这个温湿度传感器不需要使用寄存器,直接写入指令。例如写入测量湿度的指令。

命令:i2cset -y 0 0x40 0xF5

  然后再获取数据:i2cget -y 0 0x40

温湿度传感器si7020-a20 linux驱动编写_第6张图片

  这个数据是只读取了一个字节哦,数据的低字节被丢弃了。但是这也说明了我们的硬件是没有问题的。

  当insmod si7020.ko之后,0x40那儿就变成了UU。

温湿度传感器si7020-a20 linux驱动编写_第7张图片

你可能感兴趣的:(温湿度传感器si7020-a20 linux驱动编写)