【GPS模组】移远EC20 基于Arduino的GPS流速仪
1. 硬件准备与接线指南
想要用Arduino和移远EC20模块制作GPS流速仪,首先得把硬件捣鼓明白。我刚开始玩这个的时候,最头疼的就是接线问题,后来发现只要记住"交叉相连"这个口诀就简单多了。具体来说,你需要准备以下硬件:
- Arduino UNO开发板:建议选正版,稳定性有保障。我试过某宝30块的仿制版,偶尔会出现串口通信不稳定的情况
- 移远EC20 4G模块:注意要选带GNSS功能的版本,有些精简版阉割了GPS功能
- USB转TTL模块:用于前期调试,后期可以去掉
- 杜邦线若干:建议用不同颜色区分电源线和信号线
接线其实很简单,但有几个坑我踩过要提醒你:
- 电源部分:EC20的工作电压是3.3V-4.3V,千万别直接接5V!我烧过一个模块才记住这个教训。正确的做法是通过AMS1117稳压模块降压
- 串口连接:
- Arduino的TX接EC20的RX
- Arduino的RX接EC20的TX
- 记得共地(GND接GND)
- 天线接口:GPS天线要用主动式天线,被动式在室内基本收不到信号。我第一次测试时在室内死活收不到数据,换了天线立马解决
实测下来,这套硬件组合在室外开阔地带可以稳定获取1Hz的GPS更新频率,完全满足流速计算的需求。有个小技巧:给EC20模块加个钽电容稳压,能有效减少数据丢包。
2. EC20模块的GPS功能配置
搞定硬件后,就该配置EC20的GPS功能了。这里主要靠AT指令操作,我整理了最关键的几个指令和常见问题:
2.1 基础AT指令配置
先通过串口助手发送这些指令(后面会教你怎么用Arduino代码实现):
AT+QGPSCFG="outport","uartdebug" // 配置输出到串口 AT+QGPS=1 // 开启GPS功能 AT+QGPSCFG="nmeasrc",1 // 启用NMEA数据输出 AT+QGPSGNMEA="GGA" // 只输出GGA语句这几个指令我建议按顺序发送,每个指令发送后等待300ms再发下一个。实测发现如果发送太快,模块会返回ERROR。有个细节要注意:AT指令的引号必须是英文引号,用中文引号会报错。
2.2 NMEA数据解析
EC20输出的GGA语句长这样:$GPGGA,123519,4807.038,N,01131.000,E,1,08,0.9,545.4,M,46.9,M,,*47
各字段含义:
- 123519:UTC时间12:35:19
- 4807.038,N:纬度48度07.038分北纬
- 01131.000,E:经度11度31.000分东经
- 1:定位质量指示(1=有效定位)
- 08:使用的卫星数
- 545.4,M:海拔高度545.4米
在室内测试时,你可能会看到$GPGGA,,,,,,0,,,,,,,,*66这样的无效数据,这是正常的,说明没收到卫星信号。
2.3 常见问题排查
我遇到过这些问题,分享下解决方法:
收不到任何NMEA数据:
- 检查天线连接
- 确认发送了AT+QGPS=1
- 尝试在室外开阔地带测试
数据时有时无:
- 可能是电源不稳,建议给模块单独供电
- 检查串口波特率是否匹配(EC20默认115200)
定位精度差:
- 使用"AT+QGPSXTRA=1"指令下载星历数据
- 让模块保持通电状态,冷启动需要较长时间
3. GPRS网络连接与TCP通信
算出了流速数据,还得能发出去才行。EC20的4G联网功能配置比GPS复杂些,但按照我的步骤来应该没问题。
3.1 网络连接配置
先发送这些基础AT指令建立网络连接:
AT+CPIN? // 查询SIM卡状态 AT+COPS? // 查询运营商 AT+CREG? // 检查网络注册状态 AT+CGREG? // 检查GPRS注册状态 AT+QICSGP=1,1,"CMNET" // 设置APN(根据你的运营商修改) AT+QIACT=1 // 激活PDP上下文这些指令我在代码里做成了状态机,一个执行成功再执行下一个。有个坑要注意:有些地区的物联卡需要特别设置APN,建议先问清楚你的卡商。
3.2 TCP连接建立
网络通了之后,就可以连接服务器了:
AT+QIOPEN=1,0,"TCP","your.server.ip",port,0,0这个指令执行成功后,模块会返回"CONNECT"。我建议在这里加个重试机制,因为网络不好的时候可能会连接失败。
3.3 数据透传模式
发送数据最稳定的方式是使用透传模式:
AT+QISWTMD=0,2 // 进入透传模式 AT+QISEND // 开始发送数据 +++ // 退出透传模式(注意要延迟1秒再发)透传模式下,所有串口收到的数据都会直接发给服务器。实测发现,如果数据量较大,最好每发送1KB就退出透传模式休息一下,否则容易断连。
4. Arduino代码实现与优化
前面说了那么多理论,现在来看看具体的代码实现。我会分享几个关键函数和优化技巧。
4.1 基础通信框架
先建立一个稳定的AT指令通信框架:
#include <SoftwareSerial.h> SoftwareSerial ATserial(12, 13); // RX, TX void clear_serial() { while(ATserial.read() >= 0){} while(Serial.read() >= 0){} } bool send_at_command(const char* cmd, const char* expect, int timeout=300) { clear_serial(); ATserial.println(cmd); unsigned long start = millis(); while(millis() - start < timeout) { if(ATserial.find(expect)) { return true; } } return false; }这个框架我用了很多次,非常稳定。关键点:
- 每次发送指令前清空串口缓存
- 设置合理的超时时间
- 提供预期的返回结果判断
4.2 GPS数据采集与解析
获取并解析GGA数据的代码:
String get_gga_data() { if(!send_at_command("AT+QGPSGNMEA=\"GGA\"", "GGA")) { return ""; } String data; unsigned long start = millis(); while(millis() - start < 500) { if(ATserial.available()) { char c = ATserial.read(); data += c; if(c == '\n') break; } } return data; } void parse_gga(String gga) { if(gga.length() < 10 || gga.indexOf(",,") != -1) { Serial.println("无效的GGA数据"); return; } int comma1 = gga.indexOf(','); int comma2 = gga.indexOf(',', comma1+1); // 继续解析其他字段... }解析时要注意处理无效数据,室外测试时发现大约有5%的数据是无效的。
4.3 速度计算算法
根据连续两个点的坐标计算速度:
float calculate_speed(float lat1, float lon1, unsigned long time1, float lat2, float lon2, unsigned long time2) { // 将经纬度转换为弧度 lat1 = radians(lat1); lon1 = radians(lon1); lat2 = radians(lat2); lon2 = radians(lon2); // 地球半径(米) const float R = 6371000; // 计算差值 float dlat = lat2 - lat1; float dlon = lon2 - lon1; // 哈弗辛公式 float a = sin(dlat/2) * sin(dlat/2) + cos(lat1) * cos(lat2) * sin(dlon/2) * sin(dlon/2); float c = 2 * atan2(sqrt(a), sqrt(1-a)); float distance = R * c; // 时间差(秒) float time_diff = (time2 - time1) / 1000.0; return distance / time_diff; // 米/秒 }这个算法计算的是直线距离,对于低速移动物体足够精确。如果要更精确,可以考虑使用卡尔曼滤波。
4.4 数据上报逻辑
最后是把计算出的速度上报到服务器:
void report_speed(float speed) { if(!send_at_command("AT+QIOPEN=1,0,\"TCP\",\"server.ip\",port,0,0", "CONNECT")) { Serial.println("连接服务器失败"); return; } String payload = "{\"speed\":" + String(speed, 2) + "}"; send_at_command("AT+QISWTMD=0,2", "CONNECT"); delay(100); ATserial.print("AT+QISEND="); ATserial.println(payload.length()); if(send_at_command(payload.c_str(), "SEND OK", 1000)) { Serial.println("数据发送成功"); } send_at_command("+++", "", 1000); // 退出透传 }建议在这里添加重试机制和本地存储,防止网络中断时数据丢失。
5. 实际应用中的注意事项
做完基础功能后,我在实际项目中还遇到了一些值得分享的经验。
5.1 为什么选择4G而不是2G/NB-IoT
虽然2G/NB-IoT模块更便宜,但在移动场景下有严重问题:
- 多普勒效应:当物体移动速度超过120km/h时,2G信号会发生频移,导致丢包率飙升。实测在高铁上,2G的丢包率能达到50%以上
- 网络覆盖:很多地区已经开始关闭2G网络
- 延迟问题:NB-IoT的延迟通常在1-10秒,不适合实时性要求高的场景
4G模块虽然贵些,但能提供更稳定的连接。EC20还有个优势是支持多星座定位(GPS+北斗),在城市峡谷环境中表现更好。
5.2 电源管理技巧
这个项目最耗电的就是EC20模块,峰值电流能到2A。几个省电技巧:
- 使用硬件开关:不用GPS时完全断电
- 调整GPS更新频率:静态时可以设为0.1Hz,检测到移动再提高频率
- 选择低功耗模式:AT+QSCLK=1可以开启慢时钟模式
我做过测试,2000mAh的锂电池,持续工作能用约8小时。如果每小时上报一次,可以撑3天左右。
5.3 数据可靠性与完整性
确保数据不丢失的几个方法:
- 本地存储:添加SD卡模块,网络不通时先存本地
- 数据校验:每条数据加CRC校验
- 重试机制:发送失败后等待一段时间重试
- 心跳包:每5分钟发个心跳包检测连接状态
我在代码里实现了简单的队列机制,最多缓存50条数据,防止内存不足。
6. 性能优化与扩展思路
基本功能实现后,还可以做很多优化和扩展。
6.1 卡尔曼滤波优化
原始GPS数据有噪声,可以用卡尔曼滤波平滑:
// 简化的卡尔曼滤波实现 class KalmanFilter { private: float Q_angle; // 过程噪声协方差 float Q_bias; // 过程噪声协方差 float R_measure; // 测量噪声协方差 float angle; // 计算出的角度 float bias; // 陀螺仪漂移 float P[2][2]; // 误差协方差矩阵 public: KalmanFilter() { Q_angle = 0.001; Q_bias = 0.003; R_measure = 0.03; angle = 0; bias = 0; P[0][0] = 0; P[0][1] = 0; P[1][0] = 0; P[1][1] = 0; } float update(float newAngle, float newRate, float dt) { // 预测阶段 angle += dt * (newRate - bias); P[0][0] += dt * (dt*P[1][1] - P[0][1] - P[1][0] + Q_angle); P[0][1] -= dt * P[1][1]; P[1][0] -= dt * P[1][1]; P[1][1] += Q_bias * dt; // 更新阶段 float y = newAngle - angle; float S = P[0][0] + R_measure; float K[2]; K[0] = P[0][0] / S; K[1] = P[1][0] / S; angle += K[0] * y; bias += K[1] * y; float P00_temp = P[0][0]; float P01_temp = P[0][1]; P[0][0] -= K[0] * P00_temp; P[0][1] -= K[0] * P01_temp; P[1][0] -= K[1] * P00_temp; P[1][1] -= K[1] * P01_temp; return angle; } };这个滤波器对速度信号平滑效果很明显,特别是对于车载应用。
6.2 多传感器融合
除了GPS,还可以加入:
- IMU传感器:MPU6050等,弥补GPS更新频率低的问题
- 气压计:检测海拔变化
- 里程计:提供车轮转速数据
融合算法可以使用互补滤波或者更复杂的EKF,取决于你的应用场景。
6.3 云端数据处理
服务器端可以做的事情:
- 轨迹重现:将离散的点连成轨迹
- 超速报警:设置速度阈值
- 电子围栏:判断是否进入特定区域
- 大数据分析:分析常去地点、行驶习惯等
我用Node-RED做过一个简单的演示系统,半小时就能搭出可视化界面。
