在嵌入式 Linux 开发中,我们经常会遇到 I2C、SPI、UART、CAN 等总线。
但还有一种非常特殊的总线:1-Wire(单总线) 它的特点非常直接:一根数据线,就可以挂载多个设备。Linux 并不是简单地提供一个“GPIO 读写接口”来操作 1-Wire,而是专门设计了一套 W1(1-Wire)子系统其具体框架如下。

1-Wire 是一种典型的 Master-Slave(主从)总线协议。
+----------------+
| Linux |
| CPU |
+-------+--------+
|
| GPIO
|
=========================+====================== 1-Wire DATA
|
+-----------+-----------+
| | |
DS18B20 DS18B20 DS18B20
Sensor #1 Sensor #2 Sensor #3
它和 I2C 最大的区别之一就是:
I2C:
SCL ───────────────────────────────
SDA ───────────────────────────────
至少需要两根信号线。
1-Wire:
DATA ──────────────────────────────
只需要一根数据线。
严格来说,实际物理连接通常还需要:
DATA
GND
因此 Linux 内核文档也将它描述为:single signal wire plus ground,即一根信号线加地线。
这是理解 1-Wire 最重要的问题。我们先假设你设计一个非常简单的嵌入式设备。CPU 需要连接:
传统方式可能需要:
对于资源非常紧张的 MCU 来说,GPIO 和 PCB 走线都可能成为问题。于是就产生了一个非常朴素的需求:
能不能只使用一根数据线,同时连接多个低速外设?
1-Wire 就是为这种场景设计的。
极少的硬件引脚
最典型的 DS18B20:
MCU
|
GPIO
|
DATA
|
+-------- DS18B20
一个 GPIO 就可以作为整个总线的物理接口。
一个总线可以挂多个设备
例如:
+---- DS18B20 #1
|
GPIO -----------+---- DS18B20 #2
|
+---- DS18B20 #3
|
+---- DS18B20 #4
这时候最大的问题就来了:
既然只有一根线,Linux 怎么知道现在应该和哪一个设备通信?
答案就是:每一个 1-Wire 设备都有自己的唯一 ROM ID。
1-Wire 为什么能够区分多个设备?
一个典型的 1-Wire ROM ID 是 64 bit。可以理解为:
┌────────────┬──────────────────────────────┬───────────┐
│ Family Code│ Serial Number │ CRC │
│ 8 bit │ 48 bit │ 8 bit │
└────────────┴──────────────────────────────┴───────────┘
Linux 内核中的 struct w1_reg_num 也是按照来描述这个设备标识的。:
family
id
crc
例如某个 DS18B20:
28-0123456789ab
这里可以理解为:
28
│
└── Family Code
表示 DS18B20 家族
0123456789ab
│
└── 设备唯一序列号
所以当总线上挂了:
28-AAAA...
28-BBBB...
28-CCCC...
Linux 就可以同时管理三个设备。
“既然 1-Wire 是总线,是不是设备树里应该把 DS18B20 直接写进去?1-Wire 的核心特点是:
总线启动
↓
搜索总线
↓
发现设备
↓
得到 ROM ID
↓
根据 Family Code 找驱动
也就是说:1-Wire 设备本身具有动态发现的特点。
Linux W1 Core 会周期性执行总线搜索,发现设备之后,再根据设备 Family Code 匹配对应的 family driver。

