用鴻蒙開發AI應用(五)HDF 驅動補光燈

前言

上一篇,我們在鴻蒙上運行了第一個程序,這一篇我們來編寫一個驅動開啓攝像頭的紅外補光燈,順便熟悉一下鴻蒙上的 HDF 驅動開發。

 

#2020徵文-開發板# 用鴻蒙開發AI應用(五)HDF 驅動補光燈

 

硬件準備
先查一下原理圖(具體可參考第一篇的硬件資料),找到紅外燈的 IO 口編號,GPIO5_1。

#2020徵文-開發板# 用鴻蒙開發AI應用(五)HDF 驅動補光燈

HDF 驅動開發
1. 簡介
HDF(OpenHarmony Driver Foundation)驅動框架,爲驅動開發者提供驅動框架能力,包括驅動加載、驅動服務管理和驅動消息機制。旨在構建統一的驅動架構平臺,爲驅動開發者提供更精準、更高效的開發環境,力求做到一次開發,多系統部署。

HDF框架以組件化的驅動模型作爲核心設計思路,爲開發者提供更精細化的驅動管理,讓驅動開發和部署更加規範。HDF框架將一類設備驅動放在同一個host裏面,驅動內部實現開發者也可以將驅動功能分層獨立開發和部署,支持一個驅動多個node,HDF框架管理驅動模型如下圖所示:

#2020徵文-開發板# 用鴻蒙開發AI應用(五)HDF 驅動補光燈

 

2. 驅動框架
2.1 驅動框架實現
在 huawei/hdf 目錄下新建一個文件夾 led, 然後在其中新建一個源文件 led.c。

#include "hdf_device_desc.h"  // HDF框架對驅動開放相關能力接口的頭文件
#include "hdf_log.h"          // HDF 框架提供的日誌接口頭文件

#define HDF_LOG_TAG led_driver   // 打印日誌所包含的標籤,如果不定義則用默認定義的HDF_TAG標籤

//驅動對外提供的服務能力,將相關的服務接口綁定到HDF框架
int32_t HdfLedDriverBind(struct HdfDeviceObject *deviceObject)
{
    HDF_LOGD("Led driver bind success");
    return 0;
}

// 驅動自身業務初始的接口
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;
}

// 驅動資源釋放的接口
void HdfLedDriverRelease(struct HdfDeviceObject *deviceObject)
{
    if (deviceObject == NULL) {
        HDF_LOGE("Led driver release failed!");
        return;
    }

    HDF_LOGD("Led driver release success");
    return;
}

2.2 驅動入口註冊到HDF框架

// 定義驅動入口的對象,必須爲HdfDriverEntry(在hdf_device_desc.h中定義)類型的全局變量
struct HdfDriverEntry g_ledDriverEntry = {
    .moduleVersion = 1,
    .moduleName = "led_driver",
    .Bind = HdfLedDriverBind,
    .Init = HdfLedDriverInit,
    .Release = HdfLedDriverRelease,
};

// 調用HDF_INIT將驅動入口註冊到HDF框架中,在加載驅動時HDF框架會先調用Bind函數,再調用Init函數加載該驅動,當Init調用異常時,HDF框架會調用Release釋放驅動資源並退出。
HDF_INIT(g_ledDriverEntry);

3. 驅動編譯

在 huawei/hdf/led 目錄下新建編譯文件 Makefile

include $(LITEOSTOPDIR)/../../drivers/hdf/lite/lite.mk  #導入hdf預定義內容,必需

MODULE_NAME := hdf_led_driver  #生成的結果文件
LOCAL_SRCS += led.c  #本驅動的源代碼文件
LOCAL_INCLUDE := ./include  #本驅動的頭文件目錄
LOCAL_CFLAGS += -fstack-protector-strong -Wextra -Wall -Werror  #自定義的編譯選項
include $(HDF_DRIVER)  #導入模板makefile完成編譯

這裏的hdf_led_driver爲驅動文件名,注意對應關係。

 

4. 編譯結果鏈接到內核鏡像

修改 huawei/hdf/hdf_vendor.mk 文件,添加以下代碼

LITEOS_BASELIB += -lhdf_led_driver  #鏈接生成的靜態庫
LIB_SUBDIRS    += $(VENDOR_HDF_DRIVERS_ROOT)/led  #驅動代碼Makefile的目錄

填入驅動文件名和源碼路徑。

 

5. 驅動配置

驅動配置包含兩部分,HDF框架定義的驅動設備描述和驅動的私有配置信息。

5.1 驅動設備描述(必選)
HDF框架加載驅動所需要的信息來源於HDF框架定義的驅動設備描述。

修改 vendor/hisi/hi35xx/hi3516dv300/config/device_info/device_info.hcs配置文件,添加驅動的設備描述。

