简介:这份支持包面向在 Android 平台上集成和芯星通 UM220-III 北斗/GPS 定位芯片的开发者与集成商,提供从系统框架到硬件抽象层的完整适配方案,帮助解决芯片识别、定位服务调用与性能调试等问题。压缩包共 29 个文件,约 2.55MB,以 Java 源码、XML 配置、C/C++ 与头文件、mk 编译脚本及 aidl 接口定义为主,另附测试 apk、协议规范与参考 PDF 文档。其中 framework 与 hal 部分分别给出修改前后的对照代码,便于理解系统服务、广播接收器与标准硬件接口的定制方式;测试 apk 可用于验证定位速度、精度与信号强度,协议文档则说明芯片工作模式与数据格式。已有 857 人学习,适合需要快速完成芯片移植、排查定位异常并优化功耗的中高级 Android 开发者参考。
1. 拆开 UM220-III 的 Android 支持包:一块北斗模块怎么在安卓上跑起来
手里有一块 UM220-III 北斗/GPS 双模模块,串口能出 NMEA,但插到安卓板子上就抓瞎——这是很多做车载终端、手持机、工控平板的开发者都会撞上的场景。和芯星通这颗模块本身是成熟的 GNSS 接收机,输出标准 NMEA-0183 语句,硬件层没什么玄学,真正卡人的是 Android 这一侧:串口权限怎么拿、数据怎么从 ttyS 读到应用层、HAL 层怎么把原始语句喂给框架、定位结果怎么进到系统 LocationManager。这份 Android 支持包解决的正是这段“最后一公里”,它把串口驱动适配、HAL 接口、JNI 桥接和示例应用打包在一起,适合已经能点亮模块、但还没打通安卓定位链路的从业者。下面按“包里有啥 → 怎么接 → 坑在哪”的顺序拆。
2. 支持包的组成与 HAL 分层:先搞清楚哪一层在干活
2.1 从串口到 LocationManager 的四层结构
Android 上接一颗串口 GNSS 模块,数据流大致是四层,支持包也是按这个分层组织的。最底层是内核串口节点,UM220-III 通常走 UART,对应/dev/ttyS*,波特率默认 9600,部分固件配置可到 115200,这个参数必须和模块出厂配置一致,否则读出来全是乱码。往上一层是 native 层的串口读取线程,负责按行切分 NMEA 语句、做校验和验证、丢弃残缺帧。再往上是 HAL 层,Android 8 之后 GNSS 走的是android.hardware.gnss@2.x这套 HIDL/AIDL 接口,支持包里一般会提供一个GnssHal的实现骨架,把 NMEA 解析成GnssLocation结构体回填给框架。最上层才是应用通过LocationManager拿到的经纬度、速度、时间。
理解这个分层很关键,因为出问题时现象和层是对应的:应用拿不到定位,可能是 HAL 没注册;HAL 注册了但一直没回调,多半是串口没读到数据;串口有数据但解析报错,通常是波特率或语句类型不匹配。支持包的价值就在于把中间两层(native 读取 + HAL 适配)的样板代码给出来了,你只需要改串口路径、波特率和板级配置。
2.2 支持包里几个必须认识的目录和文件
拿到包先别急着编译,花十分钟把结构摸清楚能省后面几小时的翻车。典型布局如下(不同版本命名略有差异,以实际包为准):
| 路径 | 作用 | 改动频率 |
|---|---|---|
hal/ | GNSS HAL 实现,含GnssHal.cpp与接口注册 | 中,需改串口参数 |
jni/ | JNI 桥接与 native 读取线程 | 低,逻辑基本通用 |
app/ | 示例应用,演示 LocationManager 调用 | 低,仅验证用 |
config/ | 板级配置文件,串口节点、波特率、语句白名单 | 高,按板子改 |
scripts/ | 编译与推送脚本 | 低 |
config/是改动最频繁的地方。常见做法是把串口节点、波特率、需要解析的语句类型(GGA、RMC、GSA、GSV)写成键值对,native 层启动时读取。这样换一块板子只改配置不改代码,是这套包设计上比较务实的一点。语句白名单别图省事全开,GSV 语句量大且对定位精度没直接贡献,全解析会白白吃掉串口带宽和 CPU,尤其在低端安卓板子上能明显感觉到。
2.3 编译前先确认工具链和 SELinux 策略
这一步是新手最容易跳过、然后卡最久的。支持包里的 HAL 是 native 代码,要用对应 Android 版本的 NDK 或源码树里的编译系统来编。如果你是在完整 AOSP 源码树里集成,把hal/挂到hardware/interfaces/gnss对应的实现目录,改Android.bp里的模块名和依赖;如果是独立编译再推送到设备,得确认目标设备的 ABI(armeabi-v7a 还是 arm64-v8a)和系统库版本对得上,ABI 不匹配的表现是 push 上去一执行就Exec format error。
SELinux 是第二个隐形门槛。Android 8 以后串口设备节点的访问受file_contexts和sepolicy管控,HAL 进程默认没有读/dev/ttyS*的权限,现象是串口open()返回Permission denied,但用 adb shell 手动 cat 又能读到数据——这个反差就是 SELinux 在拦。解决方式是在设备 sepolicy 里给 HAL 的 domain 加allow xxx tty_device:chr_file rw_file_perms;,具体 domain 名看你的 HAL 服务定义。这块没有后悔药,策略不对编译能过、运行必挂,建议集成前先把设备的 sepolicy 目录翻一遍。
# 确认串口节点存在且权限可读(adb shell 内执行) ls -l /dev/ttyS* # 典型输出:crw-rw---- 1 root root 4, 64 ... /dev/ttyS4 # 用 stty 核对波特率,必须与模块配置一致 stty -F /dev/ttyS4 -a | grep speed # 手动抓几行 NMEA 验证模块在出数 cat /dev/ttyS4 | head -n 20上面三条命令是集成前的“体检”。第一条确认节点名,别想当然认为一定是 ttyS0,很多板子 GNSS 挂在 ttyS3 或 ttyS4 上;第二条核对波特率,stty显示的 speed 和你配置的不一致时,读出来就是花屏;第三条最关键,cat能看到$GNGGA、$GNRMC开头的语句,说明硬件链路通了,后面所有问题都在软件层,看不到就先去查硬件供电和天线。注意cat会一直阻塞,用head截断或者 Ctrl+C 退出。
3. 把 HAL 接进系统:配置、注册与数据回填
3.1 串口参数与 NMEA 语句解析的配置项
配置是这套包能不能跑起来的第一道关。核心参数就几个,但每个都有讲究:
| 参数 | 典型值 | 说明与坑 |
|---|---|---|
serial_port | /dev/ttyS4 | 按实际板子填,填错表现为 open 失败 |
baud_rate | 9600 | 必须与模块一致,不一致读到乱码 |
nmea_types | GGA,RMC,GSA | 白名单,GSV 建议按需开 |
fix_interval_ms | 1000 | 回填频率,过高增加框架负担 |
min_snr | 25 | 信噪比门限,低于此值不上报定位 |
fix_interval_ms这个参数容易被忽视。UM220-III 默认 1Hz 输出,你把回填频率设成 100ms 并不会让定位更准,只会让 HAL 反复上报同一个位置,框架层还要做去重,纯属浪费。min_snr是过滤弱信号用的,城市峡谷场景下卫星信噪比波动大,门限设太高会导致定位频繁丢失,设太低又会把不可靠的定位点报给应用,25 到 30 之间是比较稳的区间,具体看你的使用场景。
解析部分,支持包的 native 层一般用状态机按行读,遇到$开始、*后跟两位十六进制校验和、\r\n结束。校验和算错的行直接丢,这是防止串口误码污染定位数据的必要动作。GGA 给定位质量、卫星数和海拔,RMC 给经纬度、速度和时间,GSA 给定位模式(2D/3D),这三类足够支撑大部分应用。解析出来的字段要按 HAL 接口要求填进GnssLocation,注意经纬度单位是度、速度单位是米每秒,NMEA 里速度是节,得乘 0.514444 换算,这个换算漏了速度会大近一倍,是血泪经验级别的常见错误。
3.2 HAL 服务注册与框架侧验证
HAL 实现写好后要注册成系统服务,框架才能发现它。Android 8 到 10 用 HIDL,gnss@2.0的IGnss接口通过hw_get_module或 HIDL 服务注册;Android 11 之后逐步转 AIDL。支持包会给出对应版本的注册代码,你要做的是确认服务名和设备 manifest 里的声明一致,不一致的表现是LocationManager初始化时日志里报gnss hal not found,应用侧isProviderEnabled返回 false。
注册成功后,验证顺序建议从下往上:先看 HAL 进程日志有没有“串口打开成功”和“收到首帧 NMEA”;再看框架日志里GnssLocation回调是否按fix_interval_ms的节奏出现;最后才在示例应用里调requestLocationUpdates看有没有回调。这个顺序能快速定位断点在哪一层,比一上来就在应用里打日志高效得多。
// HAL 侧回填定位的简化示意,重点看单位换算和字段映射 GnssLocation loc = {}; loc.latitudeDegrees = gga.lat; // 已是十进制度 loc.longitudeDegrees = gga.lon; loc.altitudeMeters = gga.alt; loc.speedMetersPerSec = rmc.speedKnots * 0.514444f; // 节转米每秒 loc.timestamp = rmc.utcMs; // 定位质量差时不上报,避免污染上层 if (gga.fixQuality == 0 || gga.snr < cfg.min_snr) { return; // 无有效定位,直接返回 } reportLocation(loc);这段代码的逻辑说明:先做单位换算,节转米每秒的系数 0.514444 是固定值,别用 0.5 近似,长距离轨迹累积误差会显现;再做有效性判断,fixQuality为 0 表示无效定位,此时上报的经纬度可能是上一次的残值或全零,直接返回比上报错误数据更安全;最后才调用上报接口。参数上,min_snr从配置读,不要硬编码,方便不同场景调。这段是 HAL 适配的核心逻辑,其余都是管道代码。
3.3 用示例应用做端到端验证
示例应用的作用不是给你做产品,是验证链路通没通。装上去之后开定位权限,调requestLocationUpdates,看onLocationChanged有没有按秒级回调。如果 HAL 日志显示在回填但应用收不到,八成是权限或 provider 没使能;如果应用收到了但经纬度明显偏,回到 3.1 查单位换算和语句解析。验证阶段建议把 HAL 日志级别调高,把每帧解析出的原始字段打出来,和串口cat到的原始 NMEA 对一遍,对得上才算解析没问题。这一步做完,支持包就算跑通了,后面才是往产品里集成的事。
4. 避坑与排查:五个真实会撞上的问题
4.1 串口能读到数据但 HAL 一直无回调
现象:cat /dev/ttyS4有 NMEA,HAL 日志里却没有“收到首帧”。原因通常是 HAL 进程打开的是另一个串口节点,或者 SELinux 拦了 HAL 进程的读权限而 shell 有权限。解决:在 HAL 日志里打印实际open()的路径和返回值,Permission denied就去补 sepolicy,路径不对就改配置。这个问题的迷惑性在于 shell 能读会让人误以为权限没问题。
4.2 定位经纬度小数点错位
现象:定位点飘到几百公里外,或者经纬度看着像但差一个数量级。原因:NMEA 的经纬度是度分格式(ddmm.mmmm),不是十进制度,解析时忘了把分转成度。解决:纬度除以 100 取整得度,余数除以 60 得分,再相加;经度同理。这个转换在支持包的解析函数里应该有,如果你自己改过解析逻辑,重点查这里。
4.3 速度值偏大近一倍
现象:静止时速度显示几十公里每小时。原因:NMEA 的 RMC 语句速度单位是节,直接当米每秒或公里每小时用了。解决:节转米每秒乘 0.514444,转公里每小时乘 1.852。这是 3.1 里提过的换算坑,实际项目里踩的人不少,因为静止时偶尔有噪声速度,不仔细看不容易发现。
4.4 冷启动后长时间不定位
现象:模块上电后几分钟甚至十几分钟才出有效定位。原因:冷启动需要下载星历,开阔天空下通常 30 秒到 2 分钟,室内或遮挡严重时更久,这不是软件问题。解决:确认天线位置和供电,别在室内测冷启动;需要快速定位的场景考虑模块的温启动/热启动条件,或者用 AGPS 辅助(需额外数据链路)。把冷启动慢当成 HAL bug 去改代码,是方向性错误。
4.5 高频率回填导致框架卡顿
现象:把fix_interval_ms调到 100 后系统定位相关线程 CPU 占用明显上升。原因:模块本身 1Hz 出数,HAL 却按 100ms 回填重复位置,框架层反复处理。解决:回填频率不要高于模块实际输出频率,1Hz 模块就设 1000ms。这个参数不是越小越好,按模块能力设。
5. 进阶:把原始 NMEA 留一份,排查时少走弯路
跑通之后我建议做一件事:在 HAL 的读取线程里加一个可开关的原始语句落盘,把每帧 NMEA 原样写到文件或环形缓冲里,带上时间戳。这个动作成本很低,但排障价值极高。定位漂移、偶发丢星、时间跳变这类问题,事后靠应用层日志根本还原不了现场,有了原始语句就能离线重放解析逻辑,判断是模块输出本身的问题还是解析代码的问题。开关做成配置项,量产时关掉,调试时打开。
// 原始 NMEA 落盘,仅调试期开启 if (cfg.raw_log_enable) { FILE* fp = fopen("/data/vendor/gnss/raw_nmea.log", "a"); if (fp) { fprintf(fp, "[%lld] %s", nowMs(), line.c_str()); // line 含 \r\n fclose(fp); } }逻辑说明:按行追加写,带毫秒时间戳,方便和系统日志对齐;路径选在 HAL 有写权限的目录,别用/sdcard,SELinux 大概率不让写;fopen每次开关文件在 1Hz 下开销可接受,如果频率高改成常开文件句柄加定期 flush。参数上raw_log_enable默认 false,通过config/控制,避免遗忘导致量产设备写满存储。
另一个值得做的验证是拿一段录下来的原始 NMEA 做离线回放:写个小程序按行喂给解析函数,比对输出的经纬度和在线时是否一致。这样解析逻辑的改动可以在 PC 上验证,不用每次烧板子。我一般会在改任何解析相关代码前先跑一遍回放,确认改动前后差异符合预期再上设备。
从那以后我每次集成新的 GNSS 模块,都强制先把原始语句落盘和离线回放这两步走一遍,再谈上层功能,省下的返工时间远超这点前期投入。希望帮到你。
本文还有配套的精品资源,点击获取