时间轴

2025-12-23

init


Linux 驱动笔记

目录 链接
1. Linux 驱动框架
2. Linux 驱动加载逻辑
3. 字符设备基础
4. 并发与竞争
5. 高级字符设备进阶
6. 中断
7. 平台总线
8. 设备树
9. 设备模型
10. 热插拔
11. pinctrl 子系统
12. gpio 子系统
13. 输入子系统
14. 单总线
15. I2C
16. SPI
17. UART
18. PWM
19. RTC
20. Watchdog
21. CAN
22. 网络设备
23. ADC
24. IIO
25. USB
26. LCD

单总线简介

单总线概述

单总线(One-Wire)是一种串行通信协议和硬件总线,用于在电子设备之间传输数据和控制信号。它是由独立的芯片制造商 Dallas Semiconductor 开发的,并且在多种应用中得到了广泛应用。

与 SPI I2C 等串行数据通信方式不同,单总线的特点是只需要一根信号线,既可以传输时钟又可以传输数据,而且数据是双向的。所以单总线具有节省 IO 口,结构简单,便于扩展和维护等特点。

单总线用于各种应用,包括温度传感器,湿度传感器,EEPROM 存储器,时钟等。它在许多领域中得到了广泛应用,例如工业自动化,家庭自动化,物联网和电子设备监测等。本篇使用温度传感器 DS18b20 进行举例学习单总线。

单总线的硬件结构

  1. 信号线:单总线使用一根信号线进行数据传输和通信。这根线被称为数据线,也是提供电源的线路。单总线上的所有设备都连接到这根信号线上。
  2. 上拉电阻:单总线需要一个上拉电阻连接到信号线和电源之间,以确保在没有设备发送数据时,信号线上的电平保持为高电平(逻辑 1)。上拉电阻的值通常在 4.7 千欧姆到 10 千欧姆之间。(GPIO设置成上拉)
  3. 设备:单总线支持多个设备连接到信号线上。每个设备都具有唯一的 64 位地址,通过这个地址来识别和选择通信的目标设备。设备可以是各种类型的传感器,存储器,时钟等。
  4. 处理器:处理器是单总线上的控制器,处理器负责发送命令,读取响应和控制单总线上的从设备

单总线的通信步骤

单总线是主从结构当主机呼叫从机时,从机才会应答,所以主机都必须严格遵循单总线的命令时序。如果命令时序不对,则器件不会响应。

单总线的通信步骤通常包括以下几个阶段。

  1. 初始化:通信开始之前,主设备会发送初始化信号来确保单总线上没有其他设备正在通信。初始化信号是一个特定的序列,通常是将数据线拉低一段时间然后释放。
  2. ROM 操作命令
  3. 功能命令

DS18B20 介绍

参考:

芯片概述

DS18b20 是一种数字温度传感器芯片,提供 9 到 12bit 分辨率的温度测量,可以通过可编程非易失性存储单元实现温度的下限和上限报警。

它是基于单总线通信协议的设备,只需要一根信号线和一根地线。

DS18b20 能够以较高的精度测量温度精确到 0.0625°C。它具有广泛的测量范围,通常介于-55°C 到+125°C 之间。

DS18B20 芯片可以通过单总线从主设备获取供电,也可以通过外部电源进行供电。这使得它在一些低功耗应用中能够灵活选择供电方式。

每个DS18b20 都会有一个全球唯一的 64 位序列号,可以将多个 DS18b20 串联在同一根单总线上进行组网,只需要一个处理器就可以控制分布在大面积区域中的多颗 DS18b20。这种组网方式特别适合 HVAC 环境控制,建筑,设备,粮情测温和工业测温以及过程监测控制等应用领域。

基本性能

