DS18B20 驅動編寫
對驅動工程師而言,時序分析的意思是讓晶片之間的訪問滿足晶片手冊中時序圖訊號的有效的先後順序、取樣建立時間、保持時間的要求,通過時序來達到資料傳輸和硬體控制的目的。
嵌入式開發平臺:mini2440
DS18B20 所用GPIO:S3C2410_GPF(3)
一、DS18B20 時序分析
DS18B20的一線工作協議流程是:初始化→ROM操作指令→儲存器操作指令→資料傳輸,其工作時序包括:初始化時序、寫時序、讀時序。
1、初始化時序
主機首先發出一個480-960微秒的低電平脈衝,然後釋放匯流排變為高電平,並在隨後的480微秒時間內對匯流排進行檢測,如果有低電平出現說明總線上有器件已做出應答,若無低電平出現一直都是高電平說明總線上無器件應答。
作為從器件的DS18B20在一上電後就一直在檢測總線上是否有480-960微秒的低電平出現,如果有,在匯流排轉為高電平後等待15-60微秒後將匯流排電平拉低60-240微秒做出響應存在脈衝,告訴主機本器件已做好準備,若沒有檢測到就一直在檢測等待。
static int ds18b20_init(void)
{
int retval = 0;
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0); // 拉低ds18b20匯流排,復位ds18b20
udelay(500); // 保持復位電平500us
s3c2410_gpio_setpin(DQ, 1); // 釋放ds18b20匯流排
udelay(60);
// 若復位成功,ds18b20發出存在脈衝(低電平,持續60~240us)
s3c2410_gpio_cfgpin(DQ, CFG_IN);
retval = s3c2410_gpio_getpin(DQ);
udelay(500);
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1); // 釋放匯流排
return retval;
}
2、寫時序
寫週期最少為60微秒,最長不超過120微秒,寫週期一開始作為主機先把匯流排拉低1微秒錶示寫週期開始,隨後若主機想寫0,則繼續拉低電平最少60微秒直至寫週期結束,然後釋放匯流排為高電平;若主機想寫1,在一開始拉低匯流排電平1微秒後就釋放匯流排為高電平,一直到寫週期結束。
而作為從機的DS18B20則在檢測到匯流排被拉低後等待15微秒然後從15μs到45μs開始對匯流排取樣,在取樣期內匯流排為高電平則為1,若取樣期內匯流排為低電平則為0。
static void write_byte(unsigned char data)
{
int i = 0;
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 1);
for (i = 0; i < 8; i++)
{
// 匯流排從高拉至低電平時,就產生寫時隙
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0);
s3c2410_gpio_setpin(DQ, data & 0x01);
udelay(60);
data >>= 1;
}
s3c2410_gpio_setpin(DQ, 1); // 重新釋放ds18b20匯流排
}
3、讀時序
對於讀資料操作時序也分為讀0時序和讀1時序兩個過程,讀時序是從主機把單匯流排拉低之後,在1微秒之後就得釋放單匯流排為高電平,以讓DS18B20把資料傳輸到單總線上。DS18B20在檢測到匯流排被拉低1微秒後,便開始送出資料,若是要送出0就把匯流排拉為低電平直到讀週期結束;若要送出1則釋放匯流排為高電平。
主機在一開始拉低匯流排1微秒後釋放匯流排,然後在包括前面的拉低匯流排電平1微秒在內的15微秒時間內完成對匯流排進行取樣檢測,取樣期內匯流排為低電平則確認為0,取樣期內匯流排為高電平則確認為1,完成一個讀時序過程,至少需要60μs才能完成。
static unsigned char read_byte(void)
{
int i;
unsigned char data = 0;
// 匯流排從高拉至低,只需維持低電平17ts,再把匯流排拉高,就產生讀時隙
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
for (i = 0; i < 8; i++)
{
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0);
udelay(2);
s3c2410_gpio_setpin(DQ, 1);
udelay(8);
data >>= 1;
s3c2410_gpio_cfgpin(DQ, CFG_IN);
if (s3c2410_gpio_getpin(DQ))
data |= 0x80;
udelay(50);
}
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1); // 釋放ds18b20匯流排
return data;
}
二、操作方法
DS18B20單線通訊功能是分時完成的,有嚴格的時序概念,如果出現序列混亂,1-WIRE器件將不影響主機,因此讀寫時序很重要。系統對DS18B20的各種操作必須按協議進行,根據DS18B20的協議規定,微控制器控制DS18B20完成溫度的轉換必須經過以下4個步驟:
1)每次讀寫前對DS18B20進行復位初始化。復位要求主CPU將資料線下拉500μs,然後釋放,DS18B20收到訊號後等待16μs-60μs左右,然後發出60μs-240μs的存在低脈衝,主CPU收到此訊號後表示復位成功。
2)傳送一條ROM指令
3)傳送儲存器指令
1、讓DS18B20進行一次溫度轉換的具體操作如下:
a -- 主機先做個復位操作;
b -- 主機再寫跳過ROM的操作(CCH)命令;
c -- 然後主機接著寫轉換溫度的操作指令,後面釋放匯流排至少1秒,讓DS18B20完成轉換操作。需要注意的是每個命令位元組在寫的時候都是低位元組先寫,例如CCH的二進位制為11001100,在寫到總線上時要從低位開始寫,寫的順序是“0、0、1、1、0、0、1、1”,整個操作的匯流排狀態如圖所。
2、讀取RAM的溫度資料,同樣,這個操作也要按照三個步驟:
a -- 主機發出復位操作並接受DS18B20的應答(存在)脈衝;
b -- 主機發出跳過對ROM操作的命令(CCH);
c -- 主機發出讀取RAM的命令(BEH),隨後主機依次讀取DS18B20發出的從第0-第8,共九個位元組的資料。如果只想讀取溫度資料,那在讀完第0和第1個數據後就不再理會後面DS18B20發出的資料即可,同樣讀取資料也是低位在前,整個操作的匯流排狀態如圖所示。
三、具體驅動編寫
1、ds18b20_drv.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/types.h>
#include <linux/fs.h>
#include <mach/regs-gpio.h>
#include <mach/hardware.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/errno.h>
#include <linux/gpio.h>
#include <linux/device.h>
/* 相關引腳定義,方便以後移植 */
#define DQ S3C2410_GPF(3)
#define CFG_IN S3C2410_GPIO_INPUT
#define CFG_OUT S3C2410_GPIO_OUTPUT
// ds18b20主次裝置號(動態分配)
static int ds18b20_major = 0;
static int ds18b20_minor = 0;
static int ds18b20_nr_devs = 1;
// 定義裝置型別
static struct ds18b20_device
{
struct cdev cdev;
};
struct ds18b20_device *ds18b20_devp; /*裝置結構體指標 */
static struct class *ds18b20_class;
static struct class_device *ds18b20_class_dev;
/* 函式宣告 */
static int ds18b20_open(struct inode *inode, struct file *filp);
static int ds18b20_init(void);
static void write_byte(unsigned char data);
static unsigned char read_byte(void);
static ssize_t ds18b20_read(struct file *filp, char __user * buf, size_t count, loff_t * f_pos);
void ds18b20_setup_cdev(struct ds18b20_device *dev, int index);
static int ds18b20_open(struct inode *inode, struct file *filp)
{
int flag = 0;
flag = ds18b20_init();
if (flag & 0x01)
{
printk(KERN_WARNING "open ds18b20 failed\n");
return -1;
}
printk(KERN_NOTICE "open ds18b20 successful\n");
return 0;
}
static int ds18b20_init(void)
{
int retval = 0;
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0); // 拉低ds18b20匯流排,復位ds18b20
udelay(500); // 保持復位電平500us
s3c2410_gpio_setpin(DQ, 1); // 釋放ds18b20匯流排
udelay(60);
// 若復位成功,ds18b20發出存在脈衝(低電平,持續60~240us)
s3c2410_gpio_cfgpin(DQ, CFG_IN);
retval = s3c2410_gpio_getpin(DQ);
udelay(500);
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1); // 釋放匯流排
return retval;
}
static void write_byte(unsigned char data)
{
int i = 0;
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 1);
for (i = 0; i < 8; i++)
{
// 匯流排從高拉至低電平時,就產生寫時隙
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0);
s3c2410_gpio_setpin(DQ, data & 0x01);
udelay(60);
data >>= 1;
}
s3c2410_gpio_setpin(DQ, 1); // 重新釋放ds18b20匯流排
}
static unsigned char read_byte(void)
{
int i;
unsigned char data = 0;
for (i = 0; i < 8; i++)
{
// 匯流排從高拉至低,只需維持低電平17ts,再把匯流排拉高,就產生讀時隙
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1);
udelay(2);
s3c2410_gpio_setpin(DQ, 0);
udelay(2);
s3c2410_gpio_setpin(DQ, 1);
udelay(8);
data >>= 1;
s3c2410_gpio_cfgpin(DQ, CFG_IN);
if (s3c2410_gpio_getpin(DQ))
data |= 0x80;
udelay(50);
}
s3c2410_gpio_cfgpin(DQ, CFG_OUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1); // 釋放ds18b20匯流排
return data;
}
static ssize_t ds18b20_read(struct file *filp, char __user * buf, size_t count, loff_t * f_pos)
{
int flag;
unsigned long err;
unsigned char result[2] = { 0x00, 0x00 };
//struct ds18b20_device *dev = filp->private_data;
flag = ds18b20_init();
if (flag & 0x01)
{
printk(KERN_WARNING "ds18b20 init failed\n");
return -1;
}
write_byte(0xcc);
write_byte(0x44);
flag = ds18b20_init();
if (flag & 0x01)
return -1;
write_byte(0xcc);
write_byte(0xbe);
result[0] = read_byte(); // 溫度低八位
result[1] = read_byte(); // 溫度高八位
err = copy_to_user(buf, &result, sizeof(result));
return err ? -EFAULT : min(sizeof(result), count);
}
static struct file_operations ds18b20_dev_fops = {
.owner = THIS_MODULE,
.open = ds18b20_open,
.read = ds18b20_read,
};
void ds18b20_setup_cdev(struct ds18b20_device *dev, int index)
{
int err, devno = MKDEV(ds18b20_major, ds18b20_minor + index);
cdev_init(&dev->cdev, &ds18b20_dev_fops);
dev->cdev.owner = THIS_MODULE;
err = cdev_add(&(dev->cdev), devno, 1);
if (err)
{
printk(KERN_NOTICE "ERROR %d add ds18b20\n", err);
}
}
static int __init ds18b20_dev_init(void)
{
int result;
dev_t dev = 0;
dev = MKDEV(ds18b20_major, ds18b20_minor);
if (ds18b20_major)
{
result = register_chrdev_region(dev, ds18b20_nr_devs, "ds18b20");
}
else
{
result = alloc_chrdev_region(&dev, ds18b20_minor, ds18b20_nr_devs, "ds18b20");
ds18b20_major = MAJOR(dev);
}
if (result < 0)
{
printk(KERN_WARNING "ds18b20: failed to get major\n");
return result;
}
/* 為新裝置分配記憶體和初始化 */
ds18b20_devp = kmalloc(sizeof(struct ds18b20_device), GFP_KERNEL);
if (!ds18b20_devp)
{ /*申請失敗 */
result = -ENOMEM;
goto fail_malloc;
}
memset(ds18b20_devp, 0, sizeof(struct ds18b20_device));
ds18b20_setup_cdev(ds18b20_devp, 0);
/* 自動建立裝置節點 */
ds18b20_class = class_create(THIS_MODULE, "ds18b20_sys_class");
if (IS_ERR(ds18b20_class))
return PTR_ERR(ds18b20_class);
ds18b20_class_dev =
device_create(ds18b20_class, NULL, MKDEV(ds18b20_major, 0), NULL, "ds18b20");
if (unlikely(IS_ERR(ds18b20_class_dev)))
return PTR_ERR(ds18b20_class_dev);
return 0;
fail_malloc:
unregister_chrdev_region(dev, 1);
return result;
}
static void __exit ds18b20_dev_exit(void)
{
cdev_del(&ds18b20_devp->cdev); /*登出cdev */
kfree(ds18b20_devp); /*釋放裝置結構體記憶體 */
unregister_chrdev_region(MKDEV(ds18b20_major, 0), ds18b20_nr_devs); /*釋放裝置號 */
device_unregister(ds18b20_class_dev);
class_destroy(ds18b20_class);
}
module_init(ds18b20_dev_init);
module_exit(ds18b20_dev_exit);
MODULE_LICENSE("Dual BSD/GPL");
2、app-ds18b20.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/ioctl.h>
// 函式宣告
void ds18b20_delay(int i);
int main()
{
int fd, i;
unsigned char result[2]; // 從ds18b20讀出的結果,result[0]存放低八位
unsigned char integer_value = 0;
float decimal_value = 0; // 溫度數值,decimal_value為小數部分的值
float temperature = 0;
fd = open("/dev/ds18b20", 0);
if (fd < 0)
{
perror("open device failed\n");
exit(1);
}
while (1)
{
i++;
read(fd, &result, sizeof(result));
integer_value = ((result[0] & 0xf0) >> 4) | ((result[1] & 0x07) << 4);
// 精確到0.25度
decimal_value = 0.5 * ((result[0] & 0x0f) >> 3) + 0.25 * ((result[0] & 0x07) >> 2);
temperature = (float)integer_value + decimal_value;
printf("Current Temperature:%6.2f\n", temperature);
ds18b20_delay(500);
}
}
void ds18b20_delay(int i)
{
int j, k;
for (j = 0; j < i; j++)
for (k = 0; k < 50000; k++) ;
}
測試結果:
[[email protected] home]#
[[email protected] home]#./app-ds18b20
open ds18b20 successful
Current Temperature: 23.50
Current Temperature: 23.50
Current Temperature: 23.25
Current Temperature: 23.50
Current Temperature: 23.50
Current Temperature: 23.50
^C
[[email protected] home]#