摘要

本文介绍如何使用OpenHarmony的Input框架模型,并编写app,在按键事件处理中翻转led灯。

小熊派micro 按键翻转led灯

本文目的在于通过学习input框架模型,对openharmony的驱动系统有一个大体的理解。通过本文的学习,应该能够理解如图的驱动框架:
OpenHarmony HDF Input框架模块 按键控制LED基于小熊派micro_openharmony

开发环境

硬件:小熊派micro

KEY驱动程序

在openharmony中已经完成了key驱动程序的编写。源码在:/drivers/framework/model/input/driver/hdf_key.c,我们需要做的就是在配置文件中增加key节点的配置信息,key驱动程序就能成功加载。

首先看key驱动程序源码,key驱动使用到gpio的接口,了解该部分内容可查看:OpenHarmony HDF 按键中断开发基于小熊派hm micro

驱动程序入口对象:

struct HdfDriverEntry g_hdfKeyEntry = {
    .moduleVersion = 1,
    .moduleName = "HDF_KEY",
    .Bind = HdfKeyDriverBind,
    .Init = HdfKeyDriverInit,
    .Release = HdfKeyDriverDeInit,
};

HDF_INIT(g_hdfKeyEntry);

主要起作用的是Init函数:其分为两部分,首先根据配置文件读取key的IO管脚、中断模式等信息,然后根据配置初始化gpio,注册中断函数,并注册一个代表key的input_device到input_device_manager。

static int32_t HdfKeyDriverInit(struct HdfDeviceObject *device)
{
	...
    //根据input_config.hcs创建keyCfg
    KeyChipCfg *keyCfg = KeyConfigInstance(device);
    if (keyCfg == NULL)
    {
        HDF_LOGE("%s: instance key config failed", __func__);
        return HDF_ERR_MALLOC_FAIL;
    }
    //创建inputdev,注册到input_manager
    int32_t ret = RegisterKeyDevice(keyCfg);
    if (ret != HDF_SUCCESS)
    {
        goto EXIT;
    }
	...
}

KeyConfigInstance:

//创建keycfg对象
static KeyChipCfg *KeyConfigInstance(struct HdfDeviceObject *device)
{
    KeyChipCfg *keyCfg = (KeyChipCfg *)OsalMemAlloc(sizeof(KeyChipCfg));

    (void)memset_s(keyCfg, sizeof(KeyChipCfg), 0, sizeof(KeyChipCfg));
    keyCfg->hdfKeyDev = device; //保存按键设备

    //读取input_config.hcs中的按键配置信息到keycfg
    if (ParseKeyConfig(device->property, keyCfg) != HDF_SUCCESS)
    {
        HDF_LOGE("%s: parse key config failed", __func__);
        OsalMemFree(keyCfg);
        keyCfg = NULL;
    }
    return keyCfg;
}

ParseKeyConfig:解析deviceNode的信息,我们需要根据这个函数来编写key的配置信息。见下文

int32_t ParseKeyConfig(const struct DeviceResourceNode *node, KeyChipCfg *config)
{
	...
    struct DeviceResourceIface *parser = NULL;
    parser = DeviceResourceGetIfaceInstance(HDF_CONFIG_SOURCE);

    const struct DeviceResourceNode *keyNode = node;
    int32_t ret = parser->GetString(keyNode, "keyName", &config->keyName, NULL);
    CHECK_PARSER_RET(ret, "GetString");
    ret = parser->GetUint8(keyNode, "inputType", &config->devType, 0);
    CHECK_PARSER_RET(ret, "GetUint8");
    ret = parser->GetUint16(keyNode, "gpioNum", &config->gpioNum, 0);
    CHECK_PARSER_RET(ret, "GetUint32");
    ret = parser->GetUint16(keyNode, "irqFlag", &config->irqFlag, 0);
    CHECK_PARSER_RET(ret, "GetUint8");
    ret = parser->GetUint32(keyNode, "debounceTime", &config->debounceTime, 0); //好像没使用到
    CHECK_PARSER_RET(ret, "GetUint32");

    return HDF_SUCCESS;
}

