1.UART

UART 协议介绍

UART (Universal Asynchronous Receiver/Transmitter) 是一种经典的异步串行通信协议,广泛应用于设备间短距离通信(如微控制器与传感器、PC与嵌入式设备)。其核心特点如下:

  1. 物理层

    • 通常使用 TX (发送线)RX (接收线) 两根数据线(外加 GND)。
    • 信号为 数字电平 (如 TTL: 0V 表示逻辑 0, 3.3V/5V 表示逻辑 1;RS-232: 负电压表示 1,正电压表示 0)。
    • 需要 电平转换芯片 (如 MAX232, CH340) 连接不同电平标准的设备。
  2. 数据帧结构

    • 起始位 (Start Bit): 1 位逻辑低电平 (0),标志数据帧开始。

    • 数据位 (Data Bits): 5-9 位 (常用 8 位),传输实际数据(LSB 先行)。

    • 校验位 (Parity Bit): 可选 (无、奇、偶校验),用于简单错误检测。

    • 停止位 (Stop Bit(s)): 1 或 2 位逻辑高电平 (1),标志数据帧结束。

      示例(8N1格式)
      传输字符 'A'(ASCII 0x41,二进制 01000001)的帧结构:
      [Start:0] [Data:1 0 0 0 0 0 1 0 (LSB优先)] [Parity:None] [Stop:1]

  3. 关键概念

    • 异步 (Asynchronous): 通信双方无共享时钟信号,依靠预定义的波特率 (Baud Rate) 同步时序。常见波特率:9600, 19200, 38400, 57600, 115200 等(bps - 每秒位数)。
    • 双工 (Duplex): 通常是全双工 (Full Duplex),TX 和 RX 独立,可同时收发。
    • 点对点 (Point-to-Point): 主要用于两个设备间通信。
  4. 通信流程

    • 发送端: 检测总线空闲(高电平)→ 拉低TXD(起始位)→ 依次发送数据位 → 发送校验位(如有)→ 拉高TXD(停止位)。
    • 接收端: 检测到起始位下降沿 → 延时1.5个位时间(采样中点)→ 逐位采样数据 → 校验错误(如有)→ 检测停止位。
  5. 硬件实现

    • TTL/CMOS电平

      • 逻辑0:0V,逻辑1:3.3V/5V(直接连接MCU引脚)。
      • 传输距离短(<1米),易受干扰。
    • RS-232电平

      • 逻辑0:+3V+15V,逻辑1:-3V-15V(通过MAX232等芯片转换)。
      • 抗干扰增强,传输距离延长至15米。
    • 可选硬件流控(CTS/RTS):

      • RTS(Request to Send):接收方准备好时拉低。
      • CTS(Clear to Send):发送方检测到低电平时发送数据。
    • 避免缓冲区溢出(如高速传输时MCU处理不及)。

优点: 简单、成本低、实现广泛、适合低速可靠通信。

缺点: 无时钟同步(依赖精确波特率)、无寻址机制(点对点)、无高级错误校验/流控(需软件实现)。


Linux 中的应用

Linux 将 UART 设备视为 TTY (Teletype) 设备。系统通过 tty 子系统 管理串口设备。

1. 识别串口设备

  • 物理串口 (主板原生):通常命名为 /dev/ttyS0, /dev/ttyS1, ...

  • USB 转串口适配器:通常命名为 /dev/ttyUSB0, /dev/ttyUSB1, ...

  • 树莓派等开发板上的串口:/dev/ttyAMA0 (硬件 UART), /dev/ttyS0 (miniUART) 等。

  • 查看设备

    dmesg | grep tty  # 查看内核启动日志中的串口信息
    ls /dev/tty*      # 列出当前系统中的 tty 设备
    

2. 配置串口参数