platform :: host {
    hostName = "platform_host";  // host名稱,host節點是用來存放某一類驅動的容器
    priority = 50;  // host啓動優先級(0-200),值越大優先級越低,建議默認配100,優先級相同則不保證host的加載順序

    device_led :: device {                  // led設備節點
        device0 :: deviceNode {             // led驅動的DeviceNode節點
            policy = 2;                     // policy字段是驅動服務發佈的策略,在驅動服務管理章節有詳細介紹
            priority = 100;                 // 驅動啓動優先級(0-200),值越大優先級越低,建議默認配100,優先級相同則不保證device的加載順序
            preload = 0;                    // 驅動按需加載字段
            permission = 0666;              // 驅動創建設備節點權限
            moduleName = "led_driver";      // 驅動名稱,該字段的值必須和驅動入口結構的moduleName值一致
            serviceName = "led_service";    // 驅動對外發布服務的名稱,必須唯一
            deviceMatchAttr = "led_config"; // 驅動私有數據匹配的關鍵字,必須和驅動私有數據配置表中的match_attr值相等
        }
    }

其中,moduleName、serviceName和deviceMatchAttr 都比較重要,分佈鏈接到源碼的不同位置,我這裏都分開命名,便於理解。

 

5.2 驅動私有配置信息(可選)
如果驅動有私有配置,則可以添加一個驅動的配置文件,用來填寫一些驅動的默認配置信息,HDF框架在加載驅動的時候,會將對應的配置信息獲取並保存在HdfDeviceObject 中的property裏面,通過Bind和Init(參考驅動開發)傳遞給驅動。

在 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. 驅動消息機制管理

當用戶態應用和內核態驅動需要交互時,可以使用HDF框架的消息機制來實現。用消息管理可以在用戶態和內核態之間架起橋樑,這爲我們之後的APP提供了操控底層設備功能的能力。

這裏我們在用戶態實現一個簡單的消息機制,內核態接受到消息後,翻轉攝像頭兩側的紅外補光燈。

 

6.1 配置服務策略
HDF框架定了驅動對外發布服務的策略,是由配置文件中的policy字段來控制。

typedef enum {
    /* 驅動不提供服務 */
    SERVICE_POLICY_NONE = 0,
    /* 驅動對內核態發佈服務 */
    SERVICE_POLICY_PUBLIC = 1,
    /* 驅動對內核態和用戶態都發布服務 */
    SERVICE_POLICY_CAPACITY = 2,
    /* 驅動服務不對外發布服務,但可以被訂閱 */
    SERVICE_POLICY_FRIENDLY = 3,
    /* 驅動私有服務不對外發布服務,也不能被訂閱 */
    SERVICE_POLICY_PRIVATE = 4,
    /* 錯誤的服務策略 */
    SERVICE_POLICY_INVALID
} ServicePolicy;

我們將驅動配置信息中服務策略policy字段設置爲2,在之前的設備描述文件device_info.hcs裏已經配置好了。

 

6.2 實現服務
在第2章,我們實現了一個空的驅動框架,現在繼續實現內核態的消息服務接口。

編輯 huawei/hdf/led/led.c, 實現服務基類成員IDeviceIoService中的Dispatch方法。收到用戶態發來的命令後,操作LED設備,然後將返回值通過reply傳回,最後再將收到的命令回傳給用戶態程序。

// Dispatch是用來處理用戶態發下來的消息
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);  # 操作設備
            // 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函數,將服務綁定到框架。

//驅動對外提供的服務能力,將相關的服務接口綁定到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. 業務代碼
內核態核心功能,就簡單實現一個每調用一次,就翻轉一下LED狀態的CtrlLED函數。這裏mode爲 -1 時爲翻轉,也可以直接指定高電平或低電平來開關,方便後續擴展。

其中Hi3516DV300的控制器管理12組GPIO管腳,每組8個。

GPIO號 = GPIO組索引(0~11)* 每組GPIO管腳數(8) + 組內偏移。

那麼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)
    {
        // 翻轉輸出口
        (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設備也能相應控制,可以盡情發揮想象力了。

 

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"

好了,內核態的程序基本都搞定了。

 

9. 用戶態程序
我們開始寫個主程序通過消息機制來與內核態交互。

新建applications/sample/camera/myApp/my_led_app.c源文件:

9.1 定義參數
led_service爲服務名稱,需要與之前定義的匹配;LED_WRITE_READ爲命令標識,用戶態和內核態通過這個來標識消息類型。

#define LED_WRITE_READ 1
#define HDF_LOG_TAG LED_APP
#define LED_SERVICE "led_service"

 

9.2 發送消息
先實現一個發送消息的函數SendEvent,發送字符串命令後,收回內核態reply中操作設備後的返回值,放入replyData中打印出來。這裏操作成功返回0。

文章後續內容和相關附件可以點擊下面的原文鏈接前往學習
原文鏈接:https://harmonyos.51cto.com/posts/2820#bkwz


想了解更多關於鴻蒙的內容,請訪問:

51CTO和華爲官方戰略合作共建的鴻蒙技術社區

https://harmonyos.51cto.com/#bkwz


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章