在野火i.MX6ULL上实现DS18B20的Linux字符设备驱动开发

单总线协议因其简洁的硬件连接和独特的工作机制,在嵌入式系统中广泛应用于温度传感器、身份识别等场景。本文将深入探讨如何在野火i.MX6ULL开发板上为DS18B20温度传感器开发一个完整的Linux字符设备驱动,从协议原理到内核实现,提供一套可落地的技术方案。

1. 单总线协议与DS18B20工作原理

1.1 单总线协议的核心特性

单总线(1-Wire)是Dallas Semiconductor(现为Maxim Integrated)开发的一种异步半双工通信协议,仅需一根数据线(加上地线)即可实现设备间的数据传输。与I2C、SPI等常见总线相比,它具有以下显著特点:

  • 严格的时序要求 :所有通信都基于精确的微秒级时序控制
  • 独特的设备寻址 :每个设备内置全球唯一的64位ROM ID
  • 寄生供电模式 :在特定条件下可省去外部电源线
  • 多设备共享总线 :支持在同一总线上挂载多个设备

DS18B20作为典型的单总线设备,其温度测量范围为-55°C至+125°C,精度可达±0.5°C(-10°C至+85°C范围内),9-12位可编程分辨率。

1.2 DS18B20的通信流程

DS18B20的完整操作包含三个关键阶段:

  1. 初始化序列 (复位脉冲+存在脉冲)
  2. ROM命令 (选择特定设备)
  3. 功能命令 (启动转换、读取数据等)

典型温度读取流程如下表所示:

步骤 操作 说明
1 复位脉冲 主机拉低总线480μs以上
2 存在脉冲 从机回应60-240μs低电平
3 发送SKIP ROM命令(0xCC) 跳过设备寻址(单设备时)
4 发送CONVERT T命令(0x44) 启动温度转换
5 等待转换完成 典型等待时间750ms(12位分辨率)
6 复位脉冲 重新初始化通信
7 发送SKIP ROM命令(0xCC) 跳过设备寻址
8 发送READ命令(0xBE) 读取暂存器内容
9 读取温度数据 连续读取2字节温度值

2. 硬件连接与设备树配置

2.1 野火i.MX6ULL的硬件接口

i.MX6ULL处理器提供了丰富的GPIO资源,我们可以选择任意一个GPIO引脚连接DS18B20的数据线。典型连接方式如下:

i.MX6ULL GPIO1_IO02 ────┬─── DS18B20 DATA
                        │
                      4.7KΩ上拉电阻
                        │
                       VDD(3.3V)

注意:上拉电阻对单总线通信的稳定性至关重要,通常使用4.7KΩ电阻将数据线上拉到3.3V

2.2 设备树节点配置

在Linux内核中,我们需要通过设备树来描述硬件连接关系。对于DS18B20设备,添加如下节点:

/dts-v1/;

/ {
    ds18b20 {
        compatible = "fireimx,ds18b20";
        pinctrl-names = "default";
        pinctrl-0 = <&pinctrl_ds18b20>;
        ds18b20-gpios = <&gpio1 2 GPIO_ACTIVE_HIGH>;
        status = "okay";
    };
};

&iomuxc {
    pinctrl_ds18b20: ds18b20grp {
        fsl,pins = <
            MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x10b0
        >;
    };
};

设备树配置要点说明:

  1. compatible 属性用于驱动匹配,需与驱动代码中保持一致
  2. pinctrl 子节点配置GPIO的复用功能和电气特性
  3. ds18b20-gpios 指定使用的具体GPIO引脚

编译并更新设备树后,可通过以下命令验证节点是否成功加载:

# 查看设备树节点
ls /proc/device-tree/ds18b20

# 检查GPIO分配
cat /sys/kernel/debug/gpio

3. 驱动核心实现

3.1 GPIO模拟单总线时序

由于Linux内核没有原生单总线控制器,我们需要通过GPIO模拟实现协议时序。关键点在于精确控制微秒级延时和电平变化。

/* 复位脉冲检测 */
static int ds18b20_reset(void)
{
    int ret = 1;
    unsigned long flags;
    
    spin_lock_irqsave(&ds18b20_dev.lock, flags);
    gpio_direction_output(ds18b20_dev.gpio, 0);  // 拉低总线
    udelay(480);                                 // 保持480μs以上
    gpio_direction_input(ds18b20_dev.gpio);      // 释放总线
    udelay(70);                                  // 等待15-60μs
    ret = gpio_get_value(ds18b20_dev.gpio);      // 检测存在脉冲
    udelay(410);                                 // 完成复位时序
    spin_unlock_irqrestore(&ds18b20_dev.lock, flags);
    
    return ret;  // 0表示设备存在
}

写时序实现示例:

