提示:温度传感器-DS18B20驱动框架编写
这里不仅仅是温度传感器驱动框架层编写,也基本上是所有驱动框架的基本框架,所以这里有整理、归整知识点的必要
文章目录
- 前言-引言
- [一、 参考资料](#一、 参考资料)
- 二、源码骨架
- 三、头文件逐行解析
- [四、probe 函数解析(核心!)](#四、probe 函数解析(核心!))
-
- [probe 是驱动的「入口函数」](#probe 是驱动的「入口函数」)
- [真实驱动中,probe 要做这些事:](#真实驱动中,probe 要做这些事:)
- [参数 struct platform_device *dev](#参数 struct platform_device *dev)
- 五、设备树匹配表解析
- [六、platform_driver 结构体解析](#六、platform_driver 结构体解析)
- 七、模块初始化函数解析
-
- [__init 宏:](#__init 宏:)
- platform_driver_register
- [八、模块宏 + 协议声明](#八、模块宏 + 协议声明)
- 九、完整执行流程(最重要!)
- 十、这个驱动的价值与扩展方向
-
- 价值
- [扩展成完整 DS18B20 驱动需要加什么?](#扩展成完整 DS18B20 驱动需要加什么?)
-
- [在 probe 中](#在 probe 中)
- [2、注册字符设备 / 杂项设备,提供应用层接口;](#2、注册字符设备 / 杂项设备,提供应用层接口;)
- 3、添加定时器,周期性读取温度。
- 总结
前言-引言

我们会发现,知识点1-6 其实之前都已经学习过或者说都实验过,有一定的基础。 这里唯一的知识点不就是在之前知识点基础上用单总线协议 来 实现文档传奇器 DS18B20 的温度获取和设置嘛。
这里核心点:把之前的知识点 字符设备驱动、平台总线、设备属、解析设备树、gpio子系统、pinctrl 子系统 知识点串联起来,然后理解和使用单总线协议 实现基本功能。
一、 参考资料
这里我们的目标是写一个驱动框架,如上前言描述 用到的是平台总线。 其实就是写一个平台总线的驱动框架。 之前我们有过平台总线相关知识点,平台总线驱动的代码暂未整理,但是其它知识点还是有必要回顾的:
-
总线设备知识点:
platform总线注册流程分析
在总线下注册设备及设备注册流程分析
Linux驱动之platform 总线设备注册流程分析 -
总线驱动相关知识点:
驱动-在自定义总线上创建驱动-分析驱动注册流程
二、源码骨架
对于新手而言,强烈建议先搞清楚基本知识点,如上: 参考资料 是基本知识点,也可以自己了解并掌握基本知识点先
1、源码实现
java
#include <linux/init.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/of.h>
int ds18b20_probe(struct platform_device *dev){
printk("This is probe \n");
return 0;
}
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"},
{},
};
struct platform_driver ds18b20_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ds18b20",
.of_match_table = ds18b20_match_table,
},
.probe = ds18b20_probe,
};
static int __init ds18b20_init(void){
int ret;
ret = platform_driver_register(&ds18b20_driver);
if(ret < 0){
printk("platform_driver_register error\n");
return -1;
}
return 0;
}
static void __exit ds18b20_exit(void){
platform_driver_unregister(&ds18b20_driver);
}
module_init(ds18b20_init);
module_exit(ds18b20_exit);
MODULE_LICENSE("GPL");
2、源码简要分析
驱动入口和出口函数
这两个是最没有含金量的,但是是入口和出口,照葫芦画瓢即可;核心点是其实现方法里面是 注册平台总线驱动和协助平台总线驱动
java
module_init(ds18b20_init);
module_exit(ds18b20_exit);
java
static int __init ds18b20_init(void){
int ret;
ret = platform_driver_register(&ds18b20_driver);
if(ret < 0){
printk("platform_driver_register error\n");
return -1;
}
return 0;
}
static void __exit ds18b20_exit(void){
platform_driver_unregister(&ds18b20_driver);
}
驱动注册函数platform_driver_register 分析
java
ret = platform_driver_register(&ds18b20_driver);
#define platform_driver_register(drv) \
__platform_driver_register(drv, THIS_MODULE)
/**
* __platform_driver_register - register a driver for platform-level devices
* @drv: platform driver structure
* @owner: owning module/driver
*/
int __platform_driver_register(struct platform_driver *drv,
struct module *owner)
{
drv->driver.owner = owner;
drv->driver.bus = &platform_bus_type;
drv->driver.probe = platform_drv_probe;
drv->driver.remove = platform_drv_remove;
drv->driver.shutdown = platform_drv_shutdown;
return driver_register(&drv->driver);
}
如上跟踪代码,最终跟进到了 __platform_driver_register 函数,传递的参数是 platform_driver 指针,所以:platform_driver_register(&ds18b20_driver) 注册参数就是一个结构体的地址, 我们就定义一个 platform_driver 结构体即可
platform_driver 结构体定义

先搞清楚源码结构,对比源码和我们自己写的程序:
.probe对象是什么?int (*probe)(struct platform_device *)返回的是int,所以 我们定义的一个方法返回int值,传入platform_device 结构体对象
java
int ds18b20_probe(struct platform_device *dev){
printk("This is probe \n");
return 0;
}
.driver对象,是一个结构体
看源码对象中的定义:struct device_driver driver;,在实际开发中,我们直接实现的,当然可以定义一个结构体然后赋值,对比源码如下:
java
.driver = {
.owner = THIS_MODULE,
.name = "ds18b20",
.of_match_table = ds18b20_match_table,
},
我们看我们定义的结构体,如下:
java
struct platform_driver ds18b20_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "ds18b20",
.of_match_table = ds18b20_match_table,
},
.probe = ds18b20_probe,
};
所以在结构体对象里面,我们实现了两个核心的platform_driver的对象调用:.driver 和 。probe
platform_driver 结构体对象 .driver 实现
我们在.driver 对象中又实现了什么:
java
.owner = THIS_MODULE,
.name = "ds18b20",
.of_match_table = ds18b20_match_table,


platform_driver 结构体对象 .probe 实现
3、源码小结
代码是一个平台驱动框架:
- 向 Linux 内核注册一个 ds18b20 平台驱动;
- 通过设备树 compatible 属性匹配硬件设备;
- 匹配成功后,内核自动调用 probe 函数(打印一句话);
- 卸载驱动时,注销驱动并释放资源。
三、头文件逐行解析
java
#include <linux/init.h> // 模块初始化/卸载宏(__init / __exit)
#include <linux/module.h> // 模块核心API( GPL协议、模块入口出口等)
#include <linux/platform_device.h> // 平台总线核心结构体(platform_driver、platform_device)
#include <linux/of.h> // 设备树操作API(of_device_id 匹配表)
- 这 4 个是平台驱动 + 设备树必备头文件,缺一不可。
linux/of.h专门用于设备树匹配,没有它驱动无法和设备树绑定。
四、probe 函数解析(核心!)
java
// 设备与驱动匹配成功后,内核自动调用的函数
int ds18b20_probe(struct platform_device *dev){
printk("This is probe \n"); // 内核打印函数(类似printf)
return 0; // 0=成功,负数=失败
}
关键知识点
probe 是驱动的「入口函数」
- 不是我们手动调用,是内核自动调用;
- 触发条件:设备树中的设备 和 驱动 匹配成功。
真实驱动中,probe 要做这些事:
- 申请 GPIO / 中断 / I2C/SPI 资源;
- 注册字符设备 / 杂项设备;
- 初始化硬件(DS18B20 复位、读取 ID);
- 创建 sysfs 节点、注册输入子系统等。
参数 struct platform_device *dev
- 指向匹配成功的硬件设备;
- 可以从中获取设备树配置(寄存器地址、GPIO 号等)。
五、设备树匹配表解析
java
// 设备树匹配表(驱动 <-> 设备树 绑定的关键)
const struct of_device_id ds18b20_match_table[] = {
{.compatible = "ds18b20"}, // 匹配规则:compatible 属性完全一致
{}, // 空结构体,代表表结束(必须写!)
};
核心作用:设备树与驱动的匹配规则
- 内核会遍历设备树,查找 compatible = "ds18b20" 的节点;
- 找到后,就把这个设备和我们的驱动绑定;
- 绑定成功 → 自动调用 probe。
对应设备树节点写法(必须配套)
这里我们暂且 延伸一下如下基本知识点 设备属节点
java
ds18b20@0 {
compatible = "ds18b20"; // 必须和驱动里的字符串完全一样!
pinctrl-names = "default";
gpios = <&gpio 5 GPIO_ACTIVE_HIGH>;
};
六、platform_driver 结构体解析
java
// 平台驱动核心结构体
struct platform_driver ds18b20_driver = {
.driver = { // 基础驱动信息
.owner = THIS_MODULE, // 固定写法:防止驱动被卸载时出现内存错误
.name = "ds18b20", // 驱动名字(sysfs可见,无设备树时也用它匹配)
.of_match_table = ds18b20_match_table, // 绑定设备树匹配表
},
.probe = ds18b20_probe, // 绑定匹配成功后的执行函数
};
关键字段说明
| 字段 | 说明 |
|---|---|
| .owner = THIS_MODULE | 内核必备,必须写,用于模块引用计数; |
| .name = "ds18b20" | 驱动名称,在 /sys/bus/platform/drivers/ 下可见;无设备树时,靠这个名字匹配设备; |
| .of_match_table | 设备树匹配的核心,把上面的匹配表挂到驱动上; |
| .probe | 绑定我们实现的 probe 函数。 |
七、模块初始化函数解析
java
// 模块安装时执行(insmod xx.ko)
static int __init ds18b20_init(void){
int ret;
// 向内核注册平台驱动
ret = platform_driver_register(&ds18b20_driver);
if(ret < 0){
printk("platform_driver_register error\n");
return -1;
}
return 0;
}
__init 宏:
- 告诉内核,这个函数只执行一次,执行完释放内存;
platform_driver_register
- 内核核心 API,把驱动注册到平台总线;
- 注册后,内核立刻开始匹配设备树设备。
八、模块宏 + 协议声明
java
module_init(ds18b20_init); // 注册模块入口函数
module_exit(ds18b20_exit); // 注册模块出口函数
MODULE_LICENSE("GPL"); // 声明开源协议(必须写,否则内核报错)
- module_init:指定 insmod 时执行哪个函数;
- module_exit:指定 rmmod 时执行哪个函数;
- MODULE_LICENSE("GPL"):强制要求,没有会触发内核污染警告。
九、完整执行流程(最重要!)
java
insmod ds18b20.ko
↓
ds18b20_init() 执行
↓
platform_driver_register() 注册驱动到平台总线
↓
内核遍历设备树
↓
找到 compatible = "ds18b20" 的设备
↓
设备与驱动匹配成功
↓
内核自动调用 ds18b20_probe()
↓
打印 This is probe
↓
驱动加载完成
=====================
rmmod ds18b20.ko
↓
ds18b20_exit() 执行
↓
platform_driver_unregister() 注销驱动
↓
驱动卸载完成
十、这个驱动的价值与扩展方向
价值
这是Linux 设备树 + 平台驱动的标准模板,几乎所有 ARM 板载外设驱动(GPIO、I2C、SPI、传感器)都用这套框架。
扩展成完整 DS18B20 驱动需要加什么?
在 probe 中
- 从设备树获取 GPIO 编号;
- 申请 GPIO;
- 实现单总线(1-Wire)时序;
- 实现温度读取函数;
2、注册字符设备 / 杂项设备,提供应用层接口;
3、添加定时器,周期性读取温度。
总结
- 这是Linux Platform 平台驱动最简框架,支持设备树匹配;
- probe 函数是驱动的灵魂,匹配成功自动执行;
- of_device_id 是驱动与设备树绑定的桥梁,compatible 必须完全一致;
- 核心 API:platform_driver_register(注册)、platform_driver_unregister(注销);
- 适合所有片上外设、简单传感器的驱动开发。
- 最后:个人理解,还是需要理解源码,学会查看源码,学会分析基本源码。此文就是一个平台总线的驱动骨架,驱动代码中最基础的内容,后续可以拿着直接复用 , 理解并掌握特别重要。