尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

I2C与SPI设备驱动实战:嵌入式Linux下的外设通信协议开发完整指南

I2C与SPI设备驱动实战:嵌入式Linux下的外设通信协议开发完整指南
📅 发布时间:2026/7/10 22:14:18

I2C与SPI设备驱动实战:嵌入式Linux下的外设通信协议开发完整指南

一、总线选择的工程决策:I2C与SPI的本质差异

I2C和SPI是嵌入式Linux中最常见的两种外设通信总线,但很多选型决策停留在"看引脚数"的直觉层面。实际上,两条总线在物理层、协议层和软件栈上都有本质差异,选型需要从四个核心维度综合评估:引脚数量、通信速率、拓扑复杂度和内核驱动的成熟度。

flowchart TD A[外设通信需求分析] --> B{MCU引脚资源紧张?} B -->|是,仅2根可用| C{速率要求} B -->|否,引脚充裕| D{速率要求与实时性} C -->|低于400kbps| E[I2C: 2线SDA+SCL] C -->|超过1Mbps| F[SPI: 至少4线MISO+MOSI+SCLK+CS] D -->|低于10Mbps| G{拓扑复杂度分析} D -->|高于10Mbps| F G -->|多从设备共享总线| E G -->|单从设备独占或星型连接| H[SPI也可选用] E --> I[设备树配置与驱动编写] F --> I H --> I I --> J[集成测试:逻辑分析仪验证] style E fill:#3498db,color:#fff style F fill:#e67e22,color:#fff style J fill:#27ae60,color:#fff

在实际工程经验中,规则非常清晰:温度、湿度、加速度、陀螺仪等传感器几乎都用I2C,因为它们的数据量小(每帧几个字节)、不需要高速率(400kHz以内足够)、总线上可以挂多个设备(最多127个,受7位地址限制)。而LCD屏幕、SPI Flash、高速ADC/DAC等外设几乎必用SPI,因为它们的吞吐量要求(1~50MHz)远超I2C的能力范围,而且通常是控制器的独占设备。

I2C的另一个优势是硬件实现简单——两根线加两个上拉电阻即可。但这恰恰也是I2C最常见故障的根源:上拉电阻的阻值选择影响总线速率和抗干扰能力。SPI不需要上拉电阻,但它的多线结构(最少4线,带中断可能到6线)在PCB布局时需要更谨慎的布线规划。

二、I2C设备驱动的标准框架与关键细节

I2C驱动在Linux内核中有非常成熟的标准框架:i2c_driver结构体是核心入口,probe函数负责设备初始化和资源申请,remove负责清理,id_table和of_match_table负责设备和驱动的匹配。

