Saggita RTK定位定向模块

Saggita®
版本: V1.2

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


阅读提示

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

1. 产品介绍

S-SIM 2 集成 RTK 高精度定位双天线航向测向技术,支持串口 RTCM 差分数据输入 及 4G CORS 网络RTK接入, 可实现厘米级定位精度与高可靠航向解算。模块面向复杂户外应用场景设计,适配 无人机人形机器人四足机器人无人驾驶车辆等 移动机器人系统。对外提供 DroneCAN、UARTMQTT 等多种通信接口,便于系统集成与多平台接入。

参数指标

项目 分类 参数
星座 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

接口定义

S-SIM 2 线序示意图

该模块预留了 CAN 和 UART 接口,定义如图所示。其中 2 组 CAN 信号线内部短接在一起,方便用户外部串接其它 CAN 设备。如果该模块为 CAN 总线的终端设备,建议在剩余的一组 CAN 信号线上串接 120 欧电阻。注意,VCC 和 GND内部直接连在一起,同时只能有一个外部电源供电输入,可通过多余的电源线给其它设备供电。串口 1 用于输出定位信息,串口 2 用于传输 RTCM 信息。

名称 定义 备注说明
VCC 电源正 5–16 V,绝对值不可超过 18 V
GND 电源地 同时作为信号地
CAN_H CAN 总线 H CAN 标准信号
CAN_L CAN 总线 L CAN 标准信号
TX1 串口 1 发送 3.3 V TTL 信号;若外部接口为 5 V TTL,请串接限流电阻
RX1 串口 1 接收 3.3 V TTL 信号;若外部接口为 5 V TTL,请串接限流电阻
TX2 串口 2 发送 3.3 V TTL 信号;若外部接口为 5 V TTL,请串接限流电阻
RX2 串口 2 接收 3.3 V TTL 信号;若外部接口为 5 V TTL,请串接限流电阻

2. 参数设置

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

RTK Manager 参数配置界面

⚙️ 参数说明

  • 设备模式
    模块可配置为 “移动端”“基站端”,正常使用下建议不要更改该配置。

  • 数据模式
    可设置为 “默认”“自定义” 两种模式。
    该参数仅影响 串口输出数据CAN 口输出数据不受影响

  • 默认数据:Fix3 数据格式
  • 自定义数据:UM982 直接输出数据,用户可自定义 UM982 输出的数据帧

  • 默认数据
    默认数据包含三种数据类型,可选择是否输出,仅影响 CAN 口数据,串口数据不受影响。建议不要更改 RPH、Hdg 和 Fix2 的配置。

  • ODR:数据更新频率,最大 20 Hz
  • Offset:双天线测向角度偏移量

  • 波特率
    可分别设置 CAN用户串口(串口 1)电台串口(串口 2) 的波特率。

  • CORS
    配置通过 4G 网络 获取 RTK 校正数据 的相关信息。

  • MQTT
    预留 MQTT 协议接口,模块定位、定向等关键信息可通过该协议传输至远程服务器。

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


3.安装配置

S-SIM 2 安装示意图

S-SIM 2 斜立安装示意图

S-SIM 2 直立安装示意图

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

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

  3. 模块固定
    使用双面胶将 S-SIM 2 模块固定在机体表面,避免振动或松动。

  4. 通信连接
    根据控制器需求,通过 CANUART 与主控系统建立通信。

4.偏移设置

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

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

坐标系定义示意图

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

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


DroneCAN 配置

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

Holybro 飞控连接示意图

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

CAN_P1_DRIVER = 1 (启用 CAN1 驱动) 

GPS_AUTO_CONFIG = 0 (关闭 DroneCAN GPS自动配置,模块出厂已完成配置,建议保持关闭)

GPS1_TYPE = 9 (设置 GPS 类型为 **DroneCAN**)

GPS1_POS_X:主天线相对于重心的 [X轴]偏移量 dx(前偏移为正,后偏移为负)

GPS1_POS_Y:主天线相对于重心的 [Y轴]偏移量 dy(右偏移为正,左偏移为负)

GPS1_POS_Z:主天线相对于重心的 [Z轴]偏移量 dz (下偏移为正,上偏移为负)

EK2_SRC1_YAW = 2 / 3
   - 2:仅使用 GPS 作为航向源
   - 3:优先使用 GPS,罗盘作为备份

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

