🛰️ 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
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 信道