面试-总线接口驱动.md 32 KB


title: Linux总线与接口驱动面试精选 tags:

  • 嵌入式Linux
  • 驱动开发
  • I2C
  • SPI
  • USB
  • CAN
  • 串口
  • 面试 created: 2026-09-17 updated: 2026-09-17 ---

Linux总线与接口驱动面试精选

⚠️ 来源说明:本文为基于《I.MX6U嵌入式Linux驱动开发指南》第六十六、六十七、六十二、六十一、六十三章整理的扩展面试题,题目本身不属于原书内容;涉及的事实(API、结构体、设备树、寄存器)已按原书相应章节核对。

I2C驱动面试题

Q1: 请描述Linux I2C驱动框架的分层结构,以及各层的作用

答案要点

  1. I2C总线驱动(适配器驱动)负责SOC的I2C控制器
  2. I2C设备驱动负责具体的I2C外设
  3. I2C核心层提供与硬件无关的API函数

详细解答: Linux I2C驱动框架采用分层架构,主要分为三层:

// I2C总线数据结构
struct bus_type i2c_bus_type = {
    .name = "i2c",
    .match = i2c_device_match,
    .probe = i2c_device_probe,
    .remove = i2c_device_remove,
};
  • I2C总线驱动:SOC的I2C控制器驱动,初始化i2c_adapter结构体,实现i2c_algorithm中的master_xfer传输函数。通常由半导体厂商编写。
  • I2C设备驱动:针对具体I2C设备的驱动,实现i2c_driver结构体中的proberemove函数。
  • I2C核心层:提供i2c_add_adapteri2c_del_adapteri2c_add_driver等API。

追问

  • I2C总线驱动和设备驱动分别由谁编写?
  • i2c_algorithm结构体中的master_xfer函数有什么作用?

Q2: 请说明I2C设备和驱动的匹配过程,包括设备树匹配和传统匹配

答案要点

  1. 通过i2c_device_match函数完成匹配
  2. 设备树匹配比较compatible属性
  3. 传统匹配比较id_table中的name字段

详细解答

static int i2c_device_match(struct device *dev, struct device_driver *drv)
{
    // 设备树匹配
    if (of_driver_match_device(dev, drv))
        return 1;

    // ACPI匹配
    if (acpi_driver_match_device(dev, drv))
        return 1;

    // 传统ID表匹配
    if (driver->id_table)
        return i2c_match_id(driver->id_table, client) != NULL;

    return 0;
}

匹配顺序:

  1. 首先尝试设备树匹配,比较设备节点的compatible属性与驱动的of_device_id
  2. 然后尝试ACPI匹配
  3. 最后尝试传统ID表匹配,比较设备名与i2c_device_id的name字段

追问

  • 设备树匹配和传统匹配有什么区别?
  • 如果两种匹配方式都定义了,优先使用哪种?

Q3: 请编写一个完整的I2C设备驱动框架,包括probe、remove函数和驱动注册

答案要点

  1. 实现probe和remove函数
  2. 定义id_table和of_match_table
  3. 构建i2c_driver结构体并注册

详细解答

// probe函数
static int xxx_probe(struct i2c_client *client, const struct i2c_device_id *id)
{
    // 初始化设备
    // 注册字符设备
    return 0;
}

// remove函数
static int xxx_remove(struct i2c_client *client)
{
    // 注销字符设备
    return 0;
}

// 传统匹配ID列表
static const struct i2c_device_id xxx_id[] = {
    {"xxx", 0},
    {}
};

// 设备树匹配列表
static const struct of_device_id xxx_of_match[] = {
    { .compatible = "xxx" },
    { /* Sentinel */ }
};

// i2c驱动结构体
static struct i2c_driver xxx_driver = {
    .probe = xxx_probe,
    .remove = xxx_remove,
    .driver = {
        .owner = THIS_MODULE,
        .name = "xxx",
        .of_match_table = xxx_of_match,
    },
    .id_table = xxx_id,
};

// 驱动入口函数
static int __init xxx_init(void)
{
    return i2c_add_driver(&xxx_driver);
}

// 驱动出口函数
static void __exit xxx_exit(void)
{
    i2c_del_driver(&xxx_driver);
}

