用STM32F103玩转智能家居:低成本DIY温控报警器全攻略
用STM32F103玩转智能家居:低成本DIY温控报警器全攻略
你是否曾想过,家里的空调或风扇能根据你的生活习惯自动开关,或者在温度异常时第一时间通知你?市面上琳琅满目的智能温控产品,要么功能单一,要么价格不菲,而且数据隐私也让人隐隐担忧。作为一名电子爱好者,我始终觉得,最可靠、最贴合自己需求的智能家居设备,往往诞生于自己的工作台。今天,我想和你分享的,就是如何用一颗经典的STM32F103C8T6“蓝板”核心,搭配常见的DS18B20传感器,亲手打造一个功能远超基础测温的智能温控报警系统。它不仅能本地显示、报警,更能通过Wi-Fi将数据送上云端,实现手机远程监控,甚至联动继电器控制你的空调、风扇等大功率设备。整个过程成本极低,但获得的控制权、可玩性和成就感,却是成品设备无法比拟的。
1. 项目核心:从“采集”到“控制”的思维跃迁
传统的温度采集系统,终点往往是本地显示和蜂鸣器报警。但在智能家居的语境下,数据本身不是目的,基于数据的决策与联动控制才是价值所在。我们的项目思路需要完成一次升级:将STM32从一个孤立的“数据采集器”,转变为一个具备网络接入和物理控制能力的“智能决策终端”。
这意味着,我们需要在硬件和软件架构上,同时考虑三个层次:
- 感知层:精准、稳定地获取环境温度数据。
- 网络层:将数据可靠地传输到本地网络或云端,并接收远程指令。
- 执行层:根据预设逻辑或远程指令,驱动继电器等执行机构,改变物理环境。
基于这个框架,我们选择的STM32F103C8T6虽然是一款基础型的Cortex-M3内核MCU,但其72MHz的主频、充足的GPIO和通信接口(USART、SPI、I2C),完全足以胜任这个多任务角色。关键在于,我们要为它搭配合适的“左膀右臂”。
提示:在开始动手前,建议先准备好所有核心模块。除了STM32最小系统板和DS18B20,你还需要一个ESP-01S这类ESP8266 Wi-Fi模块用于联网,一个5V继电器模块用于控制,以及一个0.96寸OLED屏或LCD1602用于本地状态显示。这些模块在电商平台都能以非常低廉的价格购得。
2. 硬件架构设计与核心电路解析
一套稳定可靠的硬件是项目成功的基石。我们的系统可以看作由四个功能模块组成:主控、传感、人机交互、执行与联网。下面我们来逐一拆解其连接与设计要点。
2.1 主控与传感器:STM32与DS18B20的经典搭配
STM32F103C8T6最小系统板是大脑,DS18B20是感知温度的“皮肤”。DS18B20采用单总线协议,仅需一根数据线(DQ)即可与MCU通信,这大大简化了布线。但正是由于其单线特性,时序要求非常严格。
连接方式:
- DS18B20的 VDD 引脚接3.3V或5V(建议5V以获得更稳定的工作状态)。
- GND 引脚接地。
- DQ 引脚接STM32的任一GPIO(例如PA1),同时通过一个 4.7kΩ 的上拉电阻连接到VDD(3.3V或5V)。这个上拉电阻对于确保总线在空闲时处于高电平至关重要,不可省略。
驱动要点: DS18B20的通信依赖于精确的微秒级延时。在STM32上,我们通常不直接使用delay_us()进行死等,而是利用系统滴答定时器(SysTick)或通用定时器(TIM)来产生更精准的延时,以确保读写时序的可靠性。下面是一个简化的初始化与读取温度的核心代码逻辑:
// 假设DS18B20数据线连接在PA1
#define DS18B20_DQ_PIN GPIO_Pin_1
#define DS18B20_DQ_PORT GPIOA
// 复位DS18B20
uint8_t DS18B20_Reset(void) {
uint8_t presence = 0;
GPIO_SetBits(DS18B20_DQ_PORT, DS18B20_DQ_PIN); // 先拉高
DQ_OUT_MODE(); // 设置为推挽输出模式
GPIO_ResetBits(DS18B20_DQ_PORT, DS18B20_DQ_PIN); // 主机拉低总线480us~960us
delay_us(480);
GPIO_SetBits(DS18B20_DQ_PORT, DS18B20_DQ_PIN); // 释放总线,上拉电阻拉高
DQ_IN_MODE(); // 切换为浮空输入模式,等待DS18B20回应
delay_us(60);
if (!GPIO_ReadInputDataBit(DS18B20_DQ_PORT, DS18B20_DQ_PIN)) {
presence = 1; // 检测到应答脉冲
}
delay_us(480); // 等待应答脉冲结束
return presence;
}
// 读取一个字节
uint8_t DS18B20_ReadByte(void) {
uint8_t i, data = 0;
for (i=0; i<8; i++) {
data >>= 1;
DQ_OUT_MODE();
GPIO_ResetBits(DS18B20_DQ_PORT, DS18B20_DQ_PIN);
delay_us(2); // 拉低2us后立即释放
GPIO_SetBits(DS18B20_DQ_PORT, DS18B20_DQ_PIN);
DQ_IN_MODE();
delay_us(8); // 等待15us内采样
if (GPIO_ReadInputDataBit(DS18B20_DQ_PORT, DS18B20_DQ_PIN)) {
data |= 0x80;
}
delay_us(60); // 等待读时隙结束
}
return data;
}
2.2 联网桥梁:ESP8266 Wi-Fi模块的接入
要让数据“飞”起来,ESP-01S模块是性价比最高的选择。它通过串口(UART)与STM32通信,内置了完整的TCP/IP协议栈。STM32只需通过AT指令集即可命令其连接路由器、上传数据到云平台或接收服务器下发的指令。
硬件连接(以USART2为例):
| STM32引脚 | ESP-01S引脚 | 说明 |
|---|---|---|
| PA2 (USART2_TX) | RX | STM32发送,ESP8266接收 |
| PA3 (USART2_RX) | TX | STM32接收,ESP8266发送 |
| 3.3V | VCC | 供电(注意:ESP-01S必须由3.3V供电) |
| GND | GND | 共地 |
| 任一GPIO (如PA4) | CH_PD/EN | 使能引脚,接高电平(3.3V) |
| 任一GPIO (如PA5) | RST | 复位引脚,通常上拉,低电平复位 |
软件交互逻辑: STM32通过串口向ESP8266发送AT指令字符串,并解析其返回的响应。例如,连接Wi-Fi的流程如下:
- 发送
AT+CWMODE=1设置模块为Station模式。 - 发送
AT+CWJAP="你的Wi-Fi名称","你的密码"连接路由器。 - 发送
AT+CIPSTART="TCP","云服务器地址",端口号建立TCP连接。 - 发送
AT+CIPSEND=数据长度,然后发送实际数据(如JSON格式的温度数据)。
注意:ESP8266的AT指令响应通常包含“OK”或“ERROR”,以及一些信息回显。在编程时,必须做好超时处理和响应校验,否则网络不稳定时程序容易卡死。建议将网络通信部分放在一个独立的任务或状态机中处理。
2.3 执行机构:继电器模块的安全驱动
继电器模块是我们控制空调、风扇等220V交流设备的“安全开关”。市面上常见的5V继电器模块,其控制端实际上是一个光耦隔离的开关信号输入。
连接与驱动:
- 继电器模块的 VCC 和 GND 分别接5V和地。
- 控制引脚 IN 接STM32的任一GPIO(例如PA6)。
- 当STM32将此GPIO设置为低电平时,继电器常开触点吸合,电路导通;高电平时断开。
关键安全设计:
- 电气隔离:继电器模块本身提供了弱电(STM32的3.3V/5V)与强电(220V)的隔离,这是人身和设备安全的第一道防线。务必确保强电部分接线牢固,并用绝缘胶带包裹裸露部分。
- 初始状态:在STM32初始化时,务必先将控制继电器的GPIO设置为高电平(输出模式),确保继电器处于断开状态,防止上电瞬间的误动作。
- 负载选择:确认你的继电器模块触点容量(如10A 250VAC)大于你所控制设备的额定电流。控制空调压缩机等感性负载时,建议留有一倍以上的余量。
// 继电器控制示例
#define RELAY_PIN GPIO_Pin_6
#define RELAY_PORT GPIOA
void Relay_Init(void) {
GPIO_InitTypeDef GPIO_InitStructure;
RCC_APB2PeriphClockCmd(RCC_APB2Periph_GPIOA, ENABLE);
GPIO_InitStructure.GPIO_Pin = RELAY_PIN;
GPIO_InitStructure.GPIO_Mode = GPIO_Mode_Out_PP; // 推挽输出
GPIO_InitStructure.GPIO_Speed = GPIO_Speed_50MHz;
GPIO_Init(RELAY_PORT, &GPIO_InitStructure);
GPIO_SetBits(RELAY_PORT, RELAY_PIN); // 初始化设为高电平,继电器断开
}
// 打开设备(继电器吸合)
void Device_On(void) {
GPIO_ResetBits(RELAY_PORT, RELAY_PIN);
}
// 关闭设备(继电器断开)
void Device_Off(void) {
GPIO_SetBits(RELAY_PORT, RELAY_PIN);
}
3. 软件系统:多任务协同与云端通信
硬件连接好后,软件就是让整个系统“活”起来的灵魂。我们需要让温度采集、本地显示、逻辑判断、网络通信和继电器控制这几个任务有序、高效地协同工作。
3.1 系统主循环与状态机设计
对于没有使用RTOS的简单系统,一个清晰的状态机(State Machine)是管理复杂逻辑的利器。我们可以将主循环设计为按固定周期(如100ms)执行一次的状态扫描。
typedef enum {
STATE_IDLE,
STATE_READ_TEMP,
STATE_CHECK_LOGIC,
STATE_UPDATE_DISPLAY,
STATE_NETWORK_SEND,
STATE_NETWORK_RECV,
STATE_CTRL_DEVICE
} SystemState_t;
SystemState_t sysState = STATE_IDLE;
uint32_t systemTick = 0;
int main(void) {
// 初始化所有外设:时钟、GPIO、定时器、串口、传感器、显示屏等
All_Init();
while(1) {
// 每100ms执行一次主状态机
if((HAL_GetTick() - systemTick) >= 100) {
systemTick = HAL_GetTick();
switch(sysState) {
case STATE_IDLE:
sysState = STATE_READ_TEMP;
break;
case STATE_READ_TEMP:
if(DS18B20_ReadTemperature(¤tTemp) == SUCCESS) {
sysState = STATE_CHECK_LOGIC;
}
break;
case STATE_CHECK_LOGIC:
// 检查温度是否超过阈值,更新报警标志和控制逻辑
CheckTemperatureLogic();
sysState = STATE_UPDATE_DISPLAY;
break;
case STATE_UPDATE_DISPLAY:
// 更新OLED/LCD显示当前温度、阈值、网络状态等
UpdateDisplay();
sysState = STATE_NETWORK_SEND;
break;
case STATE_NETWORK_SEND:
// 非阻塞方式发送数据到云端,发送完成后立即切换状态
if(Network_SendData_NonBlocking() == DONE) {
sysState = STATE_NETWORK_RECV;
}
break;
case STATE_NETWORK_RECV:
// 检查是否有云端下发的指令(如远程开关命令)
Parse_Network_Command();
sysState = STATE_CTRL_DEVICE;
break;
case STATE_CTRL_DEVICE:
// 根据本地逻辑或云端指令,控制继电器动作
Control_Relay();
sysState = STATE_IDLE; // 回到起点,开始下一轮循环
break;
default:
sysState = STATE_IDLE;
break;
}
}
// 此处可以处理其他实时性要求高的任务,如按键扫描
Key_Scan();
}
}
这种设计使得每个功能模块都有机会在固定的周期内执行,避免了某个耗时操作(如网络发送失败等待)阻塞整个系统。Network_SendData_NonBlocking() 函数是实现非阻塞通信的关键,它内部应使用超时机制,而不是无限等待服务器响应。
3.2 云端数据交互与手机报警
数据上云是实现远程监控和报警的核心。我们可以选择免费的物联网平台,如阿里云物联网平台、OneNET或Blinker,它们都提供了简单的设备接入方式和手机App。
以发送数据到云平台为例,STM32需要组织一个结构化的数据包。目前,JSON格式是物联网领域最通用的数据交换格式。
// 构建一个简单的JSON数据字符串
// {"temp": 26.5, "humi": 45, "alert": 0}
char jsonBuffer[128];
float currentTemp = 25.6; // 从DS18B20读取的温度值
uint8_t alertFlag = 0; // 报警标志,0正常,1超温
sprintf(jsonBuffer, "{\"temp\":%.1f,\"alert\":%d}", currentTemp, alertFlag);
// 此时jsonBuffer中的内容为:{"temp":25.6,"alert":0}
// 然后通过ESP8266的串口发送AT指令,将这个JSON字符串发送到云平台指定的TCP端口
UART_SendString(USART2, "AT+CIPSEND=");
UART_SendNumber(USART2, strlen(jsonBuffer)); // 发送数据长度
UART_SendString(USART2, "\r\n");
delay_ms(100);
UART_SendString(USART2, jsonBuffer); // 发送JSON数据本身
在云平台侧,你可以设置规则引擎。例如,当收到数据中 "alert":1 时,自动触发一个“报警”事件,这个事件可以关联到“向绑定的手机App推送一条消息”,或者“发送一封邮件”,甚至“调用一个Webhook来拨打网络电话”。这样,一旦你DIY的温控器检测到温度异常,你的手机几乎能立刻收到通知。
3.3 本地逻辑与联动控制策略
云端报警是“告知”,本地联动控制则是“自治”。我们可以在STM32内部实现灵活的控制逻辑。最基本的当然是阈值控制:
#define TEMP_HIGH_THRESHOLD 30.0 // 高温阈值,单位摄氏度
#define TEMP_LOW_THRESHOLD 18.0 // 低温阈值
void CheckTemperatureLogic(void) {
static uint8_t cooling_on = 0; // 记录制冷设备(风扇/空调)状态
if (currentTemp >= TEMP_HIGH_THRESHOLD) {
// 温度过高
alertFlag = 1;
if (!cooling_on) {
Device_On(); // 打开继电器,启动风扇/空调
cooling_on = 1;
}
} else if (currentTemp <= TEMP_LOW_THRESHOLD) {
// 温度过低,如果需要,可以关闭制冷或开启加热
alertFlag = 1; // 低温报警
if (cooling_on) {
Device_Off(); // 关闭制冷设备
cooling_on = 0;
}
} else {
// 温度正常范围内
alertFlag = 0;
// 可以加入迟滞区间,防止设备在阈值附近频繁开关
if (cooling_on && currentTemp <= (TEMP_HIGH_THRESHOLD - 2.0)) {
Device_Off();
cooling_on = 0;
}
}
}
更高级的策略可以包括:
- 时间表控制:只在特定时间段(如下班回家前)启动温控。
- 自适应学习:记录每天的温度变化曲线,提前启动设备以达到舒适温度。
- 多传感器融合:结合湿度传感器,计算体感温度,实现更精准的控制。
4. 进阶优化与实战调试经验
一个能稳定运行的原型只是开始,要让其真正融入智能家居环境,还需要考虑功耗、稳定性、用户体验等细节。
4.1 低功耗设计与电源管理
如果你希望这个设备能够电池供电或长期插电但更省电,低功耗设计必不可少。STM32F103支持多种低功耗模式。
- 睡眠模式(Sleep):CPU停止,外设(如串口、定时器)仍可运行。适合在等待网络响应或定时采集间隔时进入。
- 停止模式(Stop):所有时钟停止,SRAM和寄存器内容保持。功耗极低,可由外部中断或RTC闹钟唤醒。适合在夜间等无需工作的时段使用。
实现思路:在STATE_IDLE状态,如果判断下一个采集周期还很久(比如设定为每5分钟采集一次),且没有网络活动,则让STM32进入Stop模式。通过一个基本的RTC(实时时钟)或低功耗定时器(LPTIM)来定时唤醒。ESP8266模块也可以通过AT指令(AT+GSLP)进入深度睡眠,由STM32的GPIO唤醒它。
// 进入停止模式示例(基于HAL库)
void Enter_Stop_Mode(uint32_t wakeupDelaySeconds) {
// 1. 配置一个唤醒源,比如RTC闹钟
RTC_Alarm_Set(wakeupDelaySeconds);
// 2. 关闭不必要的GPIO时钟,将IO口设置为模拟输入以减少功耗
GPIO_Analog_Config();
// 3. 设置唤醒引脚(如PA0,对应WKUP引脚)
HAL_PWR_EnableWakeUpPin(PWR_WAKEUP_PIN1);
// 4. 进入停止模式
HAL_PWR_EnterSTOPMode(PWR_LOWPOWERREGULATOR_ON, PWR_STOPENTRY_WFI);
// 系统在此处暂停...
// 5. 被唤醒后,重新初始化系统时钟(HAL库会自动处理部分)
SystemClock_Config();
// 重新初始化外设...
}
4.2 固件升级(OTA)与维护
设备部署后,如果发现bug或想增加新功能,拆下来用ST-Link烧录显然不现实。通过网络进行空中升级(OTA)是智能设备的必备能力。
对于STM32+ESP8266的方案,一个实用的OTA路径是:
- Bootloader设计:STM32上电后,先运行一小段Bootloader程序。它检查某个标志位(如Flash中的特定变量或某个GPIO的电平),决定是跳转到主应用程序,还是进入固件接收模式。
- 固件传输:手机App或服务器将新的固件文件(bin文件)通过Wi-Fi发送给ESP8266。
- 固件转发与写入:ESP8266通过串口将固件数据转发给STM32。STM32的Bootloader或应用程序将接收到的数据写入Flash中预留的“新固件存储区”。
- 校验与切换:固件接收完成后,进行CRC校验。校验通过后,修改启动标志位,然后重启。Bootloader下次启动时,就会从新的固件区启动。
虽然实现完整的OTA有一定复杂度,但你可以先从简单的“指令更新”开始,比如通过云端下发新的温度阈值、控制模式等参数,存储在STM32的Flash中,实现部分功能的远程配置。
4.3 常见问题与调试技巧
在制作过程中,你可能会遇到以下问题,这里有一些排查思路:
-
DS18B20读取失败或数据为85℃/0℃:
- 检查上拉电阻:4.7kΩ上拉电阻必须连接在DQ和VCC之间。
- 检查电源:尝试给DS18B20的VDD引脚直接供电(5V),而不是使用寄生供电模式。
- 检查时序:用逻辑分析仪或示波器抓取DQ引脚的波形,对照DS18B20的时序图,检查延时函数是否精确。STM32在不同优化等级下,延时函数的时间可能变化,建议使用硬件定时器产生精确延时。
-
ESP8266无法连接Wi-Fi或服务器:
- 检查供电:ESP8266启动瞬间电流较大,确保你的3.3V电源能提供至少500mA的电流。使用劣质USB转TTL模块或LDO可能导致供电不足。
- 检查AT指令响应:在串口助手中手动发送AT指令,确认模块响应正常。注意每条指令后需要加回车换行(
\r\n)。 - 检查网络环境:确保Wi-Fi名称和密码正确,且路由器没有设置MAC地址过滤。尝试连接手机热点进行测试。
-
继电器控制不动作或动作异常:
- 确认电平:用万用表测量继电器IN引脚电压。STM32的GPIO输出高电平约为3.3V,对于某些要求5V高电平有效的继电器模块可能不够,此时需要增加电平转换电路或选择3.3V兼容的继电器模块。
- 检查负载:确保继电器输出端连接的负载(如风扇)没有短路或过载。
最后,别忘了给你的作品一个“家”。一个3D打印的外壳不仅能保护电路,还能让整个项目看起来更专业。你可以在本地显示屏上增加更多的信息,比如室内外温差、设备运行时长、网络连接状态等。当手机收到来自你自己搭建的系统的报警信息,或者下班回家前远程打开空调,那种一切尽在掌握的体验,正是DIY智能家居最大的乐趣所在。这个基于STM32F103的温控报警器项目,就像一颗种子,你可以在此基础上嫁接更多的传感器(光照、人体红外)、执行器(舵机、调光模块),甚至引入简单的语音控制模块,让它真正成长为贴合你生活每一个细节的智能家居中枢。
更多推荐



所有评论(0)