【RK3568 平台单总线 DS18B20 温度传感器开发】

178 阅读12分钟

在物联网(IoT)应用中,传感器数据采集是实现智能化控制的基础。单总线(1-Wire)作为一种极简通信协议,凭借单根数据线完成数据传输与供电,被广泛应用于温度、湿度等传感器领域。本文将以 RK3568 开发板为载体,结合 DS18B20 温度传感器,详细介绍单总线协议的原理与实战开发流程,提供整合后的完整可运行代码与设备注册逻辑。

一、单总线协议:极简高效的通信方案

1.1 协议概述

单总线协议由 Dallas 公司提出,通过一根信号线实现数据的双向传输与设备供电,极大简化了硬件设计。其核心优势在于: 低成本:仅需一根数据线,大幅减少布线与芯片资源占用。 多设备挂载:单总线上可挂载多个从设备,通过设备唯一 ROM 编码实现寻址。 长距离传输:支持最长 1000 米的通信距离,适用于工业环境。

1.2 通信原理

单总线通信基于主从模式,主设备(如 RK3568)控制总线时序,从设备(DS18B20)响应指令。关键时序包括: 初始化时序:主设备拉低总线至少 480μs,释放后等待从设备回应(低电平 60 - 240μs)。 写时序:主设备通过拉低总线不同时长,区分写 0(60 - 120μs)与写 1(15μs)。 读时序:主设备拉低总线 15μs 后释放,从设备在 60μs 内输出数据。

二、DS18B20:单总线温度传感器的典型应用

DS18B20 是单总线协议的代表性传感器,其特性如下: 高精度测温:测温范围 -55℃~125℃,精度可达 ±0.5℃。 可编程分辨率:支持 9 - 12 位温度分辨率设置。 寄生电源模式:无需外部供电,通过数据线获取能量。 DS18B20 的操作流程为: 初始化总线,确认设备存在。 发送 ROM 命令(如跳过 ROM 匹配0xCC,适用于单设备场景)。 每个单总线从设备(如 DS18B20)内部都集成了一个 64 位 ROM 地址,结构如下:


前 8 位:家族码(Family Code),标识设备类型(如 DS18B20 的家族码为 0x28)。
中间 48 位:唯一序列号(Serial Number),每个设备独一无二。
最后 8 位:CRC 校验码(Cyclic Redundancy Check),用于验证地址的正确性。

发送功能命令(如启动温度转换0x44)。 读取温度数据,解析二进制编码。

三、DS18B20驱动开发

3.1 寄存器介绍

  1. 配置寄存器 在这里插入图片描述
  2. 温度测量寄存器 LS BYTE(最低有效字节,Least Significant Byte) 和 MS BYTE(最高有效字节,Most Significant Byte) S = SIGN 表示符号位 BIT10-BIT4表示整数部分 BIT3-BIT0表示小数部分

在这里插入图片描述 在这里插入图片描述 温度计算方法: 温度>0时(高5位符号位为0),测量的温度*0.0625 以温度125举例:

07D0h转换为10进制 = 2000
2000 * 0.0625 = 125

温度<0时(高5位符号位为1),(测量的温度取反+1)*0.0625,最后加上符号位 以温度-10.125举例:

FF5Eh 取反= A1 + 1 = A2
A2转换为10进制 =  162
162 * 0.0625 = 10.125
最后加上符号位 = -10.125

3.2 ROM指令

在这里插入图片描述 常用的指令:

指令代码功能
搜索F0h主设备确定从设备的数量及其类型
读取33h总线主控读取从设备的64位ROM代码
匹配55h通过匹配的ROM命令后跟一个64位的ROM代码序列,总线主控器可以寻址多点或单点总线上的特定从设备
跳过CCh主设备可以使用此命令同时对总线上的所有设备进行寻址,而无需发送任何ROM代码信息。例如,主设备可以通过发出Skip ROM命令,然后发出Convert T [44h]命令,使总线上的所有DS18B20器件同时执行温度转换。
报警搜索ECh此命令的操作与搜索ROM命令相同,但只有设置了报警标志的从设备会响应。该命令使主设备能够判断最近一次温度转换过程中是否有DS18B20经历了报警状态

3.3 RAM指令

在这里插入图片描述

3.4 读写时序图

在这里插入图片描述

3.5 硬件连接

