当前位置: 首页 > news >正文

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

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_tableof_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)→电压层面(万用表测上拉电压)→信号层面(逻辑分析仪看波形),按这个顺序逐层排查效率最高
http://www.jsqmd.com/news/1163468/

相关文章:

  • any-sync-dockercompose架构解析:8大核心服务如何协同工作?
  • Swift-Sharing与SwiftData对比分析:为什么选择Sharing进行状态管理
  • 武汉靠谱别墅设计工作室,独栋叠墅实景案例 - 品牌红黑榜
  • 2026免费去水印工具教程:手机电脑在线安全使用方法
  • JoyCon-Driver:在Windows上解锁Switch手柄全部潜力的实用指南
  • Wayback Machine扩展社区支持与资源:获取帮助与分享经验的完整指南
  • 2026年7月最新太原卡地亚官方售后客户服务热线与维修网点地址汇总 - 卡地亚服务中心
  • MONAI自监督学习实战:解锁医疗AI的无标注数据潜力
  • Java Map 高效、实用高频方法大全(JDK8+ 重点)
  • 终极指南:如何在Android应用中快速实现液态玻璃光影效果
  • 抖音无水印下载终极指南:免费工具快速保存高清视频
  • VMware 17.5 致Linux 虚拟机卡死:有效解决办法;vmware虚拟机卡死解决办法;vmware启动一段时间后卡死;虚拟机卡住
  • Meta智能眼镜遭抵制,隐私问题待解,功能增强更添挑战
  • 如何利用 Laravel Scout ElasticSearch 实现实时搜索与自动补全功能
  • 2026年7月辽源​市公共卫生许可证检测集中空调通风系统检测水质检测空气质量甲醛检测怎么做?正规CMA机构办理指南 - 绿呼吸检测中心
  • 如何构建你的二次元技术栈:一站式ACG开发工具指南
  • Ultimate ASI Loader:为Windows游戏加载自定义插件的终极解决方案
  • gh_mirrors/re/ResourceModules社区与支持:获取帮助与参与贡献的完整指南
  • 2026年天津合同律师实力对比 5位资深律师深度测评 - 本地品牌推荐
  • Tempest Framework加密组件终极指南:如何在PHP应用中实现企业级数据安全
  • 3分钟上手Tess-4-27B:从模型下载到首次推理的完整指南
  • 2026年跨境电商新手必看:避开这8个坑 半年营收破30万的实操经验复盘
  • 太原防眩光汽车车窗膜
  • Linux 创始人林纳斯·托瓦兹谈内核发展:AI、Rust 影响几何?
  • 强烈推荐专业的中央空调公司有哪家
  • 告别 PPT 熬夜画图!研究生如何用 AI 高效率生成论文概念图?
  • CellChat在免疫学研究中的应用:解析免疫细胞间的通讯网络
  • 出海企业看上海 GEO 公司 - 筑云鲸
  • 2026年7月来宾​市公共卫生许可证检测集中空调通风系统检测水质检测空气质量甲醛检测怎么做?正规CMA机构办理指南 - 绿呼吸检测中心
  • JointJS如何让你的图表连接既美观又智能?锚点与连接策略全解析