版权声明

本文档所有内容文字资料由凌云实验室郭工编著,主要用于凌云嵌入式Linux教学内部使用,版权归属作者个人所有。任何媒体、网站、或个人未经本人协议授权不得转载、链接、转帖或以其他方式复制发布/ 发表。已经授权的媒体、网站,在下载使用时必须注明来源,违者本人将依法追究责任。

  • Copyright (C) 2021 凌云物网智科实验室·郭工

  • Author: GuoWenxue <guowenxue@gmail.com> QQ: 281143292

wechat_pub

3.6.1 一线协议——DS18B20传感器驱动

3.6.1.1.DS18B20简介

DS18B20是一种常见的数字温度传感器,其控制命令和数据都是以数字信号的方式输入输出,相比较于模拟温度传感器,具有具有体积小,硬件开销低,抗干扰能力强,精度高的特点。

测温范围:-55°C 到 +125°C 通信接口:1-Wire(单总线)

其它特征:可形成总线结构、内置温度报警功能、可寄生供电(VCC都不用接,直接信号线加GND线就可以通信)

3.6.1.2.DS18B20工作原理

模拟温度传感器:热敏电阻,可通过温度的变化改变电阻值,一般再接一个分压电阻,串联到VCC和GND之间,需要用AD转换芯片将模拟信号转换为数字信号才能供单片机使用。

DS18B20内部集成了模拟温度传感器所需的电路,其内部也相当于有个小芯片,将模拟信号处理成数字信号后存到RAM中,再通过引脚,将信号传给单片机使用。

DS18B20

3.6.1.3.DS18B20操作流程

初始化:从机复位,主机判断从机是否响应

ROM操作:ROM指令+本指令需要的读写操作

功能操作:功能指令+本指令需要的读写操作

ROM指令

功能指令

SEARCH ROM [F0h](搜索ROM)

CONVERT T [44h](温度变换,启动温度传感器读取温度放到RAM中)

READ ROM [33h](读ROM)

WRITE SCRATCHPAD [4Eh](写RAM,加某个地址后,把数据写入到RAM中)

MATCH ROM [55h](匹配ROM,发送该指令后会紧接着发送ROM地址,用于多个设备)

READ SCRATCHPAD [BEh](读RAM,可以只读取RAM前两位的温度值,后续的不读取)

SKIP ROM [CCh](跳过ROM,只有一个设备时使用)

COPY SCRATCHPAD [48h](复制暂存器,调用该指令后会将RAM Byte2,3,4的值写入到EEPROM中,掉电不丢失)

ALARM SEARCH [ECh](报警ROM)

RECALL E2 [B8h](将EEPROM的值读取到RAM对应地址处)

READ POWER SUPPLY [B4h](判断是否是寄生供电)

其中第二步执行ROM指令,也就是访问每个DS18B20,搜索64位序列号,读取匹配的序列号值,然后匹配对应的DS18B20,如果我们仅仅使用单个DS18B20,可以直接跳过ROM指令。而跳过ROM指令的字节是0xCC。

3.6.1.4.DS18B20时序

初始化DS18B20

ds18b20_start

任何器件想要使用,首先就是需要初始化,对于DS18B20单总线设备,首先初始化单总线为高电平,然后总线开始也需要检测这条总线上是否存在DS18B20这个器件。如果这条总线上存在DS18B20,总线会根据时序要求返回一个低电平脉冲,如果不存在的话,也就不会返回脉冲,即总线保持为高电平。

初始化具体时序步骤如下:

1.拉低总线至少480us,产生复位脉冲,然后释放总线(拉高电平)。 2.这时DS8B20检测到请求之后,会拉低信号,大约60~240us表示应答。 3.DS8B20拉低电平的60~240us之间,读取总线的电平,如果是低电平,那么表示初始化成功 4.DS18B20拉低电平60~240us之后,会释放总线。

写0/1 或者 读0/1

ds18b20_write

1.写时序

总线控制器通过控制单总线高低电平持续时间从而把逻辑1或0写DS18B20中。每次只传输1位数据

想要给DS18B20写入一个0时,需要将引脚拉低,保持低电平时间要在60~120us之间,然后释放总线 想要给DS18B20写入一个1时,需要将引脚拉低,拉低时间需要大于1us,然后在15us内拉高总线.

在写时序起始后15μs到60μs期间,DS18B20处于采样单总线电平状态。如果在此期间总线为高电平,则向DS18B20写入1;如果总线为低电平,则向DSl8B20写入0。

注意:2次写周期之间至少间隔1us

2.读时序

读时隙由主机拉低总线电平至少1μs然后再释放总线,读取DS18B20发送过来的1或者0

DS18B20在检测到总线被拉低1微秒后,便开始送出数据,若是要送出0就把总线拉为低电平直到读周期结束。若要送出1则释放总线为高电平。

3.6.1.5.DS18B20数据帧

温度变换:初始化→跳过ROM →开始温度变换

ds18b20_read1

温度读取:初始化→跳过ROM →读暂存器→连续的读操作

ds18b20_read2

3.6.1.6.温度存储格式

温度数据都是以补码的形式存储的,读取时要进行转换,特别是负数。

ds18b20_read3

ds18b20_read4

3.6.2 DS18B20驱动实验

3.6.2.1.设备树

首先配置设备树,imx6ull/bsp/kernel/linux-imx/arch/arm/boot/dts/overlays下创建w1.dts

/*
* Copyright (C) 2022 LingYun IoT System Studio
* Author: Guo Wenxue<guowenxue@gmail.com>
*/
/dts-v1/;
/plugin/;
#include <dt-bindings/gpio/gpio.h>
#include "../imx6ul-pinfunc.h"
/* W1(DS18B20) on 40Pin Header Pin#7 (GPIO1_IO18) */
&w1 {
	compatible = "ds18b20-gpio";
	status = "okay";
	pinctrl-names = "default";
	pinctrl-0 = <&pinctrl_w1>;
	w1-gpios = <&gpio1 18 GPIO_ACTIVE_HIGH>;
};

&iomuxc {
	pinctrl_w1: w1grp {
		fsl,pins = <
					MX6UL_PAD_UART1_CTS_B__GPIO1_IO18 0x110b0
				>;
	};
};

在Makefile中加上

dtb-y += w1.dtbo

编译设备树

make dtbs