DS18B20 是一款数字温度传感器芯片,具有以下基本性能特点:

  • 采用单总线接口仅需一个端口引脚进行通信
  • 每颗芯片具有全球唯一的 64 位的序列号
  • 具有多点分布式测温功能无需外围元器件
  • 可通过数据线供电,供电电压范围为 2.5V~5.5V
  • 测度测量范围为-55°C 到+125°C
  • 在-10°C~ 70°C 范围内精确度为±0.4°C
  • 温度分辨率 9-12 位可选
  • 最高 12 位精度下,温度转换速度小于 750ms
  • 具有用户自定义的非易失性温度报警设置
  • 报警搜索命令识别并标识超过程序设定温度的器件
  • 超强静电保护能力:HBM 8000V MM 800V
  • 可提供贴片的 MSOP8,SOP8 封装和 3 脚的 TO-92、TO-92S 封装。

引脚配置和封装

DS18B20 芯片引脚配置:

  • VDD:供电引脚,用于提供芯片的正电源。
  • DQ:数据引脚,用于单总线通信和数据传输。
  • GND:地引脚,连接芯片的地(负电源)。

DS18B20 芯片可用于不同的封装类型,其中最常见的封装是 TO-92 封装和 TO-92-3 封装。这些封装都是具有三个引脚的小型封装,适用于直插式安装和表面贴装。

TO-92 封装是一种常见的小型塑料封装,引脚按照顺序排列,依次为 VDD、DQ 和 GND。

TO-92-3 封装与 TO-92 封装类似,但引脚顺序略有不同。TO-92-3 封装的引脚顺序为 GND、DQ 和 VDD。

除了这些常见的封装类型,DS18B20 还可以在其他封装类型中使用,例如 SOT-23 封装和TO-263 封装等,这些封装类型可能具有不同的引脚排列和尺寸。如下图所示:

DS18B20封装类型

DC Electrical Characteristics

内部结构

DS18b20 是一种数字温度传感器芯片,其内部结构主要包括以下组成部分,如下图所示:

DS18b20 Block Diagram

  • 温度传感器:DS18B20 内部集成了温度传感器,用于测量环境的温度。传感器通常基于基准电压的变化来检测温度,并将其转换为数字信号。
  • A/D 转换器:DS18B20 芯片内部包含了一种模数转换器(A/D 转换器),用于将传感器测量到的模拟温度值转换为相应的数字表示。这使得温度数据能够以数字形式进行处理和传输。
  • 存储器:DS18B20 芯片还具有内部存储器,用于存储配置信息和温度测量结果。存储器可以存储唯一的 64 位地址、温度分辨率和其他相关设置。
  • 控制逻辑:DS18B20 芯片包含了控制逻辑电路,用于管理温度测量、通信和其他相关功能。控制逻辑协调各个部分的操作,并与主设备进行通信。
  • 单总线接口:DS18B20 采用了单总线通信协议,其内部结构包括一条数据线和一个上拉电阻,用于与主设备进行通信。单总线接口简化了连接和通信的布线,使得多个 DS18B20 传感器能够方便地串联在同一条总线上。

Memory Map

64-Bit Lasered ROM Code

64-Bit Lasered ROM Code

部分 含义
28 家族代码(DS18B20 固定是 0x28)
FF 1C 46 93 16 03 唯一序列号
5A CRC 校验

Memory

存储器由 9 个字节组成,其分配如下表所示

DS18B20 Memory Map

DS18B20 自带 CRC 校验, CRC 用多项式 X⁸ + X⁵ + X⁴ + 1, 主机必须自己重新算 CRC, 两个 CRC 相等 → 数据正确, 不等 → 说明通信错误, 芯片不会自动阻止错误

寄存器介绍

  • 温度寄存器(Temperature Register):温度寄存器存储了最近一次温度测量的结果。它是一个 16 位的寄存器,包含了温度值的原始数据。通过读取温度寄存器中的数据,并结合分辨率设置,可以计算出实际的温度值。
  • 配置寄存器(Configuration Register):配置寄存器用于设置 DS18B20 的工作模式和温度分辨率。它是一个 8 位的寄存器,每个位对应一个配置选项。通过写入配置寄存器,可以选择温度分辨率、触发温度转换和使用电源供电模式等。

配置寄存器

配置寄存器用于设置 DS18B20 的工作模式和温度分辨率。它是一个 8 位的寄存器,每个位对应一个配置选项。如下图所示

配置寄存器

注意:上电默认设置 R0=1, R1=1(12 位精度)。