module_init(xxx_init);
module_exit(xxx_exit);

追问

  • i2c_add_driver宏的实现是什么?
  • probe函数在什么时候被调用?

Q4: 请说明I2C适配器驱动的实现流程,以I.MX6U为例

答案要点

  1. I2C适配器驱动是platform驱动
  2. 在probe函数中初始化i2c_adapter
  3. 设置i2c_algorithm并注册适配器

详细解答: I.MX6U的I2C适配器驱动在drivers/i2c/busses/i2c-imx.c中实现:

static struct platform_driver i2c_imx_driver = {
    .probe = i2c_imx_probe,
    .remove = i2c_imx_remove,
    .driver = {
        .name = DRIVER_NAME,
        .owner = THIS_MODULE,
        .of_match_table = i2c_imx_dt_ids,
    },
    .id_table = imx_i2c_devtype,
};

probe函数主要工作:

  1. 获取资源(内存、中断)
  2. 初始化i2c_adapter结构体
  3. 设置i2c_algorithm中的传输函数
  4. 调用i2c_add_numbered_adapter注册适配器

追问

  • I2C适配器驱动为什么是platform驱动?
  • i2c_add_adapteri2c_add_numbered_adapter有什么区别?

Q5: 请解释I2C数据传输的时序,包括起始、停止、应答信号

答案要点

  1. 起始信号:SCL高电平时SDA下降沿
  2. 停止信号:SCL高电平时SDA上升沿
  3. 应答信号:第9个时钟周期SDA低电平

详细解答

// I2C时序关键点
起始条件: SCL=1, SDA↓
停止条件: SCL=1, SDA↑
应答信号: 第9个SCL高电平时SDA=0
非应答:   第9个SCL高电平时SDA=1

// I2C数据传输格式
| S | 7bit地址 | R/W | ACK | 8bit数据 | ACK | ... | P |

I2C通信特点:

  • 半双工通信
  • 多主机支持
  • 每字节传输后有ACK位
  • 地址为7位或10位

追问

  • I2C的时钟同步是如何实现的?
  • 多主机情况下如何进行总线仲裁?

SPI驱动面试题

Q6: 请描述Linux SPI驱动框架的结构,以及与I2C驱动的异同

答案要点

  1. SPI总线驱动使用spi_master结构体
  2. SPI设备驱动使用spi_driver结构体
  3. SPI是全双工,I2C是半双工

详细解答

// SPI总线数据结构
struct bus_type spi_bus_type = {
    .name = "spi",
    .dev_groups = spi_dev_groups,
    .match = spi_match_device,
    .uevent = spi_uevent,
};

// SPI设备驱动结构体
struct spi_driver {
    const struct spi_device_id *id_table;
    int (*probe)(struct spi_device *spi);
    int (*remove)(struct spi_device *spi);
    void (*shutdown)(struct spi_device *spi);
    struct device_driver driver;
};

与I2C的主要区别:

特性 SPI I2C
通信方式 全双工 半双工
片选 需要CS线 通过地址
速率 更高 较低
信号线 4根 2根

追问

  • SPI的spi_master结构体中transfer函数的作用是什么?
  • SPI设备节点中spi-max-frequency属性的含义是什么?

Q7: 请说明SPI数据传输的实现流程,包括spi_transfer和spi_message的使用

答案要点

  1. 定义spi_transfer结构体设置tx_buf和rx_buf
  2. 将spi_transfer添加到spi_message
  3. 调用spi_sync进行同步传输

详细解答

// SPI数据传输步骤
struct spi_transfer t;
struct spi_message m;

// 1. 初始化spi_message
spi_message_init(&m);

// 2. 设置spi_transfer
memset(&t, 0, sizeof(t));
t.tx_buf = tx_data;  // 发送缓冲区
t.rx_buf = rx_data;  // 接收缓冲区
t.len = len;         // 数据长度

// 3. 添加到spi_message
spi_message_add_tail(&t, &m);

// 4. 同步传输
spi_sync(spi, &m);

spi_transfer结构体关键字段:

  • tx_buf:发送数据缓冲区
  • rx_buf:接收数据缓冲区
  • len:数据长度
  • speed_hz:传输速率
  • bits_per_word:每字位数