使用 stty 命令或程序代码配置波特率、数据位、停止位、校验位、流控等。

  • 命令行配置 (stty)

    stty -F /dev/ttyUSB0 115200 cs8 -cstopb -parenb
    # 解释:
    #   -F /dev/ttyUSB0: 指定设备文件
    #   115200:         设置波特率为 115200
    #   cs8:            设置数据位为 8 位
    #   -cstopb:        设置 1 个停止位 (设置 +cstopb 为 2 个)
    #   -parenb:        禁用校验位 (设置 +parenb 启用,+parodd 为奇校验)
    #   其他常用选项:  ignbrk - 忽略 BREAK 信号, -brkint - 不因 BREAK 产生中断, -icrnl - 不将回车 CR 转为换行 NL, -imaxbel - 输入缓冲区满时不响铃, -opost - 禁用输出处理, -onlcr - 不将换行 NL 转为回车换行 CR-NL, -isig - 不使能信号字符, -icanon - 禁用规范模式 (行缓冲), -iexten - 禁用扩展输入处理, -echo - 禁用回显, -echoe - 不将 ERASE 字符回显为退格符, -echok - 在 KILL 字符后不输出换行, -noflsh - 在中断或退出后不清除输入输出队列
    
  • 查看当前配置

    stty -F /dev/ttyUSB0 -a
    

3. 访问串口设备

  • 命令行工具

    • screen: 简单连接并交互。

      screen /dev/ttyUSB0 115200
      # 退出: Ctrl-A 然后按 K (确认), 或 Ctrl-A 然后按 : 输入 quit
      
    • minicom: 功能强大的串口终端程序 (需安装 minicom 包)。

      sudo apt install minicom  # Debian/Ubuntu
      minicom -D /dev/ttyUSB0 -b 115200
      # 退出: 先按 Ctrl-A, 然后按 X
      
    • picocom: 更轻量、简单的替代品 (需安装 picocom 包)。

      sudo apt install picocom  # Debian/Ubuntu
      picocom -b 115200 /dev/ttyUSB0
      # 退出: Ctrl-A 然后 Ctrl-Q
      
    • echo / cat: 基本读写。

      echo "Hello UART" > /dev/ttyUSB0  # 写入数据
      cat < /dev/ttyUSB0               # 读取数据 (会一直显示)
      
  • 编程访问 (C/C++/Python/等): 打开设备文件 (/dev/ttyXxx),使用 termios 库配置串口参数,然后用标准文件 I/O (read, write) 读写。

    C 语言示例代码片段

    #include <stdio.h>
    #include <fcntl.h>
    #include <termios.h>
    #include <unistd.h>
    
    int main() {
        int serial_port = open("/dev/ttyUSB0", O_RDWR | O_NOCTTY | O_NDELAY);
        if (serial_port < 0) {
            perror("Error opening port");
            return -1;
        }
    
        // 获取当前配置
        struct termios tty;
        if (tcgetattr(serial_port, &tty) != 0) {
            perror("Error from tcgetattr");
            return -1;
        }
    
        // 设置波特率 (输入和输出)
        cfsetospeed(&tty, B115200);
        cfsetispeed(&tty, B115200);
    
        // 配置数据位、停止位、校验位
        tty.c_cflag &= ~PARENB; // 禁用奇偶校验
        tty.c_cflag &= ~CSTOPB; // 1 个停止位
        tty.c_cflag &= ~CSIZE;
        tty.c_cflag |= CS8;     // 8 位数据位
    
        tty.c_cflag &= ~CRTSCTS; // 禁用硬件流控 (RTS/CTS)
        tty.c_cflag |= CREAD | CLOCAL; // 启用接收器,忽略 modem 控制线
    
        // 配置输入模式 (非规范模式,无回显等)
        tty.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG);
        // 配置输出模式 (原始输出)
        tty.c_oflag &= ~OPOST;
    
        // 设置超时和最小读取字符数 (VMIN, VTIME)
        tty.c_cc[VMIN] = 0;   // 非阻塞读 (read 立即返回)
        tty.c_cc[VTIME] = 10; // 等待数据的时间 (单位: 0.1秒)
    
        // 应用新配置
        if (tcsetattr(serial_port, TCSANOW, &tty) != 0) {
            perror("Error from tcsetattr");
            return -1;
        }
    
        // 写入数据
        char msg[] = "Hello from Linux!\n";
        write(serial_port, msg, sizeof(msg) - 1);
    
        // 读取数据 (示例)
        char buf[256];
        int n = read(serial_port, &buf, sizeof(buf));
        if (n > 0) {
            buf[n] = '\0';
            printf("Received: %s\n", buf);
        }
    
        close(serial_port);
        return 0;
    }
    

    Python 示例 (使用 pyserial 库)

    import serial
    
    # 打开串口
    ser = serial.Serial(
        port='/dev/ttyUSB0',
        baudrate=115200,
        bytesize=serial.EIGHTBITS,
        parity=serial.PARITY_NONE,
        stopbits=serial.STOPBITS_ONE,
        timeout=1.0  # 秒 (读超时)
    )
    
    # 写入数据
    ser.write(b"Hello Serial\n")
    
    # 读取一行数据
    line = ser.readline()
    print("Received:", line.decode('utf-8').strip())
    
    ser.close()
    

