主頁 > 後端開發 > 安卓平臺下的GPS架構介紹及驅動移植記錄

安卓平臺下的GPS架構介紹及驅動移植記錄

2020-11-11 02:49:20 後端開發

一、前言

我的作業是關于汽車車機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所需要的才行,

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/210038.html

標籤:java

上一篇:【CustomView】Android陰影的實作方式(ShadowLayer)

下一篇:android11 存盤機制變化之保存圖片至圖庫代碼案例

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Rust中的智能指標:Box<T> Rc<T> Arc<T> Cell<T> RefCell<T> Weak

    Rust中的智能指標是什么 智能指標(smart pointers)是一類資料結構,是擁有資料所有權和額外功能的指標。是指標的進一步發展 指標(pointer)是一個包含記憶體地址的變數的通用概念。這個地址參考,或 ” 指向”(points at)一些其 他資料 。參考以 & 符號為標志并借用了他們所 ......

    uj5u.com 2023-04-20 07:24:10 more
  • Java的值傳遞和參考傳遞

    值傳遞不會改變本身,參考傳遞(如果傳遞的值需要實體化到堆里)如果發生修改了會改變本身。 1.基本資料型別都是值傳遞 package com.example.basic; public class Test { public static void main(String[] args) { int ......

    uj5u.com 2023-04-20 07:24:04 more
  • [2]SpinalHDL教程——Scala簡單入門

    第一個 Scala 程式 shell里面輸入 $ scala scala> 1 + 1 res0: Int = 2 scala> println("Hello World!") Hello World! 檔案形式 object HelloWorld { /* 這是我的第一個 Scala 程式 * 以 ......

    uj5u.com 2023-04-20 07:23:58 more
  • 理解函式指標和回呼函式

    理解 函式指標 指向函式的指標。比如: 理解函式指標的偽代碼 void (*p)(int type, char *data); // 定義一個函式指標p void func(int type, char *data); // 宣告一個函式func p = func; // 將指標p指向函式func ......

    uj5u.com 2023-04-20 07:23:52 more
  • Django筆記二十五之資料庫函式之日期函式

    本文首發于公眾號:Hunter后端 原文鏈接:Django筆記二十五之資料庫函式之日期函式 日期函式主要介紹兩個大類,Extract() 和 Trunc() Extract() 函式作用是提取日期,比如我們可以提取一個日期欄位的年份,月份,日等資料 Trunc() 的作用則是截取,比如 2022-0 ......

    uj5u.com 2023-04-20 07:23:45 more
  • 一天吃透JVM面試八股文

    什么是JVM? JVM,全稱Java Virtual Machine(Java虛擬機),是通過在實際的計算機上仿真模擬各種計算機功能來實作的。由一套位元組碼指令集、一組暫存器、一個堆疊、一個垃圾回收堆和一個存盤方法域等組成。JVM屏蔽了與作業系統平臺相關的資訊,使得Java程式只需要生成在Java虛擬機 ......

    uj5u.com 2023-04-20 07:23:31 more
  • 使用Java接入小程式訂閱訊息!

    更新完微信服務號的模板訊息之后,我又趕緊把微信小程式的訂閱訊息給實作了!之前我一直以為微信小程式也是要企業才能申請,沒想到小程式個人就能申請。 訊息推送平臺🔥推送下發【郵件】【短信】【微信服務號】【微信小程式】【企業微信】【釘釘】等訊息型別。 https://gitee.com/zhongfuch ......

    uj5u.com 2023-04-20 07:22:59 more
  • java -- 緩沖流、轉換流、序列化流

    緩沖流 緩沖流, 也叫高效流, 按照資料型別分類: 位元組緩沖流:BufferedInputStream,BufferedOutputStream 字符緩沖流:BufferedReader,BufferedWriter 緩沖流的基本原理,是在創建流物件時,會創建一個內置的默認大小的緩沖區陣列,通過緩沖 ......

    uj5u.com 2023-04-20 07:22:49 more
  • Java-SpringBoot-Range請求頭設定實作視頻分段傳輸

    老實說,人太懶了,現在基本都不喜歡寫筆記了,但是網上有關Range請求頭的文章都太水了 下面是抄的一段StackOverflow的代碼...自己大修改過的,寫的注釋挺全的,應該直接看得懂,就不解釋了 寫的不好...只是希望能給視頻網站開發的新手一點點幫助吧. 業務場景:視頻分段傳輸、視頻多段傳輸(理 ......

    uj5u.com 2023-04-20 07:22:42 more
  • Windows 10開發教程_編程入門自學教程_菜鳥教程-免費教程分享

    教程簡介 Windows 10開發入門教程 - 從簡單的步驟了解Windows 10開發,從基本到高級概念,包括簡介,UWP,第一個應用程式,商店,XAML控制元件,資料系結,XAML性能,自適應設計,自適應UI,自適應代碼,檔案管理,SQLite資料庫,應用程式到應用程式通信,應用程式本地化,應用程式 ......

    uj5u.com 2023-04-20 07:22:35 more