追问

  • spi_sync和spi_async有什么区别?
  • SPI全双工通信时,发送和接收的数据长度有什么关系?

Q8: 请描述SPI设备节点在设备树中的配置方法

答案要点

  1. 配置SPI控制器节点
  2. 添加SPI设备子节点
  3. 设置compatible、spi-max-frequency等属性

详细解答

// SPI控制器节点
ecspi3: ecspi@02010000 {
    #address-cells = <1>;
    #size-cells = <0>;
    compatible = "fsl,imx6ul-ecspi", "fsl,imx51-ecspi";
    reg = <0x02010000 0x4000>;
    interrupts = <GIC_SPI 33 IRQ_TYPE_LEVEL_HIGH>;
    clocks = <&clks IMX6UL_CLK_ECSPI3>,
             <&clks IMX6UL_CLK_ECSPI3>;
    clock-names = "ipg", "per";
    dmas = <&sdma 7 7 1>, <&sdma 8 7 2>;
    dma-names = "rx", "tx";
    status = "disabled";
};

// SPI设备节点
&ecspi3 {
    fsl,spi-num-chipselects = <1>;
    cs-gpios = <&gpio1 20 GPIO_ACTIVE_LOW>;
    pinctrl-names = "default";
    pinctrl-0 = <&pinctrl_ecspi3>;
    status = "okay";

    icm20608@0 {
        compatible = "alientek,icm20608";
        spi-max-frequency = <8000000>;
        reg = <0>;
    };
};

追问

  • spi-num-chipselects属性的含义是什么?
  • cs-gpios属性的作用是什么?

Q9: 请说明SPI适配器驱动的实现流程,以I.MX6U为例

答案要点

  1. SPI适配器驱动也是platform驱动
  2. probe函数中初始化spi_master
  3. 实现transfer函数进行数据传输

详细解答: I.MX6U的SPI适配器驱动在drivers/spi/spi-imx.c中实现:

static struct platform_driver spi_imx_driver = {
    .driver = {
        .name = DRIVER_NAME,
        .of_match_table = spi_imx_dt_ids,
        .pm = IMX_SPI_PM,
    },
    .id_table = spi_imx_devtype,
    .probe = spi_imx_probe,
    .remove = spi_imx_remove,
};

数据传输函数调用链:

spi_imx_transfer
  -> spi_imx_pio_transfer
    -> spi_imx_push
      -> spi_imx->tx (发送函数)

不同位宽的发送函数:

  • spi_imx_buf_tx_u8:8位发送
  • spi_imx_buf_tx_u16:16位发送
  • spi_imx_buf_tx_u32:32位发送

追问

  • SPI适配器驱动中DMA传输是如何实现的?
  • spi_imx_push函数的作用是什么?

Q10: 请比较SPI和I2C在嵌入式系统中的应用场景

答案要点

  1. SPI适合高速、全双工场景
  2. I2C适合多设备、低速场景
  3. 根据速率、引脚数量、设备数量选择

详细解答

应用场景 选择SPI 选择I2C
高速数据采集
Flash存储器
传感器(低速)
EEPROM
LCD显示屏
多设备连接

选择依据:

  • 速率要求:SPI可达几十MHz,I2C通常100K/400K
  • 引脚数量:SPI需要4根线,I2C只需2根
  • 设备数量:I2C通过地址可挂载多个设备,SPI需要独立CS
  • 功耗要求:I2C功耗更低

追问

  • 同一条I2C总线上可以挂载多少个设备?
  • SPI如何实现多设备连接?

USB驱动面试题

Q11: 请描述USB的拓扑结构,包括Host、Device和Hub的关系

答案要点

  1. USB采用主从架构
  2. Host控制Device通信
  3. Hub用于扩展USB接口

详细解答

USB拓扑结构:
┌─────────────────────────────────────┐
│              Host (Root Hub)        │
│    ┌──────┬──────┬──────┬──────┐   │
│    │Port1 │Port2 │Port3 │Port4 │   │
└────┴──┬───┴──┬───┴──┬───┴──┬───┘    │
         │      │      │      │
    ┌────┴──┐ ┌─┴───┐ ┌┴────┐ │
    │Device │ │ Hub │ │Dev  │ │
    └───────┘ └──┬──┘ └─────┘ │
            ┌────┴────┬───────┐│
            │Dev│Dev│Dev│Dev  ││
            └───┴───┴───┴────┘│

