ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

STM32智能手表方案:GPS定位与GSM短信上报全链路实战

STM32智能手表方案:GPS定位与GSM短信上报全链路实战

简介:这份资源面向嵌入式硬件初学者与电子类课程设计、毕业设计学生,提供一套基于STM32单片机的智能手表完整实现方案,重点解决GSM短信上报与GPS定位联动的功能落地问题。包内共233个文件,以h头文件、c源码、jpg实物图、d依赖文件、o目标文件及doc、pdf文档为主,另含少量mp4演示视频与schdoc原理图,压缩包约77.97MB,覆盖从源码到图纸的完整链路。已有1771人学习下载,说明该方案在同类课设中具备一定参考价值。读者可获得LCD1602时钟与经纬度切换显示、电位器调清晰度、手电筒按键控制、SIM800A短信收发及GET指令主动查询定位等功能的程序源码、电路图、任务书、开题报告、参考论文、系统框图与程序流程图,并附芯片资料与器件清单,便于快速复现与二次开发。

1. 从一块能发短信的 STM32 手表说起:这套资料到底能跑出什么

去年帮一个做户外监护设备的朋友选型,他开口就要“能定位、能发短信、能戴手上、成本压到两百以内”。我第一反应不是找现成模块,而是翻出一套基于 STM32 的智能手表方案——GSM 短信上报加 GPS 定位,正好卡在这个需求点上。这套资料的核心不是做一块消费级手表,而是把“定位数据采集 → 主控处理 → GSM 短信发出”这条链路完整跑通,硬件上围绕 STM32 主控、GPS 模块、GSM 模块三块板子展开,软件上覆盖串口驱动、AT 指令交互、数据解析和主循环调度。适合谁?做单片机毕业设计的学生、想快速验证定位上报原型的嵌入式工程师、以及需要给现有设备加“短信报警”功能的从业者。它不教你画表盘 UI,也不涉及蓝牙通话,就是把“位置怎么变成一条短信”这件事拆到你能照着接线、烧录、调试的程度。下面我按实际拆包和复现的顺序,把这份资料里的技术点、参数配置和踩过的坑一条条讲清楚。

2. 硬件链路怎么搭:STM32 与 GPS、GSM 的串口分配和供电边界

2.1 三块核心模块的角色与选型逻辑

这套方案里,STM32 是大脑,GPS 模块负责输出 NMEA 语句,GSM 模块负责把整理好的位置信息以短信形式发出去。常见做法是 GPS 走 USART2,GSM 走 USART3,USART1 留给调试打印。为什么这么分?因为 GPS 模块上电后是持续吐数据的,波特率通常固定在 9600,而 GSM 模块的 AT 指令交互对时序敏感,单独占一个串口能避免 GPS 数据流把 AT 响应冲散。我一般会把 GPS 的 TX 接到 STM32 的 RX,GSM 的 TX/RX 交叉接,同时共地。供电上要特别注意:GSM 模块在发射瞬间电流能冲到 2A,如果和 STM32 共用一路 LDO,电压会被拉低导致单片机复位。血泪经验是给 GSM 单独一路 3.8V~4.2V 供电,或者至少并一个 1000uF 以上的电解电容在模块电源脚附近。

2.2 串口初始化与中断接收配置

以 HAL 库为例,先配好三个串口。GPS 串口开接收中断,GSM 串口用阻塞发送加中断接收混合模式。下面这段是 USART2 接 GPS 的初始化骨架:

// GPS串口初始化,波特率9600,8N1 huart2.Instance = USART2; huart2.Init.BaudRate = 9600; huart2.Init.WordLength = UART_WORDLENGTH_8B; huart2.Init.StopBits = UART_STOPBITS_1; huart2.Init.Parity = UART_PARITY_NONE; huart2.Init.Mode = UART_MODE_TX_RX; huart2.Init.HwFlowCtl = UART_HWCONTROL_NONE; HAL_UART_Init(&huart2); // 开启接收中断,把数据存到gps_rx_buffer HAL_UART_Receive_IT(&huart2, &gps_rx_byte, 1);

逻辑说明:这里用单字节接收中断,每收到一个字节就进一次回调,在回调里把字节塞进环形缓冲区,同时重新挂上接收中断。参数上,波特率必须和 GPS 模块出厂设置一致,常见的是 9600,也有 38400 的,拿不准就先读模块手册。GSM 串口同理,但波特率通常设 115200,因为 AT 指令交互频繁,低波特率会拖慢响应。注意:如果 GPS 模块输出的是 3.3V 电平,直接接 STM32 的 RX 没问题;如果是 5V 电平,中间要加电平转换,否则长期跑会打坏引脚。

