可靠的汽车GPS追踪器:实时集成Thinger.io地图
元件与物料
| 元件 | 数量 | 备注 |
|---|---|---|
| Arduino Leonardo | 1 | 利用其独立硬件串口(Serial1) |
| SIM900 开发板 | 1 | GSM/GPRS 通信 |
| u-blox NEO-6M GPS 模块 | 1 | 硬件串口通信 |
| GPS 天线 | 1 | 建议选用优质天线以提高接收稳定性 |
| 220Ω 电阻 | 2 | 状态指示灯限流 |
| LED 发光二极管 | 2 | 电源指示、GPS 定位指示 |
| 7×5cm 万用焊板 | 1 | 用于焊接指示灯电路,可选 |
| 12V 转 5V 降压模块 | 1 | 接汽车电瓶或点烟器供电 |
| Adafruit MicroSD 卡读取器 | 1 | 5V 兼容,SPI 接口 |
| MicroSD 存储卡 | 1 | 存储累计里程数据 |
必备工具
- 电烙铁
应用与在线服务
- Thinger.io 物联网平台(免费,含仪表盘地图功能)
项目详解
本项目最初只是个简单的“GPS 追踪器构想”,最终演变为“多功能 GPS 追踪器”。作为作者的首个项目,学习曲线陡峭,欢迎各位批评指正!
追踪器安装在车内,核心功能:
- 每 2 分钟将最新 GPS 坐标通过 HTTP POST 发布至 Thinger.io 云端仪表盘地图显示;
- 支持短信指令交互:回复 Google Maps 链接(当前位置或最后已知位置);
- 软件“里程表”功能:每行驶设定公里数(如 4000 km)发送短信提醒(原意提醒清空集油罐),可自定义触发距离与提醒内容。
开发过程中深刻体会到 Arduino 资源(Flash 与 RAM)的紧张,因此采用轻量级库并优化代码:
- NeoGPS:高性能 GPS 解析库,内存占用极低(库管理器可直接安装);
- Time:UTC 时区转换;
- PetitFS:超轻量 FAT 文件系统库,用于 SD 卡读写(标准 SD 库体积过大)。
为何不直接用 Thinger.io 官方库? 官方库占用 Leonardo 约 80% Flash,余量不足以容纳其余逻辑,故改用原生 HTTP POST 请求对接云端。
短信指令集
所有指令均为大写字母:
- POS:返回带 Google Maps 链接的坐标(无当前定位则返回最后已知位置);
- GETKM:返回自上次复位以来的累计里程(公里);
- RESETKM:将里程计数器清零。

