
简介面向嵌入式与物联网开发者这是一份基于STM32的GSM短信报警系统完整设计文档。内容覆盖系统总体框图、STM32F103最小系统、SIM900A模块控制、热释电红外传感器原理及BISS0001外围电路、电源设计并给出软件初始化、中断处理、短信发送和设防/撤防等关键逻辑可帮助读者快速理解“单片机GSM红外传感”的典型报警方案。压缩包含1个PDF文档大小约821KB目录结构紧凑适合作为课程设计、毕业设计或家庭安防项目的参考。目前已有119人学习对于需要落地同类项目的开发者具有实用价值。文档还分析了实时性、远程可控、可靠性和扩展性等特点并探讨了与智能家居融合的前景能够提供从硬件选型到软件实现的全链路参考。1. 为什么用STM32GSM而不是Wi-Fi或NB-IoT做报警链路报警终端的方案评审会上总有一个问题现场有宽带为什么不用Wi-Fi跑过几个现场就明白了。库房断电时路由器先挂了农田和工地没有宽带可依赖配电柜和电梯井里信号能穿过去但网线到不了。GSM短信报警的核心价值是把传感器触发的电信号变成不依赖本地基础设施的短信而STM32在这个系统里的职责是采集、判断、控制串口和高低电平。主控选型用STM32而不是51或Arduino是因为它的外设多、功耗可控、价格透明能够用多个USART同时挂传感器、GSM模块和调试串口固件出问题也容易定位。这套方案适合做监控改造、机房动力环境监测、库房报警工程的一线工程师参考。2. 硬件选型与电路设计STM32、SIM800串口电平与电源的取舍2.1 STM32选型为什么F103C8T6在报警场景里够用报警系统的主控需求其实很收敛一个USART接GSM模块几个GPIO接传感器和继电器一个定时器做轮询RTC做时间戳。F103C8T6有3个USART、2个SPI、2个I2C、37个GPIO、64KB Flash和20KB RAM跑一套短信报警固件绰绰有余。更重要的是CubeMX生成HAL工程只需几分钟社区资料覆盖了从串口中断到低功耗Stop模式的标准写法遇到问题搜得到。如果传感器数量多或者要加蓝牙配网F103ZET6或F407也有富余资源但成本和功耗都会上升。选型时建议从串口数量倒推GSM模块占USART1调试日志占USART2如果还要接第二路传感器或读卡器再加一个USART。F103C8T6的3个USART刚好覆盖这个需求不需要为了报警项目上Cortex-M4。RAM方面20KB够用但要注意缓冲区不要随手开256字节以上还开多个环形缓冲区一般只开一个256字节给GSM另一个64字节给调试串口就足够。2.2 GSM模块选型SIM800C是默认选择串口电平要串电阻GSM短信报警场景里SIM800C几乎是默认选项。它支持四频850/900/1800/1900MHz比老款SIM900A的双频更全长期批量供货文档和例程极多。如果项目追求超小面积和低功耗M66也常见但它的串口电平是1.8V和STM32直连需要加电平转换。模块频段供电范围峰值电流串口电平适用场景SIM800C四频3.4V~4.2V2A2.8V TTL成本优先、文档全SIM900A双频3.2V~4.8V2A3.0V TTL老项目维护M66四频3.3V~4.2V1.5A1.8V TTL超小面积、低功耗接线细节常被忽略SIM800C的串口不是标准3.3V TTL而是2.8V。STM32的TX输出3.3V直接接到SIM800C的RX长期使用存在击穿风险所以ST的TX到模块的RX之间要串联1kΩ电阻限流模块的TXD输出2.8V高电平高于STM32输入高电平最低门限可以直连。如果项目要求极端可靠就加一个TXB0104电平转换芯片但大多数报警项目一个电阻就够。其他引脚安排SIM800C的PWRKEY用STM32的一个GPIO通过NPN三极管控制拉低500ms以上触发开机状态灯接模块的NETLIGHT引脚通过这个LED能直接判断模块是空闲、搜网还是在线SIM卡座选用6脚带推卡的普通卡座确保SIM卡的VCC、RST、CLK、I/O、GND、VPP全部接触到位。2.3 电源链路AMS1117喂不饱GSM必须用DC-DC加储能电容GSM模块在注册网络或短信发送瞬间会抽出最高2A的电流持续数毫秒。如果用AMS1117这类线性稳压器直接给模块供电1117的输出能力只有1A压差在输入5V输出4V时为1V满负荷下功耗接近2W芯片烫到手不能碰电压也顶不住。所以模块电源必须用DC-DC降压方案。常见做法是5V/3A适配器进板先经过MP2307或TPS5430这类降压芯片输出4V左右再在模块VBAT引脚旁边并470μF电解电容和100nF陶瓷电容。电解电容吸收GSM突发电流引起的电压跌落陶瓷电容滤高频噪声。STM32的3.3V用AMS1117从5V取电而不能从模块的4V电源取目的是隔离模块压降对单片机的干扰。如果模块发送短信时电源电压被拉低到3.4V以下模块会直接掉电重启这属于报警系统最常见的不稳定原因。2.3.1 峰值电流估算怎样确认电容容量够用峰值电流估算可以用一个简单公式模块发送脉冲功率约为2W在4V供电下电流约0.5A到1A但GSM网络支持的突发发射会让电流冲高到2A。这里需要的不能只靠电容堆容量而是先算允许的压降。假设VBAT允许从4.2V掉到3.6VΔV0.6V脉冲时长5ms按IC×ΔV/Δt反推C≈I×Δt/ΔV得到约17mF的需求这个值偏乐观。实际工程里选470μF电解加上模块内参考设计的330μF再并100nF高频电容就能把压降控制在可接受范围。如果电源板到模块的走线长还要在模块近端加钽电容或铝聚合物电容。2.4 传感器输入干接点、数字电平与内部上拉的接法传感器输入一般分三种。干接点门磁、烟感继电器输出STM32引脚内部上拉到3.3V检测到引脚被拉低就是触发。数字电平输出水浸、火焰模块输出高或低电平直接读GPIO。模拟输出气体传感器需要先经过电压比较器或STM32的ADC判断阈值。为了让多种传感器共用一套逻辑项目里一般把所有传感器信号汇总到一条公共触发线进入MCU时先串联1kΩ电阻再对地接10nF电容滤掉线缆上的工业噪声。这样设计之后固件里只需认一个GPIO的边沿或电平不再关心传感器个别类型差异。// GPIO初始化传感器输入开上拉PWRKEY设为推挽输出 GPIO_InitTypeDef gpio {0}; __HAL_RCC_GPIOA_CLK_ENABLE(); gpio.Pin SENSOR_PIN; gpio.Mode GPIO_MODE_INPUT; gpio.Pull GPIO_PULLUP; // 干接点输入默认上拉低电平触发 HAL_GPIO_Init(GPIOA, gpio); gpio.Pin PWRKEY_PIN; gpio.Mode GPIO_MODE_OUTPUT_PP; gpio.Pull GPIO_NOPULL; HAL_GPIO_Init(GPIOA, gpio);初始化GPIO时把不用的引脚统一设为模拟输入可以降低悬空引脚带来的额外功耗。传感器信号线较长时10nF电容滤波比单纯加大软件消抖更有效因为软件只能处理电平保持时间处理不了高阻线上的感生尖峰。3. 在STM32上实现GSM短信报警串口缓冲、AT指令与状态机3.1 串口接收缓冲AT响应是分包到达不能阻塞接收GSM模块对AT指令的响应不会一次性完整到达而是按UART帧分包过来。如果代码用HAL_UART_Receive阻塞等待整个主循环都会被卡住报警流程失去响应。常见做法是启用接收中断逐字节收进环形缓冲区主循环再去解析。环形缓冲区固定256字节模块的URC主动上报比如ATCRING或信号变化通知也能被缓存。// gsm_ring.c —— 环形队列缓存GSM串口数据 #define RING_SIZE 256 static uint8_t ring_buf[RING_SIZE]; static volatile uint16_t rp 0; // 读指针 static volatile uint16_t wp 0; // 写指针 void gsm_rx_irq(uint8_t byte) { uint16_t next (wp 1) % RING_SIZE; if (next rp) { return; // 队列满丢弃新字节 } ring_buf[wp] byte; wp next; } uint16_t gsm_rx_read(uint8_t *buf, uint16_t len) { uint16_t cnt 0; while ((rp ! wp) (cnt len)) { buf[cnt] ring_buf[rp]; rp (rp 1) % RING_SIZE; } return cnt; }环形队列的判满条件是写指针加一后等于读指针即队头队尾紧挨着。这里丢弃新字节而不是覆盖旧数据因为AT响应通常是先到先处理旧数据还在等待主循环解析。中断函数里只做入队操作不做任何打印和延时。HAL库下的中断接收回调如下每次收到一字节后立刻重新开启下一次接收void HAL_UART_RxCpltCallback(UART_HandleTypeDef *huart) { if (huart-Instance USART1) { gsm_rx_irq(gsm_byte); HAL_UART_Receive_IT(huart1, gsm_byte, 1); } }gsm_byte是一个全局uint8_t变量初始化时调用一次HAL_UART_Receive_IT使能接收。中断接收只填队列不解析解析逻辑放在主循环能够避免在中断里做耗时的字符串匹配操作。3.2 AT指令发送带超时、重试和期望关键字的通用函数实际项目里不需要为每条AT命令单独写收发逻辑做一个通用函数即可。函数负责发送、等待、匹配关键字、超时返回// 发送AT指令在指定超时时间内等待期望关键字返回 uint8_t gsm_at_wait_ok(const char *cmd, const char *expect, uint16_t timeout_ms) { uint8_t buf[128]; uint32_t tick_start HAL_GetTick(); memset(buf, 0, sizeof(buf)); // 先清空接收缓冲避免上一次残留数据干扰匹配 uint16_t drop; while ((drop gsm_rx_read(g_tmp, sizeof(g_tmp))) 0); if (cmd ! NULL) { HAL_UART_Transmit(huart1, (uint8_t *)cmd, strlen(cmd), 100); } while (HAL_GetTick() - tick_start timeout_ms) { uint16_t n gsm_rx_read(buf, sizeof(buf) - 1); if (n 0) { buf[n] \0; if (strstr((char *)buf, expect) ! NULL) { return 1; } } HAL_Delay(10); } return 0; }阅读这段代码时注意两点。第一发送前需要把环形缓冲里残留的数据清掉否则上一次的AT响应字符串可能被误匹配第二strstr做的是子串匹配所以expect可以传OK、、CMGS等短字符串。实际调试时把buf里的内容通过调试串口打印出来就能看到模块到底回了什么。常用AT指令和预期返回可以按下面这张表对照命令用途预期返回AT测试AT通道OKATE0关闭回显OKATCPIN?查SIM卡状态CPIN: READYATCREG?网络注册状态CREG: 0,1ATCSQ信号强度CSQ: 20,0ATCMGF短信模式选择OKATCMGS发送短信CMGS: 引用号3.3 报警状态机从触发到重试的switch实现报警逻辑天然适合状态机。一次完整的报警要经过触发检测、确认消抖、发送短信、等待确认、失败重试五个阶段。把它做成switch-case状态机主循环每次tick推进一次结构清晰且不会阻塞其他任务。typedef enum { ST_INIT, // 上电初始化验证AT通道 ST_IDLE, // 空闲巡检 ST_DEBOUNCE, // 触发确认消抖 ST_SEND, // 发送短信 ST_FAIL // 重试次数用尽 } alarm_state_t; void alarm_tick(void) { static alarm_state_t state ST_INIT; static uint32_t debounce_start 0; static uint8_t retry_cnt 0; switch (state) { case ST_INIT: if (gsm_at_wait_ok(AT\r\n, OK, 2000)) { state ST_IDLE; } else { gsm_module_reset(); // AT无响应复位模块 } break; case ST_IDLE: if (sensor_is_triggered()) { debounce_start HAL_GetTick(); state ST_DEBOUNCE; } break; case ST_DEBOUNCE: if (HAL_GetTick() - debounce_start DEBOUNCE_MS) break; if (sensor_is_triggered()) { retry_cnt 0; state ST_SEND; } else { state ST_IDLE; // 触发未保持取消报警 } break; case ST_SEND: if (gsm_send_sms_text(PHONE_ALARM, ALARM: sensor triggered!)) { state ST_IDLE; } else if (retry_cnt MAX_RETRY) { state ST_FAIL; } break; case ST_FAIL: gsm_module_reset(); // 拉低PWRKEY 500ms重新开机 state ST_INIT; break; } }这段状态机里有三个值得留意的参数。DEBOUNCE_MS一般取3000到10000毫秒太短滤不掉干接点抖动太长会让真实警情延误MAX_RETRY按网络状况设置2到5次超过后必须复位模块而不是继续傻发PHONE_ALARM保存在EEPROM或Flash里方便现场换号码而不用改程序重烧。3.4 中文短信与PDU编码UCS2码元的组织方式GSM文本模式只支持ASCII和部分扩展字符中文必须使用PDU模式并配合UCS2编码。PDU模式发送一条短信的过程有两段先发送ATCMGS长度收到提示符后再发PDU串和结束符。长度字段是指从目标号码地址字节开始算起的十六进制字节数这个位置最容易数错。中文字符转UCS2后得到2字节Unicode码点PDU串要求把每个字节转为两位十六进制。下面是把UCS2字节流转换为PDU十六进制字符串的通用函数// 将UCS2字节流转成PDU需要的十六进制字符串 uint16_t ucs2_to_pdu(const uint8_t *ucs2, uint16_t len, char *out) { const char *hex 0123456789ABCDEF; uint16_t i, idx 0; for (i 0; i len; i) { out[idx] hex[ucs2[i] 4]; out[idx] hex[ucs2[i] 0x0F]; } out[idx] \0; return idx / 2; // 返回用户数据长度单位是字节 }调用前需要把中文从UTF-8或GBK转成UCS2码点数组。STM32没有现成的系统级转码库常见做法是报警内容固定时把可能用到的中文字符串提前转码成UCS2数组存在Flash里需要拼接数字时只把数字的ASCII码填入PDU串。这样既不影响性能也不用在MCU里实现完整的Unicode转换表。3.4.1 数字拼接场景的简化做法以温度报警为例消息模板是温度过高25C其中温度过高这四个字的UCS2编码可以预先算好存储为const数组温度值25转成ASCII码2、5两字节填入PDU串的对应位置末尾的C作为ASCII字符填入。拼接完成后整个PDU串的总字节数既可以手工算出来填进ATCMGS也可以用ucs2_to_pdu函数返回的长度再加目标号码地址段长度。这里的核心原则是能预编的不要运行时编能查表的不要硬编码算法。4. GSM短信报警联调从传感器触发到短信送达的完整流程4.1 上电初始化序列与网络注册确定模块上电后不能立刻发短信必须先完成AT通道检查、SIM卡状态查询、网络注册确认。初始化顺序固定在固件开头// GSM_MODEM_Init模块上电后调用一次 MX_USART1_UART_Init(); // 9600 8N1 gsm_module_power_on(); // PWRKEY拉低500ms等待2秒 gsm_at_wait_ok(AT\r\n, OK, 2000); // 检查AT通道 gsm_at_wait_ok(ATE0\r\n, OK, 1000); // 关闭回显 gsm_at_wait_ok(ATCPIN?\r\n, READY, 3000); // 检查SIM卡PIN gsm_at_wait_ok(ATCREG?\r\n, CREG: 0,1, 10000); // 等待网络注册ATE0关闭回显很有用不然模块会把发过去的命令原样回传导致串口缓冲里混着命令和响应解析时容易被自己发的字符误匹配。ATCPIN?返回READY说明SIM卡被识别且PIN码已解除返回ERROR时先查SIM卡是否插紧或欠费。ATCREG?返回值的含义对现场排错很关键加号后面的两个数字中第一个是URC使能标志第二个是注册状态CREG返回含义处理方式0,0未注册检查天线和SIM座接触0,1已注册本地网正常0,3注册被拒绝检查SIM卡是否停机欠费0,5已注册漫游网正常可能有异地费用如果注册失败初始化流程不要直接退出而是轮询等CREG变成0,1或0,5最多等30秒。开机后第一次搜网在弱信号区域可能长达十几秒代码里超时参数从几秒起步是不现实的。4.2 信号强度查询与发送条件判断短信能否发出去信号强度比波特率重要得多。查询命令是ATCSQ返回格式为CSQ: rssi,ber第一个数值0到31表示接收信号质量第二个是误码率。联调时把这个值打印到日志串口信号质量分级如下CSQ的RSSI值信号状况是否建议发短信0~9极差不建议10~14较差可能失败建议重试15~24正常可以25~31良好可靠实际经验是CSQ低于14时短信发送成功率明显下降PWRKEY启动时间也会拉长。如果CSQ一直等于99说明信号检测失败要查天线是否接上或天线规格是否与模块匹配。4.3 触发到送达的日志观测联调时把状态机和每条AT指令的收发都打到调试串口能直观看到完整链路。下面样例日志假设调试串口映射在USART2波特率115200[11:32:05] SENSOR0, trigger detected [11:32:05] state: DEBOUNCE [11:32:10] state: DEBOUNCE pass, send sms [11:32:10] TXD: ATCMGS13800138000 [11:32:10] RXD: [11:32:10] TXD: ALARM: water leak! (0x1A) [11:32:11] RXD: CMGS: 45 [11:32:11] RXD: OK [11:32:11] state: IDLE这里看到CMGS: 45中的45是模块给这条短信分配的引用号不代表接收方已读到只表示短信被网络侧短信中心接收。如果日志停在RXD: 之后就没了多半是发了0x1A结束符之后没等到确认优先检查信号质量。4.4 报警号码的Flash存储与参数化手机号码和短信中心号码建议在初始化时从片内Flash读取而不是编译进代码。现场换卡或换主人后使用ATCMGS新号码重发一条指令就能更新不需要重刷固件。短信中心号码如果不设置多数情况下模块会直接使用SIM卡里的默认值个别网络下需要手动写ATCSCA号码可以放在菜单配置里。片内Flash读写代码的模式如下// 片内Flash读报警号码未初始化时写默认值 uint8_t phone_buf[16]; uint16_t len flash_read(ALARM_PHONE_ADDR, phone_buf, sizeof(phone_buf)); if (len 0 || phone_buf[0] 0xFF) { strcpy((char *)phone_buf, 13800138000); flash_erase_sector(ALARM_PHONE_ADDR); flash_write(ALARM_PHONE_ADDR, phone_buf, strlen((char *)phone_buf) 1); }注意Flash写入前必须先擦除扇区且擦除以扇区为单位所以号码存储区要单独划分一块。报警号码修改时先擦掉整个扇区再写别在旧数据后面追加否则地址错乱。5. 现场排错GSM模块无响应、短信乱码与误报的5个坑5.1 模块无响应先看电源再来波特率GSM模块完全无响应时排查顺序是电源、握手、波特率最后才怀疑模块损坏。用示波器或万用表在模块发送瞬间观察VBAT电压压降超过300mV基本就是电源余量不足要把输入适配器换大或者增加储能电容。另一种情况是RXD和TXD接反TX对RX、RX对TX这个在初次打样时十次里有三次会接反。波特率方面SIM800C默认9600但如果之前有人把模块配置改成了115200并保存重启后不会恢复可以用ATRESTORE恢复默认配置。5.2 注册失败或反复掉网SIM卡座是最大嫌疑网络注册失败的表现为ATCREG?返回0,0或每次都回ERROR。先查SIM卡的VCC引脚有没有稳定电源SIM800C卡座供电由模块内部LDO生成约2.8V把万用表表笔放在SIM卡座VCC引脚上直接量。很多廉价卡座弹片压力不够SIM卡放进去后某个引脚虚接表现为初始化偶尔成功偶尔失败。卡座选型时尽量选带锁定的翻盖式六个弹片都要完整不要买四弹片简配版。天线也是注册失败的高发点。GSM天线必须走线到板边缘并远离电感类器件外置天线不要用超过3米的延长线信号衰减会非常明显。5.3 短信乱码编码模式配置错了乱码问题的根源一般在ATCMGF的选择。ATCMGF1是文本模式只适合ASCII字符发送英文可以发中文收到的就是乱码或问号。中文必须用ATCMGF0进入PDU模式并使用UCS2编码。如果调试串口看到的PDU十六进制串是正常的但手机收到乱码检查模块是否被切换到GB2312编码方式SIM800系列部分固件支持ATCSCS设置字符集统一使用UCS2就不会乱。调试时还有个细节不要用串口助手的发送新行选项去发结束符0x1A很多助手默认把0x1A后面再带一个回车换行模块会认为PDU未结束而回复ERROR。5.4 误报与漏报RC滤波加软件消抖误报主要来自干接点的机械抖动和沿干扰。软件消抖只能解决电平保持问题解决不了高阻线上感应的毛刺所以GPIO输入侧必须加滤波电容。接法推荐传感器信号→1kΩ电阻→GPIO引脚同时GPIO引脚与地之间接10nF电容。这个RC时间常数约10微秒量级能把几十纳秒到几微秒的开关抖动滤掉。软件消抖时间按现场环境设3到5秒和电容配合后误报率能压得很低。漏报则相反通常是消抖时间太长或者传感器供电不足。传感器信号如果只是短暂拉低100ms就消失被消抖窗口吞掉再等下一次触发就漏报了。建议把消抖时间做成可配置参数调试时从100ms到10000ms逐步调整。5.5 模块死机不恢复独立看门狗救不了串口等待GSM模块本身有软件栈长时间运行偶发不响应是正常的关键是系统要能自动恢复。STM32的内部独立看门狗IWDG只能复位单片机不能复位GSM模块。如果程序用阻塞式等待AT响应主循环会卡在while里单片机即使被看门狗复位重启后又进入同一个死循环等于看不看门狗都一样。更可靠的做法是给AT响应加总超时比如30秒没有任何响应就通过PWRKEY触发模块冷复位然后重新走初始化序列。把复位动作放在状态机的ST_FAIL分支里程序始终有机会恢复而不是原地等待一个永远不会来的OK。6. 进阶低功耗唤醒与模块复位的可靠性设计6.1 模块断电与RTC唤醒的组合常供电场景下SIM800C待机电流约5到30mA用12V铅酸电池供电可以撑一两个月但换成锂电或太阳能供电就紧张了。最有效的降功耗手段不是调整单片机频率而是让GSM模块平时断电。用P沟道MOS管串在模块VBAT供电回路里GPIO输出低电平导通、高电平断开检测到报警时先给模块上电等启动完成后发短信。单片机本身进入Stop模式由RTC定时唤醒巡检传感器。// 进入低功耗前的调用关模块电源、挂起系统、等RTC唤醒 void system_sleep(void) { HAL_GPIO_WritePin(GSM_PWR_EN_GPIO_Port, GSM_PWR_EN_Pin, GPIO_PIN_SET); // 断开模块电源 HAL_SuspendTick(); HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI); // 唤醒后恢复系统时钟 HAL_ResumeTick(); SystemClock_Config(); }进入Stop模式前必须把GPIO设为确定电平有上拉的输入引脚要保持原有状态浮空引脚在Stop模式下会漏电使功耗下不去。GSM模块断电后模块串口引脚会反向给模块供电必须在串口线上串电阻或使用模拟开关隔离避免漏电流。6.2 短信时间戳与网络授时报警短信带上时间戳排错和追责都方便。先使用ATCCLK?\r\n读取网络时间这条命令在SIM800C上返回类似CCLK: 25/01/28,14:32:0532的字符串解析后写入STM32的RTC寄存器。RTC必须用外部32.768kHz晶体不能用内部LSI因为LSI在低温下误差大且在Stop模式下稳定性差。烧录完成后把模块网络时间与本地时间对一次误差在秒级就可以信任。6.3 天线寻优比重试更有效的信号改善手段CSQ低于10的现场不建议盲目重发短信先把天线位置调整几个角度每次调整后重新查询ATCSQ找到信号最强的固定位置。这个操作在施工文档里叫天线寻优耗时不多但对后续报警成功率影响最大。如果CSQ从7变到11短信成功率可能从一半以下升到九成以上。配合每5分钟记录一次CSQ到Flash的做法连续观测三天后就能知道这个站点规律性的信号盲区时段避开盲区调整发送策略。本文还有配套的精品资源点击获取