公司动态
用户层 I2C/SPI 开发:dev 节点外设读写实操
用户层 I2C/SPI 开发dev 节点外设读写实操一、引言前面我们讲了串口编程——那是异步通信的典型。但在嵌入式板子上更多的是同步总线I2C 和 SPI。温度传感器如 LM75、IMU如 MPU6050、OLED 显示屏、EEPROM 存储芯片几乎全挂在这些总线上。很多人以为操作 I2C/SPI 非得写内核驱动不可。其实 Linux 早就提供了用户层直接访问的接口——通过/dev/i2c-x和/dev/spidevx.x设备节点你可以在应用层像操作文件一样读写外设寄存器。本文手把手带你用 C 代码把传感器数据读出来。二、I2C vs SPI一表看懂特性I2CSPI线数2 线SCL、SDA4 线SCK、MOSI、MISO、CS通信模式半双工全双工速率标准 100kHz快速 400kHz高速 3.4MHz几 MHz 到几十 MHz寻址7 位/10 位从机地址片选信号CS拓扑多主多从总线共享一主多从各占一根 CS 线复杂度低两根线挂一堆设备中设备多了线也多典型应用传感器、RTC、EEPROMFlash、显示屏、ADC一句话I2C 用两根线挂十几个设备省引脚但慢SPI 四根线挂一个设备快但费引脚。三、用户层 I2C 开发3.1 检查 I2C 总线# 查看系统有几路 I2Cls/dev/i2c-*# 安装 i2c-tools 并检测从设备sudoaptinstalli2c-tools i2cdetect-y1# 扫描 i2c-1 上的所有设备输出示例0 1 2 3 4 5 6 7 8 9 a b c d e f 00: -- -- -- -- -- -- -- -- -- -- -- -- -- 10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 30: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 40: -- -- -- -- -- -- -- -- 48 -- -- -- -- -- -- -- 50: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- 70: -- -- -- -- -- -- -- --看到0x48亮了说明这个 I2C 地址上挂了一个设备比如 LM75 温度传感器。3.2 用 ioctl 读写 I2C 设备寄存器Linux 提供了ioctl接口直接操作 I2C 设备无需写内核驱动#includestdio.h#includestdlib.h#includeunistd.h#includefcntl.h#includestdint.h#includesys/ioctl.h#includelinux/i2c-dev.h/* 从 I2C 设备读取 16 位寄存器 */inti2c_read_reg16(intfd,uint8_treg,uint16_t*value){// 先发送寄存器地址if(write(fd,reg,1)!1){perror(write reg);return-1;}// 再读取 2 字节数据uint8_tbuf[2];if(read(fd,buf,2)!2){perror(read data);return-1;}// 大端字节序合并*value(buf[0]8)|buf[1];return0;}intmain(void){// 1. 打开 I2C 总线intfdopen(/dev/i2c-1,O_RDWR);if(fd0){perror(open i2c);return-1;}// 2. 设置从机地址 (LM75 地址 0x48)uint8_taddr0x48;if(ioctl(fd,I2C_SLAVE,addr)0){perror(ioctl I2C_SLAVE);close(fd);return-1;}// 3. 读取温度寄存器 (0x00 Temperature)uint16_traw;if(i2c_read_reg16(fd,0x00,raw)0){close(fd);return-1;}// 4. 解析温度值 (LM75: 高9位为有符号温度值)int16_ttemp_raw(int16_t)(raw7);// 右移去掉低7位floattemp_ctemp_raw*0.5;// 每个 LSB 0.5°Cprintf(温度 %.2f °C\n,temp_c);close(fd);return0;}3.3 使用 SMBus 协议更推荐ioctl方式虽然原生但 I2C 设备五花八门有的需要组合读写Write-then-Readioctl处理起来很麻烦。内核从 3.x 版本开始支持SMBus 协议通过单个ioctl调用完成组合操作#includelinux/i2c.h#includelinux/i2c-dev.h/* SMBus 读字数据 */inti2c_smbus_read_word(intfd,uint8_treg,uint16_t*value){structi2c_smbus_ioctl_dataargs;unioni2c_smbus_data data;args.read_writeI2C_SMBUS_READ;args.commandreg;// 寄存器地址args.sizeI2C_SMBUS_WORD_DATA;args.datadata;if(ioctl(fd,I2C_SMBUS,args)0){perror(SMBus read);return-1;}*valuedata.word;return0;}SMBus 读写类型一览操作类型含义数据大小I2C_SMBUS_BYTE读写单个字节1 字节I2C_SMBUS_BYTE_DATA指定寄存器读写 1 字节1 字节I2C_SMBUS_WORD_DATA指定寄存器读写 2 字节2 字节I2C_SMBUS_BLOCK_DATA指定寄存器读写最多 32 字节N 字节四、用户层 SPI 开发SPI 的用户层接口通过/dev/spidevX.Y提供其中 X 是 SPI 控制器编号Y 是片选线编号。# 查看 SPI 设备ls/dev/spi*# 输出示例: /dev/spidev0.0 /dev/spidev0.1SPI 配置与读写#includestdio.h#includestdlib.h#includestring.h#includeunistd.h#includefcntl.h#includestdint.h#includesys/ioctl.h#includelinux/spi/spidev.h/* 初始化 SPI 设备 */intspi_init(constchar*device,uint32_tspeed,uint8_tmode,uint8_tbits){intfdopen(device,O_RDWR);if(fd0){perror(open spi);return-1;}// 设置 SPI 模式 (CPOL CPHA)ioctl(fd,SPI_IOC_WR_MODE,mode);// 设置 每字位数ioctl(fd,SPI_IOC_WR_BITS_PER_WORD,bits);// 设置 最大时钟速率ioctl(fd,SPI_IOC_WR_MAX_SPEED_HZ,speed);printf(SPI 初始化: mode%d, bits%d, speed%d Hz\n,mode,bits,speed);returnfd;}/* SPI 全双工传输 */intspi_transfer(intfd,constuint8_t*tx,uint8_t*rx,intlen){structspi_ioc_transfertr{.tx_buf(unsignedlong)tx,.rx_buf(unsignedlong)rx,.lenlen,.speed_hz0,// 使用初始化时设置的速度.delay_usecs0,.bits_per_word0,// 使用初始化时设置的位数};returnioctl(fd,SPI_IOC_MESSAGE(1),tr);}/* 读取 SPI Flash 的 JEDEC ID (典型用法) */intmain(void){intfdspi_init(/dev/spidev0.0,1000000,SPI_MODE_0,8);if(fd0)return-1;// 读取 JEDEC ID 命令: 0x9Fuint8_ttx[4]{0x9F,0x00,0x00,0x00};uint8_trx[4]{0};if(spi_transfer(fd,tx,rx,4)0){perror(spi_transfer);close(fd);return-1;}// 响应格式: [r10x9f][r2Manufacturer][r3MemType][r4Capacity]printf(制造商ID: 0x%02X\n,rx[1]);printf(存储器类型: 0x%02X\n,rx[2]);printf(容量: 0x%02X\n,rx[3]);// 常见制造商: 0xEFWinbond, 0xC8GigaDevice, 0x1FAdestoclose(fd);return0;}SPI 模式选择SPI 有 4 种工作模式由 CPOL时钟极性和 CPHA时钟相位决定模式CPOLCPHA空闲时 CLK采样边沿常用场景000低电平上升沿大部分 Flash/传感器101低电平下降沿部分 ADC210高电平下降沿较少见311高电平上升沿部分显示屏绝大多数 SPI Flash 使用 Mode 0。不确定时优先看设备数据手册其次是 Mode 0 和 Mode 3 各试一次。五、开发调试清单I2C 调试# 扫描 I2C 总线上的设备i2cdetect-y1# 读取寄存器不写代码直接命令行验证i2cget-y10x48 0x00 w# 读 0x48 的寄存器 0x002 字节# 写入寄存器i2cset-y10x48 0x01 0x60SPI 调试# 查看 SPI 设备信息ls-l/dev/spi*# 如果没有 spidev 节点检查内核配置zcat /proc/config.gz|grepSPI_SPIDEV# 或cat/boot/config-$(uname-r)|grepCONFIG_SPI_SPIDEV六、总结用户层操作 I2C/SPI 的关键知识点I2C 三步走open(/dev/i2c-x)→ioctl(I2C_SLAVE, addr)→read/writeSMBus 更高效一次ioctl完成寄存器地址数据组合操作SPI 四步走open→ioctl(MODE/SPEED/BITS)→SPI_IOC_MESSAGE→close模式选择I2C 关注从机地址和寄存器地址SPI 关注 Mode 0/3 和最大速率调试工具i2cdetect/i2cget和spidev_test是调通硬件的第一道防线掌握了这些用户层接口你就可以在不写一行内核代码的情况下驱动市面上绝大多数 I2C/SPI 外设。