2.3 电源管理与抗干扰布线

GSM 模块发射时不仅吃电流,还会往电源线上灌高频噪声。我一般会在 GSM 模块的 VBAT 脚并一个 100uF 钽电容加一个 0.1uF 陶瓷电容,走线尽量短粗。STM32 的 VDDA 和 VDD 之间也要加磁珠隔离,不然 ADC 读电池电压时会跳得厉害。另外,GPS 模块的天线要远离 GSM 天线,至少隔 3 厘米以上,否则 GSM 发射时 GPS 会丢星。这个坑我在第一版板子上踩过,现象是短信一发出去,GPS 定位就从 3D Fix 掉到 No Fix,后来把两个天线摆成垂直方向才解决。

3. GPS 数据怎么解析:NMEA 语句提取与经纬度转换

3.1 NMEA 语句结构与关键字段

GPS 模块上电后默认输出一堆语句,我们只关心 $GNRMC 或 $GPRMC。这条语句里包含时间、状态、纬度、南北纬、经度、东西经、速度、日期等。格式是逗号分隔,比如:

$GNRMC,083559.00,A,2232.12345,N,11405.67890,E,0.0,0.0,010123,,,A*6C

字段含义:第 1 段是 UTC 时间,第 2 段 A 表示定位有效,第 3 段是纬度原始值,第 4 段 N/S,第 5 段是经度原始值,第 6 段 E/W。注意纬度原始值是“度分”格式,2232.12345 表示 22 度 32.12345 分,要转成十进制得用 22 + 32.12345/60。经度同理。很多新手直接拿这个值当十进制用,结果定位偏到几十公里外,这是最常见的翻车点。

3.2 解析代码实现与环形缓冲区

下面这段是实际用的解析函数,从环形缓冲区里找 $GNRMC 开头到换行结束的一整条,然后按逗号切分:

// 从环形缓冲区提取一条完整NMEA语句 char* nmea_get_sentence(char* buf, uint16_t* len) { static char sentence[128]; uint16_t i = 0; // 找'$'开头 while (buf[i] != '$' && i < *len) i++; if (i >= *len) return NULL; uint16_t start = i; // 找'\n'结尾 while (buf[i] != '\n' && i < *len) i++; if (i >= *len) return NULL; uint16_t n = i - start; memcpy(sentence, &buf[start], n); sentence[n] = '\0'; // 把已读数据移出缓冲区 *len -= (i + 1); memmove(buf, &buf[i + 1], *len); return sentence; } // 解析GNRMC,提取经纬度 void parse_rmc(char* sentence, double* lat, double* lon) { char* p = sentence; uint8_t field = 0; char lat_raw[16] = {0}, lon_raw[16] = {0}; char ns = 'N', ew = 'E'; while (*p) { if (*p == ',') { field++; p++; continue; } if (field == 3) { // 纬度原始值 uint8_t k = 0; while (*p != ',' && *p) lat_raw[k++] = *p++; lat_raw[k] = '\0'; } else if (field == 4) { ns = *p; } else if (field == 5) { uint8_t k = 0; while (*p != ',' && *p) lon_raw[k++] = *p++; lon_raw[k] = '\0'; } else if (field == 6) { ew = *p; } p++; } // 度分转十进制 double lat_deg = atof(lat_raw) / 100.0; int lat_int = (int)lat_deg; *lat = lat_int + (lat_deg - lat_int) * 100.0 / 60.0; if (ns == 'S') *lat = -*lat; double lon_deg = atof(lon_raw) / 100.0; int lon_int = (int)lon_deg; *lon = lon_int + (lon_deg - lon_int) * 100.0 / 60.0; if (ew == 'W') *lon = -*lon; }

逻辑说明:nmea_get_sentence 负责从缓冲区里抠出一条完整语句,parse_rmc 按逗号分段取字段。参数上,lat_raw 和 lon_raw 存的是原始字符串,转换时先除以 100 得到“度.分”形式,再把小数部分乘 100 除以 60 转成十进制分。注意:如果定位无效(字段 2 不是 A),经纬度可能是空的,这时候要跳过,不要硬转,否则 atof 返回 0,你会得到 (0,0) 这个“几内亚湾”坐标。

