
1. 硬件准备与接线指南想要用Arduino和移远EC20模块制作GPS流速仪首先得把硬件捣鼓明白。我刚开始玩这个的时候最头疼的就是接线问题后来发现只要记住交叉相连这个口诀就简单多了。具体来说你需要准备以下硬件Arduino UNO开发板建议选正版稳定性有保障。我试过某宝30块的仿制版偶尔会出现串口通信不稳定的情况移远EC20 4G模块注意要选带GNSS功能的版本有些精简版阉割了GPS功能USB转TTL模块用于前期调试后期可以去掉杜邦线若干建议用不同颜色区分电源线和信号线接线其实很简单但有几个坑我踩过要提醒你电源部分EC20的工作电压是3.3V-4.3V千万别直接接5V我烧过一个模块才记住这个教训。正确的做法是通过AMS1117稳压模块降压串口连接Arduino的TX接EC20的RXArduino的RX接EC20的TX记得共地GND接GND天线接口GPS天线要用主动式天线被动式在室内基本收不到信号。我第一次测试时在室内死活收不到数据换了天线立马解决实测下来这套硬件组合在室外开阔地带可以稳定获取1Hz的GPS更新频率完全满足流速计算的需求。有个小技巧给EC20模块加个钽电容稳压能有效减少数据丢包。2. EC20模块的GPS功能配置搞定硬件后就该配置EC20的GPS功能了。这里主要靠AT指令操作我整理了最关键的几个指令和常见问题2.1 基础AT指令配置先通过串口助手发送这些指令后面会教你怎么用Arduino代码实现ATQGPSCFGoutport,uartdebug // 配置输出到串口 ATQGPS1 // 开启GPS功能 ATQGPSCFGnmeasrc,1 // 启用NMEA数据输出 ATQGPSGNMEAGGA // 只输出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各字段含义123519UTC时间12:35:194807.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数据检查天线连接确认发送了ATQGPS1尝试在室外开阔地带测试数据时有时无可能是电源不稳建议给模块单独供电检查串口波特率是否匹配EC20默认115200定位精度差使用ATQGPSXTRA1指令下载星历数据让模块保持通电状态冷启动需要较长时间3. GPRS网络连接与TCP通信算出了流速数据还得能发出去才行。EC20的4G联网功能配置比GPS复杂些但按照我的步骤来应该没问题。3.1 网络连接配置先发送这些基础AT指令建立网络连接ATCPIN? // 查询SIM卡状态 ATCOPS? // 查询运营商 ATCREG? // 检查网络注册状态 ATCGREG? // 检查GPRS注册状态 ATQICSGP1,1,CMNET // 设置APN根据你的运营商修改 ATQIACT1 // 激活PDP上下文这些指令我在代码里做成了状态机一个执行成功再执行下一个。有个坑要注意有些地区的物联卡需要特别设置APN建议先问清楚你的卡商。3.2 TCP连接建立网络通了之后就可以连接服务器了ATQIOPEN1,0,TCP,your.server.ip,port,0,0这个指令执行成功后模块会返回CONNECT。我建议在这里加个重试机制因为网络不好的时候可能会连接失败。3.3 数据透传模式发送数据最稳定的方式是使用透传模式ATQISWTMD0,2 // 进入透传模式 ATQISEND // 开始发送数据 // 退出透传模式注意要延迟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 timeout300) { 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(ATQGPSGNMEA\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(,, comma11); // 继续解析其他字段... }解析时要注意处理无效数据室外测试时发现大约有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(ATQIOPEN1,0,\TCP\,\server.ip\,port,0,0, CONNECT)) { Serial.println(连接服务器失败); return; } String payload {\speed\: String(speed, 2) }; send_at_command(ATQISWTMD0,2, CONNECT); delay(100); ATserial.print(ATQISEND); 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检测到移动再提高频率选择低功耗模式ATQSCLK1可以开启慢时钟模式我做过测试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做过一个简单的演示系统半小时就能搭出可视化界面。