UART/SPI/I2C通信协议——Linux
1.UART
UART 协议介绍
UART (Universal Asynchronous Receiver/Transmitter) 是一种经典的异步串行通信协议,广泛应用于设备间短距离通信(如微控制器与传感器、PC与嵌入式设备)。其核心特点如下:
物理层:
- 通常使用 TX (发送线) 和 RX (接收线) 两根数据线(外加 GND)。
- 信号为 数字电平 (如 TTL: 0V 表示逻辑 0, 3.3V/5V 表示逻辑 1;RS-232: 负电压表示 1,正电压表示 0)。
- 需要 电平转换芯片 (如 MAX232, CH340) 连接不同电平标准的设备。
数据帧结构:
起始位 (Start Bit): 1 位逻辑低电平 (
0
),标志数据帧开始。数据位 (Data Bits): 5-9 位 (常用 8 位),传输实际数据(LSB 先行)。
校验位 (Parity Bit): 可选 (无、奇、偶校验),用于简单错误检测。
停止位 (Stop Bit(s)): 1 或 2 位逻辑高电平 (
1
),标志数据帧结束。示例(8N1格式):
传输字符'A'
(ASCII0x41
,二进制01000001
)的帧结构:[Start:0] [Data:1 0 0 0 0 0 1 0 (LSB优先)] [Parity:None] [Stop:1]
关键概念:
- 异步 (Asynchronous): 通信双方无共享时钟信号,依靠预定义的波特率 (Baud Rate) 同步时序。常见波特率:9600, 19200, 38400, 57600, 115200 等(bps - 每秒位数)。
- 双工 (Duplex): 通常是全双工 (Full Duplex),TX 和 RX 独立,可同时收发。
- 点对点 (Point-to-Point): 主要用于两个设备间通信。
通信流程:
- 发送端: 检测总线空闲(高电平)→ 拉低TXD(起始位)→ 依次发送数据位 → 发送校验位(如有)→ 拉高TXD(停止位)。
- 接收端: 检测到起始位下降沿 → 延时1.5个位时间(采样中点)→ 逐位采样数据 → 校验错误(如有)→ 检测停止位。
硬件实现
TTL/CMOS电平:
- 逻辑0:0V,逻辑1:3.3V/5V(直接连接MCU引脚)。
- 传输距离短(<1米),易受干扰。
RS-232电平:
- 逻辑0:+3V
+15V,逻辑1:-3V-15V(通过MAX232等芯片转换)。 - 抗干扰增强,传输距离延长至15米。
- 逻辑0:+3V
可选硬件流控(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*
。 - 解决方法:
使用
sudo
: 在命令前加sudo
(如sudo minicom
),适用于临时调试。修改设备权限: 使用
chmod
临时更改权限 (不推荐)。sudo chmod 666 /dev/ttyUSB0 # 让所有用户可读写 (重启后失效)
创建 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
命令查找设备的idVendor
和idProduct
。 - 将用户加入
dialout
组 (或uucp
组,依发行版而定):sudo usermod -aG dialout your_username
。 - 重启或重新加载 udev 规则 (
sudo udevadm control --reload-rules && sudo udevadm trigger
),然后重新插拔设备。
- 使用
5. 调试与测试
- 回环测试 (Loopback Test):
- 将串口适配器的 TX 短接到 RX。
- 发送的数据会立即被自己接收回来。这是验证串口硬件、驱动和配置是否正常的基本方法。
- 用
screen
、minicom
或自写程序发送字符,看是否能正确接收。
- 逻辑分析仪/示波器: 直接观察 TX/RX 线上的信号,确认物理层波形、波特率、数据帧是否正确。
- 查看系统日志:
dmesg | grep tty
或journalctl -k
查看内核关于串口设备初始化、插拔、错误的信息。 setserial
工具: 查询和设置更底层的串口参数(如 I/O 端口地址、IRQ),主要用于老式硬件串口 (/dev/ttyS*
)。
总结
在 Linux 中使用 UART 协议的关键是理解其作为 TTY 设备的表示方式。通过 /dev/ttyXxx
设备文件访问,使用 stty
、minicom
、screen
等命令行工具或 termios
(C) / pyserial
(Python) 等编程接口进行配置和通信。解决权限问题(推荐使用 udev 规则)和掌握基本调试方法(如回环测试)是顺利应用的基础。UART 因其简单可靠,在嵌入式系统开发、设备调试、传感器连接等场景中仍是不可或缺的技术。
2.SPI
SPI 协议介绍
SPI (Serial Peripheral Interface) 是一种高速、全双工的同步串行通信协议,由摩托罗拉开发,广泛应用于嵌入式系统中连接微控制器、传感器、存储器等外设。
核心特性
同步通信:
- 使用共享时钟信号 (SCLK) 同步数据传输
- 时钟由主设备 (Master) 控制,决定通信速度
主从架构:
- 一个主设备控制通信,可连接多个从设备 (Slave)
- 通过片选信号 (CS/SS) 选择目标从设备
信号线:
信号线 名称 方向 功能描述 SCLK 串行时钟 主→从 提供同步时钟信号 MOSI 主出从入 主→从 主设备发送数据,从设备接收 MISO 主入从出 从→主 从设备发送数据,主设备接收 CS/SS 片选/从机选择 主→从 选择目标从设备(低电平有效) 工作模式:
- 通过时钟极性 (CPOL) 和时钟相位 (CPHA) 定义四种模式:
模式 CPOL CPHA 时钟空闲状态 数据采样时刻 0 0 0 低电平 时钟上升沿 1 0 1 低电平 时钟下降沿 2 1 0 高电平 时钟下降沿 3 1 1 高电平 时钟上升沿
- 通过时钟极性 (CPOL) 和时钟相位 (CPHA) 定义四种模式:
数据传输:
- 每次传输以字节为单位(通常8位)
- 数据在时钟边沿采样,MSB(最高位)或LSB(最低位)先行可配置
- 全双工通信:发送和接收同时进行
优缺点:
- 优点:高速(可达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. 常用工具与调试
查看SPI设备:
ls /dev/spidev* # 列出可用SPI设备 ls /sys/bus/spi/devices/ # 查看内核识别的SPI设备
逻辑分析仪:
- 使用Saleae Logic或PulseView验证信号时序
- 检查时钟极性/相位、片选信号和数据波形
sysfs调试接口:
# 查看SPI控制器信息 cat /sys/bus/spi/devices/spi0.0/uevent # 查看支持的SPI模式 cat /sys/class/spi_master/spi0/device/mode
示波器测量:
- 验证时钟频率(SCLK)是否符合配置
- 检查MOSI/MISO信号质量
- 确认片选信号在传输期间保持低电平
6. 性能优化技巧
DMA传输:
- 对于大数据传输,使用
SPI_IOC_MESSAGE
配合DMA
struct spi_ioc_transfer tr = { .tx_buf = tx_buf_addr, .len = len, .tx_dma = dma_addr_tx, // 启用DMA };
- 对于大数据传输,使用
双缓冲机制:
- 在驱动中实现ping-pong缓冲区减少延迟
高速模式选择:
// 在设备树中指定高速模式 spi-tx-rx; // 启用双线模式(如果外设支持) spi-3wire; // 启用三线模式(共享MISO/MOSI)
实时性保证:
// 设置高优先级SPI线程 sched_setscheduler(current, SCHED_FIFO, ¶m);
典型应用场景
- 存储设备:NOR/NAND Flash (Winbond, Macronix)
- 传感器:加速度计(ADXL345)、陀螺仪(MPU6050)、温湿度传感器
- 显示接口:OLED屏幕(SSD1306)、TFT液晶控制器
- 通信模块:LoRa无线电(SX1276)、2.4GHz收发器(nRF24L01+)
- ADC/DAC:MCP3008(ADC), MCP4921(DAC)
通过灵活运用Linux的SPI子系统,开发者可以高效连接各种外设,满足从低速传感器到高速存储器的多样化需求。
3.I2C
I2C协议介绍
I2C (Inter-Integrated Circuit) 是一种广泛使用的同步、半双工串行通信协议,由飞利浦(现恩智浦)开发。它以其简单的两线设计和多主从架构而闻名,常用于连接低速外设(如传感器、EEPROM、RTC等)。
核心特性
两线制:
- SDA (Serial Data Line):双向数据线
- SCL (Serial Clock Line):时钟信号线,由主设备控制
- 所有设备共享总线,通过上拉电阻保持高电平
多主从架构:
- 支持多个主设备和多个从设备(通过地址识别)
- 7位地址模式(128个地址)或10位地址模式(1024个地址)
- 地址0x00为广播地址(所有设备响应)
通信过程:
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)
关键信号:
- 起始条件 (Start):SCL高电平时,SDA从高→低
- 停止条件 (Stop):SCL高电平时,SDA从低→高
- ACK/NACK:每字节后接收方拉低SDA(ACK)或保持高电平(NACK)
速度模式:
模式 速度 标准模式 100 kbps 快速模式 400 kbps 快速模式+ 1 Mbps 高速模式 3.4 Mbps 优缺点:
- 优点:引脚少、支持多设备、有硬件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. 调试与问题排查
硬件检测:
# 查看I2C适配器 ls /sys/bus/i2c/devices/i2c-* # 检查设备是否被识别 dmesg | grep i2c
信号质量检测:
- 使用示波器检查SDA/SCL信号
- 检查上拉电阻(通常4.7kΩ)
- 确认总线电容 < 400pF
常见问题解决:
- 设备未响应:检查地址、电源、物理连接
- ACK丢失:缩短总线长度,减小上拉电阻值
- 数据损坏:降低时钟频率,添加屏蔽
动态调试:
# 启用I2C调试日志 echo 8 > /sys/module/i2c_core/parameters/debug dmesg -w
6. 高级特性
SMBus协议:
- I2C的子集,增加超时和协议规范
- 使用
i2c_smbus_*
函数族
多主设备仲裁:
- 当多个主设备同时启动传输时,硬件自动仲裁
- 先发送低电平的设备赢得总线控制权
时钟延展:
// 在驱动中处理时钟延展 struct i2c_adapter *adap = client->adapter; adap->bus_lock(adap); // 锁定总线 // 执行敏感操作 adap->bus_unlock(adap);
10位地址支持:
// 使用10位地址 struct i2c_client *client; client->flags |= I2C_CLIENT_TEN;
7. 性能优化
批量传输:
u8 buffer[32]; struct i2c_msg msg = { .addr = client->addr, .flags = 0, .len = sizeof(buffer), .buf = buffer, }; i2c_transfer(client->adapter, &msg, 1);
DMA传输:
msg.flags |= I2C_M_DMA_SAFE;
时钟频率调整:
// 在设备树中指定 clock-frequency = <1000000>; // 1MHz
典型应用场景
- 传感器:温度/湿度(BME280)、加速度计(MPU6050)
- RTC时钟:DS1307、PCF8563
- EEPROM存储器:AT24C系列
- 电源管理:PMIC配置
- 触摸控制器:FT5x06
- LED控制器:PCA9633
通过Linux的I2C子系统,开发者可以高效访问各种I2C设备,从简单的用户空间工具到复杂的内核驱动,满足不同层次的开发需求。