嵌入式 Linux 驱动开发学习笔记:内核定时器、platform 总线与 I²C 驱动

一、前言

随着驱动开发逐渐深入,仅仅掌握 GPIO 和中断还不够。实际项目中,驱动可能需要周期性检查设备状态、延迟执行某些操作,或者通过统一的设备模型与硬件进行匹配。

Linux 内核提供了定时器机制和多种设备驱动框架。其中,platform 总线常用于描述片上设备,I²C 子系统则为通过 I²C 总线连接的传感器、扩展芯片等设备提供了统一的访问方式。

本文介绍内核定时器、platform 驱动模型和 I²C 驱动的基本结构,并简要说明 IIO 与 USB 子系统的用途。

二、Linux 内核定时器

2.1 定时器的作用

内核定时器用于在指定时间到达后触发回调函数。它适用于延迟处理、周期性检查以及部分简单的状态监测任务。

例如,在按键驱动中,可以在检测到电平变化后延迟一段时间再次读取状态,以辅助实现按键消抖。

需要注意,定时器回调通常运行在不能睡眠的上下文中,因此不应在回调中执行可能睡眠的操作。

2.2 jiffies 与 HZ

Linux 内核使用 jiffies 记录系统启动后累计的时钟节拍数,而 HZ 表示每秒的时钟节拍数。

在相关计算中,可以通过 msecs_to_jiffies() 将毫秒转换成内核节拍数。例如:

unsigned long delay;

delay = msecs_to_jiffies(100);

该代码用于计算约 100 毫秒对应的节拍数。具体精度取决于内核配置和定时器机制,不应把普通内核定时器当成高精度实时定时器。

2.3 较早内核中的定时器接口

在 Linux 4.1.15 等较早内核中,常见的定时器接口包括:

init_timer(&timer);

timer.function = timer_callback;

timer.data = (unsigned long)dev;

timer.expires = jiffies + msecs_to_jiffies(100);

add_timer(&timer);

其中:

init_timer():初始化定时器。

timer.function:指定回调函数。

timer.data:向回调传递参数。

timer.expires:指定到期时间。

add_timer():将定时器加入内核定时器系统。

修改已经启动的定时器时间时,可以使用 mod_timer();删除定时器时,可以使用 del_timer() 或适当的同步删除接口。

以上接口适用于相应旧版本内核。较新的 Linux 内核已采用不同的定时器初始化方式,开发时应以目标内核头文件和文档为准。

2.4 定时器回调示例

static void timer_callback(unsigned long data)

{

/* 执行简短且不能睡眠的操作 */

}

在使用该接口的内核版本中,回调函数参数类型与上述旧接口相对应。

如果要实现周期性任务,通常需要在回调中重新设置定时器,或者将任务交给工作队列执行。对于需要睡眠或较复杂的处理,不能直接放在定时器回调中完成。

三、platform 总线与驱动模型

3.1 为什么需要 platform 总线

在嵌入式 SoC 中,很多设备直接集成在芯片内部,例如 GPIO 控制器、UART 控制器和定时器控制器。这些设备不一定通过 PCI 或 USB 等外部总线连接。

Linux 使用 platform 设备模型来管理这类设备,并通过设备与驱动的匹配关系完成驱动绑定。

在设备树体系中,设备节点通常由内核转换为相应的设备对象,platform 驱动再根据匹配结果执行初始化。

3.2 platform 驱动的基本结构

一个典型的 platform 驱动包含:

struct platform_driver:描述驱动。

probe():设备与驱动匹配后执行初始化。

remove():设备移除时执行资源清理。

of_match_table:描述设备树匹配规则。

示例:

static const struct of_device_id demo_of_match\[\] = {

{ .compatible = "demo,device" },

{ }

};

static struct platform_driver demo_driver = {

.probe = demo_probe,

.remove = demo_remove,

.driver = {

.name = "demo_device",

.of_match_table = demo_of_match,

},

};

module_platform_driver(demo_driver);

这里的 demo_probe() 和 demo_remove() 需要由驱动开发者实现。示例只展示基本结构,并不构成可以直接操作硬件的完整驱动。

3.3 probe 函数的主要任务

probe() 是 platform 驱动初始化设备的关键位置,通常需要完成:

1.获取设备树和设备资源。

2.申请 GPIO、中断或其他硬件资源。

3.初始化设备状态和驱动私有数据。

4.注册字符设备、misc 设备或其他用户接口。

5.完成后续硬件初始化。

如果中间某一步失败,应当及时释放已经成功申请的资源,避免重复注册或资源泄漏。

3.4 设备与驱动分离

采用 platform 驱动模型后,硬件资源描述与驱动控制逻辑可以分开维护。

设备树主要描述硬件的存在及其资源,驱动则负责如何初始化和操作设备。当硬件连接方式发生变化时,如果驱动逻辑仍然适用,通常可以通过调整设备树完成适配,而不必将所有硬件信息重新写进驱动代码。

四、I²C 驱动基础

4.1 什么是 I²C

I²C 是一种常见的串行通信总线,通常使用 SDA 数据线和 SCL 时钟线连接控制器与外设。

嵌入式设备中的温度传感器、EEPROM、时钟芯片和部分扩展 IO 芯片,都可能通过 I²C 与处理器通信。

Linux 内核提供了 I²C 子系统,用于统一管理控制器、总线设备和对应驱动。

4.2 I²C 子系统中的主要对象