精度和转换时间之间有直接关系。配置寄存器的位 7 和位 0 到 4 被器件保留,禁止写入。温度分辨率设置表如下所示:

精度和转换时间的关系

温度寄存器

DS18B20 芯片的温度寄存器是一个 16 位的寄存器,用于存储最近一次温度测量的原始数据。温度寄存器的位布局如下所示:

Temperature Register Format

温度寄存器的最低有效位(LSB)是 24 2^{-4} 位,表示温度的最小精度为 0.0625°C。其他位依次表示更高的温度精度,分别为:

  • 23 2^{-3} (0.125)
  • 22 2^{-2} (0.25)
  • 21 2^{-1} (0.5)
  • 20 2^{0} (1)
  • 21 2^{1} (2)
  • 22 2^{2} (4)
  • 23 2^{3} (8)

DS18B20 芯片的温度寄存器中存储的原始数据可以通过以下步骤计算出实际温度值:

  1. 从温度寄存器读取的 16 位数据可以解释为一个有符号整数,其中最高位(MSB)表示符号位。如果符号位为 0,表示正温度;如果符号位为 1,表示负温度。
  2. 取出温度寄存器中的低 11 位(位 4 到位 15),这些位表示温度的绝对值,其中位值为 1 表示该位对应的温度分辨率有效。
  3. 将这 11 位数据与符号位组合成一个有符号整数。
  4. 根据所选择的温度分辨率,将有符号整数乘以相应的分辨率因子,以获得实际的温度值。
  5. 当温度大于 0 时,符号位为 0,测量到的温度值乘以分辨率因子即可得到实际的温度。
  6. 当温度小于 0 时,符号位为 1,测量得到的温度值取反加一再乘以分辨率因子即可得到实际的温度。

举个例子,假设选择了 12 位的温度分辨率,并从温度寄存器读取的数据为 0x1FFF。

0x1FFF 的二进制表示为:0001 1111 1111 1111。最高位为 0,表示正温度。取出低 11 位:111 1111 1111。将这 11 位与符号位组合,得到有符号整数为:0111 1111 1111(对应 0x07FF)。

对于 12 位的温度分辨率,分辨率因子为 0.0625°C。将有符号整数 0x07FF乘以分辨率因子:0x07FF * 0.0625 = 127.9375°C。

因此,从温度寄存器读取的数据 0x1FFF 对应着约 127.94°C 的实际温度值。

指令介绍

根据 DS18B20 的通讯协议,主机(单片机)控制 DS18B20 完成温度转换必须经过三个步骤:

  • 每一次读写之前都要对 DS18B20 进行复位操作
  • 复位成功后发送一条 ROM 指令
  • 最后发送 RAM 指令,这样才能对 DS18B20 进行预定的操作。

复位要求主 CPU 将数据线下拉 500 微秒,然后 释放,当 DS18B20 收到信号后等待 16~60 微秒左右,后发出 60~240 微秒的存在低脉冲,主 CPU 收到此信号表示复位成功。

ROM Command

在主机检测到存在脉冲之后,它可以发送一个 ROM 命令。这些命令作用于每个从设备唯一的 64 位 ROM 编码,使主机在 1-Wire 总线上存在多个设备时,能够选中特定的一个设备。

这些命令还允许主机:

  • 判断总线上有多少设备
  • 确认设备类型
  • 检测是否有设备触发了报警状态

一共有 5 种 ROM 命令,每条命令长度都是 8 位

在发送 DS18B20 的功能命令之前,主机必须先发送一个合适的 ROM 命令。

指令 约定代码 功能
Search ROM F0H 用于确定挂接在同一总线上 DS1820 的个数和识别 64 位 ROM 地址。为操作各器件作好准备。
Read ROM 33H 读 DS1820 温度传感器 ROM 中的编码(即 64 位地址)
Match ROM 55H 发出此命令之后,接着发出 64 位 ROM 编码,访问单总线上与该编码相对应的 DS1820 使之作出响应,为下一步对该 DS1820 的读写作准备。
Skip ROM CCH 忽略 64 位 ROM 地址,直接向 DS1820 发温度变换命令。适用于单片工作。
Alarm Search ECH 执行后只有温度超过设定值上限或下限的片子才做出响应。