/* i2c_temp_sensor.c — I2C温度传感器完整驱动 */ #include <linux/i2c.h> #include <linux/module.h> #include <linux/of.h> #include <linux/sysfs.h> #include <linux/delay.h> #define SENSOR_REG_TEMP 0x00 #define SENSOR_REG_CONF 0x01 #define SENSOR_REG_HYST 0x02 #define SENSOR_I2C_ADDR 0x48 #define SENSOR_CONV_DELAY 10 /* ADC转换等待时间ms */ struct sensor_data { struct i2c_client *client; struct mutex lock; int last_temp; /* 单位: m°C */ }; static int sensor_write_reg( struct i2c_client *client, u8 reg, u8 value ) { u8 buf[2] = {reg, value}; int ret; ret = i2c_master_send(client, buf, 2); if (ret != 2) { dev_err(&client->dev, "I2C write reg 0x%02x failed: %d\n", reg, ret); return ret < 0 ? ret : -EIO; } return 0; } static int sensor_read_temp( struct i2c_client *client, int *temp ) { u8 reg = SENSOR_REG_TEMP; u8 buf[2] = {0}; int ret; /* 第一步:发出寄存器地址 */ ret = i2c_master_send(client, &reg, 1); if (ret < 0) { dev_err(&client->dev, "I2C write reg addr failed: %d\n", ret); return ret; } /* 等待ADC完成转换 */ msleep(SENSOR_CONV_DELAY); /* 第二步:读取温度值(2字节大端序) */ ret = i2c_master_recv(client, buf, 2); if (ret < 0) { dev_err(&client->dev, "I2C read temp failed: %d\n", ret); return ret; } /* 将2字节原始值拼为16位有符号整数 */ *temp = (buf[0] << 8) | buf[1]; /* 补码转有符号(如果最高位为1) */ if (*temp & 0x8000) *temp -= 65536; /* 典型传感器:12位精度,LSB=0.0625°C */ /* 转换为毫摄氏度(m°C) */ *temp = (*temp * 625) / 10; return 0; } static int sensor_probe(struct i2c_client *client) { struct sensor_data *data; struct device *dev = &client->dev; int ret, temp; /* 使用设备资源管理分配,无需手动释放 */ data = devm_kzalloc( dev, sizeof(*data), GFP_KERNEL ); if (!data) return -ENOMEM; >/* spi_flash.c — SPI Flash驱动(含DMA) */ #include <linux/spi/spi.h> #include <linux/dma-mapping.h> #define FLASH_CMD_READ 0x03 #define FLASH_CMD_WREN 0x06 #define FLASH_PAGE_SIZE 256 #define FLASH_SPI_SPEED 20000000 /* 20MHz */ struct flash_data { struct spi_device *spi; struct mutex lock; u8 *tx_buf; u8 *rx_buf; dma_addr_t tx_dma; dma_addr_t rx_dma; }; static void flash_setup_spi(struct spi_device *spi) { /* 配置SPI工作模式: * MODE0: CPOL=0, CPHA=0 * 即空闲时钟低电平,上升沿采样 */ spi->mode = SPI_MODE_0; spi->bits_per_word = 8; spi->max_speed_hz = FLASH_SPI_SPEED; if (spi_setup(spi)) dev_err(&spi->dev, "SPI setup failed\n"); } static int flash_read_page( struct spi_device *spi, u32 addr, u8 *buf, size_t len ) { struct spi_transfer xfer[2] = {0}; struct spi_message msg; u8 cmd[4]; int ret; /* 构造命令头:读命令(1B)+地址(3B) */ cmd[0] = FLASH_CMD_READ; cmd[1] = (addr >> 16) & 0xFF; cmd[2] = (addr >> 8) & 0xFF; cmd[3] = addr & 0xFF; /* 第一个传输段:发送命令+地址 */ xfer[0].tx_buf = cmd; xfer[0].len = 4; /* 第二个传输段:接收数据(可使用DMA加速) */ xfer[1].rx_buf = buf; xfer[1].len = len; /* 构造并同步执行SPI消息 */ spi_message_init(&msg); spi_message_add_tail(&xfer[0], &msg); spi_message_add_tail(&xfer[1], &msg); ret = spi_sync(spi, &msg); if (ret) dev_err(&spi->dev, "SPI read page failed: %d\n", ret); return ret; } static int flash_probe(struct spi_device *spi) { struct flash_data *data; struct device *dev = &spi->dev; flash_setup_spi(spi); data = devm_kzalloc( dev, sizeof(*data), GFP_KERNEL ); if (!data) return -ENOMEM; >/* 设备树配置:I2C和SPI总线的完整声明 */ /* I2C总线 */ &i2c1 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_i2c1>; clock-frequency = <100000>; /* 100kHz标准模式 */ status = "okay"; temp_sensor: temp-sensor@48 { compatible = "vendor,temp-sensor"; reg = <0x48>; /* 7位I2C地址 */ interrupt-parent = <&gpio1>; interrupts = <5 IRQ_TYPE_EDGE_FALLING>; }; }; /* SPI总线 */ &ecspi2 { pinctrl-names = "default"; pinctrl-0 = <&pinctrl_ecspi2>; cs-gpios = <&gpio5 13 GPIO_ACTIVE_LOW>; status = "okay"; flash: flash@0 { compatible = "vendor,spi-flash"; reg = <0>; /* 片选索引0 */ spi-max-frequency = <20000000>; spi-cpha; /* CPHA=1, MODE1 */ }; }; /* GPIO引脚复用 */ &iomuxc { pinctrl_i2c1: i2c1grp { fsl,pins = < MX8MQ_IOMUXC_I2C1_SCL_I2C1_SCL 0x4000007f MX8MQ_IOMUXC_I2C1_SDA_I2C1_SDA 0x4000007f >; }; pinctrl_ecspi2: ecspi2grp { fsl,pins = < MX8MQ_IOMUXC_ECSPI2_SCLK_ECSPI2_SCLK 0x82 MX8MQ_IOMUXC_ECSPI2_MOSI_ECSPI2_MOSI 0x82 MX8MQ_IOMUXC_ECSPI2_MISO_ECSPI2_MISO 0x82 MX8MQ_IOMUXC_ECSPI2_SS0_GPIO5_IO13 0x82 >; }; };

