I2C与SPI设备驱动实战嵌入式Linux下的外设通信协议开发完整指南一、总线选择的工程决策I2C与SPI的本质差异I2C和SPI是嵌入式Linux中最常见的两种外设通信总线但很多选型决策停留在看引脚数的直觉层面。实际上两条总线在物理层、协议层和软件栈上都有本质差异选型需要从四个核心维度综合评估引脚数量、通信速率、拓扑复杂度和内核驱动的成熟度。flowchart TD A[外设通信需求分析] -- B{MCU引脚资源紧张?} B --|是仅2根可用| C{速率要求} B --|否引脚充裕| D{速率要求与实时性} C --|低于400kbps| E[I2C: 2线SDASCL] C --|超过1Mbps| F[SPI: 至少4线MISOMOSISCLKCS] 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位精度LSB0.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: CPOL0, CPHA0 * 即空闲时钟低电平上升沿采样 */ 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-sensor48 { 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: flash0 { compatible vendor,spi-flash; reg 0; /* 片选索引0 */ spi-max-frequency 20000000; spi-cpha; /* CPHA1, 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 ; }; };调试三板斧在实际工程中非常重要I2C设备检测i2cdetect -y 1扫描总线上所有地址确认设备在位。如果设备显示UU表示被内核驱动占用正常显示数字地址表示设备在线但无驱动接管异常显示--表示无回应硬件连接或上拉电阻问题。SPI通道测试spidev_test -D /dev/spidev0.0测试SPI通道的基本读写确认信号连通。逻辑分析仪排查当软件层面查不出问题时逻辑分析仪是终极武器。连接到SDA/SCLI2C或MOSI/MISO/SCLK/CSSPI直接观察总线波形。I2C常见问题包括上拉电阻过大导致信号边沿变缓、时钟拉伸过长。SPI常见问题包括时钟极性和相位配置错误、片选信号在传输完成前提前拉高。五、总结I2C适合引脚少2线、多从设备最多127个、速率不超过400kbps的低速传感器场景SPI适合高速1~50MHz、全双工、外设独占的显示和存储场景I2C驱动标准框架i2c_driver结构体probe/remove/id_table、devm_kzalloc资源管理、probe末尾做验证性读取确认硬件就绪SPI驱动核心概念spi_transfer数组spi_message实现命令/数据连续传输高速场景必须用dmam_alloc_coherent分配DMA缓冲区设备树是硬件到驱动的配置桥梁compatible属性匹配驱动of_match_tablereg属性指定I2C地址或SPI片选索引调试三阶段软件层面i2cdetect/spidev_test/dmesg→电压层面万用表测上拉电压→信号层面逻辑分析仪看波形按这个顺序逐层排查效率最高