当前位置:首页>Linux>Linux(34)-单总线

Linux(34)-单总线

  • 2026-08-25 11:07:50
Linux(34)-单总线

Linux(34)-单总线

背景

在嵌入式 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 需要连接:

  • 温度传感器
  • 湿度传感器
  • 按键
  • EEPROM
  • RTC
  • ADC

传统方式可能需要:

  • I2C:SCL、SDA
  • SPI:SCLK、MOSI、MISO、CS
  • UART:TX、RX

对于资源非常紧张的 MCU 来说,GPIO 和 PCB 走线都可能成为问题。于是就产生了一个非常朴素的需求:

能不能只使用一根数据线,同时连接多个低速外设?

1-Wire 就是为这种场景设计的。

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。

架构分析

核心层 (W1 Core)

  • 职责:维护 Master 与 Slave 设备链表,提供统一的读写接口(如重置总线、写字节、读字节),并由后台内核线程(w1_process)定期执行总线扫描(Search ROM 过程)。

  • 自动发现机制:单总线设备都有一个唯一的 64 位 ROM ID(前 8 位为 Family ID,如 DS18B20 的 0x28)。内核扫描到设备后,根据 Family ID 匹配对应的 Slave 驱动程序。

主机控制器层 (W1 Master Driver)

  • 职责:抽象底层硬件对于总线时序的控制。

  • 数据结构: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 的时序等待。

从设备驱动层 (W1 Slave Driver)

  • 职责:解析特定硬件的功能逻辑。
  • 数据结构:struct w1_family 和 struct w1_family_ops。
  • 驱动注册:驱动通过 w1_register_family() 注册其支持的 Family ID。匹配成功后,内核建立 Slave 设备并导出 sysfs 节点。

sysfs 文件系统接口

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 01057 : crc=57 YES
72014b467f ff 01057 t=23125  <-- 温度 23.125 °C

核心函数

用户空间层(User Space)

路径
作用
/sys/bus/w1/devices/<family>-<id>/w1_slave
读取从设备的原始数据(如温度值)
/sys/bus/w1/devices/<family>-<id>/name
设备名称
/sys/bus/w1/devices/<family>-<id>/id
设备 64 位 ROM ID

交互机制:用户态执行 cat w1_slave 时,会触发底层 struct w1_family_ops 中注册的 read_bin 回调函数,最终通过 Master 的硬件操作完成数据读取。

W1 Core(核心层)

核心层是整个子系统的大脑,负责设备发现、匹配、通信调度、Netlink 消息等。以下是核心层的关键结构体:

    1. struct w1_master —— 总线主设备描述符
// 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。

  • 2.struct 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 编码,核心层据此进行设备匹配。

  • 3.struct w1_family + struct w1_family_ops —— 设备家族定义
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_tloff_t); // 二进制数据读取
ssize_t (*write_bin)(struct w1_slave *, constchar *, size_tloff_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 校验码
        };
    };
};
    1. struct w1_bus_type —— 总线类型
struct bus_type w1_bus_type = {
    .name  = "w1",
    .match = w1_match,         // 设备与驱动的匹配函数
    .uevent = w1_uevent,
};

设备层 / 控制器层(W1 Slave + W1 Master)

  • 1.W1 Slave(设备层)—— 以 w1_therm 为例 设备层不需要直接操作总线时序,只需实现 w1_family_ops 的回调即可:
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
};
  • 2.W1 Master(控制器层)—— 以 w1-gpio / ds2490 为例

控制器层的核心结构体是 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
1-Wire 的"触位"操作,读写在同一时间槽完成(最核心接口)
reset_bus
总线复位 + 存在脉冲检测
triplet
搜索算法中读取两位并写入一位(用于构建搜索树)
search
可选,DS2490 等专用芯片可硬件加速搜索
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);

硬件总线层(GPIO / 专有芯片)

这一层不涉及特定结构体,是物理层的实现方式:

方案
特点
典型代表
GPIO 软件模拟
成本低,灵活,但占用 CPU,时序精度依赖 udelay
w1-gpio
专用桥接芯片
硬件加速,支持寄生供电,可靠性高
DS2482(I2C 转 W1)
USB 专用芯片
支持高速通信,硬件搜索加速
DS2490(USB 转 W1)

数据流向总结

用户 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";
    };
};
  • compatible = "w1-gpio" → 用于匹配 w1_gpio_driver
  • gpios → 指定 DQ 数据线所用的 GPIO 引脚

二、驱动加载阶段

