OpenHarmony实战:瑞芯微RK3566移植案例(中)
上篇文章:OpenHarmony实战:瑞芯微RK3566移植案例(上)
TOUCH PANEL
常见的INPUT设备有键盘、鼠标、游戏杆、Touch Screen等。Touch 设备与主机通讯采用标准 I2C 总线,触屏 IC 提供中断支持,提高了触屏数据的实时性。本项目的触摸屏器件IC 为 GT911。
驱动框架模型
INPUT驱动模型
INPUT 驱动模型核心部分由设备管理层、公共驱动层、器件驱动层组成。
(1)设备管理层:为各类输入设备驱动提供input设备的注册、注销接口,同时统一管理 input 设备列表;
(2)平台驱动层:指各类input设备的公共抽象驱动(例如触摸屏的公共驱动),负责对板级硬件进行初始化、硬件中断处理、向manager注册input设备等;
(3)器件驱动层:指各器件厂家的差异化驱动,通过适配平台驱动预留的差异化接口,实现器件驱动开发量最小化。
HDI接口层框架图
INPUT驱动提供给系统服务Input Service可直接调用的驱动能力接口,按照属性分类三类:input设备管理模块、input数据上报模块、input业务控制模块,HDI接口主要包括如下三大类:
- input设备管理模块:管理输入设备,包括输入设备的打开、关闭、设备列表信息获取等;
- input数据上报模块:负责输入事件的上报,包括注册、注销数据上报回调函数等;
- input业务控制模块:提供input设备的业务控制接口,包括获取器件信息及设备类型、设置电源状态等。
HDF驱动适配
HCS配置
配置设备描述信息,在device_info.hcs中添加device_touch_chip:
input :: host {
hostName = "input_host";
priority = 100;
device_input_manager :: device { // Input管理层设备描述信息
device0 :: deviceNode {
policy = 2;
priority = 100;
preload = 0;
permission = 0660;
moduleName = "HDF_INPUT_MANAGER";
serviceName = "hdf_input_host";
deviceMatchAttr = "";
}
}
device_hdf_touch :: device { // Input公共驱动层设备描述信息
device0 :: deviceNode {
policy = 2;
priority = 120;
preload = 0;
permission = 0660;
moduleName = "HDF_TOUCH";
serviceName = "hdf_input_event1";
deviceMatchAttr = "touch_device1";
}
}
device_touch_chip :: device { // Input器件驱动层信息
device0 :: deviceNode {
policy = 0;
priority = 130;
preload = 0;
permission = 0660;
moduleName = "HDF_TOUCH_GT911";
serviceName = "hdf_touch_gt911_service";
deviceMatchAttr = "zsj_gt911_5p5";
}
}
device_hdf_hid :: device {
device0 :: deviceNode {
policy = 2;
priority = 200;
preload = 0;
permission = 0660;
moduleName = "HDF_HID";
}
}
}
配置Touch器件信息,在input_config.hcs中添加器件的特性:
chipConfig {
template touchChip {
match_attr = "";
chipName = "gt911";
vendorName = "zsj";
chipInfo = "AAAA11222";
busType = 0;
deviceAddr = 0x5D;
irqFlag = 2;
maxSpeed = 400;
chipVersion = 0; //parse Coord TypeA
powerSequence {
/* [type, status, dir , delay]
<type> 0:none 1:vcc-1.8v 2:vci-3.3v 3:reset 4:int
<status> 0:off or low 1:on or high 2:no ops
<dir> 0:input 1:output 2:no ops
<delay> meanings delay xms, 20: delay 20ms
*/
powerOnSeq = [4, 0, 1, 5,
3, 0, 1, 10,
3, 1, 1, 60,
4, 2, 0, 50];
suspendSeq = [3, 0, 2, 10];
resumeSeq = [3, 1, 2, 10];
powerOffSeq = [3, 0, 2, 10,
1, 0, 2, 20];
}
}
chip0 :: touchChip {
match_attr = "zsj_gt911_5p5";
chipInfo = "ZIDN45100";
chipVersion = 0;
}
}
适配文件
Touch驱动适配涉及的文件及目录:
1、 编辑 Makefile 文件:./drivers/adapter/khdf/linux/model/input/Makefile
2、 公共配置文件:./vendor/kaihong/khdvk_3566b/hdf_config/khdf/device_info/device_info.hcs
3、 私有配置文件:./vendor/kaihong/khdvk_3566b/hdf_config/khdf/input/input_config.hcs
4、 驱动:drivers\framework\model\input\driver\touchscreen
HDF驱动模型高度抽象集成,TP驱动的适配主要是器件驱动层的适配,首先需要明确TP所需要的软硬件资源。
TP模组需要主机上的如下硬件资源:
1.中断引脚
2.Reset引脚
3.使用的哪一组i2c,从设备的地址是什么
4.TP的初始化固件(通常由IC厂商提供)
5.触摸屏的分辨率
TP模组需要依赖主机上的如下软件资源:
1.Hdf gpio子系统 用于设置gpio pin脚以及一些中断资源
2.Hdf i2c 子系统 用于进行i2c通信
3.Input模型
器件差异化接口适配,示例代码路径:
./drivers/framework/model/input/driver/touchscreen/Touch_gdi_gt911.c
static struct TouchChipOps g_gt911ChipOps = { // 器件IC接口
.Init = ChipInit, // 初始化
.Detect = ChipDetect, // 器件检测
.Resume = ChipResume, // 唤醒
.Suspend = ChipSuspend, // 休眠
.DataHandle = ChipDataHandle, // 器件数据读取
.UpdateFirmware = UpdateFirmware, // 固件升级
.SetAbility = SetAbility, // 配置
};
器件驱动初始化及HDF注册,示例代码路径:
./drivers/framework/model/input/driver/touchscreen/touch_jdi_gt911.c
static int32_t HdfGoodixChipInit(struct HdfDeviceObject *device)
{
...
/* 器件配置结构体内存申请、配置信息解析及挂载 */
chipCfg = ChipConfigInstance(device);
...
/* 器件实例化 */
chipDev = ChipDeviceInstance();
...
/* 器件信息挂载及器件私有操作挂载 */
chipDev->chipCfg = chipCfg;
chipDev->ops = &g_gt911ChipOps;
...
/* 注册器件驱动至平台驱动 */
RegisterChipDevice(chipDev);
...
}
struct HdfDriverEntry g_touchGoodixChipEntry = {
.moduleVersion = 1,
.moduleName = "HDF_TOUCH_GT911",
.Init = HdfGoodixChipInit, // 器件驱动初始化函数
.Release = HdfGoodixChipRelease,
};
HDF_INIT(g_touchGoodixChipEntry); // 注册器件驱动至HDF框架
代码分布
/drivers/peripheral/input
/drivers/framework/model/input
OpenHarmony Camera HDF驱动框架概述
OpenHarmony Camera驱动模型结构
- HDI Implementation:对上实现HDI接口,向下调用框架层的接口,完成HDI接口任务的转发。
- Buffer Manager:屏蔽不同内存管理的差异,为子系统提供统一的操作接口,同时提供buffer轮转的功能。
- Pipeline Core:解析HCS配置完成pipeline的搭建,调度pipeline中的各个node完成流的处理
- Device Manager:通过调用底层硬件适配层接口,实现查询控制底层设备、枚举监听底层设备的功能
- Platform Adaption:屏蔽硬件差异,为Device Manager提供统一的操作底层硬件的能力
CameraService 进程
CameraService源码目录为:foundation/multimedia/camera_standard,camera app通过camera service与hal层进行交互
├── bundle.json
├── figures
├── frameworks camera frameworks部分,支持js和native转换
│ ├── js
│ └── native
├── hisysevent.yaml
├── interfaces CameraService接口
│ ├── inner_api
│ └── kits
├── LICENSE
├── OAT.xml
├── README.md
├── README_zh.md
├── sa_profile CameraService进程加载配置文件
│ ├── 3008.xml
│ └── BUILD.gn
└── services CameraService启动相关
├── camera_service
└── etc
CameraService启动入口在foundation/multimedia/camera_standard/services/etc/camera_service.cfg进行启动配置
"services" : [{
"name" : "camera_service",
"path" : ["/system/bin/sa_main", "/system/profile/camera_service.xml"],
"uid" : "cameraserver",
"gid" : ["system", "shell"],
"secon" : "u:r:camera_service:s0"
}
]
Camera驱动框架介绍
###Camera驱动整体架构
camera驱动源码分布
Camera 驱动框架所在的仓为:drivers_peripheral,源码目录为:“drivers/peripheral/camera”。
├── bundle.json
├── figures
│ ├── Camera模块驱动模型.png
│ └── logic-view-of-modules-related-to-this-repository_zh.png
├── hal
│ ├── adapter #平台适配层,适配平台
│ ├── buffer_manager
│ ├── BUILD.gn #Camera驱动框架构建入口
│ ├── camera.gni #定义组件所使用的全局变量
│ ├── device_manager
│ ├── hdi_impl
│ ├── include
│ ├── init #demo sample
│ ├── pipeline_core
│ ├── test #测试代码
│ └── utils
├── hal_c #为海思平台提供专用C接口
│ ├── BUILD.gn
│ ├── camera.gni
│ ├── hdi_cif
│ └── include
├── interfaces #HDI接口
│ ├── hdi_ipc
│ ├── hdi_passthrough
│ ├── include
│ └── metadata
└── README_zh.md
Camera Host HDF驱动
###配置文件
Camera Host HDF配置相关在“vendor/kaihong/khdvk_3566b/hdf_config/uhdf/device_info.hcs”
hdi_server :: host {
hostName = "camera_host";
priority = 50;
caps = ["DAC_OVERRIDE", "DAC_READ_SEARCH"];
camera_device :: device {
device0 :: deviceNode {
policy = 2;
priority = 100;
moduleName = "libcamera_hdi_impl.z.so";
serviceName = "camera_service";
}
}
...
}
其中主要参数说明如下:
- hostName = "camera_host":camera host节点,该节点为一个独立进程,如果需要独立进程,新增属于自己的host节点
- policy = 2:服务发布策略,Camera使用HDI服务,需设置为2
- moduleName:camera host驱动实现库名
- serviceName:服务名称,请保持全局唯一性,后面HDF Manager会根据这个名称拉起camera hdf
###camera host服务启动 camera host 服务由hdf_devhost启动,配置文件存放于vendor/etc/init/hdf_devhost.cfg
{
"name" : "camera_host",
"path" : ["/vendor/bin/hdf_devhost", "8", "camera_host"],
"uid" : "camera_host",
"gid" : ["camera_host"],
"caps" : ["DAC_OVERRIDE", "DAC_READ_SEARCH"],
"secon" : "u:r:camera_host:s0"
}
###Camera host驱动实现 代码路径:drivers/peripheral/camera/interfaces/hdi_ipc/server/src/camera_host_driver.cpp
驱动入口结构体,后面将该结构体注册进HDF框架中
struct HdfDriverEntry g_cameraHostDriverEntry = {
.moduleVersion = 1,
.moduleName = "camera_service",
.Bind = HdfCameraHostDriverBind,
.Init = HdfCameraHostDriverInit,
.Release = HdfCameraHostDriverRelease,
};
消息发布服务
static int32_t CameraServiceDispatch(struct HdfDeviceIoClient *client, int cmdId,
struct HdfSBuf *data, struct HdfSBuf *reply)
{
HdfCameraService *hdfCameraService = CONTAINER_OF(client->device->service, HdfCameraService, ioservice);
return CameraHostServiceOnRemoteRequest(hdfCameraService->instance, cmdId, data, reply);
}
参数说明:
client:HdfDeviceIoClient设备句柄
cmdId:请求消息命令字
data:其他服务或者IO请求数据
reply:存储返回消息内容数据
绑定服务:初始化设备服务对象和资源对象
int HdfCameraHostDriverBind(HdfDeviceObject *deviceObject)
{
...
hdfCameraService->ioservice.Dispatch = CameraServiceDispatch;
hdfCameraService->ioservice.Open = nullptr;
hdfCameraService->ioservice.Release = nullptr;
hdfCameraService->instance = CameraHostStubInstance();
deviceObject->service = &hdfCameraService->ioservice;
return HDF_SUCCESS;
}
相关说明:
hdfCameraService->ioservice.Dispatch:注册消息分发服务接口
hdfCameraService->instance:创建camerahost实例
驱动初始化函数: 探测并初始化驱动程序
int HdfCameraHostDriverInit(struct HdfDeviceObject *deviceObject)
{
return HDF_SUCCESS;
}
驱动资源释放函数 : 如已经绑定的设备服务对象
void HdfCameraHostDriverRelease(HdfDeviceObject *deviceObject)
{
if (deviceObject == nullptr || deviceObject->service == nullptr) {
HDF_LOGE("%{public}s deviceObject or deviceObject->service is NULL!", __FUNCTION__);
return;
}
HdfCameraService *hdfCameraService = CONTAINER_OF(deviceObject->service, HdfCameraService, ioservice);
if (hdfCameraService == nullptr) {
HDF_LOGE("%{public}s hdfCameraService is NULL!", __FUNCTION__);
return;
}
OsalMemFree(hdfCameraService);
}
设备创建不成功,关闭服务,释放相关资源
DeviceManager
创建SensorManager、FlashManager、ISPManager管理相应的设备。
####SensorManager sensor Manager结构如下
class SensorManager : public IManager {
public:
SensorManager();
explicit SensorManager(ManagerId managerId);
virtual ~SensorManager();
RetCode CreateController(ControllerId controllerId, std::string hardwareName);
RetCode DestroyController(ControllerId controllerId, std::string hardwareName);
std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);
void Configure(std::shared_ptr<CameraMetadata> meta);
RetCode Start(std::string hardwareName, int buffCont, DeviceFormat& format);
RetCode Stop(std::string hardwareName);
RetCode PowerUp(std::string hardwareName);
RetCode PowerDown(std::string hardwareName);
std::shared_ptr<ISensor> GetSensor(std::string sensorName);
RetCode SendFrameBuffer(std::shared_ptr<FrameSpec> buffer, std::string hardwareName);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName);
void SetNodeCallBack(const NodeBufferCb cb, std::string hardwareName);
void SetMetaDataCallBack(const MetaDataCb cb, std::string hardwareName);
private:
bool CheckCameraIdList(std::string hardwareName);
std::vector<std::shared_ptr<SensorController>> sensorList_;
};
}
PowerUp为上电接口,OpenCamera时调用此接口进行设备上电操作
PowerDown为下电接口,CloseCamera时调用此接口进行设备下电操作
Configures为Metadata下发接口,如需设置metadata参数到硬件设备,可实现此接口进行解析及下发
Start为硬件模块使能接口,pipeline中的各个node进行使能的时候,会去调用,可根据需要定义实现,比如sensor的起流操作就可放在此处进行实现,Stop和Start为相反操作,可实现停流操作
SendFrameBuffer为每一帧buffer下发接口,所有和驱动进行buffer交互的操作,都是通过此接口进行的
SetNodeCallBack为pipeline,通过此接口将buffer回调函数设置到devicemanager
SetMetaDataCallBack为metadata回调接口,通过此接口将从底层获取的metadata数据上报给上层
BufferCallback上传每一帧已填充数据buffer的接口,通过此接口将buffer上报给pipeline
SetAbilityMetaDataTag设置需要从底层获取哪些类型的metadata数据,因为框架支持单独获取某一类型或多类型的硬件设备信息,所以可以通过此接口,获取想要的metadata数据
Camera Sensor Controller结构如下:
class SensorController : public IController {
public:
SensorController();
explicit SensorController(std::string hardwareName);
virtual ~SensorController();
RetCode Init();
RetCode PowerUp();
RetCode PowerDown();
RetCode Configure(std::shared_ptr<CameraMetadata> meta);
RetCode Start(int buffCont, DeviceFormat& format);
RetCode Stop();
...
void SetMetaDataCallBack(MetaDataCb cb) override;
void BufferCallback(std::shared_ptr<FrameSpec> buffer);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);
RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta);
RetCode Flush(int32_t streamId);
...
};
PowerUp下发命令给v4l2 dev去操作实际设备进行上电操作 PowerDown下发命令给v4l2 dev去操作实际设备进行下电操作 同理其他操作参考SensorManager. ####FlashManager Flash Manger结构如下:
class FlashManager : public IManager {
public:
FlashManager();
explicit FlashManager(ManagerId managerId);
virtual ~FlashManager();
RetCode CreateController(ControllerId controllerId, std::string hardwareName);
std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);
RetCode PowerUp(std::string hardwareName);
RetCode PowerDown(std::string hardwareName);
void Configure(std::shared_ptr<CameraMetadata> meta);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName)
{
(void)abilityMetaDataTag;
(void)hardwareName;
return;
}
RetCode SetFlashlight(FlashMode flashMode, bool enable, std::string hardwareName);
private:
bool CheckCameraIdList(std::string hardwareName);
std::vector<std::shared_ptr<FlashController>> flashList_;
}
Flash controller结构如下:
class FlashController : public IController {
public:
FlashController();
explicit FlashController(std::string hardwareName);
virtual ~FlashController();
RetCode Init();
RetCode PowerUp();
RetCode PowerDown();
RetCode Configure(std::shared_ptr<CameraMetadata> meta)
{
(void)meta;
return RC_OK;
}
RetCode SetFlashlight(FlashMode flashMode, bool enable);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag);
RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta);
private:
std::mutex startVolock_;
bool startVoState_ = false;
}
####ISPManager ISP Manager结构如下
class IspManager : public IManager {
public:
IspManager();
explicit IspManager(ManagerId managerId);
virtual ~IspManager();
RetCode CreateController(ControllerId controllerId, std::string hardwareName);
std::shared_ptr<IController> GetController(ControllerId controllerId, std::string hardwareName);
void Configure(std::shared_ptr<CameraMetadata> meta);
RetCode Start(std::string hardwareName);
RetCode Stop(std::string hardwareName);
RetCode PowerUp(std::string hardwareName);
RetCode PowerDown(std::string hardwareName);
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag, std::string hardwareName)
{
(void)abilityMetaDataTag;
(void)hardwareName;
return;
}
private:
bool CheckCameraIdList(std::string hardwareName);
std::vector<std::shared_ptr<IspController>> ispList_;
};
ISP controller结构如下
class IspController : public IController {
public:
IspController();
explicit IspController(std::string hardwareName);
virtual ~IspController();
RetCode Init();
RetCode Configure(std::shared_ptr<CameraMetadata> meta);
RetCode PowerUp();
RetCode PowerDown();
RetCode Stop();
RetCode Start();
void SetAbilityMetaDataTag(std::vector<int32_t> abilityMetaDataTag)
{
(void)abilityMetaDataTag;
return;
}
RetCode GetAbilityMetaData(std::shared_ptr<CameraMetadata> meta)
{
(void)meta;
return RC_OK;
}
private:
std::mutex startIsplock_;
bool startIspState_ = false;
}
PlatForm Adapter
这部分通过V4l2框架对video设备进行管理,包括对相应设备的打开、启动/关闭数据流、设置/获取图像格式等等
####源代码 V4l2 Adapter 源码位于driver/peripheral/camera/hal/adapter/platform/v4l2/src/driver_adapter 部分关键函数如下:
class HosV4L2Dev {
public:
...
RetCode start(const std::string& cameraID);
RetCode stop(const std::string& cameraID);
RetCode CreatBuffer(const std::string& cameraID, const std::shared_ptr<FrameSpec>& frameSpec);
RetCode StartStream(const std::string& cameraID);
RetCode QueueBuffer(const std::string& cameraID, const std::shared_ptr<FrameSpec>& frameSpec);
RetCode ReleaseBuffers(const std::string& cameraID);
RetCode StopStream(const std::string& cameraID);
RetCode SetCallback(BufCallback cb);
static RetCode Init(std::vector<std::string>& cameraIDs);
static std::map<std::string, std::string> deviceMatch;
private:
std::shared_ptr<HosV4L2Buffers> myBuffers_ = nullptr;
std::shared_ptr<HosV4L2Streams> myStreams_ = nullptr;
std::shared_ptr<HosFileFormat> myFileFormat_ = nullptr;
std::shared_ptr<HosV4L2Control> myControl_ = nullptr;
...
enum v4l2_memory memoryType_ = V4L2_MEMORY_USERPTR;
enum v4l2_buf_type bufferType_ = V4L2_BUF_TYPE_PRIVATE;
};
PipeLineCore
这个模块解析HCS配置完成pipeline的搭建,调度pipeline中的各个node完成流的处理
###IPP算法加载 IPP是pipeline 中的一个算法插件模块,由ippnode加载,对流数据进行算法处理,ippnode支持同时多路数据输入,只支持一路数据输出。
vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/pipeline_core/ipp_algo_config.hcs为算法插件配置文件,后面有新的算法库需要在这里添加相关内容,添加模板如下:
root {
module="sample";
ipp_algo_config {
algo1 {
name = "example";
description = "example algorithm";
path = "libcamera_ipp_algo_example.z.so";
mode = "IPP_ALGO_MODE_NORMAL";
}
}
}
name:算法插件名称 description:描述算法插件的功能 path:算法插件所在路径 mode:算法插件所运行的模式
算法插件可运行的模式由 drivers/peripheral/camera/hal/pipeline_core/ipp/include/ipp_algo.h中的IppAlgoMode提供,可以根据需要进行扩展。
enum IppAlgoMode {
IPP_ALGO_MODE_BEGIN,
IPP_ALGO_MODE_NORMAL = IPP_ALGO_MODE_BEGIN,
IPP_ALGO_MODE_BEAUTY,
IPP_ALGO_MODE_HDR,
IPP_ALGO_MODE_END
};
算法插件由device/board/kaihong/khdvk_3566b/camera/BUILD.gn文件进行编译,算法插件需实现如下接口(接口由ipp_algo.h指定)供ippnode调用:
typedef struct IppAlgoFunc {
int (*Init)(IppAlgoMeta* meta);
int (*Start)();
int (*Flush)();
int (*Process)(IppAlgoBuffer* inBuffer[], int inBufferCount, IppAlgoBuffer* outBuffer, IppAlgoMeta* meta);
int (*Stop)();
} IppAlgoFunc;
Init : 算法插件初始化接口,在起流前被ippnode调用,其中IppAlgoMeta定义在ipp_algo.h 中,为ippnode和算法插件提供非图像数据的传递通道,如当前运行的场景,算法处理后输出的人脸坐标等等,可根据实际需求进行扩展
Start:开始接口,起流时被ippnode调用
Flush:刷新数据的接口,停流之前被ippnode调用。此接口被调用时,算法插件需尽可能快地停止处理
Process: 数据处理接口,每帧数据都通过此接口输入至算法插件进行处理。inBuffer是一组输入buffer,inBufferCount是输入buffer的个数,outBuffer是输出buffer,meta是算法处理时产生的非图像数据,IppAlgoBuffer在ipp_algo.h中定义
Stop:停止处理接口,停流时被ippnode调用
下边代码中的id指的是和ippnode对应的port口id,比如inBuffer[0]的id为0,则对应的是ippnode 的第0个输入port口。需要注意的是outBuffer可以为空,此时其中一个输入buffer 被ippnode作为输出buffer传递到下个node,inBuffer至少有一个buffer不为空。输入输出buffer 由pipeline配置决定。 比如在普通预览场景无算法处理且只有一路拍照数据传递到ippnode的情况下,输入buffer只有一个,输出buffer为空,即对于算法插件输入buffer 进行了透传; 比如算法插件进行两路预览图像数据进行合并的场景,第一路buffer需要预览送显示。把第二路图像拷贝到第一路的buffer即可,此时输入buffer有两个,输出buffer为空; 比如在算法插件中进行预览数据格式转换的场景,yuv转换为RGBA,那么只有一个yuv格式的输入buffer的情况下无法完成RGBA格式buffer的输出,此时需要一个新的buffer,那么ippnode的输出port口buffer作为outBuffer传递到算法插件。也即输入buffer只有一个,输出buffer也有一个。
typedef struct IppAlgoBuffer {
void* addr;
unsigned int width;
unsigned int height;
unsigned int stride;
unsigned int size;
int id;
} IppAlgoBuffer;
##camera HDF驱动适配 ###rk3566rp camera HDF驱动编译选项添加 camera HDF驱动的配置位于drivers/peripheral/camera/hal/camera.gni中,内容如下:
if (defined(ohos_lite)) {
import("//build/lite/config/component/lite_component.gni")
import("//device/board/hisilicon/hispark_taurus/device.gni")
} else {
import("//build/ohos.gni")
import("//vendor/$product_company/$product_name/product.gni")
}
camera_path = "//drivers/peripheral/camera/hal"
current_path = "."
enable_camera_device_utest = false
use_hitrace = false
if (use_hitrace) {
defines += [ "HITRACE_LOG_ENABLED" ]
}
if (defined(ohos_lite)) {
defines += [ "CAMERA_BUILT_ON_OHOS_LITE" ]
}
根据编译配置可以找到对应的vendor/kaihong/khdvk_3566b/product.gni,从中获取到实际的文件是device/board/kaihong/khdvk_3566b/device.gni,后面修改入口基于这里
soc_company = "rockchip"
soc_name = "rk3566"
import("//device/soc/${soc_company}/${soc_name}/soc.gni")
import("//build/ohos.gni")
if (!defined(defines)) {
defines = []
}
product_config_path = "//vendor/${product_company}/${device_name}"
board_camera_path = "//device/board/${product_company}/khdvk_3566b/camera"
camera_product_name_path = "//vendor/${product_company}/${device_name}"
camera_device_name_path = "//device/board/${product_company}/khdvk_3566b"
is_support_v4l2 = true
if (is_support_v4l2) {
is_support_mpi = false
defines += [ "SUPPORT_V4L2" ]
chipset_build_deps = "$camera_device_name_path/camera:chipset_build"
camera_device_manager_deps = "$camera_device_name_path/camera/device_manager:camera_device_manager"
camera_pipeline_core_deps = "$camera_device_name_path/camera/pipeline_core:camera_pipeline_core"
}
最终这里的配置文件里的参数将被drivers/peripheral/camera/hal/BUILD.gn使用。 ###HCS配置文件介绍 camera的配置文件位于vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/
目录结构如下:
├── hdi_impl
│ ├── camera_host_config.hcs
└── pipeline_core
├── config.hcs
├── ipp_algo_config.hcs
└── params.hcs
Camera所有配置文件使用系统支持的HCS类型的配置文件,HCS类型的配置文件,在编译时,会转成HCB文件,最终烧录到开发板里的配置文件即为HCB格式,代码中通过HCS解析接口解析HCB文件,获取配置文件中的信息。
ohos_prebuilt_etc("camera_host_config.hcb") {
deps = [ ":build_camera_host_config" ]
hcs_outputs = get_target_outputs(":build_camera_host_config")
source = hcs_outputs[0]
relative_install_dir = "hdfconfig"
install_images = [ chipset_base_dir ]
subsystem_name = "hdf"
part_name = "camera_device_driver"
}
camera_host_config.hcs:配置当前camera支持的能力集,物理/逻辑Camera配置、能力配置,此处的物理/逻辑Camera配置,需要在hal内部使用,逻辑Camera及能力配置需要上报给上层,这里需要根据设备实际支持的属性进行相应的修改。 这里的键值对参考文件drivers/peripheral/camera/hal/hdi_impl/include/camera_host/metadata_enum_map.h
ability_01 :: ability {
logicCameraId = "lcam001";
physicsCameraIds = [
"CAMERA_FIRST",
"CAMERA_SECOND"
];
metadata {
aeAvailableAntiBandingModes = [
"OHOS_CAMERA_AE_ANTIBANDING_MODE_OFF"
];
aeAvailableModes = ["OHOS_CAMERA_AE_MODE_OFF"];
availableFpsRange = [30, 30];
cameraPosition = "OHOS_CAMERA_POSITION_FRONT";
cameraType = "OHOS_CAMERA_TYPE_WIDE_ANGLE";
cameraConnectionType ="OHOS_CAMERA_CONNECTION_TYPE_BUILTIN";
faceDetectMaxNum = "10";
aeCompensationRange = [0, 0];
aeCompensationSteps = [0, 0];
availableAwbModes = [
"OHOS_CAMERA_AWB_MODE_OFF"
];
...
}
vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/pipeline_core/config.hcs为pipeline的连接方式,按场景划分每一路流由哪些Node组成,其连接方式是怎样的。
normal_preview :: pipeline_spec {
name = "normal_preview";
v4l2_source :: node_spec {
name = "v4l2_source#0";
status = "new";
out_port_0 :: port_spec {
name = "out0";
peerPortName = "in0";
peerPortNodeName = "sink#0";
direction = 1;
width = 0;
height = 0;
format = 0;
}
}
sink :: node_spec {
name = "sink#0";
status = "new";
streamType = "preview";
in_port_0 :: port_spec {
name = "in0";
peerPortName = "out0";
peerPortNodeName = "v4l2_source#0";
direction = 0;
}
}
}
上面为preview场景的示例,normal_preview为该场景的名称,source和sink为Node,source为数据数据源端,sink为末端,source为第一个node,node的名称是source#0,status、in/out_port分别为Node状态及输入/输出口的配置。
以in_port_0为例,name = “in0”代表它的输入为“port0”,它的对端为source node的port口out0口,direction为它的源Node和对端Node是否为直连方式。如新添加芯片产品,必须按实际连接方式配置此文件。
新增功能node时需继承NodeBase类,且在cpp文件中注册该node。具体可参考//drivers/peripheral/camera/hal/pipeline_core/nodes/src下已经实现的node。
vendor/kaihong/khdvk_3566b/hdf_config/uhdf/camera/pipeline_core/param.hcs为场景、流类型名及其id定义,pipeline内部是以流id区分流类型的,所以此处需要添加定义。
root {
module = "";
template stream_info {
id = 0;
name = "";
}
template scene_info {
id = 0;
name = "";
}
priview :: stream_info {
id = 0;
name = "preview";
}
video :: stream_info {
id = 1;
name = "video";
}
snapshot :: stream_info {
id = 2;
name = "snapshot";
}
normal :: scene_info {
id = 0;
name = "normal";
}
dual :: scene_info {
id = 1;
name = "dual";
}
}
##适配过程中遇到的问题 ###camera启动时无法出图排查方向
首先排查camera sensor有没有正常的上下电,初始化序列是否正确。 如果上述都正常,需要到HDF层面,看看设备配置是否正确,具体操作如下: 在ohos系统的上电启动过程中,camera host 服务进程调用InitSensors() -->SensorController::Init()-->HosV4L2Dev::Init()->HosFileFormat::V4L2MatchDevice()既ohos在初始化过程中就会去匹配camera实例与linux 驱动系统中的camera硬件,如果匹配则记录存下cameraId与/dev/videox的关系;所以在camera drive中一般需要修改的地方就是camera hardware的name与linux驱动的/dev/videox关系; 代码如下: cameraIDs向量组内是hdf支持的所以camera 的名称(string); ./drivers/peripheral/camera/hal/adapter/platform/v4l2/src/device_manager/include/v4l2_device_manager.h定义的cameraId
std::vector<HardwareConfiguration> hardware = {
{CAMERA_FIRST, DM_M_SENSOR, DM_C_SENSOR, (std::string) "bm2835 mmal"},
{CAMERA_FIRST, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
{CAMERA_FIRST, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"},
{CAMERA_SECOND, DM_M_SENSOR, DM_C_SENSOR, (std::string) "Imx600"},
{CAMERA_SECOND, DM_M_ISP, DM_C_ISP, (std::string) "isp"},
{CAMERA_SECOND, DM_M_FLASH, DM_C_FLASH, (std::string) "flash"}
};
每个名称应与/dev/videox其中任意一个的capabilities中的driver name是一样的,只有一样的名称才能将hdf的camera name与/dev/videox绑定;
void HosFileFormat::V4L2MatchDevice(std::vector<std::string>& cameraIDs)
{
struct stat st = {};
char devName[16] = {0};
std::string name = DEVICENAMEX;
int fd = 0;
int rc = 0;
for (auto &it : cameraIDs) {
for (int i = 0; i < MAXVIDEODEVICE; ++i) {
if ((sprintf_s(devName, sizeof(devName), "%s%d", name.c_str(), i)) < 0) {
CAMERA_LOGE("%s: sprintf devName failed", __func__);
}
...
rc = V4L2GetCapability(fd, devName, it);
if (rc == RC_ERROR) {
close(fd);
continue;
}
...
}
}
}
注意“(cameraId != std::string((char*)cap.driver)”比较cap中的名称是否相同。
RetCode HosFileFormat::V4L2GetCapability(int fd, const std::string& devName, std::string& cameraId)
{
struct v4l2_capability cap = {};
int rc = ioctl(fd, VIDIOC_QUERYCAP, &cap);
if (rc < 0) {
return RC_ERROR;
}
if (!(cap.capabilities & V4L2_CAP_STREAMING)) {
return RC_ERROR;
}
if (!((cap.capabilities & V4L2_CAP_VIDEO_CAPTURE_MPLANE) || (cap.capabilities & V4L2_CAP_VIDEO_CAPTURE))) {
return RC_ERROR;
}
if (cameraId != std::string((char*)cap.driver)) {
return RC_ERROR;
}
std::lock_guard<std::mutex> l(HosV4L2Dev::deviceFdLock_);
HosV4L2Dev::deviceMatch.insert(std::make_pair(std::string((char*)cap.driver), devName));
...
return RC_OK;
}
BT
HCI接口
蓝牙整体硬件架构上分为主机(计算机或MCU)和主机控制器(BT蓝牙模组)两部分;通信遵循主机控制器接口(HCI),通常使用串口进行通信,如下所示:
HCI定义了如何交换命令,事件,异步和同步数据包。异步数据包(ACL)用于数据传输,而同步数据包(SCO)用于带有耳机和免提配置文件的语音。
硬件连接
从RK3566芯片描述中看,该芯片并不没有集成WIFI/蓝牙功能,都需要外接蓝牙芯片才能支持蓝牙功能,这也符合上述逻辑架构。串口使用普通带流控串口即可,一般在原理图中可以看到对应的串口引脚:
可以看到使用的是UART1 M0,在设备树里就要使能对应的串口和pinctrl,同时还可以看到有几个管脚分别做电源和休眠控制。
wireless_bluetooth: wireless-bluetooth {
compatible = "bluetooth-platdata";
clocks = <&rk817 1>;
clock-names = "ext_clock";
//wifi-bt-power-toggle;
uart_rts_gpios = <&gpio2 RK_PB5 GPIO_ACTIVE_LOW>;
pinctrl-names = "default", "rts_gpio";
pinctrl-0 = <&uart1m0_rtsn &bt_host_wake_gpio &bt_poweren &bt_host_wake_irq>;
pinctrl-1 = <&uart1_gpios>;
BT,reset_gpio = <&gpio0 RK_PC1 GPIO_ACTIVE_HIGH>;
BT,wake_gpio = <&gpio0 RK_PB6 GPIO_ACTIVE_HIGH>;
BT,wake_host_irq = <&gpio0 RK_PB5 GPIO_ACTIVE_HIGH>;
status = "okay";
};
wireless-bluetooth {
uart1_gpios: uart1-gpios {
rockchip,pins = <2 RK_PB5 RK_FUNC_GPIO &pcfg_pull_none>;
};
bt_host_wake_irq: bt-host-wake-irq {
rockchip,pins = <0 RK_PB5 RK_FUNC_GPIO &pcfg_pull_down>;
};
bt_host_wake_gpio: bt-host-wake-gpio {
rockchip,pins = <0 RK_PB6 RK_FUNC_GPIO &pcfg_pull_down>;
};
bt_poweren: bt-poweren {
rockchip,pins = <0 RK_PC1 RK_FUNC_GPIO &pcfg_pull_down>;
};
};
&uart1 {
status = "okay";
pinctrl-names = "default";
pinctrl-0 = <&uart1m0_xfer &uart1m0_ctsn>;
};
蓝牙VENDORLIB适配
vendorlib是什么
vendorlib部署在主机侧,可以认为是主机侧对蓝牙芯片驱动层,屏蔽不同蓝牙芯片的技术细节。从代码层面解读,其主要功能有两个:
1、为协议栈提供蓝牙芯片之间的通道(串口的文件描述符)
2、提供特定芯片的具体控制方法
代码层面解读vendorlib
bt_vendor_lib.h 路径:
foundation/communication/bluetooth/services/bluetooth_standard/hardware/include
该文件定义了协议栈和vendor_lib交互接口,分为两组:
1、 vendorlib实现,协议栈调用
typedef struct {
/**
\* Set to sizeof(bt_vndor_interface_t)
*/
size_t size;
/**
\* Caller will open the interface and pass in the callback routines
\* to the implemenation of this interface.
*/
int (*init)(const bt_vendor_callbacks_t* p_cb, unsigned char* local_bdaddr);
/**
\* Vendor specific operations
*/
int (*op)(bt_opcode_t opcode, void* param);
/**
\* Closes the interface
*/
void (*close)(void);
} bt_vendor_interface_t;
协议栈启动时的基本流程如下:
1.1、协议栈动态打开libbt_vendor.z.so,并调用init函数,初始化vendorlib
1.2、协议栈调用op函数,分别调用BT_OP_POWER_ON、BT_OP_HCI_CHANNEL_OPEN、BT_OP_INIT三个opcode;原则上BT_OP_INIT成功后说明芯片初始化完成。
2、协议栈实现,vendorlib调用(回调函数)
typedef struct {
/**
\* set to sizeof(bt_vendor_callbacks_t)
*/
size_t size;
/* notifies caller result of init request */
init_callback init_cb;
/* buffer allocation request */
malloc_callback alloc;
/* buffer free request */
free_callback dealloc;
/* hci command packet transmit request */
cmd_xmit_callback xmit_cb;
} bt_vendor_callbacks_t;
init_cb在BT_OP_INIT完成后调用
alloc/dealloc用于发送HCI消息时申请/释放消息控件
xmit_cb发送HCI Commands
vendor_lib实现的几个重要函数
1、 init函数
static int init(const bt_vendor_callbacks_t *p_cb, unsigned char *local_bdaddr)
{
/* * ... */
userial_vendor_init();
upio_init();
vnd_load_conf(VENDOR_LIB_CONF_FILE);
/* store reference to user callbacks */
bt_vendor_cbacks = (bt_vendor_callbacks_t *)p_cb;
/* This is handed over from the stack */
return memcpy_s(vnd_local_bd_addr, BD_ADDR_LEN, local_bdaddr, BD_ADDR_LEN);
}
vendorlib被调用的第一个函数,vendorlib保存好协议栈的callback和mac地址即可。
2、 BT_OP_POWER_ON对应处理
观名知意,这个操作理论上需要拉高电源管脚电平;该函数中使用rfill设备来处理,并没有直接调用驱动拉高电平
int upio_set_bluetooth_power(int on)
{
int sz;
int fd = -1;
int ret = -1;
char buffer = '0';
switch (on) {
case UPIO_BT_POWER_OFF:
buffer = '0';
break;
case UPIO_BT_POWER_ON:
buffer = '1';
break;
default:
return 0;
}
/* check if we have rfkill interface */
if (is_rfkill_disabled()) {
return 0;
}
if (rfkill_id == -1) {
if (init_rfkill()) {
return ret;
}
}
fd = open(rfkill_state_path, O_WRONLY);
if (fd < 0) {
return ret;
}
sz = write(fd, &buffer, 1);
/* ... */
return ret;
}
3、BT_OP_HCI_CHANNEL_OPEN对应处理
case BT_OP_HCI_CHANNEL_OPEN: { // BT_VND_OP_USERIAL_OPEN
int(*fd_array)[] = (int(*)[])param;
int fd, idx;
fd = userial_vendor_open((tUSERIAL_CFG *)&userial_init_cfg);
if (fd != -1) {
for (idx = 0; idx < HCI_MAX_CHANNEL; idx++)
(*fd_array)[idx] = fd;
retval = 1;
}
/* retval contains numbers of open fd of HCI channels */
break;
userial_vendor_open函数打开串口设备(UART)得到文件描述符(fd),通过op的参数param返回该fd
该串口设备在系统中的名字在vendor下的bluetooth相关目录中的bt_vendor_brcm.h文件定义了,本次开发板上设备为/dev/ttyS1
4、BT_OP_INIT对应处理
该操作码要求对蓝牙芯片进行初始化,具体要进行的处理和蓝牙芯片强相关。以本次调测的AP6xxx芯片为例,初始化过程中主要是下发蓝牙固件。
初始化结束后,必须调用init_cb回调函数(参见bt_vendor_callbacks_t)通知协议栈初始化结果,否则会阻塞协议栈线程导致蓝牙相关功能无法正常使用。协议栈的具体处理如下:
协议栈调用BT_OP_INIT后会等待信号量,该信号量由init_cb函数置位
static int HciInitHal()
{
int result = BT_NO_ERROR;
g_waitHdiInit = SemaphoreCreate(0);
int ret = g_hdiLib->hdiInit(&g_hdiCallbacks);
if (ret == SUCCESS) {
SemaphoreWait(g_waitHdiInit);
}
}
vendorlib移植问题
1、 vendorlib的so命名
vendorlib必须是libbt_vendor.z.so;因为协议栈打开动态链接库就是这个名字
2、 固件问题
开发时一定要关注芯片固件,有些蓝牙芯片可能无需升级固件,有些则必须升级固件, 不同型号的蓝牙对应固件也不一样;本次AP6xxx适配过程中最开始没有下发固件,导致蓝牙接收信号很差。固件下发时需要注意如下两点:
2.1、对于AP6xxx芯片,因为蓝牙芯片内并没有类似flash存储,要求芯片上下电后必须重新下发,固件要通过BUILD.gn把固件标记为prebuilt_etc
ohos_prebuilt_etc("BCM43430A1.hcd") {
source = "//vendor/kaihong/RK3566-xx/bluetooth/BCM43430A1.hcd"
install_images = [ vendor_base_dir ]
relative_install_dir = "firmware"
part_name = "kaihong_products"
install_enable = true
}
然后在device/kaihong/build中把固件打包在镜像中
"//vendor/kaihong/RK3566-xx/bluetooth:libbt_vendor",
"//vendor/kaihong/RK3566-xx/bluetooth:BCM43430A1.hcd",
2.2、按照芯片本身的要求处理,最好能找到厂商的参考代码;以Broadcom系列芯片为例,其固件下发过程比较复杂,通过一个状态机驱动;共如下9个状态
/ Hardware Configuration State */
enum {
HW_CFG_START = 1,
HW_CFG_SET_UART_CLOCK,
HW_CFG_SET_UART_BAUD_1,
HW_CFG_READ_LOCAL_NAME,
HW_CFG_DL_MINIDRIVER,
HW_CFG_DL_FW_PATCH,
HW_CFG_SET_UART_BAUD_2,
HW_CFG_SET_BD_ADDR,
HW_CFG_READ_BD_ADDR
};
在收到BT_OP_INIT后初始化状态机,然后发送HCI_REST命令,切换状态为HW_CFG_START;
void hw_config_start(void)
{
HC_BT_HDR *p_buf = NULL;
uint8_t *p;
hw_cfg_cb.state = 0;
hw_cfg_cb.fw_fd = -1;
hw_cfg_cb.f_set_baud_2 = FALSE;
if (bt_vendor_cbacks) {
p_buf = (HC_BT_HDR *)bt_vendor_cbacks->alloc(BT_HC_HDR_SIZE +
HCI_CMD_PREAMBLE_SIZE);
}
if (p_buf) {
p_buf->event = MSG_STACK_TO_HC_HCI_CMD;
p_buf->offset = 0;
p_buf->layer_specific = 0;
p_buf->len = HCI_CMD_PREAMBLE_SIZE;
p = (uint8_t *)(p_buf + 1);
UINT16_TO_STREAM(p, HCI_RESET);
*p = 0;
hw_cfg_cb.state = HW_CFG_START;
bt_vendor_cbacks->xmit_cb(HCI_RESET, p_buf);
} else {
if (bt_vendor_cbacks) {
HILOGE("vendor lib fw conf aborted [no buffer]");
bt_vendor_cbacks->init_cb(BTC_OP_RESULT_FAIL);
}
}
}
收到芯片返回的HCI_RESET完成事件后,继续切换到下一个状态机并发送下一个COMMAND,一直到状态机完成固件下发。
详细实现请参见hw_config_cback函数。
3、 关注系统间接口差异
不同系统的接口可能有一些细微差异,需要重点关注;对比安卓和OHOS的接口,vendorlib调用xmit_cb发送HCI命令的函数定义略有差异
安卓:
/* define callback of the cmd_xmit_cb
*
The callback function which HCI lib will call with the return of command
complete packet. Vendor lib is responsible for releasing the buffer passed
in at the p_mem parameter by calling dealloc callout function.
*/
typedef void (*tINT_CMD_CBACK)(void* p_mem);
typedef uint8_t (*cmd_xmit_cb)(uint16_t opcode, void* p_buf, tINT_CMD_CBACK p_cback);
OHOS:
/**
hci command packet transmit callback
Vendor lib calls cmd_xmit_cb function in order to send a HCI Command
packet to BT Controller.
*
The opcode parameter gives the HCI OpCode (combination of OGF and OCF) of
HCI Command packet. For example, opcode = 0x0c03 for the HCI_RESET command
packet. */
typedef uint8_t (*cmd_xmit_callback)(uint16_t opcode, void* p_buf);
也就是说vendorlib中发送命令后,安卓会直接调用callback通知芯片返回的消息,OHOS则是通过BT_OP_EVENT_CALLBACK操作码(参见bt_opcode_t定义)通知芯片返回的消息;vendorlib需要解析报文中的消息码确认芯片是处理的哪个消息,然后调用对应的处理函数。
void hw_process_event(HC_BT_HDR *p_buf)
{
uint16_t opcode;
uint8_t *p = (uint8_t *)(p_buf + 1) + HCI_EVT_CMD_CMPL_OPCODE;
STREAM_TO_UINT16(opcode, p);
switch (opcode) {
case HCI_VSC_WRITE_BD_ADDR:
\#if (USE_CONTROLLER_BDADDR == TRUE)
case HCI_READ_LOCAL_BDADDR:
\#endif
case HCI_READ_LOCAL_NAME:
case HCI_VSC_DOWNLOAD_MINIDRV:
case HCI_VSC_WRITE_FIRMWARE:
case HCI_VSC_LAUNCH_RAM:
case HCI_RESET:
case HCI_VSC_WRITE_UART_CLOCK_SETTING:
case HCI_VSC_UPDATE_BAUDRATE:
hw_config_cback(p_buf);
break;
另外,OHOS返回的是发送消息的字节数,<=0为发送失败,和安卓接口的返回值也不同
4、 btvendor日志
在vendor下的bluetooth/include相关目录里的Log.h中定义log文件的保存路径,我们代码里生成文件为/data/btvendor.log。也可以通过wireshark或其它报文分析工具可以看到Host和Controller之间的交互流程,有助于问题分析。
未完待续~~~~~
最后
有很多小伙伴不知道学习哪些鸿蒙开发技术?不知道需要重点掌握哪些鸿蒙应用开发知识点?而且学习时频繁踩坑,最终浪费大量时间。所以有一份实用的鸿蒙(HarmonyOS NEXT)资料用来跟着学习是非常有必要的。
这份鸿蒙(HarmonyOS NEXT)资料包含了鸿蒙开发必掌握的核心知识要点,内容包含了(ArkTS、ArkUI开发组件、Stage模型、多端部署、分布式应用开发、音频、视频、WebGL、OpenHarmony多媒体技术、Napi组件、OpenHarmony内核、Harmony南向开发、鸿蒙项目实战等等)鸿蒙(HarmonyOS NEXT)技术知识点。
希望这一份鸿蒙学习资料能够给大家带来帮助,有需要的小伙伴自行领取,限时开源,先到先得~无套路领取!!
获取这份完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
鸿蒙(HarmonyOS NEXT)最新学习路线
-
HarmonOS基础技能
- HarmonOS就业必备技能
- HarmonOS多媒体技术
- 鸿蒙NaPi组件进阶
- HarmonOS高级技能
- 初识HarmonOS内核
- 实战就业级设备开发
有了路线图,怎么能没有学习资料呢,小编也准备了一份联合鸿蒙官方发布笔记整理收纳的一套系统性的鸿蒙(OpenHarmony )学习手册(共计1236页)与鸿蒙(OpenHarmony )开发入门教学视频,内容包含:ArkTS、ArkUI、Web开发、应用模型、资源分类…等知识点。
获取以上完整版高清学习路线,请点击→纯血版全套鸿蒙HarmonyOS学习资料
《鸿蒙 (OpenHarmony)开发入门教学视频》
《鸿蒙生态应用开发V2.0白皮书》
《鸿蒙 (OpenHarmony)开发基础到实战手册》
OpenHarmony北向、南向开发环境搭建
《鸿蒙开发基础》
- ArkTS语言
- 安装DevEco Studio
- 运用你的第一个ArkTS应用
- ArkUI声明式UI开发
- .……
《鸿蒙开发进阶》
- Stage模型入门
- 网络管理
- 数据管理
- 电话服务
- 分布式应用开发
- 通知与窗口管理
- 多媒体技术
- 安全技能
- 任务管理
- WebGL
- 国际化开发
- 应用测试
- DFX面向未来设计
- 鸿蒙系统移植和裁剪定制
- ……
《鸿蒙进阶实战》
- ArkTS实践
- UIAbility应用
- 网络案例
- ……
获取以上完整鸿蒙HarmonyOS学习资料,请点击→纯血版全套鸿蒙HarmonyOS学习资料
总结
总的来说,华为鸿蒙不再兼容安卓,对中年程序员来说是一个挑战,也是一个机会。只有积极应对变化,不断学习和提升自己,他们才能在这个变革的时代中立于不败之地。
原文地址:https://blog.csdn.net/m0_64420071/article/details/137522079
免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!