DS18B20 VCC ---> RK3568 VCC3V3_SYS
DS18B20 GND ---> RK3568 GND
DS18B20 DQ  ---> RK3568 DVP_PWREN0_H_GPIO0_B0

在这里插入图片描述

3.6 设备树(DTS)配置

在 RK3568 的设备树文件kernel/arch/arm64/boot/dts/rockchip/rk3568-evb1-ddr4-v10.dtsi中添加 DS18B20 节点: 在这里插入图片描述

ds18b20_gpio: ds18b20-gpio {  
        compatible = "maxim,ds18b20";
		ds18b20-gpio = <&gpio0 RK_PB0 GPIO_ACTIVE_HIGH>;
		pinctrl-names = "default";
		pinctrl-0 = <&ds18b20_pins>;
		
    };

设置引脚复用功能 在这里插入图片描述

	ds18b20 {
		ds18b20_pins: ds18b20-pins {
			rockchip,pins = <0 RK_PB0 RK_FUNC_GPIO &pcfg_pull_up>;
		};
	};

3.7 内核驱动开发

将单总线核心驱动与 DS18B20 驱动整合在一个文件中:

#include <linux/module.h>
#include <linux/gpio.h>
#include <linux/delay.h>
#include <linux/kernel.h>
#include <linux/init.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/of_gpio.h>
#include <linux/of.h>
#include <linux/platform_device.h>
#include <linux/mod_devicetable.h>
#include <linux/uaccess.h> 

#define DS18B20_DEVICE_NAME "ds18b20"

// 定义ioctl命令
#define DS18B20_IOC_MAGIC  'd'
#define DS18B20_IOC_GET_TEMP _IOR(DS18B20_IOC_MAGIC, 1, int)


static dev_t dev_num;
static struct cdev ds18b20_cdev;
static struct class *ds18b20_class;

// 初始化总线​
int ds18b20_reset(int gpio) {
    gpio_direction_output(gpio, 1);
	gpio_set_value(gpio, 0);
    udelay(600);  
	gpio_set_value(gpio, 1);
    gpio_direction_input(gpio);
    int presence = 0;
	int i;
	while(presence = gpio_get_value(gpio));//当释放总线时,5kΩ上拉电阻将单线总线拉高。当DS18B20检测到上升沿时,它会等待15微秒至60微秒。
	while(presence = !gpio_get_value(gpio));//然后通过将单线总线拉低60微秒至240微秒来发送存在脉冲。
	udelay(480);
	printk(KERN_INFO "ds18b20_reset = %d", presence);
    return presence;
}

// 写一位数据​
void ds18b20_write_bit(int gpio, int bit) {
    
    gpio_direction_output(gpio, 1);
	gpio_set_value(gpio, 0);
	if(bit)
	{
		udelay(10);
		gpio_direction_output(gpio, 1);
	}
	udelay(65);
    gpio_direction_output(gpio, 1);
	udelay(2);
	
}

// 写1字节 (调用8次write_bit)
static void ds18b20_write_byte(int gpio, u8 byte) {
    int i;
    for (i = 0; i < 8; i++) {
        ds18b20_write_bit(gpio, byte & 0x01);
        byte >>= 1;
    }
}

/**
 * ds18b20_read_bit - 从DS18B20读取1位数据
 * 返回: 读取的位 (0 或 1)
 */
static int8_t ds18b20_read_bit(int gpio) {
    int8_t bit = 0;
    
    gpio_direction_output(gpio, 1);
	gpio_set_value(gpio, 0);
    udelay(2);  // 典型值2μs
    
    gpio_direction_input(gpio);
    udelay(10);  // 延时10μs(位于15μs窗口内)
    bit = gpio_get_value(gpio);
	printk(KERN_INFO "ds18b20_read_bit = %d", bit);
    
    //等待位周期结束(总时间≥60μs)
    udelay(60);  // 完成60μs的位周期
    
    return bit;
}

/**
 * ds18b20_read_byte - 从DS18B20读取1字节数据
 * 返回: 读取的字节数据 (8位)
 * 
 * 注意:需确保DS18B20_GPIO_PIN已正确配置
 */
static int8_t ds18b20_read_byte(int gpio) {
    int8_t byte = 0;
    uint8_t i;
    
    // 逐位读取,低位(LSB)优先
    for (i = 0; i < 8; i++) {
        // 读取1位数据
        int8_t bit = ds18b20_read_bit(gpio);
        
        // 组合字节数据(LSB优先)
        byte |= (bit << i);
        
    }
	printk(KERN_INFO "ds18b20_read_byte = %d", byte);
    
    return byte;
}



