用鴻蒙開發(fā)AI應(yīng)用(五)HDF 驅(qū)動補光燈
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos.51cto.com/#zz
前言
上一篇,我們在鴻蒙上運行了第一個程序,這一篇我們來編寫一個驅(qū)動開啟攝像頭的紅外補光燈,順便熟悉一下鴻蒙上的 HDF 驅(qū)動開發(fā)。
硬件準(zhǔn)備
先查一下原理圖(具體可參考第一篇的硬件資料),找到紅外燈的 IO 口編號,GPIO5_1。

HDF 驅(qū)動開發(fā)
1. 簡介
HDF(OpenHarmony Driver Foundation)驅(qū)動框架,為驅(qū)動開發(fā)者提供驅(qū)動框架能力,包括驅(qū)動加載、驅(qū)動服務(wù)管理和驅(qū)動消息機制。旨在構(gòu)建統(tǒng)一的驅(qū)動架構(gòu)平臺,為驅(qū)動開發(fā)者提供更精準(zhǔn)、更高效的開發(fā)環(huán)境,力求做到一次開發(fā),多系統(tǒng)部署。
HDF框架以組件化的驅(qū)動模型作為核心設(shè)計思路,為開發(fā)者提供更精細化的驅(qū)動管理,讓驅(qū)動開發(fā)和部署更加規(guī)范。HDF框架將一類設(shè)備驅(qū)動放在同一個host里面,驅(qū)動內(nèi)部實現(xiàn)開發(fā)者也可以將驅(qū)動功能分層獨立開發(fā)和部署,支持一個驅(qū)動多個node,HDF框架管理驅(qū)動模型如下圖所示:

2. 驅(qū)動框架
2.1 驅(qū)動框架實現(xiàn)
在 huawei/hdf 目錄下新建一個文件夾 led, 然后在其中新建一個源文件 led.c。
- #include "hdf_device_desc.h" // HDF框架對驅(qū)動開放相關(guān)能力接口的頭文件
- #include "hdf_log.h" // HDF 框架提供的日志接口頭文件
- #define HDF_LOG_TAG led_driver // 打印日志所包含的標(biāo)簽,如果不定義則用默認定義的HDF_TAG標(biāo)簽
- //驅(qū)動對外提供的服務(wù)能力,將相關(guān)的服務(wù)接口綁定到HDF框架
- int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
- {
- HDF_LOGD("Led driver bind success");
- return 0;
- }
- // 驅(qū)動自身業(yè)務(wù)初始的接口
- int32_t HdfLedDriverInit(struct HdfDeviceObject *deviceObject)
- {
- if (deviceObject == NULL) {
- HDF_LOGE("Led driver Init failed!");
- return HDF_ERR_INVALID_OBJECT;
- }
- HDF_LOGD("Led driver Init success");
- return HDF_SUCCESS;
- }
- // 驅(qū)動資源釋放的接口
- void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject)
- {
- if (deviceObject == NULL) {
- HDF_LOGE("Led driver release failed!");
- return;
- }
- HDF_LOGD("Led driver release success");
- return;
- }
2.2 驅(qū)動入口注冊到HDF框架
- // 定義驅(qū)動入口的對象,必須為HdfDriverEntry(在hdf_device_desc.h中定義)類型的全局變量
- struct HdfDriverEntry g_ledDriverEntry = {
- .moduleVersion = 1,
- .moduleName = "led_driver",
- .Bind = HdfLedDriverBind,
- .Init = HdfLedDriverInit,
- .Release = HdfLedDriverRelease,
- };
- // 調(diào)用HDF_INIT將驅(qū)動入口注冊到HDF框架中,在加載驅(qū)動時HDF框架會先調(diào)用Bind函數(shù),再調(diào)用Init函數(shù)加載該驅(qū)動,當(dāng)Init調(diào)用異常時,HDF框架會調(diào)用Release釋放驅(qū)動資源并退出。
- HDF_INIT(g_ledDriverEntry);
3. 驅(qū)動編譯
在 huawei/hdf/led 目錄下新建編譯文件 Makefile
- include $(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk #導(dǎo)入hdf預(yù)定義內(nèi)容,必需
- MODULE_NAME := hdf_led_driver #生成的結(jié)果文件
- LOCAL_SRCS += led.c #本驅(qū)動的源代碼文件
- LOCAL_INCLUDE := ./include #本驅(qū)動的頭文件目錄
- LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror #自定義的編譯選項
- include $(HDF_DRIVER) #導(dǎo)入模板makefile完成編譯
這里的hdf_led_driver為驅(qū)動文件名,注意對應(yīng)關(guān)系。
4. 編譯結(jié)果鏈接到內(nèi)核鏡像
修改 huawei/hdf/hdf_vendor.mk 文件,添加以下代碼
- LITEOS_BASELIB += -lhdf_led_driver #鏈接生成的靜態(tài)庫
- LIB_SUBDIRS += $(VENDOR_HDF_DRIVERS_ROOT)/led #驅(qū)動代碼Makefile的目錄
填入驅(qū)動文件名和源碼路徑。
5. 驅(qū)動配置
驅(qū)動配置包含兩部分,HDF框架定義的驅(qū)動設(shè)備描述和驅(qū)動的私有配置信息。
5.1 驅(qū)動設(shè)備描述(必選)
HDF框架加載驅(qū)動所需要的信息來源于HDF框架定義的驅(qū)動設(shè)備描述。
修改 vendor/hisi/hi35xx/hi3516dv300/config/device_info/device_info.hcs配置文件,添加驅(qū)動的設(shè)備描述。
- platform :: host {
- hostName = "platform_host"; // host名稱,host節(jié)點是用來存放某一類驅(qū)動的容器
- priority = 50; // host啟動優(yōu)先級(0-200),值越大優(yōu)先級越低,建議默認配100,優(yōu)先級相同則不保證host的加載順序
- device_led :: device { // led設(shè)備節(jié)點
- device0 :: deviceNode { // led驅(qū)動的DeviceNode節(jié)點
- policy = 2; // policy字段是驅(qū)動服務(wù)發(fā)布的策略,在驅(qū)動服務(wù)管理章節(jié)有詳細介紹
- priority = 100; // 驅(qū)動啟動優(yōu)先級(0-200),值越大優(yōu)先級越低,建議默認配100,優(yōu)先級相同則不保證device的加載順序
- preload = 0; // 驅(qū)動按需加載字段
- permission = 0666; // 驅(qū)動創(chuàng)建設(shè)備節(jié)點權(quán)限
- moduleName = "led_driver"; // 驅(qū)動名稱,該字段的值必須和驅(qū)動入口結(jié)構(gòu)的moduleName值一致
- serviceName = "led_service"; // 驅(qū)動對外發(fā)布服務(wù)的名稱,必須唯一
- deviceMatchAttr = "led_config"; // 驅(qū)動私有數(shù)據(jù)匹配的關(guān)鍵字,必須和驅(qū)動私有數(shù)據(jù)配置表中的match_attr值相等
- }
- }
其中,moduleName、serviceName和deviceMatchAttr 都比較重要,分布鏈接到源碼的不同位置,我這里都分開命名,便于理解。
5.2 驅(qū)動私有配置信息(可選)
如果驅(qū)動有私有配置,則可以添加一個驅(qū)動的配置文件,用來填寫一些驅(qū)動的默認配置信息,HDF框架在加載驅(qū)動的時候,會將對應(yīng)的配置信息獲取并保存在HdfDeviceObject 中的property里面,通過Bind和Init(參考驅(qū)動開發(fā))傳遞給驅(qū)動。
在 vendor/hisi/hi35xx/hi3516dv300/config/ 目錄下新建一個文件夾 led, 然后在其中新建一個源文件 led_config.hcs, 填入以下代碼。
- root {
- LedDriverConfig {
- led_version = 1;
- match_attr = "led_config"; //該字段的值必須和device_info.hcs中的deviceMatchAttr值一致
- }
- }
配置信息定義之后,需要將該配置文件添加到板級配置入口文件hdf.hcs。
5.3 板級配置(可選)
修改 vendor/hisi/hi35xx/hi3516dv300/config/hdf.hcs文件,添加代碼
- #include "device_info/device_info.hcs"
- #include "led/led_config.hcs"
6. 驅(qū)動消息機制管理
當(dāng)用戶態(tài)應(yīng)用和內(nèi)核態(tài)驅(qū)動需要交互時,可以使用HDF框架的消息機制來實現(xiàn)。用消息管理可以在用戶態(tài)和內(nèi)核態(tài)之間架起橋梁,這為我們之后的APP提供了操控底層設(shè)備功能的能力。
這里我們在用戶態(tài)實現(xiàn)一個簡單的消息機制,內(nèi)核態(tài)接受到消息后,翻轉(zhuǎn)攝像頭兩側(cè)的紅外補光燈。
6.1 配置服務(wù)策略
HDF框架定了驅(qū)動對外發(fā)布服務(wù)的策略,是由配置文件中的policy字段來控制。
- typedef enum {
- /* 驅(qū)動不提供服務(wù) */
- SERVICE_POLICY_NONE = 0,
- /* 驅(qū)動對內(nèi)核態(tài)發(fā)布服務(wù) */
- SERVICE_POLICY_PUBLIC = 1,
- /* 驅(qū)動對內(nèi)核態(tài)和用戶態(tài)都發(fā)布服務(wù) */
- SERVICE_POLICY_CAPACITY = 2,
- /* 驅(qū)動服務(wù)不對外發(fā)布服務(wù),但可以被訂閱 */
- SERVICE_POLICY_FRIENDLY = 3,
- /* 驅(qū)動私有服務(wù)不對外發(fā)布服務(wù),也不能被訂閱 */
- SERVICE_POLICY_PRIVATE = 4,
- /* 錯誤的服務(wù)策略 */
- SERVICE_POLICY_INVALID
- } ServicePolicy;
我們將驅(qū)動配置信息中服務(wù)策略policy字段設(shè)置為2,在之前的設(shè)備描述文件device_info.hcs里已經(jīng)配置好了。
6.2 實現(xiàn)服務(wù)
在第2章,我們實現(xiàn)了一個空的驅(qū)動框架,現(xiàn)在繼續(xù)實現(xiàn)內(nèi)核態(tài)的消息服務(wù)接口。
編輯 huawei/hdf/led/led.c, 實現(xiàn)服務(wù)基類成員IDeviceIoService中的Dispatch方法。收到用戶態(tài)發(fā)來的命令后,操作LED設(shè)備,然后將返回值通過reply傳回,最后再將收到的命令回傳給用戶態(tài)程序。
- // Dispatch是用來處理用戶態(tài)發(fā)下來的消息
- int32_t LedDriverDispatch(struct HdfDeviceIoClient *client, int cmdCode, struct HdfSBuf *data, struct HdfSBuf *reply)
- {
- int32_t result = HDF_FAILURE;
- 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:
- const char *recv = HdfSbufReadString(data);
- if (recv != NULL)
- {
- HDF_LOGI("recv: %s", recv);
- result = CtlLED(-1); # 操作設(shè)備
- // CtlLED(GPIO_VAL_HIGH);
- if (!HdfSbufWriteInt32(reply, result))
- {
- HDF_LOGE("replay is fail");
- }
- return HdfDeviceSendEvent(client->device, cmdCode, data);
- }
- break;
- default:
- break;
- }
- return result;
- }
修改 HdfLedDriverBind函數(shù),將服務(wù)綁定到框架。
- //驅(qū)動對外提供的服務(wù)能力,將相關(guān)的服務(wù)接口綁定到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;
- }
7. 業(yè)務(wù)代碼
內(nèi)核態(tài)核心功能,就簡單實現(xiàn)一個每調(diào)用一次,就翻轉(zhuǎn)一下LED狀態(tài)的CtrlLED函數(shù)。這里mode為 -1 時為翻轉(zhuǎn),也可以直接指定高電平或低電平來開關(guān),方便后續(xù)擴展。
其中Hi3516DV300的控制器管理12組GPIO管腳,每組8個。
GPIO號 = GPIO組索引(0~11)* 每組GPIO管腳數(shù)(8) + 組內(nèi)偏移。
那么GPIO5_1的GPIO號 = 5 * 8 +1 = 41。
- static int32_t CtlLED(int mode)
- {
- int32_t ret;
- uint16_t valRead;
- /* LED的GPIO管腳號 */
- uint16_t gpio = 5 * 8 + 1; // 紅外補光燈
- // uint16_t gpio = 2 * 8 + 3; // 綠色指示燈
- // uint16_t gpio = 3 * 8 + 4; // 紅色指示燈
- /* 將GPIO管腳配置為輸出 */
- ret = GpioSetDir(gpio, GPIO_DIR_OUT);
- if (ret != 0)
- {
- HDF_LOGE("GpioSerDir: failed, ret %d\n", ret);
- return ret;
- }
- if (mode == -1)
- {
- // 翻轉(zhuǎn)輸出口
- (void)GpioRead(gpio, &valRead);
- ret = GpioWrite(gpio, (valRead == GPIO_VAL_LOW) ? GPIO_VAL_HIGH : GPIO_VAL_LOW);
- }
- else
- {
- ret = GpioWrite(gpio, mode);
- }
- if (ret != 0)
- {
- HDF_LOGE("GpioWrite: failed, ret %d\n", ret);
- return ret;
- }
- return ret;
- }
同理,GPIO2_3、GPIO3_4和蜂鳴器組件等等通用IO設(shè)備也能相應(yīng)控制,可以盡情發(fā)揮想象力了。
8. 配置Kconfig
在`vendor/huawei/hdf/led/`下,新建一個目錄`driver`,再在其下新建`Kconfig`文件。
- config LOSCFG_DRIVERS_HDF_PLATFORM_LED
- bool "Enable HDF LED driver"
- default n
- depends on LOSCFG_DRIVERS_HDF_PLATFORM
- help
- Answer Y to enable HDF LED driver.
將其鏈接到板級Kconfig中,在vendor/huawei/hdf/Kconfig增加。
- source "../../vendor/huawei/hdf/led/driver/Kconfig"
好了,內(nèi)核態(tài)的程序基本都搞定了。
9. 用戶態(tài)程序
我們開始寫個主程序通過消息機制來與內(nèi)核態(tài)交互。
新建applications/sample/camera/myApp/my_led_app.c源文件:
9.1 定義參數(shù)
led_service為服務(wù)名稱,需要與之前定義的匹配;LED_WRITE_READ為命令標(biāo)識,用戶態(tài)和內(nèi)核態(tài)通過這個來標(biāo)識消息類型。
- #define LED_WRITE_READ 1
- #define HDF_LOG_TAG LED_APP
- #define LED_SERVICE "led_service"
9.2 發(fā)送消息
先實現(xiàn)一個發(fā)送消息的函數(shù)SendEvent,發(fā)送字符串命令后,收回內(nèi)核態(tài)reply中操作設(shè)備后的返回值,放入replyData中打印出來。這里操作成功返回0。
- static int SendEvent(struct HdfIoService *serv, char *eventData)
- {
- int ret = 0;
- struct HdfSBuf *data = HdfSBufObtainDefaultSize();
- if (data == NULL)
- {
- HDF_LOGE("fail to obtain sbuf data");
- return 1;
- }
- struct HdfSBuf *reply = HdfSBufObtainDefaultSize();
- if (reply == NULL)
- {
- HDF_LOGE("fail to obtain sbuf reply");
- ret = HDF_DEV_ERR_NO_MEMORY;
- goto out;
- }
- if (!HdfSbufWriteString(data, eventData))
- {
- HDF_LOGE("fail to write sbuf");
- ret = HDF_FAILURE;
- goto out;
- }
- ret = serv->dispatcher->Dispatch(&serv->object, LED_WRITE_READ, data, reply);
- if (ret != HDF_SUCCESS)
- {
- HDF_LOGE("fail to send service call");
- goto out;
- }
- int replyData = 0;
- if (!HdfSbufReadInt32(reply, &replyData))
- {
- HDF_LOGE("fail to get service call reply");
- ret = HDF_ERR_INVALID_OBJECT;
- goto out;
- }
- HDF_LOGE("Get reply is: %d", replyData);
- out:
- HdfSBufRecycle(data);
- HdfSBufRecycle(reply);
- return ret;
- }
9.3 設(shè)置回調(diào)
收到內(nèi)核態(tài)發(fā)來的字符串,簡單打印一下。
- static int OnDevEventReceived(void *priv, uint32_t id, struct HdfSBuf *data)
- {
- const char *string = HdfSbufReadString(data);
- if (string == NULL)
- {
- HDF_LOGE("fail to read string in event data");
- return HDF_FAILURE;
- }
- HDF_LOGE("%s: dev event received: %u %s", (char *)priv, id, string);
- return HDF_SUCCESS;
- }
9.4 主程序
先構(gòu)造一個服務(wù),通過服務(wù)名稱,綁定到對應(yīng)的驅(qū)動。然后設(shè)置監(jiān)聽,等待來自內(nèi)核的消息。最后每隔1秒發(fā)出一條翻轉(zhuǎn)LED的指令。
- int main(void)
- {
- struct HdfIoService *serv = HdfIoServiceBind(LED_SERVICE, 0);
- if (serv == NULL)
- {
- HDF_LOGE("fail to get service %s", LED_SERVICE);
- return HDF_FAILURE;
- }
- static struct HdfDevEventlistener listener = {
- .callBack = OnDevEventReceived,
- .priv = "Service0"};
- if (HdfDeviceRegisterEventListener(serv, &listener) != HDF_SUCCESS)
- {
- HDF_LOGE("fail to register event listener");
- return HDF_FAILURE;
- }
- char *send_cmd = "toggle LED";
- while (1)
- {
- if (SendEvent(serv, send_cmd))
- {
- HDF_LOGE("fail to send event");
- return HDF_FAILURE;
- }
- sleep(1);
- }
- if (HdfDeviceUnregisterEventListener(serv, &listener))
- {
- HDF_LOGE("fail to unregister listener");
- return HDF_FAILURE;
- }
- HdfIoServiceRecycle(serv);
- HDF_LOGI("exit");
- return HDF_SUCCESS;
- }
9.5 配置BUILD.gn
在 drivers/hdf/lite/manager/BUILD.gn里增加以下代碼,生成led_app應(yīng)用。
- lite_component("hdf_manager") {
- features = [
- ":hdf_core",
- ]
- }
- executable("led_app") {
- sources = [
- "//applications/sample/camera/myApp/my_led_app.c"
- ]
- include_dirs = [
- "../adapter/syscall/include",
- "../adapter/vnode/include",
- "$HDF_FRAMEWORKS/ability/sbuf/include",
- "$HDF_FRAMEWORKS/core/shared/include",
- "$HDF_FRAMEWORKS/core/host/include",
- "$HDF_FRAMEWORKS/core/master/include",
- "$HDF_FRAMEWORKS/include/core",
- "$HDF_FRAMEWORKS/include/utils",
- "$HDF_FRAMEWORKS/utils/include",
- "$HDF_FRAMEWORKS/include/osal",
- "//third_party/bounds_checking_function/include",
- ]
- deps = [
- "//drivers/hdf/lite/manager:hdf_core",
- "//drivers/hdf/lite/adapter/osal/posix:hdf_posix_osal",
- ]
- public_deps = [
- "//third_party/bounds_checking_function:libsec_shared",
- ]
- defines = [
- "__USER__",
- ]
- cflags = [
- "-Wall",
- "-Wextra",
- "-Werror",
- ]
- }
10. 編譯和燒錄
由于這次我們要用到`HDF`框架,需要用到的組件比較多,簡單復(fù)制一個`build\lite\product\ipcamera_hi3516dv300.json`改名為`my_hi3516dv300`即可。
- python build.py my_hi3516dv300 -b debug
編譯和燒錄的過程參考前文,這里不再贅述了。順利的話,啟動程序就能看見LED歡快的閃爍了。
- ./bin/led_app
紅外光在肉眼下不太顯眼,在鏡頭下比較亮些,照度范圍很大,后續(xù)再測一下夜視補光的效果。
總結(jié)
驅(qū)動開發(fā)涉及到文件和配置比較多,關(guān)系也比較紛繁,而且分散在各個目錄。
這里列出主要文件再梳理一下:

