1. 项目概述
在嵌入式开发领域,I2C总线因其简单可靠的两线制设计,成为连接各类传感器和外设的首选方案。RK3576作为瑞芯微新一代高性能处理器,内置10路硬件I2C控制器,为开发者提供了丰富的硬件资源。本章将深入探讨如何在安卓环境下通过JNI/NDK调用Linux标准I2C驱动,实现对SHT30温湿度传感器的数据采集。
提示:本方案完全基于Linux标准i2c-dev驱动框架,不依赖任何厂商私有API,具有极高的可移植性,可轻松适配其他安卓设备。
2. 硬件准备与原理分析
2.1 I2C总线基础特性
I2C(Inter-Integrated Circuit)总线采用主从式架构,仅需两根信号线即可实现多设备通信:
- SCL (Serial Clock):时钟线,由主设备产生
- SDA (Serial Data):双向数据线
- 标准模式速率100kHz,快速模式400kHz
- 7位地址空间支持最多112个设备(保留部分特殊地址)
2.2 RK3576 I2C硬件资源
RK3576芯片的I2C控制器具有以下特点:
| 特性 | 参数 |
|---|---|
| 控制器数量 | 10路独立I2C |
| 设备节点 | /dev/i2c-0 ~ /dev/i2c-9 |
| 时钟速率 | 最高400kHz |
| 中断支持 | 有 |
| DMA支持 | 有 |
开发板通常将i2c-2和i2c-3引出到排针,方便用户连接外设。通过adb命令可以验证总线是否可用:
bash复制adb shell
i2cdetect -y 2
正常输出应显示连接的设备地址,SHT30默认地址为0x44。
3. Linux I2C驱动开发
3.1 驱动框架分析
Linux内核将I2C控制器抽象为字符设备,通过ioctl系统调用提供以下核心功能:
c复制#include <linux/i2c-dev.h>
// 关键ioctl命令
#define I2C_SLAVE 0x0703 // 设置从机地址
#define I2C_RDWR 0x0707 // 复合读写
3.2 数据结构详解
i2c_msg结构体是I2C通信的核心:
c复制struct i2c_msg {
__u16 addr; // 从机地址(7位)
__u16 flags; // 读写标志
__u16 len; // 数据长度
__u8 *buf; // 数据缓冲区
};
典型的数据传输流程:
- 打开设备节点获取文件描述符
- 构造i2c_msg数组
- 填充i2c_rdwr_ioctl_data结构体
- 调用ioctl执行传输
- 解析返回数据
4. NDK层实现
4.1 I2C工具类封装
cpp复制class I2CDevice {
public:
I2CDevice(const char* path) {
fd = open(path, O_RDWR);
if (fd < 0) {
throw std::runtime_error("Open I2C device failed");
}
}
~I2CDevice() {
if (fd >= 0) close(fd);
}
int writeRead(uint8_t addr, uint8_t* wbuf, size_t wlen,
uint8_t* rbuf, size_t rlen) {
struct i2c_msg msgs[2];
struct i2c_rdwr_ioctl_data rdwr;
msgs[0].addr = addr;
msgs[0].flags = 0;
msgs[0].len = wlen;
msgs[0].buf = wbuf;
msgs[1].addr = addr;
msgs[1].flags = I2C_M_RD;
msgs[1].len = rlen;
msgs[1].buf = rbuf;
rdwr.msgs = msgs;
rdwr.nmsgs = 2;
return ioctl(fd, I2C_RDWR, &rdwr);
}
private:
int fd = -1;
};
4.2 SHT30驱动实现
SHT30传感器通信协议要点:
- 测量命令:0x2C06(高精度模式)
- 数据格式:6字节(温度+湿度+CRC)
- 转换公式:
- 温度 = -45 + 175 * (raw/65535)
- 湿度 = 100 * (raw/65535)
cpp复制class SHT30 {
public:
SHT30(I2CDevice& dev) : i2c(dev) {}
bool read(float& temp, float& hum) {
uint8_t cmd[2] = {0x2C, 0x06};
uint8_t data[6];
if (i2c.writeRead(0x44, cmd, 2, data, 6) < 0)
return false;
uint16_t rawTemp = (data[0] << 8) | data[1];
uint16_t rawHum = (data[3] << 8) | data[4];
temp = -45.0f + 175.0f * (rawTemp / 65535.0f);
hum = 100.0f * (rawHum / 65535.0f);
return true;
}
private:
I2CDevice& i2c;
};
5. JNI接口设计
5.1 Java本地方法声明
java复制public class SHT30Driver {
static {
System.loadLibrary("sht30");
}
public native boolean open();
public native void close();
public native float[] read();
}
5.2 JNI实现
cpp复制extern "C" JNIEXPORT jboolean JNICALL
Java_com_example_SHT30Driver_open(JNIEnv* env, jobject thiz) {
try {
if (!device) {
device = new I2CDevice("/dev/i2c-2");
sensor = new SHT30(*device);
}
return true;
} catch (...) {
return false;
}
}
extern "C" JNIEXPORT jfloatArray JNICALL
Java_com_example_SHT30Driver_read(JNIEnv* env, jobject thiz) {
float temp, hum;
if (sensor && sensor->read(temp, hum)) {
jfloatArray result = env->NewFloatArray(2);
env->SetFloatArrayRegion(result, 0, 1, &temp);
env->SetFloatArrayRegion(result, 1, 1, &hum);
return result;
}
return nullptr;
}
6. 安卓应用集成
6.1 UI设计要点
xml复制<LinearLayout>
<TextView android:id="@+id/tempView"/>
<TextView android:id="@+id/humView"/>
<Button android:id="@+id/refreshBtn"/>
</LinearLayout>
6.2 数据刷新逻辑
java复制private void startMonitoring() {
new Thread(() -> {
while (running) {
float[] data = driver.read();
runOnUiThread(() -> {
tempView.setText(String.format("%.1f℃", data[0]));
humView.setText(String.format("%.1f%%", data[1]));
});
Thread.sleep(1000);
}
}).start();
}
7. 调试与优化
7.1 常见问题排查
-
权限问题:
bash复制adb shell chmod 666 /dev/i2c-* -
接线错误:
- 确认VCC接3.3V
- 检查SDA/SCL是否接反
- 确保GND共地
-
数据异常:
- 检查CRC校验
- 验证转换公式
- 测量电源稳定性
7.2 性能优化建议
- 适当降低采样频率
- 使用硬件滤波电容
- 避免频繁打开/关闭设备
- 考虑使用I2C时钟延展
8. 扩展应用
本方案可轻松适配其他I2C传感器:
- 气压传感器:BMP280
- 环境光传感器:BH1750
- 运动传感器:MPU6050
只需修改传感器特定的命令和数据处理逻辑,底层I2C驱动代码可完全复用。
通过本章内容,我们系统性地实现了从硬件连接到上层应用的完整I2C开发流程。这种方案不仅适用于RK3576平台,也可移植到其他安卓设备,为物联网和嵌入式开发提供了可靠的技术基础。