基于platform 模型的DS18B20驱动实例
通过前面的学习了解到DS18B20是基于单总线协议靠一个单线端口与CPU通信实现数据传输,关于单总线设备之前接触的比较少,这次通过学习编写DS18B20的驱动,总算对这种通信协议有了多一点的了解。
单总线通信协议时序理解
初始化序列——复位和存在脉冲
如图为DS18B20的单总线通信协议的初始化序列时序,DS18B20的所有通信都由由复位脉冲组成的初始化序列开始。该初始化序列由主机发出,后跟由DS18B20发出的存在脉冲(presence pulse)。当发出应答复位脉冲的存在脉冲后,DS18B20通知主机它在总线上并且准备好操作了。
在初始化步骤中,总线上的主机通过拉低单总线至少480μs来产生复位脉冲。然后总线主机释放总线并进入接收模式。 当总线释放后,5kΩ的上拉电阻把单总线上的电平拉回高电平。当DS18B20检测到上升沿后等待15到60us,然后以拉低总线60-240us的方式发出存在脉冲。
读写时序
写时序
由以上的时序图可知,总线主机使用写“1”时间隙向DS18B20写入逻辑1,使用写“0”时间隙向DS18B20写入逻辑0.所有的写时隙必须有最少60us的持续时间,相邻两个写时隙必须要有最少1us的恢复时间。两种写时隙都通过主机拉低总线产生。为产生写1时隙,在拉低总线后主机必须在15μs内释放总线。在总线被释放后,由于5kΩ上拉电阻的作用,总线恢复为高电平。为产生写0时隙,在拉低总线后主机必须继续拉低总线以满足时隙持续时间的要求(至少60μs)。 在主机产生写时序后,DS18B20会在其后的15到60us的一个时间窗口内采样单总线。在采样的时间窗口内,如果总线为高电平,主机会向DS18B20写入1;如果总线为低电平,主机会向DS18B20写入0。
读时序
DS18B20只有在主机发出读暂存器命令 [BEh]或温度转换命令T [44h]后,主机才会产生读时隙以便DS18B20提供所需数据。在主机产生读时序后,DS18B20开始发送0或1到总线上。DS18B20让总线保持高电平的方式发送1,以拉低总线的方式表示发送0.当发送0的时候,DS18B20在读时序的末期将会释放总线,总线将会被上拉电阻拉回高电平(也是总线空闲的状态)。DS18B20输出的数据在下降沿(下降沿产生读时隙)产生后15us后有效。因此,主机释放总线和采样总线等动作要在15μs内完成。
DS18B20温度值获取原理
DS18B20的测温原理如图2所示,图中低温度系数晶振的振荡频率受温度的影响很小[1],用于产生固定频率的脉冲信号送给减法计数器1,高温度系数晶振随温度变化其震荡频率明显改变,所产生的信号作为减法计数器2的脉冲输入,图中还隐含着计数门,当计数门打开时,DS18B20就对低温度系数振荡器产生的时钟脉冲后进行计数,进而完成温度测量。计数门的开启时间由高温度系数振荡器来决定,每次测量前,首先将-55 ℃所对应的基数分别置入减法计数器1和温度寄存器中,减法计数器1和温度寄存器被预置在-55 ℃所对应的一个基数值。减法计数器1对低温度系数晶振产生的脉冲信号进行减法计数,当减法计数器1的预置值减到0时温度寄存器的值将加1,减法计数器1的预置将重新被装入,减法计数器1重新开始对低温度系数晶振产生的脉冲信号进行计数,如此循环直到减法计数器2计数到0时,停止温度寄存器值的累加,此时温度寄存器中的数值即为所测温度。
驱动程序&&测试程序
下面是自己参考ds18b20的datasheet以及前辈们的博客编写的驱动和测试程序:
- 驱动程序:s3c_plat_ds18b20.c
/*********************************************************************************
* Copyright: (C) 2017 Li Wanneng<liwjng@gmail.com>
* All rights reserved.
*
* Filename: s3c_ds18b20.c
* Description: This file is driver for s3c_ds18b20
*
* Version: 1.0.0(04/24/2017)
* Author: Li Wanneng <liwjng@gmail.com>
* ChangeLog: 1, Release initial version on "04/24/2017 07:34:17 PM"
*
********************************************************************************/
#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>
#include<linux/platform_device.h>
#define DRV_AUTHOR "Li Wanneng<liwjng@gmail.com>"
#define DRV_DESC "S3C_DS18B20 driver"
#define DISABLE 0
/* Driver version */
#define DRV_MAJOR_VER 1
#define DRV_MINOR_VER 0
#define DRV_REVER_VER 0
/* Set major static */
#define DEV_NAME "s3c_ds18b20"
/* dynamic major by default */
#ifndef DEV_MAJOR
#define DEV_MAJOR 0
#endif
#define DQ S3C2410_GPG(0)
#define INPUT S3C2410_GPIO_INPUT
#define OUTPUT S3C2410_GPIO_OUTPUT
static int debug = DISABLE;
static int dev_major = DEV_MAJOR;
static int dev_minor = 0;
struct ds18b20_device
{
struct s3c_ds18b20_platform_data *data;
struct class *dev_class;
struct cdev cdev;
} ds18b20_device;
static struct s3c_ds18b20_platform_data
{
unsigned int gpio;
unsigned int is_open_drain:1;
};
static struct s3c_ds18b20_platform_data s3c_ds18b20_data = {
.gpio = S3C2410_GPG(0),
.is_open_drain = 0,
};
/* Reset W1-ds18b20 and read presence pluse */
static unsigned int w1_ds18b20_reset(void)
{
int err;
s3c2410_gpio_cfgpin(DQ, OUTPUT);
s3c2410_gpio_pullup(DQ, 0);
s3c2410_gpio_setpin(DQ, 1);
udelay(10);
s3c2410_gpio_setpin(DQ, 0);
udelay(600);
s3c2410_gpio_setpin(DQ, 1);
udelay(60);
s3c2410_gpio_cfgpin(DQ, INPUT);
udelay(400);
err = s3c2410_gpio_getpin(DQ);
return err;
}
/* w1 write bit */
static unsigned int w1_ds18b20_write(unsigned char data)
{
unsigned int i;
s3c2410_gpio_cfgpin(DQ, OUTPUT);
for (i = 0; i < 8; i++)
{
s3c2410_gpio_setpin(DQ, 0);
udelay(5);
if (data & 0x01)
{
s3c2410_gpio_setpin(DQ, 1);
udelay(60);
}
else
udelay(60);
data >>= 1;
s3c2410_gpio_setpin(DQ, 1);
udelay(1);
}
return 2;
}
/* w1 read byte */
static unsigned int w1_ds18b20_read(void)
{
unsigned int i;
unsigned char data = 0x00;
for (i = 0; i < 8; i++)
{
s3c2410_gpio_cfgpin(DQ, OUTPUT);
s3c2410_gpio_setpin(DQ, 1);
udelay(1);
s3c2410_gpio_setpin(DQ, 0);
udelay(2);
s3c2410_gpio_setpin(DQ, 1);
s3c2410_gpio_cfgpin(DQ, INPUT);
data >>= 1;
if (0 != s3c2410_gpio_getpin(DQ))
data |= 0x80;
udelay(60);
}
return data;
}
static ssize_t ds18b20_read(struct file *filp, char __user * buf, size_t count, loff_t * f_pos)
{
unsigned char Data[2] = { 0x00, 0x00 };
unsigned long err;
/* Enable temperature conversion*/
if ((w1_ds18b20_reset()) <= 0)
{
printk(KERN_ERR "ds18b20 reset fail!\n");
return -1;
}
w1_ds18b20_write(0xcc);/* Skip ROM command. */
w1_ds18b20_write(0x44);/* Initiates temperature conversion*/
/* Read temperature value.*/
if ((w1_ds18b20_reset()) <= 0)
{
printk(KERN_ERR "ds18b20 reset fail!\n");
return -1;
}
w1_ds18b20_write(0xcc);/* Skop ROM commond. */
w1_ds18b20_write(0xbe);/* Read Scratchpad. */
Data[0] = w1_ds18b20_read();/* Read low byte */
Data[1] = w1_ds18b20_read();/* Read high byte */
w1_ds18b20_reset();
err = copy_to_user(buf, Data, sizeof(Data));
return err ? -EFAULT : count;
}
static int ds18b20_release(struct inode *inode, struct file *file)
{
return 0;
}
static int ds18b20_open(struct inode *inode, struct file *file)
{
int flag = 0;
struct ds18b20_device *pdev;
struct s3c_ds18b20_platform_data *pdata;
/* debug */
printk(KERN_INFO"ds18b20_open.\n");
pdev = container_of(inode->i_cdev,struct ds18b20_device, cdev);
pdata = pdev->data;
file->private_data = pdata;
flag = w1_ds18b20_reset();
if (flag)
{
printk(KERN_INFO "open ds18b20 successful!\n");
}
else
printk("open ds18b20 failed!\n");
return 0;
}
static struct platform_device s3c_ds18b20_device = {
.name = "s3c_ds18b20",
.id = -1,
.dev = {
.platform_data = &s3c_ds18b20_data,
},
};
static struct file_operations ds18b20_fops = {
.owner = THIS_MODULE,
.open = ds18b20_open,
.read = ds18b20_read,
.release = ds18b20_release,
};
static int s3c_ds18b20_probe(struct platform_device *dev)
{
struct s3c_ds18b20_platform_data *pdata = dev->dev.platform_data;
int result = 0;
dev_t devno;
/* Alloc the device for driver */
if (0 != dev_major)
{
devno = MKDEV(dev_major, dev_minor);
result = register_chrdev_region(devno, 1, DEV_NAME);
}
else
{
result = alloc_chrdev_region(&devno, dev_minor, 1, DEV_NAME);
dev_major = MAJOR(devno);
}
/* debug */
printk(KERN_INFO "major is %d.\n",dev_major);
/* Alloc for device major failure */
if (result < 0)
{
printk("%s driver can't get major %d\n", DEV_NAME, dev_major);
return result;
}
/* Initialize ds18b20 structure and register cdev */
memset(&ds18b20_device, 0, sizeof(ds18b20_device));
ds18b20_device.data = dev->dev.platform_data;
cdev_init(&(ds18b20_device.cdev), &ds18b20_fops);
ds18b20_device.cdev.owner = THIS_MODULE;
result = cdev_add(&(ds18b20_device.cdev), devno, 1);
/* debug */
printk(KERN_INFO "result is %d.\n",result);
if (result)
{
printk(KERN_NOTICE "error %d add %s device", result, DEV_NAME);
goto ERROR;
}
ds18b20_device.dev_class = class_create(THIS_MODULE, DEV_NAME);
if (IS_ERR(ds18b20_device.dev_class))
{
printk("%s driver create class failture\n", DEV_NAME);
result = -ENOMEM;
goto ERROR;
}
device_create(ds18b20_device.dev_class, NULL, devno, NULL, DEV_NAME);
printk("S3C %s driver version %d.%d.%d initiliazed.\n", DEV_NAME, DRV_MAJOR_VER, DRV_MINOR_VER,
DRV_REVER_VER);
return 0;
ERROR:
printk("S3C %s driver version %d.%d.%d install failure.\n", DEV_NAME, DRV_MAJOR_VER,
DRV_MINOR_VER, DRV_REVER_VER);
cdev_del(&(ds18b20_device.cdev));
unregister_chrdev_region(devno, 1);
return result;
}
static int s3c_ds18b20_remove(struct platform_device *dev)
{
dev_t devno = MKDEV(dev_major, dev_minor);
cdev_del(&(ds18b20_device.cdev));
device_destroy(ds18b20_device.dev_class, devno);
class_destroy(ds18b20_device.dev_class);
unregister_chrdev_region(devno, 1);
printk("%s driver removed\n", DEV_NAME);
return 0;
}
static struct platform_driver s3c_ds18b20_driver = {
.probe = s3c_ds18b20_probe,
.remove = s3c_ds18b20_remove,
.driver = {
.name = "s3c_ds18b20",
.owner = THIS_MODULE,
},
};
static int __init s3c_ds18b20_init(void)
{
int ret = 0;
ret = platform_device_register(&s3c_ds18b20_device);
if (ret)
{
printk(KERN_ERR "%s:%d: Can't register platform device %d\n", __FUNCTION__, __LINE__, ret);
goto fail_reg_plat_dev;
}
ret = platform_driver_register(&s3c_ds18b20_driver);
if (ret)
{
printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__, __LINE__, ret);
goto fail_reg_plat_drv;
}
return 0;
fail_reg_plat_dev:
return ret;
fail_reg_plat_drv:
platform_driver_unregister(&s3c_ds18b20_driver);
return ret;
}
static void s3c_ds18b20_exit(void)
{
platform_driver_unregister(&s3c_ds18b20_driver);
platform_device_unregister(&s3c_ds18b20_device);
}
module_init(s3c_ds18b20_init);
module_exit(s3c_ds18b20_exit);
module_param(debug, int, S_IRUGO);
module_param(dev_major, int, S_IRUGO);
module_param(dev_minor, int, S_IRUGO);
MODULE_AUTHOR(DRV_AUTHOR);
MODULE_DESCRIPTION(DRV_DESC);
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:S3C_DS18B20");
- Makefile
[lwn@localhost ds18b20]$ vim Makefile
LINUX_SRC?=~/myfl2440/kernel/linux-lwn-3.0.1
CROSS_COMPILE=/opt/dl/buildroot-2012.08/ARM920t/usr/bin/arm-linux-
obj-m := s3c_plat_ds18b20.o
modules:
@make -C $(LINUX_SRC) M=`pwd` modules
@make clean
clean:
rm -f *.ko.* *.o *mod.c *.order *.symvers
- 测试程序:test_ds18b20.c
/*********************************************************************************
* Copyright: (C) 2017 Li Wanneng<liwjng@gmail.com>
* All rights reserved.
*
* Filename: test_ds18b20.c
* Description: This file used for test ds18b20 driver
*
* Version: 1.0.0(04/26/2017)
* Author: Li Wanneng <liwjng@gmail.com>
* ChangeLog: 1, Release initial version on "04/26/2017 03:19:52 PM"
*
********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <errno.h>
/********************************************************************************
* Description:
* Input Args:
* Output Args:
* Return Value:
********************************************************************************/
int main(int argc, char **argv)
{
int fd;
int data = 0;
unsigned char result[2];
float temperature = 0;
fd = open("/dev/s3c_ds18b20", O_RDWR | O_NONBLOCK);
if (fd < 0)
{
printf("user:open s3c_ds18b20 failed.\n");
printf("user_Message : %s\n", strerror(errno));
return -1;
}
while (1)
{
int ret = 0;
usleep(100);
ret = read(fd, result, sizeof(result));
if (ret != 2)
{
printf("read wrong\n");
exit(0);
}
data = (int)result[1];
data <<= 8;
data = data | result[0];
temperature = data * 0.0625;
printf("Temperature = %.2f\n", temperature);
fflush(stdout);
sleep(1);
}
close(fd);
return 0;
} /* ----- End of main() ----- */
编译&&测试
完成上述的驱动和测试程序的编写,对于驱动程序基于上述makefile使用make命令编译生成ko文件,对于测试程序使用gcc交叉编译生成可执行二进制文件test,然后使用tftp将其下载到开发板测试。
- 编译生成驱动文件和测试文件
- 开发板测试温度
遇到的问题及解决:
1.Q:在测试驱动程序的时候,一直打不开设备节点,读取文件失败。提示错误信息:No such device or address。
A:通过调试发现,在ds18b20_open(struct inode *inode, struct file *file)函数中没有对参数file赋值。于是赶紧添加了下面三行代码,重新编译之后驱动程序可以运行了。
pdev = container_of(inode->i_cdev,struct ds18b20_device, cdev);
pdata = pdev->data;
file->private_data = pdata;
2.Q:执行测试程序的时候出现以下错误信息:
WARNING: at drivers/gpio/gpiolib.c:101 gpio_ensure_requested+0x54/0xd4()
autorequest GPIO-192
Modules linked in: s3c_plat_ds18b20
[<c002d508>] (unwind_backtrace+0x0/0xf0) from [<c003a9ac>] (warn_slowpath_common+0x48/0x60)
[<c003a9ac>] (warn_slowpath_common+0x48/0x60) from [<c003aa58>] (warn_slowpath_fmt+0x30/0x40)
[<c003aa58>] (warn_slowpath_fmt+0x30/0x40) from [<c01fad00>] (gpio_ensure_requested+0x54/0xd4)
A:经查阅资料以及警告信息(autorequest GPIO-192)得知是因为gpio冲突,联想到前两天添加了内核自带的ds18b20驱动,可能是两个驱动都同时使用了同一个GPIO管脚,于是我在make manuconfig中取消内核对于ds18b20驱动的选项,重新编译内核。最终问题得到了解决。
参考文章:
http://www.cnblogs.com/wangyuezhuiyi/archive/2012/10/12/2721839.html
http://blog.csdn.net/u010944778/article/details/48058433
在此感谢两位网友的文章给予的帮助和启发