Search ROM [F0h]

系统上电后,主机必须识别总线上所有从设备的 ROM 编码,这样才能知道:

  • 有多少设备
  • 每个设备的类型

主机通过一种“排除法”完成这个过程,需要重复执行Search ROM 命令 + 数据交换多次,直到找出所有设备。

如果总线上只有一个设备,可以用更简单的Read ROM [33h]代替 Search ROM。

每完成一次 Search ROM 过程,主机都必须回到通信流程的第 1 步(初始化)。

Read ROM [33h]

这个命令 只能在总线上只有一个从设备时使用

它允许主机直接读取该设备的 64 位 ROM 编码,而不需要执行 Search ROM 过程。

如果总线上有多个设备却使用这个命令:

  • 所有设备会同时响应
  • 会发生数据冲突

Match ROM [55h]

主机发送 Match ROM + 64 位 ROM 编码 就可以精确选中某一个设备。

只有 ROM 编码完全匹配的设备才会响应后续功能命令,其他设备都会等待下一次 reset。

适用于:

  • 多设备总线
  • 单设备总线

Skip ROM [CCh]

这个命令会:

  • 同时选中总线上所有设备
  • 不需要发送 ROM 编码

例如Skip ROM + Convert T [44h] 可以让所有 DS18B20 同时开始温度转换。

注意Skip ROM + Read Scratchpad [BEh]只能在总线上只有一个设备时使用。

如果总线上有多个设备却使用这个命令:

  • 所有设备会同时响应
  • 会发生数据冲突

Alarm Search [ECh]

这个命令和 Search ROM 的操作方式一样,

但只有触发报警标志的设备 才会响应。它用于检测最近一次温度转换中是否有设备超出报警阈值

每完成一次 Alarm Search 过程,主机必须回到初始化步骤。

ROM Commands 时序图

ROM commands 时序图

Function Command

DS18B20 Function Command Set

Convert T [44h]

这个命令启动一次温度测量。

转换完成后:

  • 温度结果写入 scratchpad 的 2 字节温度寄存器
  • 芯片进入低功耗空闲状态

如果是 寄生供电模式

  • 主机在发命令后 10µs 内
  • 必须开启强上拉
  • 持续整个转换时间 tCONV

如果是 外部供电模式

主机可以不断读时间槽:

  • 返回 0 → 还在转换
  • 返回 1 → 转换完成

寄生供电时不能这样查询,因为总线被强上拉。

Write Scratchpad [4Eh]

允许主机写 3 个字节到 scratchpad:

  • 第 1 字节 → TH(byte 2)
  • 第 2 字节 → TL(byte 3)
  • 第 3 字节 → 配置寄存器(byte 4)

数据必须:

  • 低位先传(LSB first)
  • 三个字节必须一次写完再 reset,否则数据可能损坏。

Read Scratchpad [BEh]

主机读取 scratchpad 内容:

  • 从 byte 0 的最低位开始
  • 一直读到 byte 8(CRC)

如果只需要部分数据:

  • 可以随时 reset 终止读取

Copy Scratchpad [48h]

把 scratchpad 的:

1
2
TH / TL / 配置寄存器
(byte 2,3,4)

复制到 EEPROM。

寄生供电模式下:

  • 10µs 内必须开启强上拉
  • 至少持续 10ms

Recall E2 [B8h]

从 EEPROM 读取:

1
TH / TL / 配置

重新加载到 scratchpad。

主机可以查询状态:

  • 0 → 正在恢复
  • 1 → 完成

这个操作在上电时会自动执行,所以一上电 scratchpad 就有有效数据

Read Power Supply [B4h]

检测供电方式:

主机发命令后读一个时间槽:

  • 总线被拉低 → 寄生供电
  • 总线保持高 → 外部供电

Function Commands 时序图

DS18B20 Function Commands Flowchart

操作举例

单总线上只有一个 DS18b20

