---------------------------------------------------------------------------------------
主机操作系统:centos 6.7
交叉编译器版本:arm-linux-gcc-4.5.4
开发板平台:fl2440
linux内核版本:Linux-3.0
Author: shaocongshuai <916962705@qq.com>
-----------------------------------------------------------------------------------------------------------------
ds18b20简介:
DS18B20是Dallas公司生产的数字温度传感器,具有体积小、适用电压宽、经济灵活的特点。它内部使用了onboard专利技术,全部传感元件及转换电路集成在一个形如三极管的集成电路内。DS18B20有电源线、地线及数据线3根引脚线,工作电压范围为3~5.5 V,支持单总线接口。
结构及原理:
详细的信息参考ds18b20的datasheet
ds18b20.c代码:
#include<linux/module.h>
#include<linux/kernel.h>#include<linux/fs.h>
#include<linux/init.h>
#include<linux/delay.h>
#include<linux/gpio.h>
#include<linux/types.h>
#include<asm/irq.h>
#include<mach/regs-gpio.h>
#include<mach/hardware.h>
#include<linux/device.h>
#include<linux/kdev_t.h>
#include<linux/cdev.h>
#include<linux/errno.h>
#include<asm/uaccess.h>
#define DQ S3C2410_GPG(0)
#define INPUT S3C2410_GPIO_INPUT
#define OUTPUT S3C2410_GPIO_OUTPUT
#ifndef DS18B20_MAJOR
#define DS18B20_MAJOR 0
#endif
#define DRV_AUTHOR "shaocongshuai"
#define DRV_DESC "S3C24XX DS18B20 driver"
#define DEV_NAME "ds18b20"
static int ds18b20_major = DS18B20_MAJOR;
static int ds18b20_minor = 0;
static struct cdev *ds18b20_cdev; //记得给指针分配内存
static struct class *ds18b20_class;
void ds18b20_reset(void) //重置ds18b20
{
int err;
s3c2410_gpio_cfgpin(DQ, OUTPUT); //配置端口的GPIO的功能
s3c2410_gpio_pullup(DQ, 1); //配置上拉电阻.0不需要上拉电阻,1需要上拉电阻。
s3c2410_gpio_setpin(DQ, 0); //写数据到端口 。输入值是0,就是低电平,to为1,表示该pin输出为1
//拉低总线保持480us以发出一个复位脉冲
udelay(480);
s3c2410_gpio_setpin(DQ, 1);//释放总线
udelay(60); //DS18B20拉低信号,60~240us表示应答
s3c2410_gpio_cfgpin(DQ, INPUT);//读入DS18B20拉低信号
while(s3c2410_gpio_getpin(DQ)); //等待DS18B20应答
while(!s3c2410_gpio_getpin(DQ)); //等待DS18B20释放总线
}
static unsigned int ds18b20_write(unsigned char data)
{
unsigned int i;
s3c2410_gpio_cfgpin(DQ, OUTPUT); //设置为输出
s3c2410_gpio_pullup(DQ, 1); //上拉
for (i = 0; i < 8; i++) //只能一位一位的读写
{
s3c2410_gpio_setpin(DQ, 0);//拉低,写时序开始
udelay(15); //写时序开始后的15us释放总线
if(data & 0x01) //线上如果是高电平则写1时序
{
s3c2410_gpio_setpin(DQ ,1);
udelay(60);
}
else //线上如果是低电平则写0时序
{
s3c2410_gpio_setpin(DQ ,0);
udelay(60); //所有写时序必须60us以上
}
s3c2410_gpio_setpin(DQ, 1); //释放总线
udelay(1);
data >>= 1; //从最低位开始判断;每比较完一次便把数据向右移,获得新的最低位状态
}
return 2;
}
static unsigned int ds18b20_read(void)
{
unsigned int i;
unsigned char data = 0x00;
for (i =0; i < 8 ; i++)
{
data >>= 1;
s3c2410_gpio_cfgpin(DQ, OUTPUT);
s3c2410_gpio_setpin(DQ, 0); //拉低总线,启动输入
udelay(1); //至少1us,然后总线释放
s3c2410_gpio_setpin(DQ, 1); //释放总线
s3c2410_gpio_cfgpin(DQ, INPUT);
if(0 != s3c2410_gpio_getpin(DQ))
data |= 0x80; //最低位数据从data的最高位放起,边放边右移直到读取位完毕。
udelay(60); //读时序必须至少60us
}
return data;
}
/*unsigned int s3c2410_gpio_getpin(unsigned int pin)
{
void __iomem *base = S3C24XX_GPIO_BASE(pin);
unsigned long offs = S3C2410_GPIO_OFFSET(pin);
return __raw_readl(base + 0x04) & (1<< offs);
}
s3c2410_gpio_getpin()的返回值是GPxDAT寄存器的值与所要读取的GPIO对应的bit mask相与以后的值,0表示该GPIO对应的bit为0, 非0表示该bit为1,所以s3c2410_gpio_getpin(S3C2410_GPG(9))如果GPG9为低电平则返回的是0,如果是高电平则返回的是GPxDAT中的GPG9对应位的值为0x0100而不是0x0001,查处问题后修改也很简单了。*/
static ssize_t read_ds18b20(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
unsigned char Data[2] = {0x00, 0x00};
ds18b20_reset();
ds18b20_write(0xcc); //忽略rom指令
ds18b20_write(0x44); //温度转换
ds18b20_reset();
ds18b20_write(0xcc);
ds18b20_write(0xbe); //读存储器
Data[0] = ds18b20_read(); //读低8位,存放在result[0]
Data[1] = ds18b20_read(); //读高8位,存放在result[1]
ds18b20_reset();
if ( copy_to_user(buf, Data, sizeof(Data)))
return -EFAULT; //return -EFAULT代表返回一个错误代码;
else
printk("copy to user is ok.\n");
}
/*#include <linux/uaccess.h>
unsigned long copy_to_user(void __user *to, const void *from, unsigned long n);
如果数据拷贝成功,则返回零;否则,返回没有拷贝成功的数据字节数。
*to是用户空间的指针,
*from是内核空间指针,
n表示从内核空间向用户空间拷贝数据的字节数*/
static int open_ds18b20(struct inode *inode, struct file *filp)
{
int flag = 0;
flag = ds18b20_reset();
if(flag)
{
printk("open ds18b20 successful!\n");
}
else printk("open ds18b20 failed!\n");
return 0;
}
static int release_ds18b20(struct inode *inode, struct file *filp)
{
return 0;
}
static struct file_operations ds18b20_fops={
.owner = THIS_MODULE,
.read = read_ds18b20,
.open = open_ds18b20,
.release = release_ds18b20,
};
static int __init ds18b20_init(void)
{
int result;
int err;
dev_t devno = 0;
if(ds18b20_major)
{
devno = MKDEV(ds18b20_major, ds18b20_minor);
result = register_chrdev_region(devno, 1, DEV_NAME);
}
else
{
result = alloc_chrdev_region(&devno, ds18b20_minor, 1, DEV_NAME);
ds18b20_major = MAJOR(devno);
}
if(result < 0)
{
printk(KERN_ERR "%s can't use major %d\n",DEV_NAME, ds18b20_major);
return -ENODEV;
}
printk("%s use major %d\n",DEV_NAME, ds18b20_major);
if(NULL == (ds18b20_cdev=cdev_alloc()) )
{
printk(KERN_ERR "S3C %s driver can't alloc for the cdev.\n", DEV_NAME);
unregister_chrdev_region(devno, 1);
return -ENOMEM;
}
ds18b20_cdev->owner = THIS_MODULE;
cdev_init(ds18b20_cdev, &ds18b20_fops);
err = cdev_add(ds18b20_cdev, devno, 1);
if(err)
{
printk(KERN_NOTICE"ERROR %d add ds18b20\n",err);
goto ERROR;
}
内核中定义了struct class结构体,一个struct class 结构体类型变量对应一个类,内核同时提供了class_create()函数,可以用它来创建一个类,这个类存放于sysfs下面,一旦创建了这个类,再调用device_create()函数在/dev目录下创建相应的设备节点。这样,加载模块的时候,用户空间中的udev会自动响应device_create()函数,去/sysfs下寻找对应的类而创建设备节点.
~ >: ls sys/class/
bdi/ i2c-dev/ net/ scsi_host/ vc/
block/ ieee80211/ ppp/ sound/ vtconsole/
display/ input/ rfkill/ spi_master/ xt_idletimer/
ds18b20/ mem/ rtc/ spidev/
firmware/ misc/ scsi_device/ tty/
graphics/ mmc_host/ scsi_disk/ ubi/
i2c-adapter/ mtd/ scsi_generic/ usb_device/
ds18b20_class = class_create(THIS_MODULE, DEV_NAME);
(1).第一个参数指定类的所有者是那个模块,一般为THIS_MODULE
(2)第二个参数是类目录名,在/sys/class下创建类目录
(3)调用class_create()函数后,要用IS_ERR()函数判断创建的类是否正确。
{
printk(KERN_ERR "S3C %s driver can't create a class.\n", DEV_NAME);
return -1;
}
device_create(ds18b20_class, NULL, MKDEV(ds18b20_major, ds18b20_minor), NULL, DEV_NAME);
printk(KERN_NOTICE"Ds18b20 is ok!\n");
return 0;
ERROR:
printk(KERN_ERR"%s driver installed failure.\n",DEV_NAME);
cdev_del(ds18b20_cdev);
unregister_chrdev_region(devno, 1);
return err;
}
static void __exit ds18b20_exit(void)
{
dev_t devno = MKDEV(ds18b20_major, 0);
cdev_del(ds18b20_cdev);
device_destroy(ds18b20_class,devno);
class_destroy(ds18b20_class);
unregister_chrdev_region(devno, 1);
printk(KERN_NOTICE"Goodbye ds18b20!\n");
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("Dual BSD/GPL");
测试代码 ds18b20_test.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char **argv)
{
int fd;
unsigned char result[2]; // 从ds18b20读出的结果,result[0]存放低八位
unsigned short temp = 0;
double temperature = 0;
if ((fd=open("/dev/ds18b20", O_RDWR|O_NONBLOCK|O_NOCTTY))<0)
{
perror("open device failed\n");
exit(1);
}
printf ("open ds18b20 success.\n");
while (1)
{
read(fd, result, sizeof(result));
temp = ((unsigned short)result[1])<<8;
temp |= (unsigned short)result[0];
temperature = ((double)temp) * 0.0625;
printf("Current Temperature:%6.2f\n", temperature);
sleep(2);
}
close(fd);
return 0;
}
注意:
二进制中的前面5位是符号位,如果测得的温度大于0, 这5位为0,只要将测到的数值乘于0.0625即可得到实际温度;如果温度小于0,这5位为1,测到的数值需要取反加1再乘于0.0625即可得到实际 温度。 例如+125℃的数字输出为07D0H,+25.0625℃的数字输出为0191H,-25.0625℃的数字输出为FE6FH,-55℃的数字输出为FC90H