职责:维护 Master 与 Slave 设备链表,提供统一的读写接口(如重置总线、写字节、读字节),并由后台内核线程(w1_process)定期执行总线扫描(Search ROM 过程)。
自动发现机制:单总线设备都有一个唯一的 64 位 ROM ID(前 8 位为 Family ID,如 DS18B20 的 0x28)。内核扫描到设备后,根据 Family ID 匹配对应的 Slave 驱动程序。
职责:抽象底层硬件对于总线时序的控制。
数据结构:struct w1_bus_master 包含了具体的操作函数指针(Reset、Read Bit、Write Bit 或 Read Byte、Write Byte)。
实现方式:
软件模拟 (Bit-Banging):最常见的方式是使用 GPIO 模拟时序(驱动为 w1-gpio),对定时精度要求极高(微秒级)。
硬件控制器:使用专用芯片(如 DS2482 I2C-to-W1 或 DS2490 USB-to-W1)卸载 CPU 的时序等待。
Linux W1 架构将设备信息暴露在 /sys/bus/w1/ 目录下:
/sys/bus/w1/masters/w1_bus_master1/:展示 Master 状态,可手动写入触发搜寻(如 echo 1 > w1_master_search)。
/sys/bus/w1/devices/:列出所有挂载在总线上的设备,目录名通常为 <family_id>-<64bit_unique_id>(例如 28-0000080172bf)。
读取数据:以 DS18B20 为例,直接读取设备目录下的 w1_slave 文件即可获得原始数据与解析出的温度值:
cat /sys/bus/w1/devices/28-0000080172bf/w1_slave
# 输出示例:
# 72014b467f ff 0e 1057 : crc=57 YES
# 72014b467f ff 0e 1057 t=23125 <-- 温度 23.125 °C
/sys/bus/w1/devices/<family>-<id>/w1_slave | |
/sys/bus/w1/devices/<family>-<id>/name | |
/sys/bus/w1/devices/<family>-<id>/id |
交互机制:用户态执行 cat w1_slave 时,会触发底层 struct w1_family_ops 中注册的 read_bin 回调函数,最终通过 Master 的硬件操作完成数据读取。
核心层是整个子系统的大脑,负责设备发现、匹配、通信调度、Netlink 消息等。以下是核心层的关键结构体:
// include/linux/w1.h
structw1_master {
structlist_headw1_master_entry;// 链入全局 master 列表
structmodule *owner;// 模块所有者
char name[W1_MAXNAMELEN]; // 总线名称(如 "w1_bus_master1")
structmutexlist_mutex;// 保护从设备列表的互斥锁
structlist_headslist;// 该 master 下挂载的所有 slave 链表
int max_slave_count; // 允许挂载的最大从设备数
int slave_count; // 当前已发现的从设备数
int slave_ttl; // slave 生存时间(未找到则递减,归零则移除)
u32 id; // master 唯一标识(总线编号)
int search_count; // 搜索次数配置(-1 表示无限循环)
atomic_t refcnt; // 引用计数
void *priv; // 私有数据指针
int enable_pullup; // 是否启用强上拉
int pullup_duration; // 强上拉持续时间(ms)
structtask_struct *thread;// 搜索线程(周期性执行 0xF0 搜索命令)
structmutexmutex;// master 操作互斥锁
structmutexbus_mutex;// 总线访问互斥锁
structdevicedev;// 内嵌的 Linux device 对象
structw1_bus_master *bus_master;// 指向底层硬件控制器的操作接口
u32 seq; // 事件序列号
};
核心职责:每个注册的 Master 对应一个 w1_master 实例。核心层会为其创建一个内核线程(w1_search_process),周期性地向总线发送搜索命令(0xF0),发现新设备后创建对应的 w1_slave。
structw1_slave {
structmodule *owner;// 模块所有者
char name[W1_MAXNAMELEN]; // ASCII 格式的设备 ID(如 "28-000005e2d31b")
structlist_headw1_slave_entry;// 链入 master->slist 的节点
structw1_reg_numreg_num;// 设备注册编号(family + id + crc)
atomic_t refcnt; // 引用计数
int ttl; // 生存时间计数器
unsignedlong flags; // 状态标志(W1_SLAVE_ACTIVE / W1_SLAVE_DETACH)
structw1_master *master;// 所属的 master
structw1_family *family;// 所属的设备家族
void *family_data; // 家族驱动的私有数据
structdevicedev;// 内嵌的 Linux device(用于 sysfs 注册)
};
核心职责:代表总线上的一个物理从设备。其 reg_num 中包含了 64 位 ROM 编码,核心层据此进行设备匹配。
structw1_family {
int family; // 家族编号(如 0x28 = DS18B20 温度传感器)
structw1_family_ops *fops;// 家族操作函数集
structmodule *owner;// 模块所有者
};
structw1_family_ops {
int (*add_slave)(struct w1_slave *); // slave 添加时回调
void (*remove_slave)(struct w1_slave *); // slave 移除时回调
ssize_t (*read_bin)(struct w1_slave *, char *, size_t, loff_t); // 二进制数据读取
ssize_t (*write_bin)(struct w1_slave *, constchar *, size_t, loff_t); // 二进制数据写入
};
核心职责:这是 Core 层与 Slave 驱动层的桥梁。当搜索到一个新设备后,核心层读取其 ROM 的 family 字节(如 DS18B20 的 0x28),然后通过 w1_family 注册表查找对应驱动,调用 add_slave 完成绑定。
-4. struct w1_reg_num —— 设备 64 位 ROM 编码
structw1_reg_num {
union {
u64 rn;
struct {
u64 family : 8; // 设备类型(如 0x28=温度,0x26=EEPROM)
u64 id : 48; // 唯一序列号
u64 crc : 8; // CRC 校验码
};
};
};
struct bus_type w1_bus_type = {
.name = "w1",
.match = w1_match, // 设备与驱动的匹配函数
.uevent = w1_uevent,
};
staticstructw1_familyw1_therm_family_DS18B20 = {
.family = W1_THERM_DS18B20, // 0x28
.fops = &w1_therm_fops,
};
staticstructw1_family_opsw1_therm_fops = {
.add_slave = w1_therm_add_slave,
.remove_slave = w1_therm_remove_slave,
.read_bin = w1_therm_read_bin, // 响应用户的 cat w1_slave
};
控制器层的核心结构体是 w1_bus_master:
structw1_bus_master {
void *data; // 私有数据(如 GPIO 号、USB 设备指针)
/* 位级操作(最基础,未实现高层接口时 core 层会用它们模拟) */
u8 (*read_bit)(void *data);
void (*write_bit)(void *data, u8 bit);
/* 读写一体操作(推荐实现,效率更高) */
u8 (*touch_bit)(void *data, u8 bit);
/* 字节级操作 */
u8 (*read_byte)(void *data);
void (*write_byte)(void *data, u8 byte);
/* 块级操作 */
u8 (*read_block)(void *data, u8 *buf, int len);
void (*write_block)(void *data, const u8 *buf, int len);
/* 搜索时的三态操作(用于 1-Wire 搜索算法) */
u8 (*triplet)(void *data, u8 dbit);
/* 总线复位 */
u8 (*reset_bus)(void *data);
/* 强上拉控制(用于寄生供电设备如 DS18B20) */
u8 (*set_pullup)(void *data, int delay);
/* 硬件搜索(专有芯片可硬件加速搜索,GPIO 方案一般不实现) */
void (*search)(void *data, struct w1_master *master,
u8 search_type, w1_slave_found_callback cb);
};
touch_bit | |
reset_bus | |
triplet | |
search | |
set_pullup |
w1-gpio 实现示例(简化的 GPIO 方案)
// drivers/w1/masters/w1-gpio.c
staticvoidw1_gpio_write_bit(void *data, u8 bit){
structw1_gpio_platform_data *pdata = data;
if (bit)
gpio_set_value(pdata->pin, 1); // 释放总线
else
gpio_set_value(pdata->pin, 0); // 拉低总线
udelay(2); // 满足时序要求
}
static u8 w1_gpio_read_bit(void *data){
structw1_gpio_platform_data *pdata = data;
u8 bit = gpio_get_value(pdata->pin);
udelay(10);
return bit;
}
// 注册到 core 层
staticstructw1_bus_masterw1_gpio_bm = {
.data = &pdata,
.read_bit = w1_gpio_read_bit,
.write_bit = w1_gpio_write_bit,
.reset_bus = w1_gpio_reset_bus,
// ...
};
w1_add_master_device(&w1_gpio_bm);
这一层不涉及特定结构体,是物理层的实现方式:
udelay | w1-gpio | |
用户 cat /sys/bus/w1/devices/28-xxx/w1_slave
│
▼
w1_therm_fops.read_bin() ← W1 Slave 设备层
│
▼
w1_read_block() / w1_write_block() ← W1 Core 核心层
│
▼
w1_bus_master->read_block() ← W1 Master 控制器层
│
▼
GPIO拉低/拉高 或 I2C写DS2482 ← 硬件总线层
┌─────────────────────────────────────────────────────┐
│ 用户空间 │
│ cat w1_slave → sysfs read → family_ops->read_bin │
├─────────────────────────────────────────────────────┤
│ W1 Core(核心层) │
│ w1_master ──管理──▶ w1_slave(多个) │
│ │ │ │
│ │ 指向 │ 指向 │
│ ▼ ▼ │
│ w1_bus_master w1_family │
├─────────────────────────────────────────────────────┤
│ W1 Slave 设备层 W1 Master 控制器层 │
│ w1_therm/w1_ds2431 w1_bus_master 实现 │
│ (实现 family_ops) (实现 read_bit/write_bit) │
├─────────────────────────────────────────────────────┤
│ 硬件总线层:GPIO / DS2482(I2C) / DS2490(USB) │
└─────────────────────────────────────────────────────┘
一、硬件连接与设下面以 w1-gpio(纯 GPIO 位模拟)+ DS18B20 温度传感器 为例,从设备树到最终读取温度数据,逐层、逐函数地完整分析整个流程。
物理连接
SoC DS18B20
┌──────┐ ┌──────────┐
│ 3.3V ├──── 上拉电阻 ────── ┤ VDD │
│ │ (4.7kΩ) │ │
│GPIO12├────────────────────┤ DQ │
│ │ │ │
│ GND ├────────────────────┤ GND │
└──────┘ └──────────┘
只有一根数据线(DQ),通过上拉电阻保持高电平,主机和从设备通过开漏方式拉低总线来通信。
设备树节点
/ {
onewire {
compatible = "w1-gpio";
gpios = <&gpio1 12 GPIO_ACTIVE_HIGH>;
status = "okay";
};
};
内核启动时,三个模块各自完成初始化:
w1_core.ko → module_init(w1_init) 注册总线类型
w1_gpio.ko → module_init(w1_gpio_init) 注册 platform_driver
w1_therm.ko → module_init(w1_therm_init) 注册 family 驱动
// drivers/w1/w1.c
staticint __init w1_init(void)
{
int retval;
retval = bus_register(&w1_bus_type); // 注册 w1 总线类型到内核设备模型
if (retval)
goto err_out_exit;
retval = driver_register(&w1_master_driver); // 注册 master 驱动
if (retval)
goto err_out_bus;
retval = driver_register(&w1_slave_driver); // 注册 slave 驱动
if (retval)
goto err_out_master;
return0;
}
此时内核中注册了 bus_type = "w1",后续所有 w1 设备都会挂在这条总线上。
// drivers/w1/slaves/w1_therm.c
staticint __init w1_therm_init(void)
{
int err, i;
// 遍历注册所有支持的温度传感器家族
for (i = 0; i < ARRAY_SIZE(w1_therm_families); ++i) {
err = w1_register_family(w1_therm_families[i].f);
if (err)
w1_therm_families[i].broken = 1;
}
return0;
}
其中 DS18B20 对应的家族注册结构为:
staticstructw1_familyw1_therm_family_DS18B20 = {
.fid = 0x28, // DS18B20 的家族码
.fops = &w1_therm_fops, // 操作函数集
};
staticstructw1_family_opsw1_therm_fops = {
.add_slave = w1_therm_add_slave,
.remove_slave = w1_therm_remove_slave,
};
w1_register_family() 将这个 w1_family 挂入 W1 Core 维护的全局家族链表 w1_families 中。后续搜索到设备时,Core 会根据 ROM 中的 family 字节(0x28)在此链表中查找匹配。
设备树中的 compatible = "w1-gpio" 节点会被内核的 OF(Open Firmware)子系统解析,自动生成一个 platform_device,其 of_node 指向设备树节点。w1_gpio_driver 的匹配表:
staticconststructof_device_idw1_gpio_dt_ids[] = {
{ .compatible = "w1-gpio" },
{ }
};
MODULE_DEVICE_TABLE(of, w1_gpio_dt_ids);
staticstructplatform_driverw1_gpio_driver = {
.driver = {
.name = "w1-gpio",
.of_match_table = of_match_ptr(w1_gpio_dt_ids),
},
.probe = w1_gpio_probe,
.remove = w1_gpio_remove,
};
内核总线匹配成功后,调用 w1_gpio_probe()。
// drivers/w1/masters/w1-gpio.c
staticintw1_gpio_probe(struct platform_device *pdev)
{
structw1_bus_master *master;
structw1_gpio_platform_data *pdata;
int err;
// 1. 从设备树解析 GPIO 引脚号
err = w1_gpio_probe_dt(pdev); // 解析 compatible="w1-gpio" 的节点
if (err < 0)
return err;
pdata = dev_get_platdata(&pdev->dev);
if (!pdata)
return -ENXIO;
w1_gpio_probe_dt() 内部:
staticintw1_gpio_probe_dt(struct platform_device *pdev)
{
structw1_gpio_platform_data *pdata;
structdevice_node *np = pdev->dev.of_node;
int gpio;
pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL);
if (!pdata)
return -ENOMEM;
// 检查是否为开漏模式
if (of_get_property(np, "linux,open-drain", NULL))
pdata->is_open_drain = 1;
// 从设备树 gpios 属性获取引脚号
gpio = of_get_gpio(np, 0); // 获取第 0 个 GPIO → GPIO1_IO12
if (gpio < 0)
return gpio;
pdata->pin = gpio;
// 可选的外部上拉控制引脚
gpio = of_get_gpio(np, 1);
pdata->ext_pullup_enable_pin = gpio;
pdev->dev.platform_data = pdata;
return0;
}
// 2. 向内核 GPIO 子系统申请该引脚
err = devm_gpio_request(&pdev->dev, pdata->pin, "w1");
if (err)
return err;
这是最关键的一步——将 GPIO 操作封装为 1-Wire 协议所需的位级操作函数:
master = devm_kzalloc(&pdev->dev, sizeof(*master), GFP_KERNEL);
if (!master)
return -ENOMEM;
master->data = pdata; // 私有数据(含 GPIO 引脚号)
master->read_bit = w1_gpio_read_bit; // 读一位
if (pdata->is_open_drain) {
// 开漏模式:直接写值
gpio_direction_output(pdata->pin, 1);
master->write_bit = w1_gpio_write_bit_val;
} else {
// 普通模式:通过切换输入/输出来模拟开漏
gpio_direction_input(pdata->pin);
master->write_bit = w1_gpio_write_bit_dir;
master->set_pullup = w1_gpio_set_pullup; // 强上拉支持
}
写一位(通过切换方向模拟开漏):
staticvoidw1_gpio_write_bit_dir(void *data, u8 bit)
{
structw1_gpio_platform_data *pdata = data;
if (bit)
gpio_direction_input(pdata->pin); // 写"1":释放总线,上拉电阻拉高
else
gpio_direction_output(pdata->pin, 0); // 写"0":输出低电平,拉低总线
}
读一位:
static u8 w1_gpio_read_bit(void *data)
{
structw1_gpio_platform_data *pdata = data;
return gpio_get_value(pdata->pin) ? 1 : 0; // 直接读取引脚电平
}
本质:w1-gpio 只实现了 read_bit 和 write_bit 两个最基本的位操作。W1 Core 会基于这两个函数,通过软件模拟出完整的 1-Wire 协议时序(复位、写字节、读字节、搜索等)。
// 3. 注册到 W1 核心层
err = w1_add_master_device(master);
if (err)
return err;
platform_set_drvdata(pdev, master);
return0;
}
// drivers/w1/w1_int.c
intw1_add_master_device(struct w1_bus_master *master)
{
structw1_master *dev;
int retval;
// 1. 分配 w1_master 结构体
dev = kzalloc(sizeof(struct w1_master), GFP_KERNEL);
// 2. 拷贝硬件操作函数指针(从 w1_bus_master 复制到 w1_master 内部)
memcpy(&dev->bus_master, master, sizeof(struct w1_bus_master));
// 3. 初始化从设备链表
INIT_LIST_HEAD(&dev->slist);
// 4. 分配总线编号
dev->id = ++w1_id;
// 5. 生成设备名称(如 "w1_bus_master1")
dev_set_name(&dev->dev, "w1_bus_master%u", dev->id);
// 6. 创建 sysfs 属性文件
retval = w1_create_master_attributes(dev);
// → 在 /sys/bus/w1/devices/w1_bus_master1/ 下创建:
// w1_master_name, w1_master_slaves, w1_master_search 等
// 7. 注册到设备模型(挂到 w1 总线上)
device_register(&dev->dev);
// 8. 启动内核搜索线程 ★
dev->thread = kthread_run(&w1_process, dev, "%s", dev->name);
// 9. 挂入全局 master 链表
list_add(&dev->w1_master_entry, &w1_masters);
return0;
}
此时 sysfs 中出现了:
/sys/bus/w1/devices/w1_bus_master1/
├── w1_master_name → "w1_bus_master1"
├── w1_master_slaves → (暂时为空)
├── w1_master_search → -1(持续搜索)
├── w1_master_slave_count → 0
└── ...
// drivers/w1/w1.c
intw1_process(void *data)
{
structw1_master *dev = data;
while (!kthread_should_stop()) {
// 执行一次总线搜索(0xF0 命令)
w1_search_process(dev, W1_SEARCH);
// 休眠等待(由 timeout 参数控制,默认 10 秒)
msleep_interruptible(w1_timeout * 1000);
}
return0;
}
w1_search_process() 调用 w1_search(),执行 1-Wire ROM 搜索算法(二叉树搜索):
// drivers/w1/w1.c
voidw1_search(struct w1_master *dev, u8 search_type, w1_slave_found_callback cb)
{
u8 triplet_ret;
int search_bit, desc_bit;
u8 last_rn[8]; // 上一次搜索到的 ROM
u64 rn = 0; // 当前正在构建的 ROM
int last_zero = 0;
int i;
// 1. 复位总线 → 检测存在脉冲
w1_reset_bus(dev);
// 2. 发送搜索命令(0xF0)
w1_write_8(dev, search_type);
// 3. 逐位搜索(64位 ROM,每位做一次 triplet 操作)
for (i = 0; i < 64; i++) {
// triplet = 读两位(id_bit, comp_bit)+ 写一位(选择方向)
triplet_ret = w1_triplet(dev, search_bit);
// 将读到的位拼入 rn
rn |= ((u64)(triplet_ret & 0x01)) << i;
}
// 4. 搜索完成,得到完整的 64 位 ROM
// 例如: 28-0123456789AB
// ↑ family code (0x28 = DS18B20)
// 5. 回调函数处理发现的设备
cb(dev, rn); → w1_slave_found()
}
w1_triplet() 内部调用链(当 master 只实现了 read_bit/write_bit 时):
w1_triplet()
→ w1_read_bit() // 读 id_bit
→ dev->bus_master->read_bit() → w1_gpio_read_bit()
→ w1_read_bit() // 读 comp_bit
→ dev->bus_master->read_bit() → w1_gpio_read_bit()
→ w1_write_bit() // 写选择位
→ dev->bus_master->write_bit() → w1_gpio_write_bit_dir()
// drivers/w1/w1.c
staticvoidw1_slave_found(struct w1_master *dev, u64 rn)
{
structw1_slave *sl;
structw1_reg_numtmp;
tmp = *((struct w1_reg_num *)&rn);
// tmp.family = 0x28 (DS18B20)
// tmp.id = 0x0123456789AB
// tmp.crc = CRC校验值
// 1. 检查是否已在链表中
sl = w1_slave_search(dev, &tmp);
if (sl) {
sl->ttl = dev->slave_ttl; // 刷新存活计数
return;
}
// 2. 新设备,分配 w1_slave
sl = kzalloc(sizeof(*sl), GFP_KERNEL);
sl->reg_num = tmp;
sl->master = dev;
sl->ttl = dev->slave_ttl;
// 3. 根据 family code 查找已注册的 family 驱动
sl->family = w1_family_get(tmp.family);
// → 在全局链表 w1_families 中查找 fid == 0x28
// → 找到 w1_therm_family_DS18B20(w1_therm_init 时注册的)
// 4. 生成设备名称 "28-0123456789ab"
snprintf(sl->name, sizeof(sl->name), "%02x-%012llx",
tmp.family, (unsignedlonglong)tmp.id);
// 5. 注册到设备模型
sl->dev.bus = &w1_bus_type;
dev_set_name(&sl->dev, "%s", sl->name);
device_register(&sl->dev);
// 6. 调用 family 的 add_slave 回调
if (sl->family && sl->family->fops->add_slave)
sl->family->fops->add_slave(sl);
// → w1_therm_add_slave(sl)
// → 创建 sysfs 文件 w1_slave
// 7. 加入 master 的从设备链表
list_add_tail(&sl->w1_slave_entry, &dev->slist);
dev->slave_count++;
}
w1_therm_add_slave() 创建 sysfs 属性文件:
staticintw1_therm_add_slave(struct w1_slave *sl)
{
// 在 /sys/bus/w1/devices/28-0123456789ab/ 下创建 w1_slave 文件
return device_create_file(&sl->dev, &w1_therm_attr);
// w1_therm_attr = __ATTR(w1_slave, S_IRUGO, w1_therm_read, NULL)
}
/sys/bus/w1/devices/
├── w1_bus_master1/ ← Master 设备
│ ├── w1_master_name → "w1_bus_master1"
│ ├── w1_master_slaves → "28-0123456789ab"
│ ├── w1_master_slave_count → 1
│ └── w1_master_search → -1
│
└── 28-0123456789ab/ ← DS18B20 从设备
├── name → "28-0123456789ab"
├── id → "0123456789ab"
└── w1_slave → (读此文件触发温度读取)
$ cat /sys/bus/w1/devices/28-0123456789ab/w1_slave
28014b467f ff 0c 106f : crc=6f YES
28014b467f ff 0c 106f t=20500
用户空间: cat w1_slave
│
▼ read() 系统调用
VFS / Sysfs 层:
│ sysfs_kf_read()
│ → kobj_attr_show()
│ → w1_therm_read() ← device_attribute 的 show 回调
▼
W1 Slave 设备层 (w1_therm.c):
│ w1_therm_read()
│ ├── mutex_lock(&sl->master->mutex)
│ │
│ ├── ① 复位总线 + 选中该 slave
│ │ w1_reset_select_slave(sl)
│ │ → w1_reset_bus(dev)
│ │ → dev->bus_master->reset_bus()
│ │ → w1_gpio_write_bit_dir(data, 0) // 拉低 ≥480μs
│ │ → gpio_direction_input() // 释放
│ │ → udelay(70) // 等待存在脉冲
│ │ → w1_gpio_read_bit(data) // 检测低电平=有设备
│ │ → w1_write_8(dev, W1_MATCH_ROM) // 发送 0x55
│ │ → 8次 w1_touch_bit() → write_bit/read_bit
│ │ → w1_write_block(dev, sl->reg_num, 8) // 发送64位ROM地址
│ │ → 64次 w1_touch_bit()
│ │
│ ├── ② 发送温度转换命令
│ │ w1_write_8(dev, W1_CONVERT_TEMP) // 发送 0x44
│ │ → 8次 w1_touch_bit()
│ │ → w1_gpio_write_bit_dir() // GPIO时序模拟
│ │
│ ├── ③ 等待转换完成(12位精度约750ms)
│ │ msleep(750)
│ │
│ ├── ④ 再次复位 + 选中该 slave
│ │ w1_reset_select_slave(sl)
│ │ → (同上复位 + Match ROM 流程)
│ │
│ ├── ⑤ 发送读暂存器命令
│ │ w1_write_8(dev, W1_READ_SCRATCHPAD) // 发送 0xBE
│ │
│ ├── ⑥ 读取9字节暂存器数据
│ │ count = w1_read_block(dev, rom, 9)
│ │ → 72次 w1_touch_bit(1) // 每位发读时隙
│ │ → w1_gpio_write_bit_dir(data, 1) // 拉低 >1μs
│ │ → gpio_direction_input() // 释放
│ │ → udelay(10) // 等待从设备驱动
│ │ → w1_gpio_read_bit(data) // 采样电平
│ │
│ ├── ⑦ CRC校验
│ │ crc = w1_calc_crc8(rom, 8)
│ │ if (crc != rom[8]) → 校验失败,重试
│ │
│ ├── ⑧ 温度转换
│ │ w1_convert_temp(rom, sl->family->fid)
│ │ → w1_DS18B20_convert_temp(rom)
│ │ → t = ((rom[1] << 8) | rom[0]) // 原始16位数据
│ │ → t = t * 625 / 100// 转换为毫度
│ │ → 例: rom[0]=0x50, rom[1]=0x05
│ │ → 0x0550 = 1360
│ │ → 1360 * 625 / 100 = 8500 → 不对
│ │ → 实际: t = (rom[1]<<8|rom[0]) → 有符号16位
│ │ → 温度(毫度) = t * 625 / 10
│ │ → 例: 0x0190 = 400 → 400*625/10 = 25000 → 25.000°C
│ │
│ ├── ⑨ 格式化输出
│ │ snprintf(buf, ..., "%02x %02x ... : crc=%02x YES\n", ...)
│ │ snprintf(buf, ..., "%02x %02x ... t=%d\n", ..., temperature)
│ │
│ └── mutex_unlock(&sl->master->mutex)
▼
返回用户空间:
28014b467f ff 0c 106f : crc=6f YES
28014b467f ff 0c 106f t=25000
// drivers/w1/slaves/w1_therm.c(简化版)
staticssize_tw1_therm_read(struct device *device,
struct device_attribute *attr, char *buf)
{
structw1_slave *sl = dev_to_w1_slave(device);
structw1_master *dev = sl->master;
u8 rom[9], crc, verdict;
int i, max_trying = 10;
ssize_t c = PAGE_SIZE;
mutex_lock(&dev->mutex);
retry:
// ① 复位 + 选中目标 slave
if (w1_reset_select_slave(sl)) {
count = 0;
goto out;
}
// ② 发送温度转换命令 0x44
w1_write_8(dev, W1_CONVERT_TEMP);
// ③ 等待转换完成
msleep(750);
// ④ 再次复位 + 选中
if (w1_reset_select_slave(sl)) {
count = 0;
goto out;
}
// ⑤ 发送读暂存器命令 0xBE
w1_write_8(dev, W1_READ_SCRATCHPAD);
// ⑥ 读取 9 字节
if ((count = w1_read_block(dev, rom, 9)) != 9) {
dev_warn(device, "w1_read_block() returned %d instead of 9.\n", count);
}
// ⑦ CRC 校验
crc = w1_calc_crc8(rom, 8);
verdict = (crc == rom[8]);
if (!verdict && max_trying--)
goto retry;
// ⑧ 格式化第一行:原始数据 + CRC
for (i = 0; i < 9; ++i)
c -= snprintf(buf + PAGE_SIZE - c, c, "%02x ", rom[i]);
c -= snprintf(buf + PAGE_SIZE - c, c, ": crc=%02x %s\n",
crc, (verdict) ? "YES" : "NO");
// ⑨ 格式化第二行:温度值
c -= snprintf(buf + PAGE_SIZE - c, c, "t=%d\n",
w1_convert_temp(rom, sl->family->fid));
out:
mutex_unlock(&dev->mutex);
return PAGE_SIZE - c;
}
以上所有 w1_write_8()、w1_read_block() 最终都落到 GPIO 引脚的电平操作上。以一个完整的读时隙为例:
写"1"时隙: 写"0"时隙: 读时隙:
┌──┐ ┌──────────┐ ┌──┐
│ │ │ │ │ │
──┘ └──────────── 60μs └──────────┘──── 60μs └──┘ └─── 60μs
↑ 拉低1~15μs后释放 拉低≥60μs ↑ 拉低>1μs后释放
│ │ │ 在15μs内采样
从设备不拉低 从设备可能拉低 从设备驱动总线电平
复位脉冲:
主机拉低 ≥480μs → 释放 → 等待 15~60μs → 从设备拉低 60~240μs(存在脉冲)→ 主机检测到
┌─ 设备树 ──────────────────────────────────────────────────────────────┐
│ compatible = "w1-gpio" + gpios = <GPIO1_IO12> │
└──────────────────────────────┬────────────────────────────────────────┘
│ OF子系统解析,创建 platform_device
▼
┌─ w1-gpio probe ──────────────────────────────────────────────────────┐
│ of_get_gpio() → gpio_request() → 填充 w1_bus_master │
│ master->read_bit = w1_gpio_read_bit │
│ master->write_bit = w1_gpio_write_bit_dir │
│ w1_add_master_device(master) │
└──────────────────────────────┬────────────────────────────────────────┘
│
▼
┌─ W1 Core: w1_add_master_device() ───────────────────────────────────┐
│ 分配 w1_master → 拷贝 bus_master → 创建 sysfs → kthread_run() │
│ 启动搜索线程 w1_process() │
└──────────────────────────────┬────────────────────────────────────────┘
│ 搜索线程执行
▼
┌─ W1 Core: w1_search_process() ──────────────────────────────────────┐
│ w1_reset_bus() → w1_write_8(0xF0) → w1_triplet() × 64次 │
│ → 得到 ROM = 28-0123456789ab │
│ → w1_slave_found() → 分配 w1_slave │
│ → w1_family_get(0x28) → 匹配到 w1_therm │
│ → w1_therm_add_slave() → 创建 sysfs w1_slave 文件 │
└──────────────────────────────┬────────────────────────────────────────┘
│ 框架搭建完成
▼
┌─ 用户空间: cat w1_slave ────────────────────────────────────────────┐
│ w1_therm_read() │
│ → w1_reset_select_slave() [复位 + Match ROM 0x55 + 64位地址] │
│ → w1_write_8(0x44) [温度转换命令] │
│ → msleep(750) [等待转换] │
│ → w1_reset_select_slave() [再次复位 + Match ROM] │
│ → w1_write_8(0xBE) [读暂存器命令] │
│ → w1_read_block(9字节) [读取温度数据] │
│ → w1_calc_crc8() [CRC校验] │
│ → w1_DS18B20_convert_temp() [原始数据→毫度] │
│ → snprintf("t=25000\n") [格式化输出] │
└──────────────────────────────────────────────────────────────────────┘
SoC (如 i.MX6 / RK3399 / RPi) DS18B20
┌──────────┐ ┌──────────┐
│ 3.3V ├──── 4.7kΩ 上拉电阻 ──────┤ VDD (Pin1)│
│ │ │ │
│ GPIO_4 ├──────────────────────────┤ DQ (Pin2) │
│ │ │ │
│ GND ├──────────────────────────┤ GND (Pin3)│
└──────────┘ └──────────┘
/ {
onewire {
compatible = "my,w1-gpio"; /* 匹配自定义驱动 */
gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>; /* GPIO1_IO04 作为 DQ */
status = "okay";
};
};
一、自定义 Master 控制器驱动(my_w1_master.c)
/*
* my_w1_master.c
* 自定义 1-Wire GPIO Master 控制器驱动
*
* 功能:从设备树获取 GPIO 引脚,封装为 1-Wire 位操作接口,注册到 W1 Core
* 编译:make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
* 加载:sudo insmod my_w1_master.ko
*/
#include<linux/module.h>
#include<linux/platform_device.h>
#include<linux/of.h>
#include<linux/of_gpio.h>
#include<linux/gpio.h>
#include<linux/delay.h>
#include<linux/w1.h>
#include<linux/slab.h>
/* ---------- 私有数据结构 ---------- */
structmy_w1_data {
int pin; /* DQ 数据线对应的 GPIO 编号 */
structw1_bus_master *master;/* 注册到 W1 Core 的 master 描述符 */
};
/* ========== 位级操作实现 ========== */
/*
* 写一个 bit 到 1-Wire 总线
*
* 1-Wire 写时隙:
* 写"0":主机拉低总线 ≥60μs,然后释放
* 写"1":主机拉低总线 1~15μs,然后释放(总线被上拉电阻拉高)
*
* 这里通过切换 GPIO 方向来模拟开漏输出:
* 拉低 = gpio_direction_output(pin, 0) → 输出低电平
* 释放 = gpio_direction_input(pin) → 高阻态,上拉电阻拉高
*/
staticvoidmy_w1_write_bit(void *data, u8 bit)
{
structmy_w1_data *priv = data;
/* 拉低总线,发起写时隙 */
gpio_direction_output(priv->pin, 0);
if (bit) {
/* 写"1":短时拉低(约6μs)后释放 */
udelay(6);
gpio_direction_input(priv->pin); /* 释放,上拉电阻拉高 */
} else {
/* 写"0":长时拉低(约60μs)后释放 */
udelay(60);
gpio_direction_input(priv->pin); /* 释放 */
}
/* 时隙恢复时间(整个时隙至少 60μs,两次时隙间隔至少 1μs) */
udelay(10);
}
/*
* 从 1-Wire 总线读一个 bit
*
* 1-Wire 读时隙:
* 主机拉低总线 >1μs 后释放
* 在 15μs 内采样总线电平:
* 高电平 = 从设备发送"1"
* 低电平 = 从设备发送"0"
*/
static u8 my_w1_read_bit(void *data)
{
structmy_w1_data *priv = data;
u8 bit;
/* 主机拉低,发起读时隙 */
gpio_direction_output(priv->pin, 0);
udelay(2); /* 拉低约 2μs(>1μs 即可) */
/* 释放总线,让从设备驱动电平 */
gpio_direction_input(priv->pin);
udelay(10); /* 等待从设备输出(在 15μs 窗口内) */
/* 采样 */
bit = gpio_get_value(priv->pin) ? 1 : 0;
/* 完成时隙 */
udelay(50);
return bit;
}
/*
* 总线复位 + 存在脉冲检测
*
* 复位序列:
* 1. 主机拉低总线 ≥480μs
* 2. 释放总线
* 3. 等待 15~60μs
* 4. 从设备拉低总线 60~240μs(存在脉冲)
* 5. 主机采样:低电平 = 有设备应答
*
* 返回值:0 = 有设备存在,1 = 无设备
*/
static u8 my_w1_reset_bus(void *data)
{
structmy_w1_data *priv = data;
u8 presence;
/* 1. 拉低总线 ≥480μs */
gpio_direction_output(priv->pin, 0);
udelay(480);
/* 2. 释放总线 */
gpio_direction_input(priv->pin);
/* 3. 等待从设备响应(约 70μs 后采样) */
udelay(70);
/* 4. 采样存在脉冲 */
presence = gpio_get_value(priv->pin) ? 1 : 0;
/* 5. 等待复位序列完成(总计至少 480μs) */
udelay(410);
/* presence=0 表示从设备拉低了总线(有设备) */
return presence;
}
/* ========== 平台驱动框架 ========== */
staticintmy_w1_probe(struct platform_device *pdev)
{
structmy_w1_data *priv;
structdevice_node *np = pdev->dev.of_node;
int gpio_num;
int ret;
/* ---- 第 1 步:从设备树解析 GPIO 引脚 ---- */
gpio_num = of_get_gpio(np, 0);
if (!gpio_is_valid(gpio_num)) {
dev_err(&pdev->dev, "设备树中未找到有效的 GPIO 定义\n");
return -EINVAL;
}
/* ---- 第 2 步:申请 GPIO 资源 ---- */
ret = devm_gpio_request(&pdev->dev, gpio_num, "my_w1_dq");
if (ret) {
dev_err(&pdev->dev, "GPIO %d 申请失败: %d\n", gpio_num, ret);
return ret;
}
/* 初始状态:输入模式(高阻态),总线由上拉电阻保持高电平 */
gpio_direction_input(gpio_num);
/* ---- 第 3 步:分配私有数据 ---- */
priv = devm_kzalloc(&pdev->dev, sizeof(*priv), GFP_KERNEL);
if (!priv)
return -ENOMEM;
priv->pin = gpio_num;
/* ---- 第 4 步:填充 w1_bus_master 结构体 ---- */
priv->master = devm_kzalloc(&pdev->dev,
sizeof(struct w1_bus_master), GFP_KERNEL);
if (!priv->master)
return -ENOMEM;
priv->master->data = priv; /* 私有数据指针 */
priv->master->read_bit = my_w1_read_bit; /* 读一位 */
priv->master->write_bit = my_w1_write_bit; /* 写一位 */
priv->master->reset_bus = my_w1_reset_bus; /* 复位总线 */
/* ---- 第 5 步:注册到 W1 Core ---- */
ret = w1_add_master_device(priv->master);
if (ret) {
dev_err(&pdev->dev, "注册 w1 master 失败: %d\n", ret);
return ret;
}
platform_set_drvdata(pdev, priv);
dev_info(&pdev->dev, "自定义 1-Wire Master 注册成功,GPIO=%d\n",
gpio_num);
return0;
}
staticintmy_w1_remove(struct platform_device *pdev)
{
structmy_w1_data *priv = platform_get_drvdata(pdev);
/* 从 W1 Core 注销 master */
w1_remove_master_device(priv->master);
dev_info(&pdev->dev, "自定义 1-Wire Master 已移除\n");
return0;
}
/* 设备树匹配表 */
staticconststructof_device_idmy_w1_of_match[] = {
{ .compatible = "my,w1-gpio" },
{ }
};
MODULE_DEVICE_TABLE(of, my_w1_of_match);
staticstructplatform_drivermy_w1_driver = {
.driver = {
.name = "my-w1-master",
.of_match_table = my_w1_of_match,
},
.probe = my_w1_probe,
.remove = my_w1_remove,
};
module_platform_driver(my_w1_driver);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XIAOQIAN");
MODULE_DESCRIPTION("Custom 1-Wire GPIO Master Driver");
二、自定义 Slave 设备驱动(my_w1_therm.c)
/*
* my_w1_therm.c
* 自定义 DS18B20 温度传感器 Family 驱动
*
* 功能:注册 family=0x28,实现温度转换和读取
* 编译:make -C /lib/modules/$(uname -r)/build M=$(pwd) modules
* 加载:sudo insmod my_w1_therm.ko
*/
#include<linux/module.h>
#include<linux/device.h>
#include<linux/delay.h>
#include<linux/w1.h>
#include<linux/slab.h>
/* ========== DS18B20 命令定义 ========== */
#define DS18B20_CMD_CONVERT_T 0x44 /* 温度转换命令 */
#define DS18B20_CMD_READ_SCRATCHPAD 0xBE /* 读暂存器命令 */
#define DS18B20_CMD_WRITE_SCRATCHPAD 0x4E /* 写暂存器命令 */
/* 1-Wire 功能命令 */
#define W1_CMD_MATCH_ROM 0x55 /* 匹配 ROM(选中特定设备) */
#define W1_CMD_SEARCH_ROM 0xF0 /* 搜索 ROM */
/* 转换等待时间(12位精度,最大 750ms) */
#define DS18B20_CONV_TIME_MS 750
/* 暂存器大小 */
#define DS18B20_SCRATCHPAD_SIZE 9
/* ========== 私有数据 ========== */
structmy_therm_data {
int resolution; /* 当前精度配置(9/10/11/12 位) */
};
/* ========== 核心读取函数 ========== */
/*
* 从 DS18B20 读取温度
*
* 完整流程:
* 1. 复位总线 + Match ROM 选中目标设备
* 2. 发送 Convert T (0x44) 启动温度转换
* 3. 等待转换完成
* 4. 复位总线 + Match ROM 再次选中
* 5. 发送 Read Scratchpad (0xBE)
* 6. 读取 9 字节暂存器数据
* 7. CRC 校验
* 8. 解析温度值
*/
staticssize_tmy_therm_read(struct device *dev,
struct device_attribute *attr,
char *buf)
{
structw1_slave *sl = dev_to_w1_slave(dev);
structw1_master *master = sl->master;
u8 scratchpad[DS18B20_SCRATCHPAD_SIZE];
u8 crc;
int temperature_mc; /* 毫摄氏度 */
s16 raw_temp;
int retry = 3;
ssize_t count;
mutex_lock(&master->mutex);
retry_read:
/* ---- 步骤 1:复位总线 + 选中目标 slave ---- */
if (w1_reset_select_slave(sl)) {
dev_err(dev, "总线复位或设备选择失败\n");
count = -EIO;
goto out;
}
/* ---- 步骤 2:发送温度转换命令 ---- */
w1_write_8(master, DS18B20_CMD_CONVERT_T);
/* ---- 步骤 3:等待转换完成 ---- */
msleep(DS18B20_CONV_TIME_MS);
/* ---- 步骤 4:再次复位 + 选中 ---- */
if (w1_reset_select_slave(sl)) {
dev_err(dev, "第二次总线复位失败\n");
count = -EIO;
goto out;
}
/* ---- 步骤 5:发送读暂存器命令 ---- */
w1_write_8(master, DS18B20_CMD_READ_SCRATCHPAD);
/* ---- 步骤 6:读取 9 字节暂存器 ---- */
/*
* DS18B20 暂存器格式(9 字节):
* Byte 0: 温度低字节 (TL)
* Byte 1: 温度高字节 (TH)
* Byte 2: TH 寄存器(高温报警阈值)
* Byte 3: TL 寄存器(低温报警阈值)
* Byte 4: 配置寄存器(精度配置)
* Byte 5-7: 保留(0xFF)
* Byte 8: CRC 校验码
*/
w1_read_block(master, scratchpad, DS18B20_SCRATCHPAD_SIZE);
/* ---- 步骤 7:CRC 校验 ---- */
crc = w1_calc_crc8(scratchpad, 8);
if (crc != scratchpad[8]) {
dev_warn(dev, "CRC 校验失败: 计算=0x%02x, 读取=0x%02x\n",
crc, scratchpad[8]);
if (--retry > 0)
goto retry_read;
count = -EIO;
goto out;
}
/* ---- 步骤 8:解析温度值 ---- */
/*
* DS18B20 温度数据格式(12位精度):
* 温度 = (TH << 8 | TL) / 16.0 (摄氏度)
*
* 例: TL=0x50, TH=0x05
* raw = 0x0550 = 1360
* 温度 = 1360 / 16 = 85.0°C
*
* 例: TL=0x90, TH=0x01
* raw = 0x0190 = 400
* 温度 = 400 / 16 = 25.0°C
*
* 负温度处理:raw 为有符号 16 位数
* 例: TL=0x50, TH=0xFF
* raw = (s16)0xFF50 = -176
* 温度 = -176 / 16 = -11.0°C
*/
raw_temp = (s16)((scratchpad[1] << 8) | scratchpad[0]);
/* 转换为毫摄氏度(×1000),再除以 16(12位精度分辨率) */
temperature_mc = (int)raw_temp * 1000 / 16;
/* ---- 步骤 9:格式化输出 ---- */
count = snprintf(buf, PAGE_SIZE,
/* 第一行:原始数据 + CRC 校验结果 */
"%02x %02x %02x %02x %02x %02x %02x %02x %02x "
": crc=%02x YES\n"
/* 第二行:温度值(毫摄氏度) */
"%02x %02x %02x %02x %02x %02x %02x %02x %02x "
"t=%d\n",
scratchpad[0], scratchpad[1],
scratchpad[2], scratchpad[3],
scratchpad[4], scratchpad[5],
scratchpad[6], scratchpad[7],
scratchpad[8], crc,
scratchpad[0], scratchpad[1],
scratchpad[2], scratchpad[3],
scratchpad[4], scratchpad[5],
scratchpad[6], scratchpad[7],
scratchpad[8],
temperature_mc);
out:
mutex_unlock(&master->mutex);
return count;
}
/* ========== sysfs 属性文件 ========== */
/*
* 设备属性:w1_slave
* 用户执行 cat /sys/bus/w1/devices/28-xxxx/w1_slave 时触发 my_therm_read()
*/
staticDEVICE_ATTR(w1_slave, S_IRUGO, my_therm_read, NULL);
/* ========== Family 回调函数 ========== */
/*
* 当 W1 Core 搜索到 family=0x28 的设备时,调用此函数
* 负责创建 sysfs 属性文件
*/
staticintmy_therm_add_slave(struct w1_slave *sl)
{
structmy_therm_data *data;
int ret;
/* 分配私有数据 */
data = kzalloc(sizeof(*data), GFP_KERNEL);
if (!data)
return -ENOMEM;
data->resolution = 12; /* 默认 12 位精度 */
sl->family_data = data;
/* 创建 sysfs 文件:/sys/bus/w1/devices/28-xxxx/w1_slave */
ret = device_create_file(&sl->dev, &dev_attr_w1_slave);
if (ret) {
kfree(data);
return ret;
}
pr_info("my_w1_therm: DS18B20 设备 %s 已添加\n", sl->name);
return0;
}
/*
* 当设备从总线移除时调用
* 负责清理资源
*/
staticvoidmy_therm_remove_slave(struct w1_slave *sl)
{
/* 删除 sysfs 文件 */
device_remove_file(&sl->dev, &dev_attr_w1_slave);
/* 释放私有数据 */
kfree(sl->family_data);
sl->family_data = NULL;
pr_info("my_w1_therm: DS18B20 设备 %s 已移除\n", sl->name);
}
/* ========== Family 注册结构体 ========== */
staticstructw1_family_opsmy_therm_fops = {
.add_slave = my_therm_add_slave,
.remove_slave = my_therm_remove_slave,
};
staticstructw1_familymy_therm_family = {
.fid = 0x28, /* DS18B20 的 family code */
.fops = &my_therm_fops,
};
/* ========== 模块初始化/退出 ========== */
staticint __init my_w1_therm_init(void)
{
int ret;
/* 将 family=0x28 注册到 W1 Core 的全局家族链表中 */
ret = w1_register_family(&my_therm_family);
if (ret) {
pr_err("my_w1_therm: 注册 family 0x28 失败: %d\n", ret);
return ret;
}
pr_info("my_w1_therm: DS18B20 温度传感器驱动已加载 (family=0x28)\n");
return0;
}
staticvoid __exit my_w1_therm_exit(void)
{
/* 从 W1 Core 注销 family */
w1_unregister_family(&my_therm_family);
pr_info("my_w1_therm: DS18B20 温度传感器驱动已卸载\n");
}
module_init(my_w1_therm_init);
module_exit(my_therm_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("XIAOQIAN");
MODULE_DESCRIPTION("Custom DS18B20 1-Wire Temperature Slave Driver");
# 1. 编译内核模块
make
# 2. 加载模块(顺序很重要!)
sudo insmod my_w1_master.ko # 先加载 Master(注册 w1_bus_master)
sudo insmod my_w1_therm.ko # 再加载 Slave(注册 family=0x28)
# 3. 确认设备已被发现
ls /sys/bus/w1/devices/
# 应看到:28-xxxxxxxxxxxx w1_bus_master1
# 4. 读取温度
cat /sys/bus/w1/devices/28-xxxxxxxxxxxx/w1_slave
# 输出示例:
# 50014b467f ff 0c 106f : crc=6f YES
# 50014b467f ff 0c 106f t=25000
# 5. 运行应用层程序
gcc -Wall -o ds18b20_read ds18b20_read.c
sudo ./ds18b20_read
# 6. 卸载模块(逆序)
sudo rmmod my_w1_therm
sudo rmmod my_w1_master
┌─ my_w1_master.ko ──────────────────────────────────────────────┐
│ │
│ my_w1_probe() │
│ ├─ of_get_gpio() ← 从设备树获取 GPIO1_IO04 │
│ ├─ devm_gpio_request() ← 申请 GPIO 资源 │
│ ├─ 填充 w1_bus_master: │
│ │ .data = priv │
│ │ .read_bit = my_w1_read_bit ← GPIO 读电平 │
│ │ .write_bit = my_w1_write_bit ← GPIO 拉高/拉低 │
│ │ .reset_bus = my_w1_reset_bus ← GPIO 复位脉冲 │
│ └─ w1_add_master_device() ──────────────────────┐ │
│ │ │
└──────────────────────────────────────────────────────┼──────────┘
│
▼
┌─ W1 Core (w1_core.ko) ─────────────────────────────────────────┐
│ │
│ w1_add_master_device() │
│ ├─ 分配 w1_master │
│ ├─ 创建 sysfs: /sys/bus/w1/devices/w1_bus_master1/ │
│ └─ kthread_run(w1_process) ← 启动搜索线程 │
│ │
│ w1_process() 循环执行: │
│ ├─ w1_reset_bus() → my_w1_reset_bus() → GPIO 拉低 480μs │
│ ├─ w1_write_8(0xF0) → my_w1_write_bit() × 8 │
│ ├─ w1_triplet() × 64 → my_w1_read_bit/write_bit │
│ ├─ 得到 ROM = 28-xxxxxxxxxxxx │
│ └─ w1_slave_found() │
│ ├─ w1_family_get(0x28) ─────────────────────┐ │
│ │ │ │
│ └─ device_register() → 创建 sysfs 节点 │ │
│ │ │
└──────────────────────────────────────────────────────┼──────────┘
│
▼
┌─ my_w1_therm.ko ───────────────────────────────────────────────┐
│ │
│ my_w1_therm_init() │
│ └─ w1_register_family(&my_therm_family) ← family=0x28 │
│ │
│ W1 Core 匹配到 family=0x28 后回调: │
│ my_therm_add_slave() │
│ └─ device_create_file(w1_slave) │
│ → /sys/bus/w1/devices/28-xxxx/w1_slave │
│ │
│ 用户 cat w1_slave 时触发: │
│ my_therm_read() │
│ ├─ w1_reset_select_slave() → GPIO 时序模拟 │
│ ├─ w1_write_8(0x44) → 温度转换命令 │
│ ├─ msleep(750) → 等待转换 │
│ ├─ w1_reset_select_slave() → 再次选通 │
│ ├─ w1_write_8(0xBE) → 读暂存器命令 │
│ ├─ w1_read_block(9字节) → 读取温度数据 │
│ ├─ w1_calc_crc8() → CRC 校验 │
│ └─ snprintf("t=25000") → 格式化输出 │
│ │
└─────────────────────────────────────────────────────────────────┘