总线上只有一个 DS18B20,并且它使用寄生供电。主机向该 DS18B20 的 scratchpad 写入 TH、TL 和配置寄存器,然后读取 scratchpad 并重新计算 CRC 来验证数据。之后主机把 scratchpad 的内容复制到 EEPROM。

主机模式 数据(低位先传) 说明
Tx Reset 主机发送复位脉冲
Rx Presence DS18B20 返回存在脉冲
Tx CCh 主机发送 Skip ROM(选中总线上所有设备)
Tx 4Eh 主机发送 Write Scratchpad
Tx 3 字节数据 写入 TH、TL 和配置寄存器
Tx Reset 主机再次复位
Rx Presence DS18B20 返回存在脉冲
Tx CCh Skip ROM
Tx BEh Read Scratchpad
Rx 9 字节数据 读取 scratchpad + CRC 并校验
Tx Reset 主机复位
Rx Presence DS18B20 返回存在脉冲
Tx CCh Skip ROM
Tx 48h Copy Scratchpad
Tx 强上拉 ≥ 10ms 写 EEPROM 期间强上拉供电

单总线上有多个 DS18B20

总线上有多个 DS18B20,并且它们使用寄生供电。主机对其中某一个指定的 DS18B20 启动温度转换,然后读取它的 scratchpad,并重新计算 CRC 来验证数据是否正确。

主机模式 数据(低位先传) 说明
Tx Reset 主机发送复位脉冲
Rx Presence DS18B20 返回存在脉冲
Tx 55h 主机发送 Match ROM 命令
Tx 64 位 ROM 码 主机指定目标 DS18B20
Tx 44h 主机发送 Convert T(开始测温)
Tx 强上拉保持高电平 主机在整个转换时间 tCONV 内提供强上拉供电
Tx Reset 主机再次发送复位脉冲
Rx Presence DS18B20 返回存在脉冲
Tx 55h 主机再次发送 Match ROM
Tx 64 位 ROM 码 再次选中同一设备
Tx BEh 主机发送 Read Scratchpad
Rx 9 字节数据 主机读取 scratchpad(含 CRC),并自行计算 CRC 校验

通信时序

DS18B20 使用一种严格的 1-Wire 通信协议来保证数据完整性。该协议定义了几种信号类型:

  • reset pulse
  • presence pulse
  • write 0
  • write 1
  • read 0
  • read 1

1-Wire 总线上所有数据都是 LSB first(低位先传)

复位时序

1-Wire 总线上的所有通信都以一个 初始化序列 开始。

这个初始化序列包括:

  • 由主机发送的一个 复位脉冲(reset pulse)
  • 随后由从设备发送的 存在脉冲(presence pulse)

存在脉冲的作用是告诉主机:总线上有从设备(例如 DS18B20),并且它们已经准备好通信。

Initialization Procedure—Reset And
Presence Pulses

在初始化过程中:

  1. 主机发送复位脉冲,主机将 1-Wire 总线拉低 至少 480µs
  2. 主机释放总线,进入接收模式,释放后,总线被 5kΩ 上拉电阻拉高
  3. DS18B20 检测到上升沿,它等待 15µs ~ 60µs
  4. DS18B20 发送存在脉冲,将总线拉低 60µs ~ 240µs
  5. 总线由上拉电阻拉高(RESISTOR PULLUP)

读写时序

  • 主机在 写时隙 中向 DS18B20 写数据,
  • 读时隙 中从 DS18B20 读取数据。

在 1-Wire 总线上:每一个时隙只传输 1 bit 数据。

Read/Write Time Slot Timing Diagram

Write Time Slots

写时隙有两种:

  • Write 1(写 1)
  • Write 0(写 0)

主机用:Write 1 时隙写入逻辑 1,Write 0 时隙写入逻辑 0

Write Slots

所有写时隙必须满足:

  • 每个时隙 ≥ 60µs 且 ≤ 120us
  • 相邻时隙之间恢复时间 ≥ 1µs

两种写时隙都由主机:先把 1-Wire 总线拉低开始

  • 写 1 的规则

    • 主机拉低总线后:必须在 15µs 内释放

    • 释放后:5kΩ 上拉电阻会把总线拉高

  • 写 0 的规则

    • 主机拉低总线后:持续保持低电平 ≥ 60µs

