HarmonyOS第一个程序 – Hello World 精华
HarmonyOS第一个程序 – Hello World
搭建完环境之后,就可以开始编写代码啦。
这里从代码编写到运行,一步一步的过程总结到这儿。
一、编写代码
这个官方文档有示例,有基础的同学可以直接跟着实操。
下面以新增业务(my_first_app)为例,向开发者介绍如何进行源码修改。
1. 确定目录结构
开发者编写业务时,务必先在./applications/sample/wifi-iot/app路径下新建一个目录(或一套目录结构),用于存放业务源码文件。
例如:在app下新增业务my_first_app,其中hello_world.c为业务代码,BUILD.gn为编译脚本,具体规划目录结构如下:
.
└── applications
└── sample
└── wifi-iot
└── app
│── my_first_app
│ │── hello_world.c
│ └── BUILD.gn
└── BUILD.gn
2. 编写业务代码
新建./applications/sample/wifi-iot/app/my_first_app下的hello_world.c文件,在hello_world.c中新建业务入口函数HelloWorld,并实现业务逻辑。并在代码最下方,使用HarmonyOS启动恢复模块接口SYS_RUN()启动业务。(SYS_RUN定义在ohos_init.h文件中)
#include <stdio.h>
#include "ohos_init.h"
void Hello_World(void)
{
printf("[Demo] Hello World!\n");
}
SYS_RUN(Hello_World);
3. 编写用于将业务构建成静态库的BUILD.gn文件
新建./applications/sample/wifi-iot/app/my_first_app下的BUILD.gn文件,并完成如下配置。
如步骤1所述,BUILD.gn文件由三部分内容(目标、源文件、头文件路径)构成,需由开发者完成填写。
static_library("myapp") {
sources = [
"hello_world.c"
]
include_dirs = [
"//utils/native/lite/include"
]
}
- static_library中指定业务模块的编译结果,为静态库文件libmyapp.a,开发者根据实际情况完成填写。
- sources中指定静态库.a所依赖的.c文件及其路径,若路径中包含"//“则表示绝对路径(此处为代码根路径),若不包含”//"则表示相对路径。
- include_dirs中指定source所需要依赖的.h文件路径。
4. 编写模块BUILD.gn文件,指定需参与构建的特性模块
配置./applications/sample/wifi-iot/app/BUILD.gn文件,在features字段中增加索引,使目标模块参与编译。features字段指定业务模块的路径和目标,以my_first_app举例,features字段配置如下。
import("//build/lite/config/component/lite_component.gni")
lite_component("app") {
features = [
"my_first_app:myapp",
]
}
-
my_first_app是相对路径,指向./applications/sample/wifi-iot/app/my_first_app/BUILD.gn。
-
myapp是目标,指向./applications/sample/wifi-iot/app/my_first_app/BUILD.gn中的static_library(“myapp”)。
二、构建代码
1. 代码编译
hb 是 HarmonyOS 编译构建命令行工具,常用的命令有 hb clean 清除上一次的编译产物、hb set 设置编译类型,hb build 执行编译。
在源代码根目录下执行
root@141da74fa142:/home/openharmony/code-2.0-canary# hb set
[OHOS INFO] Input code path: .
OHOS Which product do you need? wifiiot_hispark_pegasus
root@141da74fa142:/home/openharmony/code-2.0-canary# hb build -b release
[OHOS INFO] [1/2] ACTION //device/hisilicon/hispark_pegasus/sdk_liteos:run_wifiiot_scons(//build/lite/toolchain:riscv32-unknown-elf)
[OHOS INFO] /home/openharmony/code-2.0-canary/vendor/hisilicon/hispark_pegasus/fs.yml not found,
stop packing fs. If the product does not need to be packaged, ignore it.
[OHOS INFO] wifiiot_hispark_pegasus build success
root@141da74fa142:/home/openharmony/code-2.0-canary#
以上为编译Hi3861的过程,出现build success表示编译成功
输出的过程文件和最终bin文件,在以下路径:
out\hispark_pegasus\wifiiot_hispark_pegasus
目录中有多个bin文件,bin文件说明:
2. 编译工具简介
在Unix/Linux下通常使用Make/Makefile来控制代码的编译,但是Makefile对于比较大的项目有时候会比较慢,Ninja是Google的一名程序员推出的注重速度的构建工具,目标是替代makefile之类的构建器,通过将编译任务并行组织,大大提高了构建速度。
Ninja的目标是成为汇编程序,这样才能从指令层面来优化编译性能。
Gn 是 Generate ninja 的缩写,用于产生 ninja文件。HarmonyOS 的编译构建子系统就是基于 gn 和 ninja,编译构建子系统支持OpenHarmony 组件化开发的编译框架,主要提供以下功能:构建已有产品,独立构建芯片厂商源码,独立构建单个组件。
3. 平台配置文件
vendor\hisilicon\hispark_pegasus\config.json
该文件定义的子系统中需要编译的组件。
-
模块:applications
作用:这个路径下存放了hi3681编写的应用程序代码,例如 hello world 代码就放在这个路径下。 -
模块:iot_hardware
作用:存放了 hi3681 芯片相关的驱动、例如spi、gpio、uart等。 -
模块:vendor
作用:存放了 hi3681 相关的厂商SDK之类的文件。其中,app_io_init.c 是hi3681内核启动后的io口相关设置,用户需根据应用场景,合理选择各外设的IO复用配置。app_main.c 是内核启动进入的应用程序入口。
三、启动流程
kernel初始化默认在OS main函数中已经执行,无需外部再进行初始化:
系统初始化函数调用流程为:main -> app_main -> HOS_SystemInit。其中main函数中执行了Hi3861硬件和内存相关配置/初始化、LOS_KernelInit、APP初始化(即创建app_main任务)、LOS_Start。
1. app_main()
device\hisilicon\hispark_pegasus\sdk_liteos\app\wifiiot_app\src\app_main.c
在app_main()中,打印了SDK的版本
const hi_char* sdk_ver = hi_get_sdk_version();
printf("sdk ver:%s\r\n", sdk_ver);
然后进行外设初始化、内存初始化、文件系统初始化、WIFI初始化等等一些操作,
最后在OHOS_Main()中执行 HOS_SystemInit()
进行鸿蒙系统的初始化。
2. HOS_SystemInit()
void OHOS_SystemInit(void)
{
MODULE_INIT(bsp);
MODULE_INIT(device);
MODULE_INIT(core);
SYS_INIT(service);
SYS_INIT(feature);
MODULE_INIT(run);
SAMGR_Bootstrap();
}
在 HOS_SystemInit()中,主要是初始化了一些相关模块、系统,包括有 bsp、device(设备)。其中最终的是 MODULE_INIT(run),它负责调用了所有 run 段的代码,那么 run 段的代码是哪些呢?事实上就是我们前面 application 中 hello_world.c 使用 SYS_RUN() 宏设置的函数名。
3. 链接方式
看了唐老师的一个文档受益匪浅,有兴趣的小伙伴也可以看下 https://harmonyos.51cto.com/posts/2017
我在这里做下总结:
在业务代码中通过SYS_RUN把参数定义成函数指针__zinitcall_run_hello_world通过强制编译的方式进入 .zinitcall.run2.init 段中。在ohos_init.h中可以看到定义:
再看MODULE_INIT怎么调用函数的,在链接脚本中定义的两个符号 __zinitcall_run_start (理解为数组名)和 __zinitcall_run_end 分别指向 __zinitcall_run_hello_world 所在数据段的起始位置和结束位置。
而MODULE_INIT作用就是遍历这个“数组”,是这样定义的:
#define MODULE_BEGIN(name, step) \
({ extern InitCall __zinitcall_##name##_start; \
InitCall *initCall = &__zinitcall_##name##_start; \
(initCall); \
})
#define MODULE_END(name, step) \
({ extern InitCall __zinitcall_##name##_end; \
InitCall *initCall = &__zinitcall_##name##_end; \
(initCall); \
})
#define MODULE_CALL(name, step) \
do { \
InitCall *initcall = (InitCall *)(MODULE_BEGIN(name, step)); \
InitCall *initend = (InitCall *)(MODULE_END(name, step)); \
for (; initcall < initend; initcall++) { \
(*initcall)(); \
} \
} while (0)
#define MODULE_INIT(name) \
do { \
MODULE_CALL(name, 0); \
} while (0)
在MODULE_CALL里面定义了一个起始地址和一个结束地址,然后使用一个for循环遍历起始和结束之间的内存空间,从而运行用户定义的任务。
四、烧录文件
上面介绍了如何编写文件、编译文件以及启动的一个流程,那么在这里再烧录运行一遍。
我在这里演示使用Hi3861 开发板,烧录直接使用海思工具HiBurn.exe,软件可以通过在VS Code安装中devicetool-windows-tool插件获得,也可以在购买开发板套件时给卖家索要,获取方式有很多。
打开软件需要设置相关的一下配置,我的烧录配置截图放到下面:
- 端口设置开发板使用数据线连接PC,在PC设备管理中可以看到CH340的一个COM口,然后在软件中找到这个COM口;
- Setting->Com setting波特率设置为2000000,决定烧录速度,不建议设置过高,太高了容易失败;
- 选择编译生成的Bin文件Hi3861_wifiiot_app_allinone.bin,文件位置以及说明上面有;
- 勾选Auto burn,自动烧写,不勾选可能有误;
- 点击Connect,然后复位单板,看到successful字样就是烧录成功了;
烧录完之后再按下复位,打开串口助手就可以看到 ”[Demo] Hello World!“ 啦
总结
为什么烧录的时候点击Connect还需要手动复位呢,这个其实和单板boot启动流程有关,有兴趣的同学也可以找下资料了解下;
其实官方给的文档还是比较多的,在学习鸿蒙系统的时候还是要多看多练的。
我也是刚接触鸿蒙,还有很长的路要走,以后会在这里给大家分享一下文档总结,欢迎大家指导。