关键概念:

  • Root Hub:USB控制器内置的Hub
  • Host:发起通信的主机
  • Device:响应通信的设备
  • Hub:扩展USB端口的设备

USB特点:

  • 最多127个设备
  • 热插拔
  • 支持OTG(On-The-Go)

追问

  • USB OTG是什么?如何切换Host/Device模式?
  • 一个USB系统最多支持多少层Hub?

Q12: 请描述USB的数据传输类型,包括控制传输、批量传输、中断传输和同步传输

答案要点

  1. 控制传输:用于设备配置
  2. 批量传输:用于大量数据传输
  3. 中断传输:用于实时性要求高的设备
  4. 同步传输:用于音视频等实时数据

详细解答

// USB传输类型对比
| 传输类型   | 特点           | 应用场景         | 是否保证带宽 |
|-----------|---------------|-----------------|-------------|
| 控制传输   | 可靠、有序     | 设备配置、状态查询 | 否          |
| 批量传输   | 大量、可靠     | 打印机、U盘       | 否          |
| 中断传输   | 周期性、低延迟  | 键盘、鼠标        | 是          |
| 同步传输   | 固定速率、允许丢包 | 音视频流        | 是          |

USB端点类型:

  • 控制端点(EP0):所有设备必须有
  • 批量端点:Bulk传输
  • 中断端点:Interrupt传输
  • 同步端点:Isochronous传输

追问

  • USB设备的枚举过程是什么?
  • 端点描述符中bmAttributes字段的含义是什么?

Q13: 请描述USB设备的枚举过程

答案要点

  1. 设备连接后Host复位设备
  2. Host获取设备描述符
  3. 分配地址并配置设备

详细解答

// USB枚举过程
1. 设备插入 → Hub检测到电平变化
2. Host复位端口 → 设备进入Default状态
3. Host发送GET_DESCRIPTOR请求 → 获取设备描述符
4. Host分配地址 → SET_ADDRESS请求
5. Host获取配置描述符 → 了解设备能力
6. Host加载驱动 → 匹配成功调用probe

USB描述符层次:

Device Descriptor
  └── Configuration Descriptor
        ├── Interface Descriptor (0)
        │     └── Endpoint Descriptor
        └── Interface Descriptor (1)
              └── Endpoint Descriptor

追问

  • USB设备描述符中VID和PID的作用是什么?
  • 设备描述符和配置描述符有什么区别?

Q14: 请说明USB OTG的工作原理,以及如何切换Host/Device模式

答案要点

  1. OTG通过ID引脚判断角色
  2. ID=0时为Host,ID=1时为Device
  3. 支持动态切换

详细解答

// USB OTG模式切换
ID引脚状态:
- ID = 0 → Host模式
- ID = 1 → Device模式

// OTG线连接方式
OTG线:USB A头 → Mini USB/Type-C头
- USB A头:连接USB设备
- Mini USB头:连接OTG接口
- OTG线会将ID引脚拉低

I.MX6ULL USB OTG实现:

// 硬件连接
USB_OTG1_ID引脚:
- 高电平:Device模式
- 低电平:Host模式

// 通过MT9700HT5芯片控制VBUS电源
- Host模式:VBUS输出5V
- Device模式:VBUS关闭

追问

  • OTG设备如何进行HNP协商?
  • SRP协议的作用是什么?

Q15: 请描述Linux USB驱动框架的结构

答案要点

  1. USB主机驱动(HCD)
  2. USB核心层
  3. USB设备驱动

详细解答

// Linux USB驱动层次
┌─────────────────────────────────┐
│        USB设备驱动              │
│   (U盘驱动、鼠标驱动等)         │
├─────────────────────────────────┤
│        USB核心层                │
│   (usb_core、usb接口驱动)       │
├─────────────────────────────────┤
│        USB主机驱动(HCD)         │
│   (EHCI、xHCI控制器驱动)       │
├─────────────────────────────────┤
│        USB硬件控制器            │
│   (I.MX6ULL USB OTG)           │
└─────────────────────────────────┘

