#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一) 原创 精华

Z·y
发布于 2022-8-7 22:01
浏览
2收藏

一、前言

学习了一段时间的OpenHarmony开发体系后,和小伙伴们一同完成了一个简单的小项目,基于 OpenHarmony 的实时水流量监测管理系统。

二、准备

1、BearPi-HM Micro开发板
2、配置好开发环境
开发环境搭建
3、熟悉官方提供如何编写一个点亮LED灯程序的步骤
编写一个点亮LED灯程序

三、实战

1、系统组成及功能说明

1.1整体介绍

#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
物联网的典型架构:
端:传感器模块上,使用的水表传感器不同于常见的速度/容积式水表, 选用的是霍尔传感器作为水流量的测量依据。
管:小熊派板载 wifi 模组使用 UDP 协议发数据上云端。
云:云端使用华为云服务器接收小熊派 UDP 协议上发的数据,同时建立TCP服务器(即利用 socket 库开发的一个 http 服务器)开放 http get 接口给小程序。
用:微信小程序端。用户通过小程序轻松了解到自家用水情况。

1.2 各模块介绍

1.2.1端

在BearPi-HM Micro开发板上外接一个水流量传感器获取水流信息。
#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
霍尔水流量传感器由塑料阀体、水流转子组件和霍尔传感器组成。它装在进水 端,用于检测进水流量,当水通过水流转子组件时,磁性转子转动并且转速随 着流量变化而变化,霍尔传感器输出相应脉冲信号,反馈给控制器,由控制器 判断水流量的大小,进行调控。

#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
所以只要知道连接传感器引脚上的电平值变化,我们就能知道水流量了。

查找OpenHarmony设备开发文档,了解如何使用GPIO开发
OpenHarmony设备开发文档-平台驱动使用GPIO
● 设置 GPIO 管脚方向 在进行 GPIO 管脚读写前,需要先通过如下函数设置 GPIO 管脚方向: int32_t GpioSetDir(uint16_t gpio, uint16_t dir);
● 通过 GpioRead()函数读取一个 GPIO 管脚电平: int32_t GpioRead(uint16_t gpio, uint16_t *val);
在点亮LED灯demo的基础上,我们是不是就可以直接在LED业务代码下略加修改就可以读取到管脚上的电平值呢?添加包含GPIO的头文件,就可以直接使用头文件中的函数,这样就可以跳过驱动程序的编写了

#include <sys/stat.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <stdio.h>
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"
 /* 添加的代码    #include "gpio_if.h"

#define LED_WRITE_READ 1
#define LED_SERVICE "hdf_led"

static int SendEvent(struct HdfIoService *serv, uint8_t eventData)
{
    int ret = 0;
    struct HdfSBuf *data = HdfSBufObtainDefaultSize();
    if (data == NULL)
    {
        printf("fail to obtain sbuf data!\r\n");
        return 1;
    }

    struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
    if (reply == NULL)
    {
        printf("fail to obtain sbuf reply!\r\n");
        ret = HDF_DEV_ERR_NO_MEMORY;
        goto out;
    }
    /* 写入数据 */
    if (!HdfSbufWriteUint8(data, eventData))
    {
        printf("fail to write sbuf!\r\n");
        ret = HDF_FAILURE;
        goto out;
    }
    /* 通过Dispatch发送到驱动 */
    ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
    if (ret != HDF_SUCCESS)
    {
        printf("fail to send service call!\r\n");
        goto out;
    }

    int replyData = 0;
    /* 读取驱动的回复数据 */
    if (!HdfSbufReadInt32(reply, &replyData))
    {
        printf("fail to get service call reply!\r\n");
        ret = HDF_ERR_INVALID_OBJECT;
        goto out;
    }
 /* 添加的代码    GpioSetDir(6, GPIO_DIR_IN);
 /* 添加的代码    ret = GpioRead(6, &val);
 /* 添加的代码    printf("\r\nGet reply is: %d\r\n", val);
    printf("\r\nGet reply is: %d\r\n", replyData);
out:
    HdfSBufRecycle(data);
    HdfSBufRecycle(reply);
    return ret;
}

int main(int argc, char **argv)
{
    int i;
    
    /* 获取服务 */
    struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE);
    if (serv == NULL)
    {
        printf("fail to get service %s!\r\n", LED_SERVICE);
        return HDF_FAILURE;
    }

    for (i=0; i < argc; i++)
    {
        printf("\r\nArgument %d is %s.\r\n", i, argv[i]);
    }

    SendEvent(serv, atoi(argv[1]));

    HdfIoServiceRecycle(serv);
    printf("exit");

    return HDF_SUCCESS;
}

