linux下SPI、IIC、UART、CAN的编码

在 Linux 内核中,platformI2CSPI 是三种典型的设备驱动模型。它们都基于 Linux 的设备驱动模型(总线、设备、驱动),但各自适用的硬件场景和驱动框架有所不同。下面分别介绍它们的原理及驱动代码结构。

一、Platform 总线

1. 原理

Platform 是一种虚拟总线,用于管理那些不依附于传统物理总线(如 PCI、USB、I2C、SPI)的片上设备,例如:

  • 片上外设:GPIO、UART、I2C 控制器、SPI 控制器、RTC、看门狗等;

  • 直接映射在内存地址空间的设备。

Platform 总线将设备分为两类:

  • platform_device:描述设备的硬件资源(寄存器地址、中断号、时钟等);

  • platform_driver:描述驱动程序的初始化、probe、remove 等操作。

当内核启动或动态加载驱动时,总线会匹配设备与驱动,匹配成功则调用驱动的 probe 函数,驱动进而初始化硬件。

2. 驱动代码结构

(1) 定义 platform_driver 结构体
cpp 复制代码
#include <linux/platform_device.h>

static int my_platform_probe(struct platform_device *pdev)
{
    struct resource *res;
    void __iomem *base;
    int irq;

    // 获取寄存器资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
    base = devm_ioremap_resource(&pdev->dev, res);
    if (IS_ERR(base))
        return PTR_ERR(base);

    // 获取中断资源
    irq = platform_get_irq(pdev, 0);
    if (irq < 0)
        return irq;

    // 硬件初始化,注册中断等
    // ...

    dev_info(&pdev->dev, "probe success\n");
    return 0;
}

static int my_platform_remove(struct platform_device *pdev)
{
    // 释放资源,注销中断等
    // ...
    return 0;
}

static const struct of_device_id my_platform_of_match[] = {
    { .compatible = "vendor,my-device", },
    { /* sentinel */ }
};
MODULE_DEVICE_TABLE(of, my_platform_of_match);

static struct platform_driver my_platform_driver = {
    .probe  = my_platform_probe,
    .remove = my_platform_remove,
    .driver = {
        .name = "my_platform_driver",
        .of_match_table = my_platform_of_match,  // 设备树匹配
    },
};

module_platform_driver(my_platform_driver);
(2) 设备描述方式

内核通过 i2c_adapter 表示一个 I2C 总线控制器,通过 i2c_client 表示一个连接到该总线上的设备。设备驱动的 probe 会在 i2c_clienti2c_driver 匹配时被调用。

  • 静态注册 (旧方式):通过 platform_device_register()platform_add_devices() 在板级文件中注册。

  • 设备树(DT) (推荐):在 .dts 文件中描述:my_device: my-device@12345678 {

    compatible = "vendor,my-device";

    reg = <0x12345678 0x1000>;

    interrupts = <0 42 4>;

    clocks = <&clk 10>;

    };内核启动时,设备树节点会被转换为 platform_device,并与驱动匹配。

二、I2C 子系统

1. 原理

I2C 子系统分为两部分:

  • I2C 总线控制器驱动(Adapter):负责控制具体 I2C 控制器的硬件操作(产生起始信号、传输字节等)。

  • I2C 设备驱动(Client Driver):针对挂载在 I2C 总线上的特定设备(如传感器、EEPROM)的驱动。

2. 驱动代码结构

(1) I2C 设备驱动(Client Driver)
cpp 复制代码
#include <linux/i2c.h>

static int my_i2c_probe(struct i2c_client *client,
                        const struct i2c_device_id *id)
{
    struct device *dev = &client->dev;

    // 初始化设备,如分配私有数据结构,注册中断等
    // 可以使用 i2c_transfer() 或 i2c_smbus_* 函数与设备通信
    dev_info(dev, "I2C device probed\n");
    return 0;
}

static int my_i2c_remove(struct i2c_client *client)
{
    // 清理工作
    return 0;
}

static const struct i2c_device_id my_i2c_id_table[] = {
    { "my_i2c_device", 0 },
    { }
};
MODULE_DEVICE_TABLE(i2c, my_i2c_id_table);

static const struct of_device_id my_i2c_of_match[] = {
    { .compatible = "vendor,my-i2c-device", },
    { }
};
MODULE_DEVICE_TABLE(of, my_i2c_of_match);

static struct i2c_driver my_i2c_driver = {
    .driver = {
        .name = "my_i2c_driver",
        .of_match_table = my_i2c_of_match,
    },
    .probe    = my_i2c_probe,
    .remove   = my_i2c_remove,
    .id_table = my_i2c_id_table,
};

module_i2c_driver(my_i2c_driver);
(2) 设备树中描述 I2C 设备

dts

&spi0 {

cs-gpios = <&gpio 10 0>;

status = "okay";

my_device: my-spi-device@0 {

compatible = "vendor,my-spi-device";

reg = <0>; // 片选编号

spi-max-frequency = <10000000>;

spi-cpol; // 可选属性

spi-cpha;

};

};

(3) SPI 数据传输

// 同步传输