USB主机控制器标准:

  • OHCI:USB1.1标准
  • UHCI:Intel USB1.0/1.1标准
  • EHCI:USB2.0标准(I.MX6ULL使用)
  • xHCI:USB3.0标准

追问

  • EHCI和xHCI有什么区别?
  • USB设备驱动和USB接口驱动有什么区别?

CAN驱动面试题

Q16: 请描述Linux CAN驱动框架的结构

答案要点

  1. CAN控制器驱动(网络设备驱动)
  2. CAN核心层
  3. CAN协议层

详细解答

// Linux CAN驱动层次
┌─────────────────────────────────┐
│        CAN应用层                │
│   (SocketCAN接口)              │
├─────────────────────────────────┤
│        CAN协议层                │
│   (CAN raw、CAN bcm等)         │
├─────────────────────────────────┤
│        CAN核心层                │
│   (can_dev.c)                  │
├─────────────────────────────────┤
│        CAN控制器驱动            │
│   (SJA1000、MCP2515等)         │
└─────────────────────────────────┘

CAN网络设备结构(Linux 中 CAN 控制器驱动通用的私有数据结构是 struct can_priv,定义在 include/linux/can/dev.h,由 alloc_candev() 分配):

struct can_priv {
    struct net_device *dev;
    struct can_device_stats can_stats;      /* 总线错误统计 */
    const struct can_bittiming_const *bittiming_const;
    struct can_bittiming bittiming;         /* 位时序参数 */
    const struct can_clock *clock;
    u32 ctrlmode;                           /* 控制模式标志 */
    ...
    struct napi_struct napi;
};

追问

  • SocketCAN是什么?有什么优势?
  • CAN总线的波特率是如何计算的?

Q17: 请描述CAN帧的格式,包括标准帧和扩展帧

答案要点

  1. 标准帧:11位ID
  2. 扩展帧:29位ID
  3. 帧格式包括帧起始、仲裁场、控制场、数据场等

详细解答

// CAN帧格式对比
标准帧:
| SOF | 11bit ID | RTR | IDE | r0 | DLC | Data(0-8) | CRC | ACK | EOF |

扩展帧:
| SOF | 11bit ID | SRR | IDE | 18bit ID | RTR | r1 | r0 | DLC | Data(0-8) | CRC | ACK | EOF |
// CAN帧结构
struct can_frame {
    canid_t can_id;    // CAN ID + 标志位
    __u8    can_dlc;   // 数据长度
    __u8    __pad;     // 填充
    __u8    __res0;    // 保留
    __u8    __res1;    // 保留
    __u8    data[8];   // 数据
};

追问

  • RTR位的作用是什么?
  • CAN帧的最大数据长度是多少?

Q18: 请说明CAN总线的错误处理机制

答案要点

  1. CAN有完善的错误检测机制
  2. 错误计数器管理节点状态
  3. 支持错误帧和错误恢复

详细解答

// CAN错误类型
| 错误类型   | 检测方法         | 说明              |
|-----------|----------------|-------------------|
| 位错误    | 发送与监听比较   | 发送的位与总线不同 |
| 填充错误  | 连续6个相同位    | 违反位填充规则     |
| CRC错误   | CRC校验          | 接收CRC与计算不同  |
| 形式错误  | 固定格式位       | 格式字段出错       |
| 应答错误  | ACK槽            | 发送方未收到ACK    |

// 错误状态
主动错误 → 检测到错误发送错误帧
被动错误 → 检测到错误不发送错误帧
总线关闭 → 退出总线,等待恢复

追问

  • 错误计数器TEC和REC的作用是什么?
  • 总线关闭后如何恢复?

Q19: 请描述Linux中CAN设备驱动的实现流程

答案要点

  1. 注册网络设备
  2. 实现ndo_open、ndo_stop等操作
  3. 实现发送和接收函数

详细解答

// CAN网络设备操作集
static const struct net_device_ops can_netdev_ops = {
    .ndo_open       = can_open,
    .ndo_stop       = can_close,
    .ndo_start_xmit = can_xmit,
    .ndo_do_ioctl   = can_ioctl,
};

// CAN设备注册
struct net_device *alloc_candev(int sizeof_priv, unsigned int echo_skb_max)
{
    struct net_device *dev;
    // 分配网络设备
    // 初始化can_dev结构
    return dev;
}