配置 NeoGPS 库
安装库后,需修改库安装目录下的 GPSPort.h(macOS 路径为 ~/Documents/Arduino/libraries/NeoGPS/src/GPSPort.h,Windows 路径类似),全文替换为:
#ifndef GPSport_h
#define GPSport_h
#define gpsPort Serial1
#define GPS_PORT_NAME "Serial1"
#define DEBUG_PORT Serial
#endif
此文件定义 GPS 使用的串口。若换用 Mega 等多串口板,可改为 Serial2、Serial3 等。
精度说明:GPS 静止时坐标会漂移,本项目通过“仅累加 10 米以上位移(15 秒采样一次)”及“直线距离近似”规避微小漂移,精度满足车辆里程统计需求。
配置 PetitFS 库
PetitFS 是极简 FAT 库,限制多(不可创建/扩展/追加文件、单文件操作),但体积极小(比标准 SD 库节省 ~12% Flash),使完整功能能塞进 Leonardo。配置步骤:
- 修改
pffArduino.h:将SD_CS_PIN设为 10(SPI 片选脚); - 修改
pffconf.h:将_USE_DIR、_USE_LSEEK、_FS_FAT12、_FS_FAT16全部改为0以精简编译体积(最终占用 ~96% Flash); - 修复
pff.cpp缺失返回值FRESULT的编译警告(可选,不影响运行)。
SD 卡准备:必须预先在 FAT32 格式的卡上建立 dist.txt 与 settings.txt 两个文件(建议直接使用项目附件提供的文件,格式极严格)。dist.txt 存初始里程(如 0),settings.txt 存短信触发阈值(单位:米)。
SIM900 模块准备
- 外部供电:Arduino 供电不足,需 5V/9V、≥2A 直流电源(直插 5.5mm 圆孔);
- 电源选择跳帽:拨至外部供电位置;
- 串口选择跳帽:设为 Software Serial 模式(与 Arduino D8/D9 通信);
- 软件开机:焊接 R13 跳线,通过 Arduino D7 控制 SIM900 第 9 脚实现上电(代码中
SIM900power()函数); - 去除 SIM 卡 PIN 码;
- 固件版本 ≥ B10:旧版本不支持 HTTP POST 设置
Content-Type: application/json头,会导致服务器拒绝请求。通过AT+CGMR查询版本,如为 B01 需升级(参考 ACOPTEX 教程)。
代码关键配置项
- APN:在
connectGPRS()中将TeleXXX替换为运营商 APN; - 时区:
#define UTC_OFFSET 1修改为本地时区偏移; - 触发里程:写入 SD 卡
settings.txt(单位米); - 提醒短信内容/默认号码:修改
loop()中触发逻辑及DEFAULT_NUMBER; - 调试串口等待:调试时取消注释
while (!Serial);,脱机运行前必须注释掉。
Thinger.io 集成步骤
- 注册账号,创建 Bucket(数据库)与 HTTP Device(设备);
- Device 回调设置中绑定 Bucket;
- 记录 Device 的 Method URL 与 Authorization Header(去掉 "Bearer " 前缀);
- 代码中
postDataThinger()函数替换为自己的 URL 与 Key,**必须将 https:// 改为 https://**(SIM900 不支持 SSL); - 串口监视器出现
+HTTPACTION: 1,200,0即成功;400通常因 URL/Key 错误或固件版本过低。



如需上传更多字段(如速度、电压),在 sprintf(httpContent, ...) 中按 JSON 格式追加即可,注意字节长度需与 AT+HTTPDATA 参数一致。
3D 打印外壳
提供完整 STL 文件,适配本项目 PCB 尺寸,M3 螺丝固定。顶盖安装孔需打印后钻 3mm 孔(为免支撑设计)。0.2mm 层高免支撑即可打印。