生成w1.dtbo文件

3.6.2.2.DS18B20驱动实现

#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>             //file_operations,用于联系系统调用和驱动程序
#include <linux/errno.h>          //ENODEV,ENOMEM存放的头文件
#include <linux/kernel.h>         // printk(),内核打印函数
#include <linux/device.h>         // 用于自动生成设备节点的函数头文件
#include <linux/gpio.h>           // gpio相关函数
#include <linux/gpio/consumer.h>  // gpiod相关函数
#include <linux/of_gpio.h>        // gpio子系统相关函数
#include <linux/uaccess.h>        // copy_to_user函数头文件
#include <linux/timer.h>          //定时器相关
#include <linux/delay.h>          // 延时函数头文件
#include <linux/cdev.h>           //cdev相关函数
#include <linux/platform_device.h>//platform相关结构体
#include <linux/delay.h>



#define DEV_NAME            "w1_ds18b20"    //最后在/dev路径下的设备名称,应用层open的字符串名
#define DEV_CNT             1



#ifndef DEV_MAJOR	
#define DEV_MAJOR           0
#endif

#define LOW                 0
#define HIGH                1

/* DS18B20 ROM命令 */
#define Read_ROM                   0x33   // This command can only be used when there is one slave on the bus
#define SKIP_ROM                   0xCC   // without sending out any ROM code information

/* DS18B20 FUNC命令 */
#define Convert_T                  0x44   // initiates a single temperature conversion.
#define Read_Data                  0xBE   // the master to read the contents of the scratchpad 


#define CRC_MODEL                  0x31
#define CRC_SUCCESS                0
#define CRC_FAIL                   1

static int dev_major = DEV_MAJOR;	/*主设备号*/

/*存放Led的私有属性*/
struct gpio_w1_priv {
    struct cdev          cdev;     /*cdev结构体*/
    struct class         *dev_class;/*自动创建设备节点的类*/
    struct device        *dev;
    spinlock_t           lock;
    
};

struct gpio_desc     *w1_gpiod; /* gpio descriptor */


/*DS18B20 接收发送*/
#define DS18B20_In_init()          gpiod_direction_input(w1_gpiod)//设置GPIO的方向为输出,默认电平为高    
#define DS18B20_Out_init()         gpiod_direction_output(w1_gpiod, HIGH)//设置GPIO的方向为输出,默认电平为高
#define DS18B20_DQ_IN()            gpiod_get_value(w1_gpiod)
#define DS18B20_DQ_OUT(N)          gpiod_set_value(w1_gpiod, N)

/* @description: 解析设备树,初始化w1硬件io
 *
 * @parm  : pdev - platform_device 设备指针
 * @parm  : 
 * @return: 0 successfully , !0 failure
 */