4. 权限问题

  • 普通用户通常无权限直接访问 /dev/ttyS*/dev/ttyUSB*
  • 解决方法
    1. 使用 sudo: 在命令前加 sudo (如 sudo minicom),适用于临时调试。

    2. 修改设备权限: 使用 chmod 临时更改权限 (不推荐)。

      sudo chmod 666 /dev/ttyUSB0  # 让所有用户可读写 (重启后失效)
      
    3. 创建 udev 规则 (推荐): 在 /etc/udev/rules.d/ 下创建规则文件 (如 99-usb-serial.rules),自动设置特定串口的组权限。

      # 示例规则: 将特定 VendorID/ProductID 的 USB 串口设备权限设为组 dialout
      SUBSYSTEM=="tty", ATTRS{idVendor}=="0403", ATTRS{idProduct}=="6001", GROUP="dialout", MODE="0660"
      
      • 使用 lsusb 命令查找设备的 idVendoridProduct
      • 将用户加入 dialout 组 (或 uucp 组,依发行版而定):sudo usermod -aG dialout your_username
      • 重启或重新加载 udev 规则 (sudo udevadm control --reload-rules && sudo udevadm trigger),然后重新插拔设备。

5. 调试与测试

  1. 回环测试 (Loopback Test)
    • 将串口适配器的 TX 短接到 RX
    • 发送的数据会立即被自己接收回来。这是验证串口硬件、驱动和配置是否正常的基本方法。
    • screenminicom 或自写程序发送字符,看是否能正确接收。
  2. 逻辑分析仪/示波器: 直接观察 TX/RX 线上的信号,确认物理层波形、波特率、数据帧是否正确。
  3. 查看系统日志dmesg | grep ttyjournalctl -k 查看内核关于串口设备初始化、插拔、错误的信息。
  4. setserial 工具: 查询和设置更底层的串口参数(如 I/O 端口地址、IRQ),主要用于老式硬件串口 (/dev/ttyS*)。

总结

在 Linux 中使用 UART 协议的关键是理解其作为 TTY 设备的表示方式。通过 /dev/ttyXxx 设备文件访问,使用 sttyminicomscreen 等命令行工具或 termios (C) / pyserial (Python) 等编程接口进行配置和通信。解决权限问题(推荐使用 udev 规则)和掌握基本调试方法(如回环测试)是顺利应用的基础。UART 因其简单可靠,在嵌入式系统开发、设备调试、传感器连接等场景中仍是不可或缺的技术。

2.SPI

SPI 协议介绍

SPI (Serial Peripheral Interface) 是一种高速、全双工的同步串行通信协议,由摩托罗拉开发,广泛应用于嵌入式系统中连接微控制器、传感器、存储器等外设。

