歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
Linux教程網 >> Linux基礎 >> Linux技術 >> arm+linux fl2440 ds18b20 溫度傳感器驅動編寫及測試

arm+linux fl2440 ds18b20 溫度傳感器驅動編寫及測試

日期:2017/3/3 11:25:41   编辑:Linux技術

---------------------------------------------------------------------------------------

主機操作系統: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

Copyright © Linux教程網 All Rights Reserved