DS18B20 会在一个采样窗口检测总线状态:在主机开始写时隙后的 15µs ~ 60µs 之间,如果这段时间:

  • 总线是高电平 → 写入 1
  • 总线是低电平 → 写入 0

Read Time Slots

DS18B20 只能在读时隙里发数据。所以:

  • 主机想读数据,必须主动创建读时隙
  • 从机不能自己说话

这是一种:主机驱动的同步通信

主机在这些命令后会立刻读:

  • Read Scratchpad [BEh]
  • Read Power Supply [B4h]

另外还能用读时隙查询状态:

  • Convert T [44h](温度转换是否完成)
  • Recall E2 [B8h]

Read Slots

读时隙由主机发起:

  1. 主机拉低总线 ≥ 1µs
  2. 主机释放总线
  3. DS18B20 在这个窗口发 0 或 1
    • 发 1 → 什么都不做 → 线被上拉 → 高电平
    • 发 0 → 主动拉低总线,(DS18B20 在时隙末尾释放总线,上拉电阻恢复高电平)

整个 Read Slot 要 ≥ 60µs 长时隙间恢复 ≥ 1µs

因此DS18B20 的数据在读时隙开始后 15µs 内有效,所以主机必须:在 15µs 内完成采样

Master Read1 Timing

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
#include <linux/cdev.h>
#include <linux/slab.h>
#include <linux/gpio/consumer.h>
#include <linux/delay.h>
#include <linux/uaccess.h>

// ROM Command
#define ROM_CMD_SEARCH_ROM 0xF0
#define ROM_CMD_READ_ROM 0x33
#define ROM_CMD_MATCH_ROM 0x55
#define ROM_CMD_SKIP_ROM 0xCC
#define ROM_CMD_ALARM_SEARCH 0xEC

// Function Command
#define FUNC_CMD_CONVERT_T 0x44
#define FUNC_CMD_WRITE_SCRATCHPAD 0x4E
#define FUNC_CMD_READ_SCRATCHPAD 0xBE
#define FUNC_CMD_COPY_SCRATCHPAD 0x48
#define FUNC_CMD_RECALL_E2 0xB8
#define FUNC_CMD_READ_POWER_SUPPLY 0xB4

struct ds18b20 {
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *dev;
struct gpio_desc *gpiod;
struct fasync_struct *fa;
u8 precision;
// wait_queue_head_t wq;
};

int ds18b20_reset(struct gpio_desc *gpiod)
{
int timeout;

// host send reset pulse
gpiod_direction_output(gpiod, 0);
udelay(500); // >= 480 us

// host release the bus
gpiod_direction_input(gpiod);
udelay(60); // 15 us ~ 60 us

// ds18b20 send presence pulse
timeout = 240;
while (gpiod_get_value(gpiod)) { // 60 us ~ 240 us
if (timeout == 0)
goto err;
udelay(1);
timeout--;
}

// resistor pullup
timeout = 240;
while (!gpiod_get_value(gpiod)) { // 60 us ~ 240 us
if (timeout == 0)
goto err;
udelay(1);
timeout--;
}

udelay(480);

return 0;
err:
return -EIO;
}

void ds18b20_write_bit(struct gpio_desc *gpiod, char bit)
{
gpiod_direction_output(gpiod, 0);

if (bit) {
udelay(6);
gpiod_direction_input(gpiod);
udelay(80); // 60 ~ 120
} else {
udelay(86);
gpiod_direction_input(gpiod);
}

udelay(1);
}

void ds18b20_write_byte(struct gpio_desc *gpiod, char byte)
{
int i;
for (i = 0; i < 8; i++)
ds18b20_write_bit(gpiod, (byte >> i) & 0x1);
}

u8 ds18b20_read_bit(struct gpio_desc *gpiod)
{
char bit;

gpiod_direction_output(gpiod, 0);
udelay(2); // 1 us ~ 15 us, as smaller as possible

gpiod_direction_input(gpiod);
udelay(12); // wait for 12 us then read value

bit = gpiod_get_value(gpiod);
udelay(50); // >= 45us

return bit;
}