调试三板斧在实际工程中非常重要:

  1. I2C设备检测:i2cdetect -y 1扫描总线上所有地址,确认设备在位。如果设备显示UU表示被内核驱动占用(正常),显示数字地址表示设备在线但无驱动接管(异常),显示--表示无回应(硬件连接或上拉电阻问题)。
  2. SPI通道测试:spidev_test -D /dev/spidev0.0测试SPI通道的基本读写,确认信号连通。
  3. 逻辑分析仪排查:当软件层面查不出问题时,逻辑分析仪是终极武器。连接到SDA/SCL(I2C)或MOSI/MISO/SCLK/CS(SPI),直接观察总线波形。I2C常见问题包括上拉电阻过大导致信号边沿变缓、时钟拉伸过长。SPI常见问题包括时钟极性和相位配置错误、片选信号在传输完成前提前拉高。

五、总结

  1. I2C适合引脚少(2线)、多从设备(最多127个)、速率不超过400kbps的低速传感器场景;SPI适合高速(1~50MHz)、全双工、外设独占的显示和存储场景
  2. I2C驱动标准框架:i2c_driver结构体(probe/remove/id_table)、devm_kzalloc资源管理、probe末尾做验证性读取确认硬件就绪
  3. SPI驱动核心概念:spi_transfer数组+spi_message实现命令/数据连续传输,高速场景必须用dmam_alloc_coherent分配DMA缓冲区
  4. 设备树是硬件到驱动的配置桥梁,compatible属性匹配驱动of_match_table,reg属性指定I2C地址或SPI片选索引
  5. 调试三阶段:软件层面(i2cdetect/spidev_test/dmesg)→电压层面(万用表测上拉电压)→信号层面(逻辑分析仪看波形),按这个顺序逐层排查效率最高

相关新闻

  • Swift-Sharing与SwiftData对比分析:为什么选择Sharing进行状态管理
  • Java Map 高效、实用高频方法大全(JDK8+ 重点)
  • 终极指南:如何在Android应用中快速实现液态玻璃光影效果

最新新闻

  • 托运摩托车,到底要不要打木架? - 快递物流资讯
  • 酸度梯度控制的原理---(技术突破及工艺应用)
  • UVa 483 Word Scramble
  • 技术架构:技术选型和基础设施
  • 零成本开启企业碳足迹管理:openLCA开源生命周期评估工具完全指南
  • esp32开发与应用(3.5寸触摸屏+lvgl)

日新闻

  • OpenClaw本地化部署:xParse文档解析引擎实战指南
  • 蓝牙 5.4 协议栈深度解析:从 HCI 到 L2CAP 的 7 层数据流
  • PyTorch nn.CrossEntropyLoss 实战:3种权重设置与标签平滑对比(附代码)

周新闻

  • 基于YOLOv12的番茄成熟度智能检测系统开发
  • 终极RimWorld模组管理指南:用RimSort告别模组冲突烦恼
  • AI Agent框架开发:从理论到实践的完整指南

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号