核心特性

  1. 同步通信

    • 使用共享时钟信号 (SCLK) 同步数据传输
    • 时钟由主设备 (Master) 控制,决定通信速度
  2. 主从架构

    • 一个主设备控制通信,可连接多个从设备 (Slave)
    • 通过片选信号 (CS/SS) 选择目标从设备
  3. 信号线

    信号线 名称 方向 功能描述
    SCLK 串行时钟 主→从 提供同步时钟信号
    MOSI 主出从入 主→从 主设备发送数据,从设备接收
    MISO 主入从出 从→主 从设备发送数据,主设备接收
    CS/SS 片选/从机选择 主→从 选择目标从设备(低电平有效)
  4. 工作模式

    • 通过时钟极性 (CPOL) 和时钟相位 (CPHA) 定义四种模式:
      模式 CPOL CPHA 时钟空闲状态 数据采样时刻
      0 0 0 低电平 时钟上升沿
      1 0 1 低电平 时钟下降沿
      2 1 0 高电平 时钟下降沿
      3 1 1 高电平 时钟上升沿
  5. 数据传输

    • 每次传输以字节为单位(通常8位)
    • 数据在时钟边沿采样,MSB(最高位)或LSB(最低位)先行可配置
    • 全双工通信:发送和接收同时进行
  6. 优缺点

    • 优点:高速(可达100Mbps+)、全双工、简单硬件实现
    • 缺点:需要更多引脚(每个从设备需独立CS)、无硬件错误检测、无流控机制

Linux 中的 SPI 应用

1. 系统架构

用户空间
    ├── /dev/spidevX.Y 字符设备 (通过ioctl访问)
    ├── sysfs 接口 (/sys/class/spi_master)
    └── SPI设备驱动 (如eeprom, sensors)
内核空间
    ├── SPI 核心层 (drivers/spi/spi.c)
    ├── SPI 控制器驱动 (如davinci, bcm2835)
    └── 设备树 (描述硬件连接)
硬件层
    └── SPI 控制器 (SoC内置) + 外设

2. 硬件配置(设备树)

在设备树中声明SPI控制器和外设:

/* 示例:树莓派 SPI1 配置 */
&spi1 {
    pinctrl-names = "default";
    pinctrl-0 = <&spi1_pins>;
    status = "okay";
    cs-gpios = <&gpio 18 GPIO_ACTIVE_LOW>; // CS0 使用GPIO18

    /* 连接SPI Flash */
    flash: w25q128@0 {
        compatible = "winbond,w25q128";
        reg = <0>;          // CS0
        spi-max-frequency = <50000000>; // 50MHz
    };

    /* 连接温度传感器 */
    temp_sensor: mcp3008@1 {
        compatible = "microchip,mcp3008";
        reg = <1>;          // CS1
        spi-max-frequency = <1000000>; // 1MHz
        vref-supply = <&vdd_3v3>;
    };
};

3. 用户空间访问

通过 /dev/spidevX.Y 设备文件访问(X=总线号,Y=片选号)

使用 spidev_test 工具

# 发送十六进制数据 0x01 0x02 0x03 并读取3字节
spidev_test -D /dev/spidev0.0 -s 1000000 -v -p "\x01\x02\x03"

# 参数说明:
#   -D   SPI设备文件
#   -s   波特率 (1MHz)
#   -v   详细输出
#   -p   发送数据 (十六进制格式)

Python 示例 (使用 spidev 库)

import spidev

# 初始化SPI
spi = spidev.SpiDev()
spi.open(0, 0)          # 打开总线0,设备0
spi.max_speed_hz = 500000 # 500kHz
spi.mode = 0b00         # 模式0 (CPOL=0, CPHA=0)

# 发送并接收3字节
tx_data = [0x01, 0x80, 0x00] 
rx_data = spi.xfer(tx_data) 

print(f"Received: {bytes(rx_data).hex()}")
spi.close()

C 语言示例

#include <fcntl.h>
#include <linux/spi/spidev.h>
#include <sys/ioctl.h>

int main() {
    int fd = open("/dev/spidev0.0", O_RDWR);
    
    // 设置SPI模式
    uint8_t mode = SPI_MODE_0;
    ioctl(fd, SPI_IOC_WR_MODE, &mode);
    
    // 设置波特率
    uint32_t speed = 1000000; // 1MHz
    ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
    
    // 准备传输数据
    uint8_t tx[] = {0x01, 0x02, 0x03};
    uint8_t rx[3] = {0};
    struct spi_ioc_transfer tr = {
        .tx_buf = (unsigned long)tx,
        .rx_buf = (unsigned long)rx,
        .len = sizeof(tx),
        .delay_usecs = 10,
    };
    
    // 执行传输
    ioctl(fd, SPI_IOC_MESSAGE(1), &tr);
    close(fd);
    return 0;
}