static int parser_dt_init_w1(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev; // 设备指针
    struct gpio_w1_priv     *priv = NULL;                  /*存放私有属性*/
    struct gpio_desc        *gpiod;                 /*gpiod结构体*/


    /*为w1私有属性分配存储空间*/
    priv = devm_kzalloc(dev, sizeof(struct gpio_w1_priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /*获取w1的设备树节点*/
    gpiod = gpiod_get(dev, "w1", 0);//请求gpio,有请求一定要有释放,否则模块下一次安装将请求失败
    if (IS_ERR(gpiod))  
    {
        printk("gpiod request failure\n");
        return PTR_ERR(gpiod);
    }

    /*将gpiod传给结构体*/
    w1_gpiod = gpiod; 

    dev_info(&pdev->dev, "parser dts got valid w1\n");

    platform_set_drvdata(pdev, priv); //设置私有数据,方便其他函数操作

    return 0;
}

static int w1_delay_parm = 1;


static void w1_delay(unsigned long tm)
{
	udelay(tm * w1_delay_parm);
}

/* @description: 主机发送起始信号,主机拉低 >=480us,释放总线(主机拉高),等待15~60us
 * @return: 0 successfully , !0 failure
 */
static int DS18B20_start(void)
{
    int rv;

    /*主机设置为输出*/
    DS18B20_Out_init();//设置GPIO的方向为输出,默认电平为高

    /*主机拉低 >=480us*/
    DS18B20_DQ_OUT(LOW);
    w1_delay(480);

    /*主机拉高 15~60us*/
    DS18B20_DQ_OUT(HIGH);
    w1_delay(70);

    /*主机设置为输入*/
    DS18B20_In_init();

    /*总线变低说明从设备发送了响应信号:60~240us*/
    rv = DS18B20_DQ_IN();
    if(0 != rv)
    {
        printk("%s():%d DS18B20_no respond \n", __FUNCTION__, __LINE__);
        return -EFAULT;

    }
    printk("%s():%d DS18B20_respond \n", __FUNCTION__, __LINE__);
    w1_delay(10);

    DS18B20_Out_init(); 

    DS18B20_DQ_OUT(HIGH);/*释放总线*/

    
    return rv;

}


/* @description: 主机读取一个位,整个读周期最少需要60us,启动读开始信号后必须15us内读取IO电平,否则就会被上拉拉高 
 *
 * @return: uint8_t bit 
 */
static uint8_t DS18B20_readBit(void)
{
    uint8_t bit = 0;
    /*主机再设置成输出模式准备开始读数据*/
    DS18B20_Out_init();//设置GPIO的方向为输出,默认电平为高

    /*主机拉低 >=1,表示开始读数据*/
    DS18B20_DQ_OUT(LOW);
    w1_delay(2);

    DS18B20_In_init();
  
    w1_delay(10);
    /*判断数据是0还是1*/
    bit = DS18B20_DQ_IN();

    w1_delay(50);

    return bit;
}


/* @description: 读取一个字节
 *
 * @return: uint8_t Bety 
 */
static uint8_t DS18B20_readBety(void)
{
    uint8_t     i,Bety = 0;
    uint8_t     bit;

    for(i=0; i<8; i++)
    {
        bit = DS18B20_readBit();
        if(bit)
            Bety |= (0x01 << i);
        
        // Bety |= DS18B20_readBit();/*MSB最先读到的是最高位*/
        // Bety <<= 1;
    }

    printk("%s():%d DS18B20_readBit %x \n", __FUNCTION__, __LINE__, Bety);

    return Bety;

}



/* @description: ds18b20 写一个位
 *
 * @parm  : bit - 一个字
 * @parm  : 
 * @return: 
 */
static void DS18B20_writeBit(unsigned char bit)
{
    DS18B20_Out_init();

    /* 判断 bit */
    bit = bit > 1 ? 1 : bit;
    w1_delay(50);

    DS18B20_DQ_OUT(LOW);
    w1_delay(2);
    DS18B20_DQ_OUT(bit);
    w1_delay(60);
    DS18B20_DQ_OUT(HIGH);

    w1_delay(12);
}



/* @description: ds18b20 写一个字节
 *
 * @parm  : bit - 一个字
 * @parm  : 
 * @return: 
 */
static void DS18B20_writeBety(uint8_t Bety)
{
    uint8_t     i = 0;

    for(i=0; i<8; i++)
    {
        DS18B20_writeBit((Bety >> i)&0x01);
    }

    printk("%s():%d DS18B20_writeBety %x \n", __FUNCTION__, __LINE__, Bety);

}

/* @description: 读取温度值
 *
 * @parm  : struct gpio_w1_priv *priv 私有数据结构体
 * @parm  : 
 * @return: uint16_t bit 
 */
static uint16_t DS18B20_SampleData(struct gpio_w1_priv *priv)
{
    unsigned long   flags;
    uint16_t        temp = 0;
    uint8_t         temp_H = 0;
    uint8_t         temp_L = 0;
    int             rv = 0;
    
    spin_lock_irqsave(&priv->lock, flags);
    /*主机发起起始信号,并且从设备回应*/
    if(0 != DS18B20_start())
    {
        printk("%s():%d DS18B20_start err \n", __FUNCTION__, __LINE__);
        rv = -EFAULT;
        goto undo_spin_unlock;
    }


    DS18B20_writeBety(SKIP_ROM);
    DS18B20_writeBety(Convert_T);

    spin_unlock_irqrestore(&priv->lock, flags);

    msleep(750);//待修改

    spin_lock_irqsave(&priv->lock, flags);

    if(0 != DS18B20_start())
    {
        printk("%s():%d DS18B20_start err \n", __FUNCTION__, __LINE__);
        rv = -EFAULT;
        goto undo_spin_unlock;
    }


    DS18B20_writeBety(SKIP_ROM);
    DS18B20_writeBety(Read_Data);
    
    temp_L = DS18B20_readBety();
    temp_H = DS18B20_readBety();

    spin_unlock_irqrestore(&priv->lock, flags);

    // temp_H = 0xff;
    // temp_L = 0x5e;

    temp = (temp_H << 8) | temp_L;

    //printk("%s():%d temp_H:%x, temp_L:%x ,temp %x\n", __FUNCTION__, __LINE__, temp_H, temp_L, temp);
    #if 0
    //if(temp&0x8000>0)
    
    /*如果为负数,就是算补码:取反再加一, 这个地方不需要计算,因为直接就可以判断是正还是负数*/
    if(temp < 0)
    {
        temp = ~temp + 1;
    }
    #endif

    return temp;

undo_spin_unlock:
    spin_unlock_irqrestore(&priv->lock, flags);
    return rv;    
}


/* @description: 打开设备
 *
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件,file结构体有个叫做private_data的成员变量
 *  *              一般在open的时候将private_data指向设备结构体。
 * @return: 0 successfully , !0 failure
 */
/*字符设备操作函数集*/
static int w1_open(struct inode *inode, struct file *file)
{    
    struct gpio_w1_priv *priv;

    printk("%s():%d w1_open platform driver\n", __FUNCTION__, __LINE__);

    priv = container_of(inode->i_cdev, struct gpio_w1_priv, cdev);//找到私有数据的地址
    file->private_data = priv;

    return 0;
}

/* @description: 从设备读取文件
 * @parm  : filp - 设备文件,文件描述符
 * @parm  : buf - 返回给用户空间的数据缓冲区
 * @parm  : cnt - 要读取的数据长度
 * @parm  : offt - 相对于文件首地址的偏移
 * @return: 读取的字节数,负数 - 读取失败
 */
static ssize_t w1_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    int rv = 0;
    uint16_t temp = 0;
 
    struct gpio_w1_priv *priv = filp->private_data;
   
    temp = DS18B20_SampleData(priv);
    printk("%s():%d temp %x \n", __FUNCTION__, __LINE__, temp);   
  
    rv = copy_to_user(buf, &temp, sizeof(temp));
    if(rv)
    {
        dev_err(priv->dev, "copy to user error.\n");
        return -EFAULT;
    }
    else 
    {
        rv = sizeof(temp);
    }

    return rv;
}

/* @description: 关闭设备
 *
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件,file结构体有个私有数据可以使用
 * @return: 0 successfully , !0 failure
 */
static int w1_release(struct inode *inode, struct file *file)
{
    printk("%s():%d w1_release w1 platform driver\n", __FUNCTION__, __LINE__);
    return 0;
}


/*字符设备操作函数集*/
static struct file_operations w1_fops = {
    .owner = THIS_MODULE,
    .open = w1_open,
    .read = w1_read,
    .release = w1_release,
};

/* @description: sysfs - 温度属性显示函数
 *
 * @parm  : devp - 设备指针,创建file时候会指定dev
 * @parm  : attr - 设备属性,创建时候传入
 * @parm  : buf - 传出给sysfs中显示的buf
 * @return: 显示的字节数
 */
 static ssize_t temp_show(struct device *devp, struct device_attribute *attr, char *buf)
 {
    u_int16_t temp = 0;
    struct gpio_w1_priv     *priv = dev_get_drvdata(devp);

    temp = DS18B20_SampleData(priv);

    return sprintf(buf, "t=%d\n", temp*62);//1000倍
 }

/* @description: sysfs - echo写入属性函数
 * @parm  : dev - 设备指针,创建file时候会指定dev
 * @parm  : attr - 设备属性,创建时候传入
 * @parm  : buf - 用户空间的buf
 * @parm  : count - 传入buf的size
 * @return: 写入的buf大小
 */
static ssize_t temp_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    return count;
}

/*声明并初始化一个 device_attribute结构体*/
DEVICE_ATTR(temp, 0644, temp_show, temp_store);



