OpenHarmony HDF Input 原创
@[toc]
一、摘要
本文介绍如何使用OpenHarmony的Input框架模型,并编写app,在按键事件处理中翻转led灯。下面是演示:
https://player.bilibili.com/player.html?aid=894148373
本文目的在于通过学习input框架模型,对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;
}
...
}
1、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;
}
2、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;
}
3、中断处理函数 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);
4、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;
}
5、小熊派使用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。
1、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;
}
}
2、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",
]
}