==想法异想天开,编译会报错。== 细读官网文档的小字提示:
#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
那么就转战到驱动代码处device\st\drivers\led\led.c编写

#include "hdf_log.h"         
#include "device_resource_if.h"
#include "osal_io.h"
#include "osal.h"
#include "osal_mem.h"
#include "gpio_if.h"

#define HDF_LOG_TAG led_driver // 打印日志所包含的标签,如果不定义则用默认定义的HDF_TAG标签
#define LED_WRITE_READ 1       // 读写操作码1

enum LedOps {
    LED_OFF,
    LED_ON,  
    LED_TOGGLE,
};

struct Stm32Mp1ILed {
    uint32_t gpioNum;
};
static struct Stm32Mp1ILed g_Stm32Mp1ILed;
uint8_t status = 0;
uint32_t status1 = 0;
uint8_t flag = 0;
uint16_t val;
// Dispatch是用来处理用户态发下来的消息
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
{
    uint8_t contrl;
    HDF_LOGE("Led driver dispatch");
    if (client == NULL || client->device == NULL)
    {
        HDF_LOGE("Led driver device is NULL");
        return HDF_ERR_INVALID_OBJECT;
    }

    switch (cmdCode)
    {
    /* 接收到用户态发来的LED_WRITE_READ命令 */
    case LED_WRITE_READ:
        /* 读取data里的数据,赋值给contrl */
        HdfSbufReadUint8(data,&contrl);                  
        switch (contrl)
        {
        /* 开灯 */
        case LED_ON:                                            
           /*  新增代码 */GpioSetDir(6, GPIO_DIR_IN);
            /*  新增代码 */ret = GpioRead(6, &val);
           /*  新增代码 */ status = val;
           /*  新增代码 */ if(status!=flag){
            /*  新增代码 */    GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
            /*  新增代码 */    status1++;
            /*  新增代码 */    flag=!flag;
           /*  新增代码 */ }else{
            /*  新增代码 */    GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
            }                                      
            break;
        /* 关灯 */
        case LED_OFF:                                           
            GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
            status = 0;
            break;
        /* 状态翻转 */
        case LED_TOGGLE:
            if(status == 0)
            {
                GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_LOW);
                status = 1;
            }
            else
            {
                GpioWrite(g_Stm32Mp1ILed.gpioNum, GPIO_VAL_HIGH);
                status = 0;
            }                                        
            break;
        default:
            break;
        }
        /* 把LED的状态值写入reply, 可被带至用户程序 */
        if (!HdfSbufWriteInt32(reply, status))                
        {
            HDF_LOGE("replay is fail");
            return HDF_FAILURE;
        }
        break;
    default:
        break;
    }
    return HDF_SUCCESS;
}

// 读取驱动私有配置
static int32_t Stm32LedReadDrs(struct Stm32Mp1ILed *led, const struct DeviceResourceNode *node)
{
    int32_t ret;
    struct DeviceResourceIface *drsOps = NULL;

    drsOps = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);
    if (drsOps == NULL || drsOps->GetUint32 == NULL) {
        HDF_LOGE("%s: invalid drs ops!", __func__);
        return HDF_FAILURE;
    }
    /* 读取led.hcs里面led_gpio_num的值 */
    ret = drsOps->GetUint32(node, "led_gpio_num", &led->gpioNum, 0); 
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: read led gpio num fail!", __func__);
        return ret;
    }
    return HDF_SUCCESS;
}