/*probe函数实现字符设备的注册和LED灯的初始化*/
static int gpio_w1_probe(struct platform_device *pdev)
{
    struct gpio_w1_priv     *priv; //临时存放私有属性的结构体
    struct device           *dev;  //设备结构体
    dev_t                   devno; //设备的主次设备号
    int                     rv = 0;

    /*解析设备树并初始化w1状态*/
    rv = parser_dt_init_w1(pdev);
    if( rv < 0 )
        return rv;

    /*将之前存入的私有属性。放入临时结构体中*/ 
    priv = platform_get_drvdata(pdev);

    /*---------------------注册 字符设备部分-----------------*/
    /*1.分配主次设备号,这里即支持静态指定,也至此动态申请*/
    if (0 != dev_major)  /*static*/
    {
        devno = MKDEV(dev_major, 0);
        rv = register_chrdev_region(devno, 1, DEV_NAME);	/* /proc/devices/my_w1 */
    } 
    else
    {
        rv = alloc_chrdev_region(&devno, 0, 1, DEV_NAME);	/*动态申请字符设备号*/
        dev_major = MAJOR(devno);	/*获取主设备号*/
    }
	
	/*自动分配设备号失败*/
    if (rv < 0) 
    {
        printk("%s driver can't get major %d", DEV_NAME, dev_major);
        return rv;
    }
    printk(" %s driver use major %d\n", DEV_NAME, dev_major);
    
    /*2.分配cdev结构体,绑定主次设备号、fops到cdev结构体中,并注册给Linux内核*/
    priv->cdev.owner = THIS_MODULE;	/*.owner这表示谁拥有你这个驱动程序*/
    cdev_init(&priv->cdev, &w1_fops);  /*初始化cdev,把fops添加进去*/
    rv = cdev_add(&priv->cdev, devno, DEV_CNT);	/*注册给内核,设备数量1个*/
    
    if (0 != rv) {
        printk( " %s driver can't register cdev:result=%d\n", DEV_NAME, rv);
        goto undo_major;
    }
    printk( " %s driver can register cdev:result=%d\n", DEV_NAME, rv);
    
    //3.创建类,驱动中进行节点创建
    priv->dev_class = class_create(THIS_MODULE, DEV_NAME); /* /sys/class/my_w1 创建类*/
    if(IS_ERR(priv->dev_class))
    {
        printk("%s driver create class failure\n", DEV_NAME);
        rv = -ENOMEM;
        goto undo_cdev;
    }

    //4.创建设备
   
    devno = MKDEV(dev_major, 0); //给每一个led设置设备号
    dev = device_create(priv->dev_class, NULL, devno, NULL, DEV_NAME);		 /* /dev/my_w1 注册这个设备节点*/
    if(IS_ERR(priv->dev_class))
    {
        rv = -ENOMEM;   //返回错误码,应用空间strerror查看
        goto undo_class;
    }
    
    /*5. 初始化自旋锁*/
    spin_lock_init(&priv->lock);

    //6. 创建sys 属性 在platform下
    if(device_create_file(dev, &dev_attr_temp))
    {
        rv = -ENOMEM;
        goto undo_device;
    }

    priv->dev =  dev; /*把dev赋给priv结构体*/

    //7. 保存私有数据
    platform_set_drvdata(pdev, priv);
    dev_set_drvdata(priv->dev, priv);
    dev_info(priv->dev, "%s driver probe okay.\n", DEV_NAME);
    /*class_create->name , device_create->name */
    dev_info(&pdev->dev, "%s driver probe okay.\n", DEV_NAME);
    /*driver->name, device->name(devic_tree_node->name)*/

undo_device:
        device_destroy(priv->dev_class, devno); /*注销每一个设备号*/

undo_class:
        class_destroy(priv->dev_class);

undo_cdev:
        cdev_del(&priv->cdev);

undo_major:
        unregister_chrdev_region(devno, DEV_CNT);

        return rv;
}


/* @description: 设备卸载时候执行
 *
 * @parm  : pdev - 设备指针
 * @return: 0 successfully , !0 failure
 */
static int gpio_w1_remove(struct platform_device *pdev)
{   
    struct gpio_w1_priv   *priv = platform_get_drvdata(pdev);//临时存放私有属性的结构体

    dev_t devno = MKDEV(dev_major, 0);

    //删除sys中的属性
    device_remove_file(priv->dev, &dev_attr_temp);

    device_destroy(priv->dev_class, devno); /*注销每一个设备号*/
  

    class_destroy(priv->dev_class); //注销类
    cdev_del(&priv->cdev);  //删除cdev
    unregister_chrdev_region(MKDEV(dev_major, 0), DEV_CNT);//释放设备号
    
 
    gpiod_set_value(w1_gpiod, 0); //低电平关闭灯
    gpiod_put(w1_gpiod); //释放gpiod
    

    devm_kfree(&pdev->dev, priv); //释放堆

    printk("gpio w1 driver removed.\n");
    return 0;
}

/* @description: 设备停止执行的函数
 *
 * @parm  : pdev - 设备指针
 * @return: void
 */
static void imx_w1_shutdown(struct platform_device *pdev)
{
    struct gpio_w1_priv *priv = platform_get_drvdata(pdev);

    
    gpiod_set_value(w1_gpiod, 0);
    
}

/*------------------第一部分----------------*/
static const struct of_device_id w1_match_table[] = {
    {.compatible = "ds18b20-gpio"},
    {/* sentinel */},
};

MODULE_DEVICE_TABLE(of, w1_match_table);

/*定义平台驱动结构体*/
static struct platform_driver gpio_w1_driver = {
	.probe		= gpio_w1_probe,    //驱动安装时候会执行的钩子函数
	.remove	    = gpio_w1_remove,   //驱动卸载时候
    .shutdown   = imx_w1_shutdown,  //设备停止时候
	.driver		= {					 //描述这个驱动的属性
			.name	= "ds18b20-gpios",      //无设备树时,用于设备和驱动间匹配
        	.owner = THIS_MODULE,    
			.of_match_table = w1_match_table,//有设备树后,利用设备树匹配表
	},
};

/*------------------第二部分----------------*/
/*驱动初始化函数*/
static int __init platdrv_gpio_w1_init(void)
{
    int     rv = 0;

    rv = platform_driver_register(&gpio_w1_driver);  //注册platform的led驱动
    if(rv)
    {
        printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__, __LINE__, rv);
        return rv;
    }
    printk("Regist w1 Platform Driver successfully!\n");
    return 0;
}

