Android GPS HAL 驱动移植实战:UART 串口 NMEA 解析与 3 大关键接口实现

📅 2026/7/11 21:54:50
Android GPS HAL 驱动移植实战:UART 串口 NMEA 解析与 3 大关键接口实现
Android GPS HAL 驱动移植实战UART 串口 NMEA 解析与 3 大关键接口实现在 Android 系统开发中GPS 功能是许多应用场景的核心需求。本文将深入探讨如何为通过 UART 串口输出 NMEA 数据的 GPS 模块编写 HAL 层驱动重点解析 NMEA 数据格式并实现与 Android 框架对接的 3 个关键接口。1. GPS HAL 驱动开发基础Android 的硬件抽象层HAL是连接底层硬件和上层框架的桥梁。对于 GPS 模块HAL 层主要负责初始化 GPS 硬件设备解析 NMEA 数据流通过回调接口向框架层上报位置信息管理 GPS 工作状态典型的 GPS HAL 驱动需要实现以下核心结构体typedef struct { size_t size; int (*init)(GpsCallbacks* callbacks); int (*start)(void); int (*stop)(void); void (*cleanup)(void); int (*inject_time)(GpsUtcTime time, int64_t timeReference, int uncertainty); int (*inject_location)(double latitude, double longitude, float accuracy); void (*delete_aiding_data)(GpsAidingData flags); int (*set_position_mode)(GpsPositionMode mode, GpsPositionRecurrence recurrence, uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time); const void* (*get_extension)(const char* name); } GpsInterface;提示Android 不同版本对 HAL 接口的要求有所差异Android 7.x 使用传统的 GPS HAL 接口而 Android 9 则使用 GNSS HAL 接口。2. UART 串口通信实现大多数 GPS 模块通过 UART 串口与主控芯片通信典型配置如下参数典型值说明波特率9600/115200需与 GPS 模块设置一致数据位8停止位1校验位无流控无在 HAL 驱动中打开串口的基本流程int open_serial_port(const char *port, int baudrate) { int fd open(port, O_RDWR | O_NOCTTY | O_NDELAY); if (fd 0) { ALOGE(Cannot open %s: %s, port, strerror(errno)); return -1; } struct termios options; tcgetattr(fd, options); cfsetispeed(options, baudrate); cfsetospeed(options, baudrate); options.c_cflag | (CLOCAL | CREAD); options.c_cflag ~PARENB; options.c_cflag ~CSTOPB; options.c_cflag ~CSIZE; options.c_cflag | CS8; options.c_lflag ~(ICANON | ECHO | ECHOE | ISIG); options.c_oflag ~OPOST; tcsetattr(fd, TCSANOW, options); return fd; }3. NMEA 数据解析详解NMEA 0183 是 GPS 模块普遍采用的标准化数据格式由美国国家海洋电子协会制定。每条 NMEA 语句以$开头以\r\n结尾字段间用逗号分隔。3.1 常见 NMEA 语句类型GGA- GPS 定位信息GSA- 当前卫星信息GSV- 可见卫星信息RMC- 推荐最小定位信息VTG- 地面速度信息以 GGA 语句为例解析字段含义$GPGGA,082923.00,3901.106815,N,11712.322006,E,1,12,1.0,60.6,M,-4.0,M,,*5A字段含义示例值1UTC 时间082923.002纬度3901.1068153纬度方向(N/S)N4经度11712.3220065经度方向(E/W)E6定位质量(0无效,1有效)17使用卫星数128HDOP(水平精度因子)1.09海拔高度60.610高度单位(M米)M11大地水准面高度-4.012高度单位M13差分GPS数据年龄空14差分参考站ID空15校验和*5A3.2 NMEA 解析代码实现void parse_nmea(const char *nmea, GpsLocation *location) { if (strstr(nmea, $GPGGA) ! NULL) { char *token strtok((char *)nmea, ,); int field 0; double lat 0, lon 0; while (token ! NULL) { switch (field) { case 1: // UTC时间 location-timestamp parse_utc_time(token); break; case 2: // 纬度 lat atof(token); break; case 3: // 纬度方向 if (*token S) lat -lat; break; case 4: // 经度 lon atof(token); break; case 5: // 经度方向 if (*token W) lon -lon; break; case 6: // 定位质量 location-flags | (*token 1) ? GPS_LOCATION_HAS_ACCURACY : 0; break; case 9: // 海拔高度 location-altitude atof(token); location-flags | GPS_LOCATION_HAS_ALTITUDE; break; } token strtok(NULL, ,); field; } location-latitude convert_to_decimal_degrees(lat); location-longitude convert_to_decimal_degrees(lon); } }4. 三大关键接口实现4.1 初始化接口 (init)init接口负责注册回调函数这些回调将在 HAL 需要与框架通信时使用static int gps_init(GpsCallbacks* callbacks) { if (callbacks NULL) { ALOGE(callbacks cannot be NULL); return -EINVAL; } // 保存回调函数 sGpsCallbacks *callbacks; // 创建串口读取线程 pthread_create(sThread, NULL, gps_thread_loop, NULL); return 0; }关键回调函数说明location_cb- 上报位置信息status_cb- 上报 GPS 状态变化sv_status_cb- 上报卫星状态nmea_cb- 上报原始 NMEA 数据4.2 启动/停止接口 (start/stop)static int gps_start(void) { sGpsState GPS_STATE_STARTED; // 发送启动命令到GPS模块 const char *cmd $PMTK101*32\r\n; write(sFd, cmd, strlen(cmd)); // 通知框架GPS已启动 if (sGpsCallbacks.status_cb ! NULL) { sGpsCallbacks.status_cb(GPS_STATUS_SESSION_BEGIN); } return 0; } static int gps_stop(void) { sGpsState GPS_STATE_STOPPED; // 发送停止命令到GPS模块 const char *cmd $PMTK104*37\r\n; write(sFd, cmd, strlen(cmd)); return 0; }4.3 位置模式设置接口 (set_position_mode)static int gps_set_position_mode(GpsPositionMode mode, GpsPositionRecurrence recurrence, uint32_t min_interval, uint32_t preferred_accuracy, uint32_t preferred_time) { char cmd[64]; // 根据模式生成不同的PMTK命令 switch (mode) { case GPS_POSITION_MODE_STANDALONE: snprintf(cmd, sizeof(cmd), $PMTK886,%d*2A\r\n, 0); break; case GPS_POSITION_MODE_MS_BASED: snprintf(cmd, sizeof(cmd), $PMTK886,%d*2A\r\n, 1); break; case GPS_POSITION_MODE_MS_ASSISTED: snprintf(cmd, sizeof(cmd), $PMTK886,%d*2A\r\n, 2); break; default: return -EINVAL; } write(sFd, cmd, strlen(cmd)); return 0; }5. Android 版本适配要点不同 Android 版本对 GPS HAL 的要求有所差异以下是关键版本的区别Android 版本HAL 接口类型主要变化7.x 及以下Legacy GPS HAL使用传统的 GpsInterface 结构体8.xGNSS HAL 1.0引入 HIDL 接口支持更多卫星系统9.xGNSS HAL 1.1增加测量接口和导航消息接口10GNSS HAL 2.0支持低功耗模式和新卫星系统对于 Android 9 设备还需要在设备 manifest 中添加 GNSS HAL 服务声明hal formathidl nameandroid.hardware.gnss/name transporthwbinder/transport version1.1/version interface nameIGnss/name instancedefault/instance /interface /hal6. 调试与测试技巧在开发 GPS HAL 驱动时以下调试方法非常有用NMEA 模拟器使用工具如gps-sdr-sim生成模拟 NMEA 数据流ADB 命令adb shell dmesg | grep gps # 查看内核日志 adb logcat | grep -i gps # 查看框架层日志权限配置确保 SELinux 策略允许 HAL 访问串口设备allow hal_gps_default vendor_gps_device:chr_file { read write open };测试工具gps_test- 命令行测试工具GPSTest- 开源 Android 应用Google 地图 - 验证实际定位效果在实际项目中我曾遇到 GPS 模块首次定位时间(TTFF)过长的问题。通过分析发现是 AGPS 数据注入不正确导致的修正后 TTFF 从 2-3 分钟缩短到 30 秒以内。