大致上分三個部分,內(nèi)核態(tài)、用戶態(tài)和驅(qū)動配置。
1. 內(nèi)核態(tài)
首先由led.c生成名為led_service的服務(wù),以g_ledDriverEntry結(jié)構(gòu)注冊到HCS框架。編譯成hdf_led_driver驅(qū)動,通過 huawei/hdf/hdf_vendor.mk鏈接到內(nèi)核鏡像中。
通過 HdfLedDriverBind函數(shù)將led_driver模塊綁定到框架,IDeviceIoService中的Dispatch方法來處理來自用戶態(tài)消息。
2. 用戶態(tài)
用戶態(tài)以HdfIoServiceBind通過服務(wù)名led_service來找到相應(yīng)的驅(qū)動,用HdfSbufWriteString來發(fā)送消息,serv->dispatcher->Dispatch來接收返回值,通過HdfDeviceRegisterEventListener設(shè)置監(jiān)聽,來獲取內(nèi)核態(tài)主動發(fā)送的消息。
3. 驅(qū)動配置
以模塊名led_driver找到注冊到HCS框架的驅(qū)動程序(內(nèi)核側(cè)的別名);
以服務(wù)名led_service,暴露給用戶態(tài)程序或內(nèi)核態(tài)程序調(diào)用(用戶側(cè)的別名);
以led_config鏈接驅(qū)動私有配置文件。兩個別名雙向解綁,最后通過配置文件來進行耦合,保證了靈活性。這種設(shè)計在分布式的場合中,會有比較大的便利性。
資料下載
下一篇預(yù)告
本期主要介紹了一下HDF的驅(qū)動開發(fā),
界面部分礙于篇幅留在下一篇介紹了,
敬請期待...

©著作權(quán)歸作者和HarmonyOS技術(shù)社區(qū)共同所有,如需轉(zhuǎn)載,請注明出處,否則將追究法律責(zé)任。
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
https://harmonyos.51cto.com/#zz