4. 内核驱动开发

为SPI设备编写内核驱动:

#include <linux/spi/spi.h>

static struct spi_device *spi_device;

// 探测函数(设备初始化)
static int mydev_probe(struct spi_device *spi)
{
    // 1. 获取SPI配置
    spi->mode = SPI_MODE_0;
    spi->max_speed_hz = 5000000; // 5MHz
    spi_setup(spi);
    
    // 2. 初始化设备硬件
    const u8 init_cmd[] = {0xAB, 0xCD};
    spi_write(spi, init_cmd, sizeof(init_cmd));
    
    // 3. 注册字符设备/创建sysfs节点等
    // ...
    return 0;
}

// 移除函数
static int mydev_remove(struct spi_device *spi)
{
    // 清理资源
    return 0;
}

// 设备ID表
static const struct spi_device_id mydev_id[] = {
    { "my-spi-device", 0 },
    { }
};
MODULE_DEVICE_TABLE(spi, mydev_id);

// 设备树匹配表
static const struct of_device_id mydev_of_match[] = {
    { .compatible = "vendor,my-spi-device" },
    { }
};
MODULE_DEVICE_TABLE(of, mydev_of_match);

// SPI驱动结构
static struct spi_driver mydev_driver = {
    .driver = {
        .name = "my_spi_driver",
        .of_match_table = mydev_of_match,
    },
    .probe = mydev_probe,
    .remove = mydev_remove,
    .id_table = mydev_id,
};

module_spi_driver(mydev_driver);

5. 常用工具与调试

  1. 查看SPI设备

    ls /dev/spidev*  # 列出可用SPI设备
    ls /sys/bus/spi/devices/ # 查看内核识别的SPI设备
    
  2. 逻辑分析仪

    • 使用Saleae Logic或PulseView验证信号时序
    • 检查时钟极性/相位、片选信号和数据波形
  3. sysfs调试接口

    # 查看SPI控制器信息
    cat /sys/bus/spi/devices/spi0.0/uevent
    
    # 查看支持的SPI模式
    cat /sys/class/spi_master/spi0/device/mode
    
  4. 示波器测量

    • 验证时钟频率(SCLK)是否符合配置
    • 检查MOSI/MISO信号质量
    • 确认片选信号在传输期间保持低电平

6. 性能优化技巧

  1. DMA传输

    • 对于大数据传输,使用SPI_IOC_MESSAGE配合DMA
    struct spi_ioc_transfer tr = {
        .tx_buf = tx_buf_addr,
        .len = len,
        .tx_dma = dma_addr_tx, // 启用DMA
    };
    
  2. 双缓冲机制

    • 在驱动中实现ping-pong缓冲区减少延迟
  3. 高速模式选择

    // 在设备树中指定高速模式
    spi-tx-rx; // 启用双线模式(如果外设支持)
    spi-3wire; // 启用三线模式(共享MISO/MOSI)
    
  4. 实时性保证

    // 设置高优先级SPI线程
    sched_setscheduler(current, SCHED_FIFO, &param);
    

典型应用场景

  1. 存储设备:NOR/NAND Flash (Winbond, Macronix)
  2. 传感器:加速度计(ADXL345)、陀螺仪(MPU6050)、温湿度传感器
  3. 显示接口:OLED屏幕(SSD1306)、TFT液晶控制器
  4. 通信模块:LoRa无线电(SX1276)、2.4GHz收发器(nRF24L01+)
  5. ADC/DAC:MCP3008(ADC), MCP4921(DAC)

通过灵活运用Linux的SPI子系统,开发者可以高效连接各种外设,满足从低速传感器到高速存储器的多样化需求。

3.I2C

I2C协议介绍

I2C (Inter-Integrated Circuit) 是一种广泛使用的同步、半双工串行通信协议,由飞利浦(现恩智浦)开发。它以其简单的两线设计和多主从架构而闻名,常用于连接低速外设(如传感器、EEPROM、RTC等)。

