---------------------------------------------------------------------------------------
主機操作系統:centos 6.7
交叉編譯器版本:arm-linux-gcc-4.5.4
開發板平台:fl2440
linux內核版本:Linux-3.0
Author: shaocongshuai <[email protected]>
-----------------------------------------------------------------------------------------------------------------
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()函數判斷創建的類是否正確。
if ( IS_ERR(ds18b20_class) )
{
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