🛰️ GPS 追踪器

基于 BLE 的 GPS 位置追踪设备,支持实时位置上报和历史轨迹回放。

📐 系统架构

flowchart TB subgraph GPS["GPS 追踪器"] GNSS[GNSS 模块] --> MCU[BLE MCU] MCU --> FLASH[Flash 存储] MCU --> ANT[天线] end subgraph Phone["手机 App"] ANT2[天线] --> MCU2[手机 BLE] MCU2 --> MAP[地图显示] MCU2 --> DB[(本地数据库)] end ANT -.->|BLE 连接 | ANT2 style GPS fill:#e3f2fd,stroke:#007bff style Phone fill:#d4edda,stroke:#28a745

📋 自定义 GATT 服务设计

flowchart TB subgraph Service["位置服务 (自定义 UUID)"] LOC[位置特征值
Notify/Indicate] CFG[配置特征值
Write/Read] HIST[历史数据特征值
Read] BATT[电池特征值
Read] end LOC --> CCCD1[CCCD] CFG --> CCCD2[CCCD] style Service fill:#e3f2fd,stroke:#007bff style LOC fill:#fff3cd,stroke:#ffc107

📊 位置数据格式

// 位置数据特征值数据格式 (20 bytes)
typedef struct {
    uint8_t  flags;           // 标志位
    int32_t  latitude;        // 纬度 (1e-7 度)
    int32_t  longitude;       // 经度 (1e-7 度)
    int16_t  altitude;        // 海拔 (米)
    uint8_t  speed;           // 速度 (km/h * 10)
    uint8_t  satellites;      // 卫星数量
    uint32_t timestamp;       // UTC 时间戳
} gps_location_t;

// 标志位定义
#define GPS_FLAG_3D_FIX     0x01  // 3D 定位
#define GPS_FLAG_MOTION     0x02  // 移动中
#define GPS_FLAG_CHARGING   0x04  // 充电中
#define GPS_FLAG_ALARM      0x08  // 报警状态

// 示例:北京位置,海拔 50m,速度 36km/h,8 颗卫星
uint8_t gps_data[] = {
    0x03,                   // Flags: 3D fix + motion
    0x00, 0xD9, 0x04, 0x19, // Latitude: 39.9042° N (0x1904D900 * 1e-7)
    0x00, 0x7D, 0x39, 0x07, // Longitude: 116.4074° E
    0x32, 0x00,             // Altitude: 50m
    0x5A,                   // Speed: 36 km/h (90 = 9.0 m/s)
    0x08,                   // Satellites: 8
    0x00, 0x00, 0x00, 0x00  // Timestamp: UTC
};

📈 完整工作流程

sequenceDiagram participant GPS as GPS 追踪器 participant Phone as 手机 App Note over GPS,Phone: 1. 广播阶段 GPS->>Phone: ADV_IND (位置服务 UUID) Note over GPS,Phone: 2. 连接阶段 Phone->>GPS: CONNECT_IND GPS-->>Phone: 连接完成 Note over GPS,Phone: 3. 服务发现 Phone->>GPS: 发现自定义服务 Phone->>GPS: 发现所有特征值 Note over GPS,Phone: 4. 配置上报 Phone->>GPS: 写入配置 (间隔 1s) Phone->>GPS: 启用位置通知 Note over GPS,Phone: 5. 实时位置 loop 每秒 GPS->>Phone: 位置通知 (经纬度 + 时间戳) end Note over GPS,Phone: 6. 历史轨迹 (可选) Phone->>GPS: 读取历史数据 GPS-->>Phone: 批量位置数据 Note over GPS,Phone: 7. 进入低功耗 Phone->>GPS: 断开连接 GPS->>GPS: 休眠 (GPS 模块关闭)

💻 固件代码示例

#include "nrf_sdm.h"
#include "nrf_sdh.h"
#include "ble_gatts.h"

// 自定义服务 UUID: 12345678-1234-5678-1234-56789ABCDEF0
// 位置特征值 UUID: 12345678-1234-5678-1234-56789ABCDEF1

static uint16_t m_service_handle;
static ble_gatts_char_handles_t m_location_char;
static uint16_t m_conn_handle = BLE_CONN_HANDLE_INVALID;

// 初始化位置服务
void gps_service_init(void) {
    ble_uuid_t service_uuid = {
        .uuid = 0x0001,
        .type = m_uuid_type
    };
    
    // 添加服务
    sd_ble_gatts_service_add(BLE_GATTS_SRVC_TYPE_PRIMARY, 
                             &service_uuid, 
                             &m_service_handle);
    
    // 配置位置特征值
    ble_gatts_char_md_t char_md = {
        .char_props.notify = 1,
        .char_props.read = 1,
        .char_props.write = 1,
        .p_char_user_desc = "GPS Location",
    };
    
    ble_uuid_t char_uuid = {
        .uuid = 0x0001,
        .type = m_uuid_type
    };
    
    ble_attr_char_value_t char_val = {
        .def_val = NULL,
        .max_len = 20,
    };
    
    sd_ble_gatts_characteristic_add(m_service_handle, 
                                    &char_md, 
                                    &char_attr, 
                                    &m_location_char);
}

