天天看點

OpenHarmony HDF Input架構子產品 按鍵控制LED基于小熊派micro

文章目錄

      • 摘要
      • 開發環境
      • KEY驅動程式
        • KeyConfigInstance:
        • RegisterKeyDevice
        • 中斷處理函數 KeyIrqHandle
        • RegisterInputDevice:注冊key裝置到檔案系統
        • 小熊派使用key
      • HDI驅動接口
        • manager
          • OpenInputDevice
        • reporter
      • 編寫應用程式

摘要

本文介紹如何使用OpenHarmony的Input架構模型,并編寫app,在按鍵事件進行中翻轉led燈。

小熊派micro 按鍵翻轉led燈

本文目的在于通過學習input架構模型,對openharmony的驅動系統有一個大體的了解。通過本文的學習,應該能夠了解如圖的驅動架構:

OpenHarmony HDF Input架構子產品 按鍵控制LED基于小熊派micro

開發環境

硬體:小熊派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;
}
           
//驅動服務事件的統一回調
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;
}
           

編寫應用程式

#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;
}

           
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",
    ]
}

           

繼續閱讀