核心特性

  1. 两线制

    • SDA (Serial Data Line):双向数据线
    • SCL (Serial Clock Line):时钟信号线,由主设备控制
    • 所有设备共享总线,通过上拉电阻保持高电平
  2. 多主从架构

    • 支持多个主设备和多个从设备(通过地址识别)
    • 7位地址模式(128个地址)或10位地址模式(1024个地址)
    • 地址0x00为广播地址(所有设备响应)
  3. 通信过程

    sequenceDiagram
        Master->>+Slave: 起始条件 (S)
        Master->>Slave: 发送地址 + R/W位
        Slave-->>Master: ACK (应答)
        Master->>Slave: 发送寄存器地址 (可选)
        Slave-->>Master: ACK
        Master->>Slave: 发送/接收数据
        Slave-->>Master: ACK/NACK
        Master->>Slave: 停止条件 (P)
    
  4. 关键信号

    • 起始条件 (Start):SCL高电平时,SDA从高→低
    • 停止条件 (Stop):SCL高电平时,SDA从低→高
    • ACK/NACK:每字节后接收方拉低SDA(ACK)或保持高电平(NACK)
  5. 速度模式

    模式 速度
    标准模式 100 kbps
    快速模式 400 kbps
    快速模式+ 1 Mbps
    高速模式 3.4 Mbps
  6. 优缺点

    • 优点:引脚少、支持多设备、有硬件ACK
    • 缺点:半双工、速度较低、总线电容限制设备数量

Linux中的I2C应用

1. 系统架构

用户空间
    ├── /dev/i2c-X 字符设备
    ├── sysfs接口 (/sys/bus/i2c/devices/)
    └── i2c-tools工具
内核空间
    ├── I2C核心层 (drivers/i2c/i2c-core.c)
    ├── I2C适配器驱动 (控制器驱动)
    └── I2C设备驱动 (如eeprom, sensors)
硬件层
    └── I2C控制器 + 外设

2. 硬件配置(设备树)

/* 树莓派I2C1配置示例 */
&i2c1 {
    pinctrl-names = "default";
    pinctrl-0 = <&i2c1_pins>;
    clock-frequency = <400000>; // 400kHz
    status = "okay";

    /* 温度传感器 */
    temp_sensor: lm75@48 {
        compatible = "national,lm75";
        reg = <0x48>; // 7位地址
    };

    /* EEPROM */
    eeprom: at24c32@50 {
        compatible = "atmel,24c32";
        reg = <0x50>;
        pagesize = <32>;
    };
};

3. 用户空间访问

使用i2c-tools

# 安装工具
sudo apt install i2c-tools

# 扫描I2C总线上的设备
i2cdetect -y 1  # 扫描总线1

# 输出示例:
#      0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
# 00:                         -- -- -- -- -- -- -- -- 
# 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 
# 50: 50 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 
# 70: -- -- -- -- -- -- -- --  

# 读取设备寄存器
i2cget -y 1 0x48 0x00  # 从地址0x48读取寄存器0x00

# 写入设备寄存器
i2cset -y 1 0x50 0x00 0x12  # 向地址0x50的寄存器0x00写入0x12

# 连续读取
i2cdump -y 1 0x50 b  # 以字节形式显示EEPROM内容

Python示例(使用smbus2库)

from smbus2 import SMBus, i2c_msg

with SMBus(1) as bus:  # 打开I2C总线1
    # 写入后读取:向寄存器0x00写入,然后读取2字节
    write = i2c_msg.write(0x48, [0x00])
    read = i2c_msg.read(0x48, 2)
    bus.i2c_rdwr(write, read)
    
    # 转换读取的数据(假设为16位温度值)
    data = list(read)
    temp = (data[0] << 8) | data[1]
    print(f"Temperature: {temp/256:.2f}°C")
    
    # 写入EEPROM (地址0x50)
    bus.write_byte_data(0x50, 0x00, 0xAB)  # 地址0x00写入0xAB

C语言示例

#include <linux/i2c-dev.h>
#include <fcntl.h>
#include <unistd.h>

