OpenHarmony:全流程講解如何編寫GPIO平臺(tái)驅(qū)動(dòng)以及應(yīng)用程序
想了解更多關(guān)于開源的內(nèi)容,請(qǐng)?jiān)L問:
一、案例簡(jiǎn)介
該程序是基于OpenHarmony標(biāo)準(zhǔn)系統(tǒng)編寫的基礎(chǔ)外設(shè)類:GPIO驅(qū)動(dòng)。
目前已在凌蒙派-RK3568開發(fā)板跑通。詳細(xì)資料請(qǐng)參考官網(wǎng):https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony/tree/master/samples/b03_platform_device_gpio。
詳細(xì)資料請(qǐng)參考OpenHarmony官網(wǎng):
- GPIO平臺(tái)驅(qū)動(dòng)開發(fā)
- GPIO應(yīng)用程序開發(fā)
二、基礎(chǔ)知識(shí)
1、GPIO簡(jiǎn)介
GPIO(General-purpose input/output)即通用型輸入輸出。通常,GPIO控制器通過分組的方式管理所有GPIO管腳,每組GPIO有一個(gè)或多個(gè)寄存器與之關(guān)聯(lián),通過讀寫寄存器完成對(duì)GPIO管腳的操作。
2、GPIO平臺(tái)驅(qū)動(dòng)
GPIO(General-purpose input/output)即通用型輸入輸出。通常,GPIO控制器通過分組的方式管理所有GPIO管腳,每組GPIO有一個(gè)或多個(gè)寄存器與之關(guān)聯(lián),通過讀寫寄存器完成對(duì)GPIO管腳的操作。
GPIO模塊各分層作用:
- 接口層提供操作GPIO管腳的標(biāo)準(zhǔn)方法。
- 核心層主要提供GPIO管腳資源匹配,GPIO管腳控制器的添加、移除以及管理的能力,通過鉤子函數(shù)與適配層交互,供芯片廠家快速接入HDF框架。
- 適配層主要是將鉤子函數(shù)的功能實(shí)例化,實(shí)現(xiàn)具體的功能。
GPIO統(tǒng)一服務(wù)模式結(jié)構(gòu)圖:
為了保證上層在調(diào)用GPIO接口時(shí)能夠正確的操作GPIO管腳,核心層在//drivers/hdf_core/framework/support/platform/include/gpio/gpio_core.h中定義了以下鉤子函數(shù),驅(qū)動(dòng)適配者需要在適配層實(shí)現(xiàn)這些函數(shù)的具體功能,并與鉤子函數(shù)掛接,從而完成適配層與核心層的交互。
GpioMethod定義:
struct GpioMethod {
int32_t (*request)(struct GpioCntlr *cntlr, uint16_t local); // 【預(yù)留】
int32_t (*release)(struct GpioCntlr *cntlr, uint16_t local); // 【預(yù)留】
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); // 【預(yù)留】
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結(jié)構(gòu)體成員的鉤子函數(shù)功能說明:
函數(shù)成員 | 入?yún)?/p> | 出參 | 返回值 | 功能 |
write | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) val:uint16_t類型,電平傳入值 | 無 | HDF_STATUS相關(guān)狀態(tài) | GPIO引腳寫入電平值 |
read | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí) | val:uint16_t類型指針,用于傳出電平值。 | HDF_STATUS相關(guān)狀態(tài) | GPIO引腳讀取電平值 |
setDir | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) dir:uint16_t類型,管腳方向傳入值 | 無 | HDF_STATUS相關(guān)狀態(tài) | 設(shè)置GPIO引腳輸入/輸出方向 |
getDir | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) | dir:uint16_t類型指針,用于傳出管腳方向值 | HDF_STATUS相關(guān)狀態(tài) | 讀GPIO引腳輸入/輸出方向 |
setIrq | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) mode:uint16_t類型,表示觸發(fā)模式(邊沿或電平) func:函數(shù)指針,中斷服務(wù)程序; arg:void指針,中斷服務(wù)程序入?yún)?/p> | 無 | HDF_STATUS相關(guān)狀態(tài) | 將GPIO引腳設(shè)置為中斷模式 |
unsetIrq | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) | 無 | HDF_STATUS相關(guān)狀態(tài) | 取消GPIO中斷設(shè)置 |
enableIrq | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) | 無 | HDF_STATUS相關(guān)狀態(tài) | 使能GPIO管腳中斷 |
disableIrq | cntlr:結(jié)構(gòu)體指針,核心層GPIO控制器 local:uint16_t類型,GPIO端口標(biāo)識(shí)號(hào) | 無 | HDF_STATUS相關(guān)狀態(tài) | 禁止GPIO管腳中斷 |
3、GPIO應(yīng)用程序
GPIO驅(qū)動(dòng)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) | 設(shè)置GPIO管腳方向 |
int32_t GpioUnsetIrq(uint16_t gpio, void *arg); | 取消GPIO管腳對(duì)應(yīng)的中斷服務(wù)函數(shù) |
int32_t GpioSetIrq(uint16_t gpio, uint16_t mode, GpioIrqFunc func, void *arg) | 設(shè)置GPIO管腳對(duì)應(yīng)的中斷服務(wù)函數(shù) |
int32_t GpioEnableIrq(uint16_t gpio) | 使能GPIO管腳中斷 |
int32_t GpioDisableIrq(uint16_t gpio) | 禁止GPIO管腳中斷 |
GPIO標(biāo)準(zhǔn)API通過GPIO管腳號(hào)來操作指定管腳,使用GPIO的一般流程如下圖所示:
三、代碼解析
1、準(zhǔn)備工作
查看《凌蒙派-RK3568開發(fā)板_排針說明表_》(即Git倉庫的//docs/board/凌蒙派-RK3568開發(fā)板_排針說明表_v1.0.xlsx),選中0_B5(即GPIO0_B5)。
2、配置文件
(1)device_info.hcs
創(chuàng)建config/device_info.hcs,用于GPIO驅(qū)動(dòng)設(shè)備描述,具體內(nèi)容如下:
root {
device_info {
platform :: host {
device_gpio :: device {
device0 :: deviceNode { // GPIO控制器信息描述
policy = 2; // 對(duì)外發(fā)布服務(wù),必須為2,用于定義GPIO管理器的服務(wù)
priority = 50;
permission = 0644;
moduleName = "HDF_PLATFORM_GPIO_MANAGER"; // 這與drivers/hdf_core/framework/support/platform/src/gpio/gpio_service.c的g_gpioServiceEntry.moduleName對(duì)應(yīng),它主要負(fù)責(zé)GPIO引腳的管理
serviceName = "HDF_PLATFORM_GPIO_MANAGER";
}
device1 :: deviceNode {
policy = 0; // 等于0,不需要發(fā)布服務(wù)
priority = 55; // 驅(qū)動(dòng)驅(qū)動(dòng)優(yōu)先級(jí)
permission = 0644; // 驅(qū)動(dòng)創(chuàng)建設(shè)備節(jié)點(diǎn)權(quán)限
moduleName = "linux_gpio_adapter"; // 用于指定驅(qū)動(dòng)名稱,必須是linux_adc_adapter,與drivers/hdf_core/adapter/khdf/linux/platform/gpio/gpio_adapter.c對(duì)應(yīng)
deviceMatchAttr = ""; // 用于配置控制器私有數(shù)據(jù),不定義
}
}
}
}
}
注意:
- device_gpio:為配置樹對(duì)gpio的設(shè)備類結(jié)點(diǎn)。
- device0:是用于啟用HDF_PLATFORM_GPIO_MANAGER驅(qū)動(dòng)的,它負(fù)責(zé)對(duì)GPIO進(jìn)行對(duì)外接口管理。
- device1:是用于啟用linux_gpio_adapter驅(qū)動(dòng)的,它負(fù)責(zé)對(duì)Linux GPIO的讀寫(即對(duì)Linux Gpio子系統(tǒng)進(jìn)行操作)。
(2)參與配置樹編譯
編輯//vendor/lockzhiner/rk3568/hdf_config/khdf/hdf.hcs,將device_info.hcs添加配置樹中。具體內(nèi)容如下所示:
#include "../../samples/b03_platform_device_gpio/config/device_info.hcs"
3、HDF驅(qū)動(dòng)
//drivers/hdf_core/adapter/khdf/linux/platform/gpio/gpio_adapter.c已對(duì)Linux Gpio子系統(tǒng)進(jìn)行規(guī)范化操作。因此,我們不需要額外的GPIO寄存器操作。
4、應(yīng)用程序
(1)gpio_test.c
gpio_test.c主要分為兩個(gè)部分:
- 對(duì)gpio引腳進(jìn)行讀操作。
- 對(duì)gpio引腳進(jìn)行寫操作。
1.對(duì)gpio引腳進(jìn)行讀操作。
// GPIO設(shè)置為輸出
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.對(duì)gpio引腳進(jìn)行寫操作。
// GPIO設(shè)置為輸出
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)參與應(yīng)用程序編譯
編輯//vendor/lockzhiner/rk3568/samples/BUILD.gn,開啟sample編譯。具體如下:
"b03_platform_device_gpio/app:rk3568_gpio_test",
四、編譯說明
建議使用docker編譯方法,運(yùn)行如下:
hb set -root .
hb set
#選擇lockzhiner下的rk3568編譯分支。
hb build -f
五、運(yùn)行結(jié)果
該程序運(yùn)行結(jié)果如下所示:
# 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輸出結(jié)果。