RTK Ultra 定位定向模块

RTK Ultra®

版本: V1.0

本手册内容仅用于 RTK Ultra 产品的安装、配置和使用,不作为其他用途。


阅读提示

  • 建议先观看官方教学视频
  • 再阅读本手册及随产品附带的文档
  • 如有问题请联系官方技术支持工程师

介绍核心功能、性能指标与通信接口

产品介绍

RTK Ultra 定位定向模块

RTK Ultra 集成 RTK 高精度定位与双天线航向测向技术,支持串口 RTCM 差分数据输入、4G CORS 网络 RTK,并可选配 星基 RTK 高精度定位服务,实现 厘米级精准定位与高可靠航向解算,满足复杂环境下移动机器人对高精度位置与姿态信息的需求。 模块采用 小型化、高集成度设计,兼顾性能、可靠性与系统集成效率,广泛适用于 无人机、机器狗、人形机器人、无人驾驶车辆、无人船等智能移动平台。 支持 DroneCAN、UART、USB 等多种通信接口,可快速适配 PX4、ArduPilot(APM) 无人机生态,以及 宇树、云深处、智身智能等主流机器人平台。支持 5–26 VDC 宽电压输入,降低二次开发与系统集成难度,为各类无人系统提供稳定、可靠的高精度定位与航向解决方案。

参数指标

项目 分类 参数
星座 BDS / GPS / GLONASS / Galileo / QZSS
主 / 从天线频点 BDS B1I、B2I、B3I
GPS L1C/A、L2P(Y)、L2C、L5
GLONASS G1、G2
Galileo E1、E5a、E5b
QZSS L1、L2、L5
定位精度 单点定位 (RMS) 平面:1.5 m
高程:2.5 m
DGPS 平面:0.4 m + 1 ppm
高程:0.8 m + 1 ppm
RTK 平面:0.8 cm + 1 ppm
高程:1.5 cm + 1 ppm
PPP 平面:5 cm
高程:10 cm
定向精度 (RMS) 0.1° / 1 m 基线
PPS 精度 (RMS) 20 ns
速度精度 (RMS) 0.03 m/s
首次定位时间 冷启动 < 30 s
热启动 < 4 s
数据更新率 调试软件可修改 默认 10 Hz,最大 20 Hz

接口定义

RTK Ultra 接口标注

该模块预留 CAN 与 UART 通信接口,接口定义如图所示。

其中,CAN 接口采用 DroneCAN 标准协议,可便捷接入 PX4、ArduPilot(APM) 等主流开源飞控平台,实现定位与航向信息的高效传输。

模块同时提供两路串口:

  • UART1:用于输出定位与航向数据;
  • UART2:用于接收 RTCM 差分校正数据,便于接入外部差分源或其他设备。
名称 定义 备注说明
VCC 电源正 5–26 V,绝对值不可超过 28 V
GND 电源地 同时作为信号地
CAN_H CAN 总线 H CAN 标准信号
CAN_L CAN 总线 L CAN 标准信号
TX1 串口 1 发送 支持3.3 V / 5 V TTL 信号
RX1 串口 1 接收 支持3.3 V / 5 V TTL 信号
TX2 串口 2 发送 支持3.3 V / 5 V TTL 信号
RX2 串口 2 接收 支持3.3 V / 5 V TTL 信号

配置设备模式、通信参数与网络服务

参数设置

通过本模块配套的调参软件可对模块的参数进行设置。RTK 通过 Type-C 接口连接至电脑,运行 RTK-Manager-V2.1.exe 后选择正确的端口号并打开串口。正常连接后切换到配置页面,上位机会自动读取模块参数,也可以点击参数读取按钮手动读取模块的参数,如下图所示。

RTK Manager V2.1 参数设置界面