3.3 定位有效性与冷启动时间

GPS 冷启动到第一次定位成功,室外开阔地一般 30 秒到 2 分钟,室内基本定不上。我一般会在代码里加一个超时判断:如果连续 120 秒没收到状态为 A 的 RMC,就通过 GSM 发一条“定位失败”的短信,而不是傻等。另外,GPS 模块的 PPS 脚可以接 STM32 的外部中断,用来做秒脉冲同步,但这不是必须的,这套资料里没用到,属于进阶玩法。

4. GSM 短信怎么发:AT 指令交互与 PDU/TEXT 模式选择

4.1 TEXT 模式与 PDU 模式的取舍

GSM 模块发短信有两种模式:TEXT 和 PDU。TEXT 模式直接发 ASCII,简单直观,但只支持英文和数字;PDU 模式要编码,支持中文但麻烦。这套方案里位置信息是经纬度数字加英文标识,用 TEXT 模式足够。常见做法是上电后先发 AT+CMGF=1 切到 TEXT 模式,再发 AT+CMGS="号码" 等模块回 '>' 后把内容写进去,最后发 0x1A 结束。注意:不同模块对 AT 指令的响应格式略有差异,有的回 OK,有的回 > 之前还有一堆状态码,所以解析响应时不要只匹配 OK,要匹配 '>' 或 'OK' 都算成功。

4.2 短信发送代码与超时重试

下面这段是发送短信的封装函数,带超时和重试:

// 发送短信,返回0成功,-1失败 int gsm_send_sms(char* phone, char* content) { char cmd[64]; uint8_t retry = 0; while (retry < 3) { // 设置TEXT模式 HAL_UART_Transmit(&huart3, (uint8_t*)"AT+CMGF=1\r\n", 11, 1000); if (wait_response("OK", 2000) != 0) { retry++; continue; } // 指定号码 sprintf(cmd, "AT+CMGS=\"%s\"\r\n", phone); HAL_UART_Transmit(&huart3, (uint8_t*)cmd, strlen(cmd), 1000); if (wait_response(">", 3000) != 0) { retry++; continue; } // 发送内容并以0x1A结束 HAL_UART_Transmit(&huart3, (uint8_t*)content, strlen(content), 1000); uint8_t end = 0x1A; HAL_UART_Transmit(&huart3, &end, 1, 1000); if (wait_response("+CMGS", 10000) == 0) { return 0; // 发送成功 } retry++; } return -1; }

逻辑说明:wait_response 是一个阻塞函数,在超时时间内不断从 GSM 串口缓冲区找目标字符串。参数上,AT+CMGF=1 的超时给 2 秒,AT+CMGS 等 '>' 给 3 秒,最后等 +CMGS 给 10 秒,因为短信实际发出需要网络交互,时间较长。注意:如果模块回的是 ERROR,常见原因是号码格式不对(要带国家码,比如 +8613800138000)或者 SIM 卡没插好。重试三次都失败,我会让代码拉低一个 LED 做指示,方便现场排查。

4.3 短信内容格式与上报频率控制

短信内容我一般拼成“LAT:22.5353,LON:114.0946,Time:0835”,简洁明了。上报频率不能太高,一条短信一毛钱,而且 GSM 模块频繁发射发热严重。常见做法是按键触发一次上报,或者定时每 10 分钟上报一次。如果要做移动报警,可以加一个震动传感器,震动触发后延时 30 秒再发,避免误报。这套资料里是按键触发,代码里用 EXTI 中断检测按键,置一个标志位,主循环里看到标志位就执行一次定位加发送。

5. 避坑与排查:从“发不出短信”到“定位漂移”的五个真实记录

5.1 现象:短信一直发不出去,串口打印显示 +CME ERROR: 10

原因:SIM 卡没插稳或者卡座接触不良。GSM 模块对 SIM 卡检测很敏感,卡座弹片氧化或者卡没插到底都会报这个错。解决:拔出来用橡皮擦擦金手指,重新插到听见“咔”一声,再上电。如果还不行,换一张能正常打电话的卡试,排除卡本身欠费或未开通短信功能。

5.2 现象:GPS 定位数据一直是空的,串口只收到 $GPGGA 没有 $GNRMC