RegisterKeyDevice

//创建inputDevice并注册到manager
static int32_t RegisterKeyDevice(KeyChipCfg *keyCfg)
{
    KeyDriver *keyDrv = KeyDriverInstance(keyCfg);

    //初始化按键中断
    int32_t ret = KeyInit(keyDrv);
    
    //创建inputDevice
    InputDevice *inputDev = InputDeviceInstance(keyDrv);

    //注册输入设备到input_device_manager(使用hdf_input_device_manager.h提供的接口)
    ret = RegisterInputDevice(inputDev);

    return HDF_SUCCESS;
	...
}


KeyInit就是调用:SetupKeyIrq 设置key中断处理函数

//初始化按键中断(使用gpio_if.h提供的gpio操作方法)
static int32_t SetupKeyIrq(KeyDriver *keyDrv)
{
    uint16_t intGpioNum = keyDrv->keyCfg->gpioNum;
    //irqFlag定义参考osal_irq.h
    uint16_t irqFlag = keyDrv->keyCfg->irqFlag;
    //设置gpio为输入
    int32_t ret = GpioSetDir(intGpioNum, GPIO_DIR_IN);

    //设置gpio中断触发模式,中断线程处理模式,中断线程为KeyIrqHandle 参数为keyDrv
    ret = GpioSetIrq(intGpioNum, irqFlag | GPIO_IRQ_USING_THREAD, KeyIrqHandle, keyDrv);

    //使能中断
    ret = GpioEnableIrq(intGpioNum);

    return HDF_SUCCESS;
}

中断处理函数 KeyIrqHandle

由于调用GpioSetIrq传入的参数是GPIO_IRQ_USING_THREAD,所以KeyIrqHandle是在线程环境中执行的。

在中断线程中,读取IO口电平,通过hdf的事件接口将当前的按键事件上报给应用。

//按键中断线程处理函数
int32_t KeyIrqHandle(uint16_t intGpioNum, void *data)
{
    uint16_t gpioValue = 0;
    KeyDriver *driver = (KeyDriver *)data;

    KeyEventData *event = &driver->eventData;

    //关闭中断
    int32_t ret = GpioDisableIrq(intGpioNum);

    //读取按键GPIO的值
    ret = GpioRead(intGpioNum, &gpioValue);

    //获取当前时间戳
    uint64_t curTime = OsalGetSysTimeMs();
    //保存时间戳
    driver->preStatus = gpioValue;
    driver->timeStamp = curTime;

    if (gpioValue == GPIO_VAL_LOW)
    {
        event->definedEvent = INPUT_KEY_DOWN;
        //上报key事件
        input_report_key(driver->inputdev, KEY_POWER, 1);
    }
    else if (gpioValue == GPIO_VAL_HIGH)
    {
        event->definedEvent = INPUT_KEY_UP;
        input_report_key(driver->inputdev, KEY_POWER, 0);
    }
    //同步事件(表示一个事件完成)
    input_sync(driver->inputdev);
	//打开中断
    GpioEnableIrq(intGpioNum);
    return HDF_SUCCESS;
}

中断事件的上报流程如下:

input_report_key ->PushOnePackage -> HdfDeviceSendEvent

hdf_device_desc.h:
/**
 * @brief Sends event messages.
 *
 * When the driver service invokes this function to send a message, all user-level applications that have registered
 * listeners through {@link HdfDeviceRegisterEventListener} will receive the message.
 *
 * @param deviceObject Indicates the pointer to the driver device object.
 * @param id Indicates the ID of the message sending event.
 * @param data Indicates the pointer to the message content sent by the driver.
 *
 * @return Returns <b>0</b> if the operation is successful; returns a non-zero value otherwise.
 * @since 1.0
 */
int32_t HdfDeviceSendEvent(const struct HdfDeviceObject *deviceObject, uint32_t id, const struct HdfSBuf *data);

RegisterInputDevice:注册key设备到文件系统

类似于linux,把驱动程序加入到虚拟文件系统,提供接口给上层应用。