u8 ds18b20_read_byte(struct gpio_desc *gpiod)
{
int i;
char value = 0;
for (i = 0; i < 8; i++)
value |= ds18b20_read_bit(gpiod) << i;

return value;
}

int ds18b20_open(struct inode *inode, struct file *file)
{
struct ds18b20 *drv_data = container_of(inode->i_cdev, struct ds18b20, cdev);
//struct gpio_desc *gpiod = drv_data->gpiod;

file->private_data = drv_data;
// 精度设置为12
drv_data->precision = 12;

pr_info("%s success, default precision is %d\n", __func__, drv_data->precision);

return 0;
}

int ds18b20_release(struct inode *inode, struct file *file)
{
pr_info("%s\n", __func__);
return 0;
}

ssize_t ds18b20_read(struct file *file, char __user *buf, size_t size, loff_t *offset)
{
char ls_byte, ms_byte;
struct ds18b20 *drv_data = file->private_data;
struct gpio_desc *gpiod = drv_data->gpiod;
int16_t raw_temp;
size_t count = min(sizeof(int16_t), size);
int ret;

// 复位
ret = ds18b20_reset(gpiod);
if (ret < 0) {
pr_err("%s: reset fail\n", __func__);
return ret;
}
// 只有一个ds18b20, 发送Skip ROM
ds18b20_write_byte(gpiod, ROM_CMD_SKIP_ROM);
// Function Command, 发送Convert T
ds18b20_write_byte(gpiod, FUNC_CMD_CONVERT_T);

switch (drv_data->precision) {
case 12:
msleep(750);
break;
case 11:
msleep(375);
break;
case 10:
msleep(188);
break;
case 9:
msleep(94);
break;
default:
pr_err("unsupported precision\n");
return -EFAULT;
}

// 复位
ret = ds18b20_reset(gpiod);
if (ret < 0) {
pr_err("%s: reset fail\n", __func__);
return ret;
}
// 只有一个ds18b20, 发送Skip ROM
ds18b20_write_byte(gpiod, ROM_CMD_SKIP_ROM);
// Function Command, 发送Read Scratchpad
ds18b20_write_byte(gpiod, FUNC_CMD_READ_SCRATCHPAD);
// read val
ls_byte = ds18b20_read_byte(gpiod);
ms_byte = ds18b20_read_byte(gpiod);

raw_temp = (int16_t)(((u16)ms_byte << 8) | ls_byte);

pr_info("raw_temp is %d\n", raw_temp);

if (copy_to_user(buf, &raw_temp, count) != 0)
return -EFAULT;

// TODO CRC

return count;
}

#define DS_CMD_START _IO('D', 0)
#define DS_CMD_SET_PREC _IOW('D', 1, int)
#define DS_CMD_GET_TEMP _IOR('D', 2, int)

long ds18b20_unlocked_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
{
char ls_byte, ms_byte;
struct ds18b20 *drv_data = file->private_data;
struct gpio_desc *gpiod = drv_data->gpiod;
int16_t raw_temp;
int ret;

switch (cmd) {
case DS_CMD_START:
// 复位
ret = ds18b20_reset(gpiod);
if (ret < 0) {
pr_err("%s: ds18b20 reset failed\n", __func__);
return ret;
}
// 只有一个ds18b20, 发送Skip ROM
ds18b20_write_byte(gpiod, ROM_CMD_SKIP_ROM);
// Function Command, 发送Convert T
ds18b20_write_byte(gpiod, FUNC_CMD_CONVERT_T);

switch (drv_data->precision) {
case 12:
msleep(750);
break;
case 11:
msleep(375);
break;
case 10:
msleep(188);
break;
case 9:
msleep(94);
break;
default:
pr_err("unsupported precision\n");
return -EFAULT;
}
break;
case DS_CMD_SET_PREC:
break;
case DS_CMD_GET_TEMP:
// 复位
ret = ds18b20_reset(gpiod);
if (ret < 0) {
pr_err("%s: reset fail\n", __func__);
return ret;
}
// 只有一个ds18b20, 发送Skip ROM
ds18b20_write_byte(gpiod, ROM_CMD_SKIP_ROM);
// Function Command, 发送Read Scratchpad
ds18b20_write_byte(gpiod, FUNC_CMD_READ_SCRATCHPAD);
// read val
ls_byte = ds18b20_read_byte(gpiod);
ms_byte = ds18b20_read_byte(gpiod);

raw_temp = (int16_t)(((u16)ms_byte << 8) | ls_byte);

pr_info("raw_temp is %d\n", raw_temp);

// if (copy_to_user((void *)arg, &raw_temp, sizeof(int16_t)) != 0)
// return -EFAULT;
if(put_user(raw_temp, (int16_t __user *)arg))
return -EFAULT;
break;
default:
pr_err("unknown ioctl cmd\n");
return -EFAULT;
}

return 0;
}

