I2C 设备驱动开发
I2C 设备驱动开发I2C 是嵌入式最常用的低速串行总线传感器、EEPROM、RTC 很多都是 I2C 接口。这篇讲 Linux I2C 子系统架构、i2c_driver 框架、设备树匹配、I2C 读写 API以及完整的传感器驱动示例。大家好我是黒漂技术佬。I2C 总线用两根线SCL 时钟、SDA 数据就能挂一堆设备地址寻址非常方便。温度传感器、加速度计、EEPROM、触摸屏……很多外设都是 I2C 接口。Linux 有完整的 I2C 子系统驱动不用自己模拟时序用内核提供的 API 就行。这篇讲 I2C 驱动怎么写I2C 子系统架构、i2c_driver、设备树匹配、读写函数、完整示例。一、I2C 总线简介特点两线制SCL时钟、SDA数据半双工同步串行主从结构主机发起通信7 位或 10 位地址一条总线最多挂 127 个设备7位地址标准模式 100kbit/s快速模式 400kbit/s高速模式 3.4Mbit/s通信过程主机发起起始信号START主机发从机地址 读写位从机应答ACK数据传输每字节一个应答主机发停止信号STOP常见 I2C 设备传感器温度、湿度、加速度、陀螺仪、光照存储EEPROM、FRAM时钟RTC 实时时钟外设IO 扩展、ADC、DAC、触摸屏控制器二、Linux I2C 子系统架构三层结构I2C 设备驱动我们写的 ↓ I2C 核心i2c-core ↓ I2C 总线驱动适配器驱动SoC厂商提供1. I2C 适配器adapterSoC 的 I2C 控制器驱动由芯片厂商提供负责实际的 I2C 时序收发。2. I2C 核心核心层提供通用 API连接适配器和设备驱动。3. I2C 设备驱动我们写的针对具体 I2C 设备传感器、EEPROM 等的驱动。三、I2C 驱动框架i2c_driver 结构体跟 platform_driver 类似#includelinux/i2c.hstaticintmy_probe(structi2c_client*client){// 匹配成功初始化return0;}staticvoidmy_remove(structi2c_client*client){// 清理}staticconststructof_device_idmy_of_match[]{{.compatiblevendor,my-sensor},{}};MODULE_DEVICE_TABLE(of,my_of_match);staticconststructi2c_device_idmy_id[]{{my-sensor,0},{}};MODULE_DEVICE_TABLE(i2c,my_id);staticstructi2c_drivermy_driver{.probemy_probe,.removemy_remove,.id_tablemy_id,// 旧的匹配方式.driver{.namemy-sensor,.of_match_tablemy_of_match,// 设备树匹配},};module_i2c_driver(my_driver);module_i2c_driver 宏自动注册注销 i2c_driver省得写 module_init/exit。i2c_client匹配成功后probe 的参数就是 i2c_client代表一个 I2C 从设备structi2c_client{unsignedshortaddr;// 从机地址structi2c_adapter*adapter;// 所属适配器structdevicedev;// ...};addr 就是 I2C 设备地址读写的时候用。四、设备树中的 I2C 设备I2C 控制器节点SoC 的 I2C 控制器i2c0: i2c10000000 { compatible vendor,i2c-controller; reg 0x10000000 0x1000; #address-cells 1; #size-cells 0; clock-frequency 400000; // 400kHz status okay; };I2C 设备子节点挂在 I2C 控制器下面i2c0 { temp_sensor: temp48 { compatible vendor,temp-sensor; reg 0x48; // I2C 地址 status okay; }; eeprom50 { compatible atmel,24c02; reg 0x50; }; };reg 0x48I2C 从机地址7位子节点挂在 I2C 控制器节点下面驱动的 compatible 跟这里匹配五、I2C 读写 API1. 简单收发// 发送inti2c_master_send(conststructi2c_client*client,constchar*buf,intcount);// 接收inti2c_master_recv(conststructi2c_client*client,char*buf,intcount);返回成功传输的字节数负数失败自动加 START/STOP2. 通用消息传输更灵活可以组合多次传输比如先写寄存器地址再读inti2c_transfer(structi2c_adapter*adap,structi2c_msg*msgs,intnum);i2c_msg 结构体structi2c_msg{__u16 addr;// 从机地址__u16 flags;// 标志I2C_M_RD 读0 写__u16 len;// 数据长度__u8*buf;// 数据缓冲区};3. SMBus 方式大部分 I2C 设备兼容 SMBus 协议有更方便的寄存器读写函数#includelinux/i2c-smbus.h// 读/写一个字节s32i2c_smbus_read_byte_data(conststructi2c_client*client,u8 command);s32i2c_smbus_write_byte_data(conststructi2c_client*client,u8 command,u8 value);// 读/写字16位s32i2c_smbus_read_word_data(conststructi2c_client*client,u8 command);s32i2c_smbus_write_word_data(conststructi2c_client*client,u8 command,u16 value);// 读/写块数据最多32字节s32i2c_smbus_read_i2c_block_data(conststructi2c_client*client,u8 command,u8 length,u8*values);s32i2c_smbus_write_i2c_block_data(conststructi2c_client*client,u8 command,u8 length,constu8*values);command就是寄存器地址。传感器驱动最常用这个大部分传感器都是「写寄存器地址 → 读数据」的模式smbus 函数刚好对应。六、封装寄存器读写实际驱动里一般封装成读寄存器、写寄存器的函数// 读一个寄存器staticintreg_read(structi2c_client*client,u8 reg,u8*val){s32 reti2c_smbus_read_byte_data(client,reg);if(ret0)returnret;*valret;return0;}// 写一个寄存器staticintreg_write(structi2c_client*client,u8 reg,u8 val){returni2c_smbus_write_byte_data(client,reg,val);}// 读多个寄存器staticintreg_read_bulk(structi2c_client*client,u8 reg,u8*buf,intlen){returni2c_smbus_read_i2c_block_data(client,reg,len,buf);}然后业务逻辑就直接调 reg_read / reg_write清晰很多。七、完整示例温度传感器驱动假设一个温度传感器I2C 地址 0x48寄存器 0x00 是温度值16位0x01 是配置寄存器。设备树i2c0 { temp48 { compatible vendor,temp-sensor; reg 0x48; status okay; }; };驱动代码#includelinux/module.h#includelinux/i2c.h#includelinux/of.h#includelinux/hwmon.h#defineREG_TEMP0x00#defineREG_CONFIG0x01structtemp_data{structi2c_client*client;// ...};staticinttemp_read_reg(structi2c_client*client,u8 reg,u16*val){s32 reti2c_smbus_read_word_data(client,reg);if(ret0)returnret;*valret;return0;}staticinttemp_write_reg(structi2c_client*client,u8 reg,u16 val){returni2c_smbus_write_word_data(client,reg,val);}staticintget_temperature(structtemp_data*data,int*temp){u16 raw;intret;rettemp_read_reg(data-client,REG_TEMP,raw);if(ret)returnret;// 假设寄存器是 12 位单位 0.0625 度rawbe16_to_cpu(raw)4;*tempraw*625;// 毫摄氏度return0;}// sysfs 属性读温度staticssize_ttemp1_input_show(structdevice*dev,structdevice_attribute*attr,char*buf){structtemp_data*datadev_get_drvdata(dev);inttemp,ret;retget_temperature(data,temp);if(ret)returnret;returnsprintf(buf,%d\n,temp);}staticDEVICE_ATTR_RO(temp1_input);staticstructattribute*temp_attrs[]{dev_attr_temp1_input.attr,NULL};ATTRIBUTE_GROUPS(temp);staticinttemp_probe(structi2c_client*client){structtemp_data*data;structdevice*hwmon_dev;u16 config;intret;if(!i2c_check_functionality(client-adapter,I2C_FUNC_SMBUS_WORD_DATA))return-ENODEV;datadevm_kzalloc(client-dev,sizeof(*data),GFP_KERNEL);if(!data)return-ENOMEM;data-clientclient;i2c_set_clientdata(client,data);// 读配置寄存器验证芯片是否存在rettemp_read_reg(client,REG_CONFIG,config);if(ret){dev_err(client-dev,读取配置寄存器失败\n);returnret;}// 注册为 hwmon 设备Linux 硬件监控子系统hwmon_devdevm_hwmon_device_register_with_groups(client-dev,client-name,data,temp_groups);dev_info(client-dev,温度传感器 probe 成功\n);returnPTR_ERR_OR_ZERO(hwmon_dev);}staticvoidtemp_remove(structi2c_client*client){// devm 自动清理}staticconststructof_device_idtemp_of_match[]{{.compatiblevendor,temp-sensor},{}};MODULE_DEVICE_TABLE(of,temp_of_match);staticconststructi2c_device_idtemp_id[]{{temp-sensor,0},{}};MODULE_DEVICE_TABLE(i2c,temp_id);staticstructi2c_drivertemp_driver{.probetemp_probe,.removetemp_remove,.id_tabletemp_id,.driver{.nametemp-sensor,.of_match_tabletemp_of_match,},};module_i2c_driver(temp_driver);MODULE_LICENSE(GPL);MODULE_AUTHOR(Heipiao);MODULE_DESCRIPTION(I2C 温度传感器驱动);八、调试方法i2cdetect扫描总线上的设备# 扫描 i2c-0 总线i2cdetect-y0# 输出UU 表示被驱动占用了数字表示检测到的地址i2cget / i2cset命令行读写# 读 0x48 设备的 0x00 寄存器i2cget-y00x48 0x00# 写 0x48 设备的 0x01 寄存器为 0x80i2cset-y00x48 0x01 0x80i2cdumpdump 所有寄存器i2cdump-y00x48调试驱动前先用 i2cdetect 确认设备能扫到i2cget 能读到数据再写驱动。九、常见坑坑 1I2C 地址不对有的 datasheet 写的是 8 位地址包含读写位Linux 用的是 7 位地址要右移一位地址对了 i2cdetect 才能扫到坑 2引脚复用没配对SCL/SDA 引脚没配置成 I2C 功能通信肯定失败。检查 pinctrl 配置。坑 3上拉电阻I2C 总线必须有上拉电阻。硬件没焊上拉或者上拉阻值不对通信不稳定甚至完全不通。坑 4字节序16位寄存器有的是大端有的是小端读出来要转换。用 be16_to_cpu / le16_to_cpu。坑 5不检查适配器功能有的 I2C 控制器不支持 SMBus 某些命令调用前用 i2c_check_functionality 检查。坑 6probe 里不验证设备上来就初始化设备不存在也不报错。先读一个已知的寄存器比如 ID 寄存器验证芯片真的在总线上。十、本篇小结I2C两线制串行总线主从结构7位地址100k/400k 速率Linux I2C 子系统三层适配器驱动、核心层、设备驱动i2c_driver 框架probe/remove、of_match_table 设备树匹配、module_i2c_driver 宏i2c_client代表一个 I2C 从设备包含地址和适配器信息设备树I2C 设备作为子节点挂在控制器下reg 是 I2C 地址读写 APIi2c_smbus_read/write_byte_data 最常用适合寄存器型设备i2c_transfer 更灵活可以组合多次 START调试工具i2cdetect 扫描、i2cget/set 读写、i2cdump 查看全部常见问题地址不对、上拉电阻、引脚复用、字节序下一篇讲SPI 设备驱动开发SPI 总线、spi_driver、四种模式、读写传输。我是黒漂技术佬。