CAN数据接收:

// 中断处理中接收CAN帧
static irqreturn_t can_rx_irq(int irq, void *dev_id)
{
    struct can_frame *frame = &priv->rx_frame;
    // 读取CAN帧
    // 调用netif_rx上报
    netif_rx_ni(&priv->skb);
}

追问

  • CAN设备的环形缓冲区是如何实现的?
  • 如何处理CAN总线的高负载情况?

Q20: 请比较CAN和SPI在数据传输中的应用

答案要点

  1. CAN适合分布式系统、长距离通信
  2. SPI适合板级高速通信
  3. 根据距离、速率、可靠性要求选择

详细解答

特性 CAN SPI
通信距离 最远10km 板级
最高速率 1Mbps 几十MHz
多主支持 支持 不支持
错误检测 完善
通信方式 差分信号 单端信号
应用场景 汽车、工业 传感器、Flash

选择依据:

  • 分布式系统:CAN(多主、远距离)
  • 板级通信:SPI(高速、简单)
  • 可靠性要求高:CAN(错误检测完善)
  • 成本敏感:SPI(引脚少、简单)

追问

  • CAN总线的终端电阻作用是什么?
  • SPI如何实现长距离通信?

串口驱动面试题

Q21: 请描述Linux串口驱动框架的结构

答案要点

  1. 串口核心层
  2. 串口驱动层
  3. 串口设备层

详细解答

// Linux串口驱动层次
┌─────────────────────────────────┐
│        用户空间                  │
│   (ttyS0, ttyS1等)             │
├─────────────────────────────────┤
│        TTY核心层                 │
│   (tty_io.c)                   │
├─────────────────────────────────┤
│        串口核心层                │
│   (serial_core.c)              │
├─────────────────────────────────┤
│        串口驱动层                │
│   (8250_dw.c, imx.c等)         │
└─────────────────────────────────┘

串口操作集:

static const struct uart_ops serial_imx_pops = {
    .tx_empty   = imx_tx_empty,
    .set_mctrl  = imx_set_mctrl,
    .get_mctrl  = imx_get_mctrl,
    .stop_tx    = imx_stop_tx,
    .start_tx   = imx_start_tx,
    .stop_rx    = imx_stop_rx,
    .break_ctl  = imx_break_ctl,
    .startup    = imx_startup,
    .shutdown   = imx_shutdown,
    .set_termios = imx_set_termios,
};

追问

  • UART和串口有什么区别?
  • tty设备和串口设备有什么关系?

Q22: 请描述UART的通信协议,包括数据帧格式

答案要点

  1. 数据帧包括起始位、数据位、校验位、停止位
  2. 波特率决定通信速率
  3. 支持多种校验方式

详细解答

// UART数据帧格式
| 起始位(1) | 数据位(5-8) | 校验位(0-1) | 停止位(1-2) |

// 常见配置
数据位: 7, 8
校验位: None, Even, Odd, Mark, Space
停止位: 1, 1.5, 2

波特率计算:

波特率 = 时钟频率 / (16 * 分频系数)

常用波特率:

  • 9600
  • 19200
  • 38400
  • 57600
  • 115200

追问

  • 什么是流控?RTS/CTS的作用是什么?
  • RS232和TTL串口有什么区别?

Q23: 请说明Linux中串口驱动的初始化流程

答案要点

  1. 注册platform驱动
  2. probe函数中初始化UART端口
  3. 注册串口核心层

详细解答

// I.MX6ULL串口驱动注册
static struct platform_driver imx_uart_driver = {
    .driver = {
        .name = "imx-uart",
        .of_match_table = imx_uart_dt_ids,
    },
    .probe = imx_probe,
    .remove = imx_remove,
};

// probe函数主要工作
static int imx_probe(struct platform_device *pdev)
{
    struct uart_port *sport;

    // 1. 获取资源
    res = platform_get_resource(pdev, IORESOURCE_MEM, 0);

    // 2. 映射寄存器
    sport->membase = devm_ioremap_resource(&pdev->dev, res);

    // 3. 获取时钟
    sport->clk = devm_clk_get(&pdev->dev, NULL);

    // 4. 注册串口
    uart_add_one_port(&imx_reg, sport);
}