int ds18b20_fasync(int fd, struct file *file, int on)
{
struct ds18b20 *drv_data = file->private_data;
return fasync_helper(fd, file, on, &drv_data->fa);
}

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

static int ds18b20_probe(struct platform_device *pdev)
{
int ret;
//struct device *dev = &pdev->dev;
struct ds18b20 *ds18b20;

ds18b20 = kzalloc(sizeof(*ds18b20), GFP_KERNEL);
if (!ds18b20) {
ret = -ENOMEM;
goto err_kzalloc;
}

ret = alloc_chrdev_region(&ds18b20->dev_num, 0, 1, "ds18b20");
if (ret < 0)
goto err_chrdev_region;

cdev_init(&ds18b20->cdev, &ds18b20_fops);
ds18b20->cdev.owner = THIS_MODULE;
ret = cdev_add(&ds18b20->cdev, ds18b20->dev_num, 1);
if (ret < 0)
goto err_cdev;

ds18b20->class = class_create(THIS_MODULE, "one-wire");
if (IS_ERR(ds18b20->class)) {
ret = PTR_ERR(ds18b20->class);
goto err_class;
}

// /sys/class/one-wire/ds18b20
ds18b20->dev = device_create(ds18b20->class, NULL, ds18b20->dev_num, NULL, "ds18b20");
if (IS_ERR(ds18b20->dev)) {
ret = PTR_ERR(ds18b20->dev);
goto err_device_create;
}

ds18b20->gpiod = gpiod_get_optional(&pdev->dev, "ds18b20", GPIOD_OUT_HIGH);
if (IS_ERR_OR_NULL(ds18b20->gpiod)) {
if (ds18b20->gpiod == NULL)
ret = -ENODEV;
else if (IS_ERR(ds18b20->gpiod))
ret = PTR_ERR(ds18b20->gpiod);
goto err_get_gpio;
}

platform_set_drvdata(pdev, ds18b20);

return 0;
err_get_gpio:
device_destroy(ds18b20->class, ds18b20->dev_num);
err_device_create:
class_destroy(ds18b20->class);
err_class:
cdev_del(&ds18b20->cdev);
err_cdev:
unregister_chrdev_region(ds18b20->dev_num, 1);
err_chrdev_region:
kfree(ds18b20);
err_kzalloc:
return ret;
}

static int ds18b20_remove(struct platform_device *pdev)
{
struct ds18b20 *ds18b20 = platform_get_drvdata(pdev);

gpiod_put(ds18b20->gpiod);
device_destroy(ds18b20->class, ds18b20->dev_num);
cdev_del(&ds18b20->cdev);
class_destroy(ds18b20->class);
unregister_chrdev_region(ds18b20->dev_num, 1);
kfree(ds18b20);

return 0;
}

const struct of_device_id match_table[] = {
{ .compatible = "even629,ds18b20" },
};

struct platform_driver ds18b20_pdrv = {
.driver = {
.name = "ds18b20",
.owner = THIS_MODULE,
.of_match_table = match_table,

},
.probe = ds18b20_probe,
.remove = ds18b20_remove,
};

module_platform_driver(ds18b20_pdrv);

MODULE_LICENSE("GPL");
MODULE_AUTHOR("even629<asqwgo@outlook.com>");
MODULE_DESCRIPTION("This is a test sample for ds18b20");