// 启动温度转换
void ds18b20_convert_temp(int gpio) {
    ds18b20_reset(gpio);
    ds18b20_write_byte(gpio, 0xCC);  // 跳过ROM
    ds18b20_write_byte(gpio, 0x44);  // 启动转换
}

// 读取温度数据
int ds18b20_read_temp(int gpio) {
    
    int8_t lsb, msb;
    int raw_temp;

    
    ds18b20_convert_temp(gpio);
    mdelay(750);  // 等待转换完成

    ds18b20_reset(gpio);
    ds18b20_write_byte(gpio, 0xCC);
    ds18b20_write_byte(gpio, 0xBE);  // 读取暂存器

    lsb = ds18b20_read_byte(gpio);
    msb = ds18b20_read_byte(gpio);
    
    // 组合原始温度值(16位有符号整数)
    raw_temp = (msb << 8) | lsb;
	printk(KERN_INFO "ds18b20_read_temp = %d", raw_temp);
    

    return raw_temp;
}


// 设备驱动与设备树匹配表
static const struct of_device_id ds18b20_of_match[] = {
    { .compatible = "maxim,ds18b20" },
    {},
};


// 驱动探测函数 - 当设备树节点与驱动匹配时调用
static int ds18b20_probe(struct platform_device *pdev) {
    struct device *dev = &pdev->dev;
    int ret;

    // 从设备树获取GPIO配置
    int ds18b20_gpio = of_get_named_gpio_flags(dev->of_node, 
                                           "ds18b20-gpio", 0, NULL);
    if (!gpio_is_valid(ds18b20_gpio)) {
        dev_err(dev, "Failed to get GPIO\n");
        return -ENODEV;
    }

    // 申请GPIO
    ret = devm_gpio_request(dev, ds18b20_gpio, "ds18b20-gpio");
    if (ret) {
        dev_err(dev, "Failed to request GPIO\n");
        return ret;
    }

    // 设置GPIO方向为输入
    gpio_direction_input(ds18b20_gpio);

    /*// 检查DS18B20是否存在
    if (ds18b20_reset(ds18b20_gpio)) {
        dev_err(dev, "DS18B20 not found on GPIO%d\n", ds18b20_gpio);
        return -ENODEV;
    }*/

    dev_info(dev, "DS18B20 found on GPIO%d\n", ds18b20_gpio);
    return 0;
}

// 驱动移除函数
static int ds18b20_remove(struct platform_device *pdev) {
    dev_info(&pdev->dev, "DS18B20 driver removed\n");
    return 0;
}

// 平台驱动结构
static struct platform_driver ds18b20_platform_driver = {
    .probe = ds18b20_probe,
    .remove = ds18b20_remove,
    .driver = {
        .name = "ds18b20",
        .of_match_table = ds18b20_of_match,
        .owner = THIS_MODULE,
    },
};


static int ds18b20_open(struct inode *inode, struct file *filp) {
    return 0;
}

static int ds18b20_release(struct inode *inode, struct file *filp) {
    return 0;
}


static long ds18b20_ioctl(struct file *filp, unsigned int cmd, unsigned long arg) {
    int gpio = of_get_named_gpio_flags(of_find_compatible_node(NULL, NULL, "maxim,ds18b20"),
                                      "ds18b20-gpio", 0, NULL);

    int temperature;
    int ret;

    if (gpio < 0) {
        return -ENODEV;
    }
	
	printk(KERN_INFO "ds18b20_ioctl cmd = %d", cmd);
    switch (cmd) {
        case DS18B20_IOC_GET_TEMP:  // 读取温度
            temperature = ds18b20_read_temp(gpio);
			printk(KERN_INFO "ds18b20_ioctl temperature = %d", temperature);
             ret = copy_to_user((int *)arg, &temperature, sizeof(int));
            if (ret) {
                return -EFAULT;
            }
            return 0;
        default:
            return -EINVAL;
    }
}

static struct file_operations ds18b20_fops = {
   .owner = THIS_MODULE,
   .open = ds18b20_open,
   .release = ds18b20_release,
   .unlocked_ioctl = ds18b20_ioctl,
};

