文章目錄
-
-
- 摘要
- 開發環境
- KEY驅動程式
-
- KeyConfigInstance:
- RegisterKeyDevice
- 中斷處理函數 KeyIrqHandle
- RegisterInputDevice:注冊key裝置到檔案系統
- 小熊派使用key
- HDI驅動接口
-
- manager
-
- OpenInputDevice
- reporter
- 編寫應用程式
-
摘要
本文介紹如何使用OpenHarmony的Input架構模型,并編寫app,在按鍵事件進行中翻轉led燈。
小熊派micro 按鍵翻轉led燈
本文目的在于通過學習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;
}
...
}
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",
]
}