static void ds18b20_write_bit(uint8_t bit)
{
    unsigned long flags;
    
    spin_lock_irqsave(&ds18b20_dev.lock, flags);
    gpio_direction_output(ds18b20_dev.gpio, 0);  // 拉低总线
    udelay(1);                                   // 保持1μs
    gpio_set_value(ds18b20_dev.gpio, bit);       // 写入bit值
    udelay(60);                                  // 保持60μs
    gpio_direction_input(ds18b20_dev.gpio);      // 释放总线
    spin_unlock_irqrestore(&ds18b20_dev.lock, flags);
}

3.2 字符设备驱动框架

Linux字符设备驱动的标准框架包括以下组件:

  1. 设备号分配 alloc_chrdev_region
  2. 文件操作结构体 file_operations
  3. 设备节点创建 device_create
  4. 平台设备驱动 platform_driver

典型的file_operations实现:

static const struct file_operations ds18b20_fops = {
    .owner = THIS_MODULE,
    .open = ds18b20_open,
    .read = ds18b20_read,
    .release = ds18b20_release,
    .llseek = no_llseek,
};

温度读取函数实现:

static ssize_t ds18b20_read(struct file *filp, char __user *buf, 
                           size_t count, loff_t *ppos)
{
    uint8_t temp_l, temp_h;
    short temp_raw;
    float temperature;
    int ret;
    
    // 启动温度转换
    if (ds18b20_reset()) return -EIO;
    ds18b20_write_byte(0xCC);  // SKIP ROM
    ds18b20_write_byte(0x44);  // CONVERT T
    
    msleep(750);  // 等待转换完成
    
    // 读取温度数据
    if (ds18b20_reset()) return -EIO;
    ds18b20_write_byte(0xCC);  // SKIP ROM
    ds18b20_write_byte(0xBE);  // READ SCRATCHPAD
    
    temp_l = ds18b20_read_byte();  // 温度低字节
    temp_h = ds18b20_read_byte();  // 温度高字节
    
    temp_raw = (temp_h << 8) | temp_l;
    temperature = temp_raw * 0.0625f;  // 转换为实际温度值
    
    if (copy_to_user(buf, &temperature, sizeof(temperature)))
        return -EFAULT;
    
    return sizeof(temperature);
}

4. 驱动测试与性能优化

4.1 用户空间测试程序

编写简单的测试程序验证驱动功能:

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

#define DEV_PATH "/dev/ds18b20"

int main()
{
    int fd = open(DEV_PATH, O_RDONLY);
    if (fd < 0) {
        perror("open device failed");
        return -1;
    }
    
    float temp;
    while (1) {
        if (read(fd, &temp, sizeof(temp)) == sizeof(temp)) {
            printf("Current temperature: %.2f°C\n", temp);
        } else {
            perror("read failed");
        }
        sleep(1);
    }
    
    close(fd);
    return 0;
}

4.2 驱动性能优化策略

在实际应用中,可以考虑以下优化措施:

  1. 延时精度提升

    • 使用高精度定时器(hrtimer)替代udelay
    • 根据CPU频率动态校准延时
  2. 电源管理优化

    // 在设备挂起时关闭传感器
    static int ds18b20_suspend(struct device *dev)
    {
        ds18b20_write_byte(0xCC);  // SKIP ROM
        ds18b20_write_byte(0xB4);  // COPY SCRATCHPAD to EEPROM
        return 0;
    }
    
  3. 多设备支持

    • 实现ROM ID搜索算法(0xF0命令)
    • 为每个设备创建独立的设备节点
  4. 中断驱动设计

    • 利用GPIO中断检测总线状态变化
    • 减少轮询带来的CPU开销

4.3 常见问题排查

开发过程中可能遇到的典型问题及解决方案:

问题现象 可能原因 解决方法
复位失败 时序不准确 检查udelay实现,必要时使用ndelay
读取数据全为0xFF 总线未释放 确保每次操作后正确设置GPIO方向
温度值跳变 电源干扰 增加去耦电容,检查上拉电阻
驱动加载失败 设备树不匹配 检查compatible字符串一致性
权限问题 设备节点权限 配置udev规则或使用chmod

在i.MX6ULL平台上,特别需要注意GPIO配置的电气特性。通过调整pinctrl节点中的参数可以优化信号质量:

fsl,pins = <
    MX6UL_PAD_GPIO1_IO02__GPIO1_IO02 0x1b0b0
>;

其中0x1b0b0参数控制:

  • 驱动强度
  • 压摆率
  • 上拉/下拉配置
  • 输入使能等特性
Logo

智能硬件社区聚焦AI智能硬件技术生态,汇聚嵌入式AI、物联网硬件开发者,打造交流分享平台,同步全国赛事资讯、开展 OPC 核心人才招募,助力技术落地与开发者成长。

更多推荐