⚙️ 参数说明

  • 设备模式

    模块支持 “移动端”、“普通基站”、“RTK基站” 和 “自定义” 四种工作模式,默认配置为 “移动端”,可根据不同应用场景快速切换。

    • 移动端:作为 RTK 移动站使用,串口 1 输出 FIX3 协议数据,CAN 接口输出 DroneCAN 协议数据。
    • 普通基站:作为普通基准站使用,该基站的坐标为普通定位精度,每次重新上电后基站的坐标可能会有较大的变化。串口 1 输出 RTCM 差分校正数据,可配合数传设备,将差分数据发送至一个或多个移动端,实现 RTK 差分定位。
    • RTK 基站:作为 RTK 基站使用,该基站会通过 CORS 获取校正数据,自身达到 RTK 固定解状态,每次重新上电后基站的坐标基本一致。串口 1 输出 RTCM 差分校正数据,可配合数传设备,将差分数据发送至一个或多个移动端,实现 RTK 差分定位。
    • 自定义:用户可根据实际需求灵活配置数据输出内容,支持 GGA、ZDA 等 NMEA 0183 标准报文以及 RTCM 差分数据等。更多自定义报文及配置方式,请联系技术支持。
  • 基础配置

    模块支持对 数据频率、航向偏置、CAN 波特率、串口 1 波特率、串口 2 波特率 等基础参数进行配置。参数修改完成后,点击 “参数保存”,设备重启后生效。

    • 数据频率:设置定位数据的输出频率,支持 1 / 2 / 5 / 10 / 20 Hz,可根据飞控、机器人控制器或上位机的数据刷新需求进行选择。
    • 航向偏置:用于设置双天线安装方向与设备机体坐标系之间的角度偏差,当 天线未按照标准方向安装时,可通过航向偏置进行角度补偿。
    • CAN 波特率:设置 CAN 总线通信波特率,需与飞控或控制器的 CAN 通信参数保持一致,默认设置为 1000 kbps。
    • 串口 1 波特率:设置 UART1 的通信波特率,主要用于输出定位、航向等 GNSS 数据,也可接收或输出 RTCM 差分校正数据,需与接收设备的串口波特率保持一致。
    • 串口 2 波特率:设置 UART2 的通信波特率,主要用于输出定位、航向等 GNSS 数据,也可接收或输出 RTCM 差分校正数据,需与接收设备的串口波特率保持一致。
  • CORS 配置

    模块支持通过 4G 网络接入 CORS / NTRIP 差分服务。启用后,设备可自动连接指定的差分服务器并接收 RTCM 校正数据,实现网络 RTK 高精度定位。

    • 使能:开启或关闭 CORS 网络 RTK 功能
    • 域名:填写 CORS / NTRIP 服务器的 域名或 IP 地址
    • 挂载点:填写差分服务对应的 Mountpoint(挂载点)
    • 端口:填写 CORS / NTRIP 服务器的通信端口
    • 账号:填写差分服务平台提供的登录账号
    • 密码:填写与账号对应的登录密码

说明: 请依次输入服务器地址、端口、挂载点、账号及密码等参数。每项参数输入完成后均需按下回车键确认,界面提示 “写入成功” 表示该项参数已成功写入。全部参数配置完成后,点击 “参数保存”,设备重启后生效。

  • MQTT 配置

    模块支持通过 MQTT 协议连接远程服务器,可用于上传定位数据、设备状态等信息,便于接入云平台或用户自建服务器。

    • 使能:开启或关闭 MQTT 通信功能
    • 域名:填写 MQTT Broker 的 域名或 IP 地址
    • 账号:填写 MQTT 服务器的登录账号
    • 密码:填写与账号对应的登录密码
    • 端口:填写 MQTT Broker 的通信端口

说明: 请依次输入域名、端口、账号及密码等参数。每项参数输入完成后均需按下回车键确认,界面提示 “写入成功” 表示该项参数已成功写入。全部参数配置完成后,点击 “参数保存”,设备重启后生效。

⚠️ 注意

  • 对于输入型参数(如 CORS 账号),输入新参数后每次均需按 回车键 写入模块
  • 写入成功后将弹出 “写入成功” 提示框
  • 所有参数配置完成后,请点击 “参数保存”
  • 若未保存,重新断电后参数将恢复为上一次配置

完成天线、模块与通信线路安装

安装配置

RTK Ultra 安装示意图

斜立支架安装示意图

