嵌入式开发平台: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++) ;
- }
测试结果:
- [root@www.linuxidc.com home]#
- [root@www.linuxidc.com 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
- [root@www.linuxidc.com home]#