int spi_write_then_read(struct spi_device *spi,

const void *txbuf, unsigned n_tx,

void *rxbuf, unsigned n_rx);

int spi_sync(struct spi_device *spi, struct spi_message *message);

// 异步传输

int spi_async(struct spi_device *spi, struct spi_message *message);

四、三者关系与对比

特性 Platform I2C SPI
总线类型 虚拟总线(无物理总线) 物理总线(两线制) 物理总线(四线制 + 片选)
匹配方式 设备树 compatible / 名称 设备树 compatible + reg 地址 设备树 compatible + reg 片选
核心结构体 platform_driver, platform_device i2c_driver, i2c_client, i2c_adapter spi_driver, spi_device, spi_master
主要用途 片上外设、内存映射设备 低速传感器、EEPROM 高速传感器、显示屏、ADC
数据传输 函数 直接访问内存映射寄存器 i2c_transfer / SMBus 函数 spi_sync / spi_async

共同点

  • 都遵循 Linux 设备驱动模型:驱动注册到对应总线,总线负责匹配设备与驱动。

  • 都支持设备树,用于描述硬件资源。

  • 都提供 probe/remove 接口,驱动在 probe 中完成硬件初始化、中断注册、创建设备文件等。

  • 都支持电源管理回调(suspend/resume)。

五、总结

  • Platform 是 Linux 为"无总线"设备提供的通用总线模型,使得片上外设能够以统一的方式被管理。

  • I2C/SPI 是具体的物理总线子系统,内核将控制器驱动(Adapter/Master)与设备驱动(Client/Slave)分离,提高了代码复用性。

  • 编写驱动时,只需关注设备侧驱动,填充相应的 *_driver 结构体,实现 probe 和数据传输函数,并通过 module_*_driver 宏注册即可。

理解这三种驱动模型,是掌握 Linux 嵌入式驱动开发的基础。实际项目中,通常会结合设备树和内核提供的辅助函数(如 devm_* 资源管理)来简化驱动代码。

  • module_platform_driver 宏是在较新的内核中引入的(大约是2.6.30+?),但更常见于3.x及以后。

  • 设备树支持在ARM架构中从2.6.38开始引入,但广泛使用是在3.x。

  • devm_* 资源管理函数是在2.6.30左右引入的。

  • of_match_tablemodule_platform_driver 等在现代内核中普遍使用。

  • 上述代码示例主要适用于 Linux 内核 3.x 及以上版本 ,也兼容部分 2.6.30+ 的较新 2.6 内核,但无法直接应用于早期的 2.6 内核(如 2.6.20 之前)或 2.4 内核。

具体版本依赖点

  1. module_platform_driver() / module_i2c_driver() / module_spi_driver()

    这些简化注册的宏是在内核 2.6.30 左右引入的。早期版本需要手动调用 platform_driver_register() / i2c_add_driver() 并在 module_exit 中注销。

  2. 设备树支持 (of_match_table)

    设备树在 ARM 架构中从 2.6.38 开始引入,在 3.x 后才成为主流。早期 2.6 内核通常使用板级文件(arch/arm/mach-xxx)静态注册设备,代码中不会出现 of_match_table

  3. devm_* 资源管理函数

    例如 devm_ioremap_resource()devm_request_irq() 等是在 2.6.30 左右加入的。早期内核需要手动申请释放资源,容易出错。

  4. of_device_idMODULE_DEVICE_TABLE(of, ...)

    与设备树配套的匹配表和宏,同样需要 2.6.38+ 的支持。

总结

  • 如果你的目标内核是 3.x、4.x、5.x 等,代码无需修改即可使用。

  • 如果使用 2.6.35 ~ 2.6.39 的部分版本,代码可能仍可编译,但需要确保内核配置开启了设备树(如果使用了 of_match_table)。

  • 如果使用 2.6.30 之前的版本,上述代码需要重写为老式注册方式,并移除设备树相关部分,改用板级文件静态描述设备。

在实际开发中,建议基于较新内核(如 4.x 或 5.x),并参考内核源码中的 Documentation/ 目录获取对应版本的正确写法。

相关推荐
yj_xqj2 小时前
openGauss 数据库报错“failed: To0 many open files”
运维·数据库
欲盖弥彰13142 小时前
linux设备驱动 -- RK3568 led驱动 (led子系统&设备树)
linux·led·嵌入式linux·led子系统
小五传输2 小时前
汽车供应商协同平台如何重塑主机厂与供应商的数字化纽带?
大数据·运维·安全
三万棵雪松3 小时前
【Linux 物联网网关主控系统-Linux主控部分(四)】
linux·物联网·嵌入式linux
returnthem3 小时前
虚拟机sda紧急处理扩容(used100%,无空间剩余,开不了机)
linux·运维·服务器
ZHANG13HAO3 小时前
嵌入式温度记录仪:15 天数据存储与 BLE 无损压缩方案(CH592 最优实现)
大数据·服务器·数据库
API快乐传递者3 小时前
1688商品数据接口:供应链ERP数字化的核心引擎
java·大数据·运维
Sean‘3 小时前
Linux系统下安装Trivy
linux·运维·服务器
fqq33 小时前
权限设计模式
运维