安卓平台下的GPS架构介绍及驱动移植记录

一、前言

我的工作是关于汽车车机BSP部分。

汽车车机,其实基本和人们日常所用的手机一样,也是安卓平台的。所谓安卓,就是一层安卓服务包裹着Linux内核所形成的操作系统。

BSP组,主要工作内容就是负责soc的Linux系统部分的驱动移植、调试,及BUG解决。

从毕业到现在,工作也有大半年了。跟着前辈学习GPS模块的移植、调试,和BUG解决也有差不多两个月了。心里想着,是时候写一篇关于GPS驱动移植学习的总结和笔记了。

于是今天,我尝试着动手开始梳理这两个月来的所学所知。

二、U-blox m8l导航模块

目前汽车车机,所用的GPS都是属于高度集成的模块,所有的功能都在模块内部实现。用户只需要通过串口对数据进行读取与解析,就可以获取GPS信息。

我们公司目前所用的GPS导航模块有三种,这篇文章所要介绍,则是瑞士优北罗股份有限公司所研发制作的一款整合了运动、方向和高度传感器的NEO-M8L汽车惯性导航(ADR, Automotive Dead Reckoning)模块。该模块将陀螺仪和加速度传感器与u-blox领先的GNSS平台 - u-blox M8集成在一起,使其成为市场上性能最佳的室内/室外定位解决方案,是所有道路车辆和高精度导航应用的理想选择。

NEO-M8L模块中内置了u-blox突破性的“3D汽车惯性导航”(3D ADR)芯片技术。它利用车辆的速度信息以及模块的内置传感器,即使当卫星信号完全被遮蔽或终端设备没有安装于水平位置时,仍能提供准确的三维定位信息。此外,基于ADR技术的里程表功能还能提供正确和连续的行驶距离。

该模块能追踪所有可视的GNSS卫星,包括GPS、GLONASS、北斗和所有的SBAS系统 (欧洲的伽利略系统将在未来的固件版本中支持)。该模块目前支持两种GNSS系统的并行接收,并且能以高达每秒20次的速度输出定位信息。

三、安卓平台下的GPS架构

安卓系统,实现了一系列的架构和分层,我们BSP移植驱动,主要完成两部分工作。
- 实现Linux驱动模块,保证Linux驱动对硬件的驱动能力;
- 对接安卓hal层架构,实现hal层接口。

因为GPS属于高度集成的一体化模块,所有的功能都在模块内部实现,所以,对GPS的驱动移植,主要是为了匹配和实现安卓系统提供的HAL层接口,然后调用串口驱动的接口去读取对应串口地址的数据即可。

我们先来看一下安卓的GPS架构图。

由图可以知道,安卓的GPS架构由上到下是:app->framework->jni->hidl->hal,我们这篇文章,主要介绍从jni->hidl->hal层。

3.1、HAL层标准接口

HAL 可定义一个标准接口以供硬件供应商实现,这可让 Android 忽略较低级别的驱动程序实现。借助 HAL,您可以顺利实现相关功能,而不会影响或更改更高级别的系统。

为了保证 HAL 具有可预测的结构,每个硬件专用 HAL 接口都要具有在 hardware/libhardware/include/hardware/hardware.h 中定义的属性。这类接口可让 Android 系统以一致的方式加载 HAL 模块的正确版本。HAL 接口包含两个组件:模块和设备。

3.1.1、模块

模块代表打包的 HAL 实现,这种实现存储为共享库 (.so file)。hardware/libhardware/include/hardware/hardware.h 头文件可定义一个代表模块的结构体 (hw_module_t),其中包含模块的版本、名称和作者等元数据。Android 会根据这些元数据来找到并正确加载 HAL 模块。

另外,hw_module_t 结构体还包含指向另一个结构体 hw_module_methods_t 的指针,后面这个结构体包含指向相应模块的 open 函数的指针。此 open 函数用于与相关硬件(此 HAL 是其抽象形式)建立通信。

typedef struct hw_module_t {
    /** tag must be initialized to HARDWARE_MODULE_TAG */
    uint32_t tag;
	
	uint16_t module_api_version;
#define version_major module_api_version

    uint16_t hal_api_version;
#define version_minor hal_api_version
	
    /** Identifier of module */
    const char *id;

    /** Name of this module */
    const char *name;

    /** Author/owner/implementor of the module */
    const char *author;

    /** Modules methods */
    struct hw_module_methods_t* methods;

    /** module's dso */
    void* dso;
} hw_module_t;

typedef struct hw_module_methods_t {
    /** Open a specific device */
    int (*open)(const struct hw_module_t* module, const char* id, struct hw_device_t** device);
} hw_module_methods_t;

3.1.2、设备

设备是产品硬件的抽象表示。

