OpenHarmony:全流程講解如何編寫GPIO平臺驅動以及應用程序
一、案例簡介
該程序是基于OpenHarmony標準系統編寫的基礎外設類:GPIO驅動。
目前已在凌蒙派-RK3568開發板跑通。詳細資料請參考官網:https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony/tree/master/samples/b03_platform_device_gpio。
詳細資料請參考OpenHarmony官網:
- GPIO平臺驅動開發
- GPIO應用程序開發
二、基礎知識
1、GPIO簡介
GPIO(General-purpose input/output)即通用型輸入輸出。通常,GPIO控制器通過分組的方式管理所有GPIO管腳,每組GPIO有一個或多個寄存器與之關聯,通過讀寫寄存器完成對GPIO管腳的操作。
2、GPIO平臺驅動
GPIO(General-purpose input/output)即通用型輸入輸出。通常,GPIO控制器通過分組的方式管理所有GPIO管腳,每組GPIO有一個或多個寄存器與之關聯,通過讀寫寄存器完成對GPIO管腳的操作。
GPIO模塊各分層作用:
- 接口層提供操作GPIO管腳的標準方法。
- 核心層主要提供GPIO管腳資源匹配,GPIO管腳控制器的添加、移除以及管理的能力,通過鉤子函數與適配層交互,供芯片廠家快速接入HDF框架。
- 適配層主要是將鉤子函數的功能實例化,實現具體的功能。
GPIO統一服務模式結構圖:
為了保證上層在調用GPIO接口時能夠正確的操作GPIO管腳,核心層在//drivers/hdf_core/framework/support/platform/include/gpio/gpio_core.h中定義了以下鉤子函數,驅動適配者需要在適配層實現這些函數的具體功能,并與鉤子函數掛接,從而完成適配層與核心層的交互。
GpioMethod定義:
struct GpioMethod {
int32_t (*request)(struct GpioCntlr *cntlr, uint16_t local); // 【預留】
int32_t (*release)(struct GpioCntlr *cntlr, uint16_t local); // 【預留】
int32_t (*write)(struct GpioCntlr *cntlr, uint16_t local, uint16_t val);
int32_t (*read)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *val);
int32_t (*setDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t dir);
int32_t (*getDir)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *dir);
int32_t (*toIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t *irq); // 【預留】
int32_t (*setIrq)(struct GpioCntlr *cntlr, uint16_t local, uint16_t mode, GpioIrqFunc func, void *arg);
int32_t (*unsetIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*enableIrq)(struct GpioCntlr *cntlr, uint16_t local);
int32_t (*disableIrq)(struct GpioCntlr *cntlr, uint16_t local);
}
GpioMethod結構體成員的鉤子函數功能說明:
函數成員 | 入參 | 出參 | 返回值 | 功能 |
write | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 val:uint16_t類型,電平傳入值 | 無 | HDF_STATUS相關狀態 | GPIO引腳寫入電平值 |
read | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識 | val:uint16_t類型指針,用于傳出電平值。 | HDF_STATUS相關狀態 | GPIO引腳讀取電平值 |
setDir | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 dir:uint16_t類型,管腳方向傳入值 | 無 | HDF_STATUS相關狀態 | 設置GPIO引腳輸入/輸出方向 |
getDir | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 | dir:uint16_t類型指針,用于傳出管腳方向值 | HDF_STATUS相關狀態 | 讀GPIO引腳輸入/輸出方向 |
setIrq | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 mode:uint16_t類型,表示觸發模式(邊沿或電平) func:函數指針,中斷服務程序; arg:void指針,中斷服務程序入參 | 無 | HDF_STATUS相關狀態 | 將GPIO引腳設置為中斷模式 |
unsetIrq | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 | 無 | HDF_STATUS相關狀態 | 取消GPIO中斷設置 |
enableIrq | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 | 無 | HDF_STATUS相關狀態 | 使能GPIO管腳中斷 |
disableIrq | cntlr:結構體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標識號 | 無 | HDF_STATUS相關狀態 | 禁止GPIO管腳中斷 |
3、GPIO應用程序
GPIO驅動API接口功能:
接口名 | 描述 |
GpioGetByName(const char *gpioName) | 獲取GPIO管腳ID |
int32_t GpioRead(uint16_t gpio, uint16_t *val) | 讀GPIO管腳電平值 |
int32_t GpioWrite(uint16_t gpio, uint16_t val) | 寫GPIO管腳電平值 |
int32_t GpioGetDir(uint16_t gpio, uint16_t *dir) | 獲取GPIO管腳方向 |
int32_t GpioSetDir(uint16_t gpio, uint16_t dir) | 設置GPIO管腳方向 |
int32_t GpioUnsetIrq(uint16_t gpio, void *arg); | 取消GPIO管腳對應的中斷服務函數 |
int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg) | 設置GPIO管腳對應的中斷服務函數 |
int32_t GpioEnableIrq(uint16_t gpio) | 使能GPIO管腳中斷 |
int32_t GpioDisableIrq(uint16_t gpio) | 禁止GPIO管腳中斷 |
GPIO標準API通過GPIO管腳號來操作指定管腳,使用GPIO的一般流程如下圖所示:
三、代碼解析
1、準備工作
查看《凌蒙派-RK3568開發板_排針說明表_》(即Git倉庫的//docs/board/凌蒙派-RK3568開發板_排針說明表_v1.0.xlsx),選中0_B5(即GPIO0_B5)。
2、配置文件
(1)device_info.hcs
創建config/device_info.hcs,用于GPIO驅動設備描述,具體內容如下:
root {
device_info {
platform :: host {
device_gpio :: device {
device0 :: deviceNode { // GPIO控制器信息描述
policy = 2; // 對外發布服務,必須為2,用于定義GPIO管理器的服務
priority = 50;
permission = 0644;
moduleName = "HDF_PLATFORM_GPIO_MANAGER"; // 這與drivers/hdf_core/framework/support/platform/src/gpio/gpio_service.c的g_gpioServiceEntry.moduleName對應,它主要負責GPIO引腳的管理
serviceName = "HDF_PLATFORM_GPIO_MANAGER";
}
device1 :: deviceNode {
policy = 0; // 等于0,不需要發布服務
priority = 55; // 驅動驅動優先級
permission = 0644; // 驅動創建設備節點權限
moduleName = "linux_gpio_adapter"; // 用于指定驅動名稱,必須是linux_adc_adapter,與drivers/hdf_core/adapter/khdf/linux/platform/gpio/gpio_adapter.c對應
deviceMatchAttr = ""; // 用于配置控制器私有數據,不定義
}
}
}
}
}
注意:
- device_gpio:為配置樹對gpio的設備類結點。
- device0:是用于啟用HDF_PLATFORM_GPIO_MANAGER驅動的,它負責對GPIO進行對外接口管理。
- device1:是用于啟用linux_gpio_adapter驅動的,它負責對Linux GPIO的讀寫(即對Linux Gpio子系統進行操作)。
(2)參與配置樹編譯
編輯//vendor/lockzhiner/rk3568/hdf_config/khdf/hdf.hcs,將device_info.hcs添加配置樹中。具體內容如下所示:
#include "../../samples/b03_platform_device_gpio/config/device_info.hcs"
3、HDF驅動
//drivers/hdf_core/adapter/khdf/linux/platform/gpio/gpio_adapter.c已對Linux Gpio子系統進行規范化操作。因此,我們不需要額外的GPIO寄存器操作。
4、應用程序
(1)gpio_test.c
gpio_test.c主要分為兩個部分:
- 對gpio引腳進行讀操作。
- 對gpio引腳進行寫操作。
1.對gpio引腳進行讀操作。
// GPIO設置為輸出
ret = GpioSetDir(m_gpio_id, GPIO_DIR_OUT);
if (ret != 0) {
PRINT_ERROR("GpioSetDir failed and ret = %d\n", ret);
return -1;
}
// GPIO輸出電平
ret = GpioWrite(m_gpio_id, m_gpio_value);
if (ret != 0) {
PRINT_ERROR("GpioWrite failed and ret = %d\n", ret);
return -1;
}
2.對gpio引腳進行寫操作。
// GPIO設置為輸出
ret = GpioSetDir(m_gpio_id, GPIO_DIR_IN);
if (ret != 0) {
PRINT_ERROR("GpioSetDir failed and ret = %d\n", ret);
return -1;
}
// 讀取GPIO引腳的電平
ret = GpioRead(m_gpio_id, &m_gpio_value);
if (ret != 0) {
PRINT_ERROR("GpioRead failed and ret = %d\n", ret);
return -1;
}
printf("GPIO Read Successful and GPIO = %d, value = %d\n", m_gpio_id, m_gpio_value);
(2)BUILD.gn
import("http://build/ohos.gni")
import("http://drivers/hdf_core/adapter/uhdf2/uhdf.gni")
ohos_executable("rk3568_gpio_test") {
sources = [ "gpio_test.c" ]
include_dirs = [
"$hdf_framework_path/include",
"$hdf_framework_path/include/core",
"$hdf_framework_path/include/osal",
"$hdf_framework_path/include/platform",
"$hdf_framework_path/include/utils",
"$hdf_uhdf_path/osal/include",
"$hdf_uhdf_path/ipc/include",
"http://base/hiviewdfx/hilog/interfaces/native/kits/include",
"http://third_party/bounds_checking_function/include",
]
deps = [
"$hdf_uhdf_path/platform:libhdf_platform",
"$hdf_uhdf_path/utils:libhdf_utils",
"http://base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
]
cflags = [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-format",
"-Wno-format-extra-args",
]
part_name = "product_rk3568"
install_enable = true
}
(3)參與應用程序編譯
編輯//vendor/lockzhiner/rk3568/samples/BUILD.gn,開啟sample編譯。具體如下:
"b03_platform_device_gpio/app:rk3568_gpio_test",
四、編譯說明
建議使用docker編譯方法,運行如下:
hb set -root .
hb set
#選擇lockzhiner下的rk3568編譯分支。
hb build -f
五、運行結果
該程序運行結果如下所示:
# rk3568_gpio_test -g 13 -i
gpio id: 13
gpio dir: in
gpio value: 0
GPIO Read Successful and GPIO = 13, value = 1
#
#
# rk3568_gpio_test -g 13 -o
gpio id: 13
gpio dir: out
gpio value: 0
#
可將GPIO引腳接入排針中的GND或3V3引腳,查看GPIO輸出結果。