温湿度传感器si7020-a20 linux驱动编写
来源:互联网 发布:暴风vr电视直播软件 编辑:程序博客网 时间:2024/06/11 15:45
温湿度传感器在工业当中运用的比较广泛,通常用于检测设备所处环境的温度和湿度,温度过高,设备就有可能自动关机来保证设备不被烧坏。
我所采用的温湿度传感器是si7020-a20的芯片,I2C接口,地址如图描述:
也就是说,作为从机,si7020地址是0x40。另外,cpu采用atml 9x35,内核2.6.39。知道0x40这个地址之后,就要在板级文件board-sam9x5ek中添加相应的代码。</span>
找到ek_i2c_devices数组,在数组中添加如下代码:
{I2C_BOARD_INFO("si7020", 0x40)},第一个是这个模块的名字,待会儿要和驱动中的.name 匹配。0x40是i2c地址。这个数组在ek_board_init函数中,通过i2c_register_board_info(0,ek_i2c_devices, ARRAY_SIZE(ek_i2c_devices)); 注册进内核。
新建makefile:
obj-m := si7020.oKDIR := ../linux-2.6.39all: make -C $(KDIR) M=$(PWD) modules CROSS_COMPILE=arm-linux- ARCH=armclean: rm -f *.ko *.o *.mod.o *.mod.c *.symvers *.order然后新建驱动代码源文件si7020.c:
#include <linux/module.h>#include <linux/types.h>#include <linux/fs.h>#include <linux/errno.h>#include <linux/init.h>#include <linux/cdev.h>#include <asm/uaccess.h>#include <linux/slab.h>#include <linux/spi/spi.h>#include <linux/miscdevice.h>#include <linux/device.h>#include <linux/io.h>#include <asm/uaccess.h>#include <linux/sched.h>#include <linux/i2c.h>#include <linux/delay.h>struct si7020_data{struct i2c_client *client;struct mutex lock;int buf[2];};enum {humidity,temperature,};struct si7020_data *si7020;static void get_humi_val(){unsigned int ret = 0;char tmp[2] = {0};//printk(KERN_INFO "enter get_humi_val\n");i2c_smbus_write_byte(si7020->client, 0xf5);msleep(100);i2c_master_recv(si7020->client, tmp, 2);ret =((tmp[0]<<8)|tmp[1]);if(ret < 0)dev_err(&si7020->client->dev, "Read Error\n");//printk(KERN_INFO "humi ret %d\n",ret);si7020->buf[humidity] = 125*ret/65536 - 6;if(si7020->buf[humidity] < 1)si7020->buf[humidity] = 0;if(si7020->buf[humidity] > 99)si7020->buf[humidity] = 100;//printk("si7020->buf[humidity] : %d\n", si7020->buf[humidity]);//printk(KERN_INFO "exit get_humi_val\n");}static void get_temperature(){unsigned int ret = 0;char tmp[2] = {0};//printk(KERN_INFO "enter get_temperature\n");i2c_smbus_write_byte(si7020->client, 0xf3);msleep(100);i2c_master_recv(si7020->client, tmp, 2);ret =((tmp[0]<<8)|tmp[1]);if(ret < 0)dev_err(&si7020->client->dev, "Read Error\n");//printk(KERN_INFO "temperature ret %d\n",ret);si7020->buf[temperature] = ret;//printk("si7020->buf[temperature] : %d\n", si7020->buf[temperature]);//printk(KERN_INFO "exit get_temperature\n");}static int si7020_open(struct inode *inode, struct file *filp){filp->private_data = si7020;nonseekable_open(inode, filp);//设置为不可随机读取。return 0;}static ssize_t si7020_write(struct file *filp, const char __user *buf,size_t size, loff_t *ppos){return 0;}static ssize_t si7020_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){int missing;struct si7020_data *dev = filp->private_data;//printk(KERN_INFO "enter si7020_read\n");mutex_lock(&dev->lock);get_humi_val();msleep(100);get_temperature();missing = copy_to_user(buf, &dev->buf, 8);mutex_unlock(&dev->lock);return 8;}static long si7020_ioctl(struct file *filp, unsigned int cmd, unsigned long args){return 0;}static int si7020_release(struct inode *inode, struct file *filp){return 0;}struct file_operations si7020_fops = { .owner = THIS_MODULE, .open = si7020_open, .write = si7020_write, .read = si7020_read, .unlocked_ioctl = si7020_ioctl, .release = si7020_release,};struct miscdevice si7020_misc = {.minor = MISC_DYNAMIC_MINOR,.name= "si7020",.fops = &si7020_fops,}; static int __devinit si7020_probe(struct i2c_client*client, const struct i2c_device_id *id){int ret;si7020 = kzalloc(sizeof(*si7020), GFP_KERNEL);if (!si7020) {return -ENOMEM;}si7020->client = client;mutex_init(&si7020->lock);i2c_set_clientdata(client, si7020);ret = misc_register(&si7020_misc);if (ret != 0) {printk(KERN_INFO "cannot register miscdev err = %d\n", ret);}return 0;}static int __devexit si7020_remove(struct i2c_client *client){struct si7020_data *si7020;si7020 = i2c_get_clientdata(client);kfree(si7020);misc_deregister(&si7020_misc);return 0;}/*-------------------------------------------------------------------------*/static const struct i2c_device_id si7020_id[]= {{"si7020",0},};static struct i2c_driver si7020_driver = {.driver = {.name = "si7020",.owner = THIS_MODULE,},.probe = si7020_probe,.remove = __devexit_p(si7020_remove),.id_table = si7020_id,};static int __init si7020_init(void){printk(KERN_INFO "enter si7020_init\n");return i2c_add_driver(&si7020_driver);}module_init(si7020_init);static void __exit si7020_exit(void){printk(KERN_INFO "exit si7020\n");i2c_del_driver(&si7020_driver);}module_exit(si7020_exit);MODULE_DESCRIPTION("Driver for SPI SC16IS752");MODULE_AUTHOR("tianyu");MODULE_LICENSE("GPL");MODULE_ALIAS("sct");通过i2c_add_driver向内核注册了一个i2c驱动,名字为si7020,通过这个名字匹配到板级文件,匹配成功之后,驱动就有了spi控制器的权限,然后会调用probe函数。整体的思路就是,分配一个si7020_data空间,将获取的struct i2c_client(配成功后,自动会通过probe参数传入进来)传给si7020_data中的client成员变量。
下面分段介绍代码。
struct si7020_data{struct i2c_client *client;struct mutex lock;int buf[2];};client成员用于保存从probe传入的client指针,lock成员用于读取数据时加锁,buf用来保存温度和湿度的数据,buf[0]保存湿度,buf[1]保存温度。
static int __devinit si7020_probe(struct i2c_client*client, const struct i2c_device_id *id){int ret;si7020 = kzalloc(sizeof(*si7020), GFP_KERNEL);if (!si7020) {return -ENOMEM;}si7020->client = client;mutex_init(&si7020->lock);i2c_set_clientdata(client, si7020);ret = misc_register(&si7020_misc);if (ret != 0) {printk(KERN_INFO "cannot register miscdev err = %d\n", ret);}return 0;}probe函数非常简单,使用kzalloc分配一个si7020_data空间,然后初始化si7020,初始化锁,注册一个混杂设备驱动。
接下来主要讲讲open、read、函数。因为这个设备比较简单,不需要向此设备写入数据,也不需要设置模式,并且该设备的寄存器一般用不上。为了简单起见,不需要写太多的冗余代码。
static int si7020_open(struct inode *inode, struct file *filp){filp->private_data = si7020;nonseekable_open(inode, filp);//设置为不可随机读取。return 0;}open中,就是将si7020_data传给该驱动的私有数据,然后设置为该设备不可随机读取。
static ssize_t si7020_read(struct file *filp, char __user *buf, size_t size, loff_t *ppos){int missing;struct si7020_data *dev = filp->private_data;//printk(KERN_INFO "enter si7020_read\n");mutex_lock(&dev->lock);get_humi_val();msleep(100);get_temperature();missing = copy_to_user(buf, &dev->buf, 8);mutex_unlock(&dev->lock);return 8;}read中调用了get_humi_val函数获取湿度,并放到si7020->buf[0]中。调用get_temperature获取温度,并放到si7020->buf[1]中,通过copy_to_user将buf数组拷贝到应用空间。
get_humi_val函数和get_temperature函数比较类似,分析一种就可以了,以分析get_humi_val为例:
static void get_humi_val(){unsigned int ret = 0;char tmp[2] = {0};//printk(KERN_INFO "enter get_humi_val\n");i2c_smbus_write_byte(si7020->client, 0xf5);msleep(100);i2c_master_recv(si7020->client, tmp, 2);ret =((tmp[0]<<8)|tmp[1]);if(ret < 0)dev_err(&si7020->client->dev, "Read Error\n");//printk(KERN_INFO "humi ret %d\n",ret);si7020->buf[humidity] = 125*ret/65536 - 6;if(si7020->buf[humidity] < 1)si7020->buf[humidity] = 0;if(si7020->buf[humidity] > 99)si7020->buf[humidity] = 100;//printk("si7020->buf[humidity] : %d\n", si7020->buf[humidity]);//printk(KERN_INFO "exit get_humi_val\n");}函数首先调用i2c_smbus_write_byte,向设备写一个0xf5命令,该命令是向si7020芯片发送测量湿度指令,并设置为非主机模式,也就是从机模式。然后调用i2c_master_recv函数,接收2byte的数据。下图是芯片指令说明,芯片读写协议。
si7020指令说明
读写协议
细心的人可能会发现,为啥接收使用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]);代码进行转换。
下图是温湿度计算公式。
在内核中应该尽量避免使用浮点运算,所以传上去的温度值还需要在用户空间处理才能得到正确值,儿湿度没有浮点运算,因此在内核空间就计算好了,便传到用户空间。
下面是测试代码:
#include <stdio.h>#include <string.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>enum{humidity,temperature,};int main(){/*内核空间传到应用空间是大小为 2 的int型数组。buf用来存放内核空间传上来的值。buf[0]存放湿度,湿度已经计算好了。buf[1]存放温度。温度没有计算好,因为内核尽量避免使用浮点运算。温度计算公式为:175.72*buf[temperature]/65536-46.85;*/int buf[2] = {0};int fd;int humi;float temper;fd = open("/dev/si7020", O_RDWR);while(1){read(fd, buf, sizeof(buf));humi = buf[humidity];printf("humidity is: %d\n",buf[humidity]);temper = 175.72*buf[temperature]/65536-46.85;printf("temperature is %.2f\n",temper);sleep(2);}close(fd);return 0;}测试结果如图:
在未编写驱动之前,可以先使用i2c-tools来测试i2c硬件是否已经通了。首先检查系统有几组i2c总线。
命令:i2cdetect -l
这说明只有一组i2c。
然后查看i2c-0总线挂载的i2c设备。
命令:i2cdetect -y 0
可以知道,i2c-0总线上有0x1a,0x40,和0x51这三个设备,其中UU表示已经有驱动程序为这个设备服务。0x40正是我们的温湿度传感器,现在是没有驱动为温湿度传感器服务的,待会加载了驱动之后,就会变为UU。
寄存器写入格式:如果向I2C设备中写入某字节,可输入指令i2cset -y 1 0x50 0x00 0x13
命令:i2cset -y 0 0x40 0xF5
然后再获取数据:i2cget -y 0 0x40
这个数据是只读取了一个字节哦,数据的低字节被丢弃了。但是这也说明了我们的硬件是没有问题的。
当insmod si7020.ko之后,0x40那儿就变成了UU。
- 温湿度传感器si7020-a20 linux驱动编写
- 温湿度传感器si7020-a20 linux驱动编写
- Linux的温湿度传感器DHT11驱动
- 温湿度传感器驱动模块
- 基于C8051F410的DHT11温湿度传感器驱动编写
- 基于C8051F410的am2305温湿度传感器的驱动编写
- [嵌入式Linux驱动]S5PV210的DHT11温湿度传感器Linux驱动
- STM32驱动DHT11温湿度传感器
- STM32F1驱动AM2302温湿度传感器
- SRM32fx103驱动AM2302温湿度传感器
- STM32 AM2305高精度温湿度传感器驱动
- 温湿度传感器SHT20的驱动与使用
- DHT12温湿度传感器STM32驱动IIC
- 基于Tiny4412的DHT11温湿度传感器的Linux设备驱动的简单实现
- STM32外设驱动篇——DHT11温湿度传感器
- DHT11温湿度传感器的树莓派3 python3驱动代码
- 传感器系列 温湿度传感器DHt11
- DHT21温湿度传感器使用
- 有会java串口通信的大神么?图中的通讯模式怎么获取呢?怎么知道是GPRS或者RF呢?
- linux nc/netcat命令使用详解
- spring security 3 自定义认证,授权示例
- WCF、WebAPI、WCFREST、WebService之间的区别
- 基于MFC的ActiveX控件开发
- 温湿度传感器si7020-a20 linux驱动编写
- 初识贪心:POJ2393--Yogurt factory
- Juicer 中文文档
- 简便使用命令行跑脚本
- listView刷新单个Item
- POJ2996Help Me with the Game
- Java类的初始化
- PHP函数之error_reporting
- CF703D. Mishka and Interesting sum