简介:这份资源面向嵌入式硬件与单片机方向的初学者及课程设计、毕业设计开发者,提供一套基于STM32的智能手表完整实现方案,重点解决GPS定位与GSM短信上报的联动问题。包内共233个文件,以h头文件、c源码、jpg实物图、d依赖文件、doc与pdf文档为主,另含mp4演示视频、schdoc原理图及hex固件,压缩包约77.97MB,覆盖从源码到图纸的完整链路。功能上,LCD1602可切换显示时钟日期与经纬度,GPS获取定位信息,SIM800A模块支持按键发送短信、指示灯提示发送状态,并可通过回复GET主动查询位置,同时具备手电筒与电位器调屏功能。资源还附带任务书、开题报告、参考论文、系统框图、程序流程图、芯片资料与器件清单,便于快速理解整体架构与答辩准备。目前已有1771人学习下载,适合需要完整软硬件参考的嵌入式学习者。
1. 智能手表短信上报GPS定位:从一块STM32裸板到能发坐标的完整链路
你手上可能有一块STM32最小系统板、一个SIM800C模块、一个ATGM336H GPS模块,外加一块小尺寸TFT屏。把它们拼成一块“智能手表”,核心诉求其实就一个:按下按键,手表把当前经纬度通过GSM短信发到指定手机号。听起来简单,但真正动手时你会发现,GPS冷启动要等几十秒、GSM注册网络要等十几秒、串口互相抢资源、短信中文编码还容易翻车。这套方案适合做毕业设计、课程设计或者物联网入门的第一个完整项目,也适合已经玩过51单片机、想升级到STM32做点“能拿出手”的东西的人。下面按硬件选型、GPS解析、GSM短信、主循环调度、避坑、进阶验证六步走,每一步都给出可复现的代码和参数。
2. 硬件选型与接线:为什么选STM32F103C8T6 + SIM800C + ATGM336H
2.1 主控、通信、定位三件套的取舍逻辑
STM32F103C8T6是绕不开的起点。72MHz主频、64KB Flash、20KB RAM,跑GPS解析和GSM AT指令绰绰有余,关键是资料多、价格低、HAL库和标准库都能用。如果你手头是STC或51单片机,也能做,但多串口同时收发的压力会很大,GPS和GSM各占一个串口,51很难同时伺候好。GSM模块选SIM800C而不是SIM900A,因为SIM800C体积小、功耗低、支持2G频段,短信和GPRS都够用。注意:SIM800C只支持移动和联通2G,电信卡不能用。GPS模块选ATGM336H而不是NEO-6M,因为ATGM336H支持北斗+GPS双模,冷启动时间更短,价格也差不多。
接线原则:GPS和GSM都用UART,STM32F103C8T6有3个USART,USART1接GPS,USART2接GSM,USART3留给调试打印。TFT屏用SPI接口,ILI9341驱动,读ID是0xA1A1的那个版本。供电要特别注意:SIM800C发射瞬间电流能到2A,必须单独用锂电池或者大电流LDO供电,不能直接从STM32的3.3V取电,否则一发短信就复位。
2.2 最小系统接线表与供电注意事项
| 模块 | STM32引脚 | 说明 |
|---|---|---|
| ATGM336H TX | PA10 (USART1_RX) | GPS发,STM32收 |
| ATGM336H RX | PA9 (USART1_TX) | STM32发,GPS收,一般不用 |
| SIM800C TX | PA3 (USART2_RX) | GSM发,STM32收 |
| SIM800C RX | PA2 (USART2_TX) | STM32发,GSM收 |
| SIM800C VCC | 外部4.2V锂电池 | 必须独立供电,共地 |
| ILI9341 SCK | PA5 (SPI1_SCK) | 屏幕时钟 |
| ILI9341 MOSI | PA7 (SPI1_MOSI) | 屏幕数据 |
| ILI9341 CS | PA4 | 片选 |
| ILI9341 DC | PB0 | 数据/命令切换 |
| ILI9341 RST | PB1 | 复位 |
| 按键 | PB12 | 按下触发发短信 |
供电部分单独说:SIM800C的VCC范围是3.4V~4.4V,典型4.0V。用3.7V锂电池直接供,或者用MP1584降压到4.0V。STM32和GPS用3.3V LDO供。所有模块共地。如果发现SIM800C注册不上网,先量VCC在发射瞬间有没有掉到3.4V以下。
3. GPS数据解析:从NMEA报文里抠出经纬度
3.1 NMEA-0183协议里哪些字段真正有用
ATGM336H上电后默认输出NMEA-0183报文,波特率9600。你会在串口看到一堆$GNRMC、$GNGGA、$GPGSV。真正有用的只有两条:$GNRMC包含经纬度、速度、日期;$GNGGA包含经纬度、海拔、定位质量。其他$GPGSV是卫星信息,调试时看看就行,解析时可以忽略。$GNRMC格式如下:
$GNRMC,123519.00,A,3958.12345,N,11618.54321,E,0.0,0.0,230324,,,A*7C字段含义:第1字段时间UTC,第2字段A=有效定位V=无效,第3字段纬度ddmm.mmmmm,第4字段N/S,第5字段经度dddmm.mmmmm,第6字段E/W,第7字段速度节,第9字段日期ddmmyy。注意纬度是度分格式,要转成十进制度:度 = 整数部分,分 = 小数部分,十进制 = 度 + 分/60。
3.2 用状态机解析RMC语句的完整代码
不要用strtok在中断里切字符串,容易翻车。我一般用状态机逐字节收,收到\n后置标志,主循环里解析。下面是一个可复现的解析函数:
// gps_parse.c #include <string.h> #include <stdlib.h> #include "gps_parse.h" static char rx_buf[128]; static uint8_t rx_idx = 0; static uint8_t line_ready = 0; // 在USART1中断里调用,逐字节存入 void GPS_IRQHandler(uint8_t byte) { if (byte == '\n') { rx_buf[rx_idx] = '\0'; line_ready = 1; rx_idx = 0; } else if (byte != '\r' && rx_idx < sizeof(rx_buf) - 1) { rx_buf[rx_idx++] = byte; } } // 主循环调用,返回1表示解析到有效定位 uint8_t GPS_Parse(GPS_Data_t *out) { if (!line_ready) return 0; line_ready = 0; if (strncmp(rx_buf, "$GNRMC", 6) != 0) return 0; char *p = rx_buf; char *fields[13]; uint8_t n = 0; // 按逗号切分,最多13个字段 fields[n++] = p; while (*p && n < 13) { if (*p == ',') { *p = '\0'; fields[n++] = p + 1; } p++; } if (n < 10) return 0; if (fields[2][0] != 'A') return 0; // 无效定位 // 纬度 ddmm.mmmmm -> 十进制度 float lat_raw = atof(fields[3]); int lat_deg = (int)(lat_raw / 100); float lat_min = lat_raw - lat_deg * 100; out->latitude = lat_deg + lat_min / 60.0f; if (fields[4][0] == 'S') out->latitude = -out->latitude; // 经度 dddmm.mmmmm -> 十进制度 float lon_raw = atof(fields[5]); int lon_deg = (int)(lon_raw / 100); float lon_min = lon_raw - lon_deg * 100; out->longitude = lon_deg + lon_min / 60.0f; if (fields[6][0] == 'W') out->longitude = -out->longitude; return 1; }逻辑说明:中断只负责收字节和置标志,解析放主循环,避免在中断里做浮点运算和字符串操作。fields[2][0] != 'A'判断定位是否有效,无效直接返回0。经纬度转换是重点,ddmm.mmmmm格式里前两位或三位是度,剩下的是分。参数说明:rx_buf大小128够用,RMC语句一般不超过80字节;atof需要stdlib.h;浮点运算在F103上默认用软件浮点,如果嫌慢可以改成整数运算,但短信上报对实时性要求不高,浮点够用。
3.3 冷启动、热启动与定位有效性的判断
ATGM336H冷启动(完全断电后上电)需要30~60秒才能定位,热启动(有备份电池)3~5秒。判断定位是否有效,除了看RMC的A字段,还要看GGA里的定位质量位:0=无效,1=单点定位,2=差分定位。实际使用中,如果RMC的A字段为A但经纬度是0,说明模块还没真正定位,需要继续等。我一般会在主循环里加一个超时:如果60秒内没拿到有效定位,就在屏幕上显示“GPS未定位”,而不是傻等。
4. GSM短信上报:AT指令发中文短信的完整流程
4.1 SIM800C注册网络与短信模式设置
SIM800C上电后,先等它自动注册网络。用AT指令查:
AT+CREG? // 返回+CREG: 0,1 表示已注册 AT+CSQ // 信号质量,第一个值0-31,越大越好,99表示无信号注册成功后,设置短信模式为文本模式:
AT+CMGF=1 // 1=文本模式,0=PDU模式 AT+CSCS="GSM" // 字符集,发英文用GSM,发中文用UCS2如果要发中文短信,必须用UCS2编码,并且AT+CSCS="UCS2",然后把中文转成Unicode十六进制字符串。这就是为什么很多人发中文短信翻车——直接发中文,模块不认。
4.2 发英文短信的最小代码与AT指令时序
先看英文短信,简单直接:
// gsm_sms.c #include "usart2.h" #include <string.h> #include <stdio.h> void GSM_SendSMS_En(const char *phone, const char *text) { char cmd[64]; // 设置文本模式 USART2_SendString("AT+CMGF=1\r\n"); HAL_Delay(500); // 设置目标号码 sprintf(cmd, "AT+CMGS=\"%s\"\r\n", phone); USART2_SendString(cmd); HAL_Delay(500); // 发送内容,以Ctrl+Z(0x1A)结束 USART2_SendString(text); USART2_SendByte(0x1A); HAL_Delay(3000); // 等短信发出 }逻辑说明:AT+CMGF=1设置文本模式,AT+CMGS="号码"后模块返回>提示符,然后发内容,最后发0x1A(Ctrl+Z)触发发送。参数说明:HAL_Delay(500)是给模块反应时间,实际调试时可以用串口助手看返回,如果返回>说明可以发内容了。HAL_Delay(3000)是等短信发出,SIM800C发短信一般2~5秒,3秒是保守值。注意:号码要带国家码,比如+8613800138000。
4.3 中文短信UCS2编码转换与发送
中文短信需要把GBK或UTF-8转成UCS2。STM32上资源有限,我一般用查表法或者只支持常用汉字。下面是一个简化的UCS2转换函数,假设输入是GBK编码的中文字符串:
// 将GBK中文转UCS2十六进制字符串 // 输入: "北京" (GBK: B1B1 BE A9) // 输出: "53174EAC" void GBK_To_UCS2(const char *gbk, char *ucs2_out) { // 这里用简化映射,实际项目建议用完整GBK-UCS2表 // 仅示例:北=0x5317, 京=0x4EAC const struct { uint16_t gbk; uint16_t ucs2; } table[] = { {0xB1B1, 0x5317}, // 北 {0xBE A9, 0x4EAC}, // 京 }; // 实际代码需要遍历gbk字符串,查表拼接 // 省略完整实现,重点在流程 }更可靠的做法:在PC端把中文转成UCS2十六进制字符串,直接硬编码到STM32里。比如“北京”的UCS2是53174EAC,发送时:
void GSM_SendSMS_CN(const char *phone, const char *ucs2_hex) { USART2_SendString("AT+CSCS=\"UCS2\"\r\n"); HAL_Delay(500); USART2_SendString("AT+CMGF=1\r\n"); HAL_Delay(500); char cmd[64]; sprintf(cmd, "AT+CMGS=\"%s\"\r\n", phone); USART2_SendString(cmd); HAL_Delay(500); USART2_SendString(ucs2_hex); // 例如 "53174EAC" USART2_SendByte(0x1A); HAL_Delay(3000); }参数说明:AT+CSCS="UCS2"必须在AT+CMGF=1之前或之后都行,但发内容前必须设置好。UCS2字符串是纯十六进制,不带0x前缀。如果发出去是乱码,检查UCS2字节序——SIM800C默认大端,5317对应“北”。
5. 主循环调度与低功耗:按键触发、屏幕刷新、GPS与GSM互不打架
5.1 用状态机替代delay的主循环框架
新手最容易犯的错:在GPS解析里HAL_Delay(1000)等定位,在GSM发送里HAL_Delay(3000)等短信,结果屏幕卡死、按键没反应。正确做法是用状态机+定时器,主循环里非阻塞地轮询。下面是一个可复现的框架:
// main.c typedef enum { STATE_IDLE, STATE_GPS_WAIT, STATE_SMS_SEND } SysState_t; int main(void) { HAL_Init(); SystemClock_Config(); USART1_Init(); // GPS USART2_Init(); // GSM SPI1_Init(); // TFT TFT_Init(); GPS_Init(); GSM_Init(); SysState_t state = STATE_IDLE; GPS_Data_t gps; uint32_t gps_start = 0; while (1) { // 按键触发 if (Key_Pressed() && state == STATE_IDLE) { state = STATE_GPS_WAIT; gps_start = HAL_GetTick(); TFT_ShowString("Locating..."); } // GPS解析,非阻塞 if (state == STATE_GPS_WAIT) { if (GPS_Parse(&gps)) { char msg[64]; sprintf(msg, "Lat:%.6f Lon:%.6f", gps.latitude, gps.longitude); TFT_ShowString(msg); state = STATE_SMS_SEND; } else if (HAL_GetTick() - gps_start > 60000) { TFT_ShowString("GPS Timeout"); state = STATE_IDLE; } } // 短信发送 if (state == STATE_SMS_SEND) { char sms[80]; sprintf(sms, "GPS:%.6f,%.6f", gps.latitude, gps.longitude); GSM_SendSMS_En("+8613800138000", sms); TFT_ShowString("SMS Sent"); state = STATE_IDLE; } HAL_Delay(10); // 小延时让出CPU } }逻辑说明:状态机三个状态,IDLE等按键,GPS_WAIT等定位,SMS_SEND发短信。HAL_GetTick()做超时,60秒没定位就回IDLE。参数说明:HAL_Delay(10)是主循环节拍,10ms一次,既不太快也不太慢。GPS_Parse和GSM_SendSMS内部如果有delay,要改成非阻塞或者放到状态里,这里为了简洁保留了GSM的delay,实际项目建议也改成状态机。
5.2 屏幕刷新与GPS/GSM串口冲突的处理
ILI9341用SPI,GPS和GSM用UART,硬件上不冲突。但软件上,如果GPS中断和GSM中断同时来,要注意优先级。我一般把GPS的USART1中断优先级设得比GSM的USART2高,因为GPS数据量大,丢一个字节可能整句就废了。屏幕刷新不要放在中断里,放主循环,每次刷一小块,避免长时间占用SPI。如果屏幕花屏,检查SPI时钟是不是太快,ILI9341一般不超过10MHz,先降到4MHz试试。
6. 避坑与排查:GPS定位慢、短信发不出、屏幕花屏的5个血泪经验
6.1 坑一:GPS冷启动半小时都定不了位
现象:上电后串口有NMEA数据,但RMC的A字段一直是V,经纬度是0。原因:GPS天线没接好,或者天线放在室内。ATGM336H必须接有源天线,而且天线要放在窗边或室外。解决:先确认天线供电(3.3V或5V,看模块要求),再把天线吸在窗外。如果还不行,用AT+GNSS指令查卫星数,少于4颗基本定不了。
6.2 坑二:SIM800C发短信返回ERROR
现象:发AT+CMGS后返回ERROR,或者一直不返回>。原因:号码格式不对,或者短信模式没设对。解决:号码必须带国家码,比如+8613800138000,不能只写13800138000。另外,AT+CMGF=1和AT+CSCS的顺序要对,先设模式再设字符集。如果返回+CMS ERROR: 500,说明模块没注册上网,等AT+CREG?返回0,1再发。
6.3 坑三:发中文短信对方收到乱码
现象:英文短信正常,中文短信收到一堆问号或方块。原因:没有用UCS2编码,或者UCS2字符串字节序反了。解决:AT+CSCS="UCS2"必须设,内容必须是十六进制字符串。如果收到乱码,把UCS2字符串每两个字符交换一下,比如5317改成1753试试。另外,SIM800C对UCS2长度有限制,一条短信最多70个汉字,超了要分条。
6.4 坑四:一按按键STM32就复位
现象:按键触发发短信,SIM800C一开始发射,STM32就重启。原因:SIM800C发射瞬间电流2A,把电源拉垮了。解决:SIM800C单独用锂电池供电,或者用大电流LDO(至少3A),并在VCC和GND之间并一个1000uF电解电容。STM32的3.3V和SIM800C的4.0V要共地,但电源要分开。
6.5 坑五:ILI9341读ID是0xA1A1但屏幕不亮
现象:SPI能读到ID 0xA1A1,说明通信正常,但屏幕背光亮、没内容。原因:初始化序列不对,或者复位时序不对。解决:ILI9341的RST引脚要先拉低10ms再拉高,然后等120ms再发初始化命令。初始化序列里0x11(Sleep Out)后必须等120ms,0x29(Display ON)后等50ms。如果还不行,检查DC引脚在发命令和发数据时是否正确切换。
7. 进阶验证:用串口助手模拟GPS和GSM做半实物测试
7.1 为什么要在没有硬件的情况下先跑通逻辑
GPS和GSM模块都受环境影响,调试时经常分不清是代码问题还是模块问题。我一般先用串口助手模拟GPS发NMEA语句,模拟GSM返回OK和>,把STM32的逻辑跑通,再接实物。这样能把代码bug和环境问题分开。具体做法:用PC的串口助手接STM32的USART1,手动发$GNRMC,123519.00,A,3958.12345,N,11618.54321,E,0.0,0.0,230324,,,A*7C,看STM32能不能解析出经纬度并显示在屏幕上。
7.2 用Python脚本模拟GPS数据流
下面是一个Python脚本,通过串口持续发NMEA语句,模拟GPS模块:
# gps_sim.py import serial import time ser = serial.Serial('COM3', 9600, timeout=1) # 改成你的串口号 rmc = "$GNRMC,123519.00,A,3958.12345,N,11618.54321,E,0.0,0.0,230324,,,A*7C\r\n" while True: ser.write(rmc.encode()) time.sleep(1)逻辑说明:每秒发一次RMC语句,STM32的GPS解析函数应该能稳定解析出纬度39.9687,经度116.3090。参数说明:COM3改成实际串口,波特率9600和STM32的USART1一致。如果STM32解析不出来,先检查串口线是不是TX-RX交叉接了。
7.3 验证短信发送成功的三个检查点
接上SIM800C后,怎么确认短信真的发出去了?三个检查点:第一,AT+CMGS后模块返回>,说明号码和模式都对;第二,发完0x1A后模块返回+CMGS: 15,说明短信已提交给网络;第三,目标手机收到短信。如果卡在第一步,查号码和AT+CMGF;卡在第二步,查信号质量和SIM卡余额;卡在第三步,查目标号码是不是写错了。我习惯在发短信前先发一条AT+CSQ,信号值低于10就先等等,别硬发。
最后说个习惯:每次改完代码,先用串口助手看AT指令的返回,别急着接屏幕和GPS。STM32的串口打印是你的黑匣子,printf重定向到USART3,所有关键步骤都打一行日志,出问题时一眼就能看出卡在哪。这个项目我前后做了三版,第一版用delay阻塞,第二版改状态机,第三版才把中文短信和低功耗加上。别想着一次成功,先把英文短信发出去,再搞中文,再搞屏幕,一步一步来。希望帮到你。
本文还有配套的精品资源,点击获取