一、前言
学习了一段时间的OpenHarmony开发体系后,和小伙伴们一同完成了一个简单的小项目,基于 OpenHarmony 的实时水流量监测管理系统。
二、准备
1、BearPi-HM Micro开发板
2、配置好开发环境
3、熟悉官方提供如何编写一个点亮LED灯程序的步骤。
三、实战
1、系统组成及功能说明
(1)整体介绍
物联网的典型架构:
端:传感器模块上,使用的水表传感器不同于常见的速度/容积式水表, 选用的是霍尔传感器作为水流量的测量依据。
管:小熊派板载 wifi 模组使用 UDP 协议发数据上云端。
云:云端使用华为云服务器接收小熊派 UDP 协议上发的数据,同时建立TCP服务器(即利用 socket 库开发的一个 http 服务器)开放 http get 接口给小程序。
用:微信小程序端。用户通过小程序轻松了解到自家用水情况。
(2)各模块介绍
端
在BearPi-HM Micro开发板上外接一个水流量传感器获取水流信息。
霍尔水流量传感器由塑料阀体、水流转子组件和霍尔传感器组成。它装在进水 端,用于检测进水流量,当水通过水流转子组件时,磁性转子转动并且转速随 着流量变化而变化,霍尔传感器输出相应脉冲信号,反馈给控制器,由控制器 判断水流量的大小,进行调控。
所以只要知道连接传感器引脚上的电平值变化,我们就能知道水流量了。
查找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"
if (!HdfSbufWriteUint8(data, eventData))
{
printf("fail to write sbuf!\r\n");
ret = HDF_FAILURE;
goto out;
}
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;
}
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;
}
想法异想天开,编译会报错。 细读官网文档的小字提示:
那么就转战到驱动代码处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)
{
case LED_WRITE_READ:
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;
}
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;
}
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;
}
ret = Stm32LedReadDrs(led, device->property);
if (ret != HDF_SUCCESS) {
HDF_LOGE("%s: get led device resource fail:%d", __func__, ret);
return ret;
}
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函数。
用户态获取服务接口并发送消息到驱动。
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;
}
驱动接收用户态消息。
// Dispatch是用来处理用户态发下来的消息
int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
驱动给用户态发消息。
if (!HdfSbufWriteInt32(reply, status1))
{
HDF_LOGE("replay is fail");
return HDF_FAILURE;
}
用户程序是无法直接访问驱动的,当只有驱动程序向用户态暴露server后,用户程序才能通过Dispatch的方式发送指令到驱动程序,并可以将用户态的数据携带到驱动程序,也可以从驱动程序读出数据,如下图所示为用户态程序与驱动自己数据交互的过程。
就此用户态界面可以获得GPIO的电平变化次数:
根据实际测量得500ml水会引起GPIO电平变化的次数为320次,所以累计水流量可以用公式。
Flow=(T/320)✖500 (ml)表示。