int main() {
    int file;
    char filename[20];
    unsigned char buffer[10];
    
    // 打开I2C总线
    snprintf(filename, 19, "/dev/i2c-1");
    file = open(filename, O_RDWR);
    
    // 设置从设备地址
    ioctl(file, I2C_SLAVE, 0x48);
    
    // 写入寄存器地址
    buffer[0] = 0x00;  // 温度寄存器
    write(file, buffer, 1);
    
    // 读取2字节温度数据
    read(file, buffer, 2);
    int temp = (buffer[0] << 8) | buffer[1];
    
    // EEPROM写入示例
    ioctl(file, I2C_SLAVE, 0x50);
    buffer[0] = 0x00;  // 地址
    buffer[1] = 0xAB;  // 数据
    write(file, buffer, 2);
    
    close(file);
    return 0;
}

4. 内核驱动开发

I2C设备驱动示例

#include <linux/i2c.h>
#include <linux/module.h>

static int sensor_probe(struct i2c_client *client,
                       const struct i2c_device_id *id)
{
    struct device *dev = &client->dev;
    
    // 读取设备ID验证连接
    u8 dev_id = i2c_smbus_read_byte_data(client, 0x0F);
    if (dev_id != 0xA0) {
        dev_err(dev, "Device ID mismatch: 0x%02X\n", dev_id);
        return -ENODEV;
    }
    
    // 初始化设备
    i2c_smbus_write_byte_data(client, 0x20, 0x0F); // 配置寄存器
    
    // 创建sysfs接口或字符设备
    // ...
    return 0;
}

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

static struct i2c_driver sensor_driver = {
    .driver = {
        .name = "lm75_driver",
    },
    .probe = sensor_probe,
    .id_table = sensor_id,
};

module_i2c_driver(sensor_driver);

5. 调试与问题排查

  1. 硬件检测

    # 查看I2C适配器
    ls /sys/bus/i2c/devices/i2c-*
    
    # 检查设备是否被识别
    dmesg | grep i2c
    
  2. 信号质量检测

    • 使用示波器检查SDA/SCL信号
    • 检查上拉电阻(通常4.7kΩ)
    • 确认总线电容 < 400pF
  3. 常见问题解决

    • 设备未响应:检查地址、电源、物理连接
    • ACK丢失:缩短总线长度,减小上拉电阻值
    • 数据损坏:降低时钟频率,添加屏蔽
  4. 动态调试

    # 启用I2C调试日志
    echo 8 > /sys/module/i2c_core/parameters/debug
    dmesg -w
    

6. 高级特性

  1. SMBus协议

    • I2C的子集,增加超时和协议规范
    • 使用i2c_smbus_*函数族
  2. 多主设备仲裁

    • 当多个主设备同时启动传输时,硬件自动仲裁
    • 先发送低电平的设备赢得总线控制权
  3. 时钟延展

    // 在驱动中处理时钟延展
    struct i2c_adapter *adap = client->adapter;
    adap->bus_lock(adap);  // 锁定总线
    // 执行敏感操作
    adap->bus_unlock(adap);
    
  4. 10位地址支持

    // 使用10位地址
    struct i2c_client *client;
    client->flags |= I2C_CLIENT_TEN;
    

7. 性能优化

  1. 批量传输

    u8 buffer[32];
    struct i2c_msg msg = {
        .addr = client->addr,
        .flags = 0,
        .len = sizeof(buffer),
        .buf = buffer,
    };
    i2c_transfer(client->adapter, &msg, 1);
    
  2. DMA传输

    msg.flags |= I2C_M_DMA_SAFE;
    
  3. 时钟频率调整

    // 在设备树中指定
    clock-frequency = <1000000>; // 1MHz
    

典型应用场景

  1. 传感器:温度/湿度(BME280)、加速度计(MPU6050)
  2. RTC时钟:DS1307、PCF8563
  3. EEPROM存储器:AT24C系列
  4. 电源管理:PMIC配置
  5. 触摸控制器:FT5x06
  6. LED控制器:PCA9633

通过Linux的I2C子系统,开发者可以高效访问各种I2C设备,从简单的用户空间工具到复杂的内核驱动,满足不同层次的开发需求。