安卓平台下的GPS架构介绍及驱动移植记录
一、前言我的工作是关于汽车车机BSP部分。汽车车机,其实基本和人们日常所用的手机一样,也是安卓平台的。所谓安卓,就是一层安卓服务包裹着Linux内核所形成的操作系统。BSP组,主要工作内容就是负责soc的Linux系统部分的驱动移植、调试,及BUG解决。从毕业到现在,工作也有大半年了。跟着前辈学习GPS模块的移植、调试,和BUG解决也有差不多两个月了。心里想着,是时候写一篇关于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所需要的才行。
更多推荐
所有评论(0)