使用SHT3x-DIS温湿度传感器的I2C案例 原创 精华
前言
$\qquad$本文将介绍I2C总线、SHT3x DIS温度传感器的相关知识以及OpenHarmony的HDF驱动和NAPI框架的使用方法。
一、I2C总线原理
$\qquad$I2C总线是飞利浦公司开发的一种双向二线制同步串行总线。只需要两根线便可在连接于总线上的器件之间进行传输信息。I2C通信为点对点通信,存在主设备和从设备之分。主从设备通过两根线进行通信,其中两根线分别是SDA和SCL,其中SDA为数据线,SCL为时钟线。
::: hljs-center
:::
$\qquad$主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。
$\qquad$通信过程包含应答响应,时钟同步。传输的数据字节格式有一定要求,每个字节必须为8位,每次发送的字节字数不受限制,每个字节后面必须跟一位校验位。应答响应,数据传输必须有响应,由主机产生,在响应中发送器将时钟线电平被拉高,接收器将电平拉低,保持稳定的电压差;时钟同步,数据传输只发生在时钟信号的高电平期间,所以需要同步双方时钟信号以确保数据的准确性;
二、传感器SHT3X DIS
$\qquad$Sensirion SHT3x-DIS湿度和温度传感器基于CMOSens®传感器芯片,更加智能、可靠,精度更高。SHT3x-DIS具有增强的信号处理能力、两个独特的用户可选I2C地址,通信速度高达1MHz。SHT35-DIS的典型相对湿度 (RH) 精度为±1.5%,典型温度精度为±0.1°C。SHT3x-DIS具有2.5mm x 2.5mm x 0.9mm(长x宽x高)占位面积,电源电压范围为2.4V至5.5V。
::: hljs-center
:::
2.1 特性
- 完全校准、线性化和温度补偿的数字输出
- I2C接口,通信速度高达1MHz,具有两个用户可选地址
- SHT35的典型精度为+/-1.5% RH和+/-0.1°C
- 启动和测量速度极快
- 2.15V到5.5V的宽电源电压范围
- 小型8引脚DFN封装
2.2 引脚介绍
主要引脚SDA,SCL,VCC,GND。
::: hljs-center
:::
2.3 通信过程
- 开始测量
$\qquad$在开始测量前,主设备必须先把开始测量的信号发送到传感器。发送的信号被称为I2C写入标头,由7比特的I2C设备地址和一个·0(0表示写入,1表示读取),再加上16比特的测量命令构成。当传感器接收到信号时,将会把SDA信号先拉低,响应信号ACK,在第八个时钟信号下降沿时表示传感器接收到了主设备的信号,开始测量。
::: hljs-center
:::
-
模式
$\qquad$传感器数据采集模式多种多样,我们可以选择不同的方式进行测量以满足不同的应用场景,这便是以上提到写入表头最后两位字节表示的是测量命令,大类分为两种采集模式。
单次数据采集模式
周期性数据采集模式
-
其它命令
$\qquad$除此之外,传感器里还设置了其它命令,可在传感器说明文档中查看。 -
数据
$\qquad$当测量开始时,主设备便可以接收到信号,而此时标头要使用读取标头,将0改为1。传感器返回的后六位字节便是测量到的温度和相对湿度的数据。其中六个字节,高三位为两位温度和一位校验位,低三位为两位相对湿度和一位校验位,采用CRC校验。
数据转换公式如下:
::: hljs-center
:::
三、简单实现
以下代码只是简单地演示如何使用传感器,没有过多的规范要求。
3.1 接口定义
int SendCMD(char *devName, char addr, uint16_t command)
{
int fd = -1;
uint8_t cmdBuf[2L] = {0};
struct i2c_rdwr_ioctl_data i2c_data;
fd = open(devName, O_RDWR); //获取I2C设备句柄
i2c_data.nmsgs = 1;
i2c_data.msgs = (struct i2c_msg *)malloc(i2c_data.nmsgs * sizeof(struct i2c_msg));
ioctl(fd, I2C_TIMEOUT, 1);
ioctl(fd, I2C_RETRIES, 2L);
cmdBuf[0] = command >> 8L; //对指令数据进行处理 高八位和低八位
cmdBuf[1] = command & 0xFF;
i2c_data.msgs[0].len = 2L;
i2c_data.msgs[0].addr = addr;
i2c_data.msgs[0].flags = 0;
i2c_data.msgs[0].buf = cmdBuf;
ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data); //将数据写入进行传输
free(i2c_data.msgs);
close(fd);
return 0;
}
// 再定义一些数据转换函数和校验函数 简单的数据转换 忽略
int ConvertTH(uint8_t tempRH, float *rawTemp, float *rawHum);
...
3.2 主函数
int main(int argc, char *argv[])
{
char *dev_name = "/dev/i2c-5";
SendCMD(dev_name,ADDR,0x3093) //重启
usleep(50L * 1000L);
SendCMD(dev_name,ADDR,0x202F) //开始测量
usleep(50L * 1000L);
int fd = -1;
struct i2c_rdwr_ioctl_data i2c_data;
uint8_t rawData[6L] = {0};
float rawTemp = 0, rawHum = 0;
fd = open(devName, O_RDWR);
i2c_data.nmsgs = 1;
i2c_data.msgs = (struct i2c_msg *)malloc(i2c_data.nmsgs * sizeof(struct i2c_msg));
i2c_data.msgs[0].len = 6L;
i2c_data.msgs[0].addr = addr;
i2c_data.msgs[0].flags = 1;
i2c_data.msgs[0].buf = rawData;
ioctl(fd, I2C_RDWR, (unsigned long)&i2c_data);
free(i2c_data.msgs);
close(fd);
ConvertTH(rawData, &rawTemp, &rawHum);
printf("Temp: %.2f°C\nHum: %.2f°F",rawTemp,rawHum);
return 0;
}
四、采用标准系统HDF驱动实现
使用到:九联科技unionpi_tiger开发板,SHT3x-DIS温湿度传感器,OpenHarmony源码。
4.1 配置产品驱动(一般厂商都会配置好,若没配置可以跳转至官方文档查看详细教程)
- 实例化驱动入口:
实例化HdfDriverEntry结构体成员。
调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。 - 配置属性文件:
在device_info.hcs文件中添加deviceNode描述。
//device_info.hcs 配置参考
root {
device_info {
match_attr = "hdf_manager";
device_i2c :: device {
device0 :: deviceNode {
policy = 2;
priority = 50;
permission = 0644;
moduleName = "HDF_PLATFORM_I2C_MANAGER";
serviceName = "HDF_PLATFORM_I2C_MANAGER";
deviceMatchAttr = "hdf_platform_i2c_manager";
}
device1 :: deviceNode {
policy = 0; // 等于0,不需要发布服务
priority = 55; // 驱动启动优先级
permission = 0644; // 驱动创建设备节点权限
moduleName = "hi35xx_i2c_driver"; //【必要】用于指定驱动名称,需要与期望的驱动Entry中的moduleName一致;
serviceName = "HI35XX_I2C_DRIVER"; //【必要】驱动对外发布服务的名称,必须唯一
deviceMatchAttr = "hisilicon_hi35xx_i2c";//【必要】用于配置控制器私有数据,要与i2c_config.hcs中对应控制器保持一致
// 具体的控制器信息在 i2c_config.hcs 中
}
}
}
}
// i2c_config.hcs 配置参考 (需要根据使用的开发板配置)
root {
platform {
i2c_config {
match_attr = "hisilicon_hi35xx_i2c";//【必要】需要和device_info.hcs中的deviceMatchAttr值一致
template i2c_controller { //模板公共参数,继承该模板的节点如果使用模板中的默认值,则节点字段可以缺省
bus = 0; //【必要】i2c 识别号
reg_pbase = 0x120b0000; //【必要】物理基地址
reg_size = 0xd1; //【必要】寄存器位宽
irq = 0; //【可选】根据厂商需要来使用
freq = 400000; //【可选】根据厂商需要来使用
clk = 50000000; //【可选】根据厂商需要来使用
}
controller_0x120b0000 :: i2c_controller {
bus = 0;
}
controller_0x120b1000 :: i2c_controller {
bus = 1;
reg_pbase = 0x120b1000;
}
...
}
}
}
- 实例化I2C控制器对象:
初始化I2cCntlr成员。
实例化I2cCntlr成员I2cMethod和I2cLockMethod。
p.s 使用到的九联开发板已有相关配置,以上配置无需做更改或添加。
- 一个结构三个接口
- I2cMsg结构体:用于传输数据载体,由地址addr,缓存buf,缓存长度len,信号标记flags组成。
struct I2cMsg {
/** Address of the I2C device */
uint16_t addr;
/** Address of the buffer for storing transferred data */
uint8_t *buf;
/** Length of the transferred data */
uint16_t len;
/**
* Transfer Mode Flag | Description
* ------------| -----------------------
* I2C_FLAG_READ | Read flag
* I2C_FLAG_ADDR_10BIT | 10-bit addressing flag
* I2C_FLAG_READ_NO_ACK | No-ACK read flag
* I2C_FLAG_IGNORE_NO_ACK | Ignoring no-ACK flag
* I2C_FLAG_NO_START | No START condition flag
* I2C_FLAG_STOP | STOP condition flag
*/
uint16_t flags;
};
- 三个接口分别为I2cOpen()、I2cClose()、I2cTransfer()。
//number指I2C所挂载的总线号
DevHandle I2cOpen(int16_t number);
//handle是I2cOpen()返回的设备句柄
void I2cClose(DevHandle handle);
//msgs所要传输的数据结构体,count是传输结构体的大小
int32_t I2cTransfer(DevHandle handle, struct I2cMsg *msgs, int16_t count);
4.3 代码
- 头文件
#include <cstdio> //标准输入输出
#include <unistd.h> //使用到usleep()进程挂起函数
#include "i2c_if.h" //HDF i2c 接口
#include "hdf_log.h" //日志打印头文件
- 结构体与接口
// 重新定义结构体方便使用
typedef struct
{
struct I2cMsg * i2cMsg;
uint8_t msgLen; //i2cMsg的长度
} I2cMessage;
//定义命令发送函数
int32_t SendCMD(DevHandle handle,uint16_t command)
{
int32_t ret;
I2cMessage i2cMessage;
i2cMessage.msgLen = 1;
i2cMessage.i2cMsg = new I2cMsg[1]; //申请内存
uint8_t cmdBuf[2L] = {0};
cmdBuf[0] = command >> 8L; //将命令拆分成高低位分别保存
cmdBuf[1] = command & 0xFF;
i2cMessage.i2cMsg[0].len = 2L;
i2cMessage.i2cMsg[0].addr = ADDR;
i2cMessage.i2cMsg[0].flags = WRITE_FLAGS;
i2cMessage.i2cMsg[0].buf = cmdBuf;
ret = I2cTransfer(handle,i2cMessage.i2cMsg,i2cMessage.msgLen);
if(ret < 0){
LOGE("%s: SendCommend faided",__func__);
delete i2cMessage.i2cMsg;
return -1;
}
delete i2cMessage.i2cMsg; //释放内存
usleep(50L * 1000L); //等待发送完成
return 1;
}
- 主函数
int main(int argc, char** argv)
{
/**
* 数据初始化
*/
DevHandle i2cHandle;
/**
* 获取句柄
*/
i2cHandle = I2cOpen(BUSID);
if(i2cHandle == NULL){
LOGE("%s:get handle failed",__func__);
I2cClose(i2cHandle);
return 0;
}
/**
* 发送命令
*/
SendCMD(i2cHandle,0x3093); //关闭reset命令
SendCMD(i2cHandle,0x202F); //发送命令 repeatability=Low mps=0.5
/**
* 接收数据
*/
I2cMessage i2cMessage;
i2cMessage.msgLen = 1;
i2cMessage.i2cMsg = new I2cMsg[1];
uint8_t regData[6L] = {0};
i2cMessage.i2cMsg[0].len = 6L;
i2cMessage.i2cMsg[0].addr = ADDR;
i2cMessage.i2cMsg[0].flags = READ_FLAGS;
i2cMessage.i2cMsg[0].buf = regData;
I2cTransfer(i2cHandle,i2cMessage.i2cMsg,i2cMessage.msgLen);
delete i2cMessage.i2cMsg;
/**
* 数据处理
*/
uint16_t value = 0;
value = regData[0] << 8;
value = value | regData[1];
printf("Temperature: %.2f C\n",175.0f * (float)value / 65535.0f - 45.0f);
value = 0;
value = regData[3] << 8;
value = value | regData[4];
printf("Humidity: %.2f H\n",100.0f * (float)value / 65535.0f);
/**
* 关闭设备
*/
I2cClose(i2cHandle);
return 0;
}
至此,成功通过OpenHarmony的HDF驱动来获取传感器的值
五、实现NAPI
5.1 模块定义与注册
/**
* 模块定义
*/
static napi_module i2cHDF_demoModule = {
.nm_version = 1,
.nm_flags = 0,
.nm_filename = nullptr,
.nm_register_func = registerI2cHDF_DemoApis,
.nm_modname = "i2chdf_demo",
.nm_priv = ((void *)0),
.reserved = {0},
};
/**
* 模块注册
*/
extern "C" __attribute__((constructor)) void RegisterI2cHDFoModule(void)
{
napi_module_register(&i2cHDF_demoModule);
}
5.2 接口定义与注册
int32_t SendCMD(DevHandle handle,uint16_t command)
{
int32_t ret;
struct I2cMsg * i2cMsg;
int msgLen = 1;
i2cMsg = new I2cMsg[msgLen];
uint8_t cmdBuf[2L] = {0};
cmdBuf[0] = command >> 8L;
cmdBuf[1] = command & 0xFF;
i2cMsg[0].len = 2L;
i2cMsg[0].addr = ADDR;
i2cMsg[0].flags = WRITE_FLAGS;
i2cMsg[0].buf = cmdBuf;
ret = I2cTransfer(handle,i2cMsg,msgLen);
delete i2cMsg;
usleep(50L * 1000L);
return 1;
}
/**
* 接口定义
*/
static napi_value readI2cBuf(napi_env env,napi_callback_info info)
{
napi_value ret;
DevHandle i2cHandle;
i2cHandle = I2cOpen(BUSID);
SendCMD(i2cHandle,0x3093);
SendCMD(i2cHandle,0x202F);
struct I2cMsg * i2cMsg;
int msgLen = 1;
i2cMsg = new I2cMsg[msgLen];
uint8_t regData[6L] = {0};
i2cMsg[0].len = 6L;
i2cMsg[0].addr = ADDR;
i2cMsg[0].flags = READ_FLAGS;
i2cMsg[0].buf = regData;
I2cTransfer(i2cHandle,i2cMsg,msgLen);
delete i2cMsg;
uint16_t value = 0;
double sHTTemp = 0;
value = regData[0] << 8;
value = value | regData[1];
sHTTemp = 175.0f * (double)value / 65535.0f - 45.0f;
//设计思路与上HDF大同小异,只不过将最后获取的值通过转换再返回
//此处只处理返回温度的值,方便演示
NAPI_CALL(env, napi_create_double(env, sHTTemp, &ret));
return ret;
}
/**
* 接口注册
*/
static napi_value registerI2cHDF_DemoApis(napi_env env, napi_value exports)
{
napi_property_descriptor desc[] = {
DECLARE_NAPI_FUNCTION("readI2cBuf",readI2cBuf), //NAPI名字,上面的函数
};
NAPI_CALL(env,napi_define_properties(env,exports,sizeof(desc)/sizeof(desc[0]),desc));
return exports;
}
5.3 北向接口
- NAPI
function readI2cBuf(): number;
- Index.ets
import i2chdf from '@ohos.i2chdf'
@Entry
@Component
struct Index {
@State message: string = 'Temperature: '+ i2chdf.readI2cBuf().toFixed(2) + '°C';
aboutToAppear(): void{
var Id = setInterval(() =>{
this.message = 'Temperature: '+ i2chdf.readI2cBuf().toFixed(2) + '°C';
},1000)
}
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
}
.width('100%')
}
.height('100%')
}
}
5.4 效果演示
::: hljs-center
:::
总结
$\qquad$整个案例整体思路都是围绕着I2C通信流程和SHT3x温度传感器工作流程展开的。而在HDF驱动的使用中,我们会发现,获取设备句柄的方式我们只用一个数字就可以,相比之前的"/dev/i2c-5"更加简易明了,这也是HDF的特性之一。NAPI的实现,将整个OpenHarmoy南北向打通,使得北向程序可通过本地的接口就可以访问传感器的温湿度。
冲着作者的名字我就得给作者点赞