/*------------------第三部分----------------*/
/*驱动注销函数*/
static void __exit platdrv_gpio_w1_exit(void)
{
    printk("%s():%d remove w1 platform driver\n", __FUNCTION__, __LINE__);
    platform_driver_unregister(&gpio_w1_driver);    //卸载驱动
}

module_init(platdrv_gpio_w1_init);
module_exit(platdrv_gpio_w1_exit);

MODULE_AUTHOR("Guowenxue <guowenxue@gmail.com>");
MODULE_DESCRIPTION("GPIO W1 driver on i.MX6ULL platform");
MODULE_LICENSE("GPL");
MODULE_ALIAS("platform:w1-dev");

3.6.2.2.1.注册平台设备

驱动入口和出口函数仅仅用于注册、注销平台设备驱动,代码块如下:

/*------------------第一部分----------------*/
static const struct of_device_id w1_match_table[] = {
    {.compatible = "ds18b20-gpio"},
    {/* sentinel */},
};

MODULE_DEVICE_TABLE(of, w1_match_table);

/*定义平台驱动结构体*/
static struct platform_driver gpio_w1_driver = {
	.probe		= gpio_w1_probe,    //驱动安装时候会执行的钩子函数
	.remove	    = gpio_w1_remove,   //驱动卸载时候
    .shutdown   = imx_w1_shutdown,  //设备停止时候
	.driver		= {					 //描述这个驱动的属性
			.name	= "ds18b20-gpios",      //无设备树时,用于设备和驱动间匹配
        	.owner = THIS_MODULE,    
			.of_match_table = w1_match_table,//有设备树后,利用设备树匹配表
	},
};

/*------------------第二部分----------------*/
/*驱动初始化函数*/
static int __init platdrv_gpio_w1_init(void)
{
    int     rv = 0;

    rv = platform_driver_register(&gpio_w1_driver);  //注册platform的led驱动
    if(rv)
    {
        printk(KERN_ERR "%s:%d: Can't register platform driver %d\n", __FUNCTION__, __LINE__, rv);
        return rv;
    }
    printk("Regist w1 Platform Driver successfully!\n");
    return 0;
}

/*------------------第三部分----------------*/
/*驱动注销函数*/
static void __exit platdrv_gpio_w1_exit(void)
{
    printk("%s():%d remove w1 platform driver\n", __FUNCTION__, __LINE__);
    platform_driver_unregister(&gpio_w1_driver);    //卸载驱动
}

module_init(platdrv_gpio_w1_init);
module_exit(platdrv_gpio_w1_exit);

3.6.2.2.2.解析设备树

获取设备树节点,申请GPIO。

/* @description: 解析设备树,初始化w1硬件io
 *
 * @parm  : pdev - platform_device 设备指针
 * @parm  : 
 * @return: 0 successfully , !0 failure
 */
static int parser_dt_init_w1(struct platform_device *pdev)
{
    struct device *dev = &pdev->dev; // 设备指针
    struct gpio_w1_priv     *priv = NULL;                  /*存放私有属性*/
    struct gpio_desc        *gpiod;                 /*gpiod结构体*/


    /*为w1私有属性分配存储空间*/
    priv = devm_kzalloc(dev, sizeof(struct gpio_w1_priv), GFP_KERNEL);
    if (!priv)
        return -ENOMEM;

    /*获取w1的设备树节点*/
    gpiod = gpiod_get(dev, "w1", 0);//请求gpio,有请求一定要有释放,否则模块下一次安装将请求失败
    if (IS_ERR(gpiod))  
    {
        printk("gpiod request failure\n");
        return PTR_ERR(gpiod);
    }

    /*将gpiod传给结构体*/
    w1_gpiod = gpiod; 

    dev_info(&pdev->dev, "parser dts got valid w1\n");

    platform_set_drvdata(pdev, priv); //设置私有数据,方便其他函数操作

    return 0;
}

3.6.2.2.3.probe函数和.remove函数实现

在.probe中实现一些初始化工作,设置platform_device 的私有数据,用来传递设备结构体指针。

然后申请设备号,初始化cdev绑定ops,将cdev注册到内核中,并创建class 和 device ,然后创建设备属性文件。

在.remove函数中实现退出之前的清理工作。

prob函数

/*probe函数实现字符设备的注册和初始化*/
static int gpio_w1_probe(struct platform_device *pdev)
{
    struct gpio_w1_priv     *priv; //临时存放私有属性的结构体
    struct device           *dev;  //设备结构体
    dev_t                   devno; //设备的主次设备号
    int                     rv = 0;

    /*解析设备树并初始化w1状态*/
    rv = parser_dt_init_w1(pdev);
    if( rv < 0 )
        return rv;

    /*将之前存入的私有属性。放入临时结构体中*/ 
    priv = platform_get_drvdata(pdev);

    /*---------------------注册 字符设备部分 省略-----------------*/
    
    /*5. 初始化自旋锁*/
    spin_lock_init(&priv->lock);

    //6. 创建sys 属性 在platform下
    if(device_create_file(dev, &dev_attr_temp))
    {
        rv = -ENOMEM;
        goto undo_device;
    }

    priv->dev =  dev; /*把dev赋给priv结构体*/

    //7. 保存私有数据
    platform_set_drvdata(pdev, priv);
    dev_set_drvdata(priv->dev, priv);
    dev_info(priv->dev, "%s driver probe okay.\n", DEV_NAME);
    /*class_create->name , device_create->name */
    dev_info(&pdev->dev, "%s driver probe okay.\n", DEV_NAME);
    /*driver->name, device->name(devic_tree_node->name)*/
    
    return rv;
}

remove函数

/* @description: 设备卸载时候执行
 *
 * @parm  : pdev - 设备指针
 * @return: 0 successfully , !0 failure
 */
static int gpio_w1_remove(struct platform_device *pdev)
{   
    struct gpio_w1_priv   *priv = platform_get_drvdata(pdev);//临时存放私有属性的结构体

    dev_t devno = MKDEV(dev_major, 0);

    //删除sys中的属性
    device_remove_file(priv->dev, &dev_attr_temp);

    device_destroy(priv->dev_class, devno); /*注销每一个设备号*/
  

    class_destroy(priv->dev_class); //注销类
    cdev_del(&priv->cdev);  //删除cdev
    unregister_chrdev_region(MKDEV(dev_major, 0), DEV_CNT);//释放设备号
    
 
    gpiod_set_value(w1_gpiod, 0); //低电平关闭灯
    gpiod_put(w1_gpiod); //释放gpiod
    

    devm_kfree(&pdev->dev, priv); //释放堆

    printk("gpio w1 driver removed.\n");
    return 0;
}

