一.背景
LM75,温度传感器.
工作电压:2.7V-5.5V
精度:
设备地址:0x48,低三位可通过焊接置0或1
表示温度,十六进制值乘以分辨率0.5,负温度值用补码保存,先转为原码再计算
通过配置寄存器低三位的值选择不同寄存器,本次配置000,即读取温度。
读取2字节温度,舍弃低7位。
float get_lm75_temp(void) { short temp; unsigned char s[2]; i2c_read(I2C1, 0x48, 0, s, 2); temp = s[0] << 8; temp |= s[1]; temp >>= 7; return temp * 0.5; }此外,对read和write的传输寄存器地址代码进行改进,由于寄存器地址可能不止一位(AT24C02能存储256字节数据,所以char刚好可以表示;LMA75从P0-P7也为一字节长度,这两都可用char表示,但后续操作显示屏时char则不够),所以用char类型定义寄存器地址会出现问题,用unsigned short reg_address定义地址, int reg_len定义寄存器地址长度,改进如下
int i = 0; for (i = 0; i < reg_len; i++) { // 中断标记位清零 base->I2SR &= ~(1 << IIF); // 发送寄存器地址 base->I2DR = reg_address >> (reg_len - i - 1) * 8; // 等待从机ACK while ((base->I2SR & (1 << IIF)) == 0); }二.底层
最下面的硬件层即上篇及本篇使用的传感器,AT24C02和LMA75等,连接的上面I2C_adapter即SOC芯片内部的I2C控制器,具体实现就是适配器驱动这一层,最近对I2C的学习所实现的代码其实写的就是控制器这块的代码,显然偏底层,这部分代码通常为芯片厂商负责写,比如使用imx6ull为NXP公司生产,所以由他们负责写控制器底层代码,而不同芯片厂商生产的芯片对应的寄存器自然不相同。
再往上则可以由Linux系统进行调用这些封装好的代码,可适配不同芯片,屏蔽底层芯片,直接面向已经被芯片厂商封装好的函数。图中核心层的i2c_transfer是灵魂函数。当设备驱动调用i2c_transfer时,核心层会打包好i2c_msg(包含设备地址、读写标志、数据长度),然后去调用底层适配器驱动注册好的master_xfer函数指针。底层再根据这个指针,去操作真正的 i.MX6ULL 寄存器。
master_xfer,每个芯片厂商都会写的,不管使用哪个芯片,想使用I2C都可调用master_xfer。
以下为该函数的实现
// I2C传输方向 enum I2C_Direction { I2C_Write = 0, I2C_Read = 1 }; // I2C信息 typedef struct { unsigned char dev_address; unsigned short reg_address; int reg_len; unsigned char *data; int len; enum I2C_Direction direction; }I2C_Msg; void master_xfer(I2C_Type *base, I2C_Msg *msg) //主机_发送/接收,不管是哪个芯片厂商生产的,在linux系统下,都可以调用此函数 { if (msg->direction == I2C_Write) { i2c_write(base, msg->dev_address, msg->reg_address, msg->reg_len, msg->data, msg->len); } else if (msg->direction == I2C_Read) { i2c_read(base, msg->dev_address, msg->reg_address, msg->reg_len, msg->data, msg->len); } }调用该封装函数实现LM75传感器读取温度
float get_lm75_temp(void) { short temp; unsigned char s[2]; I2C_Msg msg = { .direction = I2C_Read, .dev_address = 0x48, .reg_address = 0, .reg_len = 1, .data = s, .len = 2 }; master_xfer(I2C1, &msg); temp = s[0] << 8; temp |= s[1]; temp >>= 7; return temp * 0.5; }总结:Linux社区不管每一款芯片在底层怎么操作自己的寄存器,只是提供芯片厂商一个函数,比如master_xfer,告诉厂商你们负责实现你们自己芯片对应的这个函数,而不同厂商则在master_xfer中实现如何操纵自己的寄存器,实现好了之后由Linux封装到自己的函数接口i2c_transfer内部,再把这个函数接口提供给用户层使用,用户使用该i2c_transfer函数也不用考虑底层是哪个芯片厂商生产的,底层寄存器是怎么操作的,都由芯片厂商实现了。