直立安装示意图

  1. 天线安装

    根据飞行器 / 机器人尺寸,尽可能增大天线安装跨度。建议 Master 与 Slave 天线间距 ≥ 30 cm,以获得最佳定向性能。

  2. 天线固定

    安装天线支架后,旋紧螺旋天线,确保牢固可靠。

  3. 模块固定

    使用双面胶将 RTK Ultra 模块固定在机体表面,避免振动或松动,也可以使用 M3 螺栓固定。

  4. 通信连接

    根据控制器需求,通过 CAN 或 UART 与主控系统建立通信。

设置天线位置偏移与航向补偿

偏移设置

本模块的定位主要参考 Master 天线,定向结果为 Master 天线指向 Slave 天线与正北方向的夹角。如需设置定位天线的偏移量,请以 Master 天线 的偏移量为准。模块默认采用 NED 坐标系,定义如下(见示意图):

  • 北偏(dx):正值,南偏(dx):负值
  • 右偏(dy):正值,左偏(dy):负值
  • 下偏(dz):正值,上偏(dz):负值

坐标系定义示意图

正常情况下将 Master 天线安装在正后方,Slave 天线安装在正前方,模块输出的航向角和机头方向保持一致。如果安装条件受限,需要将两个天线旋转一定角度安装,则按照 Master 天线指向 Slave 天线与机头方向逆时针方向的夹角来设置航向偏移量。下图所示为四种典型的安装方式及相应的航向偏移量。注意,航向偏移量需通过 RTK Manager 调参软件设置,详见参数设置中对默认数据的描述(设置 Offset 的值)。

S-SIM 2 坐标偏移与安装基准示意图


配置 APM 与 PX4 的 DroneCAN 参数

DroneCAN 配置

本模块的 CAN 接口通过 DroneCAN 协议适配当前主流的开源飞控。下面分别对使用 APM 和 PX4 固件的开源飞控应用该模块的配置做详细说明。

CUAV X25 飞控连接示意图

APM 固件参数配置:(上位机软件采用 MissionPlanner,固件版本 4.6 以上)

配置
CAN_P1_DRIVER = 1 (启用 CAN1 驱动)
GPS_AUTO_CONFIG = 0 (关闭 DroneCAN GPS 自动配置,模块出厂已完成配置,建议保持关闭)
GPS1_TYPE = 9 (设置 GPS 类型为 **DroneCAN**)
GPS1_MB_TYPE=1 (设置该参数并保存后,重新刷新参数列表,才会出现 GPS1_MB_OFS)
GPS1_MB_OFS_X=X (主天线相对位置X,主天线在从天线前方为正)
GPS1_MB_OFS_Y=Y (主天线相对位置Y,主天线在从天线右方为正)
GPS1_MB_OFS_Z=Z (主天线相对位置Z,主天线在从天线下方为正)
GPS1_POS_X=X(主天线相对重心的X偏移,NED坐标,前偏移为正,后偏移为负)
GPS1_POS_Y=Y(主天线相对重心的Y偏移,NED坐标,右偏移为正,左偏移为负)
GPS1_POS_Z=Z(主天线相对重心的Z偏移,NED坐标,下偏移为正,上偏移为负)
EK2_SRC1_YAW = 2 / 3
   - 2:仅使用 GPS 作为航向源
   - 3:优先使用 GPS,罗盘作为备份
EK3_OGN_HGT_MASK = 5 (GPS修正气压计高度)

PX4 固件参数配置:(上位机软件采用 QGC)

配置
UAVCAN_ENABLE="Sensors Automatic Config"
UAVCAN_SUB_GPS="Enable"
UAVCAN_SUB_GPS_R="Enable"
EKF2_GPS_CTRL=15
EKF2_GPS_POS_X(主天线相对重心的X偏移,NED坐标,前偏移为正,后偏移为负)
EKF2_GPS_POS_Y(主天线相对重心的Y偏移,NED坐标,右偏移为正,左偏移为负)
EKF2_GPS_POS_Z(主天线相对重心的Z偏移,NED坐标,下偏移为正,上偏移为负)
EKF2_MAG_TYPE="Automatic" / "None" / "Init"
    - "Automatic"(通常情况可选用该模式,磁罗盘会作为备份参与解算)
    - "None"(完全不使用磁罗盘)
    - "Init"(只用电子罗盘做初始化)
