项目概述
目标: 设计并实现一个基于STM32的智能家居控制系统,能够通过按键、手机APP或网页远程控制家中的LED灯、风扇等设备,并实时监测环境温湿度。

(图片来源网络,侵删)
核心特点:
- 本地控制: 通过物理按键直接控制设备。
- 无线远程控制: 通过Wi-Fi或蓝牙连接手机,实现远程控制。
- 状态反馈: 将设备状态(开关)和环境数据(温湿度)实时反馈到用户端。
- 可扩展性: 硬件和软件设计应易于扩展新的传感器和执行器。
核心模块与硬件选型
一个典型的智能家居系统由以下几个部分组成:
| 模块 | 功能 | 推荐硬件(基于STM32) | 备注 |
|---|---|---|---|
| 主控单元 | 大脑,处理所有逻辑和数据 | STM32F103C8T6 (Blue Pill) 或 STM32F407VGT6 (Black Pill) | F103性价比高,足够用;F407性能更强,适合运行复杂协议或带屏幕。 |
| 无线通信 | 连接手机/网络 | ESP8266-01S (Wi-Fi模块) 或 HC-05/HC-06 (蓝牙模块) | ESP8266功能强大,可做TCP服务器/客户端,适合物联网;蓝牙适合近距离控制。 |
| 传感器 | 感知环境 | DHT11/DHT22 (温湿度传感器) | DHT11便宜,DHT22精度更高。 |
| 执行器 | 控制物理设备 | 5V继电器模块 (控制灯、风扇等) | 通过STM32的GPIO控制继电器吸合/断开来开关220V设备(注意安全!)。 |
| 人机交互 | 本地操作与显示 | 按键、LED指示灯、OLED显示屏 (0.96寸 I2C) | 按键用于手动控制,LED显示设备状态,OLED可显示更多信息。 |
| 电源模块 | 提供稳定供电 | USB转TTL (5V) 或 独立5V/3.3V电源适配器 | 为整个系统供电,继电器部分可能需要独立供电。 |
硬件连接示意图
+-----------------+
| STM32 MCU |
| (e.g., F103C8T6)|
+--------+--------+
|
+-------------------+-------------------+
| | |
+-------v------+ +--------v--------+ +------v------+
| ESP8266 | | DHT11/DHT22 | | 继电器模块 |
| (Wi-Fi) | | (温湿度) | | (控制风扇) |
+-------+------+ +--------+--------+ +------+------+
| | |
| (UART: TX/RX) | (GPIO: Data) | (GPIO: IN)
+-------------------+-------------------+
|
+--------v--------+
| 按键 & LED |
| (本地控制) |
+-----------------+
软件架构与开发流程
我们采用分层架构,使代码结构清晰,易于维护和移植。
- 硬件抽象层: 直接操作STM32的寄存器或使用标准库/库函数,实现对GPIO、UART、I2C等外设的控制。
- 驱动层: 封装底层硬件,提供简单的API。
LED_On(),Relay_Toggle(),DHT11_Read()。 - 协议层: 实现设备间的通信协议,与ESP8266通过AT指令集通信,或自定义简单的应用层协议。
- 应用层: 实现核心业务逻辑,如按键处理、数据解析、状态机、定时任务等。
- 用户接口层: 处理来自本地(按键)和远程(Wi-Fi/蓝牙)的命令,并生成反馈数据。
开发流程
-
环境搭建:
(图片来源网络,侵删)- 安装 Keil MDK 或 STM32CubeIDE。
- 安装 STM32CubeMX(图形化配置工具,强烈推荐)。
- 安装 串口调试助手 (如 SSCOM, MobaXterm)。
-
基础外设配置 (使用STM32CubeMX):
- SYS: 设置
Debug为Serial Wire。 - RCC: 配置外部晶振(如果使用)。
- GPIO: 配置按键输入(上拉/下拉)、LED和继电器控制引脚为输出。
- UART: 配置与ESP8266通信的串口(如USART1),设置波特率(通常是115200)。
- I2C: 配置与OLED和DHT11(如果使用I2C版本)通信的I2C接口(如I2C1)。
- 生成代码: 生成MDK或IDE工程。
- SYS: 设置
核心代码示例
驱动层示例:DHT11温湿度读取
DHT11是单总线协议,需要精确的时序控制,这里提供一个基于HAL库的示例函数。
// dht11.h
#ifndef __DHT11_H
#define __DHT11_H
#include "main.h"
#include "stdint.h"
typedef struct {
float temperature;
float humidity;
uint8_t error;
} DHT11_Data;
void DHT11_Init(void);
DHT11_Data DHT11_ReadData(void);
#endif
// dht11.c
#include "dht11.h"
#define DHT11_PORT GPIOA
#define DHT11_PIN GPIO_PIN_8
void DHT11_Start(void) {
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_RESET); // 拉低
HAL_Delay(20); // 拉低至少18ms
HAL_GPIO_WritePin(DHT11_PORT, DHT11_PIN, GPIO_PIN_SET); // 拉高
HAL_Delay(30); // 等待DHT11响应
}
uint8_t DHT11_ReadByte(void) {
uint8_t i, data = 0;
for (i = 0; i < 8; i++) {
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET); // 等待50us高电平结束
HAL_Delay(40); // 判断高电平持续时间
if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) {
data |= (1 << (7 - i)); // 高电平持续时间较长,为1
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET); // 等待这一位结束
} else {
data &= ~(1 << (7 - i)); // 高电平持续时间较短,为0
}
}
return data;
}
DHT11_Data DHT11_ReadData(void) {
DHT11_Data data = {0};
uint8_t buf[5];
DHT11_Start();
// 等待DHT11响应
if (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET) {
data.error = 1;
return data;
}
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET);
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_RESET);
while (HAL_GPIO_ReadPin(DHT11_PORT, DHT11_PIN) == GPIO_PIN_SET);
// 读取40位数据
for (int i = 0; i < 5; i++) {
buf[i] = DHT11_ReadByte();
}
// 校验和检查
if (buf[0] + buf[1] + buf[2] + buf[3] == buf[4]) {
data.humidity = buf[0] + buf[1] / 10.0;
data.temperature = buf[2] + buf[3] / 10.0;
} else {
data.error = 2; // 校验和错误
}
return data;
}
协议层示例:通过ESP8266发送数据 (AT指令)
ESP8266工作在Station模式,连接到路由器后,作为TCP客户端向服务器发送数据。
#include "stdio.h"
#include "string.h"
#include "usart.h" // 假设已封装好串口发送函数
// AT指令发送函数
void ESP8266_SendAT(const char* cmd, uint32_t timeout) {
HAL_UART_Transmit(&huart1, (uint8_t*)cmd, strlen(cmd), timeout);
HAL_UART_Transmit(&huart1, (uint8_t*)"\r\n", 2, timeout);
}
// ESP8266初始化并连接WiFi
void ESP8266_InitAndConnect(const char* ssid, const char* password, const char* server_ip, uint16_t server_port) {
char cmd_buffer[100];
// 1. 测试AT指令
ESP8266_SendAT("AT", 100);
// 等待返回 "OK"
// 2. 设置为Station模式
ESP8266_SendAT("AT+CWMODE=1", 100);
// 3. 连接WiFi
sprintf(cmd_buffer, "AT+CWJAP=\"%s\",\"%s\"", ssid, password);
ESP8266_SendAT(cmd_buffer, 10000); // 连接可能需要较长时间
// 4. 创建TCP连接
sprintf(cmd_buffer, "AT+CIPSTART=\"TCP\",\"%s\",%d", server_ip, server_port);
ESP8266_SendAT(cmd_buffer, 2000);
}
// 发送传感器数据到服务器
void SendDataToServer(float temp, float humi) {
char data_str[50];
sprintf(data_str, "Temp: %.1fC, Humi: %.1f%%", temp, humi);
char cmd_buffer[20];
int len = strlen(data_str);
sprintf(cmd_buffer, "AT+CIPSEND=%d", len);
// 发送发送数据长度的AT指令
ESP8266_SendAT(cmd_buffer, 100);
// 等待返回 "OK"
// 发送实际数据
HAL_UART_Transmit(&huart1, (uint8_t*)data_str, len, 100);
// 等待发送完成
}
应用层示例:主循环逻辑
在 main.c 的 while(1) 循环中整合所有功能。
// main.c
// 假设这些引脚已定义
#define LED_PIN GPIO_PIN_5
#define LED_PORT GPIOA
#define RELAY_PIN GPIO_PIN_6
#define RELAY_PORT GPIOA
#define KEY_PIN GPIO_PIN_7
#define KEY_PORT GPIOA
// 全局变量
DeviceState g_DeviceState = {0}; // 定义一个结构体来存储设备状态
int main(void) {
HAL_Init();
SystemClock_Config();
MX_GPIO_Init();
MX_USART1_UART_Init(); // 与ESP8266通信
MX_I2C1_Init(); // 与OLED通信
// 初始化外设
OLED_Init();
ESP8266_InitAndConnect("YourWiFiSSID", "YourPassword", "192.168.1.100", 8080);
// 设置按键中断(可选,推荐用轮询)
// ...
while (1) {
// 1. 本地按键检测
if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) { // 假设按键按下为低电平
HAL_Delay(50); // 消抖
if (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET) {
g_DeviceState.fan_state = !g_DeviceState.fan_state;
HAL_GPIO_WritePin(RELAY_PORT, RELAY_PIN, g_DeviceState.fan_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
HAL_GPIO_WritePin(LED_PORT, LED_PIN, g_DeviceState.fan_state ? GPIO_PIN_SET : GPIO_PIN_RESET);
}
while (HAL_GPIO_ReadPin(KEY_PORT, KEY_PIN) == GPIO_PIN_RESET); // 等待按键释放
}
// 2. 读取传感器数据
DHT11_Data dht_data = DHT11_ReadData();
if (dht_data.error == 0) {
g_DeviceState.temperature = dht_data.temperature;
g_DeviceState.humidity = dht_data.humidity;
// 在OLED上显示
OLED_ShowString(0, 0, "Temp: ");
OLED_ShowNum(50, 0, (int)dht_data.temperature, 2);
OLED_ShowString(80, 0, "C");
OLED_ShowString(0, 2, "Humi: ");
OLED_ShowNum(50, 2, (int)dht_data.humidity, 2);
OLED_ShowString(80, 2, "%");
}
// 3. 通过Wi-Fi发送数据 (例如每5秒一次)
static uint32_t last_send_time = 0;
if (HAL_GetTick() - last_send_time > 5000) {
last_send_time = HAL_GetTick();
SendDataToServer(g_DeviceState.temperature, g_DeviceState.humidity);
}
HAL_Delay(100); // 主循环延时
}
}
远程控制方案
要实现手机APP或网页控制,你需要一个服务器作为中介。
方案A: ESP8266作为TCP服务器(简单)
- 工作方式: ESP8266自己创建一个TCP服务器,手机APP作为客户端连接到它。
- 优点: 不需要额外的服务器,成本低。
- 缺点: ESP8266处理能力有限,能同时连接的客户端少;手机需要知道ESP8266的公网IP(如果不在内网)。
- 实现:
- ESP8266发送
AT+CIPMUX=1(允许多连接)。 - ESP8266发送
AT+CIPSERVER=1, 8080(在端口8080上开启服务器)。 - STM32监听串口,当收到来自客户端的数据(如
FAN_ON),就解析并控制继电器。
- ESP8266发送
方案B: 自建服务器(推荐)
- 工作方式:
- ESP8266连接到你的家庭Wi-Fi。
- ESP8266作为TCP客户端,连接到你自建的服务器(可以用Python、Node.js等语言写)。
- 手机APP也连接到同一个服务器。
- STM32将数据发送给服务器,服务器再将数据广播给所有连接的手机APP,手机APP的指令也通过服务器传给STM32。
- 优点: 稳定、可扩展、支持多用户、可以实现更复杂的逻辑(如定时、场景)。
- 缺点: 需要一台一直开机的电脑或树莓派作为服务器,或使用云服务器。
方案C: 使用IoT云平台(最方便)
- 工作方式: 将设备数据上传到阿里云IoT、腾讯云IoT、Blynk、ThingSpeak等平台,这些平台提供了强大的API、数据可视化和手机APP SDK。
- 优点: 开发周期短,平台稳定,无需关心后端细节。
- 缺点: 可能有费用,数据隐私需要考虑。
未来扩展方向
- 增加更多传感器: 烟雾传感器、人体红外传感器、光照传感器、门窗磁传感器等。
- 增加更多执行器: 伺服电机(控制窗帘)、舵机、蜂鸣器等。
- 增加本地显示: 使用TFT-LCD彩屏,制作更精美的UI。
- 实现语音控制: 集成语音识别模块(如LD3320)或连接到云语音API。
- 实现自动化场景: 当检测到光照不足且有人存在时,自动开灯。
- 加入安全机制: 对远程指令进行鉴权,防止非法控制。
这个项目从简单的LED控制开始,逐步加入传感器和无线通信,最终可以演变成一个功能完善的智能家居系统,祝你学习愉快,项目成功!
