使用SHT3x-DIS温湿度传感器的I2C案例 原创 精华

落叶亦知冬
发布于 2023-3-17 16:43
浏览
2收藏

前言

$\qquad$本文将介绍I2C总线、SHT3x DIS温度传感器的相关知识以及OpenHarmony的HDF驱动和NAPI框架的使用方法。


一、I2C总线原理

$\qquad$I2C总线是飞利浦公司开发的一种双向二线制同步串行总线。只需要两根线便可在连接于总线上的器件之间进行传输信息。I2C通信为点对点通信,存在主设备和从设备之分。主从设备通过两根线进行通信,其中两根线分别是SDA和SCL,其中SDA为数据线,SCL为时钟线。
::: hljs-center

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::

$\qquad$主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。
使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区
$\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

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::

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

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::

2.3 通信过程

  • 开始测量
    $\qquad$在开始测量前,主设备必须先把开始测量的信号发送到传感器。发送的信号被称为I2C写入标头,由7比特的I2C设备地址和一个·0(0表示写入,1表示读取),再加上16比特的测量命令构成。当传感器接收到信号时,将会把SDA信号先拉低,响应信号ACK,在第八个时钟信号下降沿时表示传感器接收到了主设备的信号,开始测量。
    ::: hljs-center

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::

  • 模式
    $\qquad$传感器数据采集模式多种多样,我们可以选择不同的方式进行测量以满足不同的应用场景,这便是以上提到写入表头最后两位字节表示的是测量命令,大类分为两种采集模式。
    单次数据采集模式
    使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区
    周期性数据采集模式
    使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

  • 其它命令
    $\qquad$除此之外,传感器里还设置了其它命令,可在传感器说明文档中查看。

  • 数据
    $\qquad$当测量开始时,主设备便可以接收到信号,而此时标头要使用读取标头,将0改为1。传感器返回的后六位字节便是测量到的温度和相对湿度的数据。其中六个字节,高三位为两位温度和一位校验位,低三位为两位相对湿度和一位校验位,采用CRC校验。
    数据转换公式如下:
    ::: hljs-center

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::


三、简单实现

以下代码只是简单地演示如何使用传感器,没有过多的规范要求。
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 配置产品驱动(一般厂商都会配置好,若没配置可以跳转至官方文档查看详细教程)

  1. 实例化驱动入口:
    实例化HdfDriverEntry结构体成员。
    调用HDF_INIT将HdfDriverEntry实例化对象注册到HDF框架中。
  2. 配置属性文件:
    在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;
    }
    ...
    }
}
}
  1. 实例化I2C控制器对象:
    初始化I2cCntlr成员。
    实例化I2cCntlr成员I2cMethod和I2cLockMethod。

p.s 使用到的九联开发板已有相关配置,以上配置无需做更改或添加。

  1. 一个结构三个接口
  • 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

使用SHT3x-DIS温湿度传感器的I2C案例-鸿蒙开发者社区

:::

总结

$\qquad$整个案例整体思路都是围绕着I2C通信流程和SHT3x温度传感器工作流程展开的。而在HDF驱动的使用中,我们会发现,获取设备句柄的方式我们只用一个数字就可以,相比之前的"/dev/i2c-5"更加简易明了,这也是HDF的特性之一。NAPI的实现,将整个OpenHarmoy南北向打通,使得北向程序可通过本地的接口就可以访问传感器的温湿度。

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-3-17 16:47:37修改
6
收藏 2
回复
举报
1条回复
按时间正序
/
按时间倒序
红叶亦知秋
红叶亦知秋

冲着作者的名字我就得给作者点赞


1
回复
2023-3-17 17:24:41
回复
    相关推荐