关于EKF2_MAG_TYPE的设置,如果选择"Automatic"或者"Init",在PX4飞控中可能会因为磁罗盘受干扰,
出现航向角间歇性跳变的问题(这大概率是 PX4 飞控算法本身的问题)。如果出现该问题,建议将 EKF2_MAG_TYPE 设置为 "None"。

检查安装、连接、定位与指示灯状态

系统检查

安装及部件检查

  1. 确认 RTK Ultra 模块 安装正确且牢固。
  2. 确认 通信线缆与螺旋天线 连接正确、可靠。

工作状态指示检查

  1. 检查 LED 状态指示灯 是否与当前工作状态对应。
  2. 确认设定的 螺旋天线间距 以及 主 / 副天线位置 是否正确。
  3. 在地面站软件中检查 RTK 是否获得固定解(RTK Fixed)。

💡 LED 指示灯速查

状态 绿灯常亮 红灯常亮
RTK RTK 固定解,输出高精度厘米级定位信息 非 RTK 固定解,输出常规定位信息
PVT 卫星接收个数正常,输出常规定位信息 未定位,无有效定位信息
ERR 系统正常 系统故障

排查定位、通信与移动异常

常见问题排查与解决

问题描述 解决办法
PVT亮红灯 此时卫星接收数量不足,检查周围环境是否有严重遮挡。移动新的位置进行测试
室内是无法接收卫星信息,需要到户外测试
螺旋天线线缆是否拧紧,或SMA线缆是否断裂
RTK亮红灯,始终无法进入固定解 检查SMA接头是否拧紧;射频线是否断裂(射频线比较脆弱不能小半径折弯)
可以尝试将slave和master的天线互换,判断是否是射频线损坏
检查CORS的账号输入是否正确,每次输入后都要敲一个回车,界面会跳出来【写入成功】
螺旋天线的45°的范围内,尽量不要有遮挡,会影响卫星的接收能力
物联网卡的每月免费流量是2GB,用完后次月恢复。可提供SN码查询剩余流量
检查CORS账号是否到期
检查CORS账号是否输入正确
检查‘设备模式’是否为 ‘移动端’
串口调试助手乱码 不要使用TypeC接口,这是上位机的调试口,不能拿来解析Fix3的协议
需要用【串口1】,可以使用USB转TTL模块接入PC查看输出数据
请更换具备数据传输功能的TypeC线后再次测试
机器人走动的时候,RTK灯就会灭掉,停下来就恢复 检查master接口的SMA线是否过度折弯损坏,接口是否松动?
可将slave和master的天线互换,判断射频线是否损坏

说明 ULINK 帧结构、FIX3 字段与解析方法

ULINK 协议定义

区域 定义 数据类型 起始字节 字节长度 说明
帧头 head1 uint8 1 1 帧头1
帧头 head2 uint8 2 1 帧头2
帧头 id uint16 3 2 消息ID
帧头 addr uint16 5 2 地址
帧头 len uint16 7 2 数据长度字段:最高位 len[15] 表示是否存在 data 域;len[14:0] 表示 data 域长度。
• len[15] = 1:存在 data,实际长度为 len[14:0]
• len[15] = 0:无 data,data 长度为 0
帧头 check uint16 9 2 校验值:除 check 字段外所有字节求和,结果取 uint16
数据域 data 参见具体消息定义 11 len[14:0] 指定 当 len[15] = 0 时,数据长度为 0

FIX3 消息定义

