目录
前言一、开发工具及环境搭建二、知识了解三、案例编写3.1 代码结构3.2 led_gpio.c 代码编写3.3 添加模块配置规则3.4 添加部件配置规则3.5 修改子系统配置文件3.6 修改产品配置文件3.7 配置HCS
四、代码编译、烧录五、测试运行六、结果
前言
笔者作为一名Web开发者,最近接手了一个新项目,其中涉及到了硬件开发方面的需求。考虑到我对硬件方面领域一窍不通,这对我来说无疑是一个巨大的挑战。尽管时间紧迫,任务繁重,但我抱着学习的态度,经历了两周的不懈努力,终于实现了一个LED的案例。在这个过程中我踩坑无数遇到了无数次的困难 ,但也因此获得了宝贵的经验和教训。希望通过这篇博客能分享我的学习经历和成果,帮助更多与我有类似经历的开发者,互相勉励。
笔者开发环境情况如下:
DevEco Device Tool 4.0 Release VMware Workstation Pro 17 OpenHarmony-v3.2-Release 润和 瑞芯微(非标准) RK3568 标准系统 HDC 1.2.0a
目标:通过HDF API调用实现搭载OpenHarmony-v3.2-Release的瑞芯微 RK3568 的LED点亮和熄灭。
一、开发工具及环境搭建
官方文档已经给出详细搭建教程,将不再过多赘述。HUAWEI DevEco DeviceTool使用指南如果过程中遇到什么问题,建议参考此课程辅助能解决很多问题。HarmonyOS Connect开发工具系列课
二、知识了解
开发前的基础知识,详情请移步官网
GPIOHDFHDC
三、案例编写
3.1 代码结构
打开项目工程,在代码根目录创建samples子系统文件夹,在子系统目录下创建led部件文件夹, led目录下创建构建文件BUILD.gn及部件配置文件bundle.json及src文件夹,src文件夹下创建led_gpio.c源文件,完整目录如下:
samples/led │── BUILD.gn │── src │ └── led_gpio.c │── bundle.json build └── subsystem_config.json vendor/hihope └── rk3568 └── config.json
3.2 led_gpio.c 代码编写
#include
#include
#include
#include
#include
#include
#include "hdf_log.h" // 标准日志接口头文件
#include "gpio_if.h" // GPIO标准接口头文件
// 定义版本号
#define SOFTWARE_VERSION "V1.0"
// 打印信息,用于打印普通信息
#define PRINT_INFO(fmt, args...) printf("%s, %s, %d, info: "fmt, __FILE__, __func__, __LINE__, ##args)
// 打印信息,用于打印错误信息
#define PRINT_ERROR(fmt, args...) printf("%s, %s, %d, error: "fmt, __FILE__, __func__, __LINE__, ##args)
// GPIO引脚序号
static uint16_t m_gpio_id = 0;
// GPIO引脚是否设置为输入,GPIO_DIR_OUT为输出,GPIO_DIR_IN为输入
static uint16_t m_gpio_dir = GPIO_DIR_IN;
// GPIO引脚的高低电平,GPIO_VAL_LOW为低电平,GPIO_VAL_HIGH为高电平
static uint16_t m_gpio_value = GPIO_VAL_LOW;
///
/***************************************************************
* 函数名称: main_help_gpio
* 说 明: 帮助文档
* 参 数: 无
* 返 回 值: 无
***************************************************************/
void main_help_gpio(char *cmd)
{
printf("%s: platform device gpio\n", cmd);
printf("Version: %s\n", SOFTWARE_VERSION);
printf("%s [options]...\n", cmd);
printf(" -g, --gpio gpio id\n");
printf(" -v, --value the value of gpio, 0 is low, 1 is high\n");
printf(" -o, --out gpio dir set to out\n");
printf(" -i, --in gpio dir set to in\n");
printf(" -h, --help help info\n");
printf("\n");
}
/***************************************************************
* 函数名称: parse_opt_gpio
* 说 明: 解析参数
* 参 数:
* @argc: 参数数量
* @argv: 参数变量数组
* 返 回 值: 无
***************************************************************/
void parse_opt_gpio(int argc, char *argv[])
{
while (1) {
struct option long_opts[] = {
{ "gpio", required_argument, NULL, 'g' },
{ "value", required_argument, NULL, 'v' },
{ "out", no_argument, NULL, 'o' },
{ "in", no_argument, NULL, 'i' },
{ "help", no_argument, NULL, 'h' },
};
int option_index = 0;
int c;
c = getopt_long(argc, argv, "g:v:oih", long_opts, &option_index);
if (c == -1) break;
switch (c) {
case 'g':
m_gpio_id = (uint16_t)atoi(optarg);
break;
case 'v':
m_gpio_value = (uint16_t)atoi(optarg);
break;
case 'o':
m_gpio_dir = GPIO_DIR_OUT;
break;
case 'i':
m_gpio_dir = GPIO_DIR_IN;
break;
case 'h':
default:
main_help_gpio(argv[0]);
exit(0);
break;
}
}
}
/***************************************************************
* 函数名称: main
* 说 明: 主函数,用于GPIO控制
* 参 数:
* @argc: 参数数量
* @argv: 参数变量数组
* 返 回 值: 0为成功,反之为错误
***************************************************************/
int main(int argc, char* argv[])
{
int32_t ret;
// 解析参数
parse_opt_gpio(argc, argv);
printf("gpio id: %d\n", m_gpio_id);
printf("gpio dir: %s\n", ((m_gpio_dir == GPIO_DIR_OUT) ? ("out") : ("in")));
printf("gpio value: %d\n", m_gpio_value);
if (m_gpio_dir == GPIO_DIR_OUT) {
// 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;
}
} else {
// 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);
}
return 0;
}
本案例中的主要程序文件,通过HDF_PLATFORM_GPIO_MANAGER驱动实现对GPIO的控制。 代码借鉴于:https://gitee.com/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony#/Lockzhiner-Electronics/lockzhiner-rk3568-openharmony/blob/master/samples/b03_platform_device_gpio
3.3 添加模块配置规则
samples/led/BUILD.gn内容如下:
import("//build/ohos.gni") # 导入编译模板
import("//drivers/hdf_core/adapter/uhdf2/uhdf.gni")
ohos_executable("led_gpio") { # 可执行模块
sources = [ "src/led_gpio.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",
"//base/hiviewdfx/hilog/interfaces/native/kits/include",
"//third_party/bounds_checking_function/include",
]
deps = [ # 部件内部依赖
"$hdf_uhdf_path/platform:libhdf_platform",
"$hdf_uhdf_path/utils:libhdf_utils",
"//base/hiviewdfx/hilog/interfaces/native/innerkits:libhilog",
]
cflags = [
"-Wall",
"-Wextra",
"-Werror",
"-Wno-format",
"-Wno-format-extra-args",
]
cflags_c = []
cflags_cc = []
ldflags = []
configs = []
part_name = "led" # 所属部件名称,必选
install_enable = true # 是否默认安装
}
编译子系统通过模块、部件和产品三层配置来实现编译和打包。模块就是编译子系统的一个目标,包括(动态库、静态库、配置文件、预编译模块等)。模块要定义属于哪个部件,一个模块只能归属于一个部件。OpenHarmony使用定制化的Gn模板来配置模块规则,Gn语法相关的基础知识请参考官网手册 本案例中,项目在进行build命令编译过程中执行的是存在于根目录的build.sh文件,会遍历扫描.gn文件编译对应程序。ohos_executable为配置项目的可执行指令,sources 为led_gpio.c的所在位置。
3.4 添加部件配置规则
samples/led/bundle.json内容如下:
{
"name": "@ohos/led", # HPM部件英文名称,格式"@组织/部件名称"
"description": "leds example.", # 部件功能一句话描述
"version": "3.1", # 版本号,版本号与OpenHarmony版本号一致
"license": "Apache License 2.0", # 部件License
"publishAs": "code-segment", # HPM包的发布方式,当前默认都为code-segment
"segment": {
"destPath": "samples/led"
}, # 发布类型为code-segment时为必填项,定义发布类型code-segment的代码还原路径(源码路径)
"dirs": {}, # HPM包的目录结构,字段必填内容可以留空
"scripts": {}, # HPM包定义需要执行的脚本,字段必填,值非必填
"component": { # 部件属性
"name": "led", # 部件名称
"subsystem": "samples", # 部件所属子系统
"syscap": [], # 部件为应用提供的系统能力
"features": [], # 部件对外的可配置特性列表,一般与build中的sub_component对应,可供产品配置
"adapted_system_type": [ "mini", "small", "standard" ], # 轻量(mini)小型(small)和标准(standard),可以是多个
"rom": "10KB", # 部件ROM值
"ram": "10KB", # 部件RAM估值
"deps": {
"components": [], # 部件依赖的其他部件
"third_party": [] # 部件依赖的三方开源软件
},
"build": { # 编译相关配置
"sub_component": [
"//samples/led:led_gpio" # 部件编译入口
], # 部件编译入口,模块在此处配置
"inner_kits": [], # 部件间接口
"test": [] # 部件测试用例编译入口
}
}
}
bundle.json文件包含两个部分,第一部分描述该部件所属子系统的信息,第二部分component则定义该部件构建相关配置。添加的时候需要指明该部件包含的模块sub_component,假如有提供给其它部件的接口,需要在inner_kits中说明,假如有测试用例,需要在test中说明,inner_kits与test没有也可以不添加。 本案例中sub_component配置为//samples/led:led_gpio,//代表根目录,samples/led代表路径,led_gpio代表BUILD.gn文件里ohos_executable的名称。
3.5 修改子系统配置文件
在build/subsystem_config.json中添加新建的子系统的配置,新增子系统的配置如下所示:
"sample": {
"path": "samples", # 路径
"name": "samples" # 子系统名
}
子系统的配置规则主要是在build/subsystem_config.json中指定子系统的路径和子系统名称。 本案例path配置samples文件夹所在路径, name与bundle.json里subsystem名称匹配即可。
3.6 修改产品配置文件
在vendor/hihope/rk3568/config.json中添加对应的led部件,直接添加到原有部件后即可
{
"subsystem": "samples",
"components": [
{
"component": "led",
"features": []
}
]
}
本案例中,subsystem配置为bundle.json里subsystem名称,component配置为led部件名称。
3.7 配置HCS
打开vendor/hihope/rk3568/hdf_config/khdf/device_info/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 = ""; // 用于配置控制器私有数据,不定义
}
}
}
}
}
vendor/hihope/rk3568/hdf_config/khdf/hdf.hcs在此文件中引入device_info.hcs文件地址,默认已经引入无需修改。 本案例中,通过moduleName配置HDF_PLATFORM_GPIO_MANAGER驱动加载到HDF驱动平台从而实现对GPIO的控制。
四、代码编译、烧录
编译之前,删除以下文件:vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs.hcb、out/kernel/vendor/hihope/rk3568/hdf_config/khdf/hdf_test/hdf_hcs_hex.o、out/kernel/OBJ/linux-5.10/arch/arm64/boot/Image、out/kernel/OBJ/linux-5.10/arch/arm64/boot/Image.lz4、out/rk3568/packages/phone/images/boot_linux.img、out/kernel/src_tmp笔者在编译这步吃过很大的亏,很多时候只是因为编译后没有重新生成并覆盖旧生成的文件,导致卡了很久进度,如果发现编译后新文件未生效还是旧文件,大概率是编译问题建议直接删除out重新编译编译完成直接进行烧录即可
五、测试运行
烧录完成之后重启开发板,通过hdc工具调试,进入shell命令模式,执行以下命令:
# led_gpio -g 495 -v 1 -o
gpio id: 495
gpio dir: out
gpio value: 1
GPIO Read Successful and GPIO = 13, value = 1
#
#
# led_gpio -g 495 -v 0 -o
gpio id: 495
gpio dir: out
gpio value: 0
#
命令解析:
led_gpio 可执行模块 -g 495 led的gpio号,我这里的led的gpio号为495,请根据实际情况填写 -v 0 设置低电平 -v 1:设置高电平 -o 设置GPIO为输出 -i:设置GPIO为输入
六、结果
操作过程中如看到led点亮和熄灭,则案例实验成功如遇报错,需通过串口日志进一步排查问题所在
排查思路:
led设备是否挂载在i2c下,设备树dts、dtsi对应i2c是否配置并开启修改设备树是否成功,编译之后查看out/kernel/src_tmp/linux-5.10/arch/arm64/boot/dts/rockchip下的dts、dtsi文件是否成功生效,如果未生效表明修改方式不对,需要自己打patch并替换原patchHCS是否配置成功,通过hdc工具调试连接开发板,进入shell命令模式,cd /sys/devices/virtual/hdf查看文件列表是否存在HDF_PLATFORM_GPIO_MANAGERgpio号是否正确
相关阅读
发表评论