车载供电方案
作者接在随车钥匙通断的点烟器电路上(熄火断电),通过 12V 转 5V 降压模块分两路 USB 供电给 Arduino 与 SIM900。实测 GSM 工作电流 ~110mA,弱信号区会增大。如需熄火后继续工作,可加装备用电池。
已知问题
- 短信指令与 HTTP POST 并发时可能丢失指令(稍后重发即可);
- 启动时无网络覆盖则不会自动重连(仅在 setup 阶段联网)。
完整代码
GPS_tracker_Leonardo_v2.ino(Arduino 代码,含详细注释)
#include
#include
#include
#include
#include "PF.h"
#include "PetitSerial.h"
#define UTC_OFFSET 1
#define TXPin 8
#define RXPin 9
#define PWRPin 7
#define DEFAULT_NUMBER "+4712345678"
FATFS fs;
String textMessage, number = DEFAULT_NUMBER;
char sms_msg[160];
float Lat = 0, Long = 0;
boolean valid_location = false;
uint8_t num_sat;
NeoGPS::Location_t prevFix;
NMEAGPS gps;
gps_fix fix;
const char *googlePrefix = "https://maps.google.com/maps?q=";
const char *filename = "DIST.TXT";
const char *settings = "SETTINGS.TXT";
NeoGPS::time_t timeFix;
char datePrint[13], timePrint[10];
float totalDistance = 0;
float triggerDistance = 4000000;
SoftwareSerial SIM900(TXPin, RXPin);
void setup() {
pinMode(3, OUTPUT); pinMode(4, OUTPUT);
digitalWrite(3, HIGH);
Serial.begin(9600);
// while (!Serial);
SIM900.begin(9600);
gpsPort.begin(9600);
initializeSD();
SIM900power();
SIM900.println(F("AT")); delay(500);
SIM900.println(F("AT+CMGF=1")); delay(500);
SIM900.println(F("AT+CNMI=1,2,0,0,0")); delay(500);
connectGPRS();
}
void loop() {
while (gps.available(gpsPort)) {
fix = gps.read();
num_sat = fix.satellites;
if (fix.valid.location) {
digitalWrite(4, HIGH);
Lat = fix.latitude(); Long = fix.longitude();
if (Lat != 0 && Long != 0 && !valid_location) {
valid_location = true; prevFix = fix.location;
}
}
if (fix.valid.date && fix.valid.time) {
timeFix = fix.dateTime; updateTime();
}
if (valid_location) {
static const unsigned long REFRESH_INTERVAL_UPD = 15000;
static unsigned long lastRefreshTime = millis();
if (millis() - lastRefreshTime >= REFRESH_INTERVAL_UPD) {
lastRefreshTime += REFRESH_INTERVAL_UPD;
float distanceDelta = prevFix.DistanceKm(fix.location) * 1000;
if (distanceDelta > 10 && distanceDelta < 10000) totalDistance += distanceDelta;
prevFix = fix.location;
}
static const unsigned long REFRESH_INTERVAL_WRITE_SD = 120000UL;
static unsigned long lastRefreshTimeWriteSD = millis();
if (millis() - lastRefreshTimeWriteSD >= REFRESH_INTERVAL_WRITE_SD) {
lastRefreshTimeWriteSD += REFRESH_INTERVAL_WRITE_SD;
char buf[9]; dtostrf(totalDistance, 8, 0, buf);
if (PF.open(filename)) Serial.println(F("error open file"));
while (1) {
UINT nr;
if (PF.writeFile(buf, sizeof(buf), &nr)) Serial.println(F("error write file"));
if (nr == sizeof(buf)) { PF.writeFile(0, 0, &nr); break; }
}
char httpContent[60], tempstrLong[10], tempstrLat[10];
dtostrf(Lat, 2, 6, tempstrLat); dtostrf(Long, 2, 6, tempstrLong);
sprintf(httpContent, "{\"longitude\": %s , \"latitude\": %s , \"date\": \"%s %s\"}", tempstrLong, tempstrLat, datePrint, timePrint);
char httpdataLen[20];
sprintf(httpdataLen, "AT+HTTPDATA=%i,10000", strlen(httpContent));
postDataThinger(httpdataLen, httpContent);
}
}
}
if (totalDistance > triggerDistance) {
char distanceTotalMsg[10];
itoa((totalDistance / 1000), distanceTotalMsg, 10);
sprintf(sms_msg, "Empty catchtank! Current distance: %skm", distanceTotalMsg);
textMessage = ""; number = DEFAULT_NUMBER; totalDistance = 0;
sendSMS(sms_msg);
}
updateSerial();
}
// ... 其余函数:updateSerial, SIM900power, updateSIM900, extractSenderNumber, sendSMS, connectGPRS, postDataThinger, initializeSD, updateTime 等 ...
自定义零件与外壳
- 顶盖(高/矮两版本,安装孔需后钻)
- 底盖(含 SIM900 与 Arduino 开孔)
- SIM900 固定夹(两版本)
- SD 卡预置文件:
dist.txt、settings.txt
原理图

制造工艺
- 熨衣板制造:设计、材料与生产流程
- 冲浪板构造与设计:从波利尼西亚起源到现代创新
- 树莓派传感器数据融合实战:GPS 与 MPU‑9150 IMU 的 I²C 与 UART 接入
- 掌握 PSoC 6 Pioneer 开发板与 IoT 扩展盾:开发者指南
- Arduino Mega 2560 接 NEO-6M GPS 模块完整教程
- 发动机缺冷却液还能开?后果比你想得严重得多
- 内部集成系统赋能精密制造:Vanamatic的协作机器人实践
- Raspberry Pi 发布 RP2040 MCU 与 4 美元 Pico 开发板
- 室外 GPS 资产追踪:趋势、挑战与新兴技术
- 精通 Python map() 函数:语法、示例与最佳实践
- Java HashMap 综合指南
- 利用BIOVIA Living Map加速COVID-19药物再利用