|---------------|--------------------|
| 对象 | 作用 |
| i2c_adapter | 表示 I²C 控制器或适配器 |
| i2c_client | 表示挂接在总线上的具体 I²C 设备 |
| i2c_driver | 表示用于操作某类 I²C 设备的驱动 |
| i2c_algorithm | 描述底层 I²C 传输实现 |

通常情况下,I²C 控制器驱动负责实现底层传输能力,而具体设备驱动负责与传感器或其他芯片进行通信。

4.3 I²C 设备树描述

I²C 设备通常放在对应 I²C 控制器节点的子节点中。例如:

&i2c1 {

status = "okay";

sensor@48 {

compatible = "demo,sensor";

reg = <0x48>;

status = "okay";

};

};

这里的 0x48 是示例设备地址,实际地址应根据具体芯片的地址配置确定。

compatible 用于匹配驱动,reg 在此类 I²C 子节点中通常表示设备的 I²C 地址。不要将这个地址直接当作处理器的内存映射寄存器地址。

4.4 I²C 驱动的基本结构

I²C 设备驱动通常需要:

1.编写设备树匹配表。

2.实现 probe() 和设备移除处理。

3.在初始化过程中获取设备信息。

4.通过 I²C 子系统提供的接口读写设备寄存器。

5.根据具体芯片协议解析数据。

具体的读写方式取决于芯片的数据手册。有些设备需要先写入寄存器地址,再读取数据;另一些设备则采用不同的通信时序。

因此,编写 I²C 驱动之前,应先阅读目标芯片的数据手册,确认寄存器地址、数据格式和通信要求。

五、IIO 子系统简介

IIO(Industrial I/O)是 Linux 内核中用于管理多种传感器和数据采集设备的子系统。

例如,部分加速度计、陀螺仪、光照传感器和 ADC 设备,可以使用 IIO 框架向内核和用户空间提供测量数据。

相较于为每一种传感器设计完全独立的接口,IIO 提供了一套较统一的设备模型和数据访问方式。

不过,并非所有传感器都必须使用 IIO。实际选择应根据设备类型、内核已有驱动以及项目需求决定。

六、USB 子系统简介

USB 是常见的外部设备连接方式。Linux USB 子系统负责管理 USB 总线、设备枚举、接口以及驱动匹配。

在开发 USB 设备驱动时,需要了解设备描述符、接口描述符、端点以及数据传输方式等基本概念。

与 I²C 驱动不同,USB 设备的识别与匹配通常依赖 USB 设备或接口的相关标识信息。具体驱动结构和传输接口,应根据设备类型与目标内核版本确定。

本文只对 USB 子系统进行基础介绍,后续若需要开发具体 USB 设备驱动,还应单独学习 USB 的枚举流程和数据传输机制。

七、驱动调试思路

7.1 platform 驱动没有进入 probe

重点检查:

驱动是否成功注册。

设备树节点是否启用。

compatible 字符串是否一致。

实际启动使用的 DTB 是否包含设备节点。

相关设备是否成功创建。

7.2 I²C 设备无法通信

可以从以下方向排查:

I²C 控制器是否启用。

SDA、SCL 引脚复用和电气配置是否正确。

设备地址是否正确。

外设是否供电。

上拉电阻和硬件连线是否正常。

通信时序和寄存器访问方式是否符合芯片手册。

如果系统提供了 i2c-tools,还可以使用相应工具辅助检查总线设备,但使用前应确认目标系统已经安装工具,并注意不要随意扫描或写入正在工作的设备。

7.3 定时器行为异常

应检查定时器是否正确初始化、到期时间是否合理,以及回调函数中是否执行了不允许的操作。

八、总结

内核定时器、platform 总线和 I²C 子系统分别解决了不同的问题。

定时器适用于延迟触发和简单的周期性任务;platform 驱动模型用于管理许多片上设备,并通过设备与驱动匹配完成初始化;I²C 子系统则为总线上的外设提供统一的驱动框架。

IIO 和 USB 也属于 Linux 设备开发中常见的知识领域,但其内部机制和适用场景各不相同,应根据项目需求进一步学习。

学习驱动开发时,建议先掌握设备树与 GPIO,再理解中断和同步机制,最后学习 platform、I²C 等设备框架。这样更容易建立完整的 Linux 驱动知识体系。

相关推荐
传感器与混合集成电路2 小时前
150MHz主频、8路DAC与6路PWM:JLH221360伺服控制SIP解析
嵌入式硬件
承渊政道2 小时前
Linux网络学习【传输层协议UDP】
linux·网络·网络协议·学习·ubuntu·udp
andrsted2 小时前
练题簿免费刷题小程序:和学习搭子共用一份题库,一起刷题复习
学习·面试·微信小程序·小程序·学习方法·刷题
comyccccc2 小时前
STM32PID控制板设计全解析学习笔记
单片机·嵌入式硬件
雯宝2 小时前
usb_serial_init/tty_driver/alloc_tty_driver/tty_register_driver/usb_register
linux
澜山奕秋2 小时前
Rhcsa 二
linux·运维·服务器
小此方2 小时前
Linux网络(三十三):告别O(n)噩梦:从select/poll的挣扎到epoll红黑树+回调的降维打击,一文打通IO多路复用
linux·网络·驱动开发
2301_777998343 小时前
Linux 网络:数据链路层
linux·服务器·网络
迷途之人不知返3 小时前
【文件系统】-1-磁盘与扇区的定位方法
linux