内核启动时,三个模块各自完成初始化:

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 驱动
  • 2.1 W1 Core 初始化
// 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 设备都会挂在这条总线上。

  • 2.2 w1_therm 初始化(Slave 设备层)
// 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)在此链表中查找匹配。

  • 2.3 w1-gpio 平台驱动匹配

设备树中的 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()。

w1-gpio probe:Master 控制器层搭建

  • 3.1 解析设备树,获取 GPIO
// 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;
}
  • 3.2 申请 GPIO 资源
// 2. 向内核 GPIO 子系统申请该引脚
    err = devm_gpio_request(&pdev->dev, pdata->pin, "w1");
if (err)
return err;
  • 3.3 填充 w1_bus_master 结构体

这是最关键的一步——将 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;  // 强上拉支持
    }
  • 3.4 GPIO 位操作的底层实现

写一位(通过切换方向模拟开漏):

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.5 向 W1 Core 注册 Master
// 3. 注册到 W1 核心层
    err = w1_add_master_device(master);
if (err)
return err;

    platform_set_drvdata(pdev, master);
return0;
}

四、W1 Core 核心层:创建 Master 实例与搜索线程

  • 4.1 w1_add_master_device() 内部流程
// 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
└── ...
  • 4.2 搜索线程 w1_process()
// 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;
}

  • 4.3 搜索算法:发现 DS18B20

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()
  • 4.4 创建 w1_slave 并匹配 Family 搜索到 ROM 后,回调 w1_slave_found():
// 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)
}
  • 4.5 框架搭建完成后的系统状态
/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                 → (读此文件触发温度读取)

五、数据读取:完整调用链

  • 5.1 用户操作
$ cat /sys/bus/w1/devices/28-0123456789ab/w1_slave
28014b467f ff 0106f : crc=6f YES
28014b467f ff 0106f t=20500
  • 5.2 内核调用链(逐层追踪)
用户空间: 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 0106f : crc=6f YES
28014b467f ff 0106f t=25000
  • 5.3 w1_therm_read() 核心源码
// 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;
}

六、1-Wire 协议层时序细节

以上所有 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)│
 └──────────┘                          └──────────┘
  • 只有一根数据线 DQ 连接到 SoC 的 GPIO_4
  • 4.7kΩ 上拉电阻将 DQ 拉到高电平
  • DS18B20 外部供电模式(VDD 接 3.3V)

二、设备树配置

  • 2.1 设备树源码(DTS)
/ {
    onewire {
        compatible = "my,w1-gpio";               /* 匹配自定义驱动 */
        gpios = <&gpio1 4 GPIO_ACTIVE_HIGH>;     /* GPIO1_IO04 作为 DQ */
        status = "okay";
    };
};
  • 2.2 驱动层代码

一、自定义 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 0106f : crc=6f YES
50014b467f ff 0106f 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")      → 格式化输出                  │
│                                                                 │
└─────────────────────────────────────────────────────────────────┘

最新文章

随机文章