//驱动对外提供的服务能力,将相关的服务接口绑定到HDF框架
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL)
    {
        HDF_LOGE("Led driver bind failed!");
        return HDF_ERR_INVALID_OBJECT;
    }
    static struct IDeviceIoService ledDriver = {
        .Dispatch = LedDriverDispatch,
    };
    deviceObject->service = (struct IDeviceIoService *)(&ledDriver);
    HDF_LOGD("Led driver bind success");
    return HDF_SUCCESS;
}

// 驱动自身业务初始的接口
int32_t HdfLedDriverInit(struct HdfDeviceObject *device)
{
    struct Stm32Mp1ILed *led = &g_Stm32Mp1ILed;
    int32_t ret;

    if (device == NULL || device->property == NULL) {
        HDF_LOGE("%s: device or property NULL!", __func__);
        return HDF_ERR_INVALID_OBJECT;
    }
    /* 读取hcs私有属性值 */
    ret = Stm32LedReadDrs(led, device->property);
    if (ret != HDF_SUCCESS) {
        HDF_LOGE("%s: get led device resource fail:%d", __func__, ret);
        return ret;
    }
    /* 将GPIO管脚配置为输出 */
    ret = GpioSetDir(led->gpioNum, GPIO_DIR_OUT);
    if (ret != 0)
    {
        HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
        return ret;
    }
    HDF_LOGD("Led driver Init success");
    return HDF_SUCCESS;
}

// 驱动资源释放的接口
void HdfLedDriverRelease(struct HdfDevijceObect *deviceObject)
{
    if (deviceObject == NULL)
    {[请添加链接描述](https://docs.openharmony.cn/pages/v3.1/zh-cn/device-dev/driver/driver-hdf-message-management.md/)
    }
    HDF_LOGD("Led driver release success");
    return;
}

// 定义驱动入口的对象,必须为HdfDriverEntry(在hdf_device_desc.h中定义)类型的全局变量
struct HdfDriverEntry g_ledDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_LED",
    .Bind = HdfLedDriverBind,
    .Init = HdfLedDriverInit,
    .Release = HdfLedDriverRelease,
};

// 调用HDF_INIT将驱动入口注册到HDF框架中
HDF_INIT(g_ledDriverEntry);

在此之前要了解OpenHarmony的驱动程序框架HDF(Hardware Driver Foundation),内核态驱动和用户态应用如何互相传递消息。
官方文档:驱动消息机制管理
详情解读,可参考这位开发者的文章:【FFH】HDF驱动开发之编写驱动代码
简单概括就是主要靠Dispatch函数
1)用户态获取服务接口并发送消息到驱动

    ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
    if (ret != HDF_SUCCESS)
    {
        printf("fail to send service call!\r\n");
        goto out;
    }

2)驱动接收用户态消息

// Dispatch是用来处理用户态发下来的消息
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)

3)驱动给用户态发消息

/* 把LED的状态值写入reply, 可被带至用户程序 */
        if (!HdfSbufWriteInt32(reply, status1))                
        {
            HDF_LOGE("replay is fail");
            return HDF_FAILURE;
        }

用户程序是无法直接访问驱动的,当只有驱动程序向用户态暴露server后,用户程序才能通过Dispatch的方式发送指令到驱动程序,并可以将用户态的数据携带到驱动程序,也可以从驱动程序读出数据,如下图所示为用户态程序与驱动自己数据交互的过程。
#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
就此用户态界面可以获得GPIO的电平变化次数:
#物联网征文#【FFH】基于 OpenHarmony 的水流量监测系统(一)-鸿蒙开发者社区
根据实际测量得500ml水会引起GPIO电平变化的次数为320次,所以累计水流量可以用公式
Flow=(T/320)✖500 (ml)表示。

四、后记

剩下的部分正在整理,敬请期待……
【本文正在参加物联网有奖征文活动】,活动链接:https://ost.51cto.com/posts/14758;

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

像水坝排水也可以用这种方式计算吗?

回复
2022-8-8 10:25:28
红叶亦知秋
红叶亦知秋

楼主的第二篇链接:https://ost.51cto.com/posts/15850

回复
2022-8-9 15:06:13
殇时云起
殇时云起

打通南北的大佬

回复
2022-8-12 18:12:05
回复
    相关推荐