设备由 hw_device_t 结构体表示。与模块类似,每类设备都定义了一个通用 hw_device_t 的详细版本,其中包含指向特定硬件功能的函数指针。

struct gps_device_t {
    struct hw_device_t common;

    const GpsInterface* (*get_gps_interface)(struct gps_device_t* dev);
};

typedef struct hw_device_t {
    /** tag must be initialized to HARDWARE_DEVICE_TAG */
    uint32_t tag;

    uint32_t version;

    /** reference to the module this device belongs to */
    struct hw_module_t* module;

    /** padding reserved for future use */
#ifdef __LP64__
    uint64_t reserved[12];
#else
    uint32_t reserved[12];
#endif

    /** Close this device */
    int (*close)(struct hw_device_t* device);
} hw_device_t;

3.2、HIDL — HIDL HAL

hidl的官方标准定义为:HIDL是HAL接口定义语言(简称 HIDL,发音为“hide-l”),是用于指定 HAL 和其用户之间的接口的一种接口描述语言 (IDL)。

安卓官方设计 HIDL 这个机制的目的,主要是想把框架(framework)与 HAL 进行隔离,使得框架部分可以直接被覆盖、更新,而不需要重新对 HAL 进行编译。

3.2.1、客户端和服务器实现

HIDL 接口具有客户端和服务器实现:

- HIDL 接口的客户端实现是指通过在该接口上调用方法来使用该接口的代码。

- 服务器实现是指 HIDL 接口的实现,它可接收来自客户端的调用并返回结果(如有必要)。

在从libhardware HAL 转换为 HIDL HAL 的过程中,HAL 实现成为服务器,而调用 HAL 的进程则成为客户端。默认实现可提供直通和 Binder 化 HAL。

上图为HAL的几个发展历程,方式2为目前我的开发环境所使用的直通方式,框架和HAL之间通过HIDL接口实现通信,硬件厂商负责服务器的实现。

3.2.2、系统服务启动gps.ublox.so

下面从系统服务方面介绍一下系统启动,获取gps.ublox.so(gps hal驱动编译生成的文件)流程。

①、首先介绍GNSS的HIDL接口,编译生成"android.hardware.gnss@1.0"接口共享库,客户端和服务器之间就是通过这些接口来工作的,服务器端需要做的就是实现该接口。

接口编译脚本如下:

hidl_interface {
    name: "android.hardware.gnss@1.0",
    root: "android.hardware",
    vndk: {
        enabled: true,
    },
    srcs: [
        "types.hal",
        "IAGnss.hal",
        "IAGnssCallback.hal",
        "IAGnssRil.hal",
        "IAGnssRilCallback.hal",
        "IGnss.hal",
        "IGnssBatching.hal",
        "IGnssBatchingCallback.hal",
        "IGnssCallback.hal",
        "IGnssConfiguration.hal",
        "IGnssDebug.hal",
        "IGnssGeofenceCallback.hal",
        "IGnssGeofencing.hal",
        "IGnssMeasurement.hal",
        "IGnssMeasurementCallback.hal",
        "IGnssNavigationMessage.hal",
        "IGnssNavigationMessageCallback.hal",
        "IGnssNi.hal",
        "IGnssNiCallback.hal",
        "IGnssXtra.hal",
        "IGnssXtraCallback.hal",
    ],
    interfaces: [
        "android.hidl.base@1.0",
    ],
    types: [
        "GnssConstellationType",
        "GnssLocation",
        "GnssLocationFlags",
        "GnssMax",
    ],
    gen_java: true,
    gen_java_constants: true,
}

②、rc文件启动服务android.hardware.gnss@1.0-service

android.hardware.gnss@1.0-service.rc启动服务android.hardware.gnss@1.0-service

android.hardware.gnss@1.0-service.rc文件内容如下:

service vendor.gnss_service /vendor/bin/hw/android.hardware.gnss@1.0-service
class hal
user gps
group system gps radio

android.hardware.gnss@1.0-service服务由Android.bp编译得到,其次还包含了HIDL接口"android.hardware.gnss@1.0",Android.bp文件部分内容如下:

cc_binary {
    relative_install_path: "hw",
    vendor: true,
    name: "android.hardware.gnss@1.0-service",
    defaults: ["hidl_defaults"],
    init_rc: ["android.hardware.gnss@1.0-service.rc"],
    srcs: ["service.cpp"],
    shared_libs: [
        "liblog",
        "libcutils",
        "libdl",
        "libbase",
        "libutils",
        "libhardware",
        "libbinder",
        "libhidlbase",
        "libhidltransport",
        "android.hardware.gnss@1.0",
    ],
}

我们看到service.cpp,它将对提供的-impl 库执行dlopen() 操作,并将其作为 Binder 化服务提供,service.cpp代码如下:

