简介:本资源是一套完整的基于STM32F103C8T6的智能远程浇花系统设计资料,面向嵌入式初学者、课程设计学生及单片机项目开发者,解决花卉养护中环境监测与远程干预的实际问题。系统集成DHT11温湿度采集、BH1750光照检测、LCD1602本地显示、SIM800A GSM短信远程控制(浇水/补光/数据查询)、继电器驱动与LED补光电路,支持自动阈值响应与手动短信指令双重模式。压缩包含232个文件,涵盖35个C源码、36个头文件(.h)、19个编译中间文件(.o/.d)、15个Word文档(开题报告、任务书、答辩技巧等)、12个PDF参考论文与芯片手册、7个MP4演示视频,以及原理图(.sch)、Keil工程(.uvproj)、Hex固件等,总大小84.48MB。已有1653人学习下载,资料结构完整、即拿即用,包含从硬件连接、程序逻辑、GSM通信协议解析到实操录屏的全链路支撑,特别适合课程设计、毕业设计及物联网实践项目快速落地。
1. 项目概述与需求拆解
1.1 这个花盆到底要解决什么问题
养花这件事,入门容易,坚持难。尤其是出差三五天回来,看到阳台上的绿萝蔫了、薄荷干透了、月季花苞掉了满地,那个心疼劲儿,谁养谁知道。做过嵌入式的人可能一下就想到,这不就是个自动浇花器嘛——买个定时器,设个时间,到点就浇水。说实话,我一开始也这么想,但真正动手做的时候才发现,定时浇水根本不够用。
夏天大太阳晒一天,盆土上午就干了,下午浇水已经晚了;冬天阴雨天,湿度大得很,定时器照样每天浇水,结果烂根。真正靠谱的方案,是让花盆自己“感知”土壤干不干、光照够不够,然后按需浇水、按需补光。更进一步,我还要能远程控制——人在外面,想看花盆状态、手动浇一次水、开一会儿补光灯,都能用手机搞定。
所以这个项目的完整需求就出来了:以STM32单片机作为主控,搭配土壤湿度传感器、光照传感器、温湿度传感器来感知植物生长环境,通过继电器驱动水泵补水和LED补光灯,再用GSM短信模块做远程通信,实现“短信指令远程浇水+补光”的功能。系统支持自动模式(传感器阈值触发)和手动模式(手机短信指令控制),还能发短信主动上报状态。
1.2 项目选型为什么锁定STM32
很多人看到STM32,第一反应是“杀鸡用牛刀”。实话实说,如果只是做一个定时浇水,51单片机甚至一颗555定时器都能搞定。但这个项目不一样,它牵扯到多路传感器采集、ADC转换、PWM调光、串口通信、短信指令解析、状态机调度这些任务,对主控的资源要求不是一个量级的。
STMicroelectronics出品的STM32F103C8T6,也就是大家常说的“蓝丸”板子,是我用下来最顺手的型号。它基于Cortex-M3内核,主频72MHz,带64KB Flash、20KB RAM,片上集成了2个12位ADC、多个定时器、3个USART、I2C和SPI接口。它的ADC带扫描模式和DMA,采集土壤湿度、光照强度这种缓慢变化的模拟信号非常合适,不占用CPU;PWM输出可以做补光灯无级调光;多路串口可以同时挂GPS模块、GSM模块和调试串口,这在做复杂一点的物联网终端时尤其关键。
更重要的是,STM32的HAL库和LL库这些年已经非常成熟,不再需要像早期那样寄存器一个个敲。开发效率高,网上资料也全,遇到问题基本都能搜到解决方案。对一个发烧友项目来说,能快速调通、稳定运行,比单纯堆硬件参数实在得多。
1.3 适合谁做、能学到什么
这个项目的定位是电子爱好者的进阶训练项目,适合三种人:一是学完STM32基础外设、想做个完整实战作品的学生,二是玩智能家居但不想依赖云平台的DIY爱好者,三是对植物养护有需求、想“软硬结合”解决实际问题的上班族。做完这个项目,你能把单片机开发里最难串起来的几块内容全部练一遍——传感器采集、串口通信、AT指令解析、状态机编程、电源设计、嵌入式系统调试。
考虑到GSM网络本身是一个成熟的商用网络,只要插上手机SIM卡就能收发短信,用户不需要自己搭建服务器,也不需要维护什么App。这也是我认为GSM方案在“低成本、高效率、覆盖广”这个维度上仍然不可替代的原因。
2. 通信方案选型:为什么是GSM短信而不是WiFi
2.1 WiFi、蓝牙、4G、GSM的取舍过程
确定用远程控制之后,第一个要拍板的问题就是通信方式。WiFi模块(ESP8266/ESP32)便宜又好用,连家里路由器就能联网,配合云平台(比如巴法云、阿里云物联网平台)也能实现手机远程控制。蓝牙方案成本最低,但通信距离只有十几米,出了门就废了。4G Cat.1模块性能和速度最好,但模块单价贵,还要单独买物联网卡、每年交流量费。
GSM短信方案看起来最“老土”,但恰恰是居家场景下最省心的。
我来说说选型时的心路历程。WiFi方案听着香,但有两个现实问题:第一,家里路由器重启、换网络、WiFi密码改一下,设备就掉线,老人不会弄,你又不在家,花盆就成“孤儿”了;第二,依赖云平台,平台一旦停止服务,或者免费额度用完,设备就变砖。GSM短信没有这些问题——手机卡插上就有信号,短信直接走运营商的网络,不依赖家里网络环境,也不依赖任何第三方服务器。
再说覆盖问题。GSM是运营商的蜂窝网络,在农村、地下室、户外这些WiFi覆盖不到的地方照样能收发短信。我有个熟人在一楼带小院的房子养花,WiFi信号极不稳定,但手机信号满格,GSM方案在他那里就是唯一靠谱的选择。
诚然,2G网络在部分区域已经退网,但中国移动、中国联通的2G在相当长一段时间里还会保留。如果你所在地区2G信号不好,可以考虑直接选带GSM/GPRS功能的4G模块,程序逻辑几乎不用改,只是AT指令集略有差异。
2.2 GSM模块选型与SIM卡配置细节
GSM模块市面上最常见的型号是SIM800C和SIM900A。SIM900A是早年开发板界的“标配”,价格便宜,但功耗高、体积大,而且很多模块出厂时没有FCC认证,天线做得比较随意。SIM800C是SIM900A的升级版,尺寸更紧凑,功耗也低一些,工作频段支持四频(850/900/1800/1900MHz),在这两个项目里我选的是SIM800C,实测稳定性明显优于老款。
模块的供电设计是整个硬件里最容易被忽略却最容易翻车的地方。GSM模块在注册网络和发送短信的瞬间,电流脉冲能飙到2A峰值,如果你用USB口或者AMS1117线性稳压器直接供电,瞬间压降会导致模块直接重启或者发送失败。我踩过这个坑,后来做了两层处理:第一层,GSM模块用独立的5V/3A电源适配器供电,不跟单片机共用;第二层,在模块的VCC引脚旁边并联一个1000uF的电解电容和一个100nF的陶瓷电容,作为瞬态电流的储能缓冲,实测短信发送成功率从60%提升到了99%以上。
SIM卡选择上,优先用能收发短信的普通手机卡。注意三点:一是把卡的PIN码关掉,否则模块上电后需要输入PIN码才能注册网络,你的单片机可输不了;二是确保卡里短信功能正常,有些纯流量卡不能收发短信;三是如果卡是新办的,先插手机里激活一下,确认能打电话、能发短信再装到模块上。另外,天线的位置也很关键,天线要尽量靠着外壳边缘、远离金属物体和电机,信号弱的时候收发延迟会明显变大。
2.3 短信通信的业务流程与指令设计
GSM短信的收发流程,很多人第一次接触觉得难,其实拆开了就三步:模块初始化、发送短信、接收短信。
模块初始化就是发一串AT指令,让模块完成注册网络、设置短信模式这些动作。GSM模块支持两种短信模式:Text Mode(文本模式)和PDU Mode(协议数据单元模式)。PDU模式支持中文短信,但编解码要自己写,非常麻烦。因为我的短信指令全是英文和数字(比如“WATER”“LIGHT_ON”),直接用Text模式就行,开发工作量小得多,调试也直观。
发送短信用AT+CMGS命令,指令是AT+CMGS="手机号码",收到模块返回的“>”提示符之后,再发送短信内容并以0x1A(Ctrl+Z)结束。
接收短信的模式有两种:主动查询和被动接收。主动查询是单片机定时发AT+CMGL="REC UNREAD"去读未读短信,实现简单但响应不实时;被动接收是模块收到短信后用URC(Unsolicited Result Code)主动上报,配置AT+CNMI=2,1,0,0,0之后,短信到了模块会异步给单片机发一条+CMTI: "SM",索引号的消息,单片机再按索引去读取内容。实测下来被动接收更省电,也能秒级响应,推荐用这种方式。
短信指令的设计,我按“动作+参数”的格式定了一套,项目维护起来一目了然:
| 指令 | 功能 | 示例回复 |
|---|---|---|
| STATUS | 查询植物环境状态 | SOIL:35% LIGHT:2868lux TEMP:26C - AUTO |
| WATER ON | 手动浇水60秒 | WATER ON, 60s |
| WATER OFF | 立即停止浇水 | WATER OFF |
| WATER 30 | 浇水30秒后自停 | WATER RUNNING 30s |
| LIGHT ON 60 | 补光60分钟 | LIGHT ON 60min |
| LIGHT OFF | 关闭补光 | LIGHT OFF |
| AUTO ON | 切换为自动模式 | AUTO MODE ON |
| AUTO OFF | 切换为手动模式 | AUTO MODE OFF |
这套指令看着简单,但已经涵盖了所有核心操作:查状态、手动浇灌、手动补光、模式切换。指令解析用汉明距离匹配或者等长字符串比较都能胜任,关键是要做好超时重试和异常回复的处理,这点我在第5节讲代码的时候细说。
3. 系统硬件设计与核心模块选型
3.1 系统整体框架与电路拓扑
整个系统可以分成四个层级:感知层、控制层、执行层和通信层。
感知层包括土壤湿度传感器、光照强度传感器和温湿度传感器;控制层就是STM32F103C8T6最小系统板;执行层是水泵驱动继电器、补光LED驱动电路、OLED显示屏和蜂鸣器;通信层是SIM800C GSM模块。
这几层之间的电气关系必须提前规划清楚。我的做法是分成三个独立的电源域:5V电源域给GSM模块和水泵驱动继电器供电,3.3V电源域给STM32、传感器和OLED供电,补光灯驱动单独用12V电源。三个电源域的地线在总电源输入端单点互联,避免继电器吸合瞬间的大电流在地线上产生噪声,干扰ADC采样。这个“单点接地”的细节,直接决定了模拟量采集的稳定性和整机抗干扰能力。
3.2 传感器选型与接口设计
土壤湿度传感器是水分判断的核心传感器,市面主流有电阻式和电容式两种。电阻式便宜(几块钱),但靠两根金属探针测土壤电阻,通电时间长了探针容易电解腐蚀,测出来的数据还会随土壤盐分变化漂移,不建议用在需要长期在线监测的项目里。电容式传感器(比如常见的高频电容式土壤湿度模块)通过测量土壤介电常数来换算湿度,不直接接触金属电极,寿命长、精度高,强烈推荐。
电容式传感器输出有模拟电压和数字串口(I2C/RS485)两种,家用入门用模拟输出的就够,范围0~3.3V或0~5V,接到STM32的ADC引脚即可。给传感器供电要单独用一颗LDO,别跟舵机、水泵共电源。传感器预热5分钟后读数才会稳定,启动时先延时一段时间再采集,避免误判。
光照强度传感器我用的BH1750,这是一个数字I2C接口的环境光传感器,分辨率可以到1lux,做植物补光判断绰绰有余。使用它的时候要注意光照探头要朝上安装,不能放在花盆内壁阴影处,否则测到的数据反映的是阴影而不是植物实际受光。安装位置要模拟植物叶冠受光面——我把它固定在花盆边缘的支架上,探头倾斜45度朝南,基本能代表叶面实际接收的光照。
温湿度一体传感器用的DHT11就够用了,虽然精度一般(温度±2℃,湿度±5%),但植物养护对温湿度精度要求没那么严格,关键是便宜、稳定、协议简单。如果要求高一点,可以换成SHT30,I2C接口、精度高一个档次,代码改起来也简单。
3.3 执行机构:水泵、补光灯与驱动电路
水泵的选择要考虑扬程和流量,花盆浇灌不需要大流量,一个3~5W的小型潜水泵就够。我用的泵额定电压5V、工作电流约600mA、最大扬程1米,把出水管从花盆边缘插进去,浇透一盆直径30cm的花盆大概需要60~90秒。驱动电路用继电器或者MOS管都行,推荐用MOS管,因为水泵是直流电机,MOS管开关频率高、没有机械触点,寿命长。选AO3400这种小功率NMOS就行,注意在电机两端反向并联一个1N5819肖特基二极管,吸收断电瞬间的反向电动势,不然经常烧MOS管或者复位单片机。
补光灯的选择需要对着植物需求来。植物光合作用主要需要红光(660nm附近)和蓝光(450nm附近),所以直接用红蓝光LED植物补光灯珠条。我用的是一根20W的LED灯带,由12V电源供电,配一个PWM调光模块。补光策略是大棚常用的做法:光线低于阈值时开灯,高于阈值关灯。补光时间按植物生长期调整,蔬菜苗期每天12~14小时,观花植物花期每天8~10小时就够,具体由单片机内部定时器控制,配合短信指令手动开关。
这里提个安全提醒:补光灯发热量不小,LED铝基板要贴在散热片上,功率做大之后如果只靠自然散热,灯珠寿命会急剧下降。我的做法是把补光灯架固定在花盆上方30~40cm处,这块区域加装了防溅水罩,浇水的时候不会淋到灯条。
3.4 电源系统与功耗评估
整个系统的功耗大头是GSM模块和补光灯,单片机本身功耗很小。我把供电规划成三个档位:
待机状态(仅GSM模块驻网待机、单片机低速运行):整机电流约80mA,功耗0.4W,主要由5V适配器供电。如果做成电池供电,建议用12V锂电池组方案。
工作状态(水泵浇水或补光灯满功率工作):整机电流2A以上,功耗峰值约12W,此时5V适配器电流余量至少要达到3A。如果用18650锂电池组(3节串联),配一个DC-DC降压模块给系统供电,注意降压模块的纹波不要超过100mV,否则会影响GSM模块射频性能和ADC精度。
GSM瞬间状态(短信收发瞬间):电流脉冲2A,持续时间不超过1秒,依靠电容储能缓冲,不用额外增加电源功率。
在电池供电场景下,GSM模块的省电模式很关键。SIM800C支持AT+CSCLK=1命令进入慢时钟模式,以及AT+CALD命令做定时唤醒,实测待机电流能从60mA降到10mA左右。如果做长期无人值守,建议让单片机进入STOP模式,用RTC闹钟定时唤醒,每30分钟采集一次传感器,必要时才唤醒GSM模块查询短信,这样一套10000mAh电池组可以撑一个月以上。
4. 软件架构与核心控制逻辑
4.1 主程序状态机设计
这个项目的软件逻辑如果用“顺序执行+延时”的裸奔写法,很容易写成“一坨黏糊糊的面条代码”,而且GSM模块的收发、传感器采集、执行机构控制相互之间会阻塞。我最后采用的是一个简单的主循环状态机,配合串口中断和定时器中断,做出了一个小型前后台系统。
状态机分为5个状态:初始化、待机、命令解析、执行浇水、执行补光。主循环每10ms跑一次,检查当前状态和各个标志位,然后跳转到对应处理分支。关键信号用全局标志位来传递,比如短信接收完成标志、ADC采集完成标志、按键触发标志。这样写的好处是逻辑清晰,出了问题也好定位。
伪码思路大概是:
typedef enum { SYS_INIT, SYS_IDLE, SYS_CMD_PARSE, SYS_WATERING, SYS_LIGHTING } SystemState; int main(void) { SystemState state = SYS_INIT; uint32_t lastTick = 0; Hardware_Init(); GSM_Init(); Sensor_Init(); OLED_Init(); while (1) { if (HAL_GetTick() - lastTick >= 10) { lastTick = HAL_GetTick(); state = StateMachine_Process(state); } } }待机状态下,系统要做三件事:定时刷新传感器数据、定时检查自动模式条件、查询GSM模块是否收到新短信。自动模式的浇水判断逻辑是核心——不是湿度低于阈值就莽撞浇水,还要看时间间隔,避免短时间内重复浇水导致盆土积水。我设置的是“最近一次浇水时间距现在超过30分钟,并且土壤湿度低于35%”才触发浇水。这个时间间隔可以按植物种类调整,多肉和仙人掌可以设到2小时一次,喜湿植物可以缩到20分钟。
4.2 AT指令流程与短信解析模块
GSM模块控制的核心是AT指令流程。初始化过程必须带上错误重试机制,不能一条AT指令发完就认为成功。我写了一个带超时和重试的AT_SendString函数,每次发指令后等待“OK”或“ERROR”返回值,超时时间设为2秒,失败重试3次,如果三次都失败就主动上报“GSM MODULE ERROR”。
收发短信的完整AT指令流程:
// 初始化流程 AT // 测试模块在线,返回OK ATE0 // 关闭回显 AT+CMGF=1 // 设置短信为文本模式 AT+CNMI=2,1,0,0,0 // 新短信自动上报,值为+CMTI: "SM",索引 AT+CSCLK=1 // 开启慢时钟省电模式(电池供电时启用)新短信到达后,模块会向串口发送一行加号CMTI开头的URC消息。单片机的串口接收中断里先收满一行,放到环形缓冲区,然后主循环解析。解析逻辑分两步:先提取短信内容,再和预定义的指令表比对。指令表我用字符串比较实现的,指令“STATUS”“WATER ON”“LIGHT ON 60”这些,前几个字符匹配成功就进入对应分支,数字参数用字符串转整数的函数解析。注意区分大小写,我在ParseCommand之前统一把短信内容转成了大写。
读取短信内容的流程:
AT+CMGR=索引号 // 按索引读取短信,返回内容后跟OK AT+CMGD=索引号 // 读取完立即删除,避免重复处理短信读取完一定要马上删除,这是很多人容易忽略的细节。不删除的话,同一个索引会反复触发CMTI中断,系统会一直处理同一条指令,而且SIM卡存储空间满了之后会收不到新短信。
4.3 传感器数据采集与浇水补水判断策略
ADC采集土壤湿度最忌讳的就是直接在主循环里HAL_ADC_Start+读取。ADC采样需要稳定时间,加上输入阻抗不匹配会导致采样值抖动。我用的是ADC多通道扫描+DMA模式,一次性采集土壤湿度、光照模拟值(如果光照用BH1750数字接口则不需要这一路)等通道,采100次做软件平均滤波,再按传感器标定曲线换算成百分比湿度值。
标定曲线这块得说一下。电容式土壤湿度传感器的输出不是线性的,所以出厂前要先做两点标定:把传感器插在完全干燥的土壤里,记录ADC值对应0%湿度;再把传感器插在刚刚浇透水的土壤里,记录ADC值对应100%湿度。然后做线性映射。当然这不绝对精确,但对自动浇水的阈值判断来说足够用了。不同土壤的介电常数差异很大,换土之后最好重新标定一次。
浇水执行逻辑用的是一个带超时保护的有限状态机:
typedef struct { uint8_t state; // 0=停止, 1=运行中 uint32_t start_ms; uint32_t duration_s; } WateringCtrl;当收到“WATER ON”指令或者自动模式触发浇水时,设置start_ms为当前时间、duration_s为预设时长,然后切换状态机到SYS_WATERING。主循环里每100ms检查一次,如果当前时间减去start_ms超过duration_s*1000,就立即关闭水泵。这个超时保护是保命设计,防止继电器粘连或者泵卡死导致电机长时间空转烧毁。
4.4 补光策略与PWM调光逻辑
补光分成自动和手动两个模式。手动模式直接执行短信指令的开/关和时长;自动模式用BH1750光照数据和RTC时间共同决策,避免阴天下午4点就开灯这种极端情况。光照阈值我设在2000lux,这个值对大多数室内植物够用。
补光灯的亮度控制用定时器PWM输出,频率设在20kHz,超出人耳可闻范围,也不会有LED频闪。PWM占空比通过命令“LIGHT ON 60”后面的亮度值来调整,比如占空比30%适合平时补光,100%适合阴天。PWM调光要做一个斜坡渐亮渐暗,突然100%点亮对LED灯珠寿命不友好,对植物也有“光胁迫”的刺激。
后来我加了一个很有意思的小功能——日出模拟。每天早上6点半,补光灯在15分钟内从10%亮度逐渐升到设定值,模拟自然日出。原理很简单,就是在补光状态机的循环里每隔10秒把占空比增加1%,实现了“生物光照”效果,我养的薄荷在冬天长势明显比之前好。
5. 实操过程与关键代码实现
5.1 硬件接线清单与最小系统搭建
实际操作先从硬件开始。我整理了一个接线清单,按这个接基本不会出错:
| 模块 | 引脚 | 连接到STM32 | 备注 |
|---|---|---|---|
| 土壤湿度传感器 | OUT | PA0 (ADC1_IN0) | 3.3V供电 |
| BH1750光照 | SDA | PB7 | 外部上拉4.7kΩ |
| BH1750光照 | SCL | PB6 | 外部上拉4.7kΩ |
| DHT11温湿度 | DATA | PB8 | 数据线加10kΩ上拉 |
| SIM800C | TX | PA3 (USART2_RX) | 交叉连接 |
| SIM800C | RX | PA2 (USART2_TX) | 交叉连接 |
| SIM800C | PWR_KEY | PA4 | 拉低1秒开机 |
| 继电器(水泵) | IN | PB0 | 低电平触发 |
| 补光灯PWM | IN | PA8 (TIM1_CH1) | MOSFET栅极 |
| OLED | SDA/SCL | PB7/PB6 | 与BH1750共用I2C |
STM32的USART2与GSM模块的UART是交叉连接的——STM32的TX接模块的RX,RX接模块的TX,同时一定要共地。这个地线不连,串口收发就是乱码,这是新手最容易犯的错。
SIM800C的PWR_KEY引脚要专门说下,模块默认是关机状态,需要把PWR_KEY拉低1秒以上才能开机。接法是:PWR_KEY通过一个三极管或者直接用一个IO口控制,因为模块的PWR_KEY内部有上拉,直接接单片机IO可以,但要注意电平匹配。
5.2 关键代码:GSM初始化与短信读取
GSM初始化和短信读取是项目里最需要锱铢必较的。我贴几段核心代码,都是可以直接编译跑的。先看GSM模块的初始化函数,重点是AT指令后的超时判断。
uint8_t GSM_Init(void) { uint8_t retry = 0; // 开机:PWR_KEY拉低1.2秒 HAL_GPIO_WritePin(GSM_PWR_GPIO_Port, GSM_PWR_Pin, GPIO_PIN_RESET); HAL_Delay(1200); HAL_GPIO_WritePin(GSM_PWR_GPIO_Port, GSM_PWR_Pin, GPIO_PIN_SET); while (retry < 5) { if (AT_SendCmd("AT", "OK", 2000) == AT_OK) break; retry++; } if (retry >= 5) return 0; // 模块无响应 AT_SendCmd("ATE0", "OK", 2000); // 关闭回显 AT_SendCmd("AT+CMGF=1", "OK", 2000); // 文本模式 AT_SendCmd("AT+CNMI=2,1,0,0,0", "OK", 2000); // URC上报新短信 AT_SendCmd("AT+CSCLK=1", "OK", 2000); // 省电模式 return 1; }AT_SendCmd的封装要注意一个细节:GSM模块返回的数据是带回车换行的,我们用strstr函数匹配关键字符串“OK”,同时要处理“ERROR”返回的情况。下面是个常用实现:
uint8_t AT_SendCmd(char *cmd, char *expect, uint32_t timeout_ms) { RingBuffer_Clear(&g_gsmBuf); HAL_UART_Transmit(&huart2, (uint8_t *)cmd, strlen(cmd), 100); HAL_UART_Transmit(&huart2, (uint8_t *)"\r\n", 2, 100); uint32_t start = HAL_GetTick(); while (HAL_GetTick() - start < timeout_ms) { if (strstr(g_gsmBuf.buffer, expect) != NULL) { return AT_OK; } if (strstr(g_gsmBuf.buffer, "ERROR") != NULL) { return AT_ERROR; } } return AT_TIMEOUT; }5.3 关键代码:短信内容提取与指令解析
GSM模块收到新短信后,URC消息会以+CMTI开头。串口接收中断把数据放入环形缓冲区,主循环里逐行读取,检测到+CMTI就提取索引,然后发AT+CMGR=索引去读内容。
void GSM_ProcessURC(char *line) { uint8_t index; if (strncmp(line, "+CMTI", 5) == 0) { sscanf(line, "+CMTI: \"SM\",%hhu", &index); GSM_ReadSMS(index); // 内部执行AT+CMGR并解析 GSM_DeleteSMS(index); // 读取完立即删除 } }指令解析的核心是提取短信内容里的有效部分。AT+CMGR返回的消息格式是:
+CMGR: "REC UNREAD","138xxxxxxxx","","23/10/12,10:30:00+32" WATER ON 60 OK第二行就是短信正文。解析时先定位到两个连续\r\n之间的内容,去掉首尾空白字符,然后与指令表比对:
typedef struct { char *cmd; void (*handler)(char *args); } CommandEntry; const CommandEntry cmdTable[] = { {"STATUS", Cmd_Status}, {"WATER OFF", Cmd_WaterOff}, {"LIGHT OFF", Cmd_LightOff}, {"AUTO ON", Cmd_AutoOn}, {"AUTO OFF", Cmd_AutoOff}, {"WATER", Cmd_WaterOn}, // 后跟秒数 {"LIGHT", Cmd_LightOn} // 后跟分钟数 }; void Cmd_Parse(char *msg) { for (int i = 0; i < sizeof(cmdTable) / sizeof(cmdTable[0]); i++) { if (strncmp(msg, cmdTable[i].cmd, strlen(cmdTable[i].cmd)) == 0) { cmdTable[i].handler(msg + strlen(cmdTable[i].cmd)); return; } } GSM_SendSMS(PHONE_NUMBER, "UNKNOWN CMD"); }注意解析时用的是strncmp而非strcmp,这样“WATER ON”和“WATER 30”都能匹配到同一个处理函数。推荐短信指令的回复反馈也要做到位:执行成功回复一条状态短信,执行失败回复FAILED+原因,用户在外地看到短信就知道设备状态。
5.4 关键代码:自动模式浇水状态机
自动模式的状态机是这个项目里最体现工程思维的地方,要处理好“触发→执行→恢复”的闭环。我贴一段核心逻辑:
void AutoMode_Process(void) { static uint32_t lastWaterTime = 0; uint16_t soil = Sensor_GetSoilPercent(); // 必须同时满足:土壤干、距离上次浇水超过30分钟、温度在合理范围 if (soil < SOIL_THRESHOLD && (HAL_GetTick() - lastWaterTime) > 30 * 60 * 1000 && temp > 5 && temp < 45) { Watering_Start(60); // 浇水60秒 lastWaterTime = HAL_GetTick(); GSM_SendSMS(PHONE_NUMBER, "AUTO WATER: 60s"); } // 补光:光线弱于阈值、且在白天窗口(6:00-19:00) uint16_t lux = BH1750_ReadLux(); RTC_DateTypeDef date; RTC_TimeTypeDef time; HAL_RTC_GetDate(&hrtc, &date, RTC_FORMAT_BIN); HAL_RTC_GetTime(&hrtc, &time, RTC_FORMAT_BIN); if (lux < LUX_THRESHOLD && time.Hours >= 6 && time.Hours <= 19) { Light_Start(120, 80); // 补光2小时,亮度80% } else if (lux >= LUX_THRESHOLD + 200) { Light_Stop(); } }这里有个小技巧,补光判断加了一个200lux的回差(迟滞)。如果不加回差,阴天时光线在阈值附近反复横跳,补光灯会频繁开关,对LED驱动电路和植物都不友好。加上回差之后,开关频率明显下降,系统稳定性提升很大。
回差控制在自动控制里是通用手段,比如水位控制、温度控制都会用到。做这个项目的时候养成“模型思维”的习惯,后续做其他自动化项目也能少走很多弯路。
5.5 外壳设计与现场安装要点
硬件都调通之后,外壳设计和系统安装是容易被忽略但影响长期可靠性的环节。我的做法是:供电电源和GSM模块放在一个防水盒里(IP65等级),盒盖开孔穿线后打硅胶密封;传感器和花盆之间用扎带固定,探头斜向下插入土壤,插入深度约5cm,不要碰到花盆底部的排水石;补光灯架在花盆上方,与花盆保持30~40cm距离,既能覆盖整株植物,又能防止LED长时间照射导致叶面灼伤。
GSM模块的天线要尽量伸到防水盒外部,用吸盘天线贴在阳台玻璃上,实测信号强度从-89dBm提升到-63dBm,短信回复延迟从十几秒降到了3秒以内。水泵的出水管要固定好,防止出水口位置变化把水浇到桌面或电气盒里。我还在花盆底部加了一个接水盘,溢水从出水管流回集水瓶,避免了浇水后地面到处是水的尴尬。
6. 常见问题与排查技巧实录
6.1 短信收发失败的经典排查思路
GSM模块“发不出去短信”或者“收不到短信”是最常见的故障,排查的时候不要上来就改代码,要按照信号链路顺序来。
先看模块是否注册上网络,发送AT+CREG?,返回+CREG: 0,1就说明已注册到本地网络,返回0,2表示正在搜索,0,0表示未注册。如果长时间未注册,依次排查SIM卡接触是否良好、天线是否接好、供电是否稳定。这些“硬件三件套”没问题,再查SIM卡本身能不能用。
短信发送失败还有一个隐蔽原因——短信中心号码没有配置。部分SIM卡插到模块里后不会自动同步短信中心号码,需要手动用AT+CSCA指令查询和设置,号码一般是你运营商短信中心的号码。我遇到的几次发送失败,问题都出在这里。
收不到短信时,优先确认AT+CNMI指令是否设置成功。如果模块回ERROR,说明固件版本可能不一样,换用AT+CNMI=1,1,0,0,0试试。另外,新短信到达后模块会“震铃”提醒,有些模块还需要额外配置AT+CLIP=1和AT+CLCC=1,这看具体模块固件。
6.2 串口乱码与数据跳变问题
串口乱码最直接的原因是波特率不匹配。SIM800C默认是自适应波特率(9600~115200都能接受),但我建议固定到9600,代码和硬件都按这个配置,省去一些模块自适应抖动带来的麻烦。另外,GSM模块的UART电平是2.8V逻辑的,如果模块板载没有做电平转换,连STM32的3.3V UART基本没问题,但如果模块是5V逻辑(SIM900A部分版本),就要加电平转换芯片,否则长期使用会损坏IO口。
ADC采集数据跳变,先怀疑电源。用示波器看STM32的3.3V,如果噪声超过50mV,就要检查电源和GND布线了。继电器、水泵和GSM模块的地线必须走独立的回路,在电源入口处单点汇合。传感器采集时,软件上还要加滤波,我做了“去极值平均”——每100ms采一次,采5次去掉最大值最小值取平均,跳变基本消除了。
6.3 继电器打火与单片机频繁复位
继电器控制水泵时一旦发生单片机复位,多半是感性负载的反向电动势串入电源了。排查和解决办法:
- 继电器线圈两端并联续流二极管(1N4148或1N4007),阴极接正极,阳极接负极。
- 水泵电机两端并联陶瓷电容(104)和电解电容(220uF),吸收换向火花。
- 单片机电源与继电器电源用光耦隔离,控制信号隔离之后,无论后端怎么“折腾”都不怕。
- 如果还是复位,就在STM32的VDD和GND之间并联100uF电解电容和100nF瓷片电容。
我最初版本没有加续流二极管,继电器每次吸合,OLED就闪一下白屏。加了二极管之后,问题立刻消失。这个细节在批量生产或者长时间运行项目中,一定要从一开始就做进去。
6.4 常见问题速查表
| 现象 | 可能原因 | 排查动作 | 解决方案 |
|---|---|---|---|
| GSM模块无响应 | 供电不足 | 测量模块VCC电压 | 加1000uF电容,独立电源 |
| 发送短信失败 | 未注册网络 | AT+CREG? | 检查SIM卡、天线 |
| 发送短信失败 | 短信中心未配置 | AT+CSCA? | 手动设置短信中心号码 |
| 收不到短信 | CNMI未生效 | 重发AT+CNMI | 更换URC上报模式 |
| 串口乱码 | 波特率不匹配 | 测模块返回数据 | 统一固定9600 |
| ADC读数乱跳 | 电源噪声 | 示波器看3.3V纹波 | 单点接地+滤波器 |
| 继电器吸合复位 | 反电动势干扰 | 测量复位引脚 | 加续流二极管+光耦 |
| 传感器读数漂移 | 老化或水分影响 | 对比标定值 | 定期重新标定 |
| 补光灯过热 | 散热不足 | 摸灯条温度 | 加散热片、降低占空比 |
| 水泵不转但继电器响 | 泵卡死/堵转 | 断电检查泵体 | 清理泵入口滤网 |
7. 项目复盘与进阶扩展方向
7.1 从项目里学到的几个经验
这个项目从立项到稳定运行大概花了我三个周末,前前后后改了三个版本。第一个版本是完全依赖自动模式,结果发现盆土的干湿程度并不是线性变化的,浇水频次设置不合理的后果是薄荷长得快,但另一盆的根部持续积水导致烂根。第二个版本加了短信远程控制,才算真正解决了“人不在家也能干预”的痛点。第三个版本补充了状态查询、超时保护和回差控制,稳定运行了两个月没有再出过问题。
我最想分享的经验是:嵌入式项目做到后面,最大的坑往往不在算法,而在电源和通信。软件逻辑写错了能改,硬件设计错了就得重新接线。项目一开始做个电源拓扑图、明确各模块的峰值电流,能省掉一半的排查时间。
7.2 后续还能怎么扩展
这个项目的框架可以套用到很多场景里。我自己在原有基础上拓展了两个方向:一是把GSM短信改成微信通知——用GSM模块接一个低成本的微信推送服务,把浇水记录和异常告警发到微信上;二是增加摄像头模块,定时拍一张植物照片通过彩信发送,人在外面也能直接看到植株状态。
如果想把项目做得更“高级”,可以考虑加上ESP32做本地WiFi控制,与GSM短信互为冗余——WiFi在线时用手机App或网页控制,WiFi掉线自动切回GSM短信。两套通信的切换逻辑不算复杂,核心就是状态检测和通道优先级管理。这套双通道冗余设计,在真实的物联网产品里也是常见的可靠性方案,做出来之后整个项目的技术含金量会明显上一个台阶。
8. 写在最后的几点实操心得
8.1 调试阶段的三个“神器”
做这个项目时,有三个工具帮我省了不少时间,推荐给准备开坑的朋友:第一是USB转TTL串口模块,调试GSM模块AT指令时可以直接把它和SIM800C接在一起,在电脑上用串口助手手动发指令,先把GSM模块“驯服”,再让STM32去接管;第二是逻辑分析仪,看串口数据时序的时候比示波器直观,尤其排查USART收发乱序的问题,直接在软件里看波形是最快的;第三是一块可调直流电源,带电压电流显示的那种,GSM模块瞬时电流是否能拉起来、整机功耗多少,用可调电源一看便知。
8.2 做远程控制类项目,一定要考虑“失败场景”
产品思维很重要。我做了两轮实验后最大的教训是:远程控制不是“能收到指令就万事大吉”,要考虑指令发出去但设备没执行的情况。所以我在每个指令后面都加了超时保护,水泵有最大运行时间限制,补光有最长运行限制。就算GSM模块掉线、用户发10遍指令都没有收到,设备也不会因为一条卡住的指令而烧了电机或灯珠。做嵌入式系统,可靠性设计的优先级永远要排在功能之前。
8.3 最后一个小技巧:保留调试串口
一开始我是用USART2和GSM模块通信,用USART1做调试信息输出,后来发现调试串口太重要了。建议在PCB上预留一个4pin调试座,引出USART1的TX/RX、GND和3.3V,系统的运行日志(状态切换、短信收发、传感器读数)全部打印到调试串口。这样就算设备已封装在防水盒里,你也能随时打开调试座连上串口看内部状态。日志信息建议带上时间戳,排查问题时能迅速定位是哪一分钟、哪条指令导致了异常行为。
这个项目做完,你不仅拥有一个可以远程浇水补光的智能花盆,还把嵌入式开发里最核心的几个能力——传感器采集、状态机设计、AT指令通信、电源抗干扰、系统调试——全都扎实地练了一遍。如果家里的花花草草也需要人照顾,不妨从这一步开始。
本文还有配套的精品资源,点击获取