在野火i.MX6ULL上玩转单总线:手把手教你为DS18B20编写Linux字符设备驱动
在野火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的完整操作包含三个关键阶段:
- 初始化序列 (复位脉冲+存在脉冲)
- ROM命令 (选择特定设备)
- 功能命令 (启动转换、读取数据等)
典型温度读取流程如下表所示:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 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
>;
};
};
设备树配置要点说明:
compatible属性用于驱动匹配,需与驱动代码中保持一致pinctrl子节点配置GPIO的复用功能和电气特性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字符设备驱动的标准框架包括以下组件:
- 设备号分配 :
alloc_chrdev_region - 文件操作结构体 :
file_operations - 设备节点创建 :
device_create - 平台设备驱动 :
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 驱动性能优化策略
在实际应用中,可以考虑以下优化措施:
-
延时精度提升 :
- 使用高精度定时器(hrtimer)替代udelay
- 根据CPU频率动态校准延时
-
电源管理优化 :
// 在设备挂起时关闭传感器 static int ds18b20_suspend(struct device *dev) { ds18b20_write_byte(0xCC); // SKIP ROM ds18b20_write_byte(0xB4); // COPY SCRATCHPAD to EEPROM return 0; } -
多设备支持 :
- 实现ROM ID搜索算法(0xF0命令)
- 为每个设备创建独立的设备节点
-
中断驱动设计 :
- 利用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参数控制:
- 驱动强度
- 压摆率
- 上拉/下拉配置
- 输入使能等特性
更多推荐

所有评论(0)