// 发送位置数据
void send_location_data(gps_location_t *loc) {
    ble_gatts_hvx_params_t hvx_params = {
        .handle = m_location_char.value_handle,
        .p_data = (uint8_t *)loc,
        .p_len = &(uint16_t){sizeof(gps_location_t)},
        .type = BLE_GATT_HVX_NOTIFICATION,
    };
    
    sd_ble_gatts_hvx(m_conn_handle, &hvx_params);
}

// GPS 数据更新任务
void gps_update_task(void) {
    static gps_location_t location;
    
    // 从 GNSS 模块读取 NMEA 数据
    if (gnss_has_fix()) {
        nmea_data_t nmea = gnss_parse();
        
        // 转换为二进制格式
        location.flags = GPS_FLAG_3D_FIX;
        location.latitude = (int32_t)(nmea.latitude * 1e7);
        location.longitude = (int32_t)(nmea.longitude * 1e7);
        location.altitude = (int16_t)nmea.altitude;
        location.speed = (uint8_t)(nmea.speed * 10);
        location.satellites = nmea.satellites;
        location.timestamp = nmea.timestamp;
        
        // 通过 BLE 发送
        send_location_data(&location);
        
        // 存储到 Flash (用于历史轨迹)
        flash_store_location(&location);
    }
}

📱 手机 App 代码示例

// Android - GPS 追踪器客户端
class GPSTrackerClient(private val context: Context) {
    
    private var bluetoothGatt: BluetoothGatt? = null
    private var locationCharacteristic: BluetoothGattCharacteristic? = null
    private var configCharacteristic: BluetoothGattCharacteristic? = null
    
    // 自定义服务 UUID
    companion object {
        val GPS_SERVICE_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef0")
        val LOCATION_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef1")
        val CONFIG_UUID = UUID.fromString("12345678-1234-5678-1234-56789abcdef2")
        val CCCD_UUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")
    }
    
    // 解析位置数据
    private fun parseLocation(data: ByteArray): Location {
        val flags = data[0].toInt()
        
        val latitude = ((data[1].toInt() and 0xFF) or
                       ((data[2].toInt() and 0xFF) shl 8) or
                       ((data[3].toInt() and 0xFF) shl 16) or
                       ((data[4].toInt() and 0xFF) shl 24)) * 1e-7
        
        val longitude = ((data[5].toInt() and 0xFF) or
                        ((data[6].toInt() and 0xFF) shl 8) or
                        ((data[7].toInt() and 0xFF) shl 16) or
                        ((data[8].toInt() and 0xFF) shl 24)) * 1e-7
        
        val altitude = ((data[9].toInt() and 0xFF) or
                       ((data[10].toInt() and 0xFF) shl 8)).toShort()
        
        val speed = (data[11].toInt() and 0xFF) / 10.0f
        val satellites = data[12].toInt() and 0xFF
        
        return Location(latitude, longitude, altitude, speed, satellites)
    }
    
    // 配置上报间隔
    fun setUpdateInterval(seconds: Int) {
        val config = byteArrayOf(
            0x01,  // Command: Set interval
            (seconds and 0xFF).toByte(),
            ((seconds shr 8) and 0xFF).toByte()
        )
        configCharacteristic?.value = config
        bluetoothGatt?.writeCharacteristic(configCharacteristic)
    }
    
    // 读取历史轨迹
    fun downloadHistory() {
        val command = byteArrayOf(0x02)  // Command: Download history
        configCharacteristic?.value = command
        bluetoothGatt?.writeCharacteristic(configCharacteristic)
    }
}

⚡ 低功耗优化策略

flowchart LR A[上电] --> B[GPS 定位] B --> C{定位成功?} C -->|是 | D[BLE 广播] C -->|否 | E[等待 30s] E --> B D --> F{手机连接?} F -->|是 | G[实时上报] F -->|否 | H[存储数据] G --> I{断开连接?} I -->|是 | J[关闭 GPS] I -->|否 | G J --> K[深度休眠] K --> L[定时唤醒] L --> B style A fill:#28a745,color:#fff style J fill:#ffc107,color:#000 style K fill:#dc3545,color:#fff
模式 状态 功耗 说明
定位中 GPS 工作 50-100mA 搜索卫星信号
广播中 BLE 广播 10-20mA 等待手机连接
连接中 数据上报 15-25mA 实时位置推送
休眠 深度睡眠 10-50μA 定时唤醒定位

🔍 调试要点

GPS 定位问题

  • 室内无信号:需要室外测试
  • 冷启动慢:需要 30-60 秒
  • 热启动快:3-5 秒
  • 检查天线方向

BLE 连接问题

  • 连接超时:调整 supervision timeout
  • 数据丢失:使用 Indicate 代替 Notify
  • MTU 限制:协商更大 MTU
  • 干扰:避开 WiFi 信道