原因:模块默认输出的语句集里可能没开 RMC,或者波特率不匹配导致乱码。解决:先用 USB 转 TTL 直接接 GPS 模块,用串口助手看原始输出,确认波特率和语句类型。如果只有 GGA,发 PMTK314 指令开启 RMC,或者直接解析 GGA 里的经纬度字段(GGA 的第 2、4 字段也是纬经度)。注意:PMTK 指令要加校验和,算错了模块不认。

5.3 现象:短信发出去了,但内容里的经纬度是 0.0000

原因:解析函数在定位无效时没有跳过,atof 对空字符串返回 0。解决:在 parse_rmc 里先判断字段 2 是否为 'A',不是就直接返回,不更新经纬度变量。另外,主循环里发短信前再检查一次经纬度是否在合理范围(纬度 -90 到 90,经度 -180 到 180),超出范围就发“定位无效”而不是发 0。

5.4 现象:设备跑几分钟就复位,尤其是发短信瞬间

原因:GSM 发射电流把电源拉垮,STM32 的 BOR 复位。解决:给 GSM 单独供电,或者在共用电源时把稳压芯片的电流余量留足(至少 2A),并在 GSM 电源脚并大电容。我一般还会在 STM32 的复位脚加一个 0.1uF 电容到地,滤掉尖峰。如果用的是锂电池直接供,注意电池内阻要小,旧电池内阻大了照样复位。

5.5 现象:GPS 定位漂移几十米,静止时经纬度一直跳

原因:正常现象,民用 GPS 水平精度就是 2.5 米到 10 米,加上多径效应,静止时跳是正常的。解决:软件上做滑动平均,连续取 5 次定位值算平均,或者用卡尔曼滤波。但注意,滑动平均会引入延迟,如果做移动报警,延迟太大会误判。我一般取 3 次平均,兼顾平滑和响应。另外,天线要放在开阔面,远离金属和人体,手表戴手上本身就会挡信号,这是物理限制,改不了。

6. 进阶技巧:用状态机重构主循环,让定位上报不再“卡死”

这套资料原始的主循环是顺序执行:读 GPS → 解析 → 发短信 → 延时。问题很明显,发短信时 wait_response 是阻塞的,这期间 GPS 数据全丢了,而且按键也响应不了。我后来用状态机重写了一遍,把整个流程拆成 IDLE、WAIT_GPS、SEND_SMS、DONE 四个状态,每个状态只做一件事,非阻塞地推进。下面是一个简化版的状态机骨架:

typedef enum { IDLE, WAIT_GPS, SEND_SMS, DONE } state_t; state_t state = IDLE; uint32_t state_timer = 0; void main_loop(void) { switch (state) { case IDLE: if (key_pressed) { state = WAIT_GPS; state_timer = HAL_GetTick(); } break; case WAIT_GPS: if (gps_valid) { state = SEND_SMS; state_timer = HAL_GetTick(); } else if (HAL_GetTick() - state_timer > 120000) { // 超时,发定位失败短信 state = SEND_SMS; gps_valid = 0; } break; case SEND_SMS: if (gsm_send_sms_nonblocking(phone, content) == 0) { state = DONE; } else if (HAL_GetTick() - state_timer > 30000) { state = DONE; // 超时也退出 } break; case DONE: state = IDLE; break; } }

逻辑说明:每个状态只检查条件,不阻塞等待。WAIT_GPS 状态里如果 120 秒没等到有效定位,就带着“定位失败”标志进 SEND_SMS。SEND_SMS 调用的是非阻塞版本,内部用状态机推进 AT 指令,每次进函数只发一条指令并检查响应,没等到就下次再进。这样主循环每毫秒跑一次,GPS 数据不丢,按键也能随时响应。参数上,120 秒和 30 秒这两个超时值可以根据实际场景调,比如室内测试可以缩短到 30 秒和 10 秒,快速验证逻辑。

验证方法:烧录后按一下按键,串口打印应该看到状态从 IDLE 跳到 WAIT_GPS,然后等 GPS 定位成功后跳到 SEND_SMS,最后回到 IDLE。如果卡在 WAIT_GPS 超过 120 秒,应该看到它跳到 SEND_SMS 并发出“定位失败”短信。用手机接收,确认内容正确。从那以后我每次做带 GSM 的项目,都强制把主循环改成状态机,再也不写 delay 了。希望帮到你。

本文还有配套的精品资源,点击获取

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进