/* @description: 设备停止执行的函数
 *
 * @parm  : pdev - 设备指针
 * @return: void
 */
static void imx_w1_shutdown(struct platform_device *pdev)
{
    struct gpio_w1_priv *priv = platform_get_drvdata(pdev);

    
    gpiod_set_value(w1_gpiod, 0);
    
}

3.6.2.2.4.实现字符设备操作函数集

在.probe函数中添加了一个字符设备,ds18b20的初始化以及转换结果的读取都在这个字符设备的操作函数中实现,其中最主要是.open和.read函数,下面是这两个函数实现。

.open函数实现,具体代码如下:

/* @description: 打开设备
 *
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件,file结构体有个叫做private_data的成员变量
 *  *              一般在open的时候将private_data指向设备结构体。
 * @return: 0 successfully , !0 failure
 */
static int w1_open(struct inode *inode, struct file *file)
{    
    struct gpio_w1_priv *priv;

    printk("%s():%d w1_open platform driver\n", __FUNCTION__, __LINE__);

    priv = container_of(inode->i_cdev, struct gpio_w1_priv, cdev);//找到私有数据的地址
    file->private_data = priv;

    return 0;
}

.read函数实现,调用DS18B20_SampleData函数获取温度值。

/* @description: 从设备读取文件
 * @parm  : filp - 设备文件,文件描述符
 * @parm  : buf - 返回给用户空间的数据缓冲区
 * @parm  : cnt - 要读取的数据长度
 * @parm  : offt - 相对于文件首地址的偏移
 * @return: 读取的字节数,负数 - 读取失败
 */
static ssize_t w1_read(struct file *filp, char __user *buf, size_t cnt, loff_t *off)
{
    int rv = 0;
    uint16_t temp = 0;
 
    struct gpio_w1_priv *priv = filp->private_data;
   
    temp = DS18B20_SampleData(priv);//获取温度值
    printk("%s():%d temp %x \n", __FUNCTION__, __LINE__, temp);   
  
    rv = copy_to_user(buf, &temp, sizeof(temp));
    if(rv)
    {
        dev_err(priv->dev, "copy to user error.\n");
        return -EFAULT;
    }
    else 
    {
        rv = sizeof(temp);
    }

    return rv;
}

/* @description: 关闭设备
 *
 * @parm  : inode - 传递给驱动的inode
 * @parm  : filp - 设备文件,file结构体有个私有数据可以使用
 * @return: 0 successfully , !0 failure
 */
static int w1_release(struct inode *inode, struct file *file)
{
    printk("%s():%d w1_release w1 platform driver\n", __FUNCTION__, __LINE__);
    return 0;
}


/*字符设备操作函数集*/
static struct file_operations w1_fops = {
    .owner = THIS_MODULE,
    .open = w1_open,
    .read = w1_read,
    .release = w1_release,
};

3.6.2.2.5.sysfs - 温度属性显示函数

/* @description: sysfs - 温度属性显示函数
 *
 * @parm  : devp - 设备指针,创建file时候会指定dev
 * @parm  : attr - 设备属性,创建时候传入
 * @parm  : buf - 传出给sysfs中显示的buf
 * @return: 显示的字节数
 */
 static ssize_t temp_show(struct device *devp, struct device_attribute *attr, char *buf)
 {
    u_int16_t temp = 0;
    struct gpio_w1_priv     *priv = dev_get_drvdata(devp);

    temp = DS18B20_SampleData(priv);

    return sprintf(buf, "t=%d\n", temp*62);//1000倍
 }

/* @description: sysfs - echo写入属性函数
 * @parm  : dev - 设备指针,创建file时候会指定dev
 * @parm  : attr - 设备属性,创建时候传入
 * @parm  : buf - 用户空间的buf
 * @parm  : count - 传入buf的size
 * @return: 写入的buf大小
 */
static ssize_t temp_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count)
{
    return count;
}

/*声明并初始化一个 device_attribute结构体*/
DEVICE_ATTR(temp, 0644, temp_show, temp_store);

使用这个函数时要引用 device_create所返回的device*指针,作用是在/sys/class/下创建一个属性文件,从而通过对这个属性文件进行读写就能完成对应的数据操作。

3.6.2.2.6.DS18B20操作底层函数

1.设置宏来控制gpio,以便后续操作。

/*DS18B20 接收发送*/
#define DS18B20_In_init()          gpiod_direction_input(w1_gpiod)//设置GPIO的方向为输出,默认电平为高    
#define DS18B20_Out_init()         gpiod_direction_output(w1_gpiod, HIGH)//设置GPIO的方向为输出,默认电平为高
#define DS18B20_DQ_IN()            gpiod_get_value(w1_gpiod)
#define DS18B20_DQ_OUT(N)          gpiod_set_value(w1_gpiod, N)

2.DS18B20起始信号

复位按照时序图操作,先拉低,等待至少480us后再拉高,延迟70us,读取当前电平,如果被拉低,则设备存在,如果被拉高,则没有。而后延迟一段时间后释放总线。

/* @description: 主机发送起始信号,主机拉低 >=480us,释放总线(主机拉高),等待15~60us
 * @return: 0 successfully , !0 failure
 */
static int DS18B20_start(void)
{
    int rv;

    /*主机设置为输出*/
    DS18B20_Out_init();//设置GPIO的方向为输出,默认电平为高

    /*主机拉低 >=480us*/
    DS18B20_DQ_OUT(LOW);
    w1_delay(480);

    /*主机拉高 15~60us*/
    DS18B20_DQ_OUT(HIGH);
    w1_delay(70);

    /*主机设置为输入*/
    DS18B20_In_init();

    /*总线变低说明从设备发送了响应信号:60~240us*/
    rv = DS18B20_DQ_IN();
    if(0 != rv)
    {
        printk("%s():%d DS18B20_no respond \n", __FUNCTION__, __LINE__);
        return -EFAULT;

    }
    printk("%s():%d DS18B20_respond \n", __FUNCTION__, __LINE__);
    w1_delay(10);

    DS18B20_Out_init(); 

    DS18B20_DQ_OUT(HIGH);/*释放总线*/

    
    return rv;

}