UAVCAN_ENABLE = "Sensors Automatic Config" (启用 UAVCAN 传感器自动配置)

EKF2_GPS_CTRL = 15(使用双天线定向作为航向源)

EKF2_GPS_POS_X:主天线相对于重心的[X轴]偏移量 dx(前偏移为正,后偏移为负)

EKF2_GPS_POS_Y:主天线相对于重心的[Y轴]偏移量 dy (右偏移为正,左偏移为负)

EKF2_GPS_POS_Z:主天线相对于重心的[Z轴]偏移量 dz(下偏移为正,上偏移为负)

EKF2_MAG_TYPE = None / Init
  - None:不使用电子罗盘  
  - Init:仅使用电子罗盘进行航向初始化


✅ 系统检查

安装及部件检查

  1. 确认 S-SIM 2 模块 安装正确且牢固。
  2. 确认 通信线缆与天线 连接正确、可靠。

工作状态指示检查

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

💡 LED 指示灯速查

状态 绿灯常亮 红灯常亮
RTK RTK 固定解,输出高精度定位信息 非 RTK 固定解,输出常规定位信息
PVT 已正常定位,输出常规定位信息 未定位,无有效定位信息
ERR 系统正常 系统故障

常见问题排查与解决

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

GNSS_FIX3 消息定义

区域 定义 数据类型 起始字节 字节长度 说明
帧头 head1 uint8 1 1 帧头
帧头 head2 uint8 2 1 帧头
帧头 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

区域 定义 数据类型 起始字节 字节长度 实际数据 说明
帧头 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 位小数,实际数据 = m/s × 1000,单位 mm/s
vel_e int32 36 4 B36~B39 东向速度,保留 3 位小数,实际数据 = m/s × 1000,单位 mm/s
vel_d int32 40 4 B40~B43 下向速度,保留 3 位小数,实际数据 = m/s × 1000,单位 mm/s
heading int16 44 2 B44~B45 双天线航向角,保留 2 位小数,实际数据 = ° × 100,范围 [-180, 180],无效值 32767
heading_acc uint16 46 2 B46~B47 双天线航向精度,保留 2 位小数,实际数据 = ° × 100,范围 [0, 360],无效值 32767
pos_n_acc uint24 48 3 B48~B50 北向位置精度,保留 3 位小数,实际数据放大1000倍,单位 mm
pos_e_acc uint24 51 3 B51~B53 东向位置精度,保留 3 位小数,实际数据放大1000倍,单位 mm
pos_d_acc uint24 54 3 B54~B56 下向位置精度,保留 3 位小数,实际数据放大1000倍,单位 mm
vel_n_acc uint24 57 3 B57~B59 北向速度精度,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_e_acc uint24 60 3 B60~B62 东向速度精度,保留 3 位小数,实际数据放大1000倍,单位 mm/s
vel_d_acc 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++)

#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)

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']}"
            )

自定义模式

1. 模式配置

首先,将数据模式设置为 自定义模式(Custom Mode),并完成以下配置:

  • 设置 User 波特率(即用户实际使用的串口波特率)
  • 配置完成后,点击 “保存参数”
  • 重新上电设备,使配置生效

自定义模式


2. 指令配置(UART1)

设备重启后,进入 UART1 界面,发送所需的配置指令。

GPGGA 输出频率设置

  • GPGGA 1(说明:1表示时间间隔为 1 秒,即 1 Hz。)
  • GPGGA 0.1(说明:0.1 表示时间间隔为 0.1 秒,即 10 Hz。)
  • 重新上电设备,使配置生效

数据输出说明

  • 指令发送成功后,即可在界面看到输出数据。同时,UART1 串口也会同步输出对应数据

3.使用示例

示例:输出 NMEA0183 标准格式 GGA + RMC 数据(10 Hz)

完成自定义模式及波特率配置后,重新上电,在 UART1 界面发送以下指令:

GPGGA 0.1
GPRMC 0.1
SAVECONFIG

发送完成后,按 回车键(Enter) 执行。配置成功后,设备将通过 UART1 持续输出以下数据:GPGGA 定位数据+ GPRMC 推荐最小导航数据(均符合 NMEA0183 标准格式)。

  • 所有指令必须以 回车(Enter) 结束,否则不会生效

  • 未执行 SAVECONFIG 保存时,断电后配置将丢失

  • 修改波特率后,请确保上位机串口参数同步更新,否则可能无法通信