#define LOG_TAG "android.hardware.gnss@1.0-service"
#include <android/hardware/gnss/1.0/IGnss.h>
#include <hidl/LegacySupport.h>
#include <binder/ProcessState.h>

using android::hardware::gnss::V1_0::IGnss;
using android::hardware::defaultPassthroughServiceImplementation;

int main() 
{
    // The GNSS HAL may communicate to other vendor components via
    // /dev/vndbinder
    android::ProcessState::initWithDriver("/dev/vndbinder");
    return defaultPassthroughServiceImplementation<IGnss>();
}

③、"android.hardware.gnss@1.0-impl"是接口的具体实现。

-impl 库也由Android.bp编译而成,其部分内容如下:

cc_library_shared {
    name: "android.hardware.gnss@1.0-impl",
    defaults: ["hidl_defaults"],
    vendor: true,
    relative_install_path: "hw",
    srcs: [
        "ThreadCreationWrapper.cpp",
        "AGnss.cpp",
        "AGnssRil.cpp",
        "Gnss.cpp",
        "GnssBatching.cpp",
        "GnssDebug.cpp",
        "GnssGeofencing.cpp",
        "GnssMeasurement.cpp",
        "GnssNavigationMessage.cpp",
        "GnssNi.cpp",
        "GnssXtra.cpp",
        "GnssConfiguration.cpp",
        "GnssUtils.cpp",
    ],
    shared_libs: [
        "liblog",
        "libhidlbase",
        "libhidltransport",
        "libutils",
        "android.hardware.gnss@1.0",
        "libhardware",
    ],
}

④、HIDL_FETCH_IModuleName 函数

为了让 HAL 在直通模式下运行,Gnss.cpp 必须具有 HIDL_FETCH_IModuleName 函数,函数内容如下:

IGnss* HIDL_FETCH_IGnss(const char* /* hal */) {
    hw_module_t* module;
    IGnss* iface = nullptr;

    int err = hw_get_module(GPS_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
    if (err == 0) {
        hw_device_t* device;
        err = module->methods->open(module, GPS_HARDWARE_MODULE_ID, &device);
        if (err == 0) {
            iface = new Gnss(reinterpret_cast<gps_device_t*>(device));
        } else {
            ALOGE("gnssDevice open %s failed: %d", GPS_HARDWARE_MODULE_ID, err);
        }
    } else {
        ALOGE("gnss hw_get_module %s failed: %d", GPS_HARDWARE_MODULE_ID, err);
    }

    return iface;
}

这个函数会加载gps.ublox.so库。

3.3、JNI

jni是framework与hidl hal之间的一层,为java语言实现的framework调用c++语言实现的hidl hal代码提供接口。

四、源码分析

4.1、获取GPS驱动接口

4.1.1、hidl层获取hal层接口

hidl文件位置android/hardware/interface/gnss/1.0/default/Gnss.cpp。

hal层文件位置android/hardware/u-blox/gps/。

由3.2内容,可以知道,系统服务起来之后,hidl层最开始运作起来的地方就是HIDL_FETCH_IModuleName函数了,通过名字,我们可以知道,这个函数的意思是获取模块名,换句话来说,也就是获取3.1提到过的“hw_module_t”和“hw_device_t”这两个结构体。

我们先看到这个HIDL_FETCH_IModuleName函数第5行,这里调用了hw_get_module函数,这个函数会根据GPS_HARDWARE_MODULE_ID去找到对应的“hw_module_t”,hal驱动层“hw_module_t”如下:

hw_module_t HAL_MODULE_INFO_SYM = {
  .tag = HARDWARE_MODULE_TAG,
  .version_major = 2,
  .version_minor = 0,
  .id = GPS_HARDWARE_MODULE_ID,
  .name = "u-blox GPS/GNSS library",
  .author = "u-blox AG - Switzerland",
  .methods = &CGpsIf::s_hwModuleMethods,
  .dso = NULL,
  .reserved = {0}
};

可以看到.id和hw_get_module第一个参数相同,也是GPS_HARDWARE_MODULE_ID这个宏。

继续回到HIDL_FETCH_IModuleName函数,看到第8行,这里调用了module->methods->open函数,在函数内部,找到对应device之后,它会对第三个参数进行了填充,从而让HIDL_FETCH_IModuleName函数获取到对应的“hw_device_t”,open函数代码如下:

struct hw_module_methods_t CGpsIf::s_hwModuleMethods = {
    .open = CGpsIf::hwModuleOpen // open a specific device
};

int CGpsIf::hwModuleOpen(const struct hw_module_t *module, char const *name, struct hw_device_t **device)
{
    ((void)(name));
     struct gps_device_t *dev = new (std::nothrow) gps_device_t{};

     if (!dev)
         return 1;

     dev->common.tag = HARDWARE_DEVICE_TAG;
     dev->common.version = 0;
     dev->common.module = const_cast<struct hw_module_t *>(module);
     dev->common.close = CGpsIf::hwModuleClose;
     dev->get_gps_interface = CGpsIf::getIf;
     *device = (struct hw_device_t *)(void *)dev;

     return 0;
}

我们再看到HIDL_FETCH_IModuleName函数第10行,在这里,new了一个名叫Gnss的类对象出来,Gnss类对象就是hidl hal层的具体类。我们知道,一个类对象被new出来之后,它的构造函数就会被调用,构造函数内容如下:

Gnss::Gnss(gps_device_t* gnssDevice) : mDeathRecipient(new GnssHidlDeathRecipient(this)) {
    /* Error out if an instance of the interface already exists. */
    LOG_ALWAYS_FATAL_IF(sInterfaceExists);
    sInterfaceExists = true;

    if (gnssDevice == nullptr) {
        ALOGE("%s: Invalid device_t handle", __func__);
        return;
    }

    mGnssIface = gnssDevice->get_gps_interface(gnssDevice);
}

在Gnss的构造函数第11行,它调用了gnssDevice->get_gps_interface函数,在上面hwModuleOpen函数第17行,可以看到有对gnssDevice->get_gps_interface赋值,那我们先来看看这个函数代码内容:

const GpsInterface CGpsIf::s_interface = {
    IF_ANDROID23(.size = sizeof(GpsInterface), ).init = CGpsIf::init,
    .start = CGpsIf::start,
    .stop = CGpsIf::stop,
#if (PLATFORM_SDK_VERSION <= 8 /* <=2.2 */)
    .set_fix_frequency = CGpsIf::setFixFrequency,
#endif
    .cleanup = CGpsIf::cleanup,
    .inject_time = CGpsIf::injectTime,
    IF_ANDROID23(.inject_location = CGpsIf::injectLocation, ).delete_aiding_data =
CGpsIf::deleteAidingData,
    .set_position_mode = CGpsIf::setPositionMode,
    .get_extension = CGpsIf::getExtension,
};

const GpsInterface *CGpsIf::getIf(struct gps_device_t * /*dev*/) 
{
    return &s_interface; 
}

gnssDevice->get_gps_interface实际上指向的,就是上面的getIf,而在getIf中,直接返回了一个名为s_interface的结构体,其实,这个就是gps hal层的接口,接下来hidl的所有操作都是通过调用这个接口的函数来进行实现。

4.1.2、jni层获取hidl接口

jni层文件位置:android/frameworks/base/services/core/jni/com_android_server_location_GnssLocationProvider.cpp。

jni若想使用hidl的各种函数,也需要获取到hidl提供出来的接口。jni获取hidl接口函数如下:

static void android_location_GnssLocationProvider_class_init_native(JNIEnv* env, jclass clazz) {
    gnssHal_V1_1 = IGnss_V1_1::getService();
    if (gnssHal_V1_1 == nullptr) {
        ALOGD("gnssHal 1.1 was null, trying 1.0");
        gnssHal = IGnss_V1_0::getService();
    } else {
        gnssHal = gnssHal_V1_1;
		ALOGD("gnssHal 1.1 is Ok!");
    }
}

4.2、设置回调函数

在安卓GPS架构中,非常重要的,无疑不是回调操作了。所有GPS的上报,都是通过回调函数完成的。

它的gps数据回调流程是:hal->hidl->jni,可以看到,所谓回调,其实就是由下而上。

不过,设置回调结构体却是由上而下:jni->hidl->hal。

4.2.1、安卓gps标准回调结构体

gps回调结构体在android/hardware/libhardware/inlcude/gps.h中定义,以下代码为非标准结构体:

typedef struct {
    /** set to sizeof(GpsCallbacks) */
    size_t      size;
    gps_location_callback location_cb;
    gps_status_callback status_cb;
    gps_sv_status_callback sv_status_cb;
    gps_nmea_callback nmea_cb;
    gps_set_capabilities set_capabilities_cb;
    gps_acquire_wakelock acquire_wakelock_cb;
    gps_release_wakelock release_wakelock_cb;
    gps_create_thread create_thread_cb;
    gps_request_utc_time request_utc_time_cb;
	gnss_gyr_callback gyr_cb;
	gnss_acc_callback acc_cb;
    gnss_set_system_info set_system_info_cb;
    gnss_sv_status_callback gnss_sv_status_cb;
} GpsCallbacks;

可以看到,代码第13、14行,这是我们自己添加的回调,用于回调加速度、陀螺仪的信息,在标准结构体中没有这两行。

4.2.2、填充回调函数指针

因为gps hal层驱动上报信息需要用到回调结构体,而这个结构体则需要hidl层定义实现这个结构体,然后再传递给hal层,让hal层得到这个结构体指针。

而hidl回调到jni层,也需要jni定义并传递一个类似的回调结构体。

所以,接下来,我们就来看一下,代码中是如何填充回调函数指针的。

①、hidl hal

static GpsCallbacks sGnssCb;

GpsCallbacks Gnss::sGnssCb = {
    .size = sizeof(GpsCallbacks),
    .location_cb = locationCb,
    .status_cb = statusCb,
    .sv_status_cb = gpsSvStatusCb,
    .nmea_cb = nmeaCb,
    .set_capabilities_cb = setCapabilitiesCb,
    .acquire_wakelock_cb = acquireWakelockCb,
    .release_wakelock_cb = releaseWakelockCb,
    .create_thread_cb = createThreadCb,
    .request_utc_time_cb = requestUtcTimeCb,
    .set_system_info_cb = setSystemInfoCb,
    .gnss_sv_status_cb = gnssSvStatusCb,
    .gyr_cb = gnssGyrCb,
    .acc_cb = gnssAccCb,
};

Return<bool> Gnss::setCallback(const sp<IGnssCallback>& callback)
{
    ……
    sGnssCbIface = callback;
    ……
    return (mGnssIface->init(&sGnssCb) == 0);
}

可以看到第25行调用了mGnssIface->init函数,mGnssIface就是gps hal层的接口,忘记了的可以回到4.1看一下。

我们继续看mGnssIface->init函数是如何实现的,代码内容如下:

static CGpsIf s_myIf;
……

int CGpsIf::init(GpsCallbacks *callbacks)
{
    ……
    initializeGpsCallbacks(*callbacks);
    ……
}

void CGpsIf::initializeGpsCallbacks(GpsCallbacks &callbacks)
{
    int res = 0;
    char buf[92];
    if (callbacks.size == sizeof(GpsCallbacks))
    {
        s_myIf.m_callbacks = callbacks;
        res = property_get("persist.vendor.gps.debug", buf, 0);
        if (res && atoi(buf)){
            s_myIf.UBLOX_UNNECESSARY_LOG_FLAG = true;
            UBX_LOG(LCAT_WARNING, "UBLOX_UNNECESSARY_LOG_FLAG is ture");
        } else {
            s_myIf.UBLOX_UNNECESSARY_LOG_FLAG = false;
            UBX_LOG(LCAT_WARNING, "UBLOX_UNNECESSARY_LOG_FLAG is false");
        }
    } else {
        UBX_LOG(LCAT_WARNING, "callback size %zd != %zd", callbacks.size, sizeof(GpsCallbacks));
    }
}

第4行,init函数调用initializeGpsCallbacks函数,而在第14行,initializeGpsCallbacks函数则对s_myIf.m_callbacks(s_myIf就是gps module的类对象)进行了赋值,也就是填充回调函数结构体指针。

到这里,hal到hidl层的回调指针就填充完毕了,接下来看看hidl层到jni层的回调指针。

②、jni

struct GnssCallback : public IGnssCallback {
    Return<void> gnssLocationCb(const GnssLocation& location) override;
    Return<void> gnssStatusCb(const IGnssCallback::GnssStatusValue status) override;
    Return<void> gnssSvStatusCb(const IGnssCallback::GnssSvStatus& svStatus) override;
    Return<void> gnssNmeaCb(int64_t timestamp, const android::hardware::hidl_string& nmea) override;
    Return<void> gnssSetCapabilitesCb(uint32_t capabilities) override;
    Return<void> gnssAcquireWakelockCb() override;
    Return<void> gnssReleaseWakelockCb() override;
    Return<void> gnssRequestTimeCb() override;
    Return<void> gnssRequestLocationCb(const bool independentFromGnss) override;
    Return<void> gnssSetSystemInfoCb(const IGnssCallback::GnssSystemInfo& info) override;
	//fce added for acc/gry
	Return<void> gnss_gyr_callback(const IGnssCallback::AutoNaviGyr& gry) override;
	Return<void> gnss_acc_callback(const IGnssCallback::AutoNaviAcc& acc) override;
	// New in 1.1
    Return<void> gnssNameCb(const android::hardware::hidl_string& name) override;

    // TODO(b/73306084): Reconsider allocation cost vs threadsafety on these statics
    static const char* sNmeaString;
    static size_t sNmeaStringLength;
};

static jboolean android_location_GnssLocationProvider_init(JNIEnv* env, jobject obj)
{
    ……
    sp<IGnssCallback> gnssCbIface = new GnssCallback();

    Return<bool> result = false;
    if (gnssHal_V1_1 != nullptr) {
        result = gnssHal_V1_1->setCallback_1_1(gnssCbIface);
    } else {
        result = gnssHal->setCallback(gnssCbIface);
    }
    ……
}

这是jni层中的代码,可以看到上面代码第32行调用了hidl接口的setCallback函数,其中传递的参数就是jni层所定义的回调结构体,我们回到hidl层中的setCallback函数:

sp<IGnssCallback> Gnss::sGnssCbIface = nullptr;
……

Return<bool> Gnss::setCallback(const sp<IGnssCallback>& callback)
{
    ……
    sGnssCbIface = callback;
    ……
    return (mGnssIface->init(&sGnssCb) == 0);
}

可以看到hidl接口的setCallback函数其实就是hidl层为gps hal层填充回调结构体指针的函数。

在这个函数中,有这么一段代码——“sGnssCbIface = callback;”,显然,这就是在设置hidl回调到jni层的回调结构体了。

4.3、ACC/GYR回调流程范例分析

所谓ACC/GYR就是加速度/陀螺仪。

因为这个项目所带的惯导功能需要将ACC/GYR信息回调给app(地图软件),所以,需要从底层回调这些信息。

这里,我们需要一段详细的流程分析,从开始获取数据到上报数据结束。

4.3.1、注册gps hal层主线程

gps hal层主线程,在init函数中创建,主要功能就是监测串口是否有数据产生,当有数据到来时,就调用解析函数解析数据,然后通过回调结构体中的各个回调函数,将他们回调到hidl层,然后再在hidl层中的函数中调用jni层的回调函数,从而将数据传递给jni层。

int CGpsIf::init(GpsCallbacks *callbacks)
{
    ……
    createGpsThread();
    ……
}

void CGpsIf::createGpsThread()
{
    ……
    s_mainControlThread = s_myIf.m_callbacks.create_thread_cb("gps thread", ubx_thread, &s_controlThreadInfo);
    ……
}

看到以上代码,首先这个init函数就是之前setCallback所调用的init函数。然后,再看到在这个init函数中,它调用了一个名为createGpsThread的函数,在这个函数中就创建了gps的thread,并将ubx_thread这个函数注册为了gps的thread函数。

4.3.2、注册串口监控 — 多路复用之select

void ubx_thread(void *pThreadData)
{
    ControlThreadInfo *pState = (ControlThreadInfo *)pThreadData;
    ……
    for(;;)
    {
        fd_set rfds;
        int maxFd = 0;
        
        FD_ZERO(&rfds);
        FD_SET(pState->cmdPipes[0], &rfds); // Add cmd queue pipe

        if (pState->cmdPipes[0] + 1 > maxFd)
            maxFd = pState->cmdPipes[0] + 1;
        
        int res = s_ser.rselect(maxFd, &rfds, &tv);
        ……
    }
    ……
}

可以看到,在ubx_thread函数的死循环中,注册了一个select多路复用(不熟悉多路复用的,可以看我另外一篇文章)。

而在上面代码的16行,则是调用了我们自己封装的一个select监测函数,对可读集合进行监控。

4.3.3、读取串口数据

void ubx_thread(void *pThreadData)
{
    ControlThreadInfo *pState = (ControlThreadInfo *)pThreadData;
    
    checkRecvInitReq(pState, rfds, maxFd, sinceLastNmeaMs > sinceLastUbxMs ? sinceLastNmeaMs : sinceLastUbxMs);

    ……
    for(;;)
    {
        int res = s_ser.rselect(maxFd, &rfds, &tv);
        if (res > 0)
        {
            if (s_ser.fdIsSet(rfds))
            {
                unsigned char *ptr = parser.GetPointer();
                unsigned int space = (unsigned int)parser.GetSpace();

                int iSize = s_ser.readSerial(ptr, space);
            }
        }
    }
    ……
}

依旧是在ubx_thread函数中,首先是第5行,这里调用了一个叫checkRecvInitReq的函数,在这个函数里,调用了openSerial,打开gps和soc传输数据的串口。然后我们看到第18行,当监测到集合内可读事件的时候,就会调用readSerial去读取对应的串口。

这里的fdIsSet由我们调用标准的FD_ISSET函数封装而成,readSerial也是由我们调用read函数封装而成。

4.3.4、解析数据并发出回调

void ubx_thread(void *pThreadData)
{
    ControlThreadInfo *pState = (ControlThreadInfo *)pThreadData;
    ……
    for(;;)
    {
        int res = s_ser.rselect(maxFd, &rfds, &tv);
        if (res > 0)
        {
            if (s_ser.fdIsSet(rfds))
            {
                ……
                while (parser.Parse(pProtocol, pMsg, iMsg)) {
                    ……
                    pUbxGps->onNewMsg(pMsg, (unsigned int)iMsg);
                    pProtocol->Process(pMsg, iMsg, pDatabase);
                    ……
                }
            }
        }
    }
    ……
}

上述代码第13行,解析数据,这个解析算法由供应商提供,不需要我们关心。不过,我们需要关心一下第15行,这里调用了一个叫做onNewMsg的函数,函数内容很多,这里就不一一展示给大家看了。在我关注的重点里,这个函数主要是配置了gps模块输出何种数据,没错,gps要输出哪些数据是需要配置的,不配置的话就没有数据输出。比如acc/gry数据是属于UBX-ESF-MEAS数据,所以必须使能它才行。

再看到第16行,这里调用了一个Process函数,在这里,我们进行了gps信息的回调。

void CProtocolUBX::Process(const unsigned char *pBuffer, int iSize, CDatabase *pDatabase)
{
    switch (getMessageId(pBuffer)){
        ……
        case UBXID_ESF_MEAS:
  	        ProcessEsfMeas(pBuffer);
	        processMessage<UBX_ESF_MEAS>(pBuffer, iSize, UBXID_ESF_MEAS);
            break;
    }
    ……
}

void CProtocolUBX::ProcessEsfMeas(const unsigned char* pBuffer)
{
    ……
    if(CGpsIf::getInstance()->m_callbacks.acc_cb) {
        //UBX_LOG(LCAT_VERBOSE, "update_gps_acc");
	    CGpsIf::getInstance()->m_callbacks.acc_cb(ACC);
    }
    ……
    if(CGpsIf::getInstance()->m_callbacks.gyr_cb) {
        //UBX_LOG(LCAT_VERBOSE, "update_gps_gyr");
	    CGpsIf::getInstance()->m_callbacks.gyr_cb(GYR);
    }
}

可以看到,上述代码第18、23行调用了acc/gyr的回调函数,这里的回调函数会将数据回调给hidl层。

4.3.5、hidl hal回调

hal层发出回调之后,会来到hidl层,hidl层代码如下:

void Gnss::gnssGyrCb(AutoNavi_Gyr* gyr){

	android::hardware::gnss::V1_0::IGnssCallback::AutoNaviGyr gyr_temp;

    if (sGnssCbIface == nullptr || gyr == nullptr) {
        ALOGE("%s: GNSS Callback Interface configured incorrectly", __func__);
        return;
    }
	//ALOGE("%s x=%f, y=%f, z=%f", __func__, gyr->x, gyr->y, gyr->z);

	gyr_temp.x = gyr->x;
	gyr_temp.y = gyr->y;
	gyr_temp.z = gyr->z;
	gyr_temp.temp = gyr->temp;
	gyr_temp.ticktime = gyr->ticktime;
	gyr_temp.axis = gyr->axis;
	gyr_temp.interval = gyr->interval;
	
    auto ret = sGnssCbIface->gnss_gyr_callback(gyr_temp);
    if (!ret.isOk()) {
        ALOGE("%s: Unable to invoke callback", __func__);
    }
}

void Gnss::gnssAccCb(AutoNavi_Acc* acc){

	android::hardware::gnss::V1_0::IGnssCallback::AutoNaviAcc acc_temp;

    if (sGnssCbIface == nullptr || acc == nullptr) {
        ALOGE("%s: GNSS Callback Interface configured incorrectly", __func__);
        return;
    }
	//ALOGE("%s x=%f, y=%f, z=%f", __func__, acc->x, acc->y, acc->z);

	acc_temp.x = acc->x;
	acc_temp.y = acc->y;
	acc_temp.z = acc->z;
	acc_temp.ticktime = acc->ticktime;
	acc_temp.axis = acc->axis;
	acc_temp.interval = acc->interval;
	
    auto ret = sGnssCbIface->gnss_acc_callback(acc_temp);
    if (!ret.isOk()) {
        ALOGE("%s: Unable to invoke callback", __func__);
    }
}

可以看到,第19行和第42行,分别调用了jni层的回调接口,通过这个接口,就把数据给传递到了jni层。

五、总结

到这里,安卓平台下的GPS架构介绍及驱动移植记录大概就介绍完了。

实际项目中,还是要结合实际问题进行分析。

要做驱动移植,对代码还是需要熟悉的,虽然大部分代码都会由gps模块供应商提供,但是在调试的时候总不能遇到问题就问gps模块供应商,而且gps模块供应商提供的驱动代码也只是标准代码,并不一定适合你的平台,还是需要部分调整的。

另外,在调试好gps hal层驱动代码之后,还需要对标地图app,需要将我们上传的数据格式和上报频率都更改为地图app所需要的才行。

  • 10
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 13
    评论
### 回答1: Android内核驱动移植是将特定硬件设备的驱动程序适配到Android操作系统上的过程。在移植过程中需要考虑硬件设备与操作系统之间的兼容性,确保硬件设备能够在Android系统上正常工作。 首先,进行Android内核驱动移植的前提是要了解硬件设备的驱动程序。开发人员需要对设备硬件结构有一定了解,并获取硬件设备的相关驱动程序源代码和文档。 然后,开发人员需要将硬件设备的驱动程序源代码与Android内核进行集成,确保代码的兼容性。这可能涉及到对驱动程序的修改、添加和删除,以适应Android内核的要求。同时,需要进行相应的配置工作,将硬件设备与Android内核进行绑定。 接下来,需要编译和构建移植后的Android内核。开发人员需要使用交叉编译工具链,将修改后的驱动程序源代码编译为与目标平台兼容的二进制文件。构建过程中还需要确保相关的编译选项和依赖库的设置是正确的。 最后,将编译完成的Android内核镜像烧录到目标硬件设备上,进行测试和调试。通过测试和调试,开发人员可以验证驱动程序的正确性和稳定性,确保硬件设备在Android系统上的正常工作。 总之,Android内核驱动移植需要开发人员对硬件设备驱动编程有一定的了解,同时具备一定的编译和调试技能。通过适配和修改驱动程序,将硬件设备与Android内核进行集成,最终实现硬件设备在Android系统上的正常工作。 ### 回答2: Android内核驱动移植是指将一个已存在的硬件设备驱动程序移植Android系统上,以便在该系统上能够对该设备进行控制和使用。移植步骤大致包括对硬件设备的硬件接口进行理解、准备移植环境、修改设备驱动源代码、编译并生成可以在Android系统上运行的设备驱动模块。下面对Android内核驱动移植的过程进行详细说明。 首先,进行移植前需要对目标硬件设备的硬件接口进行全面理解,并获得相关的文档和驱动源代码,以便后续修改和适配。 其次,需要准备一个适合的开发环境,包括正确的Android内核版本,相应的编译工具链和必要的库文件。 然后,根据目标硬件设备的特点和Android系统的要求,修改设备驱动源代码。这一步需要根据硬件设备的数据结构、寄存器配置和控制寄存器等,对驱动程序进行相应的修改和适配。 接下来,进行编译工作。根据移植好的设备驱动源代码,在准备的开发环境中使用正确的编译工具链进行编译,生成可以在目标Android系统上运行的设备驱动模块。 最后,将生成的设备驱动模块移植Android系统中。通过adb等工具将驱动模块传输到Android设备上,并将其加载到内核中,使得系统能够识别并正确控制硬件设备。 需要注意的是,Android内核驱动移植过程中可能会遇到各种问题,例如硬件兼容性、驱动与内核版本不匹配等。因此,在移植过程中需要仔细分析和解决这些问题,确保驱动移植成功并能够正常工作。 综上所述,Android内核驱动移植是一项复杂的工作,需要对目标设备的硬件接口有深入理解,并进行相关的修改和适配,最终使得设备能够在Android系统中正常运行。 ### 回答3: Android内核驱动移植是指将一个设备的硬件驱动程序从一个硬件平台(如手机、板卡)移植到另一个硬件平台上,以使设备能够在新的硬件平台上正常工作。 移植Android内核驱动的过程包括以下几个步骤: 1. 硬件分析:首先,需要对目标硬件进行详细的分析,包括内部的组件、接口等。根据硬件的特点,了解所需移植驱动程序。 2. 源代码准备:在移植过程中,需要准备源代码,包括Android内核和相关的驱动程序。确保源代码的版本与目标硬件的兼容性。 3. 驱动程序移植:根据目标硬件的需求,对原有的驱动程序进行修改。这可能包括修改硬件接口、端口或中断处理程序,以适配新的硬件平台。 4. 编译和测试:完成驱动程序的移植后,需要进行编译和测试。确保驱动程序在新的硬件平台上能够正确运行,与其他系统组件相互协调。 5. 修复问题和优化:在测试过程中可能会出现问题,需要进行修复和优化。这可能包括调整配置参数、修复驱动程序中的错误或与其他组件的兼容性问题。 移植Android内核驱动可以使不同型号的设备在使用相同版本的Android系统时,能够充分发挥其硬件的功能。同时,也可以支持新的硬件平台加入到Android生态系统中,为用户提供更多的选择。 需要注意的是,Android内核驱动移植是一个复杂的过程,需要掌握相关的技术和知识。对于一些特殊的设备,可能需要进行更多的工作和定制。因此,在进行移植之前,要充分了解目标硬件的特性和要求,保证移植的成功和稳定性。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 13
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值