追问

  • 串口驱动中的环形缓冲区是如何实现的?
  • 中断处理函数如何处理接收数据?

Q24: 请描述串口数据的收发流程

答案要点

  1. 发送:数据从用户空间到发送FIFO
  2. 接收:数据从接收FIFO到用户空间
  3. 使用中断或DMA提高效率

详细解答

// 发送流程
用户空间 write()
  → tty_write()
    → uart_write()
      → imx_start_tx()
        → 写入THR寄存器
          → 硬件发送

// 接收流程
硬件接收
  → 触发中断
    → imx_rxint()
      → 读取RBR寄存器
        → tty_insert_flip_char()
          → tty_flip_buffer_push()
            → 用户空间 read()

DMA传输:

// DMA发送
static void imx_dma_tx(struct uart_port *sport)
{
    // 配置DMA通道
    dmaengine_submit();
    // 启动DMA传输
    dma_async_issue_pending();
}

追问

  • 串口接收中断丢失了怎么办?
  • 如何提高串口通信的实时性?

Q25: 请比较UART、I2C和SPI的特点和应用场景

答案要点

  1. UART:异步、全双工、简单
  2. I2C:同步、半双工、多设备
  3. SPI:同步、全双工、高速

详细解答

特性 UART I2C SPI
时钟 异步 同步 同步
通信方式 全双工 半双工 全双工
信号线 2根 2根 4根
设备数量 点对点 多设备 需CS线
速率 中等
复杂度 简单 中等 中等

应用场景:

  • UART:调试串口、GPS模块、蓝牙模块
  • I2C:传感器、EEPROM、实时时钟
  • SPI:Flash、LCD、高速ADC

追问

  • 如何选择合适的通信接口?
  • 长距离通信应该选择哪种接口?

总线对比面试题

Q26: 请详细对比I2C和SPI的优缺点

答案要点

  1. I2C优点:简单、多设备、节省引脚
  2. I2C缺点:速率低、半双工
  3. SPI优点:高速、全双工
  4. SPI缺点:引脚多、需要CS线

详细解答

// I2C vs SPI详细对比
| 对比项     | I2C                    | SPI                    |
|-----------|------------------------|------------------------|
| 信号线    | SDA, SCL (2根)         | MOSI, MISO, SCK, CS (4根) |
| 通信方式  | 半双工                 | 全双工                  |
| 最高速率  | 400KHz (Fast)          | 几十MHz                |
| 设备寻址  | 7/10位地址             | 片选信号               |
| 多设备    | 支持,无需额外引脚      | 需要独立CS线           |
| 时钟同步  | 支持                   | 不支持                 |
| 总线仲裁  | 支持                   | 不支持                 |
| 功耗      | 低(开漏输出)          | 较高(推挽输出)        |
| 硬件复杂度| 简单                   | 中等                   |
| 软件复杂度| 中等(ACK、仲裁)       | 简单                   |

选择建议:
- 传感器、EEPROM → I2C
- Flash、LCD → SPI
- 多设备、引脚少 → I2C
- 高速、全双工 → SPI

追问

  • I2C的开漏输出和SPI的推挽输出有什么区别?
  • 如何在同一条I2C总线上区分不同设备?

Q27: 请对比SPI、I2C和UART在嵌入式系统中的应用

答案要点

  1. 根据速率、引脚、设备数量选择
  2. SPI适合高速板级通信
  3. I2C适合低速多设备
  4. UART适合调试和简单通信

详细解答

// 三种总线综合对比
| 对比项     | SPI          | I2C          | UART         |
|-----------|--------------|--------------|--------------|
| 信号线    | 4根          | 2根          | 2根          |
| 时钟      | 同步         | 同步         | 异步         |
| 通信方式  | 全双工       | 半双工       | 全双工       |
| 最高速率  | >10MHz       | 400KHz       | 1Mbps        |
| 设备数量  | 需CS线       | 多设备       | 点对点       |
| 通信距离  | 板级         | 板级         | 可远距离     |
| 错误检测  | 无           | 有(ACK)      | 有(校验)     |
| 典型应用  | Flash, LCD   | 传感器,EEPROM| 调试, GPS    |

