以下是我学习 OpenHarmony3.0 树莓派 4B 移植的详细流程记录。实现了触摸与显示,并添加了物理按键关机的功能。希望能给像我一样第一次接触移植的嵌入式小白以帮助。

以下步骤操作于 Ubuntu 20.04 LTS。OHOS 3.0 LTS 源码解压自官方镜像(使用 repo下载得到的最新源码有较多变化,以下步骤不再适用)。
设项目根目录名为 OpenHarmony,且控制台默认处于该目录。移植系统为 OHOS 标准版(standard),内核为 Linux-5.10。
定义开发板

OpenHarmony/productdefine/common/device/bcm2711.json
{
"device_name": "bcm2711",
"device_company": "brcm",
"target_os": "ohos",
"target_cpu": "arm",
"kernel_version": "",
"device_build_path": "device/brcm/build"
}
目前 OHOS 只支持 arm。接着定义产品。建立文件:
OpenHarmony/productdefine/common/products/rpi4.json
{
"product_name": "rpi4",
"product_company": "raspberry",
"product_device": "bcm2711",
"version": "2.0",
"type": "standard",
"product_build_path": "device/brcm/build",
"parts": {
...
"brcm_products:brcm_products":{},
...
}
}
brcm_products 代表内核构建的子系统。我们需要在 OpenHarmony/build/subsystem_config.json 中定义它,加入键值对:
"brcm_products":{
"project": "hmf/brcm_products",
"path": "device/brcm/bcm2711/build",
"name": "brcm_products",
"dir": "device/brcm"
},
建立编译配置组件
目录结构:
device
└── brcm
├── bcm2711
│ ├── build
│ │ └── rootfs
│ │ ├── BUILD.gn
│ │ └── init.rpi4.cfg
│ └── BUILD.gn
└── build
├── BUILD.gn
└── ohos.build
OpenHarmony/device/brcm/bcm2711/build/rootfs/BUILD.gn
import("//build/ohos.gni")
ohos_prebuilt_etc("init.rpi4.cfg") {
source = "init.rpi4.cfg"
install_images = [ "system" ]
part_name = "brcm_products"
}
group("init_configs") {
deps = [
":init.rpi4.cfg"
]
}
OpenHarmony/device/brcm/bcm2711/build/rootfs/init.rpi4.cfg
OpenHarmony/device/hisilicon/hi3516dv300/build/rootfs/init.Hi3516DV300.cfg
OpenHarmony/device/brcm/bcm2711/BUILD.gn
import("//build/ohos.gni")
print("bcm2711_group in")
group("bcm2711_group") {
deps = [
"build/rootfs:init_configs",
"//kernel/linux/build:linux_kernel"
]
}
OpenHarmony/device/brcm/build/BUILD.gn
import("//build/ohos.gni")
group("products_group") {
deps = [
"//device/brcm/bcm2711:bcm2711_group"
]
}
OpenHarmony/device/brcm/build/ohos.build
{
"subsystem": "brcm_products",
"parts": {
"brcm_products": {
"module_list": [
"//device/brcm/build:products_group"
]
}
}
}
内核移植
①内核编译流程
首先阅读 OpenHarmony/kernel/linux/build/kernel.mk:
$(KERNEL_IMAGE_FILE):
$(hide) echo "build kernel..."
$(hide) rm -rf $(KERNEL_SRC_TMP_PATH);mkdir -p $(KERNEL_SRC_TMP_PATH);cp -arfL $(KERNEL_SRC_PATH)/* $(KERNEL_SRC_TMP_PATH)/
$(hide) cd $(KERNEL_SRC_TMP_PATH) && patch -p1 < $(HDF_PATCH_FILE) && patch -p1 -s -N < $(DEVICE_PATCH_FILE)
ifneq ($(findstring $(BUILD_TYPE), small),)
$(hide) cd $(KERNEL_SRC_TMP_PATH) && patch -p1 < $(SMALL_PATCH_FILE)
endif
$(hide) cp -rf $(KERNEL_CONFIG_PATH)/. $(KERNEL_SRC_TMP_PATH)/
$(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) distclean
$(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) $(DEFCONFIG_FILE)
ifeq ($(KERNEL_VERSION), linux-5.10)
$(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) modules_prepare
endif
$(hide) $(KERNEL_MAKE) -C $(KERNEL_SRC_TMP_PATH) ARCH=$(KERNEL_ARCH) $(KERNEL_CROSS_COMPILE) -j64 uImage
endif
删除 out(编译输出目录)下之前编译过的内核源码,重新复制一份 Linux 内核源码 OpenHarmony/kernel/linux/linux-5.10 到 OpenHarmony/out/KERNEL_OBJ/kernel/src_tmp/linux-5.10 下。
对内核源码打补丁,包括内核源码补丁与 HDF 补丁。补丁需要预先准备。
复制 defconfig,需要预先准备。
make distclean,xxx_defconfig,modules_prepare,uImage。
②生成 patch
因此首先准备树莓派 4B 需要的内核补丁:选择树莓派官方内核 rpi-5.10.y 来生成内核源码补丁。
https://github.com/raspberrypi/linux
设控制台处在项目根目录 OpenHarmony 下,依次执行:
mkdir ../rpi-kernel && cd $_
git clone git://github.com/raspberrypi/linux -b rpi-5.10.y --depth=1
cd ../OpenHarmony/kernel/linux
diff -uNr linux-5.10/ /home/username/Project/RPI/rpi-kernel/linux/ > bcm2711.patch
mkdir patches/linux-5.10/bcm2711_patch && cp bcm2711.patch $_ && cd $_
cp ../hi3516dv300_patch/hdf.patch ./
由于 kernel.mk 中用的是 patch -p1,即忽略掉路径的第一部分,因此 diff 需作用在 OHOS 自带的源码目录外。
树莓派官方内核源码需要用绝对路径,因人而异。
在 diff 时忽略源码目录下的 .git, .gitgitignore, 以及某设备树文件,保证打补丁时无冲突。
手动执行 kernel.mk 中的流程。
③生成 defconfig
在树莓派官方 bcm2711_defconfig 基础上进行增改(当前位于 OpenHarmony 目录):
cp ../rpi-kernel/linux/arch/arm/configs/bcm2711_defconfig kernel/linux/config/linux-5.10/arch/arm/configs/bcm2711_standard_defconfig
OHOS 上层系统所需:开启 SELinux,Binder,ALSA。
显示:开启 DRM,VC4。
触屏:开启 INPUT_TOUCHSCREEN,HID_MULTITOUCH,ADS7846,USB_COMPOSITE。因为我使用的微雪触屏控制器为 ADS7846,且是 USB 连线。
按键关机:开启 CONFIG_KEYBOARD_GPIO,以将 gpio-key 驱动编译进内核。
生成方法:先进行一次内核编译:
./build.sh --product-name rpi4 --ccache --jobs $(nproc) --build-target brcm_products
再进入 out 下的内核源码根目录,利用 menuconfig 配置 .config,最后用 savedefconfig 保存:
cd out/KERNEL_OBJ/kernel/src_tmp/linux-5.10
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make bcm2711_standard_defconfig
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make menuconfig
ARCH=arm CROSS_COMPILE=arm-linux-gnueabihf- make savedefconfig
cp savedefconfig ../../../../../kernel/linux/config/linux-5.10/arch/arm/configs/bcm2711_standard_defconfig
或者直接对 bcm2711_standard_defconfig 增改以下内容:
CONFIG_KEYBOARD_GPIO=y
CONFIG_TOUCHSCREEN_ADS7846=y
CONFIG_TOUCHSCREEN_RASPBERRYPI_FW=y
CONFIG_TOUCHSCREEN_USB_COMPOSITE=y
CONFIG_DRM=y
CONFIG_DRM_V3D=y
CONFIG_DRM_VC4=y
CONFIG_SND=y
CONFIG_SND_SOC=y
CONFIG_HID_MULTITOUCH=y
CONFIG_ANDROID=y
CONFIG_ANDROID_BINDER_IPC=y
# CONFIG_DRIVERS_HDF is not set
CONFIG_SECURITY_SELINUX=y
CONFIG_SECURITY_SELINUX_BOOTPARAM=y
CONFIG_SECURITY_SELINUX_CHECKREQPROT_VALUE=1
④编译脚本修正
事实上不修改也没有关系。在最终生成 uImage 之前,内核编译时会在 boot 下首先生成 zImage,手动将其复制到镜像输出目录即可:
cp out/KERNEL_OBJ/kernel/src_tmp/linux-5.10/arch/arm/boot/zImage out/ohos-arm-release/packages/phone/images
显示与触摸配置
①显示
vi third_party/weston/weston.ini
最后添加:
[output]
name=card0
不设置一般也没有关系,默认通过 card0 节点显示。如果输出节点为 HDMI-A-1 并需要旋转,则添加:
[output]
name=HDMI-A-1
transform=rotate-90
②触摸
vi third_party/eudev/rules.d/touchscreen.rules
修改为:
ATTRS{name}=="WaveShare WS170120", ENV{ID_INPUT}="1", ENV{ID_INPUT_TOUCHSCREEN}="1"
ATTRS{name}=="VSoC keyboard", ENV{ID_INPUT}="1", ENV{ID_INPUT_KEYBOARD}="1"
DRIVERS=="hid-multitouch", ENV{ID_INPUT}="1", ENV{ID_INPUT_TOUCHSCREEN}="1"
其他修正
①init.cfg
初始化配置文件,类似于 Android 里的 init.rc:
vi /base/startup/init_lite/services/etc/init.cfg
进行修改:
- "/etc/init.Hi3516DV300.cfg"
+ "/etc/init.rpi4.cfg"
- "mount ext4 /dev/block/platform/soc/10100000.himci.eMMC/by-name/vendor /vendor wait rdonly barrier=1",
- "mount ext4 /dev/block/platform/soc/10100000.himci.eMMC/by-name/userdata /data wait nosuid nodev noatime barrier=1,data=ordered,noauto_da_alloc"
+ "mount ext4 /dev/block/mmcblk0p3 /vendor wait rdonly barrier=1",
+ "mount ext4 /dev/block/mmcblk0p4 /data wait nosuid nodev noatime barrier=1,data=ordered,noauto_da_alloc"
②vendor
虽然没有移植 HDF 驱动,但是相关文件对内核编译流程是必要的。在 OpenHarmony/vendor 下新建文件,目录结构如下所示:
vendor
└── raspberry
└── rpi4
└── hdf_config
├── hdf.hcs
├── hdf_test
│ ├── hdf.hcs
│ └── Makefile
├── khdf
│ ├── hdf.hcs
│ └── Makefile
├── Makefile
└── uhdf
└── hdf.hcs
其中 hdf.hcs 均为:
root {
module = "default";
}
Makefile 均拷贝自:
OpenHarmony/vendor/hisilicon/Hi3516DV300/hdf_config/khdf/Makefile
③camera.rpi4.gni
虽然没有移植 camera 驱动,但也对编译是必要的,否则报错:
cp drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi3.gni drivers/peripheral/camera/hal/adapter/chipset/gni/camera.rpi4.gni
④镜像大小
编译
在根目录 OpenHarmony 下执行:
bash build/prebuilts_download.sh
./build.sh --product-name rpi4 --ccache --jobs $(nproc)
手动编译时可能会出现找不到环境变量 PRODUCT_PATH 的错误,手动设置:
export PRODUCT_PATH=vendor/raspberry/rpi4
制作 SD 卡并启动
①分区与格式化
利用 fdisk 设置 sd 卡分区:
sudo fdisk /dev/mmcblk0
删除既有分区。(d 命令)
新建四个主分区,分别对应 boot, system, vendor 与 userdata。大小由对应镜像决定。(n 命令)
设置 p1 分区文件类型为 W95 FAT32 (LBA)。(t 命令后选择 c)
为 p1 分区添加可启动标志。(a 命令)
可能的最终效果(p 命令):
设备 启动 起点 末尾 扇区 大小 Id 类型
/dev/mmcblk0p1 * 2048 264191 262144 128M c W95 FAT32 (LBA)
/dev/mmcblk0p2 264192 4458495 4194304 2G 83 Linux
/dev/mmcblk0p3 4458496 5507071 1048576 512M 83 Linux
/dev/mmcblk0p4 5507072 62333951 56826880 27.1G 83 Linux
对应的 fdisk 内命令执行序列:
n p 1 +128M n p 2 +2G n p 3 +512M n p t 1 c a 1 w
格式化:
sudo mkfs.msdos /dev/mmcblk0p1 -n boot
sudo mkfs.ext4 /dev/mmcblk0p2
sudo mkfs.ext4 /dev/mmcblk0p3
sudo mkfs.ext4 /dev/mmcblk0p4
②制备 boot
基于树莓派官方 firmware:
https://github.com/raspberrypi/firmware
设 boot 分区 /dev/mmcblk0p1 已经挂载到 /media/username/boot 下:
cd ../ && git clone git://github.com/raspberrypi/firmware --depth=1
cp firmware/boot/{overlays,bcm2711-rpi-4-b.dtb,bcm2711-rpi-400.dtb,bcm2711-rpi-cm4.dtb,cmdline.txt,config.txt,fixup4.dat,fixup4x.dat,start4.elf,start4x.elf} /media/username/boot -r
cp OpenHarmony/out/ohos-arm-release/packages/phone/images/zImage /media/username/boot
修改 config.txt:
vi /media/username/boot/config.txt
# Kernel
kernel=zImage
# waveshare touchscreen
max_usb_current=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt 800 480 60 6 0 0 0
hdmi_drive=1
# fake KMS
dtoverlay=vc4-fkms-v3d
enable_uart=1
# output diagnostic information
uart_2ndstage=1
# for hardware power button
dtoverlay=gpio-key,gpio=3,keycode=116,label="POWER"
修改 cmdline.txt:
vi /media/username/boot/cmdline.txt
console=serial0,115200 no_console_suspend root=/dev/mmcblk0p2 elevator=deadline rootwait spidev.bufsiz=65536 androidboot.hardware=rpi4 androidboot.selinux=permissive
③刷写镜像
cd OpenHarmony/out/ohos-arm-release/packages/phone/images
sudo dd if=system.img of=/dev/mmcblk0p2 bs=1M
sudo dd if=vendor.img of=/dev/mmcblk0p3 bs=1M
sudo dd if=userdata.img of=/dev/mmcblk0p4 bs=1M
④启动
config.txt:手动设置 HDMI 输出配置(来自官网要求:5inch_HDMI_LCD)
max_usb_current=1
hdmi_group=2
hdmi_mode=87
hdmi_cvt 800 480 60 6 0 0 0
hdmi_drive=1
按键关机
而树莓派官方已经有相应实现(详见 rpi-kernel/linux/arch/arm/boot/dts/overlays/README),只需要在 config.txt 开启即可:
dtoverlay=gpio-key,gpio=3,keycode=116,label="POWER"
Android 与 OpenHarmony 虽然不用 systemd,但可以运行服务。因此我们自己写一个简单的守护程序:
mkdir ../event0reader && cd $_
vi event0reader.c
#include <stdio.h>
#include <linux/input.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#define DEV_PATH "/dev/input/event0"
int main()
{
int keys_fd;
int ret=0;
struct input_event t;
keys_fd=open(DEV_PATH, O_RDONLY);
if(keys_fd <= 0)
{
printf("open /dev/input/event0 device error!\n");
return -1;
}
while(1)
{
if(read(keys_fd, &t, sizeof(t)) == sizeof(t))
{
if(t.type==EV_KEY && t.code==116 && t.value==1)
{
ret=system("/bin/reboot shutdown");
if (ret == -1)
{
printf("failed to shutdown!\n");
return -1;
}
}
}
usleep(100000);
}
close(keys_fd);
return 0;
}
其中 /bin/reboot 是 OHOS 自带的重启/关机程序,源码位于:
OpenHarmony/base/startup/init_lite/services/cmds/reboot/init_cmd_reboot.c
设 system 分区 /dev/mmcblk0p2 已经挂载到 /media/username/_ 下:
arm-linux-gnueabihf-gcc event0reader.c -o myservice -static
sudo cp myservice /media/username/_/system/bin/
sudo chmod 777 /media/username/_/system/bin/myservice
sudo chgrp 2000 /media/username/_/system/bin/myservice
我们在该目录下写一个对应的 myservice.cfg:
sudo vi /media/username/_/system/etc/init/myservice.cfg
{
"jobs" : [{
"name" : "post-fs",
"cmds" : [
"start myservice_shutdown"
]
}
],
"services" : [{
"name" : "myservice_shutdown",
"path" : ["/system/bin/myservice"],
"uid" : "root",
"gid" : ["system", "shell"]
}
]
}
当我们在内核启动时观察到如下信息:
# dmesg |grep myservice
[ 3.722287] [pid=1][init_read_cfg.c:110][Init][INFO] ReadCfgs :/system/etc/init/myservice.cfg from /system/etc/init success.
[ 7.558109] [pid=1][param_service.c:263][Init][INFO] SystemWriteParam name init.svc.myservice_shutdown value: running
[ 7.558618] [pid=116][init_service.c:200][Init][INFO] service->name is myservice_shutdown
[ 7.569011] [pid=1][trigger_processor.c:165][Init][INFO] PostParamTrigger init.svc.myservice_shutdown success
则意味着 myservice 成功启动了,可以利用物理按键关机。接线示意图:

👇扫码报名下周三的鸿蒙直播课👇


求分享

求点赞

求在看




