分享

DS18B20 driver for Linux

 笔录收藏 2012-12-03

花了近50小时才完成了DS18B20测温模块的驱动,主要是因为对时序的掌握不是很熟练,耽误了很多时间。

后来实用示波器仔细校准了每一段时序才得到满意的结果,小小的庆祝一下。

下面给大家分享一下心得

  • 实验平台

    Mini2440 with linux

  • DS18B20数据手册

    115

  • 引脚连线

    将DS18B20的DQ脚(2号脚)与GPF0相连(也可以选择其他GPIO口)

    GND(1号脚)接开发板的GND(3号脚)

    VDD(3号脚)接开发板的5V(1号脚)

  • 供电(Powering)

    DS18B20支持两种供电模式,一种是寄生供电(parasite power),另一种是外部供电(external power)。寄生供电是将18b20的1,3脚接地 直接“窃取”DQ上的电压,这种方式操作起来似乎不太稳定。所以我使用了外部电源供电(开发板上正好有5V的直流电源比较方便)

  • 1-WIRE BUS SYSTEM(单总线系统)

    这个比较有意思,一般的总线系统都会将时钟线和数据线分开,而单总线系统的DQ线甚至还能供电 - -

    所以对DS18B20的所有操作都是通过这一条总线,相对而言还是非常简单的。

    下面分析各种时序 - -

  • 初始化(INITIALIZATION)

    对于DS18B20初始化是所有命令的初始操作,它由两个部分组成:主机发送的重启脉冲和从机的应答脉冲 时序图:

    操作过程: 注意,对DQ脚的操作需要打开上拉电阻(pull up)
    1.由主机拉低DQ脚持续至少480us,然后释放总线(拉高引脚)
    2.18b20检测到上升沿后会向主机发送一个应答脉冲,拉低总线60-240us,也就是说驱动程序需要在释放总线后60-240us内进行采样。
    3.复位DQ

    初始化代码:

    /* 初始化DQ18B20,返回0表示初始化成功1表示失败 */
    unsigned int init_ds18b20(void)
    {
    	unsigned int dat = 0;

    DQ_OUT;
    DQ_H; //复位DQ
    udelay(10);

    DQ_L; //拉低DQ,发出reset脉冲
    udelay(600);

    DQ_H;
    udelay(65);

    DQ_IN; //设置DQ为输入,准备读取presence脉冲
    dat = DQ_STA; //读取DQ状态,dat=0表示初始化成功,dat=1 表示失败
    udelay(50);

    DQ_OUT;
    DQ_H; //复位DQ
    udelay(50);

    return dat;
    }  

  • 时隙(WRITE TIME SLOTS) 写1或0,一般向18b20中写入一个命令,需要8个写数据,即写入一个字节。写时隙至少持续60us,同时需要1us的恢复间隔 时序图:

    操作过程: 1.不论写1还是写0,写时隙都由主机拉低总线开始。
    2.写0,主机拉低总线后,需保持低电平60-120us,然后释放DQ,恢复高电平
    3.写1,主机拉低总线后,需在15us内释放总线,18b20会在15-60us内对总线进行取样

    写字节代码:

    /* Write a char */
    void write_char(unsigned char dat)
    {
    	unsigned int i = 0;
    	DQ_OUT;
    	for(i=8;i>0;i--){
    		DQ_L;		//拉低DQ,开始写时序
    		udelay(10);

    if(dat&0x01)
    DQ_H; //拉高DQ,写1
    else
    DQ_L; //拉低DQ,写0
    udelay(60);
    DQ_H; //复位DQ
    udelay(10);
    dat >>=1;
    }
    }  

  • 时隙(READ TIME SLOTS) 一般每次从DS18B20中读出8位数据,读时隙至少持续60us,同时需要1us的恢复间隔 时序图:

    操作过程: 1.读时隙由主机拉低总线至少1us后释放(拉高总线),完成初始化
    2.然后18b20会开始向总线传输数据,保持总线高电平为1,拉低总线为0
    3.主机取样(过程需要精确延时,下面细讲)
    4.复位DQ

    详细取样过程: 上图表示,由ds18b20传回的数据在15us内有效,主机最好在结束前的几微秒内取样,这样才能获得精确的结果。

    读字节代码:

    /* Read a char  */
    unsigned char read_char(void)
    {
    	unsigned int i= 0;
    	unsigned char dat = 0;
    	for(i=8;i>0;i--){
    		DQ_OUT;
    		DQ_L;
    		udelay(2);

    dat >>=1;
    DQ_H;
    udelay(2);

    DQ_IN;
    udelay(10);
    if(DQ_STA)
    dat |=0x80;
    udelay(60);
    }
    return dat;
    }

  • 驱动中用到的命令 SKIP ROM [CCh] 跳过读取ROM过程,由于总线上只有一个18b20,所以不需要读取rom进行匹配 CONVERT T [44h] 温度转换命令,18b20接受到44h命令后立刻开始温度转换,在过程中,如果主机发起读时隙,18b20返回0表示正在转换,返回1表示转换完成 注意:18b20转换速度不是很快 我在驱动中加了700ms的延时才得到正确的结果。 READ SCRATCHPAD [BEh] 读取寄存器指令,18b20会顺次将各寄存器的数据在读时隙中传回给主机。
    以下是18b20 memory map: 如上图所示,如果仅需读取温度,我们只用在BE指令后读出两个字节,就可以得到所需的温度。

  • 关于温度的格式: 十六位的温度在寄存器中是这样保存的,所以读取之后我们需要对数值进行转换,驱动只负责将数据传回用户空间。

  • 驱动源码
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 

    #include #include #include #include #include #include

    /* 引脚相关定义 */
    #define DQ S3C2410_GPF(0)
    #define DQ_OUTP S3C2410_GPIO_OUTPUT
    #define DQ_INP S3C2410_GPIO_INPUT

    /* 设置DQ引脚为输出 */
    #define DQ_OUT s3c2410_gpio_cfgpin(DQ,DQ_OUTP)
    /* 设置DQ引脚为输入 */
    #define DQ_IN s3c2410_gpio_cfgpin(DQ,DQ_INP)
    /* 打开上拉电阻 */
    #define DQ_UP s3c2410_gpio_pullup(DQ,1)
    /* 读取DQ状态 */
    #define DQ_STA s3c2410_gpio_getpin(DQ)
    /* 拉低DQ */
    #define DQ_L s3c2410_gpio_setpin(DQ,0)
    /* 拉高DQ */
    #define DQ_H s3c2410_gpio_setpin(DQ,1)

    /* 设备名称 */
    #define DEV_NEME “ds18b20”

    struct ds18b20_dev{
    dev_t devno; //设备号
    int major; //主设备号
    char *data; //缓冲区
    struct cdev cdev; //内嵌字符设备结构
    };

    struct ds18b20_dev *ds18b20_device;

    /* 初始化DQ18B20,返回0表示初始化成功1表示失败 */
    unsigned int init_ds18b20(void)
    { unsigned int dat = 0;

    DQ_OUT;
    DQ_H; //复位DQ
    udelay(10);

    DQ_L; //拉低DQ,发出reset脉冲
    udelay(600);

    DQ_H;
    udelay(65);

    DQ_IN; //设置DQ为输入,准备读取presence脉冲
    dat = DQ_STA; //读取DQ状态,dat=0表示初始化成功,dat=1 表示失败
    udelay(50);

    DQ_OUT;
    DQ_H; //复位DQ
    udelay(50);

    return dat;
    }

    /* Write a char */
    void write_char(unsigned char dat)
    { unsigned int i = 0;
    DQ_OUT;
    for(i=8;i>0;i–){
    DQ_L; //拉低DQ,开始写时序
    udelay(10);

    if(dat&0x01)
    DQ_H; //拉高DQ,写1
    else
    DQ_L; //拉低DQ,写0
    udelay(60);
    DQ_H; //复位DQ
    udelay(10);
    dat >>=1;
    }
    }

    /* Read a char */
    unsigned char read_char(void)
    { unsigned int i= 0;
    unsigned char dat = 0;
    for(i=8;i>0;i–){
    DQ_OUT;
    DQ_L;
    udelay(2);

    dat >>=1;
    DQ_H;
    udelay(2);

    DQ_IN;
    udelay(10);
    if(DQ_STA)
    dat |=0x80;
    udelay(60);
    }
    return dat;
    }

    /* Read temperature */
    int read_temperature(void)
    { unsigned int msb =0;
    unsigned int lsb =0;

    if(init_ds18b20()){
    printk(KERN_WARNING “DS18B20:init_ds18b20 failed1”);
    return -1;
    }
    write_char(0xCC); //跳过读取ROM
    write_char(0x44); //开始温度转换

    /* 此处必须有一个较长的延时,等待温度转换完成
    * 不然可能读出默认值1360 */
    msleep_interruptible(900); //此处延时较长,应该改用非忙等待。 感谢aimybee提醒~
    if(init_ds18b20()){
    printk(KERN_WARNING “DS18B20:init_ds18b20 failed2”);
    return -1;
    }
    write_char(0xCC); //跳过读取ROM
    write_char(0xBE); //读取寄存器
    lsb = read_char(); //前两个寄存器的值分别为温度的低位和高位
    msb = read_char();

    msb <<=8;
    return msb+lsb;
    }

    int ds18b20_open(struct inode *inode,struct file *filp)
    { DQ_UP; //打开上拉电阻
    DQ_OUT;
    return 0;
    }

    ssize_t ds18b20_read(struct file *filp,char __user *buf ,size_t count , loff_t *f_pos)
    { int temperature;

    temperature = read_temperature();
    sprintf(ds18b20_device->data,”%dn”,temperature);

    if(temperature < 0){
    printk(KERN_WARNING “DS18B20:Read temperature failed!”);
    return -EFAULT;
    }
    if(copy_to_user(buf,ds18b20_device->data,sizeof(ds18b20_device->data))){
    return -EFAULT;
    }
    return sizeof(ds18b20_device->data);
    }

    struct file_operations ds18b20_fops = {
    .owner = THIS_MODULE,
    .read = ds18b20_read,
    .open = ds18b20_open,
    };

    int __init init_module(void)
    { int result,err;
    /* 初始化ds18b20_device 结构体 */
    ds18b20_device = kmalloc(sizeof(struct ds18b20_dev),GFP_KERNEL);
    if(!ds18b20_device){
    result = -ENOMEM;
    goto fail_malloc_device;
    }
    memset(ds18b20_device,0,sizeof(struct ds18b20_dev));

    cdev_init(&ds18b20_device->cdev,&ds18b20_fops);
    ds18b20_device->cdev.owner = THIS_MODULE;
    /* 分配设备号 */
    result = alloc_chrdev_region(&ds18b20_device->devno,0,1,DEV_NEME);
    ds18b20_device->major = MAJOR(ds18b20_device->devno);
    if(result <0 ){
    printk(KERN_WARNING “DS18b20:cat’t get major %d”,ds18b20_device->major);
    return result;
    }
    printk(KERN_INFO “DS18b20:major = %d “,ds18b20_device->major);
    /* 分配缓冲区 */
    ds18b20_device->data = kmalloc(20,GFP_KERNEL);
    if(!ds18b20_device->data){
    result = -ENOMEM;
    goto fail_malloc_data;
    }
    memset(ds18b20_device->data,0,20);

    err = cdev_add(&ds18b20_device->cdev,ds18b20_device->devno,1);
    if(err)
    printk(KERN_WARNING “DS18b20:Error %d adding ds18b20”,err);

    printk(KERN_INFO “DS18b20:init_module”);
    return result;

    fail_malloc_data:
    kfree(ds18b20_device->data);
    fail_malloc_device:
    unregister_chrdev_region(ds18b20_device->devno,1);
    kfree(ds18b20_device);
    return result;
    }

    void __exit cleanup_module(void)
    { unregister_chrdev_region(ds18b20_device->devno,1);
    kfree(ds18b20_device->data);
    kfree(ds18b20_device);
    printk(KERN_INFO “DS18b20:cleanup_module”);
    }

    module_init(init_module);
    module_exit(cleanup_module);

    MODULE_AUTHOR(“Issac”);
    MODULE_LICENSE(“GPL”);

  • 部署驱动 将驱动源码拷贝到 kernel_source/driver/char 中
    然后在Kconfig中添加如下命令
    config DS18B20
    	tristate "Driver for DS18B20"
    	help
    	  Say Y here to include support for the DS18B20 Programmable Resolution
    	  1-Wire Digital Thermometer
    	  It is also possible to say M here to build it as a module (ds18b20)
    
    然后在Makefile 中添加
    obj-$(CONFIG_DS18B20)		+= ds18b20.o
    
    回到源码根目录
    执行meke menuconfig,选上对ds18b20的支持

  • 测试驱动 使用mknod 命令创建设备节点后(注意在/proc/devices中查看设备号)
    然后 cat 18b20
    就可以读出温度了。
    • 本站是提供个人知识管理的网络存储空间,所有内容均由用户发布,不代表本站观点。请注意甄别内容中的联系方式、诱导购买等信息,谨防诈骗。如发现有害或侵权内容,请点击一键举报。
      转藏 分享 献花(0

      0条评论

      发表

      请遵守用户 评论公约

      类似文章 更多