//注册输入设备给输入设备管理器
int32_t RegisterInputDevice(InputDevice *inputDev)
{
	...
    //获取信号量
    OsalMutexLock(&g_inputManager->mutex);
    //分配设备id

    //注册到文件系统
    ret = CreateDeviceNode(inputDev);

    //申请package的内存,用于上报数据
    ret = AllocPackageBuffer(inputDev);

    //添加到inputdev链表
    AddInputDevice(inputDev);
    //释放信号量
    OsalMutexUnlock(&g_inputManager->mutex);
    HDF_LOGI("%s: exit succ, devCount is %d", __func__, g_inputManager->devCount);
    return HDF_SUCCESS;
	...
}

CreateDeviceNode就是调用HidRegisterHdfDevice:

//注册hdf设备到文件系统
static struct HdfDeviceObject *HidRegisterHdfDevice(InputDevice *inputDev)
{
    char svcName[SERVICE_NAME_LEN] = {0};
    const char *moduleName = "HDF_HID";
    struct HdfDeviceObject *hdfDev = NULL;

    int32_t len = (inputDev->devId < PLACEHOLDER_LIMIT) ? 1 : PLACEHOLDER_LENGTH;
    //组装字符串:hdf_input_event0,1,2....
    int32_t ret = snprintf_s(svcName, SERVICE_NAME_LEN, strlen("hdf_input_event") + len, "%s%u",
        "hdf_input_event", inputDev->devId);

	//注册到/dev目录
    hdfDev = HdfRegisterDevice(moduleName, svcName, NULL);

    return hdfDev;
}

小熊派使用key

根据小熊派的原理图,可知F1按键的管脚为PG2,对应的IO编号为: PG2 = (6 * 16 + 3)-1 = 98;

需要修改两个文件:

  • bearpi-micro\device\st\bearpi_hm_micro\liteos_a\hdf_config\device_info\device_info.hcs
  • D:\VMware\share\bearpi-micro\device\st\bearpi_hm_micro\liteos_a\hdf_config\input\input_config.hcs

在device_info.hcs中,在input节点下新增key节点,可仿照device_hdf_touch来创建,如下:

root {
    device_info {        
        input :: host {
			//按键
            device_hdf_key :: device {
                device0 :: deviceNode {
                    policy = 2;
                    priority = 20;
                    preload = 0;
                    permission = 0660;
                    moduleName = "HDF_KEY";
                    serviceName = "hdf_input_event2";	//服务名称,HDI由此绑定驱动程序
                    deviceMatchAttr = "key_device1";
                }
            }
            

在input_config.hcs下同样新增key节点如下:

root {
    input_config {
        keyConfig{
            key0{
                match_attr = "key_device1";
                keyName = "key0";
                inputType = 1;      ///* 0:touch 1:key 2:keyboard 3:mouse 4:button 5:crown 6:encoder */
                gpioNum = 98;       // PG2 = (6 * 16 + 3)-1 = 98; 
                irqFlag = 2;        //下降沿触发 在osal_irq.h的定义
                debounceTime = 0;   //防抖时间 (不需要,有电容消抖)
            }
        },

HDI驱动接口

HDI调用了驱动在虚拟文件系统中的节点,再进行一个封装,提供给上层服务或用户。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aI5XUfz1-1645264389241)(picture/hdi-architecture-of-the-input-module.png)]

根据上图可知,InputHDI就是由三部分组成的。这三部分分别负责不同的功能。

  • InputManager:管理输入设备,包括输入设备的打开、关闭、设备列表信息获取等;
  • InputReporter:负责输入事件的上报,包括注册、注销数据上报回调函数等;
  • InputController:提供input设备的业务控制接口,包括获取器件信息及设备类型、设置电源状态等。

我们将精力放在manager以及reporter上,这两个是我们应用程序即将使用到的接口。我们用manager打开key设备,用reporter注册上报回调函数,再回调函数中翻转led。

manager

要使用这三个兄弟,首先要给他们一个妈,先调用**GetInputInterface()**获取input hdi接口对象:

这里创建了两个对象:

  • InstanceInputHdi:三个部分
  • InitDevManager:对外隐藏了的设备管理器,用于管理所有打开 的设备
//获取input hdi接口
int32_t GetInputInterface(IInputInterface **inputInterface)
{
    int32_t ret;
    IInputInterface *inputHdi = NULL;

    //创建input hdi接口
    inputHdi = InstanceInputHdi();

    //创建设备管理器
    ret = InitDevManager();

    *inputInterface = inputHdi;
    HDF_LOGI("%s: exit succ", __func__);
    return INPUT_SUCCESS;
}

InstanceInputHdi:创建了上述的三个对象,InstanceManagerHdi、InstanceControllerHdi、InstanceReporterHdi都是在实例化对象。

//实例化接口
static IInputInterface *InstanceInputHdi(void)
{
    int32_t ret;
    IInputInterface *hdi = (IInputInterface *)malloc(sizeof(IInputInterface));

    (void)memset_s(hdi, sizeof(IInputInterface), 0, sizeof(IInputInterface));
    //初始化manager
    ret = InstanceManagerHdi(&hdi->iInputManager);

    //初始化controler
    ret = InstanceControllerHdi(&hdi->iInputController);

    //初始化reporter
    ret = InstanceReporterHdi(&hdi->iInputReporter);

    return hdi;
}

好的,三个兄弟已经被生出来了,现在我们能使用他们的方法了。

OpenInputDevice

我们要使用key,就需要先“打开”这个设备。其实就是绑定key 驱动提供的服务,绑定的方法是通过HdfIoServiceBind()。

//绑定设备提供的服务
static int32_t OpenInputDevice(uint32_t devIndex)
{
    int32_t ret;
    struct HdfIoService *service = NULL;
    char serviceName[SERVICE_NAME_LEN] = {0};
    //检测下标
    if (CheckIndex(devIndex) != INPUT_SUCCESS) {
        return INPUT_FAILURE;
    }
    //初始化节点的字符串
    int32_t len = (devIndex < PLACEHOLDER_LIMIT) ? 1 : PLACEHOLDER_LENGTH;
    ret = snprintf_s(serviceName, SERVICE_NAME_LEN, strlen("hdf_input_event") + len, "%s%u",
        "hdf_input_event", devIndex);
\
    //通过节点名称绑定驱动服务
    service = HdfIoServiceBind(serviceName);

    //添加服务到设备管理器
    if (AddService(devIndex, service) < 0) {
        HDF_LOGE("%s: add device%d failed", __func__, devIndex);
        HdfIoServiceRecycle(service);
        return INPUT_FAILURE;
    }
}

reporter

现在我们可以使用key设备了,要读取key上报的事件,就需要调用**RegisterReportCallback()**注册上报回调函数。

//创建listener
static struct HdfDevEventlistener *EventListenerInstance(void)
{
    struct HdfDevEventlistener *listener = (struct HdfDevEventlistener *)malloc(sizeof(struct HdfDevEventlistener));
    (void)memset_s(listener, sizeof(struct HdfDevEventlistener), 0, sizeof(struct HdfDevEventlistener));
    //接收回调函数
    listener->onReceive = EventListenerCallback;
    return listener;
}

//注册设备上报回调函数
static int32_t RegisterReportCallback(uint32_t devIndex, InputEventCb *callback)
{
    DeviceInfoNode *pos = NULL;
    DeviceInfoNode *next = NULL;
    InputDevManager *manager = NULL;

    //获取设备管理器
    GET_MANAGER_CHECK_RETURN(manager);

    pthread_mutex_lock(&manager->mutex);
    DLIST_FOR_EACH_ENTRY_SAFE(pos, next, &manager->devList, DeviceInfoNode, node) {
        //遍历输入设备链表
        if (pos->payload.devIndex != devIndex) {
            continue;
        }
        
        struct HdfDevEventlistener *listener = EventListenerInstance();

        //监听设备上报的事件,当设备调用 HdfDeviceSendEvent()发送事件时,listener被回调
        if (HdfDeviceRegisterEventListener(pos->service, listener) != INPUT_SUCCESS) {
            free(listener);
            pthread_mutex_unlock(&manager->mutex);
            return INPUT_FAILURE;
        }
        manager->evtCallbackNum++;
        //绑定回调函数到DeviceInfoNode
        pos->eventCb = callback;
        //绑定listener到DeviceInfoNode
        pos->listener = listener;
        pthread_mutex_unlock(&manager->mutex);

        return INPUT_SUCCESS;
    }

    pthread_mutex_unlock(&manager->mutex);

    return INPUT_FAILURE;
}

当设备调用 HdfDeviceSendEvent()发送事件时,listener被回调。

//驱动服务事件的统一回调
static int32_t EventListenerCallback(struct HdfDevEventlistener *listener, struct HdfIoService *service,
    uint32_t id, struct HdfSBuf *data)
{
    (void)listener;
    (void)id;
    int32_t count = 0;
    uint32_t len = 0;
    EventPackage *pkgs[MAX_EVENT_PKG_NUM] = {0};
    DeviceInfoNode *pos = NULL;
    DeviceInfoNode *next = NULL;
    InputDevManager *manager = NULL;

    if (service == NULL || data == NULL) {
        HDF_LOGE("%s: invalid param", __func__);
        return INPUT_INVALID_PARAM;
    }
    //获取设备管理器
    manager = GetDevManager();
    if (manager == NULL) {
        HDF_LOGE("%s: get manager failed", __func__);
        return INPUT_NULL_PTR;
    }

    while (true) {
        if (count >= MAX_EVENT_PKG_NUM) {
            break;
        }
        //读取pkgs数据到data
        if (!HdfSbufReadBuffer(data, (const void **)&pkgs[count], &len)) {
            HDF_LOGE("%s: sbuf read finished", __func__);
            break;
        }

        if (pkgs[count] == NULL) {
            break;
        }
        count++;
    }
    //遍历输入设备列表
    DLIST_FOR_EACH_ENTRY_SAFE(pos, next, &manager->devList, DeviceInfoNode, node) {
        if (pos->service == service) {
            //匹配成功,调用用户注册的回调函数 (在本例子中是KeyIrqHandle)
            pos->eventCb->EventPkgCallback((const EventPackage **)pkgs, count, pos->payload.devIndex);
        }
    }
    return INPUT_SUCCESS;
}

编写应用程序

应用程序的代码很简单,就是调用HDI提供的函数。(如何编写应用程序可查看小熊派led程序教程)

#include "input_manager.h"
#include "input_reporter.h"
#include "hdf_sbuf.h"
#include "hdf_io_service_if.h"
#include "osal_time.h"
#include <stdio.h>

#define KEY_INDEX 2   //hdf_input_event2
#define INIT_DEFAULT_VALUE 1
#define LED_SERVICE "hdf_led"
IInputInterface *g_inputInterface;
InputEventCb g_callback;
struct HdfIoService *LedService;
static int led_status = 0;

static int SendEvent(struct HdfIoService *service,uint8_t data)
{
        //使用HdfSBufObtainDefaultSize申请内存,这种类型的内存才能用于和驱动程序交换数据
        struct HdfSBuf *send_buf = HdfSBufObtainDefaultSize();
        if(send_buf == NULL)
        {
                printf("send_buf fail\r\n");
                return -1;
        }
        struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
        if(reply == NULL)
        {
                printf("reply fail\r\n");
                goto out;
        }

        //将数据写入send_buf
        if (!HdfSbufWriteUint8(send_buf, data))
        {
                printf("write send_buf fail\r\n");
                goto out;
        }
         /* 通过Dispatch发送数据到驱动,同时驱动会将数据写入reply */
		int ret = service->dispatcher->Dispatch(&service->object, LED_WRITE_READ, send_buf, reply);
        if(ret != HDF_SUCCESS)
        {
                printf("Dispatch fail\r\n");
                goto out;
        }
        int replyData = 0;
        //读取reply的数据
        if (!HdfSbufReadInt32(reply, &replyData))
		{
			printf("fail to get service call reply!\r\n");
			goto out;
		}
		//回收内存

out:
    HdfSBufRecycle(send_buf);
    HdfSBufRecycle(reply);
    return 0;
}



/* 定义数据上报的回调函数 */
static void ReportEventPkgCallback(const EventPackage **pkgs, uint32_t count,uint32_t devIndex)
{
    if (pkgs == NULL) {
        return;
    }
    printf("ReportEventPkgCallback: recv pkgs count = %d,devIndex = %d\r\n",count,devIndex);
    for (uint32_t i = 0; i < count; i++) {
		//pkgs[0] = 0x1, 0x74,1 pkgs[1] = 0x1,0x74,0
        printf("pkgs[%d] = 0x%x, 0x%x, %d\r\n", i, pkgs[i]->type, pkgs[i]->code, pkgs[i]->value);
 
    }
	led_status == 0 ? 1 : 0;
	SendEvent(LedService,led_status);
  
}

void LED_Init()
{

    LedService = HdfIoServiceBind(LED_SERVICE);

}
int main(void)
{
    LED_Init();
    int ret = GetInputInterface(&g_inputInterface);
    if (ret != INPUT_SUCCESS) {
        printf("%s: get input interfaces failed, ret = %d", __func__, ret);
        return ret;
    }
    /* 打开特定的input设备 hdf_input_event2*/
    ret = g_inputInterface->iInputManager->OpenInputDevice(KEY_INDEX);
    if (ret) {
        printf("%s: open input device failed, ret = %d", __func__, ret);
 	    return ret;
    }
    

    /* 给特定的input设备注册数据上报回调函数 */
    g_callback.EventPkgCallback = ReportEventPkgCallback;
   
    ret  = g_inputInterface->iInputReporter->RegisterReportCallback(KEY_INDEX, &g_callback);
    if (ret) {
        printf("%s: register callback failed, ret: %d", __FUNCTION__, ret);
	    return ret;
    }
    printf("%s: running for testing, pls touch the panel now", __FUNCTION__);

    while(1)
    {
        OsalMSleep(500);    
    }

    /* 注销特定input设备上的回调函数 */
    ret  = g_inputInterface->iInputReporter->UnregisterReportCallback(KEY_INDEX);
    if (ret) {
        printf("%s: unregister callback failed, ret: %d", __FUNCTION__, ret);
        return ret;
    }

    /* 关闭特定的input设备 */
    ret = g_inputInterface->iInputManager->CloseInputDevice(KEY_INDEX);
    if (ret) {
        printf("%s: close device failed, ret: %d", __FUNCTION__, ret);
	return ret;
    }
    return 0;
}

注意BUILD.gn也需要添加一些头文件路径,依赖:

import("//build/lite/config/component/lite_component.gni")

executable("hello_world_lib"){
    output_name = "hello_world"
    sources = [ "hello.c" ]
    include_dirs = [
    "//drivers/framework/ability/sbuf/include",
    "//drivers/framework/include/core",
    "//drivers/framework/include/osal",
    "//drivers/adapter/uhdf/posix/include",
    "//drivers/framework/include/utils",
    "//drivers/framework/include/config",
    "//drivers/framework/include",
    "//drivers/peripheral/input/hal/include",
    "//drivers/peripheral/input/interfaces/include",
    "//third_party/FreeBSD/sys/dev/evdev",
    ]
    defines = [
            "__USER__",
            "__HDMI_SUPPORT__",
    ]
    cflags_c = [
            "-Wall",
    "-Wextra",
    "-Werror",
    "-fsigned-char",
    "-fno-common",
    "-fno-strict-aliasing",
    ]
    ldflags = []
    deps = [
    "//drivers/adapter/uhdf/manager:hdf_core",
    "//drivers/adapter/uhdf/platform:hdf_platform",
    "//drivers/adapter/uhdf/posix:hdf_posix_osal",
    "//drivers/peripheral/input/hal:hdi_input",
    ]
    public_deps = [ "//third_party/bounds_checking_function:libsec_shared" ]
}
lite_component("my_app")
{
    features = [
        ":hello_world_lib",
    ]
}