简介:面向Android平台定位功能开发者的和芯星通UM220-III芯片集成支持包,帮助开发者在Android系统上完成对UM220-III北斗/GPS定位芯片的适配与调试。包内围绕framework与hal两条主线展开:framework部分针对芯片定制服务与接口,使系统能识别并调用高精度定位能力;hal层定义标准接口封装底层通信细节,开发者无需深究硬件即可获取位置数据。同时附带测试apk用于验证定位速度、精度与信号强度,以及协议规范与移植协助文档,指导工作模式、数据格式与集成步骤。资源共29个文件,以java源码、xml配置、c与cpp实现、aidl接口定义及mk编译脚本为主,另含pdf文档与apk安装包,压缩包约2.55MB。已有857人学习,适合需要快速打通芯片与Android框架链路的集成开发者参考。
1. 拆开 UM220-III 的 Android 支持包:从串口数据到 HAL 层定位服务
手里有一块 UM220-III 模块,插上 USB 转串口能看到 NMEA 语句哗哗往外冒,但一到 Android 板子上就抓瞎——LocationManager拿不到任何定位数据,GPS 图标永远是灰的。这个场景在车载终端、工业平板、手持机项目里太常见了。和芯星通 UM220-III 的 Android 支持包,本质上就是解决“模块能出数据、系统不认”这最后一公里的东西:它把 UART 上的 NMEA/二进制混合流,通过 HAL 层喂给 Android 的 GNSS 框架,让上层应用像用手机 GPS 一样调用北斗+GPS 双模定位。适合谁?做 Android 定制 ROM 的、搞车载导航终端的、以及需要在国产化平台上跑定位功能的嵌入式工程师。下面按“包是什么、怎么接、坑在哪”的顺序拆。
2. 支持包的结构与 GNSS HAL 对接原理:先搞懂数据怎么进框架
2.1 包里到底有什么:三层文件各管一段
拿到支持包先别急着编译,把目录摊开看。典型结构分三块:底层驱动适配层、HAL 实现层、以及给上层用的配置与权限文件。底层适配层负责打开/dev/ttyS*或 USB 串口节点,配置波特率(UM220-III 常见出厂 9600,双模全开建议提到 115200)、数据位 8、无校验、1 停止位,然后按固定节奏读数据。HAL 实现层是核心,它要实现 Android 的IGnss接口族——IGnssCallback负责往上回调位置、状态、卫星信息,IGnssConfiguration负责下发 SUPL、NTP、参考位置等配置。配置与权限文件包括gps.conf类的参数文件、ueventd.rc里的设备节点权限、以及 SELinux 策略片段。
这里有个容易忽略的点:Android 8 之后 GNSS HAL 从gps.h老接口迁到了 HIDL/AIDL。支持包如果只带老式gps.h实现,在 Android 10 以上直接编不过,得看包里有没有android.hardware.gnss@2.x或 AIDL 版本的IGnss.hal。判断方法很简单,看 HAL 目录下是gps.c还是Gnss.cpp加.hal定义文件。老接口在hardware/libhardware/include/hardware/gps.h里,新接口在hardware/interfaces/gnss/下,两者回调结构完全不同,别混着改。
2.2 数据流走向:从 UART 字节到 Location 对象
理解这条链路,排错才有方向。UM220-III 上电后默认输出 NMEA 0183 语句(GGA、RMC、GSA、GSV、GLL 等),也可能配成输出和芯私有的二进制协议。HAL 里的解析线程做三件事:按行切分、校验$开头与*后的异或校验和、把字段映射成 Android 的GnssLocation结构。映射关系要盯紧几个字段:GGA 的 UTC 时间转timestamp(注意是毫秒且要跟elapsedRealtimeNanos对齐)、经纬度是ddmm.mmmm格式要转十进制度、海拔用altitude、定位质量字段决定GnssLocationFlags里HAS_ALTITUDE、HAS_SPEED等位是否置位。
// 简化示意:NMEA GGA 解析到 GnssLocation 的关键字段映射 // 输入形如 $GNGGA,082740.00,3958.1234,N,11618.5678,E,1,12,0.9,45.2,M,-8.5,M,,*6A bool parseGGA(const char* line, GnssLocation* out) { // 1. 校验和:$ 与 * 之间所有字符异或 uint8_t cs = 0; const char* p = line + 1; while (*p && *p != '*') cs ^= (uint8_t)*p++; if (*p == '*' && strtoul(p + 1, nullptr, 16) != cs) return false; // 2. 按逗号切字段,GGA 字段序号:1=UTC 2=纬度 3=N/S 4=经度 5=E/W 6=质量 7=卫星数 9=海拔 // 3. ddmm.mmmm -> 十进制度:度 = 整数部分/100,分 = 余数/60 // 4. 质量字段 0 表示无效定位,此时不要置 HAS_ALTITUDE 等标志位 out->gnssLocationFlags = GNSS_LOCATION_HAS_LAT_LONG | GNSS_LOCATION_HAS_ALTITUDE; return true; }上面这段是逻辑骨架,真实实现里还要处理RMC给速度和航向、GSA给定位模式(2D/3D)、GSV给每颗卫星的方位角和信噪比。参数上要特别注意:timestamp用 UTC 毫秒,但 Android 框架期望的是elapsedRealtime基准,两者差一个开机时长偏移,很多“位置时间戳跳变”的玄学问题就出在这。常见做法是在 HAL 初始化时记录一次clock_gettime(CLOCK_MONOTONIC)与 UTC 的差值,后续统一换算。
2.3 编译与集成:把 HAL 塞进系统镜像
集成方式取决于你是整机编译还是单独出 so。整机编译最省事:把 HAL 源码目录放到hardware/或vendor/下,写Android.bp声明cc_library_shared,name用android.hardware.gnss@2.1-impl这类约定名,relative_install_path指到hw/。然后在设备device.mk里加PRODUCT_PACKAGES += android.hardware.gnss@2.1-impl,再确保manifest.xml里声明了 GNSS 的 HAL 条目,否则init不会拉起服务。
# 单独验证 HAL 能否被加载(设备已 root 或 userdebug) adb shell lshal | grep -i gnss # 看 GNSS HAL 是否注册成功 adb shell dumpsys location | head -40 # 看 LocationManager 是否拿到 provider adb logcat -s GnssHal GnssLocationProvider # 盯 HAL 与框架层日志lshal没输出说明 HAL 没注册,先查manifest.xml和init.rc里的 service 定义;有输出但dumpsys里 provider 是 disabled,多半是gps.conf路径不对或权限没给够。参数上,gps.conf里NTP_SERVER、SUPL_HOST这些字段在纯北斗/GPS 冷启动场景可以留空,但XTRA_SERVER相关项如果模块不支持 AGPS 就别配,配了反而让框架反复重试拖慢首次定位。
3. 串口配置与 NMEA 解析实战:把数据真正跑通
3.1 串口打开与参数设置:别在权限上翻车
第一步永远是确认设备节点。UM220-III 走 UART 时节点通常是/dev/ttyS1到/dev/ttyS4,走 USB 转串口则是/dev/ttyUSB0。用adb shell ls -l /dev/ttyS*看权限,HAL 进程一般跑在gps用户组下,节点得是gps组可读写。改法是在ueventd.rc里加一行subsystem tty name ttyS1 mode 0660 group gps,或者直接在init.rc的on boot段chown gps gps /dev/ttyS1。
// 串口初始化:termios 配置 115200 8N1,关闭流控 int openGnssUart(const char* dev) { int fd = open(dev, O_RDWR | O_NOCTTY | O_NONBLOCK); if (fd < 0) return -1; struct termios tio; tcgetattr(fd, &tio); cfsetispeed(&tio, B115200); cfsetospeed(&tio, B115200); tio.c_cflag &= ~PARENB; // 无校验 tio.c_cflag &= ~CSTOPB; // 1 停止位 tio.c_cflag &= ~CSIZE; tio.c_cflag |= CS8; // 8 数据位 tio.c_cflag |= CLOCAL | CREAD; tio.c_cflag &= ~CRTSCTS; // 关硬件流控,模块一般不用 tio.c_lflag &= ~(ICANON | ECHO | ECHOE | ISIG); tio.c_cc[VMIN] = 0; tio.c_cc[VTIME] = 10; // 1 秒读超时 tcsetattr(fd, TCSANOW, &tio); return fd; }参数说明:O_NONBLOCK配合VMIN=0、VTIME=10实现带超时的阻塞读,避免解析线程卡死;CRTSCTS一定要关,模块侧没接 RTS/CTS 时开着会导致读不到数据,这个坑我见过不止一次。波特率必须和模块实际配置一致,UM220-III 出厂 9600,但很多整机厂在产测时改成 115200,拿不准就先 9600 试,读出来是乱码再逐档往上试。
3.2 NMEA 解析线程:缓冲、切行与校验
串口读到的是一段段字节流,不能假设一次read就是完整一行。正确做法是维护一个环形缓冲,按\n切行,每行再校验。解析线程优先级要提,Android 下用setpriority(PRIO_PROCESS, 0, -10)或放进独立SCHED_FIFO线程,否则系统忙时丢句严重,表现为定位漂移或卫星数忽高忽低。
// 环形缓冲切行 + 校验的骨架 static char ring[4096]; static size_t wpos = 0; void onUartReadable(int fd) { ssize_t n = read(fd, ring + wpos, sizeof(ring) - wpos - 1); if (n <= 0) return; wpos += n; ring[wpos] = '\0'; char* lineStart = ring; char* nl; while ((nl = strchr(lineStart, '\n')) != nullptr) { *nl = '\0'; if (lineStart[0] == '$') dispatchNmea(lineStart); // 只处理 $ 开头 lineStart = nl + 1; } // 把未成行的残余搬到缓冲头部,防止溢出 size_t remain = ring + wpos - lineStart; memmove(ring, lineStart, remain); wpos = remain; }逻辑说明:dispatchNmea里按语句类型前缀分派,$GNGGA/$GPGGA走位置解析,$GPGSV走卫星信噪比,$GNRMC走速度航向。注意多星座模块会同时输出GP(GPS)、BD/GB(北斗)、GN(组合)前缀,解析时前缀要兼容,别只认GP,否则纯北斗模式下一条都匹配不上。校验失败的行直接丢,不要试图“修复”,NMEA 校验错基本意味着串口误码或波特率不匹配。
3.3 冷启动与首次定位:给足时间,别急着判死
UM220-III 冷启动首次定位(TTFF)在开阔天空下通常 30 秒到 1 分钟量级,温启动更快,热启动几秒。HAL 里要正确上报GnssStatus的GNSS_STATUS_ENGINE_ON和SESSION_BEGIN,让框架知道“正在搜星”,否则上层应用可能提前超时。测试时用adb shell dumpsys location看last location是否更新,配合logcat里GnssLocationProvider的onReportLocation日志确认回调真的到了框架层。
提示:室内或屏蔽箱里测冷启动没有意义,信噪比上不去永远定不了位。至少拿到窗边或室外,用
$GPGSV里卫星信噪比大于 30 作为“信号可用”的粗判据。
4. 避坑与排查:五个真实翻车现场
4.1 现象:HAL 注册成功但应用拿不到位置
原因多半是GnssLocationFlags置位不全或时间戳基准错。框架层对HAS_LAT_LONG之外的标志位有校验,缺HAS_ALTITUDE时某些 ROM 会直接丢弃整条位置。解决:对照GnssLocation结构逐字段打印,确认经纬度非零、timestamp单调递增、speed/bearing在无效时置 0 且不置对应标志位。
4.2 现象:定位数据每隔几秒跳一次或回退
这是时间戳换算的经典坑。UTC 毫秒直接塞进timestamp,而框架按elapsedRealtime比较,两者基准不同导致“时间倒流”,位置被判定为过期丢弃。解决:初始化时算一次utcOffset = nowUtcMs - elapsedRealtimeMs,每条位置用elapsedRealtimeMs + utcOffset回填,保证单调。
4.3 现象:串口能读到数据但全是乱码
波特率不匹配或数据位/校验位配错。UM220-III 支持多档波特率,产测改过的话出厂值不可信。解决:先用 9600 8N1 试,乱码则依次试 38400、57600、115200;同时确认没有开硬件流控,模块侧 RTS/CTS 悬空时开流控会直接读空。
4.4 现象:SELinux 拒绝导致 HAL 打不开串口
logcat里出现avc: denied且涉及ttyS1或gps域。解决:在设备sepolicy下给 HAL 域加allow gnss_hal tty_device:chr_file rw_file_perms;,别图省事直接setenforce 0,量产镜像过不了。改完make sepolicy重刷验证。
4.5 现象:双模模式下卫星数正常但定位精度差
常见于只解析了GGA没解析GSA,导致GnssStatus里每颗卫星的usedInFix标志全为 false,框架误判为未参与解算。解决:解析GSA的卫星 PRN 列表,回填到对应卫星状态;同时确认GGA的 HDOP 字段被正确读取,精度因子异常时上层会降权。
5. 进阶:用日志与注入测试验证 HAL 的边界
跑通只是及格线,真正要交付得验证边界。我一般做两件事:一是打开 HAL 的详细日志开关(支持包里通常有GNSS_DEBUG宏或persist.vendor.gnss.debug属性),把每条 NMEA 原文和解析结果成对打印,比对字段映射有没有错位;二是做“注入测试”,用socat或自写脚本往虚拟串口灌录好的 NMEA 回放文件,覆盖冷启动、丢星、跳变、校验错四类场景,看 HAL 是否稳定不崩。
# 回放录制的 NMEA 到虚拟串口,验证解析健壮性(PC 侧预演) socat -d -d pty,raw,echo=0,link=/tmp/vgps pty,raw,echo=0,link=/tmp/vgps_in & cat nmea_coldstart.log > /tmp/vgps_in # 灌入录制数据 # 让 HAL 指向 /tmp/vgps 做离线验证,确认无崩溃、无字段错位回放文件里故意混入几条校验错的行和半截行,观察 HAL 是否丢弃而非崩溃,这是上线前最划算的后悔药。参数上,回放速度可以按 1 倍实时或加速,加速时注意 HAL 内部若有超时逻辑可能误判,建议先 1 倍跑通再加速压测。
另一个技巧是拿dumpsys location的输出做回归基线:记录一次正常定位后的GnssLocation全字段快照,每次改 HAL 后对比差异,字段级 diff 比肉眼看日志靠谱得多。表格里列一下我常用的验证项:
| 验证项 | 方法 | 通过标准 |
|---|---|---|
| HAL 注册 | lshal | grep gnss | 有对应版本条目 |
| 位置回调 | dumpsys location | last location持续更新 |
| 时间戳单调 | 日志抓timestamp序列 | 严格递增无回退 |
| 校验容错 | 注入错误行 | 丢弃且不崩溃 |
| 双模卫星 | $GSV统计 | GP 与 BD 均有信噪比 |
从那以后我每次集成新的 GNSS 支持包,都强制先跑一遍回放注入再上真机,省得在车上或现场对着灰图标干瞪眼。希望帮到你。
本文还有配套的精品资源,点击获取