区域 定义 数据类型 起始字节 字节长度 实际数据 说明
帧头 head1 uint8 1 1 0xFA 帧头
head2 uint8 2 1 0xAA 帧头
id uint16 3 2 0x0427 消息 ID = 1063
addr uint16 5 2 0x0000 消息地址 0
len uint16 7 2 0x803F 数据域长度为 63
check uint16 9 2 B9~B10 除 check 外所有字节求和,结果取 uint16
数据域 gps_time uint64 11 8 B11~B18 UTC 时间,自 1970-01-01 00:00:00 起计,单位 μs(本地时间需加时区偏移)
lon int40 19 5 B19~B23 经度,保留 8 位小数,实际数据放大10的8次方倍,单位 0.00000001°
lat int40 24 5 B24~B28 纬度,保留 8 位小数,实际数据放大10的8次方倍,单位 0.00000001°
alt int24 29 3 B29~B31 海拔高度,保留 3 位小数,实际数据放大1000倍,单位 mm
vel_n int32 32 4 B32~B35 北向速度,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_e int32 36 4 B36~B39 东向速度,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_d int32 40 4 B40~B43 下向速度,保留 3 位小数,实际数据放大1000倍,单位 mm/s
heading int16 44 2 B44~B45 双天线航向角,保留 2 位小数,实际数据放大100倍,单位0.01°,角度范围 [-180°, 180°],无效值 32767
heading_std uint16 46 2 B46~B47 双天线航向标准差,保留 2 位小数,实际数据放大100倍,无效值 32767
pos_n_std uint24 48 3 B48~B50 北向位置标准差,保留 3 位小数,实际数据放大1000倍,单位 mm
pos_e_std uint24 51 3 B51~B53 东向位置标准差,保留 3 位小数,实际数据放大1000倍,单位 mm
pos_d_std uint24 54 3 B54~B56 下向位置标准差,保留 3 位小数,实际数据放大1000倍,单位 mm
vel_n_std uint24 57 3 B57~B59 北向速度标准差,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_e_std uint24 60 3 B60~B62 东向速度标准差,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_d_std uint24 63 3 B63~B65 下向速度标准差,保留 3 位小数,实际数据放大1000倍,单位 mm/s
pdop uint16 66 2 B66~B67 位置精度因子,保留 1 位小数,实际数据放大10倍,单位 0.1
vdop uint16 68 2 B68~B69 速度精度因子,保留 1 位小数,实际数据放大10倍,单位 0.1
hdop uint16 70 2 B70~B71 垂直精度因子,保留 1 位小数,实际数据放大10倍,单位 0.1
sats_used uint8 72 1 B72 使用的卫星数量
fix_type uint8 73 1 B73 定位状态:0 无效,1 单点,2 差分,3 PPP,4 RTK FLOAT,5 RTK FIXED

解析过程示例(C / C++)

C
#include <stdint.h>

uint64_t val;
int64_t lon;

/*
 * 原始字节(小端):
 * 98 DE BE 6B 02
 */
val = ((uint64_t)0x02 << 56)
    + ((uint64_t)0x6B << 48)
    + ((uint64_t)0xBE << 40)
    + ((uint64_t)0xDE << 32)
    + ((uint64_t)0x98 << 24);

/* 右移 24 位,得到 40-bit 有符号整数 */
lon = (int64_t)val >> 24;

说明

  • 所有字段均采用 小端模式(Little Endian);
  • 解析时应先将字节 按无符号整数方式组合;
  • 对于有符号数据,应在组合完成后再转换为 有符号整数;
  • 本示例中,经纬度字段为 40-bit 有符号整数,实际物理值需再按协议约定比例缩放。

FIX3 解析代码示例(Python)

Python
import datetime


def read_uint16_le(b, p):
    return int.from_bytes(b[p:p + 2], "little", signed=False)


def read_uint24_le(b, p):
    return b[p] | (b[p + 1] << 8) | (b[p + 2] << 16)


def read_int24_le(b, p):
    v = read_uint24_le(b, p)
    return v - (1 << 24) if v & 0x800000 else v


def read_uint40_le(b, p):
    return (
        b[p]
        | (b[p + 1] << 8)
        | (b[p + 2] << 16)
        | (b[p + 3] << 24)
        | (b[p + 4] << 32)
    )


def read_int40_le(b, p):
    v = read_uint40_le(b, p)
    return v - (1 << 40) if (v & (1 << 39)) else v


def checksum16(data: bytes) -> int:
    return sum(data) & 0xFFFF


