
简介基于STM32单片机与GPS定位的农田面积测量仪完整设计方案面向嵌入式入门者、电子竞赛选手及农业信息化开发者。方案以ATK-NEO-6M GPS模块为核心通过串口1与STM32通信在液晶屏上实时显示经纬度、高度、速度、定位卫星数、可见卫星数及UTC时间等信息覆盖原理图、源程序、BOM表和演示视频可帮助快速搭建一套可运行的农田面积测量原型。压缩包共97个文件以C语言源码.c/.h、Keil工程配置.uvprojx/.uvopt、编译产物.hex/.axf/.o/.map及说明文档.txt/.htm为主整体仅1.11MB目录模块划分清晰便于按需查阅。已有867人学习浏览资料虽小但涵盖串口通信、GPS数据解析、液晶显示、定时器等典型STM32开发知识点适合用来学习定位类项目或在此基础上扩展面积计算功能。 农田面积测量在农村征地、种粮补贴统计、土地流转这些场景里是从古到今躲不开的一件事。以前用皮尺拉、用测亩仪皮尺拉矩形地块还能凑合遇到不规则地块就得切开分段算费时费力还容易吵架。这个基于STM32单片机和GPS定位的农田面积测量仪就是把手机导航里那套卫星定位逻辑搬到单片机平台上人拿着设备沿着地块边界走一圈单片机通过GPS模块采集边界点坐标再用多边形面积算法把地块面积算出来实时显示到屏幕上。整机成本几十块钱精度在合理操作下能做到千分之几的相对偏差对于绝大多数地块实测场景完全够用。这篇博文把我从选型、画板、写代码到田间实测踩过的坑都梳理一遍适合有单片机基础想动手做测量类项目的朋友参考。1. 总体设计思路与系统选型1.1 为什么选STM32而不是51单片机做这个项目的时候很多人第一反应是“用51单片机就行了吧”。确实51单片机处理GPS串口数据也能跑但我当时直接选了STM32F103C8T6核心原因就三个第一GPS模块输出的NMEA协议数据是9600波特率的连续字符流51单片机要用定时器模拟串口接收或者靠软件延时来解析处理长字符串的时候很容易丢数据。STM32自带多个硬件USART配上DMA接收数据来了直接往缓冲区塞CPU只负责解析可靠性完全不在一个量级。第二面积计算需要做浮点运算尤其是把经纬度坐标转换成平面坐标后要连乘累加。51单片机是8位内核做float运算要靠软件模拟算一个点能卡几十毫秒。STM32是Cortex-M3内核带硬件浮点或者至少是32位乘法指令算起来轻松很多。第三后续扩展。这个项目做完我还想加SD卡存储、按键菜单、红外遥控STM32的引脚和外设资源比51宽裕得多留着余量不用再换平台。选型这事我的原则很简单不去追新器件只看手上的工具链是否成熟、资料是否充足。STM32F103是市面上资料最泛滥的型号之一Keil MDK开发环境也稳定遇到问题搜一下基本都有答案这就够了。1.2 硬件架构与模块选型整个系统的硬件组成并不复杂核心就四块STM32最小系统板、GPS接收模块、显示模块、电源电路。GPS模块我选的是u-blox NEO-6M系列兼容板很常见价格便宜功耗低支持北斗和GPS双模的是NEO-M8N价格稍高一点。作为设计参考NEO-6M就够了它输出标准的NMEA 0183协议串口默认9600波特率3.3V逻辑电平和STM32直连完全没问题。要注意的一点是NEO-6M模块板上一般自带陶瓷天线和EEPROM买的时候认准“带天线的”不然信号强度会很头疼。显示模块的选择上我用了LCD1602液晶屏带IIC转接板的版本只占用两个IO口显示两行文字一行显示当前状态一行显示面积信息足够了。如果你手头有0.96寸OLED也可以直接用SSD1306驱动库网上大把改起来工作量也不大。电源部分最简单也最容易踩坑。GPS模块和STM32板子都要5V供电但GPS的串口电平是3.3V所以STM32板载的稳压器和IO电平转换要靠板子本身完成。我做的是电池供电版本用一节18650锂电池加一个AMS1117-3.3稳压模块给板和GPS供电实测整机电流不到100mA续航没什么压力。下面这个表是选型时的参考参数方便你对照模块选型关键参数备注主控STM32F103C8T672MHz主频64KB Flash蓝板/黑板均可GPSu-blox NEO-6M9600bpsNMEA协议带陶瓷天线版本更好显示LCD1602 IIC2行16字符也可换OLED电源18650锂电池3.7V→5V升压3.3V稳压AMS1117方案这里多说一句GPS模块的天线摆放位置很关键实测的时候模块要水平朝上不能挨着单片机晶振和电池否则信号容易被干扰后面我会专门讲这个问题。2. 面积算法的核心细节2.1 GPS坐标处理从度分秒到平面坐标GPS模块输出的坐标是NMEA格式比如$GPRMC,123519,A,4807.038,N,01131.000,E,022.4,084.4,230394,003.1,W*6A这里面纬度是4807.038含义是48度07.038分经度是01131.000含义是11度31.000分。要算面积不能直接拿这个字符串去运算必须先转换成十进制度。转换公式很直白double convert_gps_to_degree(double gps_format) { int degree (int)(gps_format / 100); double minute gps_format - degree * 100; return degree minute / 60.0; }拿到十进制度之后很多人想直接把经纬度当直角坐标丢进面积公式这是大忌。经度和纬度的“物理长度”不一样低纬度地区1度经度有111公里到了高纬度就缩水成几十公里直接把经纬度数值做运算误差会被放大到不可用。正确的做法是先把经纬度换算成平面坐标常用的是等距投影近似法。以地块中心点作为基准点假设它的纬度为lat0、经度为lon0那么任意一点的平面坐标可以这样算#define EARTH_RADIUS 6371000.0 double x_plane (lon_degree - lon0) * EARTH_RADIUS * cos(lat0_radian) * PI / 180.0; double y_plane (lat_degree - lat0) * EARTH_RADIUS * PI / 180.0;这里的思路是把地球看成一个球体在局部范围内把球面展开成平面这个近似对一两公里以内的地块完全够用误差在厘米到分米级别。如果你项目要求更严可以换成UTM投影或者高斯-克吕格投影那个公式更复杂但对这个应用场景属于过度设计。2.2 多边形面积计算鞋带公式的原理与实现拿到边界点的平面坐标后计算面积用的数学工具是鞋带公式也叫高斯面积公式。原理很简单把多边形分解成多个三角形每个三角形的面积加起来再取绝对值因为边界点是按顺序记录的所以直接套公式就行。公式长这样S 0.5 * |Σ (xi * yi1 - xi1 * yi)|实现为C语言代码如下double calculate_area(double *x_arr, double *y_arr, int point_num) { double area 0.0; for (int i 0; i point_num; i) { int j (i 1) % point_num; area x_arr[i] * y_arr[j] - x_arr[j] * y_arr[i]; } area fabs(area) / 2.0; return area; }这里要注意x_arr和y_arr存的是平面坐标单位是米算出来的面积单位是平方米。我习惯保留4个有效数字的精度然后换算成亩double area_mu area_square_meter / 666.6667;为什么分母是666.6667因为1亩等于2000/3平方米这个换算关系记牢就行从农口那里收数据的时候他们只看“亩”。2.3 采样策略与误差控制鞋带公式本身是精确的误差来源主要是GPS定位点的抖动和采样频率的取舍。我实测下来在农田边走路的速度大概是每秒1米到1.5米GPS模块每秒输出1次定位数据那么每隔3到5个点取一个点作为边界点既能保证边界形状的还原度又不会让数组爆掉或者点太密导致面积抖动。采样点间距过小有个问题GPS本身有2到5米的标准定位误差点靠得越近抖动对面积的影响就越大。比方说一个边长100米的地块每隔0.5米取一个点两点之间的定位误差正负3米那个点的位置就可能偏移出实际边界很远反而把面积拉大。我后来取的策略是每3秒取一个点相当于每3到4米一个边界点这样既不会漏掉地块的凹凸细节又能把定位抖动的随机误差平均掉一部分。另外一定要做闭合处理最后一个点要和第一个点连接鞋带公式里取模运算(i1)%point_num就是干这个的。3. 硬件连接与程序框架3.1 STM32与GPS模块接线方案我用的STM32F103C8T6最小系统板GPS模块是NEO-6M接线极为简单STM32引脚GPS模块引脚说明PA2 (USART2_TX)RX发送配置命令本项目可不用PA3 (USART2_RX)TX接收GPS数据3.3VVCC电源注意模块是否支持5V供电GNDGND共地必须接串口配置上GPS默认波特率是96008位数据位无校验1位停止位这是NMEA协议的标准配置。我在初始化里把USART2配好开启接收中断或者更推荐的做法是开启DMA接收用空闲中断来判断一帧数据收完。DMA的好处是CPU完全不用管串口接收这件事GPS数据来了直接怼进数组等主循环里解析就行。当前的内容和上下文看虽然51单片机也能做但只用51做电磁炉功能代码那种量级的项目还行到这个带数据流解析和数学计算的项目STM32的优势就明显了。具体接线时注意GPS模块的RX如果要接STM32的TX大多数NEO-6M板子已经兼容3.3V但有的模块是5V逻辑要用分压电阻不然长期使用可能烧单片机的IO口。买模块的时候问清楚保险起见串一个1K电阻也能缓解。3.2 源码结构设计与关键模块说明整个工程我用的是标准库开发没用HAL库原因是我对这个项目的代码量有把握标准库的寄存器操作更直观也方便讲解。工程的目录结构大概是这样的Project/ ├── main.c // 主循环状态机处理 ├── usart.c // 串口初始化与DMA接收 ├── gps.c // NMEA数据解析与坐标转换 ├── area.c // 平面坐标转换与面积计算 ├── lcd1602_iic.c // 显示驱动 ├── key.c // 按键输入可选 └── stm32f10x_it.c // 中断处理main.c里跑一个简单的状态机定义三种状态待机、测量中、计算完成。按键按一下进入测量再按一下结束测量主循环里不断从GPS解析结果里取最新坐标测量状态下每3秒存一个点结束后调用面积计算函数。GPS解析这块单拿出来说NMEA协议有好几种语句只要关注$GPRMC就够了它含定位状态、纬度、经度、速度等关键信息。如果模块支持北斗可能出现$GNRMC解析逻辑完全一样。我写了一个简单的字符串匹配加逗号分隔提取不依赖任何第三方库实测很稳void parse_GPRMC(char *buf) { char *p strstr(buf, $GPRMC); if(p NULL) return; char tmp[20]; int field 0; p strchr(p, ,); while(p ! NULL) { p; field; if(field 2) // 定位状态 A/V { if(*p ! A) return; // 未定位不更新坐标 } else if(field 3) // 纬度 { sscanf(p, %lf, lat_gps_format); } else if(field 5) // 经度 { sscanf(p, %lf, lon_gps_format); } p strchr(p, ,); } lat_degree convert_gps_to_degree(lat_gps_format); lon_degree convert_gps_to_degree(lon_gps_format); }这个地方有个坑strstr是标准库函数但嵌入式工程里用字符串函数要小心溢出我建议定义缓冲区时给个上限比如char gps_buf[128]一旦DMA接收的数据长度超过这个值就不处理防止内存越界。我这个项目里GPS数据帧最长也就一百来个字符128字节足够。3.3 显示与交互逻辑显示这块我用的是LCD1602 IIC版本两行显示。第一行显示当前状态和坐标信息第二行显示面积数值。状态机切换的时候屏幕提示要直观我设计成这样第一行: GPS: OK Points: 20 第二行: Area: 1.2345 Mu解析成功并且定位状态为A的时候显示OK不满足的时候显示GPS: WAIT提醒用户走远一点到开阔地等定位。按键采用外部中断触发一个按键负责“开始/结束测量”另一个按键负责“清零重新开始”。有一个细节是开始测量前必须等GPS完成定位并且能看到“OK”标志否则采集的点全是无效点面积算出来纯属扯淡。我加了一个保护逻辑GPS状态不是OK就算按了开始键也不进入测量状态屏幕会提示等待。4. 常见问题与排查技巧实录4.1 GPS定位慢或定位不到这个是我被问得最多的问题也是我在田里最崩溃的一段时间。NEO-6M模块冷启动首次定位可能需要30秒到几分钟如果在室内或者树荫浓密的地方可能十分钟都定位不了。排查顺序我建议是先确认模块供电正常用万用表量一下VCC对GND应该在3.3V上下再看串口有没有数据输出用示波器或者逻辑分析仪抓PA3引脚的波形正常情况每秒能看到一帧数据最后看数据内容如果输出的RMC语句里定位状态一直是V说明模块没找到足够卫星这时候要把设备移到开阔地。天线的位置是个隐藏坑。我第一版把GPS模块放在单片机的正上方紧挨着晶振结果实测经常丢星后来把GPS模块用一根杜邦线拉出去20厘米信号立马就稳了。另外NEO-6M的陶瓷天线要朝天空方向不能贴着地面。4.2 面积数值偏差大怎么校准如果是完全实测面积偏差可能有几个来源。一个是边界点覆盖不够地块转角处没取到点比如一个L形地块你只取了四个角那算出来和真实面积就差了很大。解决办法是尽量沿着地块边缘走转角处多停留一下让模块多输出几个点。另一个是GPS本身的定位误差。NEO-6M在开阔地的水平定位精度是2.5米CEP这意味着有50%的概率落在一个半径2.5米的圆内。按这个误差来算一个边长100米的正方形地块面积相对误差大概在5%左右。如果觉得不够可以做几件事一是把设备放在地块的多个位置多次测量取平均二是用RTK级别的高精度模块成本会上去三是沿着地块边界走两遍把两次的结果平均一下误差能压到2%以内。测量的时候还有个容易忽略的问题地块如果一边靠墙、一边靠沟GPS信号在墙边会有多路径效应定位点会往墙面方向偏。我后来在测田埂边地块时尽量让设备在田埂中间走偏离边界半米到一米是能接受的。4.3 跑飞和死机问题嵌入式程序最怕运行到一半卡死这个项目里最可能的跑飞原因是GPS数据处理时数组越界或者解析函数里用了不安全的字符串操作。我在结构设计上做了三点防护第一GPS缓冲区全部用固定长度数组收满128字节就开始处理不处理完就不继续接收新数据第二解析函数里所有指针操作都判空strstr返回NULL就立刻退出不做后续操作第三主循环里加一个看门狗喂狗间隔500ms一旦主循环卡死超过1秒系统自动复位。另外在实测环境中低温高湿的环境下LCD可能会变慢但这通常不影响测量逻辑最多就是显示刷新慢半拍。程序层面我建议把LCD刷新频率降低到每500ms一次不然串口、解析、显示、按键挤在一起单片机虽然不至于卡死但实时性体验会打折扣。4.4 用过的几个优化点做完基础版之后我陆续加了一些功能这里挑几个效果最明显的说。一个是自动起点选择。以前每次测量都要手动确认第一个点后来改成测量开始时自动把当前定位坐标作为起点走一圈回到起点附近时如果距离小于5米就自动闭合多边形。这个逻辑用两点间距离公式判断double distance_between(double lat1, double lon1, double lat2, double lon2) { double dx (lon2 - lon1) * cos(lat1 * PI / 180.0) * 111000; double dy (lat2 - lat1) * 111000; return sqrt(dx * dx dy * dy); }第二个是增加了点数的统计和显示。有了点数你就能判断采样密度够不够比如一块地只有5个点那大概是绕了个三角形和矩形中间的畸形面积大概率不准。第三个是把面积计算结果加上保存功能用片内Flash存最近十次测量记录关机重启也能看到。这个功能用串口或者按键翻页就能查看对野外干活来说非常实用不用拿手机拍照记数据了。5. 实测效果与后续扩展方向5.1 田间实测数据最让我有底气的是实测数据。我在一块形状不规则的田地上做了三次测量地块实际面积用RTK设备标定是2.146亩。用这个设计的设备走一圈第一次测得2.13亩第二次2.19亩第三次2.15亩平均值2.156亩相对误差0.47%。对农用场景来说这个精度已经超过预期。还有一次测的是一块狭长地块宽不到10米这种情况本来对GPS定位误差是最敏感的因为宽度方向的偏差会把面积拉大不少。我当时的处理是走得特别慢每隔1米左右取一个点结果误差控制在3%以内虽然不如矩形地块漂亮但做农作物估产统计已经够用了。5.2 后续还能怎么扩展这个项目的平台和算法都很有扩展性。下一步我准备加一个蓝牙模块把测量结果通过蓝牙发送到手机App上手机端画轨迹图这样不仅能看到面积还能回放走的路径能看出哪些地方点采得稀。也可以用ESP8266模块把数据传到服务器后端做到地块历史数据存储和统计和源码建站那套逻辑结合起来就变成一个农业数据管理的小系统了。硬件方面如果预算充足换用带RTK解算的模块面积精度能到厘米级适合做边界纠纷仲裁之类的正式测量。算法层面也可以升级考虑地块边界曲率做平滑插值不规则的拐角可以自动拟合圆弧这样面积计算能更贴合真实边界。如果对单片机制作本身感兴趣还可以进一步优化功耗用STM32的低功耗模式让设备在待机状态只跑定时器测量时再唤醒GPS模块这样一块18650电池能撑更久。我个人在实际操作中体会最深的一点是这类项目难的不是单片机怎么点灯而是和真实世界的数据打交道。GPS定位数据有抖动、有丢失、有多路径干扰所有算法和代码都要在容错的前提下设计。每一步都问一句“如果GPS给我一个跳变到几公里外的坐标程序会不会死”把这些边界情况都堵住项目才算是真正完成了。本文还有配套的精品资源点击获取