3.读信号

/* @description: 主机读取一个位,整个读周期最少需要60us,启动读开始信号后必须15us内读取IO电平,否则就会被上拉拉高 
 *
 * @return: uint8_t bit 
 */
static uint8_t DS18B20_readBit(void)
{
    uint8_t bit = 0;
    /*主机再设置成输出模式准备开始读数据*/
    DS18B20_Out_init();//设置GPIO的方向为输出,默认电平为高

    /*主机拉低 >=1,表示开始读数据*/
    DS18B20_DQ_OUT(LOW);
    w1_delay(2);

    DS18B20_In_init();
  
    w1_delay(10);
    /*判断数据是0还是1*/
    bit = DS18B20_DQ_IN();

    w1_delay(50);

    return bit;
}


/* @description: 读取一个字节
 *
 * @return: uint8_t Bety 
 */
static uint8_t DS18B20_readBety(void)
{
    uint8_t     i,Bety = 0;
    uint8_t     bit;

    for(i=0; i<8; i++)
    {
        bit = DS18B20_readBit();
        if(bit)
            Bety |= (0x01 << i);
        
        // Bety |= DS18B20_readBit();/*MSB最先读到的是最高位*/
        // Bety <<= 1;
    }

    printk("%s():%d DS18B20_readBit %x \n", __FUNCTION__, __LINE__, Bety);

    return Bety;

}

4.写信号

/* @description: ds18b20 写一个位
 *
 * @parm  : bit - 一个字
 * @parm  : 
 * @return: 
 */
static void DS18B20_writeBit(unsigned char bit)
{
    DS18B20_Out_init();

    /* 判断 bit */
    bit = bit > 1 ? 1 : bit;
    w1_delay(50);

    DS18B20_DQ_OUT(LOW);
    w1_delay(2);
    DS18B20_DQ_OUT(bit);
    w1_delay(60);
    DS18B20_DQ_OUT(HIGH);

    w1_delay(12);
}



/* @description: ds18b20 写一个字节
 *
 * @parm  : bit - 一个字
 * @parm  : 
 * @return: 
 */
static void DS18B20_writeBety(uint8_t Bety)
{
    uint8_t     i = 0;

    for(i=0; i<8; i++)
    {
        DS18B20_writeBit((Bety >> i)&0x01);
    }

    printk("%s():%d DS18B20_writeBety %x \n", __FUNCTION__, __LINE__, Bety);

}

3.6.2.2.7.获取ds18b20温度值

/* @description: 读取温度值
 *
 * @parm  : struct gpio_w1_priv *priv 私有数据结构体
 * @parm  : 
 * @return: uint16_t bit 
 */
static uint16_t DS18B20_SampleData(struct gpio_w1_priv *priv)
{
    unsigned long   flags;
    uint16_t        temp = 0;
    uint8_t         temp_H = 0;
    uint8_t         temp_L = 0;
    int             rv = 0;
    
    spin_lock_irqsave(&priv->lock, flags);
    /*主机发起起始信号,并且从设备回应*/
    if(0 != DS18B20_start())
    {
        printk("%s():%d DS18B20_start err \n", __FUNCTION__, __LINE__);
        rv = -EFAULT;
        goto undo_spin_unlock;
    }


    DS18B20_writeBety(SKIP_ROM);
    DS18B20_writeBety(Convert_T);

    spin_unlock_irqrestore(&priv->lock, flags);

    msleep(750);

    spin_lock_irqsave(&priv->lock, flags);

    if(0 != DS18B20_start())
    {
        printk("%s():%d DS18B20_start err \n", __FUNCTION__, __LINE__);
        rv = -EFAULT;
        goto undo_spin_unlock;
    }


    DS18B20_writeBety(SKIP_ROM);
    DS18B20_writeBety(Read_Data);
    
    temp_L = DS18B20_readBety();
    temp_H = DS18B20_readBety();

    spin_unlock_irqrestore(&priv->lock, flags);

    temp = (temp_H << 8) | temp_L;

    //printk("%s():%d temp_H:%x, temp_L:%x ,temp %x\n", __FUNCTION__, __LINE__, temp_H, temp_L, temp);

    return temp;

undo_spin_unlock:
    spin_unlock_irqrestore(&priv->lock, flags);
    return rv;    
}

3.6.2.3.DS18B20测试应用程序


/* 在C语言编程时,一般系统的头文件用<xxx.h>,我们自己写的头文件则用"zzz.h" */
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <string.h>
#include <time.h>
#include <errno.h>

/* 在C语言编程中,函数应该先定义再使用,如果函数的定义在函数调用后面,应该前向声明。*/
int ds18b20_get_temperature(float *temp);

int main(int argc, char *argv[])
{
    float       temp; /* 温度值有小数位,所以使用浮点数 */

    /* 1,在Linux下做C语言编程时,函数返回值一般是0表示成功,<0表示失败,我们也遵循这个规约;
     * 2,但函数调用只能有一个返回值,所以这里的采样函数只能通过指针来返回采样的温度值;
     * 3,因为要在ds18b20_get_temperature()函数中修改main()中temp的值,所以这里传&temp;
     */
    if( ds18b20_get_temperature(&temp) < 0 )
    {
        printf("ERROR: ds18b20 get temprature failure\n");
        return 1;
    }

    /* 打印DS18B20的采样温度值,因为℃是非ASCII打印字符,所以这里用 'C 代替 */
    printf("DS18B20 get temperature: %f 'C\n", temp);
    
    return 0;
}

/*
 * 函数说明: 该函数用来使用 DS18B20 温度传感器采样获取当前的温度值;
 * 参数说明: $temp: 通过指针返回DS18B20的采样温度
 * 返回说明: ==0 表示成功, <0 表示失败
 */