选择决策树:

需要高速通信?
├── 是 → SPI
└── 否 → 需要多设备?
    ├── 是 → I2C
    └── 否 → 需要远距离?
        ├── 是 → UART
        └── 否 → I2C或SPI均可

追问

  • 为什么SPI的速率可以比I2C高很多?
  • 如何在资源受限的系统中选择通信接口?

Q28: 请说明Linux中不同总线驱动的注册和匹配流程对比

答案要点

  1. 都使用bus_type结构体
  2. 都支持设备树匹配和传统匹配
  3. 匹配成功后调用probe函数

详细解答

// 各总线的bus_type定义
// I2C
struct bus_type i2c_bus_type = {
    .name = "i2c",
    .match = i2c_device_match,
    .probe = i2c_device_probe,
};

// SPI
struct bus_type spi_bus_type = {
    .name = "spi",
    .match = spi_match_device,
};

// Platform
struct bus_type platform_bus_type = {
    .name = "platform",
    .match = platform_match,
    .probe = platform_probe,
};

匹配流程对比:

// 三种总线匹配流程相似
1. of_driver_match_device()  // 设备树匹配
2. acpi_driver_match_device() // ACPI匹配
3. xxx_match_id()            // 传统ID表匹配

// 匹配成功后调用probe
xxx_probe(struct xxx_client *client, ...)

追问

  • platform总线和I2C总线有什么关系?
  • 为什么I2C适配器驱动是platform驱动?

Q29: 请比较不同总线的错误处理机制

答案要点

  1. I2C:ACK/NACK机制
  2. SPI:无内置错误检测
  3. UART:奇偶校验、帧错误
  4. CAN:完善的错误检测

详细解答

// 各总线错误处理对比
| 总线 | 错误检测机制      | 错误恢复方式      |
|------|------------------|------------------|
| I2C  | ACK/NACK         | 重传、总线恢复    |
| SPI  | 无内置机制       | 软件实现         |
| UART | 奇偶校验、帧错误  | 重传、重置       |
| CAN  | CRC、位错误、填充错误 | 错误帧、错误计数器 |

// I2C错误处理
if (!client->adapter->algo->master_xfer)
    return -EOPNOTSUPP;

// CAN错误处理
if (can_id & CAN_ERR_FLAG) {
    // 处理错误帧
    dev_err(dev, "CAN error: 0x%x\n", can_id);
}

// UART错误处理
if (lsr & UART_LSR_FE)
    port->icount.frame++;

追问

  • 如何在软件层面为SPI添加错误检测?
  • CAN总线的错误处理比其他总线完善在哪里?

Q30: 请从实时性角度对比不同总线的性能

答案要点

  1. SPI实时性最好(同步、全双工)
  2. I2C实时性中等(同步、半双工)
  3. UART实时性较差(异步、软件处理)
  4. CAN实时性好(硬件错误检测、优先级仲裁)

详细解答

// 实时性对比
| 总线 | 时钟同步 | 全双工 | 硬件FIFO | 中断响应 | 实时性评级 |
|------|---------|--------|----------|----------|----------|
| SPI  | 是      | 是     | 可选     | 快       | 最好      |
| I2C  | 是      | 否     | 无       | 中       | 中等      |
| UART | 否      | 是     | 可选     | 中       | 中等      |
| CAN  | 是      | 否     | 有       | 快       | 好        |

// 实时性优化方法
1. 使用DMA传输减少CPU占用
2. 使用硬件FIFO缓冲数据
3. 提高中断优先级
4. 使用轮询方式处理关键数据

// SPI实时性优化
struct spi_transfer t = {
    .speed_hz = 10000000,  // 高速传输
    .tx_buf = data,
    .len = len,
};
spi_sync(spi, &m);  // 同步传输,等待完成

追问

  • 如何评估一个总线的实时性能?
  • 在实时系统中如何选择通信接口?

内容来源: 《I.MX6U嵌入式Linux驱动开发指南》第六十一章 Linux I2C驱动实验、第六十二章 Linux SPI驱动实验、第六十三章 Linux RS232/485/GPS驱动实验、第六十六章 Linux CAN驱动实验、第六十七章 Linux USB驱动实验 最后更新: 2026-09-17