// 驱动初始化函数(修改后)
static int __init ds18b20_driver_init(void) {
	int ret;

    // 注册平台驱动
    ret = platform_driver_register(&ds18b20_platform_driver);
    if (ret) {
        printk(KERN_ERR "Failed to register platform driver\n");
        return ret;
    }

    // 申请设备号
    ret = alloc_chrdev_region(&dev_num, 0, 1, DS18B20_DEVICE_NAME);
    if (ret < 0) {
        printk(KERN_ERR "Failed to allocate device number\n");
        platform_driver_unregister(&ds18b20_platform_driver);
        return ret;
    }

    // 初始化字符设备​
    cdev_init(&ds18b20_cdev, &ds18b20_fops);
    ds18b20_cdev.owner = THIS_MODULE;
    // 添加字符设备到系统​
    ret = cdev_add(&ds18b20_cdev, dev_num, 1);
    if (ret < 0) {
        printk(KERN_ERR "Failed to add cdev\n");
        unregister_chrdev_region(dev_num, 1);
        return ret;
    }
    // 创建设备类​
    ds18b20_class = class_create(THIS_MODULE, DS18B20_DEVICE_NAME);
    if (IS_ERR(ds18b20_class)) {
        ret = PTR_ERR(ds18b20_class);
        printk(KERN_ERR "Failed to create class\n");
        cdev_del(&ds18b20_cdev);
        unregister_chrdev_region(dev_num, 1);
        return ret;

    }
    device_create(ds18b20_class, NULL, dev_num, NULL, DS18B20_DEVICE_NAME);
    printk(KERN_INFO "ds18b20_driver_init\n");
    return 0;
}

// 驱动退出函数(修改后)
static void __exit ds18b20_driver_exit(void) {
	// 销毁设备节点​
    device_destroy(ds18b20_class, dev_num);
    // 销毁设备类​
    class_destroy(ds18b20_class);
    // 从系统移除字符设备​
    cdev_del(&ds18b20_cdev);
    // 释放设备号​
    unregister_chrdev_region(dev_num, 1);
    
    // 注销平台驱动
    platform_driver_unregister(&ds18b20_platform_driver);
}

module_init(ds18b20_driver_init);
module_exit(ds18b20_driver_exit);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("cmy");
MODULE_DESCRIPTION("DS18B20 driver for RK3568");

3.8 用户空间测试程序

#define DS18B20_IOC_MAGIC  'd'
#define DS18B20_IOC_GET_TEMP _IOR(DS18B20_IOC_MAGIC, 1, int)

extern "C"
JNIEXPORT jint JNICALL
Java_com_example_led9_MainActivity_ds18b20(JNIEnv *env, jobject thiz) {
    int fd;
    int temp = 0;
    // 打开设备
    fd = open("/dev/ds18b20", O_RDWR);
    if (fd == -1) {
        LOGD("ds18b20 无法打开设备");
        return -1;
    }

    LOGD("ds18b20 DS18B20_IOC_GET_TEMP = %u", DS18B20_IOC_GET_TEMP);

    while(true){
        int ret = ioctl(fd, DS18B20_IOC_GET_TEMP, &temp);
        if ((temp >> 11) & 0x01) {
            temp = ~temp + 1;
            temp &= ~(0xf8 << 8);
        }
        float value = temp * 0.0625f;
        LOGD("ds18b20 temp  = %d,ret = %d,value = %.4f", temp, ret, value);
        sleep(2);
    }

    close(fd);

    return 0;
}

在这里插入图片描述

四、调试与优化

4.1 常见问题排查

设备无响应:检查硬件连接、上拉电阻与电源,使用示波器观察总线时序。 数据错误:确认 ROM 命令与功能命令是否正确,验证温度分辨率设置。

4.2 性能优化

多设备支持:通过 ROM 匹配命令(如0x55)实现单总线上多个 DS18B20 的独立寻址。 中断优化:使用 RK3568 的 GPIO 中断检测 DS18B20 的响应,减少轮询开销。

五、总结与拓展

通过本文实践,我们在 RK3568 平台上成功实现了 DS18B20 温度传感器的单总线通信。单总线协议的极简设计为物联网设备开发提供了高性价比方案,未来可拓展至湿度传感器、EEPROM 等更多单总线设备。在实际应用中,需结合具体场景优化驱动与通信逻辑,确保数据采集的准确性与实时性。 如果你在开发过程中遇到问题,欢迎在评论区留言交流,也可以尝试将本文的方案应用于智能家居、环境监测等项目中,探索更多可能性!