int ds18b20_get_temperature(float *temp)
{
    const char     *w1_path = "/sys/class/w1_ds18b20/w1_ds18b20/";/*my_ds18b20_driver*/
    char            ds_path[50]; /* DS18B20 采样文件路径 */
    char            buf[128];    /* read() 读数据存储 buffer */
    DIR            *dirp;        /* opendir()打开的文件夹句柄 */
    int             fd =-1;      /* open()打开文件的文件描述符 */
    char           *ptr;         /* 一个字符指针,用来字符串处理 */
    int             rv = 0;      /* 函数返回值,默认设置为成功返回(0) */


    /* 在C语言编程时,进入函数的第一件事应该进行函数参数的合法性检测,检查参数非法输入。
     * 否则调用者"不小心"通过 $temp 传入一个空指针,下面的代码就有可能出现段错误。
     */
    if( !temp )
    {
        return -1;
    }
    
    /*我的驱动 打开 "/sys/class/w1_ds18b20/w1_ds18b20/" 文件夹,如果打开失败则打印错误信息并退出。*/
    if((dirp = opendir(w1_path)) == NULL)
    {
        printf("opendir error: %s\n", strerror(errno));
        return -2;
    }

    snprintf(ds_path, sizeof(ds_path), "%s/temp", w1_path);/*my_ds18b20_driver*/

    /* 接下来打开 DS18B20 的采样文件,如果失败则返回相应的错误码-4。 */
    if( (fd=open(ds_path, O_RDONLY)) < 0 )
    {
        printf("open %s error: %s\n", ds_path, strerror(errno));
        return -4;
    }

    /* 读取文件中的内容将会触发 DS18B20温度传感器采样,这里读取文件内容保存到buf中 */
    if(read(fd, buf, sizeof(buf)) < 0)
    {
        printf("read %s error: %s\n", ds_path, strerror(errno));
        
        /* 1, 这里不能直接调用 return -5 直接返回,否则的话前面open()打开的文件描述符就没有关闭。
         * 这里设置 rv 为错误码-5,通过 goto 语句跳转到函数后面统一进行错误处理。
         *
         * 2, 在C语言编程时我们应该慎用goto语句进行"随意"的跳转,因为它会降低代码的可读性。但这里是
         * goto语句的一个非常典型应用,我们经常会用它来对错误进行统一的处理。
         *
         * 3,goto后面的cleanup为标号,它在下面的代码中定义。
         */
        rv = -5;
        goto cleanup;
    }

    /* 采样温度值是在字符串"t="后面,这里我们从buf中找到"t="字符串的位置并保存到ptr指针中 */
    ptr = strstr(buf, "t=");
    if( !ptr )
    {
        printf("ERROR: Can not get temperature\n");
        rv = -6;
        goto cleanup;
    }

    /* 因为此时ptr是指向 "t="字符串的地址(即't'的地址),那跳过2个字节(t=)后面的就是采样温度值 */
    ptr+=2;

    /* 接下来我们使用 atof() 函数将采样温度值字符串形式,转化成 float 类型。*/
    *temp = atof(ptr)/1000;

    /* 1,在这里我们对函数返回进行集中处理,其中 cleanup 为 goto 语句的标号;
     * 2,在函数退出时,我们应该考虑清楚在前面的代码中做了哪些事,这些事是否需要进行反向操作。如
     *    打开的文件或文件夹是否需要关闭,malloc()分配的内存是否需要free()等。
     * 3, 在最开始我们定义rv并赋初值为0(表示成功)是有原因的,如果前面的代码任何一个地方出现错误,
     *    则会将rv的值修改为相应的错误码,否则rv的值将始终为0(即没有错误发生),这里将统一返回。
     */
cleanup: 
    close(fd);
    return rv;
}

3.6.2.4.Makefile

KERNAL_DIR := /home/zying/imx6ull/bsp/kernel/linux-imx
CROSS_COMPILE := /opt/gcc-arm-11.2-2022.02/bin/arm-none-linux-gnueabihf-
TFTP_DTR := /tftp/zouying
PWD := $(shell pwd)
obj-m := w1_ds18b20.o

modules:
	$(MAKE) -C $(KERNAL_DIR) M=$(PWD) modules
	$(CROSS_COMPILE)gcc w1_sys_App.c -o w1_sys_App
	@make clear
	cp w1_ds18b20.ko w1_sys_App $(TFTP_DTR) -f

clear:
	@rm -f *.o *.cmd *.mod *.mod.c
	@rm -rf *~ core .depend .tmp_versions Module.symvers modules.order -f
	@rm -f .*ko.cmd .*.o.cmd .*.o.d
	@rm -f *.unsigned

clean:
	@rm -f *.ko

3.6.3.程序运行结果

3.6.3.1.加载设备树

在config.txt文件中添加w1.dtbo

# Enable LCD overlay
dtoverlay_lcd=yes

# Enable Camera overlay
#dtoverlay_cam=no

# Enable I2C overlay
dtoverlay_i2c1=yes

# Enable SPI overlay, SPI1 conflict with UART8(NB-IoT/4G module)
dtoverlay_spi1=yes

# Enable UART overlays
dtoverlay_uart=2 3 4 7

# Enable CAN overlays
#dtoverlay_can=1 2

# Enable PWM overlays, PWM8 conflict with UART8(NB-IoT/4G module)
#dtoverlay_pwm=7 8


# Enable extra overlays
#dtoverlay_extra=w1 adc oled_spi
dtoverlay_extra=w1

然后sync一下再reboot开发板,sync就是把刚刚修改的config.txt配置写入uboot,防止修改没有保存成功。

重启开发板后就能看到新增的w1文件启动成功。

3.6.3.2.硬件连接

DS18B20三个引脚VCC和GND连接到相应的位置,DQ连接到设备树配置的地址,这里连接的是GPIO1_IO18。

3.6.3.3.测试结果

在开发板上安装模块

root@igkboard:~/driver/06w1_ds18b20# insmod w1_ds18b20.ko 
root@igkboard:~/driver/06w1_ds18b20# ls
    w1_App  w1_ds18b20.ko

cat /sys/class/w1_ds18b20/w1_ds18b20/ temp 可以看到温度值

在这里第一个w1_ds18b20是类名,第二个w1_ds18b20是设备名,第三个 temp是我们在device下创建的文件名,然后温度值保存在这里。

root@igkboard:~# cd /sys/class/w1_ds18b20/w1_ds18b20/ 
root@igkboard:/sys/class/w1_ds18b20/w1_ds18b20# cat temp 
t=24118

运行app

root@igkboard:~/dirver/06w1_ds18b20# ./w1_App 
temperature : 25.3125 
temperature : 25.3125