基本 文件 流程 错误 SQL 调试
  1. 请求信息 : 2026-08-25 18:26:12 HTTP/2.0 GET : https://f.mffb.com.cn/a/512195.html
  2. 运行时间 : 0.118974s [ 吞吐率:8.41req/s ] 内存消耗:5,055.98kb 文件加载:140
  3. 缓存信息 : 0 reads,0 writes
  4. 会话信息 : SESSION_ID=e4e845ab597dfe3aed36a26436d6a402
  1. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/public/index.php ( 0.79 KB )
  2. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/autoload.php ( 0.17 KB )
  3. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_real.php ( 2.49 KB )
  4. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/platform_check.php ( 0.90 KB )
  5. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/ClassLoader.php ( 14.03 KB )
  6. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/composer/autoload_static.php ( 4.90 KB )
  7. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper.php ( 8.34 KB )
  8. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/helper.php ( 2.19 KB )
  9. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/helper.php ( 1.47 KB )
  10. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/stubs/load_stubs.php ( 0.16 KB )
  11. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Exception.php ( 1.69 KB )
  12. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Facade.php ( 2.71 KB )
  13. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/deprecation-contracts/function.php ( 0.99 KB )
  14. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap.php ( 8.26 KB )
  15. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/polyfill-mbstring/bootstrap80.php ( 9.78 KB )
  16. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/Resources/functions/dump.php ( 1.49 KB )
  17. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-dumper/src/helper.php ( 0.18 KB )
  18. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/symfony/var-dumper/VarDumper.php ( 4.30 KB )
  19. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/App.php ( 15.30 KB )
  20. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-container/src/Container.php ( 15.76 KB )
  21. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/container/src/ContainerInterface.php ( 1.02 KB )
  22. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/provider.php ( 0.19 KB )
  23. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Http.php ( 6.04 KB )
  24. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Str.php ( 7.29 KB )
  25. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Env.php ( 4.68 KB )
  26. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/common.php ( 0.03 KB )
  27. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/helper.php ( 18.78 KB )
  28. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Config.php ( 5.54 KB )
  29. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/app.php ( 0.95 KB )
  30. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cache.php ( 0.78 KB )
  31. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/console.php ( 0.23 KB )
  32. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/cookie.php ( 0.56 KB )
  33. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/database.php ( 2.48 KB )
  34. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Env.php ( 1.67 KB )
  35. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/filesystem.php ( 0.61 KB )
  36. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/lang.php ( 0.91 KB )
  37. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/log.php ( 1.35 KB )
  38. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/middleware.php ( 0.19 KB )
  39. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/route.php ( 1.89 KB )
  40. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/session.php ( 0.57 KB )
  41. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/trace.php ( 0.34 KB )
  42. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/config/view.php ( 0.82 KB )
  43. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/event.php ( 0.25 KB )
  44. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Event.php ( 7.67 KB )
  45. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/service.php ( 0.13 KB )
  46. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/AppService.php ( 0.26 KB )
  47. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Service.php ( 1.64 KB )
  48. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Lang.php ( 7.35 KB )
  49. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/lang/zh-cn.php ( 13.70 KB )
  50. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/Error.php ( 3.31 KB )
  51. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/RegisterService.php ( 1.33 KB )
  52. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/services.php ( 0.14 KB )
  53. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/PaginatorService.php ( 1.52 KB )
  54. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ValidateService.php ( 0.99 KB )
  55. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/service/ModelService.php ( 2.04 KB )
  56. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Service.php ( 0.77 KB )
  57. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Middleware.php ( 6.72 KB )
  58. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/initializer/BootService.php ( 0.77 KB )
  59. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Paginator.php ( 11.86 KB )
  60. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-validate/src/Validate.php ( 63.20 KB )
  61. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/Model.php ( 23.55 KB )
  62. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Attribute.php ( 21.05 KB )
  63. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/AutoWriteData.php ( 4.21 KB )
  64. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/Conversion.php ( 6.44 KB )
  65. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/DbConnect.php ( 5.16 KB )
  66. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/ModelEvent.php ( 2.33 KB )
  67. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/concern/RelationShip.php ( 28.29 KB )
  68. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Arrayable.php ( 0.09 KB )
  69. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/contract/Jsonable.php ( 0.13 KB )
  70. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/model/contract/Modelable.php ( 0.09 KB )
  71. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Db.php ( 2.88 KB )
  72. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/DbManager.php ( 8.52 KB )
  73. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Log.php ( 6.28 KB )
  74. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Manager.php ( 3.92 KB )
  75. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerTrait.php ( 2.69 KB )
  76. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/log/src/LoggerInterface.php ( 2.71 KB )
  77. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cache.php ( 4.92 KB )
  78. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/psr/simple-cache/src/CacheInterface.php ( 4.71 KB )
  79. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/helper/Arr.php ( 16.63 KB )
  80. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/driver/File.php ( 7.84 KB )
  81. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/cache/Driver.php ( 9.03 KB )
  82. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/CacheHandlerInterface.php ( 1.99 KB )
  83. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/Request.php ( 0.09 KB )
  84. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Request.php ( 55.78 KB )
  85. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/middleware.php ( 0.25 KB )
  86. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Pipeline.php ( 2.61 KB )
  87. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/TraceDebug.php ( 3.40 KB )
  88. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/middleware/SessionInit.php ( 1.94 KB )
  89. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Session.php ( 1.80 KB )
  90. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/driver/File.php ( 6.27 KB )
  91. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/SessionHandlerInterface.php ( 0.87 KB )
  92. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/session/Store.php ( 7.12 KB )
  93. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Route.php ( 23.73 KB )
  94. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleName.php ( 5.75 KB )
  95. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Domain.php ( 2.53 KB )
  96. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleGroup.php ( 22.43 KB )
  97. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Rule.php ( 26.95 KB )
  98. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/RuleItem.php ( 9.78 KB )
  99. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/route/app.php ( 1.72 KB )
  100. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/Route.php ( 4.70 KB )
  101. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/dispatch/Controller.php ( 4.74 KB )
  102. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/route/Dispatch.php ( 10.44 KB )
  103. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/controller/Index.php ( 4.81 KB )
  104. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/app/BaseController.php ( 2.05 KB )
  105. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/facade/Db.php ( 0.93 KB )
  106. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/connector/Mysql.php ( 5.44 KB )
  107. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/PDOConnection.php ( 52.47 KB )
  108. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Connection.php ( 8.39 KB )
  109. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/ConnectionInterface.php ( 4.57 KB )
  110. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/builder/Mysql.php ( 16.58 KB )
  111. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Builder.php ( 24.06 KB )
  112. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseBuilder.php ( 27.50 KB )
  113. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/Query.php ( 15.71 KB )
  114. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/BaseQuery.php ( 45.13 KB )
  115. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TimeFieldQuery.php ( 7.43 KB )
  116. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/AggregateQuery.php ( 3.26 KB )
  117. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ModelRelationQuery.php ( 20.07 KB )
  118. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ParamsBind.php ( 3.66 KB )
  119. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/ResultOperation.php ( 7.01 KB )
  120. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/WhereQuery.php ( 19.37 KB )
  121. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/JoinAndViewQuery.php ( 7.11 KB )
  122. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/TableFieldInfo.php ( 2.63 KB )
  123. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-orm/src/db/concern/Transaction.php ( 2.77 KB )
  124. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/driver/File.php ( 5.96 KB )
  125. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/LogHandlerInterface.php ( 0.86 KB )
  126. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/log/Channel.php ( 3.89 KB )
  127. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/event/LogRecord.php ( 1.02 KB )
  128. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-helper/src/Collection.php ( 16.47 KB )
  129. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/facade/View.php ( 1.70 KB )
  130. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/View.php ( 4.39 KB )
  131. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Response.php ( 8.81 KB )
  132. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/response/View.php ( 3.29 KB )
  133. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/Cookie.php ( 6.06 KB )
  134. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-view/src/Think.php ( 8.38 KB )
  135. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/framework/src/think/contract/TemplateHandlerInterface.php ( 1.60 KB )
  136. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/Template.php ( 46.61 KB )
  137. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/driver/File.php ( 2.41 KB )
  138. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-template/src/template/contract/DriverInterface.php ( 0.86 KB )
  139. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/runtime/temp/067d451b9a0c665040f3f1bdd3293d68.php ( 11.98 KB )
  140. /yingpanguazai/ssd/ssd1/www/f.mffb.com.cn/vendor/topthink/think-trace/src/Html.php ( 4.42 KB )
  1. CONNECT:[ UseTime:0.000576s ] mysql:host=127.0.0.1;port=3306;dbname=f_mffb;charset=utf8mb4
  2. SHOW FULL COLUMNS FROM `fenlei` [ RunTime:0.000762s ]
  3. SELECT * FROM `fenlei` WHERE `fid` = 0 [ RunTime:0.000313s ]
  4. SELECT * FROM `fenlei` WHERE `fid` = 63 [ RunTime:0.000260s ]
  5. SHOW FULL COLUMNS FROM `set` [ RunTime:0.000533s ]
  6. SELECT * FROM `set` [ RunTime:0.000217s ]
  7. SHOW FULL COLUMNS FROM `article` [ RunTime:0.000571s ]
  8. SELECT * FROM `article` WHERE `id` = 512195 LIMIT 1 [ RunTime:0.001668s ]
  9. UPDATE `article` SET `lasttime` = 1787653572 WHERE `id` = 512195 [ RunTime:0.014811s ]
  10. SELECT * FROM `fenlei` WHERE `id` = 67 LIMIT 1 [ RunTime:0.000298s ]
  11. SELECT * FROM `article` WHERE `id` < 512195 ORDER BY `id` DESC LIMIT 1 [ RunTime:0.000627s ]
  12. SELECT * FROM `article` WHERE `id` > 512195 ORDER BY `id` ASC LIMIT 1 [ RunTime:0.000433s ]
  13. SELECT * FROM `article` WHERE `id` < 512195 ORDER BY `id` DESC LIMIT 10 [ RunTime:0.001056s ]
  14. SELECT * FROM `article` WHERE `id` < 512195 ORDER BY `id` DESC LIMIT 10,10 [ RunTime:0.000940s ]
  15. SELECT * FROM `article` WHERE `id` < 512195 ORDER BY `id` DESC LIMIT 20,10 [ RunTime:0.008567s ]
0.120634s