def parse_ulink_frame(frame: bytes):
    if len(frame) < 11:
        raise ValueError("Frame too short")

    if frame[0] != 0xFA or frame[1] != 0xAA:
        raise ValueError("Invalid frame header")

    msg_id = read_uint16_le(frame, 2)
    addr = read_uint16_le(frame, 4)
    len_raw = read_uint16_le(frame, 6)

    has_data = bool(len_raw & 0x8000)
    data_len = (len_raw & 0x7FFF) if has_data else 0

    total_len = 11 + data_len
    if len(frame) < total_len:
        raise ValueError("Frame length mismatch")

    check = read_uint16_le(frame, 8)
    calc = checksum16(frame[:8] + frame[10:10 + data_len])
    check_ok = (check == calc)

    data = frame[10:10 + data_len] if has_data else b""

    return {
        "id": msg_id,
        "addr": addr,
        "data_len": data_len,
        "check_ok": check_ok,
        "data": data,
        "total_len": total_len,
    }


def parse_gnss_fix3(data: bytes):
    if len(data) != 63:
        raise ValueError("GNSS_FIX3 payload length error")

    gps_time_us = int.from_bytes(data[0:8], "little", signed=False)
    gps_utc = datetime.datetime(1970, 1, 1) + datetime.timedelta(
        microseconds=gps_time_us
    )

    lon = read_int40_le(data, 8) / 1e8
    lat = read_int40_le(data, 13) / 1e8
    alt = read_int24_le(data, 18) / 1000.0

    vn = int.from_bytes(data[21:25], "little", signed=True) / 1000.0
    ve = int.from_bytes(data[25:29], "little", signed=True) / 1000.0
    vd = int.from_bytes(data[29:33], "little", signed=True) / 1000.0

    heading_raw = int.from_bytes(data[33:35], "little", signed=True)
    heading = None if heading_raw == 32767 else heading_raw / 100.0

    heading_acc_raw = int.from_bytes(data[35:37], "little", signed=False)
    heading_acc = None if heading_acc_raw == 32767 else heading_acc_raw / 100.0

    posN = read_uint24_le(data, 37) / 1000.0
    posE = read_uint24_le(data, 40) / 1000.0
    posD = read_uint24_le(data, 43) / 1000.0

    vNacc = read_uint24_le(data, 46) / 1000.0
    vEacc = read_uint24_le(data, 49) / 1000.0
    vDacc = read_uint24_le(data, 52) / 1000.0

    pdop = int.from_bytes(data[55:57], "little") / 10.0
    vdop = int.from_bytes(data[57:59], "little") / 10.0
    hdop = int.from_bytes(data[59:61], "little") / 10.0

    sats = data[61]
    fix = data[62]

    return {
        "time": gps_utc,
        "lat": lat,
        "lon": lon,
        "alt": alt,
        "vn": vn,
        "ve": ve,
        "vd": vd,
        "heading": heading,
        "heading_acc": heading_acc,
        "pdop": pdop,
        "vdop": vdop,
        "hdop": hdop,
        "sats": sats,
        "fix_type": fix,
    }


def scan_frames(stream: bytes):
    frames = []
    i = 0
    n = len(stream)

    while i + 11 <= n:
        if stream[i] == 0xFA and stream[i + 1] == 0xAA:
            try:
                fr = parse_ulink_frame(stream[i:])
                frames.append(fr)
                i += fr["total_len"]
                continue
            except Exception:
                i += 1
        else:
            i += 1

    return frames


if __name__ == "__main__":
    with open("Serial Debug.txt", "r", encoding="utf-8") as f:
        hex_text = f.read().strip()

    raw = bytes(int(x, 16) for x in hex_text.split())
    frames = scan_frames(raw)

    for fr in frames:
        if fr["id"] == 0x0427 and fr["data_len"] == 63 and fr["check_ok"]:
            fix = parse_gnss_fix3(fr["data"])
            print(
                f"time={fix['time']} "
                f"lat={fix['lat']:.8f} lon={fix['lon']:.8f} "
                f"alt={fix['alt']:.3f}m "
                f"sats={fix['sats']} fix={fix['fix_type']}"
            )