5

使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区-51CTO.COM

 1 year ago
source link: https://ost.51cto.com/posts/22041
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

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


一、I2C总线原理

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

使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区

\qquad主器件用于启动总线传送数据,并产生时钟以开放传送的器件,此时任何被寻址的器件均被认为是从器件.在总线上主和从、发和收的关系不是恒定的,而取决于此时数据传送方向。如果主机要发送数据给从器件,则主机首先寻址从器件,然后主动发送数据至从器件,最后由主机终止数据传送;如果主机要接收从器件的数据,首先由主器件寻址从器件.然后主机接收从器件发送的数据,最后由主机终止接收过程。在这种情况下,主机负责产生定时时钟和终止数据传送。

使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区

\qquad通信过程包含应答响应,时钟同步。传输的数据字节格式有一定要求,每个字节必须为8位,每次发送的字节字数不受限制,每个字节后面必须跟一位校验位。应答响应,数据传输必须有响应,由主机产生,在响应中发送器将时钟线电平被拉高,接收器将电平拉低,保持稳定的电压差;时钟同步,数据传输只发生在时钟信号的高电平期间,所以需要同步双方时钟信号以确保数据的准确性;

二、传感器SHT3X DIS

\qquadSensirion 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。

使用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。

使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区

2.3 通信过程

  • 开始测量
    \qquad在开始测量前,主设备必须先把开始测量的信号发送到传感器。发送的信号被称为I2C写入标头,由7比特的I2C设备地址和一个·0(0表示写入,1表示读取),再加上16比特的测量命令构成。当传感器接收到信号时,将会把SDA信号先拉低,响应信号ACK,在第八个时钟信号下降沿时表示传感器接收到了主设备的信号,开始测量。
使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区
  • 模式
    \qquad传感器数据采集模式多种多样,我们可以选择不同的方式进行测量以满足不同的应用场景,这便是以上提到写入表头最后两位字节表示的是测量命令,大类分为两种采集模式。
    单次数据采集模式

    使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区
    周期性数据采集模式
    使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区
  • 其它命令
    \qquad除此之外,传感器里还设置了其它命令,可在传感器说明文档中查看。

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

使用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 北向接口

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 效果演示

使用SHT3x-DIS温湿度传感器的I2C案例-开源基础软件社区

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


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK